openapi: 3.1.0
info:
  title: Findings Workflow Semantic Contract
  version: 0.1.0
  description: |
    Semantic contract for tenant-context findings workflow operations and audit review.
    These endpoints describe the canonical server-side behavior required by the feature,
    even if concrete implementation remains behind Filament and Livewire action handlers.
    Human-driven and system-driven lifecycle mutations share the same server-side
    transition gateway and audit contract.
servers:
  - url: http://localhost
paths:
  /admin/t/{tenant}/findings/{finding}:
    get:
      summary: View a tenant-scoped finding
      operationId: viewFinding
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/FindingId'
      responses:
        '200':
          description: Finding returned within the entitled tenant scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Finding'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /admin/t/{tenant}/findings/{finding}/workflow/triage:
    post:
      summary: Triage a finding
      operationId: triageFinding
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/FindingId'
      responses:
        '200':
          $ref: '#/components/responses/FindingMutationSucceeded'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/InvalidTransition'
  /admin/t/{tenant}/findings/{finding}/workflow/start-progress:
    post:
      summary: Move a finding to in progress
      operationId: startFindingProgress
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/FindingId'
      responses:
        '200':
          $ref: '#/components/responses/FindingMutationSucceeded'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/InvalidTransition'
  /admin/t/{tenant}/findings/{finding}/workflow/assign:
    post:
      summary: Assign finding ownership or assignee
      operationId: assignFinding
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/FindingId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                assignee_user_id:
                  type:
                    - integer
                    - 'null'
                owner_user_id:
                  type:
                    - integer
                    - 'null'
      responses:
        '200':
          $ref: '#/components/responses/FindingMutationSucceeded'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/InvalidTransition'
        '422':
          $ref: '#/components/responses/ValidationError'
  /admin/t/{tenant}/findings/{finding}/workflow/resolve:
    post:
      summary: Resolve a finding
      operationId: resolveFinding
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/FindingId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason:
                  type: string
      responses:
        '200':
          $ref: '#/components/responses/FindingMutationSucceeded'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/InvalidTransition'
        '422':
          $ref: '#/components/responses/ValidationError'
  /admin/t/{tenant}/findings/{finding}/workflow/close:
    post:
      summary: Close a finding
      operationId: closeFinding
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/FindingId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason:
                  type: string
      responses:
        '200':
          $ref: '#/components/responses/FindingMutationSucceeded'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/InvalidTransition'
        '422':
          $ref: '#/components/responses/ValidationError'
  /admin/t/{tenant}/findings/{finding}/workflow/risk-accept:
    post:
      summary: Mark a finding as risk accepted
      operationId: riskAcceptFinding
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/FindingId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason:
                  type: string
      responses:
        '200':
          $ref: '#/components/responses/FindingMutationSucceeded'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/InvalidTransition'
        '422':
          $ref: '#/components/responses/ValidationError'
  /admin/t/{tenant}/findings/{finding}/workflow/reopen:
    post:
      summary: Reopen a terminal finding
      operationId: reopenFinding
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/FindingId'
      responses:
        '200':
          $ref: '#/components/responses/FindingMutationSucceeded'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/InvalidTransition'
  /admin/t/{tenant}/findings/workflow/bulk:
    post:
      summary: Execute a bulk workflow mutation over multiple findings
      operationId: bulkMutateFindings
      parameters:
        - $ref: '#/components/parameters/TenantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [operation, finding_ids]
              properties:
                operation:
                  type: string
                  enum: [triage, assign, resolve, close, risk_accept, reopen]
                finding_ids:
                  type: array
                  items:
                    type: integer
                payload:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: Bulk mutation completed with per-record audit history
          content:
            application/json:
              schema:
                type: object
                required: [processed, succeeded, failed]
                properties:
                  processed:
                    type: integer
                  succeeded:
                    type: integer
                  failed:
                    type: integer
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/InvalidTransition'
  /admin/audit-log:
    get:
      summary: Review finding-related audit history on the canonical audit surface
      operationId: listFindingAuditEvents
      parameters:
        - name: resource_type
          in: query
          schema:
            type: string
            enum: [finding]
        - name: tenant_id
          in: query
          schema:
            type: integer
      responses:
        '200':
          description: Audit events visible within current workspace and tenant entitlement
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FindingAuditEvent'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  parameters:
    TenantId:
      name: tenant
      in: path
      required: true
      schema:
        type: integer
    FindingId:
      name: finding
      in: path
      required: true
      schema:
        type: integer
  responses:
    FindingMutationSucceeded:
      description: Workflow mutation completed and durable audit history was written
      content:
        application/json:
          schema:
            type: object
            required: [finding, audit_event]
            properties:
              finding:
                $ref: '#/components/schemas/Finding'
              audit_event:
                $ref: '#/components/schemas/FindingAuditEvent'
    Forbidden:
      description: Viewer is in scope but lacks required capability
    NotFound:
      description: Viewer is not entitled to the requested workspace or tenant scope, or the record is outside that scope, including workspace-mismatched findings in canonical tenant context
    InvalidTransition:
      description: Requested lifecycle mutation is not valid from the current state
    ValidationError:
      description: Request payload is syntactically valid but missing required business fields
  schemas:
    Finding:
      type: object
      required: [id, tenant_id, workspace_id, status, severity]
      properties:
        id:
          type: integer
        tenant_id:
          type: integer
        workspace_id:
          type: integer
        status:
          type: string
          enum: [new, acknowledged, triaged, in_progress, reopened, resolved, closed, risk_accepted]
        severity:
          type: string
          enum: [low, medium, high, critical]
        due_at:
          type:
            - string
            - 'null'
          format: date-time
        resolved_reason:
          type:
            - string
            - 'null'
        closed_reason:
          type:
            - string
            - 'null'
    FindingAuditEvent:
      type: object
      required: [action, resource_type, resource_id, tenant_id, workspace_id, metadata]
      properties:
        action:
          type: string
          example: finding.resolved
        resource_type:
          type: string
          enum: [finding]
        resource_id:
          type: string
        tenant_id:
          type:
            - integer
            - 'null'
        workspace_id:
          type: integer
        metadata:
          $ref: './finding-audit-event.schema.yaml#/components/schemas/FindingAuditMetadata'
        actor_type:
          type: string
          enum: [human, system, scheduled, integration, platform]
          description: Distinguishes system-origin workflow mutations from human-initiated actions on the canonical audit surface.