openapi: 3.0.3
info:
  title: Cordum HTTP API
  version: '2026-04-13'
  description: Canonical OpenAPI 3.0.3 spec for the Cordum gateway HTTP surface, including all REST routes in gateway.go plus
    legacy and prefixed MCP HTTP endpoints.
  contact:
    name: Cordum Team
  license:
    name: Proprietary
servers:
- url: http://localhost:8081
  description: Gateway HTTP (local default)
- url: /
  description: Relative gateway base
tags:
- name: Auth
  description: Authentication and session management
- name: Users
  description: User CRUD (admin only)
- name: APIKeys
  description: API key lifecycle (admin only)
- name: Jobs
  description: Job submission, listing, and lifecycle
- name: DLQ
  description: Dead-letter queue management (admin only)
- name: Workflows
  description: Workflow definition CRUD and execution
- name: WorkflowRuns
  description: Workflow run inspection and control
- name: Approvals
  description: Human-in-the-loop approval queue (admin only)
- name: Policy
  description: Safety policy evaluation, bundles, and snapshots
- name: Config
  description: Hierarchical configuration management
- name: Schemas
  description: JSON schema registry
- name: Locks
  description: Distributed lock management
- name: Packs
  description: Pack installation and management (admin only)
- name: Marketplace
  description: Pack marketplace catalog (admin only)
- name: Health
  description: Health checks and system status
- name: Memory
  description: Memory pointers and artifact storage
- name: MCP
  description: Model Context Protocol (SSE + JSON-RPC)
- name: Streaming
  description: WebSocket event streaming
- name: Workers
  description: Worker agent registration, status, and active job queries
- name: WorkerCredentials
  description: Manage per-worker authentication credentials (admin only)
- name: Pools
  description: Worker pool configuration, topic assignments, and drain operations
- name: Topics
  description: Topic registry for job routing and worker discovery
- name: License
  description: Platform license status, usage metrics, and reload (admin only)
- name: Telemetry
  description: Observability export configuration, consent, and usage stats
- name: AuditExport
  description: Cryptographic audit log export configuration and health checks (admin only)
- name: LegalHold
  description: Legal hold management for audit retention compliance (admin only)
