# Tracium Admin API — TARGET contract (aspirational, NOT linted).
#
# Full intended surface after every BC ships. Used as the roadmap /
# design reference only. `admin-api.openapi.yaml` is the as-shipped
# spec that CI openapilint enforces against Go handlers. Keep this
# file in sync when adding new target endpoints; drop entries here
# once they're implemented and mirrored into admin-api.openapi.yaml.
#
# Связанные ADR: ADR-0009 (multi-tenant credentials), ADR-0012
# (visibility policy), ADR-0016 (extensible kind dictionaries),
# ADR-0018 (master key + DEK), ADR-0024 (connector contract),
# ADR-0027 (HTML scraper), ADR-0028 (federated taxonomies),
# ADR-0029 (revision-based sync).

openapi: 3.1.0
info:
  title: Tracium Admin API
  version: 1.0.0-target
  summary: Админский API Tracium (модерация, dictionary, connectors, policies)
  description: |
    Авторизация — Bearer JWT от shared/auth + OIDC, требуются роли
    `admin` / `moderator` / `security` (зависит от endpoint).

    Каждый мутирующий endpoint пишет audit-событие (ADR-0022). Сенситивные
    операции (break-glass, revert policy, disable moderation agent)
    дополнительно требуют 4-eyes подтверждения.
  contact:
    name: Tracium API Team
servers:
  - url: https://admin.tracium.ru/v1/admin
    description: Production
  - url: https://admin.staging.tracium.ru/v1/admin
    description: Staging
  - url: https://api.tracium.dev/v1/admin
    description: Local dev

security:
  - bearerAuth: []
  - oidc: []

tags:
  - name: canonical-products
  - name: identity-profiles
  - name: identity-standards
  - name: characteristics
  - name: units
  - name: manufacturers
  - name: suppliers
  - name: relationships
  - name: connectors
  - name: offers
  - name: credentials
  - name: customers
  - name: visibility-policies
  - name: moderation
  - name: price-rules
  - name: pricing-handlers
  - name: enrichment
  - name: search
  - name: estimates
  - name: plans
    description: Сетка тарифов и пакеты запросов (CRUD)
  - name: subscriptions
    description: Управление подписками клиентов
  - name: usage-analytics
    description: Аналитика использования API (per-customer, per-endpoint)
  - name: billing
    description: Счета, платежи, refunds
  - name: organizations
    description: Управление организациями (B2B контейнеры)

