# Tracium Cabinet — Customer Data API (AS-SHIPPED).
#
# Этот файл описывает customer-callable endpoints, которые **не** живут под
# `/api/cabinet/*` префиксом, а исторически зарегистрированы под `/api/v1/...`.
# На 2026-05-06 — единственный такой namespace: `/api/v1/credentials/*`
# (customer-scope CRUD над supplier credentials, owned subject из JWT-cookie).
#
# Auth: те же JWT-cookie + nginx auth_request, что и `/api/cabinet/*`.
# Никакого Bearer-токена `trk_*` здесь не используется (это не public API).
#
# Auto-lint отложен: `backend/internal/core/credentials/api/http/handler.go`
# совмещает RegisterAdmin и RegisterClient в одном файле, openapilint
# не умеет разделять по функции-инициатору. После split handler.go →
# admin.go + client.go — добавится make-target и paritet с кодом.
# Сейчас spec поддерживается вручную; контрактные регрессии ловятся
# Playwright e2e в `web/landing/e2e/cabinet-credentials.spec.ts`.
#
# Когда-то этот namespace должен переехать под `/api/cabinet/credentials`
# (TODO в `tracium_cabinet_credentials_execution.md` follow-up #7), но
# сейчас фиксируем status quo.

openapi: 3.1.0

info:
  title: Tracium Cabinet — Customer Data API
  version: 1.0.0
  summary: Customer self-service за пределами /api/cabinet/* префикса
  description: |
    JWT-cookie auth (тот же flow что cabinet-api). На 2026-05-06 содержит
    только customer-scope credentials CRUD. См. ADR-0041 для модели
    credentials, `tracium_cabinet_credentials_execution.md` memory для
    UX-flow.

servers:
  - url: https://tracium.dev/api/v1
    description: Production

tags:
  - name: customer-credentials
    description: Управление supplier credentials customer'а из cabinet UI

paths:
  /credentials:
    get:
      tags: [customer-credentials]
      operationId: listCustomerCredentials
      summary: Список credentials текущего customer'а
      security: [{cookieSession: []}]
      parameters:
        - in: query
          name: status
          schema: { type: string, enum: [active, inactive, revoked] }
        - in: query
          name: supplier_ref
          schema: { type: string }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
        - in: query
          name: offset
          schema: { type: integer, minimum: 0, default: 0 }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [items, total]
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/CustomerCredential' }
                  total: { type: integer }
        '401': { description: Unauthorized }
    post:
      tags: [customer-credentials]
      operationId: createCustomerCredential
      summary: Зарегистрировать новый credential поставщика
      description: |
        SupplierRef должен быть из catalog'а
        (`GET /api/cabinet/credentials/suppliers/catalog`). secret_value —
        opaque JSON, формат проверяется connector'ом поставщика.
      security: [{cookieSession: []}]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCustomerCredentialRequest'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CustomerCredential' }
        '400': { description: Validation failed }
        '401': { description: Unauthorized }

  /credentials/{id}:
    parameters:
      - $ref: '#/components/parameters/CredentialID'
    get:
      tags: [customer-credentials]
      operationId: getCustomerCredential
      summary: Получить credential по id (только если принадлежит customer'у)
      security: [{cookieSession: []}]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CustomerCredential' }
        '401': { description: Unauthorized }
        '404': { description: Not found или принадлежит другому customer'у }

  /credentials/{id}/secret:
    parameters:
      - $ref: '#/components/parameters/CredentialID'
    patch:
      tags: [customer-credentials]
      operationId: rotateCustomerCredentialSecret
      summary: Ротация secret payload (без изменения metadata)
      security: [{cookieSession: []}]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [secret_value]
              properties:
                secret_value:
                  type: string
                  description: Новый opaque JSON
                reason:
                  type: string
                  description: Audit-trail reason
      responses:
        '204': { description: Rotated }
        '401': { description: Unauthorized }
        '404': { description: Not found }

  /credentials/{id}/status:
    parameters:
      - $ref: '#/components/parameters/CredentialID'
    patch:
      tags: [customer-credentials]
      operationId: transitionCustomerCredentialStatus
      summary: Перевод status (active ↔ inactive); revoke зарезервирован за admin
      security: [{cookieSession: []}]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status:
                  type: string
                  enum: [active, inactive]
                reason:
                  type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CustomerCredential' }
        '400': { description: Invalid transition }
        '401': { description: Unauthorized }
        '404': { description: Not found }

components:
  parameters:
    CredentialID:
      in: path
      name: id
      required: true
      schema: { type: string, format: uuid }

  securitySchemes:
    cookieSession:
      type: apiKey
      in: cookie
      name: tracium_session
      description: |
        JWT-cookie сессия. Валидируется nginx `auth_request /_internal/auth/jwt/verify`
        (тот же flow что у `/api/cabinet/*`).

  schemas:
    CustomerCredential:
      type: object
      required: [id, supplier_ref, scope, status, allowed_usages, rate_limit, created_at, updated_at, version]
      properties:
        id: { type: string, format: uuid }
        supplier_ref:
          type: string
          description: Один из catalog suppliers (etm/iek/systeme/dkc/russvet)
        scope:
          type: string
          enum: [customer]
          description: Customer-scope; system-credentials выдаются admin'ом
        status:
          type: string
          enum: [active, inactive, revoked]
        allowed_usages:
          type: array
          items:
            type: string
            enum: [customer_proposal, price_refresh, stock_refresh, diagnostic_probe]
        rate_limit:
          type: object
          required: [requests_per_minute, requests_per_day, burst_allowance]
          properties:
            requests_per_minute: { type: integer, minimum: 0 }
            requests_per_day: { type: integer, minimum: 0 }
            burst_allowance: { type: integer, minimum: 0 }
        valid_from:
          type: string
          format: date-time
          nullable: true
        valid_to:
          type: string
          format: date-time
          nullable: true
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        version:
          type: integer
          description: Optimistic-concurrency токен
        last_check_at:
          type: string
          format: date-time
          nullable: true
          description: Health follow-up — сейчас всегда null (см. follow-ups)
        last_error:
          type: string
          nullable: true
          description: Health follow-up — сейчас всегда null

    CreateCustomerCredentialRequest:
      type: object
      required: [supplier_ref, status, allowed_usages, rate_limit, secret_value]
      properties:
        supplier_ref: { type: string }
        status:
          type: string
          enum: [active, inactive]
          default: active
        allowed_usages:
          type: array
          minItems: 1
          items:
            type: string
            enum: [customer_proposal, price_refresh, stock_refresh, diagnostic_probe]
        rate_limit:
          type: object
          properties:
            requests_per_minute: { type: integer, minimum: 0 }
            requests_per_day: { type: integer, minimum: 0 }
            burst_allowance: { type: integer, minimum: 0 }
        secret_value:
          type: string
          description: Opaque JSON; формат зависит от auth_scheme поставщика
        valid_from:
          type: string
          format: date-time
          nullable: true
        valid_to:
          type: string
          format: date-time
          nullable: true
        reason:
          type: string
          description: Audit-trail reason