security:
- apiKey: []
- bearerAuth: []
paths:
  /api/v1/auth/config:
    get:
      operationId: getAuthConfig
      summary: Get authentication configuration
      description: Returns the authentication methods enabled for the deployment.
      tags:
      - Auth
      security: []
      responses:
        '200':
          description: Auth configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthConfig'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/login:
    post:
      operationId: login
      summary: Authenticate with username and password
      tags:
      - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Authentication successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/session:
    get:
      operationId: getSession
      summary: Get current session
      description: Returns the authenticated user and token metadata.
      tags:
      - Auth
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Current session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/logout:
    post:
      operationId: logout
      summary: Logout and invalidate session
      tags:
      - Auth
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '204':
          description: Logged out
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/auth/password:
    post:
      operationId: changeOwnPassword
      summary: Change own password
      tags:
      - Auth
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChangePasswordRequest'
      responses:
        '204':
          description: Password changed
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/users:
    post:
      operationId: createUser
      summary: Create a user
      tags:
      - Users
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '201':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthUser'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      operationId: listUsers
      summary: List all users
      tags:
      - Users
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: User list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/AuthUser'
                required:
                - items
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/users/{id}:
    parameters:
    - $ref: '#/components/parameters/UserID'
    - $ref: '#/components/parameters/TenantID'
    put:
      operationId: updateUser
      summary: Update a user
      tags:
      - Users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateUserRequest'
      responses:
        '200':
          description: User updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthUser'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: deleteUser
      summary: Delete a user
      tags:
      - Users
      responses:
        '204':
          description: User deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/users/{id}/password:
    post:
      operationId: resetUserPassword
      summary: Reset a user's password (admin)
      tags:
      - Users
      parameters:
      - $ref: '#/components/parameters/UserID'
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                password:
                  type: string
                  format: password
              required:
              - password
      responses:
        '204':
          description: Password reset
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/keys:
    get:
      operationId: listAPIKeys
      summary: List API keys
      tags:
      - APIKeys
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: API key list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/APIKeyInfo'
                required:
                - items
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      operationId: createAPIKey
      summary: Create an API key
      description: 'The raw secret is returned **only once** in the response. Store it

        securely; it cannot be retrieved again.

        '
      tags:
      - APIKeys
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAPIKeyRequest'
      responses:
        '201':
          description: Key created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateAPIKeyResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/keys/{id}:
    delete:
      operationId: deleteAPIKey
      summary: Revoke an API key
      tags:
      - APIKeys
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      responses:
        '204':
          description: Key revoked
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/jobs:
    post:
      operationId: submitJob
      summary: Submit a new job
      tags:
      - Jobs
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitJobRequest'
      responses:
        '200':
          description: Job accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitJobResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      operationId: listJobs
      summary: List jobs
      tags:
      - Jobs
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
      - name: cursor
        in: query
        schema:
          type: string
      - name: state
        in: query
        schema:
          type: string
      - name: topic
        in: query
        schema:
          type: string
      - name: tenant
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Job list
          content:
            application/json:
              schema:
                allOf:
                - $ref: '#/components/schemas/PaginatedResponse'
                - type: object
                  properties:
                    items:
                      type: array
                      items:
                        $ref: '#/components/schemas/JobSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/jobs/{id}:
    get:
      operationId: getJob
      summary: Get job details
      tags:
      - Jobs
      parameters:
      - $ref: '#/components/parameters/JobID'
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Job detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/jobs/{id}/decisions:
    get:
      operationId: getJobDecisions
      summary: Get safety decisions for a job
      tags:
      - Jobs
      parameters:
      - $ref: '#/components/parameters/JobID'
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Safety decisions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SafetyDecision'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/jobs/{id}/cancel:
    post:
      operationId: cancelJob
      summary: Cancel a running job
      tags:
      - Jobs
      parameters:
      - $ref: '#/components/parameters/JobID'
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Job cancelled
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  state:
                    type: string
                    example: CANCELLED
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/jobs/{id}/remediate:
    post:
      operationId: remediateJob
      summary: Remediate a quarantined job
      tags:
      - Jobs
      parameters:
      - $ref: '#/components/parameters/JobID'
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                remediation_id:
                  type: string
              required:
              - remediation_id
      responses:
        '200':
          description: Remediation submitted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitJobResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/jobs/{id}/stream:
    get:
      operationId: streamJob
      summary: Stream job events (WebSocket)
      description: 'Upgrades to a WebSocket connection and streams real-time events for the

        specified job until it reaches a terminal state.

        '
      tags:
      - Jobs
      - Streaming
      parameters:
      - $ref: '#/components/parameters/JobID'
      - $ref: '#/components/parameters/TenantID'
      responses:
        '101':
          description: WebSocket upgrade
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/traces/{id}:
    get:
      operationId: getTrace
      summary: Get trace records for a job
      tags:
      - Jobs
      parameters:
      - name: id
        in: path
        required: true
        description: Trace ID
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Trace records
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/JobDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/dlq:
    get:
      operationId: listDLQ
      summary: List dead-letter queue entries
      tags:
      - DLQ
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: DLQ entries
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DLQEntry'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/dlq/page:
    get:
      operationId: listDLQPaginated
      summary: List DLQ entries (paginated)
      tags:
      - DLQ
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
      - name: cursor
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Paginated DLQ entries
          content:
            application/json:
              schema:
                allOf:
                - $ref: '#/components/schemas/PaginatedResponse'
                - type: object
                  properties:
                    items:
                      type: array
                      items:
                        $ref: '#/components/schemas/DLQEntry'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/dlq/{job_id}:
    delete:
      operationId: deleteDLQEntry
      summary: Remove a DLQ entry
      tags:
      - DLQ
      parameters:
      - name: job_id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      responses:
        '204':
          description: Entry removed
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/dlq/{job_id}/retry:
    post:
      operationId: retryDLQEntry
      summary: Retry a dead-lettered job
      tags:
      - DLQ
      parameters:
      - name: job_id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Retry enqueued
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workflows:
    get:
      operationId: listWorkflows
      summary: List workflow definitions
      tags:
      - Workflows
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Workflow list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WorkflowDefinition'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      operationId: createWorkflow
      summary: Create or update a workflow definition
      tags:
      - Workflows
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkflowDefinition'
      responses:
        '201':
          description: Workflow created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workflows/{id}:
    parameters:
    - $ref: '#/components/parameters/WorkflowID'
    - $ref: '#/components/parameters/TenantID'
    get:
      operationId: getWorkflow
      summary: Get a workflow definition
      tags:
      - Workflows
      responses:
        '200':
          description: Workflow definition
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowDefinition'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: deleteWorkflow
      summary: Delete a workflow definition
      tags:
      - Workflows
      responses:
        '204':
          description: Workflow deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workflows/{id}/runs:
    parameters:
    - $ref: '#/components/parameters/WorkflowID'
    - $ref: '#/components/parameters/TenantID'
    post:
      operationId: startWorkflowRun
      summary: Start a new workflow run
      tags:
      - Workflows
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Workflow input payload (schema depends on the workflow)
              additionalProperties: true
      responses:
        '200':
          description: Run started
          content:
            application/json:
              schema:
                type: object
                properties:
                  run_id:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      operationId: listWorkflowRuns
      summary: List runs for a workflow
      tags:
      - Workflows
      responses:
        '200':
          description: Run list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RunSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workflows/{id}/dry-run:
    post:
      operationId: dryRunWorkflow
      summary: Dry-run a workflow without side effects
      tags:
      - Workflows
      parameters:
      - $ref: '#/components/parameters/WorkflowID'
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                input:
                  type: object
                  additionalProperties: true
                environment:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: Dry-run result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DryRunResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workflow-runs:
    get:
      operationId: listAllWorkflowRuns
      summary: List workflow runs across all workflows
      tags:
      - WorkflowRuns
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
      - name: cursor
        in: query
        schema:
          type: string
      - name: status
        in: query
        schema:
          type: string
      - name: workflow_id
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Run list
          content:
            application/json:
              schema:
                allOf:
                - $ref: '#/components/schemas/PaginatedResponse'
                - type: object
                  properties:
                    items:
                      type: array
                      items:
                        $ref: '#/components/schemas/RunSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workflow-runs/{id}:
    parameters:
    - $ref: '#/components/parameters/RunID'
    - $ref: '#/components/parameters/TenantID'
    get:
      operationId: getWorkflowRun
      summary: Get workflow run detail
      tags:
      - WorkflowRuns
      responses:
        '200':
          description: Run detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: deleteWorkflowRun
      summary: Delete a workflow run
      tags:
      - WorkflowRuns
      responses:
        '204':
          description: Run deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workflow-runs/{id}/timeline:
    get:
      operationId: getRunTimeline
      summary: Get timeline events for a run
      tags:
      - WorkflowRuns
      parameters:
      - $ref: '#/components/parameters/RunID'
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Timeline events
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TimelineEvent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workflow-runs/{id}/chat:
    parameters:
    - $ref: '#/components/parameters/RunID'
    - $ref: '#/components/parameters/TenantID'
    get:
      operationId: getRunChat
      summary: Get chat messages for a run
      tags:
      - WorkflowRuns
      responses:
        '200':
          description: Chat messages
          content:
            application/json:
              schema:
                allOf:
                - $ref: '#/components/schemas/PaginatedResponse'
                - type: object
                  properties:
                    items:
                      type: array
                      items:
                        $ref: '#/components/schemas/ChatMessage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      operationId: postRunChat
      summary: Post a chat message to a run
      tags:
      - WorkflowRuns
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PostChatRequest'
      responses:
        '200':
          description: Message posted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatMessage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workflow-runs/{id}/rerun:
    post:
      operationId: rerunWorkflow
      summary: Re-run a workflow (optionally from a specific step)
      tags:
      - WorkflowRuns
      parameters:
      - $ref: '#/components/parameters/RunID'
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                from_step:
                  type: string
                  description: Step ID to resume from
                dry_run:
                  type: boolean
                  default: false
      responses:
        '200':
          description: Re-run started
          content:
            application/json:
              schema:
                type: object
                properties:
                  run_id:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workflows/{id}/runs/{run_id}/cancel:
    post:
      operationId: cancelWorkflowRun
      summary: Cancel a running workflow run
      tags:
      - WorkflowRuns
      parameters:
      - $ref: '#/components/parameters/WorkflowID'
      - name: run_id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      responses:
        '204':
          description: Run cancelled
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/approvals:
    get:
      operationId: listApprovals
      summary: List pending approvals
      tags:
      - Approvals
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
      - name: cursor
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Approval list
          content:
            application/json:
              schema:
                allOf:
                - $ref: '#/components/schemas/PaginatedResponse'
                - type: object
                  properties:
                    items:
                      type: array
                      items:
                        $ref: '#/components/schemas/ApprovalItem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/approvals/{job_id}/approve:
    post:
      operationId: approveJob
      summary: Approve a job
      tags:
      - Approvals
      parameters:
      - name: job_id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApprovalDecisionRequest'
      responses:
        '200':
          description: Job approved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitJobResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/approvals/{job_id}/reject:
    post:
      operationId: rejectJob
      summary: Reject a job
      tags:
      - Approvals
      parameters:
      - name: job_id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApprovalDecisionRequest'
      responses:
        '200':
          description: Job rejected
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/approvals/{job_id}/context:
    get:
      operationId: getApprovalContext
      summary: Get enriched approval context for a single job
      description: >
        Returns all enriched context for a single approval in one call:
        blast radius, prior approvals, rollback hints, policy snapshot summary,
        time remaining, and parsed constraints. Used by the approval detail page.
      tags:
      - Approvals
      parameters:
      - name: job_id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Enriched approval context
          content:
            application/json:
              schema:
                type: object
                properties:
                  approval:
                    type: object
                    description: Full approval record with decision summary and workflow metadata
                  blast_radius:
                    type: object
                    properties:
                      systems:
                        type: array
                        items:
                          type: string
                      namespaces:
                        type: array
                        items:
                          type: string
                      resources:
                        type: array
                        items:
                          type: string
                      scope_description:
                        type: string
                  prior_approvals:
                    type: array
                    items:
                      type: object
                      properties:
                        job_id:
                          type: string
                        topic:
                          type: string
                        tenant:
                          type: string
                        decision:
                          type: string
                        resolved_by:
                          type: string
                        resolved_at:
                          type: integer
                          format: int64
                        was_approved:
                          type: boolean
                  rollback_hint:
                    type: string
                    description: Pack-provided rollback instructions
                  policy_snapshot_summary:
                    type: object
                    properties:
                      rule_count:
                        type: integer
                      matched_rule:
                        type: object
                        properties:
                          id:
                            type: string
                          description:
                            type: string
                          decision:
                            type: string
                          constraints_summary:
                            type: string
                      policy_version:
                        type: string
                  time_remaining_ms:
                    type: integer
                    format: int64
                    nullable: true
                    description: Milliseconds until approval deadline, null if no deadline
                  constraints:
                    type: object
                    nullable: true
                    description: Parsed policy constraints from safety decision
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/evaluate:
    post:
      operationId: evaluatePolicy
      summary: Evaluate safety policy against a request
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PolicyCheckRequest'
      responses:
        '200':
          description: Policy evaluation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyCheckResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/simulate:
    post:
      operationId: simulatePolicy
      summary: Simulate policy evaluation (no side effects)
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PolicyCheckRequest'
      responses:
        '200':
          description: Simulation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyCheckResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/explain:
    post:
      operationId: explainPolicy
      summary: Explain policy evaluation reasoning
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PolicyCheckRequest'
      responses:
        '200':
          description: Explanation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyCheckResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/snapshots:
    get:
      operationId: listPolicySnapshots
      summary: List policy snapshots
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Snapshot list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PolicySnapshot'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/rules:
    get:
      operationId: listPolicyRules
      summary: List policy rules
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - name: include_disabled
        in: query
        schema:
          type: boolean
          default: false
      responses:
        '200':
          description: Policy rules
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/PolicyRule'
                  errors:
                    type: array
                    items:
                      type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/output/rules:
    get:
      operationId: listOutputRules
      summary: List output policy rules
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Output rules
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/OutputRule'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/output/rules/{id}:
    put:
      operationId: upsertOutputRule
      summary: Create or update an output policy rule
      tags:
      - Policy
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutputRule'
      responses:
        '200':
          description: Rule upserted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutputRule'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/bundles:
    get:
      operationId: listPolicyBundles
      summary: List policy bundles
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Bundle list
          content:
            application/json:
              schema:
                type: object
                properties:
                  bundles:
                    type: array
                    items:
                      $ref: '#/components/schemas/PolicyBundleSummary'
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/PolicyBundleSummary'
                  updated_at:
                    type: string
                    format: date-time
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/bundles/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    - $ref: '#/components/parameters/TenantID'
    get:
      operationId: getPolicyBundle
      summary: Get a policy bundle with content
      tags:
      - Policy
      responses:
        '200':
          description: Bundle detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyBundleDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      operationId: updatePolicyBundle
      summary: Update a policy bundle
      tags:
      - Policy
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdatePolicyBundleRequest'
      responses:
        '200':
          description: Bundle updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyBundleDetail'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: deletePolicyBundle
      summary: Delete a policy bundle
      tags:
      - Policy
      responses:
        '204':
          description: Policy bundle deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/bundles/{id}/simulate:
    post:
      operationId: simulatePolicyBundle
      summary: Simulate a policy bundle against a request
      tags:
      - Policy
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                request:
                  $ref: '#/components/schemas/PolicyCheckRequest'
                content:
                  type: string
                  description: Optional override bundle YAML content
      responses:
        '200':
          description: Simulation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyCheckResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/bundles/snapshots:
    get:
      operationId: listBundleSnapshots
      summary: List policy bundle snapshots
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Snapshot list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/PolicySnapshot'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      operationId: createBundleSnapshot
      summary: Create a policy bundle snapshot
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: string
      responses:
        '200':
          description: Snapshot created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicySnapshot'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/bundles/snapshots/{id}:
    get:
      operationId: getBundleSnapshot
      summary: Get a policy bundle snapshot
      tags:
      - Policy
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Snapshot detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicySnapshot'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/publish:
    post:
      operationId: publishPolicy
      summary: Publish policy bundles to production
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublishPolicyRequest'
      responses:
        '200':
          description: Policy published
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicySnapshot'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/rollback:
    post:
      operationId: rollbackPolicy
      summary: Rollback policy to a previous snapshot
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RollbackPolicyRequest'
      responses:
        '200':
          description: Policy rolled back
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicySnapshot'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/audit:
    get:
      operationId: getPolicyAudit
      summary: Get policy audit log
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Audit items
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PolicyAuditEntry'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/config:
    get:
      operationId: getConfig
      summary: Get configuration
      tags:
      - Config
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - name: scope
        in: query
        schema:
          type: string
          enum:
          - system
          - org
          - team
          - workflow
          - step
      - name: scope_id
        in: query
        schema:
          type: string
      - name: envelope
        in: query
        description: Whether to wrap in {scope, data} envelope
        schema:
          type: boolean
          default: true
      responses:
        '200':
          description: Configuration data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfigDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      operationId: setConfig
      summary: Set configuration
      tags:
      - Config
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfigDocument'
      responses:
        '204':
          description: Configuration saved
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      operationId: replaceConfig
      summary: Replace configuration
      tags:
      - Config
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfigDocument'
      responses:
        '204':
          description: Configuration saved
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/config/effective:
    get:
      operationId: getEffectiveConfig
      summary: Get merged effective configuration
      description: 'Returns the effective configuration after merging all scopes

        (global -> org -> team -> workflow -> step).

        '
      tags:
      - Config
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - name: org_id
        in: query
        schema:
          type: string
      - name: team_id
        in: query
        schema:
          type: string
      - name: workflow_id
        in: query
        schema:
          type: string
      - name: step_id
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Effective configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfigDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/schemas:
    post:
      operationId: createSchema
      summary: Register a JSON schema
      tags:
      - Schemas
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SchemaRecord'
      responses:
        '204':
          description: Schema registered
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      operationId: listSchemas
      summary: List registered schemas
      tags:
      - Schemas
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Schema list
          content:
            application/json:
              schema:
                type: object
                properties:
                  schemas:
                    type: array
                    items:
                      $ref: '#/components/schemas/SchemaRecord'
                required:
                - schemas
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/schemas/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    - $ref: '#/components/parameters/TenantID'
    get:
      operationId: getSchema
      summary: Get a schema by ID
      tags:
      - Schemas
      responses:
        '200':
          description: Schema detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SchemaRecord'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: deleteSchema
      summary: Delete a schema
      tags:
      - Schemas
      responses:
        '204':
          description: Schema deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/locks:
    get:
      operationId: getLock
      summary: Get lock status for a resource
      tags:
      - Locks
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - name: resource
        in: query
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Lock information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lock'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/locks/acquire:
    post:
      operationId: acquireLock
      summary: Acquire a distributed lock
      tags:
      - Locks
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LockRequest'
      responses:
        '200':
          description: Lock acquired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lock'
        '409':
          $ref: '#/components/responses/Conflict'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/locks/release:
    post:
      operationId: releaseLock
      summary: Release a distributed lock
      tags:
      - Locks
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LockRequest'
      responses:
        '200':
          description: Lock released
          content:
            application/json:
              schema:
                type: object
                properties:
                  lock:
                    $ref: '#/components/schemas/Lock'
                  released:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/locks/renew:
    post:
      operationId: renewLock
      summary: Renew a distributed lock TTL
      tags:
      - Locks
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LockRequest'
      responses:
        '200':
          description: Lock renewed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lock'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/packs:
    get:
      operationId: listPacks
      summary: List installed packs
      tags:
      - Packs
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Pack list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/PackRecord'
                required:
                - items
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/packs/{id}:
    get:
      operationId: getPack
      summary: Get a pack by ID
      tags:
      - Packs
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Pack detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PackRecord'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/packs/install:
    post:
      operationId: installPack
      summary: Install a pack from a bundle file
      tags:
      - Packs
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                bundle:
                  type: string
                  format: binary
                  description: Pack bundle archive
              required:
              - bundle
      responses:
        '200':
          description: Pack installed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PackRecord'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/packs/{id}/uninstall:
    post:
      operationId: uninstallPack
      summary: Uninstall a pack
      tags:
      - Packs
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                purge:
                  type: boolean
                  description: Whether to purge all pack data
                  default: false
      responses:
        '200':
          description: Pack uninstalled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PackRecord'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/packs/{id}/verify:
    post:
      operationId: verifyPack
      summary: Verify a pack's integrity
      tags:
      - Packs
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Verification results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PackVerification'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/marketplace/packs:
    get:
      operationId: listMarketplacePacks
      summary: List packs available in the marketplace
      tags:
      - Marketplace
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Marketplace catalog
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MarketplaceCatalog'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '502':
          $ref: '#/components/responses/BadGateway'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/marketplace/install:
    post:
      operationId: installMarketplacePack
      summary: Install a pack from the marketplace
      tags:
      - Marketplace
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MarketplaceInstallRequest'
      responses:
        '200':
          description: Pack installed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PackRecord'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '502':
          $ref: '#/components/responses/BadGateway'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /health:
    get:
      operationId: healthCheck
      summary: Basic health check
      tags:
      - Health
      security: []
      responses:
        '200':
          description: Service is healthy
          content:
            text/plain:
              schema:
                type: string
                example: ok
  /api/v1/status:
    get:
      operationId: getStatus
      summary: Get system status
      tags:
      - Health
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: System status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workers:
    get:
      operationId: listWorkers
      summary: List registered workers and heartbeats
      tags:
      - Workers
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Worker heartbeats
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/WorkerRuntime'
                required:
                - items
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/memory:
    get:
      operationId: getMemory
      summary: Get memory data by pointer or key
      tags:
      - Memory
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - name: ptr
        in: query
        description: Memory pointer
        schema:
          type: string
      - name: key
        in: query
        description: Memory key
        schema:
          type: string
      responses:
        '200':
          description: Memory data
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/artifacts:
    post:
      operationId: createArtifact
      summary: Upload an artifact
      tags:
      - Memory
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateArtifactRequest'
      responses:
        '200':
          description: Artifact stored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateArtifactResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/artifacts/{ptr}:
    get:
      operationId: getArtifact
      summary: Get an artifact by pointer
      tags:
      - Memory
      parameters:
      - name: ptr
        in: path
        required: true
        description: Artifact pointer
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Artifact data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArtifactDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /mcp/sse:
    get:
      operationId: mcpSSE
      summary: MCP Server-Sent Events stream
      description: 'Opens an SSE connection to the MCP server. The server sends JSON-RPC

        notifications and responses over this channel.

        '
      tags:
      - MCP
      security:
      - apiKey: []
      - bearerAuth: []
      responses:
        '200':
          description: SSE stream opened
          content:
            text/event-stream:
              schema:
                type: string
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      parameters:
      - $ref: '#/components/parameters/TenantID'
  /mcp/message:
    post:
      operationId: mcpMessage
      summary: Send a JSON-RPC 2.0 message to MCP server
      tags:
      - MCP
      security:
      - apiKey: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JsonRpcRequest'
      responses:
        '200':
          description: JSON-RPC response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JsonRpcResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      parameters:
      - $ref: '#/components/parameters/TenantID'
  /mcp/status:
    get:
      operationId: mcpStatus
      summary: Get MCP server status
      tags:
      - MCP
      security:
      - apiKey: []
      - bearerAuth: []
      responses:
        '200':
          description: MCP status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/McpStatus'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      parameters:
      - $ref: '#/components/parameters/TenantID'
  /api/v1/stream:
    get:
      operationId: streamEvents
      summary: WebSocket event bus stream
      description: 'Upgrades to a WebSocket connection. Authenticates via the

        `cordum-api-key.<base64>` subprotocol. Streams real-time bus events

        (job state changes, heartbeats, policy updates, etc.).



        Authentication: provide `X-Tenant-ID` and authenticate via `Sec-WebSocket-Protocol: cordum-api-key, <base64url(api_key)>`.'
      tags:
      - Streaming
      responses:
        '101':
          description: WebSocket upgrade
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/health:
    get:
      operationId: healthCheckV1
      summary: Gateway health check through the API namespace
      tags:
      - Health
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Service is healthy
          content:
            text/plain:
              schema:
                type: string
                example: ok
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/auth/roles:
    get:
      operationId: listRoles
      summary: List RBAC roles
      tags:
      - Auth
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Role inventory
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/auth/roles/{name}:
    parameters:
    - $ref: '#/components/parameters/RoleName'
    - $ref: '#/components/parameters/TenantID'
    get:
      operationId: getRole
      summary: Get a single RBAC role
      tags:
      - Auth
      responses:
        '200':
          description: Role detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoleDetailResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    put:
      operationId: putRole
      summary: Create or update an RBAC role
      tags:
      - Auth
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoleRequest'
      responses:
        '200':
          description: Role saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  role:
                    $ref: '#/components/schemas/RoleDefinition'
                required:
                - role
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/TierLimit'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    delete:
      operationId: deleteRole
      summary: Delete a custom RBAC role
      tags:
      - Auth
      responses:
        '200':
          description: Role deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
                  name:
                    type: string
                required:
                - deleted
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/TierLimit'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/workers/{id}:
    parameters:
    - $ref: '#/components/parameters/WorkerID'
    - $ref: '#/components/parameters/TenantID'
    get:
      operationId: getWorker
      summary: Get a single worker runtime snapshot
      tags:
      - Workers
      responses:
        '200':
          description: Worker detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkerRuntime'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/workers/{id}/jobs:
    parameters:
    - $ref: '#/components/parameters/WorkerID'
    - $ref: '#/components/parameters/TenantID'
    - $ref: '#/components/parameters/LimitQ'
    get:
      operationId: getWorkerJobs
      summary: List jobs associated with a worker
      tags:
      - Workers
      responses:
        '200':
          description: Jobs for worker
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/JobRecord'
                required:
                - items
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/workers/credentials:
    get:
      operationId: listWorkerCredentials
      summary: List external worker credentials
      tags:
      - WorkerCredentials
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Credential inventory
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/WorkerCredential'
                required:
                - items
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    post:
      operationId: createWorkerCredential
      summary: Create or rotate a worker credential
      tags:
      - WorkerCredentials
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                worker_id:
                  type: string
                allowed_pools:
                  type: array
                  items:
                    type: string
                allowed_topics:
                  type: array
                  items:
                    type: string
              required:
              - worker_id
      responses:
        '200':
          description: Credential rotated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkerCredentialIssue'
        '201':
          description: Credential created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkerCredentialIssue'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/workers/credentials/{worker_id}:
    parameters:
    - $ref: '#/components/parameters/WorkerCredentialID'
    - $ref: '#/components/parameters/TenantID'
    delete:
      operationId: deleteWorkerCredential
      summary: Revoke a worker credential
      tags:
      - WorkerCredentials
      responses:
        '204':
          description: Credential revoked
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/pools:
    get:
      operationId: listPools
      summary: List worker pools
      tags:
      - Pools
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Pool runtime inventory
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/PoolListItem'
                required:
                - items
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/pools/{name}:
    parameters:
    - $ref: '#/components/parameters/PoolName'
    - $ref: '#/components/parameters/TenantID'
    get:
      operationId: getPool
      summary: Get pool detail
      tags:
      - Pools
      responses:
        '200':
          description: Pool detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoolDetail'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      operationId: createPool
      summary: Create a worker pool
      tags:
      - Pools
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                requires:
                  type: array
                  items:
                    type: string
                description:
                  type: string
      responses:
        '201':
          description: Pool created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoolMutation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
    patch:
      operationId: updatePool
      summary: Update a worker pool
      tags:
      - Pools
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                requires:
                  type: array
                  items:
                    type: string
                description:
                  type: string
                status:
                  type: string
      responses:
        '200':
          description: Pool updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoolMutation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
    delete:
      operationId: deletePool
      summary: Delete a worker pool
      tags:
      - Pools
      parameters:
      - $ref: '#/components/parameters/PoolName'
      - $ref: '#/components/parameters/TenantID'
      - $ref: '#/components/parameters/ForceQ'
      responses:
        '204':
          description: Pool deleted
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /api/v1/pools/{name}/drain:
    parameters:
    - $ref: '#/components/parameters/PoolName'
    - $ref: '#/components/parameters/TenantID'
    post:
      operationId: drainPool
      summary: Mark a pool as draining
      tags:
      - Pools
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                timeout_seconds:
                  type: integer
                  minimum: 1
      responses:
        '200':
          description: Pool drain started
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoolMutation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /api/v1/pools/{name}/topics/{topic}:
    parameters:
    - $ref: '#/components/parameters/PoolName'
    - $ref: '#/components/parameters/TopicPath'
    - $ref: '#/components/parameters/TenantID'
    put:
      operationId: addTopicToPool
      summary: Add a topic mapping to a pool
      tags:
      - Pools
      responses:
        '204':
          description: Topic mapped to pool
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
    delete:
      operationId: removeTopicFromPool
      summary: Remove a topic mapping from a pool
      tags:
      - Pools
      responses:
        '204':
          description: Topic mapping removed
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /api/v1/topics:
    get:
      operationId: listTopics
      summary: List topics
      tags:
      - Topics
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Topic registry
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/TopicResponse'
                  registry_empty:
                    type: boolean
                required:
                - items
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    post:
      operationId: createTopic
      summary: Create or replace a topic registration
      tags:
      - Topics
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                pool:
                  type: string
                input_schema_id:
                  type: string
                output_schema_id:
                  type: string
                pack_id:
                  type: string
                requires:
                  type: array
                  items:
                    type: string
                risk_tags:
                  type: array
                  items:
                    type: string
                status:
                  type: string
              required:
              - name
      responses:
        '200':
          description: Topic replaced
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TopicResponse'
        '201':
          description: Topic created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TopicResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/topics/{name}:
    parameters:
    - $ref: '#/components/parameters/TopicName'
    - $ref: '#/components/parameters/TenantID'
    delete:
      operationId: deleteTopic
      summary: Delete a topic registration
      tags:
      - Topics
      responses:
        '204':
          description: Topic deleted
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/license:
    get:
      operationId: getLicense
      summary: Get resolved license metadata
      tags:
      - License
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: License metadata
          content:
            application/json:
              schema:
                type: object
                properties:
                  plan:
                    type: string
                  entitlements:
                    $ref: '#/components/schemas/GenericObject'
                  rights:
                    $ref: '#/components/schemas/GenericObject'
                  license:
                    $ref: '#/components/schemas/LicenseInfo'
                  expiry_status:
                    type: string
                required:
                - plan
                - entitlements
                - rights
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/license/usage:
    get:
      operationId: getLicenseUsage
      summary: Get entitlement usage vs limits
      tags:
      - License
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Current license usage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/license/reload:
    post:
      operationId: reloadLicense
      summary: Reload the active license
      tags:
      - License
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: License reloaded
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  plan:
                    type: string
                  license:
                    $ref: '#/components/schemas/LicenseInfo'
                required:
                - status
                - plan
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/telemetry/status:
    get:
      operationId: getTelemetryStatus
      summary: Telemetry status
      tags:
      - Telemetry
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  mode:
                    type: string
                  endpoint:
                    type: string
                  last_collected_at:
                    type: string
                    format: date-time
                  last_reported_at:
                    type: string
                    format: date-time
                required:
                - mode
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/telemetry/inspect:
    get:
      operationId: inspectTelemetryPayload
      summary: Telemetry inspect
      tags:
      - Telemetry
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/telemetry/export:
    get:
      operationId: exportTelemetryPayload
      summary: Telemetry export
      tags:
      - Telemetry
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/telemetry/usage:
    get:
      operationId: getTelemetryUsage
      summary: Telemetry usage
      tags:
      - Telemetry
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/telemetry/consent:
    post:
      operationId: setTelemetryConsent
      summary: Set telemetry mode
      tags:
      - Telemetry
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                mode:
                  type: string
                  enum:
                  - 'off'
                  - local_only
                  - anonymous
              required:
              - mode
      responses:
        '200':
          description: Telemetry mode updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  mode:
                    type: string
                required:
                - status
                - mode
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/admin/locks:
    get:
      operationId: listAdminLocks
      summary: List active distributed locks
      tags:
      - Locks
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Active lock entries
          content:
            application/json:
              schema:
                type: object
                properties:
                  locks:
                    type: array
                    items:
                      $ref: '#/components/schemas/AdminLock'
                required:
                - locks
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/audit/export/health:
    get:
      operationId: getAuditExportHealth
      summary: Audit export health
      tags:
      - AuditExport
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/TierLimit'
  /api/v1/audit/export/config:
    get:
      operationId: getAuditExportConfig
      summary: Audit export config
      tags:
      - AuditExport
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/audit/export/test:
    post:
      operationId: testAuditExport
      summary: Audit export test
      tags:
      - AuditExport
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/TierLimit'
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/audit/legal-hold:
    post:
      operationId: createLegalHold
      summary: Create a legal hold
      tags:
      - LegalHold
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tenant_id:
                  type: string
                reason:
                  type: string
              required:
              - reason
      responses:
        '201':
          description: Legal hold created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/TierLimit'
        '409':
          $ref: '#/components/responses/Conflict'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/audit/legal-holds:
    get:
      operationId: listLegalHolds
      summary: List legal holds
      tags:
      - LegalHold
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - $ref: '#/components/parameters/TenantQ'
      responses:
        '200':
          description: Legal hold inventory
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/TierLimit'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/audit/legal-hold/{id}:
    parameters:
    - $ref: '#/components/parameters/HoldID'
    - $ref: '#/components/parameters/TenantID'
    delete:
      operationId: releaseLegalHold
      summary: Release a legal hold
      tags:
      - LegalHold
      responses:
        '200':
          description: Legal hold released
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/TierLimit'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/approvals/{job_id}/repair:
    post:
      operationId: repairApproval
      summary: Inspect or apply approval repair
      tags:
      - Approvals
      parameters:
      - name: job_id
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                apply:
                  type: boolean
                note:
                  type: string
      responses:
        '200':
          description: Repair inspection or successful repair
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Repair conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericObject'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/output/stats:
    get:
      operationId: getOutputPolicyStats
      summary: Get aggregate output policy statistics
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      - $ref: '#/components/parameters/LimitQ'
      responses:
        '200':
          description: Output policy statistics
          content:
            application/json:
              schema:
                type: object
                properties:
                  total_checks_24h:
                    type: integer
                    format: int64
                  quarantined_24h:
                    type: integer
                    format: int64
                  avg_latency_ms:
                    type: integer
                    format: int64
                  last_check_at:
                    type: string
                    format: date-time
                required:
                - total_checks_24h
                - quarantined_24h
                - avg_latency_ms
                - last_check_at
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/velocity-rules:
    get:
      operationId: listVelocityRules
      summary: List velocity rules
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Velocity rule inventory
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/VelocityRule'
                  count:
                    type: integer
                  limit:
                    type: integer
                    format: int64
                  updated_at:
                    type: string
                    format: date-time
                  upgrade_url:
                    type: string
                  errors:
                    type: array
                    items:
                      $ref: '#/components/schemas/GenericObject'
                required:
                - items
                - count
                - limit
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      operationId: createVelocityRule
      summary: Create a velocity rule
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VelocityRule'
      responses:
        '201':
          description: Velocity rule created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VelocityRule'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/TierLimit'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/velocity-rules/stats:
    get:
      operationId: getVelocityRuleStats
      summary: Get velocity rule statistics
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      responses:
        '200':
          description: Velocity rule stats
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/VelocityStats'
                  top_rules:
                    type: array
                    items:
                      $ref: '#/components/schemas/VelocityStats'
                  generated_at:
                    type: string
                    format: date-time
                  errors:
                    type: array
                    items:
                      $ref: '#/components/schemas/GenericObject'
                required:
                - items
                - top_rules
                - generated_at
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/velocity-rules/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    - $ref: '#/components/parameters/TenantID'
    put:
      operationId: updateVelocityRule
      summary: Update a velocity rule
      tags:
      - Policy
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VelocityRule'
      responses:
        '200':
          description: Velocity rule updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VelocityRule'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/TierLimit'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: deleteVelocityRule
      summary: Delete a velocity rule
      tags:
      - Policy
      responses:
        '204':
          description: Velocity rule deleted
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/TierLimit'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/analytics:
    post:
      operationId: policyAnalytics
      summary: Compute per-rule quality analytics (false positive rates, approval fatigue)
      description: >
        Analyzes policy rule quality over a time range. Returns per-rule metrics
        including hit counts, override rates, approval latency, and daily trends.
        Read-only — no side effects.
      tags:
      - Policy
      security:
      - apiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                from:
                  type: string
                  format: date-time
                  description: Start of analysis window (RFC3339)
                to:
                  type: string
                  format: date-time
                  description: End of analysis window (RFC3339, max 7 days from 'from')
                rule_filter:
                  type: string
                  description: Optional rule ID to restrict analysis to a single rule
              required:
              - from
              - to
      responses:
        '200':
          description: Per-rule analytics
          content:
            application/json:
              schema:
                type: object
                properties:
                  time_range:
                    type: object
                    properties:
                      from:
                        type: string
                        format: date-time
                      to:
                        type: string
                        format: date-time
                  rules:
                    type: array
                    items:
                      type: object
                      properties:
                        rule_id:
                          type: string
                        hit_count:
                          type: integer
                        approval_count:
                          type: integer
                        override_count:
                          type: integer
                        override_rate:
                          type: number
                          description: override_count / approval_count (0 if no approvals)
                        avg_approval_latency_ms:
                          type: integer
                          format: int64
                        daily_hits:
                          type: array
                          items:
                            type: integer
                          description: Per-day hit counts (index 0 = oldest day)
                  summary:
                    type: object
                    properties:
                      total_rules:
                        type: integer
                      total_hits:
                        type: integer
                      total_overrides:
                        type: integer
                      highest_override_rule_id:
                        type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/policy/replay:
    post:
      operationId: replayPolicyDecisions
      summary: Replay historical jobs against a candidate policy
      tags:
      - Policy
      parameters:
      - $ref: '#/components/parameters/TenantID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PolicyReplayRequest'
      responses:
        '200':
          description: Replay result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyReplayResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /api/v1/mcp/sse:
    get:
      operationId: mcpSSEV1
      summary: MCP Server-Sent Events stream
      description: 'Opens an SSE connection to the MCP server. The server sends JSON-RPC

        notifications and responses over this channel.

        '
      tags:
      - MCP
      security:
      - apiKey: []
      - bearerAuth: []
      responses:
        '200':
          description: SSE stream opened
          content:
            text/event-stream:
              schema:
                type: string
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      parameters:
      - $ref: '#/components/parameters/TenantID'
  /api/v1/mcp/message:
    post:
      operationId: mcpMessageV1
      summary: Send a JSON-RPC 2.0 message to MCP server
      tags:
      - MCP
      security:
      - apiKey: []
      - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JsonRpcRequest'
      responses:
        '200':
          description: JSON-RPC response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JsonRpcResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      parameters:
      - $ref: '#/components/parameters/TenantID'
  /api/v1/mcp/status:
    get:
      operationId: mcpStatusV1
      summary: Get MCP server status
      tags:
      - MCP
      security:
      - apiKey: []
      - bearerAuth: []
      responses:
        '200':
          description: MCP status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/McpStatus'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      parameters:
      - $ref: '#/components/parameters/TenantID'
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key issued by the Cordum admin.
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT token obtained from /api/v1/auth/login.
  parameters:
    TenantID:
      name: X-Tenant-ID
      in: header
      required: true
      description: Tenant isolation header (required on all protected routes).
      schema:
        type: string
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Optional idempotency key for safe retries.
      schema:
        type: string
        format: uuid
    JobID:
      name: id
      in: path
      required: true
      description: Job ID
      schema:
        type: string
    WorkflowID:
      name: id
      in: path
      required: true
      description: Workflow ID
      schema:
        type: string
    RunID:
      name: id
      in: path
      required: true
      description: Workflow run ID
      schema:
        type: string
    UserID:
      name: id
      in: path
      required: true
      description: User ID
      schema:
        type: string
    RoleName:
      name: name
      in: path
      required: true
      schema:
        type: string
    WorkerID:
      name: id
      in: path
      required: true
      schema:
        type: string
    WorkerCredentialID:
      name: worker_id
      in: path
      required: true
      schema:
        type: string
    PoolName:
      name: name
      in: path
      required: true
      schema:
        type: string
    TopicName:
      name: name
      in: path
      required: true
      schema:
        type: string
    TopicPath:
      name: topic
      in: path
      required: true
      schema:
        type: string
    HoldID:
      name: id
      in: path
      required: true
      schema:
        type: string
    LimitQ:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
    ForceQ:
      name: force
      in: query
      required: false
      schema:
        type: boolean
        default: false
    TenantQ:
      name: tenant
      in: query
      required: false
      schema:
        type: string
  responses:
    BadRequest:
      description: Bad request — invalid or missing parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Unauthorized — missing or invalid credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Forbidden — insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Conflict:
      description: Conflict — resource state conflict
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    PayloadTooLarge:
      description: Payload too large
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Too many requests — rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the rate limit resets
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    BadGateway:
      description: Bad gateway — upstream service error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ServiceUnavailable:
      description: Service unavailable
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    TierLimit:
      description: Forbidden by current license entitlements
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/GenericObject'
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message
        status:
          type: integer
          description: HTTP status code
      required:
      - error
      - status
    PaginatedResponse:
      type: object
      properties:
        next_cursor:
          type: string
          nullable: true
          description: Cursor for the next page (null if no more pages)
    AuthConfig:
      type: object
      properties:
        password_enabled:
          type: boolean
        user_auth_enabled:
          type: boolean
        saml_enabled:
          type: boolean
        saml_enterprise:
          type: boolean
        saml_login_url:
          type: string
        saml_metadata_url:
          type: string
        oidc_enabled:
          type: boolean
        oidc_issuer:
          type: string
        oidc_login_url:
          type: string
        oidc_client_id:
          type: string
        oidc_redirect_uri:
          type: string
        oidc_scopes:
          type: array
          items:
            type: string
        oidc_client_secret_masked:
          type: string
        session_ttl:
          type: string
        require_rbac:
          type: boolean
        require_principal:
          type: boolean
        default_tenant:
          type: string
    LoginRequest:
      type: object
      properties:
        username:
          type: string
        password:
          type: string
          format: password
        tenant:
          type: string
      required:
      - username
      - password
    LoginResponse:
      type: object
      properties:
        token:
          type: string
        expires_at:
          type: string
          format: date-time
        user:
          $ref: '#/components/schemas/AuthUser'
    ChangePasswordRequest:
      type: object
      properties:
        current_password:
          type: string
          format: password
        new_password:
          type: string
          format: password
      required:
      - current_password
      - new_password
    AuthUser:
      type: object
      properties:
        id:
          type: string
        username:
          type: string
        email:
          type: string
          format: email
        display_name:
          type: string
        tenant:
          type: string
        roles:
          type: array
          items:
            type: string
        source:
          type: string
          description: Auth source (local, saml, oidc)
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        last_login_at:
          type: string
          format: date-time
          nullable: true
    CreateUserRequest:
      type: object
      properties:
        username:
          type: string
        email:
          type: string
          format: email
        password:
          type: string
          format: password
        tenant:
          type: string
        role:
          type: string
      required:
      - username
      - password
    UpdateUserRequest:
      type: object
      properties:
        email:
          type: string
          format: email
        display_name:
          type: string
        roles:
          type: array
          items:
            type: string
    APIKeyInfo:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        prefix:
          type: string
          description: First characters of the key for identification
        scopes:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
        lastUsed:
          type: string
          format: date-time
          nullable: true
        usageCount:
          type: integer
        expiresAt:
          type: string
          format: date-time
          nullable: true
    CreateAPIKeyRequest:
      type: object
      properties:
        name:
          type: string
        scopes:
          type: array
          items:
            type: string
        expiresAt:
          type: string
          format: date-time
          nullable: true
      required:
      - name
    CreateAPIKeyResponse:
      type: object
      properties:
        key:
          $ref: '#/components/schemas/APIKeyInfo'
        secret:
          type: string
          description: Raw API key secret (shown only once)
    SubmitJobRequest:
      type: object
      properties:
        prompt:
          type: string
          description: The job payload / prompt text
        topic:
          type: string
          default: job.default
          description: Routing topic for worker dispatch
        adapter_id:
          type: string
          description: Adapter identifier for the target worker
        priority:
          type: string
          description: Job scheduling priority
          enum:
          - batch
          - critical
          - interactive
          default: interactive
        context:
          type: object
          additionalProperties: true
          description: Arbitrary context payload for the job
        memory_id:
          type: string
          description: Memory store identifier for context retrieval
        context_mode:
          type: string
          description: Context retrieval mode (e.g. full, summary)
        tenant_id:
          type: string
          description: Tenant identifier for multi-tenant isolation
        principal_id:
          type: string
          description: Principal (user/service) submitting the job
        actor_id:
          type: string
          description: Actor identifier for audit attribution
        actor_type:
          type: string
          description: Actor type (human, agent, system)
        idempotency_key:
          type: string
          description: Client-provided idempotency key to prevent duplicate jobs
        pack_id:
          type: string
          description: Integration pack identifier
        capability:
          type: string
          description: Required worker capability for dispatch
        risk_tags:
          type: array
          items:
            type: string
          description: Risk classification tags for policy evaluation
        requires:
          type: array
          items:
            type: string
          description: Required worker capabilities for dispatch matching
        org_id:
          type: string
          description: Organization identifier for hierarchical tenancy
        team_id:
          type: string
          description: Team identifier within the organization
        project_id:
          type: string
          description: Project identifier for scoped job tracking
        labels:
          type: object
          additionalProperties:
            type: string
          description: Arbitrary key-value labels for filtering and blast radius
        max_input_tokens:
          type: integer
          format: int32
          description: Maximum input token budget for the job
        allow_summarization:
          type: boolean
          description: Whether the worker may summarize oversized input
        allow_retrieval:
          type: boolean
          description: Whether the worker may retrieve external context
        tags:
          type: array
          items:
            type: string
          description: Searchable tags for job discovery
        max_output_tokens:
          type: integer
          format: int64
          description: Maximum output token budget
        max_total_tokens:
          type: integer
          format: int64
          description: Maximum total (input + output) token budget
        deadline_ms:
          type: integer
          format: int64
          description: Job deadline in milliseconds from submission (0 = no deadline)
      required:
      - prompt
      - topic
    SubmitJobResponse:
      type: object
      properties:
        job_id:
          type: string
        trace_id:
          type: string
    JobSummary:
      type: object
      properties:
        id:
          type: string
        state:
          type: string
        topic:
          type: string
        tenant:
          type: string
        updated_at:
          type: string
          format: date-time
    JobDetail:
      allOf:
      - $ref: '#/components/schemas/JobSummary'
      - type: object
        properties:
          trace_id:
            type: string
          prompt:
            type: string
          context_ptr:
            type: string
            nullable: true
          result_ptr:
            type: string
            nullable: true
          result:
            type: object
            additionalProperties: true
            nullable: true
          capability:
            type: string
          risk_tags:
            type: array
            items:
              type: string
          labels:
            type: object
            additionalProperties:
              type: string
          adapter_id:
            type: string
          priority:
            type: integer
          created_at:
            type: string
            format: date-time
          started_at:
            type: string
            format: date-time
            nullable: true
          completed_at:
            type: string
            format: date-time
            nullable: true
          error:
            type: string
            nullable: true
          retry_count:
            type: integer
          decisions:
            type: array
            items:
              $ref: '#/components/schemas/SafetyDecision'
            nullable: true
    SafetyDecision:
      type: object
      description: Result of safety kernel policy evaluation for a job
      properties:
        rule_id:
          type: string
          description: ID of the matched policy rule
        action:
          type: string
          description: Safety decision outcome
          enum:
          - ALLOW
          - DENY
          - REQUIRE_APPROVAL
          - THROTTLE
          - ALLOW_WITH_CONSTRAINTS
          - UNAVAILABLE
        reason:
          type: string
          description: Human-readable explanation for the decision
        constraints:
          type: object
          additionalProperties: true
          nullable: true
          description: Policy constraints applied if action is ALLOW_WITH_CONSTRAINTS
        evaluated_at:
          type: string
          format: date-time
          description: Timestamp of the policy evaluation
    DLQEntry:
      type: object
      properties:
        job_id:
          type: string
        topic:
          type: string
        tenant:
          type: string
        error:
          type: string
        failed_at:
          type: string
          format: date-time
        retry_count:
          type: integer
        original_state:
          type: string
    WorkflowDefinition:
      type: object
      properties:
        id:
          type: string
        org_id:
          type: string
        team_id:
          type: string
        name:
          type: string
        description:
          type: string
        version:
          type: string
        timeout_sec:
          type: integer
        steps:
          type: object
          description: Map of step ID to step definition
          additionalProperties:
            $ref: '#/components/schemas/WorkflowStep'
        config:
          type: object
          additionalProperties: true
      required:
      - id
      - name
      - steps
    WorkflowStep:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
          description: Step type (job, switch, parallel, loop, transform, storage, subworkflow, approval)
        name:
          type: string
        depends_on:
          type: array
          items:
            type: string
        config:
          type: object
          additionalProperties: true
        timeout_sec:
          type: integer
        retry:
          type: object
          properties:
            max_attempts:
              type: integer
            backoff_sec:
              type: integer
      required:
      - id
      - type
    RunSummary:
      type: object
      properties:
        id:
          type: string
        workflow_id:
          type: string
        status:
          type: string
          description: Workflow run lifecycle status (lowercase)
          enum:
          - pending
          - running
          - waiting
          - succeeded
          - failed
          - denied
          - cancelled
          - timed_out
        started_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
          nullable: true
        error:
          type: object
          additionalProperties: true
          nullable: true
          description: Error details as key-value map (e.g. {code, message})
    RunDetail:
      allOf:
      - $ref: '#/components/schemas/RunSummary'
      - type: object
        properties:
          input:
            type: object
            additionalProperties: true
          output:
            type: object
            additionalProperties: true
            nullable: true
          steps:
            type: object
            description: Map of step ID to step run status
            additionalProperties:
              $ref: '#/components/schemas/RunStepStatus'
          created_by:
            type: string
          dry_run:
            type: boolean
    RunStepStatus:
      type: object
      properties:
        step_id:
          type: string
        status:
          type: string
        started_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        job_id:
          type: string
          nullable: true
        error:
          type: string
          nullable: true
        output:
          type: object
          additionalProperties: true
          nullable: true
    TimelineEvent:
      type: object
      properties:
        timestamp:
          type: string
          format: date-time
        type:
          type: string
        step_id:
          type: string
          nullable: true
        message:
          type: string
        data:
          type: object
          additionalProperties: true
          nullable: true
    ChatMessage:
      type: object
      properties:
        id:
          type: string
        run_id:
          type: string
        content:
          type: string
        role:
          type: string
          enum:
          - user
          - assistant
          - system
        step_id:
          type: string
          nullable: true
        job_id:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
    PostChatRequest:
      type: object
      properties:
        content:
          type: string
        role:
          type: string
          enum:
          - user
          - assistant
          - system
        step_id:
          type: string
        job_id:
          type: string
      required:
      - content
      - role
    DryRunResult:
      type: object
      properties:
        steps:
          type: array
          items:
            $ref: '#/components/schemas/RunStepStatus'
        warnings:
          type: array
          items:
            type: string
        estimated_duration_sec:
          type: number
    ApprovalItem:
      type: object
      properties:
        job:
          $ref: '#/components/schemas/JobSummary'
        decision:
          type: string
          enum:
          - PENDING
          - APPROVED
          - REJECTED
        policy_snapshot:
          type: string
          nullable: true
        policy_rule_id:
          type: string
          nullable: true
        policy_reason:
          type: string
          nullable: true
        constraints:
          type: object
          additionalProperties: true
          nullable: true
        approval_required:
          type: boolean
        requested_at:
          type: string
          format: date-time
        decided_at:
          type: string
          format: date-time
          nullable: true
        decided_by:
          type: string
          nullable: true
    ApprovalDecisionRequest:
      type: object
      properties:
        reason:
          type: string
        note:
          type: string
      required:
      - reason
    PolicyCheckRequest:
      type: object
      properties:
        job_id:
          type: string
        topic:
          type: string
        tenant:
          type: string
        org_id:
          type: string
        team_id:
          type: string
        capability:
          type: string
        risk_tags:
          type: array
          items:
            type: string
        context:
          type: object
          additionalProperties: true
        labels:
          type: object
          additionalProperties:
            type: string
    PolicyCheckResponse:
      type: object
      properties:
        decision:
          type: string
          enum:
          - ALLOW
          - DENY
          - QUARANTINE
          - REQUIRE_APPROVAL
        reason:
          type: string
        rule_id:
          type: string
          nullable: true
        constraints:
          type: object
          additionalProperties: true
          nullable: true
        evaluations:
          type: array
          items:
            $ref: '#/components/schemas/SafetyDecision'
          nullable: true
    PolicySnapshot:
      type: object
      properties:
        id:
          type: string
        note:
          type: string
        author:
          type: string
        created_at:
          type: string
          format: date-time
        bundle_ids:
          type: array
          items:
            type: string
          nullable: true
    PolicyRule:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        enabled:
          type: boolean
        action:
          type: string
          enum:
          - ALLOW
          - DENY
          - QUARANTINE
          - REQUIRE_APPROVAL
        conditions:
          type: object
          additionalProperties: true
        priority:
          type: integer
        source:
          type: string
          description: Bundle or file that defines this rule
    OutputRule:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        enabled:
          type: boolean
        scanner_type:
          type: string
        pattern:
          type: string
          nullable: true
        action:
          type: string
          enum:
          - BLOCK
          - REDACT
          - WARN
          - LOG
        config:
          type: object
          additionalProperties: true
          nullable: true
    PolicyBundleSummary:
      type: object
      properties:
        id:
          type: string
        enabled:
          type: boolean
        source:
          type: string
        author:
          type: string
        message:
          type: string
        rule_count:
          type: integer
        updated_at:
          type: string
          format: date-time
    PolicyBundleDetail:
      allOf:
      - $ref: '#/components/schemas/PolicyBundleSummary'
      - type: object
        properties:
          content:
            type: string
            description: Raw YAML content of the bundle
          rules:
            type: array
            items:
              $ref: '#/components/schemas/PolicyRule'
    UpdatePolicyBundleRequest:
      type: object
      properties:
        content:
          type: string
        enabled:
          type: boolean
        author:
          type: string
        message:
          type: string
    PublishPolicyRequest:
      type: object
      properties:
        bundle_ids:
          type: array
          items:
            type: string
        author:
          type: string
        message:
          type: string
        note:
          type: string
      required:
      - bundle_ids
      - author
    RollbackPolicyRequest:
      type: object
      properties:
        snapshot_id:
          type: string
        author:
          type: string
        message:
          type: string
        note:
          type: string
      required:
      - snapshot_id
      - author
    PolicyAuditEntry:
      type: object
      properties:
        id:
          type: string
        action:
          type: string
        author:
          type: string
        timestamp:
          type: string
          format: date-time
        bundle_id:
          type: string
          nullable: true
        snapshot_id:
          type: string
          nullable: true
        message:
          type: string
          nullable: true
    ConfigDocument:
      type: object
      properties:
        scope:
          type: string
          enum:
          - system
          - org
          - team
          - workflow
          - step
        scope_id:
          type: string
        data:
          type: object
          additionalProperties: true
          description: Configuration key-value pairs
    SchemaRecord:
      type: object
      properties:
        id:
          type: string
        schema:
          type: object
          additionalProperties: true
          description: JSON Schema document
      required:
      - id
      - schema
    Lock:
      type: object
      properties:
        resource:
          type: string
        mode:
          type: string
          enum:
          - EXCLUSIVE
          - SHARED
        owners:
          type: array
          items:
            type: string
        updated_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
    LockRequest:
      type: object
      properties:
        resource:
          type: string
        owner:
          type: string
        mode:
          type: string
          enum:
          - EXCLUSIVE
          - SHARED
        ttl_ms:
          type: integer
          description: Lock time-to-live in milliseconds
      required:
      - resource
      - owner
    PackRecord:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        version:
          type: string
        status:
          type: string
          enum:
          - ACTIVE
          - INACTIVE
          - UNINSTALLED
          - ERROR
        description:
          type: string
          nullable: true
        author:
          type: string
          nullable: true
        installed_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PackVerification:
      type: object
      properties:
        pack_id:
          type: string
        valid:
          type: boolean
        checks:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              passed:
                type: boolean
              message:
                type: string
    MarketplaceCatalog:
      type: object
      properties:
        catalogs:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              url:
                type: string
        items:
          type: array
          items:
            $ref: '#/components/schemas/MarketplacePack'
        fetched_at:
          type: string
          format: date-time
        cached:
          type: boolean
    MarketplacePack:
      type: object
      properties:
        catalog_id:
          type: string
        pack_id:
          type: string
        name:
          type: string
        version:
          type: string
        description:
          type: string
        author:
          type: string
        sha256:
          type: string
        url:
          type: string
    MarketplaceInstallRequest:
      type: object
      properties:
        catalog_id:
          type: string
        pack_id:
          type: string
        version:
          type: string
        url:
          type: string
        sha256:
          type: string
        force:
          type: boolean
          default: false
        upgrade:
          type: boolean
          default: false
        inactive:
          type: boolean
          default: false
      required:
      - catalog_id
      - pack_id
      - version
    StatusResponse:
      type: object
      properties:
        time:
          type: string
          format: date-time
        uptime_seconds:
          type: number
        build:
          type: object
          properties:
            version:
              type: string
            commit:
              type: string
            date:
              type: string
        nats:
          type: object
          properties:
            connected:
              type: boolean
            url:
              type: string
        redis:
          type: object
          properties:
            connected:
              type: boolean
            url:
              type: string
        workers:
          type: integer
          description: Number of connected workers
        license:
          type: object
          properties:
            valid:
              type: boolean
            tier:
              type: string
          nullable: true
    CreateArtifactRequest:
      type: object
      properties:
        content_base64:
          type: string
          format: byte
        content_type:
          type: string
        retention:
          type: string
          description: Retention policy (e.g., "30d", "permanent")
        labels:
          type: object
          additionalProperties:
            type: string
      required:
      - content_base64
      - content_type
    CreateArtifactResponse:
      type: object
      properties:
        artifact_ptr:
          type: string
        size_bytes:
          type: integer
    ArtifactDetail:
      type: object
      properties:
        artifact_ptr:
          type: string
        content_base64:
          type: string
          format: byte
        metadata:
          type: object
          properties:
            content_type:
              type: string
            size_bytes:
              type: integer
            created_at:
              type: string
              format: date-time
            retention:
              type: string
            labels:
              type: object
              additionalProperties:
                type: string
    JsonRpcRequest:
      type: object
      properties:
        jsonrpc:
          type: string
          enum:
          - '2.0'
        id:
          oneOf:
          - type: string
          - type: integer
        method:
          type: string
        params:
          type: object
          additionalProperties: true
      required:
      - jsonrpc
      - method
    JsonRpcResponse:
      type: object
      properties:
        jsonrpc:
          type: string
          enum:
          - '2.0'
        id:
          oneOf:
          - type: string
          - type: integer
        result:
          type: object
          additionalProperties: true
          nullable: true
        error:
          type: object
          properties:
            code:
              type: integer
            message:
              type: string
            data:
              type: object
              additionalProperties: true
              nullable: true
          nullable: true
    McpStatus:
      type: object
      properties:
        running:
          type: boolean
        connected_clients:
          type: integer
        uptime_seconds:
          type: number
        transport:
          type: string
          enum:
          - sse
          - stdio
    GenericObject:
      type: object
      properties: {}
      additionalProperties: true
    RoleDefinition:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
        permissions:
          type: array
          items:
            type: string
        inherits:
          type: array
          items:
            type: string
        built_in:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - name
    RoleListResponse:
      type: object
      properties:
        roles:
          type: array
          items:
            $ref: '#/components/schemas/RoleDefinition'
        entitled:
          type: boolean
      required:
      - roles
      - entitled
    RoleDetailResponse:
      type: object
      properties:
        role:
          $ref: '#/components/schemas/RoleDefinition'
        resolved_permissions:
          type: array
          items:
            type: string
      required:
      - role
      - resolved_permissions
    RoleRequest:
      type: object
      properties:
        description:
          type: string
        permissions:
          type: array
          items:
            type: string
        inherits:
          type: array
          items:
            type: string
    WorkerRuntime:
      type: object
      properties:
        worker_id:
          type: string
        pool:
          type: string
        active_jobs:
          type: integer
        max_parallel_jobs:
          type: integer
        capabilities:
          type: array
          items:
            type: string
        cpu_load:
          type: number
        gpu_utilization:
          type: number
        memory_load:
          type: number
        region:
          type: string
        type:
          type: string
        labels:
          type: object
          properties: {}
          additionalProperties:
            type: string
        last_heartbeat:
          type: string
          format: date-time
      required:
      - worker_id
    JobRecord:
      type: object
      properties:
        id:
          type: string
        worker_id:
          type: string
        trace_id:
          type: string
        updated_at:
          type: integer
          format: int64
        state:
          type: string
        topic:
          type: string
        tenant:
          type: string
        team:
          type: string
        principal:
          type: string
        actor_id:
          type: string
        actor_type:
          type: string
        idempotency_key:
          type: string
        capability:
          type: string
        risk_tags:
          type: array
          items:
            type: string
        requires:
          type: array
          items:
            type: string
        pack_id:
          type: string
        attempts:
          type: integer
        safety_decision:
          type: string
        safety_reason:
          type: string
        safety_rule_id:
          type: string
        safety_snapshot:
          type: string
        deadline_unix:
          type: integer
          format: int64
        failure_reason:
          type: string
      required:
      - id
      - updated_at
      - state
    WorkerCredential:
      type: object
      properties:
        worker_id:
          type: string
        allowed_pools:
          type: array
          items:
            type: string
        allowed_topics:
          type: array
          items:
            type: string
        pack_id:
          type: string
        created_by:
          type: string
        created_at:
          type: string
          format: date-time
        revoked_at:
          type: string
          format: date-time
      required:
      - worker_id
      - created_by
      - created_at
    WorkerCredentialIssue:
      allOf:
      - $ref: '#/components/schemas/WorkerCredential'
      - type: object
        properties:
          token:
            type: string
        required:
        - token
    PoolListItem:
      type: object
      properties:
        name:
          type: string
        workers:
          type: integer
        active_jobs:
          type: integer
        capacity:
          type: integer
        utilization:
          type: number
      required:
      - name
    PoolDetail:
      type: object
      properties:
        name:
          type: string
        workers:
          type: array
          items:
            $ref: '#/components/schemas/WorkerRuntime'
        active_jobs:
          type: integer
        capacity:
          type: integer
        utilization:
          type: number
        topics:
          type: array
          items:
            type: string
      required:
      - name
      - workers
      - topics
    PoolMutation:
      type: object
      properties:
        name:
          type: string
        status:
          type: string
        requires:
          type: array
          items:
            type: string
        description:
          type: string
        drain_started_at:
          type: string
          format: date-time
        drain_timeout_seconds:
          type: integer
      required:
      - name
      - status
    TopicResponse:
      type: object
      properties:
        name:
          type: string
        pool:
          type: string
        input_schema_id:
          type: string
        output_schema_id:
          type: string
        pack_id:
          type: string
        requires:
          type: array
          items:
            type: string
        risk_tags:
          type: array
          items:
            type: string
        status:
          type: string
        active_worker_count:
          type: integer
      required:
      - name
      - status
    LicenseInfo:
      type: object
      properties:
        mode:
          type: string
        status:
          type: string
        plan:
          type: string
        org_id:
          type: string
        license_id:
          type: string
        deployment_type:
          type: string
        issued_at:
          type: string
        not_before:
          type: string
        expires_at:
          type: string
        features:
          type: array
          items:
            type: string
        limits:
          type: object
          properties: {}
          additionalProperties:
            type: integer
            format: int64
    PolicyReplayRequest:
      type: object
      properties:
        from:
          type: string
          format: date-time
        to:
          type: string
          format: date-time
        filters:
          type: object
          properties:
            tenant:
              type: string
            topic_pattern:
              type: string
            original_decision:
              type: string
        candidate_bundle_id:
          type: string
        candidate_content:
          type: string
        use_current_policy:
          type: boolean
        max_jobs:
          type: integer
      required:
      - from
      - to
    PolicyReplayResponse:
      type: object
      properties:
        replay_id:
          type: string
          format: uuid
        policy_snapshot:
          type: string
        time_range:
          type: object
          properties:
            from:
              type: string
              format: date-time
            to:
              type: string
              format: date-time
          required:
          - from
          - to
        summary:
          type: object
          properties:
            total_jobs:
              type: integer
            evaluated:
              type: integer
            escalated:
              type: integer
            relaxed:
              type: integer
            unchanged:
              type: integer
            errored:
              type: integer
        rule_hits:
          type: array
          items:
            type: object
            properties:
              rule_id:
                type: string
              decision:
                type: string
              count:
                type: integer
            required:
            - rule_id
            - decision
            - count
        changes:
          type: array
          items:
            type: object
            properties:
              job_id:
                type: string
              topic:
                type: string
              tenant:
                type: string
              original_decision:
                type: string
              new_decision:
                type: string
              new_rule_id:
                type: string
              new_reason:
                type: string
              direction:
                type: string
            required:
            - job_id
            - topic
            - tenant
            - original_decision
            - new_decision
            - direction
        warnings:
          type: array
          items:
            type: string
        errors:
          type: array
          items:
            type: string
      required:
      - replay_id
      - policy_snapshot
      - time_range
      - summary
      - rule_hits
      - changes
    VelocityRule:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        match:
          type: object
          properties:
            topics:
              type: array
              items:
                type: string
            tenants:
              type: array
              items:
                type: string
            risk_tags:
              type: array
              items:
                type: string
        window:
          type: string
        key:
          type: string
        threshold:
          type: integer
        decision:
          type: string
        reason:
          type: string
        enabled:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
    VelocityStats:
      type: object
      properties:
        id:
          type: string
        hit_count_24h:
          type: integer
          format: int64
        hit_rate_24h:
          type: number
        current_window_count:
          type: integer
          format: int64
        current_window_max:
          type: integer
          format: int64
        active_buckets:
          type: integer
        exceeded_buckets:
          type: integer
        last_triggered:
          type: string
          format: date-time
        hourly_hits:
          type: array
          items:
            type: integer
            format: int64
      required:
      - id
    AdminLock:
      type: object
      properties:
        key:
          type: string
        holder:
          type: string
        ttl_remaining_ms:
          type: integer
          format: int64
        type:
          type: string
      required:
      - key
      - holder
      - ttl_remaining_ms
      - type