paths:
  # ---------------------------------------------------------------------------
  # Canonical products
  # ---------------------------------------------------------------------------
  /canonical-products:
    post:
      tags: [canonical-products]
      operationId: createCanonicalProduct
      summary: Создать canonical product (редко; обычно создаёт Matching)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CanonicalProductCreate' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminCanonicalProduct' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '409': { $ref: '#/components/responses/Conflict' }

  /canonical-products/{id}:
    get:
      tags: [canonical-products]
      operationId: getCanonicalProduct
      summary: Получить canonical (admin view с internal fields)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminCanonicalProduct' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [canonical-products]
      operationId: updateCanonicalProduct
      summary: Ручная правка canonical (characteristics / media / aliases)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AdminCanonicalProductPatch' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminCanonicalProduct' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /canonical-products/{id}/media:
    put:
      tags: [canonical-products]
      operationId: replaceCanonicalMedia
      summary: Заменить медиа-набор canonical
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                media:
                  type: array
                  items: { $ref: '#/components/schemas/MediaAsset' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminCanonicalProduct' }
        '404': { $ref: '#/components/responses/NotFound' }

  /canonical-products/{id}/lifecycle:
    put:
      tags: [canonical-products]
      operationId: updateCanonicalLifecycle
      summary: Изменить lifecycle status
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status: { $ref: '#/components/schemas/LifecycleStatus' }
                reason: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminCanonicalProduct' }
        '404': { $ref: '#/components/responses/NotFound' }

  /canonical-products/{id}/replaced-by:
    post:
      tags: [canonical-products]
      operationId: setCanonicalReplacement
      summary: Установить replaced_by (preemник)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [replaced_by]
              properties:
                replaced_by: { type: string, format: uuid }
                reason: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminCanonicalProduct' }
        '404': { $ref: '#/components/responses/NotFound' }

  # ---------------------------------------------------------------------------
  # Identity profiles & standards (dictionary)
  # ---------------------------------------------------------------------------
  /identity-profiles:
    get:
      tags: [identity-profiles]
      operationId: listIdentityProfiles
      summary: Список identity profiles
      parameters:
        - { name: kind, in: query, schema: { type: string } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200': { $ref: '#/components/responses/IdentityProfilePage' }
    post:
      tags: [identity-profiles]
      operationId: createIdentityProfile
      summary: Создать identity profile
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/IdentityProfileCreate' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/IdentityProfile' }
        '400': { $ref: '#/components/responses/BadRequest' }

  /identity-profiles/{id}:
    get:
      tags: [identity-profiles]
      operationId: getIdentityProfile
      summary: Получить identity profile
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/IdentityProfile' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [identity-profiles]
      operationId: updateIdentityProfile
      summary: Обновить identity profile
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/IdentityProfileUpdate' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/IdentityProfile' }
        '404': { $ref: '#/components/responses/NotFound' }

  /identity-standards:
    get:
      tags: [identity-standards]
      operationId: listIdentityStandards
      summary: Federated taxonomies (ETIM / eCl@ss / GS1 / OKPD2 / MPN)
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/IdentityStandard' }

  /characteristic-mappings:
    get:
      tags: [characteristics]
      operationId: listCharacteristicMappings
      summary: Mapping supplier codes → canonical characteristics
      parameters:
        - { name: supplier_id, in: query, schema: { type: string } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CharacteristicMappingPage' }
    post:
      tags: [characteristics]
      operationId: createCharacteristicMapping
      summary: Создать mapping
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CharacteristicMapping' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CharacteristicMapping' }
        '400': { $ref: '#/components/responses/BadRequest' }

  /classification-mappings:
    get:
      tags: [characteristics]
      operationId: listClassificationMappings
      summary: Mapping supplier classification → internal tags
      parameters:
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/ClassificationMapping' }
                  next_cursor: { type: [string, 'null'] }
    post:
      tags: [characteristics]
      operationId: createClassificationMapping
      summary: Создать classification mapping
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ClassificationMapping' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ClassificationMapping' }

  /manufacturer-mappings:
    get:
      tags: [manufacturers]
      operationId: listManufacturerMappings
      summary: Manufacturer aliases / canonical mapping
      parameters:
        - { name: manufacturer_id, in: query, schema: { type: string } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/ManufacturerMapping' }
                  next_cursor: { type: [string, 'null'] }
    post:
      tags: [manufacturers]
      operationId: createManufacturerMapping
      summary: Добавить manufacturer alias
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ManufacturerMapping' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ManufacturerMapping' }

  /characteristics:
    get:
      tags: [characteristics]
      operationId: listCharacteristics
      summary: Dictionary характеристик
      parameters:
        - { name: kind, in: query, schema: { type: string } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/Characteristic' }
                  next_cursor: { type: [string, 'null'] }

  /units:
    get:
      tags: [units]
      operationId: listUnits
      summary: Dictionary единиц измерения
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Unit' }

  /manufacturers:
    get:
      tags: [manufacturers]
      operationId: listManufacturers
      summary: Справочник производителей (admin view)
      parameters:
        - { name: q, in: query, schema: { type: string } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/Manufacturer' }
                  next_cursor: { type: [string, 'null'] }

  # ---------------------------------------------------------------------------
  # Supplier network
  # ---------------------------------------------------------------------------
  /suppliers:
    get:
      tags: [suppliers]
      operationId: listSuppliers
      summary: Список supplier-узлов
      parameters:
        - { name: role, in: query, schema: { type: string, enum: [vendor, marketplace, manufacturer_direct, distributor] } }
        - { name: trust_level, in: query, schema: { type: string, enum: [origin, tier_1, tier_2, unverified] } }
        - { name: q, in: query, schema: { type: string } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/Supplier' }
                  next_cursor: { type: [string, 'null'] }

  /suppliers/{supplier_id}:
    get:
      tags: [suppliers]
      operationId: getSupplier
      summary: Карточка supplier node
      parameters:
        - { name: supplier_id, in: path, required: true, schema: { type: string } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Supplier' }
        '404': { $ref: '#/components/responses/NotFound' }

  /suppliers/{supplier_id}/graph:
    get:
      tags: [suppliers]
      operationId: getSupplierGraph
      summary: Supply chain trace (warehouses + relationships)
      parameters:
        - { name: supplier_id, in: path, required: true, schema: { type: string } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SupplierGraph' }
        '404': { $ref: '#/components/responses/NotFound' }

  /suppliers/{supplier_id}/trust-level:
    put:
      tags: [suppliers]
      operationId: setSupplierTrustLevel
      summary: Назначить trust_level (для pricing tie-break)
      parameters:
        - { name: supplier_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [trust_level]
              properties:
                trust_level: { type: string, enum: [origin, tier_1, tier_2, unverified] }
                reason: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Supplier' }

  /relationships:
    get:
      tags: [relationships]
      operationId: listRelationships
      summary: Отношения между supplier nodes (direct / inferred / multi-tier)
      parameters:
        - { name: kind, in: query, schema: { type: string, enum: [direct, inferred, multi_tier] } }
        - { name: status, in: query, schema: { type: string, enum: [proposed, active, rejected] } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/SupplierRelationship' }
                  next_cursor: { type: [string, 'null'] }

  /relationships/{id}/promote:
    post:
      tags: [relationships]
      operationId: promoteInferredRelationship
      summary: Подтвердить inferred relationship (→ active)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SupplierRelationship' }
        '404': { $ref: '#/components/responses/NotFound' }

  # ---------------------------------------------------------------------------
  # Connectors
  # ---------------------------------------------------------------------------
  /connectors/{supplier_id}/pause:
    post:
      tags: [connectors]
      operationId: pauseConnector
      summary: Приостановить коннектор поставщика
      parameters:
        - { name: supplier_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ConnectorPauseRequest' }
      responses:
        '202':
          description: accepted
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ConnectorControlAck' }
        '404': { $ref: '#/components/responses/NotFound' }

  /connectors/{supplier_id}/resume:
    post:
      tags: [connectors]
      operationId: resumeConnector
      summary: Снять паузу с коннектора
      parameters:
        - { name: supplier_id, in: path, required: true, schema: { type: string } }
      responses:
        '202':
          description: accepted
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ConnectorControlAck' }
        '404': { $ref: '#/components/responses/NotFound' }

  /connectors/{supplier_id}/rate-limits:
    patch:
      tags: [connectors]
      operationId: overrideRateLimits
      summary: Override rate limits коннектора
      parameters:
        - { name: supplier_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RateLimitOverrideRequest' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ConnectorControlAck' }
        '404': { $ref: '#/components/responses/NotFound' }

  # ---------------------------------------------------------------------------
  # Offers (admin view)
  # ---------------------------------------------------------------------------
  /offers/search:
    get:
      tags: [offers]
      operationId: adminSearchOffers
      summary: Полнотекстовый поиск offers по SKU / характеристикам
      parameters:
        - { name: q, in: query, schema: { type: string } }
        - { name: supplier_id, in: query, schema: { type: string } }
        - { name: match_confidence, in: query, schema: { $ref: '#/components/schemas/MatchConfidence' } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AdminOffer' }
                  next_cursor: { type: [string, 'null'] }

  /offers/{id}:
    get:
      tags: [offers]
      operationId: getAdminOffer
      summary: Offer (admin view с observations + matching log)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminOffer' }
        '404': { $ref: '#/components/responses/NotFound' }

  # ---------------------------------------------------------------------------
  # Credentials (admin)
  # ---------------------------------------------------------------------------
  /credentials:
    get:
      tags: [credentials]
      operationId: listAllCredentials
      summary: Все credentials (admin-wide scope)
      parameters:
        - { name: customer_id, in: query, schema: { type: string, format: uuid } }
        - { name: supplier_id, in: query, schema: { type: string } }
        - { name: status, in: query, schema: { $ref: '#/components/schemas/CredentialStatus' } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AdminCredentialSummary' }
                  next_cursor: { type: [string, 'null'] }

  /credentials/pending-merges:
    get:
      tags: [credentials]
      operationId: listCredentialMergeCandidates
      summary: Candidates for auto/manual credential-group merge
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/CredentialMergeCandidate' }

  /credentials/{candidate_id}/merge:
    post:
      tags: [credentials]
      operationId: mergeCredentialGroup
      summary: Подтвердить merge candidate
      parameters:
        - { name: candidate_id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminCredentialSummary' }
        '404': { $ref: '#/components/responses/NotFound' }

  /credentials/{id}/break-glass:
    post:
      tags: [credentials]
      operationId: requestBreakGlassDecryption
      summary: Break-glass расшифровка credential (4-eyes)
      description: |
        Требует двух approver'ов с ролью `security` (ADR-0018). Каждое
        использование пишется в `credential_decrypt_audit`.
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BreakGlassRequest' }
      responses:
        '202':
          description: pending second approval
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BreakGlassPending' }
        '403': { $ref: '#/components/responses/Forbidden' }

  # ---------------------------------------------------------------------------
  # Customers
  # ---------------------------------------------------------------------------
  /customers:
    get:
      tags: [customers]
      operationId: listCustomers
      summary: Справочник клиентов (с фильтрами)
      parameters:
        - { name: q, in: query, schema: { type: string } }
        - { name: customer_type, in: query, schema: { type: string, enum: [individual, legal_entity] } }
        - { name: pricing_group, in: query, schema: { type: string } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AdminCustomer' }
                  next_cursor: { type: [string, 'null'] }

  /customers/{id}:
    get:
      tags: [customers]
      operationId: getCustomer
      summary: Клиент с полным admin-view (включая credentials / features / contracts)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminCustomer' }
        '404': { $ref: '#/components/responses/NotFound' }

  /customers/{id}/pricing-group:
    put:
      tags: [customers]
      operationId: setCustomerPricingGroup
      summary: Назначить pricing group
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pricing_group]
              properties:
                pricing_group: { type: string }
                reason: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminCustomer' }

  /customers/{id}/features:
    put:
      tags: [customers]
      operationId: setCustomerFeatures
      summary: Заменить customer features
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [features]
              properties:
                features: { type: array, items: { type: string } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminCustomer' }

  /customers/{id}/contracts:
    post:
      tags: [customers]
      operationId: createCustomerContract
      summary: Создать B2B-контракт
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CustomerContractCreate' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CustomerContract' }
        '400': { $ref: '#/components/responses/BadRequest' }

  /customers/{id}/contracts/{contract_id}:
    delete:
      tags: [customers]
      operationId: revokeCustomerContract
      summary: Отозвать B2B-контракт
      parameters:
        - { $ref: '#/components/parameters/UuidPath' }
        - { name: contract_id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '204': { description: revoked }
        '404': { $ref: '#/components/responses/NotFound' }

  /pending-legal-verifications:
    get:
      tags: [customers]
      operationId: listPendingLegalVerifications
      summary: Очередь ИНН / legal-info верификаций
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/PendingLegalVerification' }

  # ---------------------------------------------------------------------------
  # Visibility policies
  # ---------------------------------------------------------------------------
  /visibility-policies:
    get:
      tags: [visibility-policies]
      operationId: listVisibilityPolicies
      summary: Список visibility policies
      parameters:
        - { name: target_kind, in: query, schema: { type: string } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/VisibilityPolicy' }
                  next_cursor: { type: [string, 'null'] }
    post:
      tags: [visibility-policies]
      operationId: createVisibilityPolicy
      summary: Создать visibility policy
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/VisibilityPolicyCreate' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/VisibilityPolicy' }
        '400': { $ref: '#/components/responses/BadRequest' }

  /visibility-policies/{id}:
    put:
      tags: [visibility-policies]
      operationId: updateVisibilityPolicy
      summary: Обновить policy (создаёт новую версию)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/VisibilityPolicyCreate' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/VisibilityPolicy' }
        '404': { $ref: '#/components/responses/NotFound' }

  /visibility-policies/{id}/revert:
    post:
      tags: [visibility-policies]
      operationId: revertVisibilityPolicy
      summary: Откатить policy до указанной версии
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/VisibilityPolicyRevertRequest' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/VisibilityPolicy' }
        '404': { $ref: '#/components/responses/NotFound' }

  /visibility-policies/simulate:
    post:
      tags: [visibility-policies]
      operationId: simulateVisibility
      summary: Dry-run применения policy на набор candidates
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/VisibilitySimulateRequest' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/VisibilitySimulateResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }

  # ---------------------------------------------------------------------------
  # Moderation (AI agent orchestration)
  # ---------------------------------------------------------------------------
  /moderation/queue:
    get:
      tags: [moderation]
      operationId: listModerationQueue
      summary: Очередь кейсов модерации (эскалации + в работе)
      parameters:
        - { name: case_kind, in: query, schema: { $ref: '#/components/schemas/ModerationCaseKind' } }
        - { name: priority, in: query, schema: { type: string, enum: [hot, warm, cold] } }
        - { name: status, in: query, schema: { type: string, enum: [queued, analyzing, decided, applied, escalated, closed] } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/ModerationCase' }
                  next_cursor: { type: [string, 'null'] }

  /moderation/cases/{case_id}:
    get:
      tags: [moderation]
      operationId: getModerationCase
      summary: Полные детали кейса (evidence + agent decision + reasoning)
      parameters:
        - { name: case_id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ModerationCaseDetail' }
        '404': { $ref: '#/components/responses/NotFound' }
    put:
      tags: [moderation]
      operationId: resolveModerationCase
      summary: Человеческое resolve (только для escalated)
      parameters:
        - { name: case_id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ModerationResolveRequest' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ModerationCaseDetail' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /moderation/decisions-log:
    get:
      tags: [moderation]
      operationId: getModerationDecisionsLog
      summary: Append-only лог решений (для дашборда точности агентов)
      parameters:
        - { name: from, in: query, schema: { type: string, format: date-time } }
        - { name: to, in: query, schema: { type: string, format: date-time } }
        - { name: agent_id, in: query, schema: { type: string } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/ModerationDecisionLogEntry' }
                  next_cursor: { type: [string, 'null'] }

  /moderation/agent-runners/{agent_id}/enabled:
    put:
      tags: [moderation]
      operationId: setModerationAgentEnabled
      summary: Вкл/выкл moderation agent (circuit breaker / drift)
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled: { type: boolean }
                reason: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ModerationAgentRunner' }

  # ---------------------------------------------------------------------------
  # Price rules & custom pricing handlers
  # ---------------------------------------------------------------------------
  /price-rules:
    get:
      tags: [price-rules]
      operationId: listPriceRules
      summary: Правила ценообразования
      parameters:
        - { name: scope, in: query, schema: { type: string, enum: [supplier, canonical, classification_tag, customer_pricing_group, customer] } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/PriceRule' }
                  next_cursor: { type: [string, 'null'] }
    post:
      tags: [price-rules]
      operationId: createPriceRule
      summary: Создать правило (конфликты → moderation case)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PriceRuleCreate' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PriceRule' }
        '400': { $ref: '#/components/responses/BadRequest' }

  /price-rules/{id}:
    put:
      tags: [price-rules]
      operationId: updatePriceRule
      summary: Обновить правило
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PriceRuleCreate' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PriceRule' }
        '404': { $ref: '#/components/responses/NotFound' }

  /pricing-handlers:
    get:
      tags: [pricing-handlers]
      operationId: listPricingHandlers
      summary: Зарегистрированные custom pricing handlers (plugin)
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/PricingHandler' }
    post:
      tags: [pricing-handlers]
      operationId: registerPricingHandler
      summary: Зарегистрировать custom pricing handler
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PricingHandlerCreate' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PricingHandler' }

  /pricing-handlers/invocations:
    get:
      tags: [pricing-handlers]
      operationId: getPricingHandlerInvocations
      summary: Лог вызовов custom pricing handlers
      parameters:
        - { name: handler_id, in: query, schema: { type: string } }
        - { name: from, in: query, schema: { type: string, format: date-time } }
        - { name: to, in: query, schema: { type: string, format: date-time } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/PricingHandlerInvocation' }

  # ---------------------------------------------------------------------------
  # Enrichment tasks
  # ---------------------------------------------------------------------------
  /enrichment/tasks:
    get:
      tags: [enrichment]
      operationId: listEnrichmentTasks
      summary: Очередь enrichment tasks + прогресс
      parameters:
        - { name: status, in: query, schema: { type: string, enum: [queued, running, completed, failed, escalated_to_moderation] } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/EnrichmentTask' }
                  next_cursor: { type: [string, 'null'] }
    post:
      tags: [enrichment]
      operationId: enqueueEnrichmentTask
      summary: Поставить enrichment task в очередь
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EnrichmentTaskCreate' }
      responses:
        '202':
          description: accepted
          content:
            application/json:
              schema: { $ref: '#/components/schemas/EnrichmentTask' }

  /enrichment/history:
    get:
      tags: [enrichment]
      operationId: getEnrichmentHistory
      summary: История завершённых jobs + AI confidence / model_ref
      parameters:
        - { name: from, in: query, schema: { type: string, format: date-time } }
        - { name: to, in: query, schema: { type: string, format: date-time } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/EnrichmentTask' }
                  next_cursor: { type: [string, 'null'] }

  # ---------------------------------------------------------------------------
  # Search operator tools
  # ---------------------------------------------------------------------------
  /search/explain:
    post:
      tags: [search]
      operationId: explainSearch
      summary: Explain поискового запроса (ranking + фильтры)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SearchExplainRequest' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SearchExplainResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }

  /search/ranking-profile:
    put:
      tags: [search]
      operationId: updateSearchRankingProfile
      summary: Обновить ranking profile (BM25 веса, versioned)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RankingProfile' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RankingProfile' }

  /search/index-status:
    get:
      tags: [search]
      operationId: getSearchIndexStatus
      summary: Статус reindex, stale count
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SearchIndexStatus' }

  # ---------------------------------------------------------------------------
  # Estimates (admin)
  # ---------------------------------------------------------------------------
  /estimates:
    get:
      tags: [estimates]
      operationId: listAllEstimates
      summary: Все сметы (admin scope, с фильтрами)
      parameters:
        - { name: customer_id, in: query, schema: { type: string, format: uuid } }
        - { name: status, in: query, schema: { type: string } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AdminEstimateSummary' }
                  next_cursor: { type: [string, 'null'] }

  /estimates/{id}/approve:
    put:
      tags: [estimates]
      operationId: approveEstimate
      summary: Approval workflow (если включён customer feature `allow_estimate_approval`)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [decision]
              properties:
                decision: { type: string, enum: [approve, reject] }
                comment: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminEstimateSummary' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /estimates/{id}/explainability-log:
    get:
      tags: [estimates]
      operationId: getEstimateExplainabilityLog
      summary: Почему для каждой строки выбран конкретный offer
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/EstimateExplainEntry' }
        '404': { $ref: '#/components/responses/NotFound' }

  # ---------------------------------------------------------------------------
  # Plans grid (CRUD)
  # ---------------------------------------------------------------------------
  /plans:
    get:
      tags: [plans]
      operationId: adminListPlans
      summary: Сетка тарифов (включая hidden / archived)
      parameters:
        - { name: include_archived, in: query, schema: { type: boolean, default: false } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AdminPlan' }
                  next_cursor: { type: [string, 'null'] }
    post:
      tags: [plans]
      operationId: createPlan
      summary: Создать тариф (новая версия — immutable)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AdminPlanCreate' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminPlan' }
        '400': { $ref: '#/components/responses/BadRequest' }

  /plans/{code}:
    get:
      tags: [plans]
      operationId: adminGetPlan
      summary: Получить тариф по code
      parameters:
        - { name: code, in: path, required: true, schema: { type: string } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminPlan' }
        '404': { $ref: '#/components/responses/NotFound' }
    put:
      tags: [plans]
      operationId: updatePlan
      summary: Обновить тариф (создаёт новую версию; старая остаётся для subscription-snapshot'ов)
      parameters:
        - { name: code, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AdminPlanCreate' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminPlan' }
        '404': { $ref: '#/components/responses/NotFound' }

  /plans/{code}/archive:
    post:
      tags: [plans]
      operationId: archivePlan
      summary: Архивировать тариф (новые подписки невозможны, существующие продолжаются)
      parameters:
        - { name: code, in: path, required: true, schema: { type: string } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminPlan' }
        '404': { $ref: '#/components/responses/NotFound' }

  /request-packages:
    get:
      tags: [plans]
      operationId: adminListRequestPackages
      summary: Prepaid-пакеты запросов (admin CRUD)
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/AdminRequestPackage' }
    post:
      tags: [plans]
      operationId: createRequestPackage
      summary: Создать пакет запросов
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AdminRequestPackageCreate' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminRequestPackage' }

  /request-packages/{code}:
    put:
      tags: [plans]
      operationId: updateRequestPackage
      summary: Обновить пакет (новая версия)
      parameters:
        - { name: code, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AdminRequestPackageCreate' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminRequestPackage' }
        '404': { $ref: '#/components/responses/NotFound' }

  # ---------------------------------------------------------------------------
  # Subscriptions (per-customer admin management)
  # ---------------------------------------------------------------------------
  /customers/{id}/subscription:
    get:
      tags: [subscriptions]
      operationId: getCustomerSubscription
      summary: Подписка клиента (admin view)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminSubscription' }
        '404': { $ref: '#/components/responses/NotFound' }
    put:
      tags: [subscriptions]
      operationId: assignCustomerSubscription
      summary: Назначить / сменить тариф клиента (с обоснованием)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AdminSubscriptionChangeRequest' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminSubscription' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }

  /customers/{id}/subscription/cancel:
    post:
      tags: [subscriptions]
      operationId: adminCancelSubscription
      summary: Отменить подписку клиента (immediate / end-of-period)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason: { type: string }
                immediate: { type: boolean, default: false }
                refund_strategy: { type: string, enum: [none, prorated, full], default: none }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminSubscription' }

  /customers/{id}/subscription/override-limits:
    put:
      tags: [subscriptions]
      operationId: overrideCustomerLimits
      summary: Ручной override лимитов (enterprise / VIP)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LimitsOverrideRequest' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminSubscription' }

  /customers/{id}/packages:
    get:
      tags: [subscriptions]
      operationId: getCustomerPackages
      summary: Пакеты запросов клиента (активные + история)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/AdminPurchasedPackage' }
    post:
      tags: [subscriptions]
      operationId: grantCustomerPackage
      summary: Выдать пакет вручную (bonus / compensation)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [package_code, reason]
              properties:
                package_code: { type: string }
                quantity: { type: integer, default: 1 }
                reason: { type: string }
                expires_at: { type: [string, 'null'], format: date-time }
      responses:
        '201':
          description: granted
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminPurchasedPackage' }

  # ---------------------------------------------------------------------------
  # Usage analytics (aggregate)
  # ---------------------------------------------------------------------------
  /usage/events:
    get:
      tags: [usage-analytics]
      operationId: listUsageEvents
      summary: Сырой append-only лог всех запросов (с фильтрами)
      parameters:
        - { name: customer_id, in: query, schema: { type: string, format: uuid } }
        - { name: endpoint, in: query, schema: { type: string } }
        - { name: status_bucket, in: query, schema: { type: string, enum: ["2xx", "4xx", "5xx", "429"] } }
        - { name: from, in: query, schema: { type: string, format: date-time } }
        - { name: to, in: query, schema: { type: string, format: date-time } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AdminUsageEvent' }
                  next_cursor: { type: [string, 'null'] }

  /usage/summary:
    get:
      tags: [usage-analytics]
      operationId: getUsageSummary
      summary: Агрегированная статистика (per customer / per plan / per endpoint)
      parameters:
        - { name: group_by, in: query, schema: { type: string, enum: [customer, plan, endpoint, day, hour] } }
        - { name: customer_id, in: query, schema: { type: string, format: uuid } }
        - { name: plan_code, in: query, schema: { type: string } }
        - { name: from, in: query, schema: { type: string, format: date-time } }
        - { name: to, in: query, schema: { type: string, format: date-time } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  buckets:
                    type: array
                    items: { $ref: '#/components/schemas/UsageBucket' }
                  totals:
                    type: object
                    properties:
                      requests: { type: integer }
                      data_bytes: { type: integer, format: int64 }
                      429_count: { type: integer }

  /usage/rate-limit-hits:
    get:
      tags: [usage-analytics]
      operationId: listRateLimitHits
      summary: Последние 429 события (для SLA-расследований)
      parameters:
        - { name: customer_id, in: query, schema: { type: string, format: uuid } }
        - { name: window, in: query, schema: { type: string, enum: [minute, hour, day, week, month, billing_period] } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/RateLimitHit' }
                  next_cursor: { type: [string, 'null'] }

  # ---------------------------------------------------------------------------
  # Billing (admin)
  # ---------------------------------------------------------------------------
  /invoices:
    get:
      tags: [billing]
      operationId: adminListInvoices
      summary: Все invoices (admin scope)
      parameters:
        - { name: customer_id, in: query, schema: { type: string, format: uuid } }
        - { name: status, in: query, schema: { type: string, enum: [draft, open, paid, void, uncollectible] } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AdminInvoice' }
                  next_cursor: { type: [string, 'null'] }

  /invoices/{id}/refund:
    post:
      tags: [billing]
      operationId: refundInvoice
      summary: Refund счёта (частичный / полный)
      parameters: [{ $ref: '#/components/parameters/UuidPath' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason: { type: string }
                amount:
                  oneOf:
                    - { type: number }
                    - { type: 'null' }
                  description: "null = full refund"
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminInvoice' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  # ---------------------------------------------------------------------------
  # Organizations (admin CRUD)
  # ---------------------------------------------------------------------------
  /organizations:
    get:
      tags: [organizations]
      operationId: adminListOrganizations
      summary: Список всех организаций
      parameters:
        - { name: q, in: query, schema: { type: string } }
        - { name: plan_code, in: query, schema: { type: string } }
        - { name: status, in: query, schema: { type: string, enum: [active, suspended, closed] } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AdminOrganization' }
                  next_cursor: { type: [string, 'null'] }
    post:
      tags: [organizations]
      operationId: adminCreateOrganization
      summary: Создать организацию вручную (без self-service flow)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AdminOrganizationCreate' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminOrganization' }
        '400': { $ref: '#/components/responses/BadRequest' }

  /organizations/{org_id}:
    get:
      tags: [organizations]
      operationId: adminGetOrganization
      summary: Организация (полный admin view + billing state)
      parameters:
        - { name: org_id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminOrganization' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [organizations]
      operationId: adminUpdateOrganization
      summary: Обновить профиль организации (status, legal_info, overrides)
      parameters:
        - { name: org_id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AdminOrganizationPatch' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminOrganization' }

  /organizations/{org_id}/members:
    get:
      tags: [organizations]
      operationId: adminListOrganizationMembers
      summary: Участники организации (admin view)
      parameters:
        - { name: org_id, in: path, required: true, schema: { type: string, format: uuid } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AdminOrganizationMember' }
                  next_cursor: { type: [string, 'null'] }
    post:
      tags: [organizations]
      operationId: adminAttachOrganizationMember
      summary: Добавить участника в организацию (минуя приглашение — для миграций)
      parameters:
        - { name: org_id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [user_id, role]
              properties:
                user_id: { type: string, format: uuid }
                role: { type: string, enum: [owner, admin, member, viewer] }
                reason: { type: string }
      responses:
        '201':
          description: attached
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminOrganizationMember' }

  /organizations/{org_id}/subscription:
    get:
      tags: [organizations, subscriptions]
      operationId: adminGetOrganizationSubscription
      summary: Подписка организации (admin view)
      parameters:
        - { name: org_id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminOrganizationSubscription' }
        '404': { $ref: '#/components/responses/NotFound' }
    put:
      tags: [organizations, subscriptions]
      operationId: adminAssignOrganizationSubscription
      summary: Назначить / сменить тариф организации
      parameters:
        - { name: org_id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [plan_code, reason]
              properties:
                plan_code: { type: string, description: "scope=organization" }
                apply_at: { type: string, enum: [immediate, next_period], default: immediate }
                seats: { type: [integer, 'null'] }
                waive_payment: { type: boolean, default: false }
                reason: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminOrganizationSubscription' }

  /organizations/{org_id}/subscription/override-limits:
    put:
      tags: [organizations, subscriptions]
      operationId: adminOverrideOrganizationLimits
      summary: Override org pool лимитов (enterprise)
      parameters:
        - { name: org_id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LimitsOverrideRequest' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminOrganizationSubscription' }

  /organizations/{org_id}/members/{user_id}/limits:
    put:
      tags: [organizations]
      operationId: adminSetOrganizationMemberCap
      summary: Override personal cap участника (admin)
      parameters:
        - { name: org_id, in: path, required: true, schema: { type: string, format: uuid } }
        - { name: user_id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AdminMemberCapOverrideRequest' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminOrganizationMember' }

  /organizations/{org_id}/invoices:
    get:
      tags: [organizations, billing]
      operationId: adminListOrganizationInvoices
      summary: Консолидированные invoices организации (admin)
      parameters:
        - { name: org_id, in: path, required: true, schema: { type: string, format: uuid } }
        - { name: status, in: query, schema: { type: string, enum: [draft, open, paid, void, uncollectible] } }
        - { $ref: '#/components/parameters/Limit' }
        - { $ref: '#/components/parameters/Cursor' }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/AdminInvoice' }
                  next_cursor: { type: [string, 'null'] }

  /organizations/{org_id}/usage:
    get:
      tags: [organizations, usage-analytics]
      operationId: adminGetOrganizationUsage
      summary: Usage организации (pool + per-member breakdown)
      parameters:
        - { name: org_id, in: path, required: true, schema: { type: string, format: uuid } }
        - { name: from, in: query, schema: { type: string, format: date-time } }
        - { name: to, in: query, schema: { type: string, format: date-time } }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AdminOrganizationUsageSummary' }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    oidc:
      type: openIdConnect
      openIdConnectUrl: https://auth.tracium.ru/.well-known/openid-configuration

  parameters:
    UuidPath:
      name: id
      in: path
      required: true
      schema: { type: string, format: uuid }
    Limit:
      name: limit
      in: query
      schema: { type: integer, default: 50, minimum: 1, maximum: 200 }
    Cursor:
      name: cursor
      in: query
      schema: { type: string }

  responses:
    BadRequest:
      description: bad request
      content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
    Unauthorized:
      description: unauthorized
      content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
    Forbidden:
      description: forbidden (не хватает ролей / 4-eyes)
      content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
    NotFound:
      description: not found
      content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
    Conflict:
      description: conflict
      content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
    IdentityProfilePage:
      description: ok
      content:
        application/json:
          schema:
            type: object
            properties:
              items:
                type: array
                items: { $ref: '#/components/schemas/IdentityProfile' }
              next_cursor: { type: [string, 'null'] }

  schemas:
    # ----- Common -----------------------------------------------------------
    Error:
      type: object
      required: [code, title, message, category, retryable, request_id]
      properties:
        code: { type: string }
        title: { type: string }
        message: { type: string }
        category:
          type: string
          enum: [validation, auth, forbidden, not_found, conflict, rate_limit, dependency, internal]
        retryable: { type: boolean }
        details: { type: object, additionalProperties: true }
        request_id: { type: string }
        trace_id: { type: string }
    ErrorEnvelope:
      type: object
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/Error' }

    AuditStamp:
      type: object
      properties:
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        updated_by: { type: string }

    LifecycleStatus:
      type: string
      enum: [new, active, deprecated, discontinued]

    MatchConfidence:
      type: string
      enum: [exact, strong, probable, weak, unmatched]

    CredentialStatus:
      type: string
      enum: [draft, validating, active, failing, expired, revoked]

    MediaAsset:
      type: object
      properties:
        kind: { type: string, enum: [image, video, certificate, datasheet] }
        url: { type: string, format: uri }
        mime_type: { type: string }
        watermarked: { type: boolean }

    # ----- Canonical product -----------------------------------------------
    AdminCanonicalProduct:
      type: object
      properties:
        id: { type: string, format: uuid }
        manufacturer: { type: string }
        mpn: { type: string }
        identity_profile_id: { type: [string, 'null'], format: uuid }
        identity_signature: { type: object, additionalProperties: true }
        characteristics: { type: object, additionalProperties: true }
        media:
          type: array
          items: { $ref: '#/components/schemas/MediaAsset' }
        external_identities:
          type: array
          items:
            type: object
            properties:
              standard_ref: { type: string }
              value: { type: string }
        aliases:
          type: array
          items: { type: string }
        classification_tags:
          type: array
          items:
            type: string
          description: |
            Controlled classification tags applied to this canonical product.
            Populated by the normalization pipeline (AI classifier — deferred);
            production rows currently always carry an empty array. See spec
            P4c-1 (2026-04-26) and ADR-0028 §9. Read-only from the admin API
            perspective; not accepted on POST/PUT bodies.
        lifecycle_status: { $ref: '#/components/schemas/LifecycleStatus' }
        replaced_by: { type: [string, 'null'], format: uuid }
        moderation_locks:
          type: array
          items: { type: string }
        audit: { $ref: '#/components/schemas/AuditStamp' }

    CanonicalProductCreate:
      type: object
      required: [manufacturer, mpn]
      properties:
        manufacturer: { type: string }
        mpn: { type: string }
        identity_profile_id: { type: string, format: uuid }
        characteristics: { type: object, additionalProperties: true }

    AdminCanonicalProductPatch:
      type: object
      properties:
        manufacturer: { type: string }
        mpn: { type: string }
        identity_profile_id: { type: [string, 'null'], format: uuid }
        characteristics: { type: object, additionalProperties: true }
        aliases:
          type: array
          items: { type: string }
        lifecycle_status: { $ref: '#/components/schemas/LifecycleStatus' }
        replaced_by: { type: [string, 'null'], format: uuid }
        reason: { type: string }

    # ----- Identity profiles / standards -----------------------------------
    IdentityProfile:
      type: object
      properties:
        id: { type: string, format: uuid }
        kind: { type: string }
        name: { type: string }
        required_characteristics:
          type: array
          items: { type: string }
        optional_characteristics:
          type: array
          items: { type: string }
        aliases:
          type: array
          items: { type: string }
        audit: { $ref: '#/components/schemas/AuditStamp' }

    IdentityProfileCreate:
      type: object
      required: [kind, name]
      properties:
        kind: { type: string }
        name: { type: string }
        required_characteristics:
          type: array
          items: { type: string }
        optional_characteristics:
          type: array
          items: { type: string }
        aliases:
          type: array
          items: { type: string }

    IdentityProfileUpdate:
      type: object
      properties:
        name: { type: string }
        required_characteristics:
          type: array
          items: { type: string }
        optional_characteristics:
          type: array
          items: { type: string }
        aliases:
          type: array
          items: { type: string }

    IdentityStandard:
      type: object
      properties:
        ref: { type: string, description: "например etim_v7, gs1_gtin, okpd2" }
        name: { type: string }
        version: { type: string }
        homepage: { type: [string, 'null'], format: uri }
        active: { type: boolean }

    CharacteristicMapping:
      type: object
      properties:
        id: { type: string, format: uuid }
        supplier_id: { type: string }
        supplier_code: { type: string }
        canonical_characteristic_id: { type: string }
        transform: { type: string, enum: [copy, normalize_unit, enum_remap, formula], default: copy }
        params: { type: object, additionalProperties: true }

    CharacteristicMappingPage:
      type: object
      properties:
        items:
          type: array
          items: { $ref: '#/components/schemas/CharacteristicMapping' }
        next_cursor: { type: [string, 'null'] }

    ClassificationMapping:
      type: object
      properties:
        id: { type: string, format: uuid }
        supplier_id: { type: string }
        supplier_code: { type: string }
        tag: { type: string }

    ManufacturerMapping:
      type: object
      properties:
        id: { type: string, format: uuid }
        manufacturer_id: { type: string }
        alias: { type: string }
        source: { type: string, enum: [manual, llm, supplier_feed] }
        confidence: { type: [number, 'null'], format: float }

    # ----- Dictionary ------------------------------------------------------
    Characteristic:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        kind: { type: string }
        value_type: { type: string, enum: [string, integer, number, boolean, enum, dimension] }
        unit: { type: [string, 'null'] }
        enum_values:
          type: array
          items: { type: string }

    Unit:
      type: object
      properties:
        code: { type: string }
        name: { type: string }
        dimension: { type: string }

    Manufacturer:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        aliases:
          type: array
          items: { type: string }
        country: { type: string }

    # ----- Supplier network ------------------------------------------------
    Supplier:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        role: { type: string, enum: [vendor, marketplace, manufacturer_direct, distributor] }
        trust_level: { type: string, enum: [origin, tier_1, tier_2, unverified] }
        status: { type: string, enum: [active, inactive, banned] }
        aliases:
          type: array
          items: { type: string }
        rating:
          type: object
          properties:
            score: { type: number }
            last_updated: { type: string, format: date-time }

    SupplierGraph:
      type: object
      properties:
        supplier: { $ref: '#/components/schemas/Supplier' }
        warehouses:
          type: array
          items:
            type: object
            properties:
              ref: { type: string }
              kind: { type: string, enum: [own_warehouse, third_party_logistics, transit, external_marketplace] }
              region: { type: string }
        edges:
          type: array
          items: { $ref: '#/components/schemas/SupplierRelationship' }

    SupplierRelationship:
      type: object
      properties:
        id: { type: string, format: uuid }
        from_supplier_id: { type: string }
        to_supplier_id: { type: string }
        kind: { type: string, enum: [direct, inferred, multi_tier] }
        status: { type: string, enum: [proposed, active, rejected] }
        confidence: { type: [number, 'null'], format: float }
        evidence: { type: object, additionalProperties: true }

    # ----- Connectors ------------------------------------------------------
    ConnectorPauseRequest:
      type: object
      required: [reason]
      properties:
        reason: { type: string }
        ttl: { type: string, description: "duration like '15m', '1h', '24h'" }

    RateLimitBucket:
      type: object
      properties:
        rate: { type: integer }
        period: { type: string }

    RateLimitOverrideRequest:
      type: object
      properties:
        ttl: { type: string }
        buckets:
          type: object
          additionalProperties: { $ref: '#/components/schemas/RateLimitBucket' }

    ConnectorControlAck:
      type: object
      properties:
        supplier_id: { type: string }
        applied_at: { type: string, format: date-time }
        expires_at: { type: [string, 'null'], format: date-time }
        reason: { type: string }

    # ----- Offers (admin) --------------------------------------------------
    AdminOffer:
      type: object
      properties:
        id: { type: string, format: uuid }
        supplier_id: { type: string }
        supplier_sku: { type: string }
        canonical_product_id: { type: [string, 'null'], format: uuid }
        match_confidence: { $ref: '#/components/schemas/MatchConfidence' }
        characteristics_from_supplier:
          type: object
          additionalProperties: true
        last_observation_at: { type: [string, 'null'], format: date-time }
        observation_count: { type: integer }
        matching_log:
          type: array
          items:
            type: object
            properties:
              at: { type: string, format: date-time }
              decision: { type: string }
              confidence: { type: number }
              reason: { type: string }

    # ----- Credentials (admin) ---------------------------------------------
    AdminCredentialSummary:
      type: object
      properties:
        id: { type: string, format: uuid }
        customer_id: { type: string, format: uuid }
        supplier_id: { type: string }
        label: { type: string }
        auth_schema: { type: string }
        status: { $ref: '#/components/schemas/CredentialStatus' }
        last_validated_at: { type: [string, 'null'], format: date-time }
        group_id: { type: [string, 'null'], format: uuid }
        features:
          type: array
          items: { type: string }

    CredentialMergeCandidate:
      type: object
      properties:
        candidate_id: { type: string, format: uuid }
        credential_ids:
          type: array
          items: { type: string, format: uuid }
        fingerprint: { type: string }
        suggested_group_id: { type: [string, 'null'], format: uuid }
        confidence: { type: number, format: float }

    BreakGlassRequest:
      type: object
      required: [reason, incident_ref]
      properties:
        reason: { type: string }
        incident_ref: { type: string, description: "Linear/Jira ticket ID" }

    BreakGlassPending:
      type: object
      properties:
        request_id: { type: string, format: uuid }
        status: { type: string, enum: [pending_second_approval, approved, rejected] }
        first_approver: { type: string }
        expires_at: { type: string, format: date-time }

    # ----- Customers (admin) -----------------------------------------------
    AdminCustomer:
      type: object
      properties:
        id: { type: string, format: uuid }
        customer_type: { type: string, enum: [individual, legal_entity] }
        display_name: { type: string }
        email: { type: string, format: email }
        phone: { type: string }
        pricing_group: { type: string }
        features:
          type: array
          items: { type: string }
        legal_info:
          type: [object, 'null']
          additionalProperties: true
        credentials_count: { type: integer }
        contracts:
          type: array
          items: { $ref: '#/components/schemas/CustomerContract' }
        data_deletion_scheduled_at: { type: [string, 'null'], format: date-time }
        audit: { $ref: '#/components/schemas/AuditStamp' }

    CustomerContract:
      type: object
      properties:
        id: { type: string, format: uuid }
        customer_id: { type: string, format: uuid }
        number: { type: string }
        counterparty: { type: string }
        valid_from: { type: string, format: date }
        valid_to: { type: [string, 'null'], format: date }
        pricing_group_override: { type: [string, 'null'] }
        features:
          type: array
          items: { type: string }
        status: { type: string, enum: [draft, active, expired, revoked] }

    CustomerContractCreate:
      type: object
      required: [number, valid_from]
      properties:
        number: { type: string }
        counterparty: { type: string }
        valid_from: { type: string, format: date }
        valid_to: { type: string, format: date }
        pricing_group_override: { type: string }
        features:
          type: array
          items: { type: string }

    PendingLegalVerification:
      type: object
      properties:
        customer_id: { type: string, format: uuid }
        inn: { type: string }
        submitted_at: { type: string, format: date-time }
        status: { type: string, enum: [pending, edge_case] }
        reason: { type: string }

    # ----- Visibility policies ---------------------------------------------
    VisibilityPolicy:
      type: object
      properties:
        id: { type: string, format: uuid }
        version: { type: integer }
        name: { type: string }
        target_kind:
          type: string
          description: "catalog / offer / observation / price / characteristic / analytics / media"
        subject:
          type: object
          properties:
            kind: { type: string, enum: [default, customer, customer_group, customer_pricing_group, role] }
            ref: { type: [string, 'null'] }
        effect: { type: string, enum: [allow, deny, transform] }
        predicate: { type: object, additionalProperties: true }
        transform_spec:
          type: array
          items: { type: object, additionalProperties: true }
        rules:
          type: array
          items: { type: object, additionalProperties: true }
          description: "(deprecated) raw rules — для совместимости до полного перехода на predicate/effect"
        audit: { $ref: '#/components/schemas/AuditStamp' }

    VisibilityPolicyCreate:
      type: object
      required: [name, target_kind, effect]
      properties:
        name: { type: string }
        target_kind: { type: string }
        subject:
          type: object
          properties:
            kind: { type: string, enum: [default, customer, customer_group, customer_pricing_group, role] }
            ref: { type: string }
        effect: { type: string, enum: [allow, deny, transform] }
        predicate: { type: object, additionalProperties: true }
        transform_spec:
          type: array
          items: { type: object, additionalProperties: true }

    VisibilityPolicyRevertRequest:
      type: object
      required: [to_version]
      properties:
        to_version: { type: integer }
        reason: { type: string }

    VisibilitySimulateRequest:
      type: object
      required: [subject, target_kind, candidates]
      properties:
        subject:
          type: object
          properties:
            kind: { type: string, enum: [default, customer, customer_group, customer_pricing_group, role] }
            ref: { type: string }
        target_kind: { type: string }
        candidates:
          type: array
          items: { type: object, additionalProperties: true }

    VisibilitySimulateResponse:
      type: object
      properties:
        filtered:
          type: array
          items: { type: object, additionalProperties: true }
        applied_policies:
          type: array
          items:
            type: object
            properties:
              policy_id: { type: string, format: uuid }
              effect: { type: string }
              reason: { type: string }

    # ----- Moderation ------------------------------------------------------
    ModerationCaseKind:
      type: string
      enum:
        - match_review
        - orphan_resolution
        - inferred_relationship_review
        - enrichment_critical_attr_review
        - priority_overlap_review
        - credential_group_candidate
        - legal_info_verification_edge
        - manufacturer_alias_proposal
        - identity_profile_proposal

    ModerationCase:
      type: object
      properties:
        id: { type: string, format: uuid }
        case_kind: { $ref: '#/components/schemas/ModerationCaseKind' }
        priority: { type: string, enum: [hot, warm, cold] }
        status: { type: string, enum: [queued, analyzing, decided, applied, escalated, closed] }
        source_bc: { type: string }
        payload: { type: object, additionalProperties: true }
        ai_verdict:
          type: [object, 'null']
          properties:
            decision: { type: string, enum: [accept, reject, escalate, defer] }
            confidence: { type: number, format: float }
            reasoning: { type: string }
        created_at: { type: string, format: date-time }

    ModerationCaseDetail:
      allOf:
        - $ref: '#/components/schemas/ModerationCase'
        - type: object
          properties:
            evidence_gathered: { type: object, additionalProperties: true }
            proposed_action: { type: object, additionalProperties: true }
            citations:
              type: array
              items:
                type: object
                properties:
                  ref: { type: string }
                  excerpt: { type: string }
            model_ref:
              type: object
              properties:
                model_name: { type: string }
                version: { type: string }
                prompt_hash: { type: string }

    ModerationResolveRequest:
      type: object
      required: [decision]
      properties:
        decision: { type: string, enum: [accept, reject, escalate_further] }
        reason: { type: string }
        override_action:
          type: object
          additionalProperties: true

    ModerationDecisionLogEntry:
      type: object
      properties:
        case_id: { type: string, format: uuid }
        case_kind: { $ref: '#/components/schemas/ModerationCaseKind' }
        decided_by: { type: string, description: "agent id или пользователь" }
        decision: { type: string }
        confidence: { type: [number, 'null'], format: float }
        decided_at: { type: string, format: date-time }
        applied: { type: boolean }

    ModerationAgentRunner:
      type: object
      properties:
        id: { type: string }
        case_kind: { $ref: '#/components/schemas/ModerationCaseKind' }
        model_ref:
          type: object
          properties:
            model_name: { type: string }
            version: { type: string }
            prompt_hash: { type: string }
        confidence_threshold: { type: number, format: float }
        enabled: { type: boolean }
        circuit_state: { type: string, enum: [closed, half_open, open] }

    # ----- Price rules / handlers ------------------------------------------
    PriceRule:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        scope: { type: string, enum: [supplier, canonical, classification_tag, customer_pricing_group, customer] }
        scope_ref: { type: [string, 'null'] }
        priority: { type: integer }
        active: { type: boolean }
        transformation: { type: string, enum: [percentage, tiers, fixed, formula, logistics, vat] }
        condition: { type: object, additionalProperties: true }
        effect: { type: object, additionalProperties: true }
        audit: { $ref: '#/components/schemas/AuditStamp' }

    PriceRuleCreate:
      type: object
      required: [name, scope, priority, transformation, effect]
      properties:
        name: { type: string }
        scope: { type: string, enum: [supplier, canonical, classification_tag, customer_pricing_group, customer] }
        scope_ref: { type: [string, 'null'] }
        priority: { type: integer }
        active: { type: boolean, default: true }
        transformation: { type: string, enum: [percentage, tiers, fixed, formula, logistics, vat] }
        condition: { type: object, additionalProperties: true }
        effect: { type: object, additionalProperties: true }

    PricingHandler:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        endpoint: { type: string, format: uri }
        supplier_scope:
          type: array
          items: { type: string }
        active: { type: boolean }
        registered_at: { type: string, format: date-time }

    PricingHandlerCreate:
      type: object
      required: [name, endpoint]
      properties:
        name: { type: string }
        endpoint: { type: string, format: uri }
        supplier_scope:
          type: array
          items: { type: string }

    PricingHandlerInvocation:
      type: object
      properties:
        invocation_id: { type: string, format: uuid }
        handler_id: { type: string }
        started_at: { type: string, format: date-time }
        duration_ms: { type: integer }
        status: { type: string, enum: [ok, error, timeout, circuit_open] }
        error: { type: [string, 'null'] }

    # ----- Enrichment (admin) ----------------------------------------------
    EnrichmentTaskCreate:
      type: object
      required: [target_kind, target_ref, kind]
      properties:
        target_kind: { type: string, enum: [canonical, offer] }
        target_ref: { type: string }
        kind: { type: string, enum: [characteristic_extraction, content_generation, embedding, media_augment] }
        fields:
          type: array
          items: { type: string }
        priority: { type: string, enum: [high, normal, low], default: normal }

    EnrichmentTask:
      allOf:
        - $ref: '#/components/schemas/EnrichmentTaskCreate'
        - type: object
          properties:
            id: { type: string, format: uuid }
            status: { type: string, enum: [queued, running, completed, failed, escalated_to_moderation] }
            progress: { type: number, format: float }
            source: { type: string, enum: [llm, rule_calculated, manual, vendor_doc] }
            confidence: { type: [number, 'null'], format: float }
            model_ref:
              type: [object, 'null']
              properties:
                model_name: { type: string }
                version: { type: string }
                prompt_hash: { type: string }
            created_at: { type: string, format: date-time }
            finished_at: { type: [string, 'null'], format: date-time }
            error: { type: [string, 'null'] }

    # ----- Search ----------------------------------------------------------
    SearchExplainRequest:
      type: object
      required: [query]
      properties:
        query: { type: string }
        index: { type: string }
        subject: { type: object, additionalProperties: true }

    SearchExplainResponse:
      type: object
      properties:
        query_analyzed:
          type: object
          additionalProperties: true
        hits:
          type: array
          items:
            type: object
            properties:
              doc_id: { type: string }
              score: { type: number }
              explanation: { type: object, additionalProperties: true }
        applied_filters:
          type: array
          items: { type: object, additionalProperties: true }

    RankingProfile:
      type: object
      properties:
        id: { type: string }
        version: { type: integer }
        bm25_weights:
          type: object
          additionalProperties: { type: number }
        boosts:
          type: array
          items:
            type: object
            properties:
              field: { type: string }
              factor: { type: number }
        audit: { $ref: '#/components/schemas/AuditStamp' }

    SearchIndexStatus:
      type: object
      properties:
        index_name: { type: string }
        total_docs: { type: integer }
        stale_docs: { type: integer }
        reindex_in_progress: { type: boolean }
        reindex_progress: { type: [number, 'null'], format: float }
        last_successful_reindex_at: { type: [string, 'null'], format: date-time }

    # ----- Estimates (admin) -----------------------------------------------
    AdminEstimateSummary:
      type: object
      properties:
        id: { type: string, format: uuid }
        customer_id: { type: string, format: uuid }
        version: { type: integer }
        status: { type: string }
        total_price: { type: number }
        currency: { type: string }
        lines_count: { type: integer }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    EstimateExplainEntry:
      type: object
      properties:
        line_id: { type: string, format: uuid }
        canonical_product_id: { type: string, format: uuid }
        offer_id: { type: string, format: uuid }
        reason: { type: string }
        scoring:
          type: object
          additionalProperties: { type: number }
        alternatives_considered: { type: integer }

    # ----- Plans grid ------------------------------------------------------
    Money:
      type: object
      required: [amount, currency]
      properties:
        amount: { type: number, format: double }
        currency: { type: string }

    AdminPlanLimits:
      type: object
      properties:
        requests_per_minute: { type: [integer, 'null'] }
        requests_per_hour: { type: [integer, 'null'] }
        requests_per_day: { type: [integer, 'null'] }
        requests_per_week: { type: [integer, 'null'] }
        requests_per_billing_period: { type: [integer, 'null'] }
        data_bytes_per_billing_period: { type: [integer, 'null'], format: int64 }
        burst_capacity: { type: [integer, 'null'] }
        concurrent_requests: { type: [integer, 'null'] }
        websocket_connections: { type: [integer, 'null'] }
        per_endpoint_overrides:
          type: object
          additionalProperties:
            type: object
            properties:
              requests_per_minute: { type: [integer, 'null'] }
              requests_per_hour: { type: [integer, 'null'] }
              request_weight: { type: integer, default: 1 }

    AdminOveragePolicy:
      type: object
      properties:
        mode: { type: string, enum: [block, prepaid_packages, postpaid_meter, downgrade_service] }
        per_extra_request_price:
          oneOf:
            - { $ref: '#/components/schemas/Money' }
            - { type: 'null' }

    AdminPlan:
      type: object
      properties:
        id: { type: string, format: uuid }
        code: { type: string }
        version: { type: integer }
        status: { type: string, enum: [draft, active, archived] }
        name: { type: string }
        description: { type: string }
        visible: { type: boolean }
        tier_rank: { type: integer }
        billing_period: { type: string, enum: [monthly, yearly, custom] }
        price: { $ref: '#/components/schemas/Money' }
        trial_days: { type: [integer, 'null'] }
        limits: { $ref: '#/components/schemas/AdminPlanLimits' }
        features:
          type: array
          items:
            type: object
            properties:
              code: { type: string }
              label: { type: string }
              included: { type: boolean }
        overage_policy: { $ref: '#/components/schemas/AdminOveragePolicy' }
        eligible_for:
          type: array
          items: { type: string, enum: [individual, legal_entity] }
        tags:
          type: array
          items: { type: string }
        custom_contract_required: { type: boolean }
        active_subscriptions_count: { type: integer }
        audit: { $ref: '#/components/schemas/AuditStamp' }

    AdminPlanCreate:
      type: object
      required: [code, name, billing_period, price, limits]
      properties:
        code: { type: string }
        name: { type: string }
        description: { type: string }
        visible: { type: boolean, default: true }
        tier_rank: { type: integer }
        billing_period: { type: string, enum: [monthly, yearly, custom] }
        price: { $ref: '#/components/schemas/Money' }
        trial_days: { type: integer }
        limits: { $ref: '#/components/schemas/AdminPlanLimits' }
        features:
          type: array
          items:
            type: object
            properties:
              code: { type: string }
              label: { type: string }
              included: { type: boolean }
        overage_policy: { $ref: '#/components/schemas/AdminOveragePolicy' }
        eligible_for:
          type: array
          items: { type: string, enum: [individual, legal_entity] }
        tags:
          type: array
          items: { type: string }
        custom_contract_required: { type: boolean, default: false }

    AdminRequestPackage:
      type: object
      properties:
        id: { type: string, format: uuid }
        code: { type: string }
        version: { type: integer }
        status: { type: string, enum: [draft, active, archived] }
        name: { type: string }
        description: { type: string }
        requests_quantity: { type: integer }
        price: { $ref: '#/components/schemas/Money' }
        validity_days: { type: integer }
        stackable: { type: boolean }
        eligible_plans:
          type: array
          items: { type: string }
        sold_count: { type: integer }
        audit: { $ref: '#/components/schemas/AuditStamp' }

    AdminRequestPackageCreate:
      type: object
      required: [code, name, requests_quantity, price, validity_days]
      properties:
        code: { type: string }
        name: { type: string }
        description: { type: string }
        requests_quantity: { type: integer }
        price: { $ref: '#/components/schemas/Money' }
        validity_days: { type: integer }
        stackable: { type: boolean, default: true }
        eligible_plans:
          type: array
          items: { type: string }

    # ----- Subscriptions ---------------------------------------------------
    AdminSubscription:
      type: object
      properties:
        id: { type: string, format: uuid }
        customer_id: { type: string, format: uuid }
        plan_code: { type: string }
        plan_version: { type: integer }
        status: { type: string, enum: [trialing, active, past_due, paused, cancel_scheduled, cancelled, expired] }
        current_period_start: { type: string, format: date-time }
        current_period_end: { type: string, format: date-time }
        renews_at: { type: [string, 'null'], format: date-time }
        cancellation_scheduled_at: { type: [string, 'null'], format: date-time }
        cancellation_reason: { type: [string, 'null'] }
        auto_renew: { type: boolean }
        trial_ends_at: { type: [string, 'null'], format: date-time }
        limits: { $ref: '#/components/schemas/AdminPlanLimits' }
        limits_overrides:
          type: [object, 'null']
          additionalProperties: true
          description: "Manual overrides поверх plan limits (enterprise)"
        overage_policy: { $ref: '#/components/schemas/AdminOveragePolicy' }
        packages_requests_remaining: { type: integer }
        audit: { $ref: '#/components/schemas/AuditStamp' }

    AdminSubscriptionChangeRequest:
      type: object
      required: [plan_code, reason]
      properties:
        plan_code: { type: string }
        apply_at: { type: string, enum: [immediate, next_period], default: immediate }
        reason: { type: string }
        waive_payment: { type: boolean, default: false, description: "Не создавать invoice (для grant)" }
        trial_days_override: { type: [integer, 'null'] }

    LimitsOverrideRequest:
      type: object
      required: [overrides, reason]
      properties:
        overrides: { $ref: '#/components/schemas/AdminPlanLimits' }
        reason: { type: string }
        expires_at: { type: [string, 'null'], format: date-time }

    AdminPurchasedPackage:
      type: object
      properties:
        id: { type: string, format: uuid }
        customer_id: { type: string, format: uuid }
        package_code: { type: string }
        package_version: { type: integer }
        requests_total: { type: integer }
        requests_remaining: { type: integer }
        source: { type: string, enum: [purchase, grant, promo, refund_conversion] }
        purchased_at: { type: string, format: date-time }
        expires_at: { type: [string, 'null'], format: date-time }
        status: { type: string, enum: [pending_payment, active, expired, exhausted, refunded] }
        invoice_id: { type: [string, 'null'], format: uuid }
        reason: { type: [string, 'null'] }

    # ----- Usage analytics --------------------------------------------------
    AdminUsageEvent:
      type: object
      properties:
        id: { type: string, format: uuid }
        customer_id: { type: string, format: uuid }
        request_id: { type: string }
        at: { type: string, format: date-time }
        method: { type: string }
        endpoint: { type: string }
        status_code: { type: integer }
        bytes_in: { type: integer, format: int64 }
        bytes_out: { type: integer, format: int64 }
        request_weight: { type: integer }
        plan_code: { type: string }
        source: { type: string, enum: [plan_quota, package, free_tier] }
        quota_window_hit: { type: [string, 'null'] }
        client_ip: { type: [string, 'null'] }
        user_agent: { type: [string, 'null'] }

    UsageBucket:
      type: object
      properties:
        key: { type: string, description: "customer_id / plan_code / endpoint / timestamp (ISO)" }
        requests: { type: integer }
        data_bytes: { type: integer, format: int64 }
        429_count: { type: integer }
        error_count: { type: integer }
        weighted_units: { type: integer }

    RateLimitHit:
      type: object
      properties:
        at: { type: string, format: date-time }
        customer_id: { type: string, format: uuid }
        plan_code: { type: string }
        endpoint: { type: string }
        window: { type: string, enum: [minute, hour, day, week, month, billing_period] }
        limit: { type: integer }
        burst_over: { type: integer, description: "на сколько запросов превысили в момент обращения" }

    # ----- Billing ---------------------------------------------------------
    AdminInvoice:
      type: object
      properties:
        id: { type: string, format: uuid }
        customer_id: { type: [string, 'null'], format: uuid }
        organization_id: { type: [string, 'null'], format: uuid }
        number: { type: string }
        status: { type: string, enum: [draft, open, paid, void, uncollectible] }
        issued_at: { type: string, format: date-time }
        due_at: { type: [string, 'null'], format: date-time }
        paid_at: { type: [string, 'null'], format: date-time }
        currency: { type: string }
        subtotal: { $ref: '#/components/schemas/Money' }
        tax: { $ref: '#/components/schemas/Money' }
        total: { $ref: '#/components/schemas/Money' }
        refunded_amount: { type: [number, 'null'] }
        refund_reason: { type: [string, 'null'] }
        items:
          type: array
          items:
            type: object
            properties:
              description: { type: string }
              quantity: { type: number }
              unit_price: { $ref: '#/components/schemas/Money' }
              total: { $ref: '#/components/schemas/Money' }

    # ----- Organizations ---------------------------------------------------
    AdminOrgMemberCap:
      type: object
      properties:
        limits: { $ref: '#/components/schemas/AdminPlanLimits' }
        allowed_endpoints:
          type: [array, 'null']
          items: { type: string }
        blocked_endpoints:
          type: array
          items: { type: string }

    AdminOrganization:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        status: { type: string, enum: [active, suspended, closed] }
        legal_info:
          type: [object, 'null']
          additionalProperties: true
        billing_email: { type: [string, 'null'], format: email }
        default_payment_method_id: { type: [string, 'null'], format: uuid }
        owner_user_id: { type: string, format: uuid }
        members_count: { type: integer }
        seats_used: { type: integer }
        seats_limit: { type: [integer, 'null'] }
        plan_code: { type: [string, 'null'] }
        plan_scope: { type: string, enum: [personal, organization], default: organization }
        outstanding_invoice_total:
          oneOf:
            - { $ref: '#/components/schemas/Money' }
            - { type: 'null' }
        audit: { $ref: '#/components/schemas/AuditStamp' }

    AdminOrganizationCreate:
      type: object
      required: [name, owner_user_id]
      properties:
        name: { type: string }
        legal_info:
          type: object
          additionalProperties: true
        billing_email: { type: string, format: email }
        owner_user_id: { type: string, format: uuid }
        initial_plan_code: { type: string }
        initial_seats: { type: integer }
        note: { type: string }

    AdminOrganizationPatch:
      type: object
      properties:
        name: { type: string }
        status: { type: string, enum: [active, suspended, closed] }
        legal_info:
          type: object
          additionalProperties: true
        billing_email: { type: string, format: email }
        default_payment_method_id: { type: string, format: uuid }
        owner_user_id: { type: string, format: uuid }
        note: { type: string }

    AdminOrganizationMember:
      type: object
      properties:
        user_id: { type: string, format: uuid }
        organization_id: { type: string, format: uuid }
        display_name: { type: string }
        email: { type: string, format: email }
        role: { type: string, enum: [owner, admin, member, viewer] }
        status: { type: string, enum: [active, invited, suspended, left] }
        joined_at: { type: [string, 'null'], format: date-time }
        invited_at: { type: [string, 'null'], format: date-time }
        personal_cap: { $ref: '#/components/schemas/AdminOrgMemberCap' }
        cap_override_by_admin:
          type: [object, 'null']
          properties:
            set_by: { type: string }
            reason: { type: string }
            expires_at: { type: [string, 'null'], format: date-time }
        usage_current_period:
          type: object
          properties:
            requests: { type: integer }
            data_bytes: { type: integer, format: int64 }

    AdminMemberCapOverrideRequest:
      type: object
      required: [cap, reason]
      properties:
        cap: { $ref: '#/components/schemas/AdminOrgMemberCap' }
        reason: { type: string }
        expires_at: { type: [string, 'null'], format: date-time }

    AdminOrganizationSubscription:
      allOf:
        - $ref: '#/components/schemas/AdminSubscription'
        - type: object
          properties:
            organization_id: { type: string, format: uuid }
            seats_included: { type: integer }
            seats_used: { type: integer }
            seats_limit: { type: [integer, 'null'] }
            extra_seat_price:
              oneOf:
                - { $ref: '#/components/schemas/Money' }
                - { type: 'null' }
            default_member_cap: { $ref: '#/components/schemas/AdminOrgMemberCap' }
            pool_usage_current_period:
              type: object
              properties:
                requests_used: { type: integer }
                requests_limit: { type: [integer, 'null'] }
                data_bytes_used: { type: integer, format: int64 }
                data_bytes_limit: { type: [integer, 'null'], format: int64 }

    AdminOrgMemberUsage:
      type: object
      properties:
        user_id: { type: string, format: uuid }
        display_name: { type: string }
        requests: { type: integer }
        data_bytes: { type: integer, format: int64 }
        rate_limit_429_count: { type: integer }
        cap_hit_count: { type: integer }

    AdminOrganizationUsageSummary:
      type: object
      properties:
        organization_id: { type: string, format: uuid }
        plan_code: { type: string }
        billing_period:
          type: object
          properties:
            start: { type: string, format: date-time }
            end: { type: string, format: date-time }
        pool:
          type: object
          properties:
            requests_used: { type: integer }
            requests_limit: { type: [integer, 'null'] }
            data_bytes_used: { type: integer, format: int64 }
            data_bytes_limit: { type: [integer, 'null'], format: int64 }
            pool_hit_count: { type: integer }
        seats_used: { type: integer }
        seats_limit: { type: [integer, 'null'] }
        members:
          type: array
          items: { $ref: '#/components/schemas/AdminOrgMemberUsage' }
        packages_requests_remaining: { type: integer }
