openapi: 3.1.0
info:
  title: BaselineSnapshot Artifact Truth Operator Contract
  version: 1.0.0
  summary: Logical operator-action contract for baseline snapshot lifecycle and compare guards
  description: |
    This contract captures the intended request/response semantics for the operator-facing
    capture, compare, and snapshot-truth flows in Spec 159. These are logical contracts for
    existing Filament/Livewire-backed actions and read models, not a commitment to public REST endpoints.
servers:
  - url: https://tenantpilot.local
tags:
  - name: BaselineProfiles
  - name: BaselineSnapshots
  - name: BaselineCompare
paths:
  /workspaces/{workspaceId}/baseline-profiles/{profileId}/captures:
    post:
      tags: [BaselineProfiles]
      summary: Start a baseline capture
      description: Starts a baseline capture attempt. The produced snapshot begins in `building` and becomes consumable only if finalized `complete`.
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ProfileId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [sourceTenantId]
              properties:
                sourceTenantId:
                  type: integer
      responses:
        '202':
          description: Capture accepted and queued
          content:
            application/json:
              schema:
                type: object
                required: [operationRunId, snapshot]
                properties:
                  operationRunId:
                    type: integer
                  snapshot:
                    $ref: '#/components/schemas/BaselineSnapshotTruth'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /workspaces/{workspaceId}/baseline-profiles/{profileId}/effective-snapshot:
    get:
      tags: [BaselineProfiles]
      summary: Resolve effective current baseline snapshot
      description: Returns the latest complete snapshot that is valid as current baseline truth for the profile.
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/ProfileId'
      responses:
        '200':
          description: Effective current baseline truth resolved
          content:
            application/json:
              schema:
                type: object
                required: [profileId, effectiveSnapshot]
                properties:
                  profileId:
                    type: integer
                  effectiveSnapshot:
                    oneOf:
                      - $ref: '#/components/schemas/BaselineSnapshotTruth'
                      - type: 'null'
                  latestAttemptedSnapshot:
                    oneOf:
                      - $ref: '#/components/schemas/BaselineSnapshotTruth'
                      - type: 'null'
                  compareAvailability:
                    $ref: '#/components/schemas/CompareAvailability'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /workspaces/{workspaceId}/baseline-snapshots/{snapshotId}:
    get:
      tags: [BaselineSnapshots]
      summary: Read baseline snapshot truth
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - $ref: '#/components/parameters/SnapshotId'
      responses:
        '200':
          description: Snapshot truth returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BaselineSnapshotTruth'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /tenants/{tenantId}/baseline-compares:
    post:
      tags: [BaselineCompare]
      summary: Start baseline compare
      description: Starts compare only when the resolved or explicitly selected snapshot is consumable.
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                baselineSnapshotId:
                  type: integer
                  nullable: true
      responses:
        '202':
          description: Compare accepted and queued
          content:
            application/json:
              schema:
                type: object
                required: [operationRunId, snapshot]
                properties:
                  operationRunId:
                    type: integer
                  snapshot:
                    $ref: '#/components/schemas/BaselineSnapshotTruth'
        '409':
          description: Compare blocked because no consumable snapshot is available
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompareAvailability'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  parameters:
    WorkspaceId:
      name: workspaceId
      in: path
      required: true
      schema:
        type: integer
    ProfileId:
      name: profileId
      in: path
      required: true
      schema:
        type: integer
    SnapshotId:
      name: snapshotId
      in: path
      required: true
      schema:
        type: integer
    TenantId:
      name: tenantId
      in: path
      required: true
      schema:
        type: integer

  responses:
    Forbidden:
      description: Member lacks the required capability
    NotFound:
      description: Workspace or tenant scope is not entitled, or the record is not visible in that scope

  schemas:
    BaselineSnapshotTruth:
      type: object
      required:
        - id
        - workspaceId
        - baselineProfileId
        - lifecycleState
        - consumable
        - capturedAt
      properties:
        id:
          type: integer
        workspaceId:
          type: integer
        baselineProfileId:
          type: integer
        lifecycleState:
          type: string
          enum: [building, complete, incomplete, superseded]
        consumable:
          type: boolean
        capturedAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
          nullable: true
        failedAt:
          type: string
          format: date-time
          nullable: true
        supersededAt:
          type: string
          format: date-time
          nullable: true
        completionMeta:
          type: object
          additionalProperties: true
          nullable: true
        usabilityLabel:
          type: string
          examples: [Complete, Incomplete, Building, Superseded, Not usable for compare]
        reasonCode:
          type: string
          nullable: true
        reasonMessage:
          type: string
          nullable: true

    CompareAvailability:
      type: object
      required:
        - allowed
      properties:
        allowed:
          type: boolean
        effectiveSnapshotId:
          type: integer
          nullable: true
        latestAttemptedSnapshotId:
          type: integer
          nullable: true
        reasonCode:
          type: string
          nullable: true
          enum:
            - no_consumable_snapshot
            - snapshot_building
            - snapshot_incomplete
            - snapshot_superseded
            - invalid_snapshot_selection
        reasonMessage:
          type: string
          nullable: true
        nextAction:
          type: string
          nullable: true
          examples:
            - Wait for capture completion
            - Re-run baseline capture
            - Review failed capture