openapi: 3.1.0
info:
  title: TenantPilot Internal Finding Responsibility Contract
  version: 1.0.0
  description: |
    Internal review contract for Spec 219.

    These are operator-facing Filament surfaces, not public API endpoints. The document exists so
    the feature has an explicit, reviewable contract under specs/.../contracts/.

paths:
  /admin/t/{tenant}/findings:
    get:
      summary: Tenant findings list with explicit responsibility semantics
      parameters:
        - $ref: '#/components/parameters/TenantPathParam'
      responses:
        '200':
          description: Findings list rendered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FindingListSurface'
  /admin/t/{tenant}/findings/{finding}:
    get:
      summary: Finding detail with owner, assignee, and optional exception-owner context
      parameters:
        - $ref: '#/components/parameters/TenantPathParam'
        - $ref: '#/components/parameters/FindingPathParam'
      responses:
        '200':
          description: Finding detail rendered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FindingDetailSurface'
  /admin/t/{tenant}/findings/{finding}/responsibility:
    post:
      summary: Update finding owner and assignee semantics
      description: Conceptual responsibility-update contract implemented by Filament action endpoints.
      parameters:
        - $ref: '#/components/parameters/TenantPathParam'
        - $ref: '#/components/parameters/FindingPathParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResponsibilityUpdateRequest'
      responses:
        '200':
          description: Responsibility updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponsibilityUpdateResult'
        '403':
          description: Actor is an in-scope tenant member but lacks assignment capability
        '404':
          description: Tenant or finding is outside the actor scope
  /admin/t/{tenant}/findings/{finding}/exception-request:
    post:
      summary: Request exception with explicitly separate exception owner
      description: Conceptual exception-request contract implemented by Filament action endpoints.
      parameters:
        - $ref: '#/components/parameters/TenantPathParam'
        - $ref: '#/components/parameters/FindingPathParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExceptionRequestInput'
      responses:
        '200':
          description: Exception request accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExceptionOwnershipBoundaryResult'

components:
  parameters:
    TenantPathParam:
      name: tenant
      in: path
      required: true
      schema:
        type: string
      description: Tenant route identifier for tenant-scoped findings surfaces.
    FindingPathParam:
      name: finding
      in: path
      required: true
      schema:
        type: integer
      description: Tenant-owned finding identifier.

  schemas:
    UserReference:
      type: object
      additionalProperties: false
      required:
        - id
        - display_name
      properties:
        id:
          type: integer
        display_name:
          type: string

    ResponsibilityState:
      type: string
      description: Uses internal slugs for API and test contracts. The operator-facing UI label for `orphaned_accountability` is `orphaned accountability`.
      enum:
        - orphaned_accountability
        - owned_unassigned
        - assigned

    FindingResponsibilitySummary:
      type: object
      additionalProperties: false
      required:
        - finding_id
        - workflow_status
        - responsibility_state
      properties:
        finding_id:
          type: integer
        workflow_status:
          type: string
        owner:
          allOf:
            - $ref: '#/components/schemas/UserReference'
          nullable: true
        assignee:
          allOf:
            - $ref: '#/components/schemas/UserReference'
          nullable: true
        responsibility_state:
          $ref: '#/components/schemas/ResponsibilityState'
        accountability_gap:
          type: boolean
        exception_owner:
          allOf:
            - $ref: '#/components/schemas/UserReference'
          nullable: true
          description: Present only when a finding exception exists or is shown in current context.

    FindingListSurface:
      type: object
      additionalProperties: false
      required:
        - surface
        - collection_route
        - primary_inspect_model
        - items
      properties:
        surface:
          const: tenant_findings_list
        collection_route:
          const: /admin/t/{tenant}/findings
        primary_inspect_model:
          const: finding
        items:
          type: array
          items:
            $ref: '#/components/schemas/FindingResponsibilitySummary'

    FindingDetailSurface:
      allOf:
        - $ref: '#/components/schemas/FindingResponsibilitySummary'
        - type: object
          additionalProperties: false
          required:
            - surface
            - detail_route
          properties:
            surface:
              const: finding_detail
            detail_route:
              const: /admin/t/{tenant}/findings/{finding}

    ResponsibilityUpdateRequest:
      type: object
      additionalProperties: false
      properties:
        owner_user_id:
          type: integer
          nullable: true
          description: Accountable owner for the finding outcome.
        assignee_user_id:
          type: integer
          nullable: true
          description: Active remediation assignee for the finding.

    ResponsibilityUpdateResult:
      type: object
      additionalProperties: false
      required:
        - change_classification
        - responsibility
      properties:
        change_classification:
          type: string
          description: Uses explicit slugs for single-role clears and `owner_and_assignee` whenever both fields change in one update, including mixed set/clear combinations.
          enum:
            - owner_only
            - assignee_only
            - owner_and_assignee
            - clear_owner
            - clear_assignee
        responsibility:
          $ref: '#/components/schemas/FindingResponsibilitySummary'

    ExceptionRequestInput:
      type: object
      additionalProperties: false
      required:
        - owner_user_id
        - request_reason
        - review_due_at
      properties:
        owner_user_id:
          type: integer
          description: Owner of the exception artifact, not the finding owner.
        request_reason:
          type: string
        review_due_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          nullable: true

    ExceptionOwnershipBoundaryResult:
      type: object
      additionalProperties: false
      required:
        - finding_owner_preserved
        - exception_owner
      properties:
        finding_owner_preserved:
          type: boolean
          const: true
        exception_owner:
          $ref: '#/components/schemas/UserReference'