openapi: 3.0.3 info: title: Cordum HTTP API version: '2026-05-09.2' description: Canonical OpenAPI 3.0.3 spec for the Cordum gateway HTTP surface. 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: Copilot description: Copilot session transcripts and linked operational context - name: Edge description: Edge session and agent execution evidence APIs - 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) - name: Governance description: Governance-native observability — policy decisions, approval analytics - name: Agents description: Agent identities, delegation, and runtime statistics - name: Delegation description: Delegation token issuance, verification, listing, and revocation - name: Evals description: Evaluation dataset curation, incident extraction, and policy replay runs 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/oidc/group-role-mapping: put: operationId: updateOIDCGroupRoleMapping summary: Update OIDC groups-to-roles mapping description: Admin-only endpoint for updating the active Okta/OIDC groups claim and Cordum role mapping. Values are validated, applied to the live provider, and persisted into system/default config. tags: - Auth parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OIDCGroupRoleMappingUpdateRequest' responses: '200': description: Sanitized authentication configuration content: application/json: schema: $ref: '#/components/schemas/AuthConfig' '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/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/copilot/sessions/{sessionId}: get: operationId: getCopilotSession summary: Get Copilot session detail description: Returns a Copilot chat transcript plus linked jobs and governance decisions. tags: - Copilot parameters: - name: sessionId in: path required: true schema: type: string pattern: ^[A-Za-z0-9_-]{1,128}$ - $ref: '#/components/parameters/TenantID' responses: '200': description: Copilot session detail content: application/json: schema: $ref: '#/components/schemas/CopilotSessionDetailResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '501': description: Copilot session store is not wired yet content: application/json: schema: $ref: '#/components/schemas/Error' '500': $ref: '#/components/responses/InternalServerError' /api/v1/edge/sessions: post: operationId: createEdgeSession summary: Create an Edge session and initial execution description: | EDGE-060 — supports `Idempotency-Key`. A retry with the same key and identical body returns the SAME `session_id` / `execution_id` / `trace_id` (cached replay, 201). A retry with the same key but different body returns 409 `idempotency_conflict`. Missing key falls back to non-idempotent behavior (every call generates fresh UUIDs). tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EdgeSessionCreateRequest' responses: '201': description: Edge session created content: application/json: schema: $ref: '#/components/schemas/EdgeSessionCreateResponse' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '409': $ref: '#/components/responses/EdgeConflict' '413': $ref: '#/components/responses/EdgePayloadTooLarge' '429': $ref: '#/components/responses/EdgeEventCapExceeded' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' get: operationId: listEdgeSessions summary: List Edge sessions for a tenant tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: principal_id in: query schema: type: string - name: cursor in: query schema: type: string - name: limit in: query schema: type: integer minimum: 1 responses: '200': description: Edge session page content: application/json: schema: $ref: '#/components/schemas/EdgeSessionPageResponse' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/sessions/{session_id}: get: operationId: getEdgeSession summary: Get an Edge session tags: - Edge parameters: - name: session_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' responses: '200': description: Edge session content: application/json: schema: $ref: '#/components/schemas/EdgeSession' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/sessions/{session_id}/heartbeat: post: operationId: heartbeatEdgeSession summary: Refresh an Edge session heartbeat tags: - Edge parameters: - name: session_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' responses: '200': description: Heartbeat refreshed content: application/json: schema: $ref: '#/components/schemas/EdgeHeartbeatResponse' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/sessions/{session_id}/end: post: operationId: endEdgeSession summary: End an Edge session tags: - Edge parameters: - name: session_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/EdgeEndSessionRequest' responses: '200': description: Ended Edge session content: application/json: schema: $ref: '#/components/schemas/EdgeSession' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/executions: get: operationId: listEdgeExecutions summary: List Edge agent executions for a tenant tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: session_id in: query schema: type: string - name: job_id in: query schema: type: string - name: trace_id in: query schema: type: string - name: workflow_run_id in: query schema: type: string - name: cursor in: query schema: type: string - name: limit in: query schema: type: integer minimum: 1 responses: '200': description: Edge execution page content: application/json: schema: $ref: '#/components/schemas/EdgeExecutionPageResponse' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' post: operationId: createEdgeExecution summary: Create an Edge agent execution description: | EDGE-060 — supports `Idempotency-Key`. A retry with the same key and identical body returns the SAME `execution_id` (cached replay, 201) without burning against the per-session execution cap. A retry with the same key but different body returns 409 `idempotency_conflict`. Missing key falls back to non-idempotent behavior (every call generates a fresh `execution_id`). tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EdgeExecutionCreateRequest' responses: '201': description: Edge execution created content: application/json: schema: $ref: '#/components/schemas/EdgeAgentExecution' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '413': $ref: '#/components/responses/EdgePayloadTooLarge' '429': $ref: '#/components/responses/EdgeMaxExecutionsExceeded' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/executions/{execution_id}: get: operationId: getEdgeExecution summary: Get an Edge agent execution tags: - Edge parameters: - name: execution_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' responses: '200': description: Edge execution content: application/json: schema: $ref: '#/components/schemas/EdgeAgentExecution' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/executions/{execution_id}/end: post: operationId: endEdgeExecution summary: End an Edge agent execution tags: - Edge parameters: - name: execution_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/EdgeEndExecutionRequest' responses: '200': description: Ended Edge execution content: application/json: schema: $ref: '#/components/schemas/EdgeAgentExecution' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/approvals: get: operationId: listEdgeApprovals summary: List Edge action approvals description: >- Lists tenant-scoped Edge approval records for action-level governance. Non-admin/non-operator callers receive only approvals whose `principal_id` matches the authenticated `auth.PrincipalID`; admin and operator callers may list all approvals in the tenant for operations and forensics. Status, tuple, cursor, and limit filters are applied within that visibility scope so list pagination remains principal-stable. Raw action payloads are never returned; actions are represented by `event_id`, `action_hash`, `input_hash`, and `policy_snapshot`. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: status in: query schema: type: string enum: [pending, approved, rejected, expired, invalidated] - name: session_id in: query description: Optional tuple filter; must be supplied with `execution_id` and `action_hash`. schema: type: string - name: execution_id in: query description: Optional tuple filter; must be supplied with `session_id` and `action_hash`. schema: type: string - name: action_hash in: query description: Optional tuple filter; must be supplied with `session_id` and `execution_id`. schema: type: string - name: cursor in: query schema: type: string - name: limit in: query schema: type: integer minimum: 1 maximum: 200 default: 50 responses: '200': description: Edge approval page scoped to the caller's principal unless the caller has admin/operator visibility. content: application/json: schema: $ref: '#/components/schemas/EdgeApprovalPageResponse' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/approvals/{approval_ref}: get: operationId: getEdgeApproval summary: Get an Edge action approval description: >- Returns one tenant-scoped Edge approval record. Detail reads are principal-bound: the caller must be the original requester principal (`auth.PrincipalID` matches the approval `principal_id`) or hold an admin/operator role. Cross-tenant requests and same-tenant callers that fail this binding are reported as not found and no raw tool/action payload is exposed. tags: - Edge parameters: - name: approval_ref in: path required: true schema: type: string pattern: ^edge_appr_[A-Za-z0-9_-]+$ - $ref: '#/components/parameters/TenantID' responses: '200': description: Edge approval content: application/json: schema: $ref: '#/components/schemas/EdgeApproval' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': description: Edge approval not found, cross-tenant, or caller is not the original requester principal and lacks admin/operator role. The same not_found envelope is used to prevent tenant-insider enumeration of approval timing or decisions. content: application/json: schema: $ref: '#/components/schemas/EdgeError' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/approvals/{approval_ref}/approve: post: operationId: approveEdgeApproval summary: Approve an Edge action approval description: | Resolves a pending Edge approval as approved for one retry/consume. Requires approval/admin permission, enforces tenant scope and self-approval protection, and fails with 409 if the session/execution/event/action hash or policy snapshot is stale. EDGE-060 — supports `Idempotency-Key` for safe retry against the resolution state transition. Same key + same body returns the SAME cached 200 response (replay), NOT 409 "already approved" — this is the EDGE-060 DoD #7 invariant. A different key against an already-terminal approval still surfaces the existing 409 path. tags: - Edge parameters: - name: approval_ref in: path required: true schema: type: string pattern: ^edge_appr_[A-Za-z0-9_-]+$ - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/EdgeApprovalDecisionRequest' responses: '200': description: Approved Edge approval content: application/json: schema: $ref: '#/components/schemas/EdgeApproval' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': description: Approval permission denied or self-approval rejected (`code=self_approval_denied`). content: application/json: schema: $ref: '#/components/schemas/EdgeError' '404': $ref: '#/components/responses/EdgeNotFound' '409': $ref: '#/components/responses/EdgeConflict' '413': $ref: '#/components/responses/EdgePayloadTooLarge' '429': $ref: '#/components/responses/EdgeEventCapExceeded' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/approvals/{approval_ref}/wait: post: operationId: waitEdgeApproval summary: Bounded blocking wait for an Edge approval to leave Pending description: >- Agentd/local-dev affordance that polls the Edge approval store until the approval is non-pending or the bounded timeout elapses, then returns the current EdgeApproval. The wait is principal-bound: the caller must be the original requester principal (`auth.PrincipalID` matches the approval `principal_id`) or hold an admin/operator role. The server clamps `timeout_ms` to a 5 minute maximum and uses a 30 second default when omitted or non-positive. Tenant isolation is enforced; cross-tenant references and same-tenant callers that fail the principal binding return 404 with no metadata leakage. This endpoint is not required by the dashboard approve/reject UX — callers there should use the standard list/detail/approve/reject flow. tags: - Edge parameters: - name: approval_ref in: path required: true schema: type: string pattern: ^edge_appr_[A-Za-z0-9_-]+$ - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: type: object properties: timeout_ms: type: integer minimum: 0 description: Caller-requested wait budget. Server clamps to a 5-minute max and uses a 30s default when omitted or non-positive. responses: '200': description: Current Edge approval after the wait (resolved if approved/rejected/expired/invalidated; otherwise still pending). content: application/json: schema: $ref: '#/components/schemas/EdgeApproval' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': description: Edge approval not found, cross-tenant, or caller is not the original requester principal and lacks admin/operator role. The same not_found envelope is used to prevent tenant-insider enumeration of approval timing or decisions. content: application/json: schema: $ref: '#/components/schemas/EdgeError' '409': $ref: '#/components/responses/EdgeConflict' '413': $ref: '#/components/responses/EdgePayloadTooLarge' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/approvals/{approval_ref}/reject: post: operationId: rejectEdgeApproval summary: Reject an Edge action approval description: | Resolves a pending Edge approval as rejected. Requires approval/admin permission, enforces tenant scope and self-approval protection, and fails with 409 if the referenced Edge session/execution/event is stale. EDGE-060 — supports `Idempotency-Key` for safe retry against the resolution state transition. Same key + same body returns the SAME cached 200 response (replay), NOT 409 "already rejected". tags: - Edge parameters: - name: approval_ref in: path required: true schema: type: string pattern: ^edge_appr_[A-Za-z0-9_-]+$ - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/EdgeApprovalDecisionRequest' responses: '200': description: Rejected Edge approval content: application/json: schema: $ref: '#/components/schemas/EdgeApproval' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': description: Approval permission denied or self-approval rejected (`code=self_approval_denied`). content: application/json: schema: $ref: '#/components/schemas/EdgeError' '404': $ref: '#/components/responses/EdgeNotFound' '409': $ref: '#/components/responses/EdgeConflict' '413': $ref: '#/components/responses/EdgePayloadTooLarge' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/evaluate: post: operationId: evaluateEdgeAction summary: Evaluate an Edge action with Safety Kernel policy description: Classifies a bounded, redacted agent action for an existing Edge session/execution, calls the Safety Kernel using the Edge policy topic, persists a redacted policy decision or degraded event, and returns hook-friendly allow/deny fields. Raw `tool_input`, raw transcripts, and unredacted payloads are rejected; large evidence must use artifact pointers. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EdgeEvaluateRequest' responses: '200': description: Hook-friendly Edge policy decision. Safety outages may still return 200 with `degraded=true` when policy mode permits and degraded evidence was persisted. content: application/json: schema: $ref: '#/components/schemas/EdgeEvaluateResponse' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '409': $ref: '#/components/responses/EdgeConflict' '413': $ref: '#/components/responses/EdgePayloadTooLarge' '502': $ref: '#/components/responses/EdgeBadGateway' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/events: post: operationId: createEdgeEvent summary: Append a single Edge agent action event description: >- Writes a redacted AgentActionEvent for an existing Edge session/execution. Raw tool inputs, raw tool results, and transcripts must not be sent inline; send bounded `input_redacted` details and reference large evidence with `artifact_ptrs`. Optional `Idempotency-Key` retries are scoped by tenant and endpoint: the same normalized request replays the first 201 response without appending a duplicate event, while the same key with a different normalized request returns 409 `idempotency_conflict`. The append and replay record commit in one Redis transaction. If the replay record has expired and the same logical `event_id` is already present, the API returns 409 `idempotency_window_expired` and does not append a duplicate event. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EdgeAgentActionEventWriteRequest' responses: '201': description: Edge event appended content: application/json: schema: $ref: '#/components/schemas/EdgeAgentActionEvent' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '409': $ref: '#/components/responses/EdgeConflict' '413': $ref: '#/components/responses/EdgePayloadTooLarge' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/events/batch: post: operationId: createEdgeEventsBatch summary: Append a batch of Edge agent action events description: >- Appends a fully prevalidated, ordered batch for existing Edge session/execution parents. Mixed-tenant or invalid items are rejected before append where possible. Large raw payloads must be stored separately and referenced by `artifact_ptrs`. Optional `Idempotency-Key` retries are scoped by tenant and endpoint: the same normalized batch replays the first 201 response without partial append or duplicate events, while the same key with a different normalized batch returns 409 `idempotency_conflict`. The append and replay record commit in one Redis transaction. If the replay record has expired and any same logical `event_id` is already present, the API returns 409 `idempotency_window_expired` and does not append duplicate events. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EdgeAgentActionEventBatchRequest' responses: '201': description: Edge events appended in input order content: application/json: schema: $ref: '#/components/schemas/EdgeAgentActionEventBatchResponse' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '409': $ref: '#/components/responses/EdgeConflict' '413': $ref: '#/components/responses/EdgePayloadTooLarge' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/sessions/{session_id}/events: get: operationId: listEdgeSessionEvents summary: List Edge events for a session description: Returns tenant-scoped events across executions in the session with cursor pagination and optional kind, decision, and RFC3339 time-window filters. tags: - Edge parameters: - name: session_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' - name: cursor in: query schema: type: string - name: limit in: query schema: type: integer minimum: 1 maximum: 200 default: 50 - name: kind in: query description: Optional event kind filter; event kinds are open-ended non-empty strings such as `hook.pre_tool_use`. schema: type: string - name: decision in: query schema: type: string enum: [ALLOW, DENY, REQUIRE_APPROVAL, THROTTLE, CONSTRAIN, RECORDED] - name: since in: query description: Inclusive lower timestamp bound. schema: type: string format: date-time - name: until in: query description: Inclusive upper timestamp bound. schema: type: string format: date-time responses: '200': description: Edge event page content: application/json: schema: $ref: '#/components/schemas/EdgeAgentActionEventPageResponse' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/sessions/{session_id}/export: post: operationId: exportEdgeSession summary: Assemble an Edge session evidence bundle description: | Returns a `SessionExportBundle` for the named session — session record, executions, ordered AgentActionEvents, approvals, and a metadata-only manifest of every artifact pointer attached to those events. Bundles contain artifact metadata (URI, sha256, size, content_type, retention, redaction level) but never raw artifact bodies. Cross-tenant probes receive 404 indistinguishable from missing-session. tags: - Edge parameters: - name: session_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: type: object additionalProperties: false properties: max_events: type: integer minimum: 1 maximum: 10000 default: 5000 description: | Caps the number of events the bundle carries. When the session has more, `truncation.events_truncated = true` and `truncation.event_count` records the actual total. Values above 10000 are rejected with HTTP 400 + `code=max_events_too_large` (EDGE-065 request-validation bound). Values <= 0 fall back to the assembler default (5000). responses: '200': description: Edge session export bundle content: application/json: schema: type: object description: | SessionExportBundle. See docs/edge-export.md for full schema including artifact_type catalog and missing_artifacts reasons. additionalProperties: true required: - manifest_version - generated_at - tenant_id - session properties: manifest_version: type: string example: edge.export.v1 generated_at: type: string format: date-time tenant_id: type: string redaction_level: type: string enum: [standard, strict] '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '413': description: Bundle exceeds `CORDUM_EDGE_EXPORT_MAX_BYTES`; reduce `max_events` and retry. content: application/json: schema: $ref: '#/components/schemas/EdgeError' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/executions/{execution_id}/events: get: operationId: listEdgeExecutionEvents summary: List Edge events for an execution description: Returns tenant-scoped events for one execution in ascending sequence order with cursor pagination and optional kind, decision, and RFC3339 time-window filters. tags: - Edge parameters: - name: execution_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' - name: cursor in: query schema: type: string - name: limit in: query schema: type: integer minimum: 1 maximum: 200 default: 50 - name: kind in: query description: Optional event kind filter; event kinds are open-ended non-empty strings such as `hook.pre_tool_use`. schema: type: string - name: decision in: query schema: type: string enum: [ALLOW, DENY, REQUIRE_APPROVAL, THROTTLE, CONSTRAIN, RECORDED] - name: since in: query description: Inclusive lower timestamp bound. schema: type: string format: date-time - name: until in: query description: Inclusive upper timestamp bound. schema: type: string format: date-time responses: '200': description: Edge event page content: application/json: schema: $ref: '#/components/schemas/EdgeAgentActionEventPageResponse' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '404': $ref: '#/components/responses/EdgeNotFound' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /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/global: get: operationId: getPolicyGlobal summary: Read the unified Global policy authority description: | EDGE-052 — returns the five-section view of the Global policy authority that all four evaluators (Cordum job, Edge action, MCP tool, output scan) consult. Each section is a YAML SafetyPolicy fragment persisted under a dedicated `secops/`-prefixed bundle key. tags: - Policy parameters: - $ref: '#/components/parameters/TenantID' responses: '200': description: Unified Global policy snapshot content: application/json: schema: $ref: '#/components/schemas/PolicyGlobalDocument' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' put: operationId: updatePolicyGlobal summary: Atomically update the unified Global policy authority description: | EDGE-052 — atomically writes one or more sections of the Global policy. Sections not present in the request body are left unchanged. A `snapshot_version` field on the request acts as optimistic concurrency: when non-empty and != the current snapshot the request is rejected with 409 Conflict. tags: - Policy parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdatePolicyGlobalRequest' responses: '200': description: Unified Global policy updated content: application/json: schema: $ref: '#/components/schemas/PolicyGlobalDocument' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': description: snapshot_version mismatch (optimistic concurrency) '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/shadows/{id}: parameters: - name: id in: path required: true description: | Tilde-encoded bundle id; `/` in logical IDs (e.g. `secops/safety`) becomes `~` on the wire (`secops~safety`). The server decodes the tilde back to `/` before looking up the shadow. schema: type: string put: operationId: activatePolicyShadow summary: Activate or replace the shadow policy for a bundle description: | PUT upserts the shadow policy for the given bundle. A bundle has at most one shadow at a time; re-activating replaces the current one in place. Validation matches `handlePutPolicyBundle` — malformed YAML returns 400 before any Redis write. tags: - Policy security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PolicyShadowUpsertRequest' responses: '200': description: Shadow policy activated content: application/json: schema: $ref: '#/components/schemas/PolicyShadow' '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' '503': $ref: '#/components/responses/ServiceUnavailable' get: operationId: getPolicyShadow summary: Get the shadow policy for a bundle description: | Returns the active shadow policy for `{id}` or 404 if none is set. Requires `policy.read` (admin role bypasses). Dashboards translate the 404 into an empty-state render rather than an error. tags: - Policy security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' responses: '200': description: Shadow policy content: application/json: schema: $ref: '#/components/schemas/PolicyShadow' '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' '503': $ref: '#/components/responses/ServiceUnavailable' delete: operationId: deletePolicyShadow summary: Remove the shadow policy for a bundle description: | Deactivates the shadow. Emits a `safety.policy_change` audit event with `action=shadow_deactivate` and the outgoing shadow's `shadow_bundle_id` so the activate/deactivate pair correlates. tags: - Policy security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' responses: '204': description: Shadow policy 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' '500': $ref: '#/components/responses/InternalServerError' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/policy/shadows/{id}/results/summary: parameters: - name: id in: path required: true description: Tilde-encoded bundle id (see `/api/v1/policy/shadows/{id}`). schema: type: string get: operationId: getPolicyShadowResultsSummary summary: Aggregate shadow-evaluation counts over a time window description: | Counts shadow-eval outcomes (escalated / relaxed / approval_differ / unchanged) across the audit stream between `from` and `to`. Memoised for 60 s per (tenant, bundle, window) so dashboard polling doesn't thrash Redis XRANGE. Requires `policy.read`. tags: - Policy security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' - name: from in: query required: true description: Inclusive lower bound, unix milliseconds. schema: type: integer format: int64 minimum: 0 - name: to in: query required: true description: Exclusive upper bound, unix milliseconds. Must be strictly greater than `from`; the range cannot exceed 30 days. schema: type: integer format: int64 minimum: 0 responses: '200': description: Summary content: application/json: schema: $ref: '#/components/schemas/ShadowResultsSummary' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/policy/shadows/{id}/results/comparisons: parameters: - name: id in: path required: true description: Tilde-encoded bundle id (see `/api/v1/policy/shadows/{id}`). schema: type: string get: operationId: getPolicyShadowResultsComparisons summary: Paginated shadow-vs-active decision comparisons description: | Returns individual shadow-eval entries with both the active verdict and the shadow's would-be verdict. Paginates via Redis-stream cursors; clients pass `next_cursor` back as `?cursor=` to resume. `truncated_at_max=true` means the page ended because the scan hit its budget before reaching `to`. tags: - Policy security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' - name: from in: query required: true description: Inclusive lower bound, unix milliseconds. schema: type: integer format: int64 minimum: 0 - name: to in: query required: true description: Exclusive upper bound, unix milliseconds (≤ 30 days after `from`). schema: type: integer format: int64 minimum: 0 - name: diff in: query required: false description: Filter to one diff class (omit for all classes). schema: type: string enum: [escalated, relaxed, approval_differ, unchanged, all] - name: cursor in: query required: false description: Redis stream ID (`-`) returned as `next_cursor` from the previous page. schema: type: string - name: limit in: query required: false description: Page size. Default 50, maximum 500. schema: type: integer minimum: 1 maximum: 500 responses: '200': description: Paginated comparisons content: application/json: schema: $ref: '#/components/schemas/ShadowComparisonsResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/policy/shadows/{id}/results/timeseries: parameters: - name: id in: path required: true description: Tilde-encoded bundle id (see `/api/v1/policy/shadows/{id}`). schema: type: string get: operationId: getPolicyShadowResultsTimeseries summary: Zero-filled bucketed timeseries of shadow outcomes description: | Returns one row per bucket (including empty buckets) so the dashboard chart shows gaps rather than stretching the last observation. Bucket widths are a closed whitelist to prevent fine-grained requests blowing out the 2000-bucket ceiling. tags: - Policy security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' - name: from in: query required: true description: Inclusive lower bound, unix milliseconds. schema: type: integer format: int64 minimum: 0 - name: to in: query required: true description: Exclusive upper bound, unix milliseconds (≤ 30 days after `from`). schema: type: integer format: int64 minimum: 0 - name: bucket in: query required: true description: Bucket width (closed whitelist). schema: type: string enum: [1m, 5m, 15m, 1h, 1d] responses: '200': description: Timeseries content: application/json: schema: $ref: '#/components/schemas/ShadowTimeseriesResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /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 description: | Returns a filtered, paginated list of policy audit events. Filters match the gateway handler `handleListPolicyAudit` semantics: `action` / `agent_id` / `rule_id` / `type` are case-insensitive exact matches; `after` / `before` are lexicographic compares against `created_at`; `search` is a substring match across `action + actor_id + resource_type + resource_id + message` lowercased. tags: - Policy parameters: - $ref: '#/components/parameters/TenantID' - name: limit in: query required: false description: Maximum number of entries to return (default 100). schema: type: integer format: int64 default: 100 minimum: 1 example: 50 - name: offset in: query required: false description: Number of entries to skip (default 0). schema: type: integer format: int64 default: 0 minimum: 0 example: 0 - name: action in: query required: false description: Case-insensitive exact match against the entry action verb. schema: type: string example: rule.created - name: agent_id in: query required: false description: Case-insensitive exact match against the entry's agent_id. schema: type: string example: agent-claims-triage - name: after in: query required: false description: | Inclusive lower-bound on `created_at`. Lexicographic compare on the raw string the backend stores (typically RFC3339). schema: type: string format: date-time example: '2026-05-01T00:00:00Z' - name: before in: query required: false description: | Exclusive upper-bound on `created_at`. Lexicographic compare on the raw string the backend stores (typically RFC3339). schema: type: string format: date-time example: '2026-05-09T00:00:00Z' - name: search in: query required: false description: | Case-insensitive substring search across the lowercased concatenation of `action + actor_id + resource_type + resource_id + message`. schema: type: string example: rule.created - name: rule_id in: query required: false description: | Case-insensitive exact match against the entry's `resource_id`. Named `rule_id` for the common case where the audited resource is a policy rule. schema: type: string example: rule-input-secrets - name: type in: query required: false description: | Case-insensitive exact match against the entry's `resource_type`. The special value `output` returns only the legacy output-rule audit log via a separate code path that omits `total` / `has_more` / `offset` from the response envelope. schema: type: string example: input responses: '200': description: Audit items envelope content: application/json: schema: $ref: '#/components/schemas/PolicyAuditEnvelope' '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: # The handler is registered with no HTTP method prefix (net/http # mux treats the pattern as any-method). The GET op below documents # the WebSocket upgrade path; x-any-method declares the handler-side # semantics so the openapi-audit tool doesn't flag the path as # under-specified. x-any-method: true get: operationId: streamEvents summary: WebSocket event bus stream description: 'Upgrades to a WebSocket connection. Authenticates via the `cordum-api-key.` 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, `.' 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/verify: get: operationId: verifyAuditChain summary: Verify audit chain integrity description: | Walks the tenant's audit stream and attests integrity of the hash chain. Reports `status=ok` on a contiguous chain, `status=partial` when gaps are below the retention boundary, and `status=compromised` for missing sequence numbers or a broken hash link. Admin-only and entitlement-gated; NEVER returns raw event bodies — this is an integrity report surface, not event retrieval. tags: - AuditExport parameters: - $ref: '#/components/parameters/TenantID' - name: tenant in: query required: false description: Tenant id to verify; must match caller scope and defaults to the authenticated/request tenant. schema: type: string - name: since in: query required: false description: Inclusive lower bound on event time (unix ms) schema: type: integer format: int64 minimum: 0 - name: until in: query required: false description: Inclusive upper bound on event time (unix ms) schema: type: integer format: int64 minimum: 0 - name: limit in: query required: false description: Max events to read (default 10000, max 100000) schema: type: integer format: int64 minimum: 1 maximum: 100000 responses: '200': description: Chain integrity report content: application/json: schema: $ref: '#/components/schemas/AuditVerifyResult' '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/audit/export: get: operationId: exportAuditCompliance summary: Stream compliance audit export description: | Streams a compliance-shaped audit export (ndjson or csv) for a bounded time window. Admin-only; gated by the `siem_export` or `audit_export` entitlement. The response is retention-aware: windows older than the configured retention + skew are rejected. The body carries SOC2 mapping columns when the operator has configured a mapping table. tags: - AuditExport parameters: - $ref: '#/components/parameters/TenantID' - name: from in: query description: Inclusive lower bound (RFC3339 timestamp) schema: type: string format: date-time - name: to in: query description: Exclusive upper bound (RFC3339 timestamp) schema: type: string format: date-time - name: format in: query description: Output format schema: type: string enum: - ndjson - csv default: ndjson - name: max_events in: query description: Cap on events emitted (clamped to entitlement ceiling) schema: type: integer format: int64 responses: '200': description: Streaming compliance export body (ndjson or csv) content: application/x-ndjson: schema: type: string text/csv: schema: type: string '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/TierLimit' '500': $ref: '#/components/responses/InternalServerError' '503': description: Audit stream unavailable content: application/json: schema: $ref: '#/components/schemas/Error' /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/agents: get: operationId: listAgents summary: List agent identities tags: - Agents parameters: - $ref: '#/components/parameters/TenantID' - name: cursor in: query required: false schema: type: string - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 200 default: 50 - name: status in: query required: false schema: type: string - name: risk_tier in: query required: false schema: type: string - name: team in: query required: false schema: type: string responses: '200': description: Agent identity page content: application/json: schema: type: object properties: items: type: array items: type: object properties: id: {type: string} name: {type: string} description: {type: string} owner: {type: string} team: {type: string} risk_tier: {type: string} allowed_topics: type: array items: {type: string} allowed_pools: type: array items: {type: string} allowed_tools: type: array items: {type: string} data_classifications: type: array items: {type: string} status: {type: string} created_at: {type: string} updated_at: {type: string} last_active: type: integer format: int64 cursor: type: string '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' post: operationId: createAgent summary: Create an agent identity tags: - Agents parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: type: object required: [name, owner, risk_tier] properties: name: {type: string} description: {type: string} owner: {type: string} team: {type: string} risk_tier: {type: string} allowed_topics: type: array items: {type: string} allowed_pools: type: array items: {type: string} allowed_tools: type: array items: {type: string} data_classifications: type: array items: {type: string} responses: '201': description: Agent identity created content: application/json: schema: type: object additionalProperties: true '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/agents/{id}: parameters: - name: id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' get: operationId: getAgent summary: Get one agent identity tags: - Agents responses: '200': description: Agent identity content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' put: operationId: updateAgent summary: Update an agent identity tags: - Agents requestBody: required: true content: application/json: schema: type: object properties: name: {type: string} description: {type: string} owner: {type: string} team: {type: string} risk_tier: {type: string} status: {type: string} allowed_topics: type: array items: {type: string} allowed_pools: type: array items: {type: string} allowed_tools: type: array items: {type: string} data_classifications: type: array items: {type: string} responses: '200': description: Agent identity updated content: application/json: schema: type: object additionalProperties: true '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' delete: operationId: deleteAgent summary: Delete an agent identity tags: - Agents responses: '204': description: Agent identity deleted '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/agents/{id}/stats: get: operationId: getAgentStats summary: Get per-agent runtime statistics tags: - Agents parameters: - name: id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' responses: '200': description: Runtime statistics for the requested agent content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/agents/{id}/delegate: post: operationId: issueDelegationToken summary: Issue a delegation token from one agent to another tags: - Agents - Delegation parameters: - name: id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: type: object required: [target_agent_id] properties: target_agent_id: {type: string} allowed_actions: type: array items: {type: string} allowed_topics: type: array items: {type: string} ttl_seconds: type: integer format: int64 parent_token: type: string responses: '201': description: Delegation token issued content: application/json: schema: type: object properties: token: {type: string} kid: {type: string} expires_at: {type: string} chain_depth: type: integer jti: {type: string} '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' '429': $ref: '#/components/responses/RateLimited' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/agents/verify-delegation: post: operationId: verifyDelegationToken summary: Verify a delegation token for an expected audience tags: - Agents - Delegation parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: type: object required: [token] properties: token: {type: string} expected_audience: {type: string} responses: '200': description: Delegation verification result content: application/json: schema: type: object properties: valid: type: boolean sub: {type: string} aud: {type: string} allowed_actions: type: array items: {type: string} allowed_topics: type: array items: {type: string} chain_depth: type: integer delegation_chain: type: array items: $ref: '#/components/schemas/DelegationChainLink' error_code: {type: string} '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/agents/revoke-delegation: post: operationId: revokeDelegationToken summary: Revoke a delegation token by JTI description: By default revoking a token cascades to every downstream delegation that extended it. Set `cascade=false` for narrow-scoped revocation. tags: - Agents - Delegation parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: type: object required: [jti] properties: jti: {type: string} reason: {type: string} cascade: type: boolean default: true description: When true, revoke downstream delegations that extended this token. responses: '200': description: Delegation token revoked content: application/json: schema: type: object required: [jti, cascaded_count] properties: jti: type: string cascaded_count: type: integer description: Number of downstream delegations revoked in addition to the root token. '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/agents/{id}/delegations: get: operationId: listDelegationsForAgent summary: List delegation tokens issued by a specific agent tags: - Agents - Delegation parameters: - name: id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' - name: status in: query required: false schema: type: string enum: [active, revoked, expired, all] - name: scope in: query required: false schema: type: string - name: before_expiry in: query required: false schema: type: string format: date-time - name: since_issued in: query required: false schema: type: string format: date-time - name: until_issued in: query required: false schema: type: string format: date-time - name: cursor in: query required: false schema: type: string - name: limit in: query required: false schema: type: integer responses: '200': description: Delegation token page content: application/json: schema: $ref: '#/components/schemas/DelegationListResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/delegations: get: operationId: listDelegations summary: List delegation tokens for the current tenant tags: - Agents - Delegation parameters: - $ref: '#/components/parameters/TenantID' - name: status in: query required: false schema: type: string enum: [active, revoked, expired, all] - name: scope in: query required: false schema: type: string - name: before_expiry in: query required: false schema: type: string format: date-time - name: since_issued in: query required: false schema: type: string format: date-time - name: until_issued in: query required: false schema: type: string format: date-time - name: cursor in: query required: false schema: type: string - name: limit in: query required: false schema: type: integer responses: '200': description: Delegation token page content: application/json: schema: $ref: '#/components/schemas/DelegationListResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/governance/decisions: get: operationId: listGovernanceDecisions summary: Query the governance decision log tags: - Governance parameters: - $ref: '#/components/parameters/TenantID' - name: since in: query required: false schema: type: string - name: until in: query required: false schema: type: string - name: topic in: query required: false schema: type: string - name: rule_id in: query required: false schema: type: string - name: verdict in: query required: false schema: type: string - name: agent_id in: query required: false schema: type: string - name: cursor in: query required: false schema: type: string - name: limit in: query required: false schema: type: integer responses: '200': description: Governance decision page content: application/json: schema: type: object properties: items: type: array items: type: object properties: job_id: {type: string} topic: {type: string} matched_rule: {type: string} verdict: {type: string} reason: {type: string} constraints: type: object additionalProperties: true approval_status: {type: string} approval_decision: {type: string} agent_id: {type: string} policy_version: {type: string} timestamp: {type: string} next_cursor: type: string '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/governance/health: get: operationId: getGovernanceHealth summary: Composite governance health score per tenant description: | Returns the Command Center governance-health aggregate for the tenant: denial-rate, approval-latency p95, policy coverage, and audit-chain integrity rolled up into a 0-100 score plus A-F grade. Admin-only. When governance-health caching is enabled, results are cached per tenant for 60 seconds so dashboards can poll without forcing a full recompute on every refresh. If caching is disabled, the handler falls back to a fresh per-request cache and the endpoint behaves as uncached. tags: - Governance security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' - name: tenant in: query description: Tenant id (must match caller scope) required: false schema: type: string responses: '200': $ref: '#/components/responses/GovernanceHealthResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalServerError' /api/v1/mcp/approvals: get: operationId: listMcpApprovals summary: List MCP tool-call approvals tags: - MCP parameters: - $ref: '#/components/parameters/TenantID' - name: status in: query required: false schema: type: string - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 200 default: 50 - name: cursor in: query required: false schema: type: string responses: '200': description: MCP approval page content: application/json: schema: type: object properties: items: type: array items: type: object additionalProperties: true next_cursor: type: integer format: uint64 '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/mcp/approvals/{id}: get: operationId: getMcpApproval summary: Get one MCP approval record tags: - MCP parameters: - name: id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' responses: '200': description: MCP approval record content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/mcp/approvals/{id}/approve: post: operationId: approveMcpApproval summary: Approve an MCP tool-call approval request tags: - MCP parameters: - name: id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/ApprovalDecisionRequest' responses: '200': description: MCP approval resolved content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/mcp/approvals/{id}/reject: post: operationId: rejectMcpApproval summary: Reject an MCP tool-call approval request tags: - MCP parameters: - name: id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/ApprovalDecisionRequest' responses: '200': description: MCP approval resolved content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/mcp/verify-signature: post: operationId: verifyMcpSignature summary: Verify an outbound MCP signature (admin-gated) description: >- Verify an ECDSA signature over an MCP request against the trusted-key store. Admin-only. See docs/mcp/outbound-signing.md. tags: - MCP security: - apiKey: [] - bearerAuth: [] requestBody: required: true content: application/json: schema: type: object additionalProperties: true responses: '200': description: Verification verdict content: application/json: schema: type: object additionalProperties: true '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/mcp/outbound: get: operationId: listMcpOutbound summary: List outbound MCP signed-call events description: >- Walks the tenant's audit chain stream and returns observed outbound MCP calls, filtered by time range + subject. Admin-only. tags: - MCP security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' - name: since in: query required: false schema: type: string format: date-time - name: until in: query required: false schema: type: string format: date-time - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 500 default: 100 - name: cursor in: query required: false schema: type: string responses: '200': description: Outbound MCP event page content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/mcp/usage: get: operationId: getMcpUsage summary: Outbound MCP usage buckets from the audit chain description: >- Walks the tenant's audit chain and buckets outbound MCP calls by subject, method, and tool for usage analytics. Admin-only. tags: - MCP security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' - name: since in: query required: false schema: type: string format: date-time - name: until in: query required: false schema: type: string format: date-time - name: group_by in: query required: false schema: type: string enum: [subject, method, tool] default: subject responses: '200': description: Usage buckets content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/mcp/tools: get: operationId: listMcpTools summary: List MCP tools visible to an agent or the full catalogue description: >- Returns the MCP tool catalogue. Without an `agent_id` query parameter, returns the unfiltered admin view. With `agent_id`, returns the subset of tools the identity is entitled to call after applying the MCP risk- tier and data-classification filter. tags: - MCP security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' - name: agent_id in: query required: false schema: type: string responses: '200': description: Tool list for the requested scope content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /api/v1/agents/{id}/tools: get: operationId: getAgentToolVisibility summary: List MCP tools visible to a specific agent identity description: >- Returns the subset of the MCP tool catalogue this agent identity can call after applying the identity-aware filter (risk tier + data classification). A revoked or suspended identity returns an empty list with an advisory `note` field rather than failing. tags: - MCP security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' - name: id in: path required: true schema: type: string responses: '200': description: Tool list filtered for the agent identity content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/agents/{id}/denied-events: get: operationId: getAgentDeniedEvents summary: Recent mcp_tool_denied events for an agent identity description: >- Returns up to 50 of the most recent `mcp_tool_denied` audit events for the identity from the gateway's in-memory ring. Feeds the dashboard "recent denials" panel without requiring a SIEM pipeline. tags: - MCP security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' - name: id in: path required: true schema: type: string responses: '200': description: Most recent denial records for the agent content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /api/v1/evals/datasets/from-incidents: post: operationId: createEvalDatasetFromIncidents summary: Derive an eval dataset from governance incidents tags: - Evals parameters: - $ref: '#/components/parameters/TenantID' - name: dry_run in: query required: false schema: type: boolean requestBody: required: true content: application/json: schema: type: object required: [name] properties: tenant: {type: string} since: {type: string} until: {type: string} topic: {type: string} rule_id: {type: string} verdicts: type: array items: {type: string} agent_id: {type: string} max_entries: type: integer name: {type: string} description: {type: string} dry_run: type: boolean responses: '200': description: Dry-run extraction result content: application/json: schema: type: object additionalProperties: true '201': description: Eval dataset created from incidents content: application/json: schema: type: object additionalProperties: true '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' '429': $ref: '#/components/responses/RateLimited' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/evals/datasets: # The gateway registers an internal Go 1.22 catch-all at # `/api/v1/evals/datasets/` and dispatches to `/by-name/{name}`, # `/{id}`, `/{id}/run`, and `/{id}/runs` from there because ServeMux # rejects those sibling patterns as ambiguous. The public API surface # is the canonical collection path plus the concrete child paths below; # `x-subroute-dispatch` tells openapi-audit about the internal prefix # route without modeling an invalid trailing-slash OpenAPI path. x-subroute-dispatch: true get: operationId: listEvalDatasets summary: List eval datasets tags: - Evals parameters: - $ref: '#/components/parameters/TenantID' - name: cursor in: query required: false schema: type: string - name: limit in: query required: false schema: type: integer - name: name_prefix in: query required: false schema: type: string - name: created_after in: query required: false schema: type: string - name: created_before in: query required: false schema: type: string responses: '200': description: Eval dataset page content: application/json: schema: type: object properties: items: type: array items: type: object additionalProperties: true next_cursor: type: string '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' post: operationId: createEvalDataset summary: Create a curated eval dataset tags: - Evals parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: type: object required: [name, version, entries] properties: name: {type: string} version: type: integer description: {type: string} entries: type: array items: type: object properties: id: {type: string} input: type: object additionalProperties: true expected_decision: {type: string} rule_id: {type: string} metadata: type: object additionalProperties: type: string source: {type: string} source_ref: {type: string} notes: {type: string} responses: '201': description: Eval dataset created content: application/json: schema: type: object additionalProperties: true '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' '413': $ref: '#/components/responses/PayloadTooLarge' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/evals/datasets/by-name/{name}: get: operationId: listEvalDatasetVersions summary: List versions for an eval dataset family tags: - Evals parameters: - name: name in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' responses: '200': description: Dataset versions content: application/json: schema: type: object properties: items: type: array items: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/evals/datasets/by-name/{name}/versions/{version}: get: operationId: getEvalDatasetByNameVersion summary: Get a dataset by name and version tags: - Evals parameters: - name: name in: path required: true schema: type: string - name: version in: path required: true schema: type: integer - $ref: '#/components/parameters/TenantID' responses: '200': description: Eval dataset content: application/json: schema: type: object additionalProperties: true '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/evals/datasets/{id}: parameters: - name: id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' get: operationId: getEvalDataset summary: Get one eval dataset tags: - Evals responses: '200': description: Eval dataset content: application/json: schema: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' put: operationId: createEvalDatasetSuccessor summary: Create a successor version from an existing dataset tags: - Evals requestBody: required: true content: application/json: schema: type: object properties: version: type: integer description: type: string entries: type: array items: type: object additionalProperties: true responses: '200': description: Successor dataset created content: application/json: schema: type: object additionalProperties: true '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' '413': $ref: '#/components/responses/PayloadTooLarge' '503': $ref: '#/components/responses/ServiceUnavailable' delete: operationId: deleteEvalDataset summary: Delete an eval dataset tags: - Evals parameters: - name: force in: query required: false schema: type: boolean responses: '204': description: Dataset 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/evals/datasets/{id}/run: post: operationId: runEvalDataset summary: Run an eval dataset against the active or candidate policy description: Synchronous for ≤500 entries (returns 200 with full result), asynchronous for larger datasets (returns 202 with run_id; poll GET /evals/runs/{runId}). tags: - Evals parameters: - name: id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/EvalRunRequest' responses: '200': description: Run completed synchronously content: application/json: schema: $ref: '#/components/schemas/EvalRunResult' '202': description: Run accepted for asynchronous processing content: application/json: schema: $ref: '#/components/schemas/EvalRunAcceptedResponse' '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' '429': $ref: '#/components/responses/RateLimited' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/evals/datasets/{id}/runs: get: operationId: listEvalRuns summary: List historical runs for one eval dataset tags: - Evals parameters: - name: id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' - name: cursor in: query required: false schema: type: string - name: limit in: query required: false schema: type: integer - name: has_regression in: query required: false schema: type: boolean - name: since in: query required: false schema: type: string format: date-time - name: until in: query required: false schema: type: string format: date-time - name: min_score in: query required: false schema: type: number format: float responses: '200': description: Run history page content: application/json: schema: $ref: '#/components/schemas/EvalRunsResponse' '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/evals/runs/{run_id}: parameters: - name: run_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' get: operationId: getEvalRun summary: Get one eval run tags: - Evals responses: '200': description: Completed run result content: application/json: schema: $ref: '#/components/schemas/EvalRunResult' '202': description: Run is still pending or running content: application/json: schema: $ref: '#/components/schemas/EvalRunAcceptedResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' delete: operationId: deleteEvalRun summary: Delete a historical eval run tags: - Evals parameters: - name: force in: query required: false schema: type: boolean responses: '204': description: Eval run 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/governance/approvals/analytics: get: operationId: getApprovalAnalytics summary: Approval analytics (time-to-approve, auto vs manual, bottlenecks) description: | Aggregated approval KPIs for the requested window. Consumes the Policy Decision Log as the source of truth, pairs each require_approval verdict with its ApprovalRecord, and computes per-group breakdowns sorted with the slowest approvers first so bottlenecks surface at the top. Requires the `governance.read` permission. Responses are memoised per `(tenant, window, group_by, limit)` tuple for 30 seconds so dashboards can poll without thrashing the decision-log index. tags: [Governance] security: - apiKey: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantID' - name: window in: query description: Shorthand for `since`/`until` (wins when supplied). One of `24h`, `7d`, `30d`. required: false schema: type: string enum: [24h, 7d, 30d] - name: since in: query description: Inclusive lower bound (RFC3339 or unix milliseconds). Defaults to 24h before `until`. required: false schema: type: string - name: until in: query description: Exclusive upper bound (RFC3339 or unix milliseconds). Defaults to now. required: false schema: type: string - name: group_by in: query description: Breakdown axis; `overall` returns only the window-wide summary. required: false schema: type: string enum: [overall, rule, agent, topic] default: overall - name: limit in: query description: Top-N rows per group (bounded at 50). Ignored when `group_by=overall`. required: false schema: type: integer minimum: 1 maximum: 50 default: 10 responses: '200': description: Approval analytics response content: application/json: schema: $ref: '#/components/schemas/ApprovalAnalyticsResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': description: Decision log store unavailable content: application/json: schema: $ref: '#/components/schemas/Error' 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. Edge event create/batch scopes keys by tenant and endpoint, replays the first response for the same normalized request, and returns 409 `idempotency_conflict` if the same key is reused with a different normalized request. Callers SHOULD send a UUID (per the original Job submit contract); the Edge runtime additionally accepts any non-empty string up to 512 bytes for backward-compatible local-dev keys. schema: type: string format: uuid maxLength: 512 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' EdgeBadRequest: description: Edge bad request — invalid or missing parameters content: application/json: schema: $ref: '#/components/schemas/EdgeError' EdgeUnauthorized: description: Edge unauthorized — missing or invalid credentials content: application/json: schema: $ref: '#/components/schemas/EdgeError' EdgeForbidden: description: Edge forbidden — insufficient permissions or tenant access denied content: application/json: schema: $ref: '#/components/schemas/EdgeError' EdgeNotFound: description: Edge resource not found content: application/json: schema: $ref: '#/components/schemas/EdgeError' EdgeConflict: description: Edge conflict — resource state or idempotency conflict content: application/json: schema: $ref: '#/components/schemas/EdgeError' EdgePayloadTooLarge: description: Edge payload too large content: application/json: schema: $ref: '#/components/schemas/EdgeError' EdgeInternalServerError: description: Edge internal server error content: application/json: schema: $ref: '#/components/schemas/EdgeError' EdgeBadGateway: description: Edge bad gateway — upstream service error content: application/json: schema: $ref: '#/components/schemas/EdgeError' EdgeServiceUnavailable: description: Edge service unavailable content: application/json: schema: $ref: '#/components/schemas/EdgeError' EdgeMaxExecutionsExceeded: description: | The target Edge session already holds the maximum number of AgentExecution rows allowed by the gateway. Configure the cap via CORDUM_EDGE_MAX_EXECUTIONS_PER_SESSION; default 100. End the session and start a new one to continue recording executions. Error envelope `code` is `max_executions_exceeded`; `details` carries `{limit, current}`. content: application/json: schema: $ref: '#/components/schemas/EdgeError' EdgeEventCapExceeded: description: | The target Edge execution already holds the maximum number of AgentActionEvent rows allowed by the store (default 5000). End the execution or start a new session to continue recording evidence. Error envelope `code` is `event_cap_exceeded`. content: application/json: schema: $ref: '#/components/schemas/EdgeError' TierLimit: description: Forbidden by current license entitlements content: application/json: schema: $ref: '#/components/schemas/GenericObject' GovernanceHealthResponse: description: Composite governance health score content: application/json: schema: $ref: '#/components/schemas/GovernanceHealth' schemas: EdgeSessionCreateRequest: type: object properties: tenant_id: type: string description: Optional body tenant; when present it must match X-Tenant-ID. principal_id: type: string principal_type: type: string enum: [human, service, unknown] agent_product: type: string agent_version: type: string mode: type: string enum: [local-dev, enterprise-managed, workflow, ci, prod-runner] repo: type: string git_remote: type: string git_branch: type: string git_sha: type: string cwd: type: string host_id: type: string device_id: type: string trace_id: type: string workflow_run_id: type: string job_id: type: string policy_snapshot: type: string description: Redacted policy snapshot identifier or summary; raw secrets are redacted before persistence/response. enforcement_layers: $ref: '#/components/schemas/EdgeEnforcementLayers' policy_mode: type: string enum: [observe, enforce, enterprise-strict] labels: $ref: '#/components/schemas/EdgeLabels' EdgeSessionCreateResponse: type: object required: [session_id, execution_id, trace_id, policy_snapshot, dashboard_url, session, execution] properties: session_id: type: string execution_id: type: string trace_id: type: string policy_snapshot: type: string description: Redacted policy snapshot identifier or summary; raw secrets are redacted before persistence/response. dashboard_url: type: string description: Relative dashboard URL for the Edge session. session: $ref: '#/components/schemas/EdgeSession' execution: $ref: '#/components/schemas/EdgeAgentExecution' EdgeSessionPageResponse: type: object properties: items: type: array items: $ref: '#/components/schemas/EdgeSession' next_cursor: type: string nullable: true EdgeExecutionPageResponse: type: object properties: items: type: array items: $ref: '#/components/schemas/EdgeAgentExecution' next_cursor: type: string nullable: true EdgeHeartbeatResponse: type: object required: [session_id, heartbeat_alive] properties: session_id: type: string heartbeat_alive: type: boolean EdgeEndSessionRequest: type: object properties: status: type: string enum: [ended, failed] default: ended ended_at: type: string format: date-time EdgeExecutionCreateRequest: type: object required: [session_id] properties: tenant_id: type: string description: Optional body tenant; when present it must match X-Tenant-ID. session_id: type: string adapter: type: string enum: [claude-code-hook, mcp-gateway, llm-proxy, runtime-sidecar, sdk-runner] mode: type: string enum: [local-dev, enterprise-managed, workflow, ci, prod-runner] workflow_run_id: type: string step_id: type: string job_id: type: string attempt: type: integer minimum: 0 trace_id: type: string worker_id: type: string policy_snapshot: type: string description: Redacted policy snapshot identifier or summary; raw secrets are redacted before persistence/response. labels: $ref: '#/components/schemas/EdgeLabels' EdgeEndExecutionRequest: type: object properties: status: type: string enum: [succeeded, failed, cancelled, timeout] default: succeeded ended_at: type: string format: date-time EdgeApprovalDecisionRequest: type: object description: Human resolver note for approving or rejecting an Edge action approval. properties: reason: type: string maxLength: 4096 EdgeApprovalPageResponse: type: object properties: items: type: array items: $ref: '#/components/schemas/EdgeApproval' next_cursor: type: string nullable: true EdgeApproval: type: object description: Tenant-scoped action approval bound to an Edge session/execution/event, action hash, and policy snapshot. Raw action/tool payloads are not stored here; use hashes and artifact pointers for evidence. required: [approval_ref, tenant_id, session_id, execution_id, event_id, principal_id, requester, status, reason, rule_id, policy_snapshot, action_hash, input_hash, created_at, expires_at] properties: approval_ref: type: string pattern: ^edge_appr_[A-Za-z0-9_-]+$ tenant_id: type: string session_id: type: string execution_id: type: string event_id: type: string principal_id: type: string description: Principal that requested the governed action. requester: type: string description: Authenticated requester identity used for self-approval protection. resolver_id: type: string description: Principal that approved/rejected/expired the approval. resolved_by: type: string description: Display/audit identity for the resolver. status: type: string enum: [pending, approved, rejected, expired, invalidated] decision: type: string enum: ["", approve, reject, expire, invalidate] description: Empty while pending; terminal decisions match status. reason: type: string description: Trigger reason explaining why approval was required. resolution_reason: type: string description: Resolver-provided reason or system expiry reason for terminal records. rule_id: type: string policy_snapshot: type: string description: Redacted policy snapshot identifier; used to reject stale approvals. action_hash: type: string description: Stable action hash. The raw action payload is not stored. input_hash: type: string description: Stable hash of the redacted/bounded action input used for evidence correlation. created_at: type: string format: date-time expires_at: type: string format: date-time nullable: true resolved_at: type: string format: date-time nullable: true consumed_at: type: string format: date-time nullable: true description: Set once an approved approval is atomically claimed by the agent retry path. labels: $ref: '#/components/schemas/EdgeLabels' metadata: type: object additionalProperties: type: string EdgeSession: type: object required: [session_id, tenant_id, principal_type, mode, trace_id, policy_mode, status, risk_summary, started_at] properties: session_id: type: string tenant_id: type: string principal_id: type: string principal_type: type: string enum: [human, service, unknown] agent_product: type: string agent_version: type: string mode: type: string enum: [local-dev, enterprise-managed, workflow, ci, prod-runner] repo: type: string git_remote: type: string git_branch: type: string git_sha: type: string cwd: type: string host_id: type: string device_id: type: string trace_id: type: string workflow_run_id: type: string job_id: type: string policy_snapshot: type: string description: Redacted policy snapshot identifier or summary; raw secrets are redacted before persistence/response. enforcement_layers: $ref: '#/components/schemas/EdgeEnforcementLayers' policy_mode: type: string enum: [observe, enforce, enterprise-strict] status: type: string enum: [starting, running, waiting_for_approval, degraded, ended, failed] risk_summary: $ref: '#/components/schemas/EdgeRiskSummary' started_at: type: string format: date-time ended_at: type: string format: date-time nullable: true labels: $ref: '#/components/schemas/EdgeLabels' EdgeAgentExecution: type: object required: [execution_id, session_id, tenant_id, adapter, mode, status, started_at] properties: execution_id: type: string session_id: type: string tenant_id: type: string adapter: type: string enum: [claude-code-hook, mcp-gateway, llm-proxy, runtime-sidecar, sdk-runner] mode: type: string enum: [local-dev, enterprise-managed, workflow, ci, prod-runner] workflow_run_id: type: string step_id: type: string job_id: type: string attempt: type: integer minimum: 0 trace_id: type: string worker_id: type: string policy_snapshot: type: string status: type: string enum: [running, waiting_for_approval, succeeded, failed, cancelled, timeout, degraded] started_at: type: string format: date-time ended_at: type: string format: date-time nullable: true metrics: $ref: '#/components/schemas/EdgeExecutionMetrics' labels: $ref: '#/components/schemas/EdgeLabels' EdgeEvaluateRequest: type: object description: Redacted Edge action evaluation request for an existing session/execution. Do not send raw `tool_input`, `tool_result`, `raw_input`, `raw_transcript`, or `transcript`; send bounded `input_redacted`/`tool_input_redacted` plus `artifact_ptrs`. required: [session_id, execution_id, principal_id, layer, kind] properties: event_id: type: string description: Optional caller-supplied event ID; omitted values are generated by the server. tenant_id: type: string description: Optional body tenant; when present it must match X-Tenant-ID. principal_id: type: string session_id: type: string execution_id: type: string agent_product: type: string layer: type: string enum: [hook, mcp, llm, runtime, workflow, system] kind: type: string description: Non-empty Edge event kind such as `hook.pre_tool_use`. tool_name: type: string description: Hook tool name such as `Bash`; required for hook-layer tool evaluations. tool_use_id: type: string input_redacted: type: object nullable: true additionalProperties: true description: Bounded, redacted action input. The server re-redacts and computes the persisted `input_hash`. tool_input_redacted: type: object nullable: true additionalProperties: true description: Claude-hook-friendly alias for `input_redacted`. input_hash: type: string description: Optional client hash. When redacted input is supplied, the server computes/overwrites the persisted `sha256:` hash from accepted redacted input. tool_input_hash: type: string description: Claude-hook-friendly alias for `input_hash`. cwd: type: string repo: type: string git_remote: type: string git_branch: type: string git_sha: type: string action_name: type: string description: Optional client hint; ignored/overridden by the server-side classifier when derivable. capability: type: string description: Optional client hint; ignored/overridden by the server-side classifier when derivable. risk_tags: type: array description: Optional client hints. Server-side classifier output is authoritative. items: type: string labels: $ref: '#/components/schemas/EdgeLabels' artifact_ptrs: type: array maxItems: 32 description: References to redacted external artifacts for large or sensitive evidence. items: $ref: '#/components/schemas/EdgeArtifactPointer' approval_ttl_seconds: type: integer minimum: 1 maximum: 300 description: | Optional override of the default 5-minute approval TTL when policy returns REQUIRE_APPROVAL (EDGE-059). Bound to [1, 300] seconds server-side; values outside this range or omitted use the 5-minute default. Callers can ONLY shorten the TTL — values >300 are silently capped at the default. Used by e2e tests that need to exercise approval expiration inside a bounded sleep window. example: 2 EdgeEvaluateResponse: type: object required: [decision, permission_decision, exit_code] properties: decision: type: string enum: [ALLOW, DENY, REQUIRE_APPROVAL, THROTTLE, CONSTRAIN] reason: type: string rule_id: type: string policy_snapshot: type: string approval_ref: type: string description: Opaque approval reference from the Safety Kernel/approval integration. This endpoint does not create approval records. constraints: type: object nullable: true additionalProperties: true updated_input: type: object nullable: true additionalProperties: true event_id: type: string description: Persisted Edge decision/degraded event ID. degraded: type: boolean description: True when Safety was unavailable and the response follows policy-mode degraded behavior. error_code: type: string enum: [safety_unavailable] error_message: type: string description: Sanitized bounded error guidance; upstream secret-bearing error strings are not returned. permission_decision: type: string enum: [allow, deny] description: Hook-friendly permission decision. permission_decision_reason: type: string exit_code: type: integer description: Hook/terminal-friendly exit code; zero for allow-like responses and non-zero for deny/block guidance. terminal_title: type: string terminal_message: type: string wait_strategy: type: string enum: [manual_approval, backoff, retry] timeout_ms: type: integer minimum: 0 EdgeAgentActionEventWriteRequest: type: object description: Redacted event write payload. Do not send raw `tool_input`, `tool_result`, `raw_input`, `raw_transcript`, or `transcript` fields; large evidence belongs in `artifact_ptrs`. required: [event_id, session_id, execution_id, ts, layer, kind, decision, status] properties: event_id: type: string session_id: type: string execution_id: type: string tenant_id: type: string description: Optional body tenant; when present it must match X-Tenant-ID. principal_id: type: string seq: type: integer minimum: 0 description: Optional sequence number. Omit or set 0 for server assignment; explicit values must be the next sequence for the execution. ts: type: string format: date-time layer: type: string enum: [hook, mcp, llm, runtime, workflow, system] kind: type: string description: Open-ended non-empty event kind such as `hook.pre_tool_use` or `mcp.tool.pre`. agent_product: type: string tool_name: type: string tool_use_id: type: string action_name: type: string capability: type: string risk_tags: type: array items: type: string input_redacted: type: object nullable: true description: Bounded, redacted, client-safe input summary. Secrets and large raw payloads are rejected; store large evidence using `artifact_ptrs`. additionalProperties: true input_hash: type: string description: Optional stable hash. When `input_redacted` is present, the server derives a stable `sha256:` hash before persistence. decision: type: string enum: [ALLOW, DENY, REQUIRE_APPROVAL, THROTTLE, CONSTRAIN, RECORDED] decision_reason: type: string rule_id: type: string policy_snapshot: type: string approval_ref: type: string artifact_ptrs: type: array maxItems: 32 description: References to redacted external artifacts for transcripts, tool inputs/results, diffs, or evidence bundles that are too large or sensitive for inline event storage. items: $ref: '#/components/schemas/EdgeArtifactPointer' duration_ms: type: integer minimum: 0 status: type: string enum: [ok, blocked, failed, degraded] error_code: type: string error_message: type: string labels: $ref: '#/components/schemas/EdgeLabels' EdgeAgentActionEventBatchRequest: type: object required: [events] properties: events: type: array minItems: 1 items: $ref: '#/components/schemas/EdgeAgentActionEventWriteRequest' EdgeAgentActionEventBatchResponse: type: object properties: items: type: array items: $ref: '#/components/schemas/EdgeAgentActionEvent' EdgeAgentActionEventPageResponse: type: object properties: items: type: array items: $ref: '#/components/schemas/EdgeAgentActionEvent' next_cursor: type: string nullable: true EdgeAgentActionEvent: type: object required: [event_id, session_id, execution_id, tenant_id, seq, ts, layer, kind, decision, status] properties: event_id: type: string session_id: type: string execution_id: type: string tenant_id: type: string principal_id: type: string seq: type: integer minimum: 0 ts: type: string format: date-time layer: type: string enum: [hook, mcp, llm, runtime, workflow, system] kind: type: string description: Open-ended non-empty event kind such as `hook.pre_tool_use` or `mcp.tool.pre`. agent_product: type: string tool_name: type: string tool_use_id: type: string action_name: type: string capability: type: string risk_tags: type: array items: type: string input_redacted: type: object nullable: true description: Bounded redacted input summary safe to persist and return. additionalProperties: true input_hash: type: string description: Stable `sha256:` hash for the original input when available. decision: type: string enum: [ALLOW, DENY, REQUIRE_APPROVAL, THROTTLE, CONSTRAIN, RECORDED] decision_reason: type: string rule_id: type: string policy_snapshot: type: string approval_ref: type: string artifact_ptrs: type: array maxItems: 32 description: References to redacted external evidence artifacts. Large tool payloads/transcripts are represented here instead of inline raw event fields. items: $ref: '#/components/schemas/EdgeArtifactPointer' duration_ms: type: integer minimum: 0 status: type: string enum: [ok, blocked, failed, degraded] error_code: type: string error_message: type: string labels: $ref: '#/components/schemas/EdgeLabels' EdgeArtifactPointer: type: object description: Pointer to redacted evidence stored outside the event row. Its tenant/session/execution/event IDs must match the enclosing event, and the URI must be an internal artifact reference rather than a signed URL or token-bearing external URI. required: [artifact_type, session_id, execution_id, event_id, tenant_id, retention_class, redaction_level, sha256, uri, created_at] properties: artifact_type: type: string enum: [edge.transcript, edge.diff, edge.tool_input, edge.tool_result, edge.evidence_bundle] session_id: type: string execution_id: type: string event_id: type: string tenant_id: type: string retention_class: type: string enum: [short, standard, audit] redaction_level: type: string enum: [standard, strict] sha256: type: string uri: type: string description: Internal artifact URI such as `artifact://...` or `edge-artifact://...`; signed URLs, query-string tokens, userinfo, fragments, and external HTTP(S) storage URLs are rejected before persistence. created_at: type: string format: date-time EdgeRiskSummary: type: object properties: denied_count: type: integer approval_count: type: integer artifact_count: type: integer max_risk: type: string enum: [low, medium, high, critical] EdgeExecutionMetrics: type: object properties: events: type: integer allow: type: integer deny: type: integer require_approval: type: integer artifacts: type: integer llm_cost_usd: type: number EdgeLabels: type: object additionalProperties: type: string EdgeEnforcementLayers: type: object additionalProperties: type: boolean ApprovalAnalyticsResponse: type: object required: [window, summary] properties: window: type: object required: [since, until] properties: since: type: string format: date-time until: type: string format: date-time summary: $ref: '#/components/schemas/ApprovalAnalyticsSummary' groups: type: array items: $ref: '#/components/schemas/ApprovalAnalyticsGroup' ApprovalAnalyticsSummary: type: object required: [total, approved, rejected, expired, auto_resolved, manual_resolved] properties: total: type: integer description: Total decisions with verdict=require_approval in the window (includes still-pending). approved: type: integer rejected: type: integer expired: type: integer auto_resolved: type: integer description: Resolutions driven by lifecycle events (expire/invalidate/repair), not human decision. manual_resolved: type: integer description: Resolutions where a human approver entered approve or reject. avg_time_to_approve_seconds: type: number nullable: true description: Null when no approvals resolved in the window — distinguishes "no data" from "all resolved in 0 s". p50: type: number nullable: true p90: type: number nullable: true p99: type: number nullable: true ApprovalAnalyticsGroup: type: object required: [key, label, total, approved, rejected, expired, auto_count, manual_count] properties: key: type: string description: Group identifier (rule id, agent id, or topic string). label: type: string total: type: integer approved: type: integer rejected: type: integer expired: type: integer auto_count: type: integer manual_count: type: integer avg_ttar_seconds: type: number nullable: true p90_seconds: type: number nullable: true GovernanceHealth: type: object required: [score, grade, generated_at, factors] properties: score: type: integer grade: type: string enum: [A, B, C, D, F] generated_at: type: string format: date-time factors: type: object additionalProperties: $ref: '#/components/schemas/GovernanceHealthFactor' truncated_at_max: type: boolean GovernanceHealthFactor: type: object required: [score, weight] properties: score: type: integer weight: type: number raw: description: Factor-specific raw measurement payload. notes: type: string DelegationChainLink: type: object required: [agent_id, issued_at, expires_at, jti, issued_by] properties: agent_id: type: string issued_at: type: string format: date-time expires_at: type: string format: date-time jti: type: string parent_jti: type: string nullable: true issued_by: type: string DelegationView: type: object required: [jti, issuer, subject, audience, chain_depth, issued_at, expires_at, revoked] properties: jti: type: string issuer: type: string description: Root issuer for the delegation chain. subject: type: string description: Agent that issued the token. audience: type: string description: Target agent the token was minted for. allowed_actions: type: array items: type: string allowed_topics: type: array items: type: string chain: type: array items: $ref: '#/components/schemas/DelegationChainLink' chain_depth: type: integer issued_at: type: string format: date-time expires_at: type: string format: date-time revoked: type: boolean revoked_at: type: string format: date-time nullable: true revoked_reason: type: string nullable: true DelegationListResponse: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/DelegationView' next_cursor: type: string nullable: true EdgeError: type: object description: Standard error envelope for all `/api/v1/edge/*` routes. Messages and details are sanitized and must not echo raw tool payloads, API keys, signed URLs, or secrets. required: - code - message - request_id properties: code: type: string description: Stable machine-readable Edge error code. enum: - unauthorized - access_denied - tenant_required - tenant_mismatch - tenant_access_denied - missing_path_param - invalid_request - invalid_json - missing_required_field - not_found - request_too_large - service_unavailable - store_unavailable - internal_error - upstream_error - conflict - session_terminal - execution_terminal - execution_session_mismatch - raw_payload_rejected - artifact_pointer_invalid - approval_conflict - approval_not_actionable - self_approval_denied - idempotency_conflict - idempotency_key_invalid - idempotency_window_expired - max_executions_exceeded - event_cap_exceeded example: idempotency_conflict message: type: string description: Sanitized human-readable message. example: idempotency key already used with a different request request_id: type: string description: Request correlation id from `X-Request-Id`/middleware. details: type: object additionalProperties: true description: Optional sanitized structured details. 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_groups_claim: type: string description: OIDC claim containing IdP group names, defaulting to groups. oidc_group_role_mapping: type: object additionalProperties: type: string enum: [admin, operator, viewer] description: Case-insensitive group name to Cordum role mapping. oidc_client_secret_masked: type: string session_ttl: type: string require_rbac: type: boolean require_principal: type: boolean default_tenant: type: string OIDCGroupRoleMappingUpdateRequest: type: object required: [oidc_group_role_mapping] properties: oidc_groups_claim: type: string default: groups description: OIDC claim containing IdP group names. oidc_group_role_mapping: type: object additionalProperties: type: string enum: [admin, operator, viewer] description: JSON object mapping Okta/OIDC group names to Cordum roles. 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 CopilotSessionDetailResponse: type: object required: - session - jobs - decisions - truncated properties: session: $ref: '#/components/schemas/CopilotSession' jobs: type: array items: $ref: '#/components/schemas/CopilotSessionJob' decisions: type: array items: $ref: '#/components/schemas/CopilotSessionDecision' truncated: type: boolean description: True when the backend capped jobs or decisions at 500 entries. CopilotSession: type: object required: - id - userId - createdAt - updatedAt - messages properties: id: type: string title: type: string userId: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time messages: type: array items: $ref: '#/components/schemas/CopilotMessage' metadata: type: object additionalProperties: type: string CopilotMessage: type: object required: - id - role - content - timestamp properties: id: type: string role: type: string enum: [user, assistant, tool, system] content: type: string timestamp: type: string format: date-time jobIds: type: array items: type: string CopilotSessionJob: type: object required: - id - status - capabilities - riskTags - metadata properties: id: type: string type: type: string topic: type: string status: type: string pool: type: string capabilities: type: array items: type: string riskTags: type: array items: type: string metadata: type: object additionalProperties: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time CopilotSessionDecision: type: object required: - jobId - verdict - timestamp properties: jobId: type: string topic: type: string matchedRule: type: string ruleName: type: string verdict: type: string reason: type: string constraints: type: object nullable: true additionalProperties: true approvalStatus: type: string approvalDecision: type: string agentId: type: string policyVersion: type: string timestamp: type: string format: date-time 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 delegation: $ref: '#/components/schemas/DelegationLineageView' DelegationLineageChainLink: type: object properties: agent_id: type: string issued_at: type: string format: date-time expires_at: type: string format: date-time jti: type: string parent_jti: type: string DelegationLineageView: type: object properties: jti: type: string audience: type: string root_issuer: type: string parent_issuer: type: string chain_depth: type: integer chain: type: array items: $ref: '#/components/schemas/DelegationLineageChainLink' scope: type: array items: type: string expires_at: type: string format: date-time verified_at: type: integer format: int64 reverified_at_dispatch: type: boolean 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 policy_gate: type: string enum: - allow - deny - require_approval description: | Optional design-time policy hint. Populated at workflow-save time when the policy engine resolves a hint for this step. Unset means "no hint" — clients render no design-time icon and defer to runtime safety decision. NEVER defaults to "allow" when unset. example: allow 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 audit_hash: type: string nullable: true description: | Audit-chain hash for the safety decision applied to this step, joined from the audit-chain entry produced when the step ran. Unset for skipped or upstream-failed steps where no decision was emitted. Dashboard surfaces this as a copy-on-click chip in the WorkflowNodeGovernanceOverlay. example: 0x9f3a8b2c4d5e6f70 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 PolicyGlobalSection: type: object description: | One section of the unified Global policy. The bundle_id is the underlying configsvc key (e.g. `secops/global-input`, `secops/invariants`). Empty content means no studio overrides are authored for this section. properties: bundle_id: type: string content: type: string description: YAML SafetyPolicy fragment for this section. sha256: type: string enabled: type: boolean PolicyGlobalDocument: type: object description: | EDGE-052 — five-section view of the Global policy authority. The snapshot_hash matches the cfg:<sha> identifier the kernel propagates on policy decisions; clients pass it back on PUT for optimistic concurrency. properties: snapshot_version: type: string snapshot_hash: type: string updated_at: type: string format: date-time sections: type: object additionalProperties: $ref: '#/components/schemas/PolicyGlobalSection' description: Keyed by section name — input_rules, output_rules, edge_action_rules, mcp_tool_rules, invariants. UpdatePolicyGlobalRequest: type: object properties: snapshot_version: type: string description: Optimistic concurrency token. When non-empty and != current snapshot, request is rejected with 409. author: type: string message: type: string sections: type: object additionalProperties: type: object properties: content: type: string description: YAML SafetyPolicy fragment. Empty content deletes the section's bundle. enabled: type: boolean description: Keyed by section name. Only listed sections are written; absent sections remain unchanged. required: - sections PublishPolicyRequest: type: object properties: bundle_ids: type: array items: type: string author: type: string message: type: string note: type: string required: - bundle_ids - author PolicyShadow: type: object description: | Full stored representation of a shadow candidate policy. `content` is the raw YAML source — consumers that only need to list shadows may prefer the server-side summary projection (not exposed as a separate endpoint today). properties: shadow_bundle_id: type: string description: Stable `shadow-<12 hex>` handle that persists across reactivation. bundle_id: type: string description: Active bundle this shadow is tied to (one shadow per bundle). tenant_id: type: string content: type: string description: Raw YAML source of the candidate policy. created_at: type: string format: date-time activated_at: type: string format: date-time description: Bumped on each re-activation; equals `created_at` on the first. created_by: type: string description: Principal ID that activated the shadow. metadata: type: object additionalProperties: type: string description: Arbitrary operator-supplied key/value pairs (e.g. ticket refs). required: - shadow_bundle_id - bundle_id - tenant_id - content - created_at - activated_at - created_by PolicyShadowUpsertRequest: type: object description: PUT body for activating or replacing a shadow policy. properties: content: type: string description: Raw YAML source of the candidate policy. Required. metadata: type: object additionalProperties: type: string description: Arbitrary operator-supplied key/value pairs; optional. required: - content ShadowResultsSummary: type: object properties: bundle_id: type: string shadow_bundle_id: type: string from_ms: type: integer format: int64 to_ms: type: integer format: int64 total_evaluated: type: integer format: int64 escalated_count: type: integer format: int64 description: Shadow would have been stricter than active (e.g. DENY vs ALLOW). relaxed_count: type: integer format: int64 description: Shadow would have been more permissive than active. approval_differ_count: type: integer format: int64 description: Both reached a terminal decision but differed on REQUIRE_APPROVAL. unchanged_count: type: integer format: int64 truncated_at_max: type: boolean description: True when the scan hit its event budget before reaching `to_ms`. required: - bundle_id - from_ms - to_ms - total_evaluated - escalated_count - relaxed_count - approval_differ_count - unchanged_count - truncated_at_max ShadowComparisonEntry: type: object properties: ts_ms: type: integer format: int64 job_id: type: string agent_id: type: string shadow_bundle_id: type: string active_verdict: type: string shadow_verdict: type: string diff: type: string enum: [escalated, relaxed, approval_differ, unchanged] active_rule_id: type: string shadow_rule_id: type: string latency_ms: type: string description: Shadow-evaluation latency in ms, stored as a string in the audit event Extra map. seq: type: integer format: int64 ShadowComparisonsResponse: type: object properties: entries: type: array items: $ref: '#/components/schemas/ShadowComparisonEntry' next_cursor: type: string description: Redis stream ID (`-`) to pass back as `?cursor=` for the next page. Empty when no more rows. truncated_at_max: type: boolean required: - entries - truncated_at_max ShadowTimeseriesBucket: type: object properties: ts_ms: type: integer format: int64 description: Bucket start time (aligned down to the bucket boundary). escalated: type: integer format: int64 relaxed: type: integer format: int64 approval_differ: type: integer format: int64 unchanged: type: integer format: int64 total: type: integer format: int64 required: - ts_ms - escalated - relaxed - approval_differ - unchanged - total ShadowTimeseriesResponse: type: object properties: bucket: type: string description: Bucket width as the caller requested (e.g. `1m`, `5m`). enum: [1m, 5m, 15m, 1h, 1d] from_ms: type: integer format: int64 to_ms: type: integer format: int64 buckets: type: array items: $ref: '#/components/schemas/ShadowTimeseriesBucket' truncated_at_max: type: boolean required: - bucket - from_ms - to_ms - buckets - truncated_at_max RollbackPolicyRequest: type: object properties: snapshot_id: type: string author: type: string message: type: string note: type: string required: - snapshot_id - author PolicyAuditEntry: type: object description: | One entry in the policy audit log. Mirrors `policybundles.PolicyAuditEntry` in `core/controlplane/gateway/policybundles/types.go`. The legacy `author` / `timestamp` / `bundle_id` (singular) / `snapshot_id` fields are preserved for backwards-compat with consumers of the v1 spec but are not populated by the current backend handler; prefer `actor_id` / `created_at` / `bundle_ids` (plural) / `snapshot_before` + `snapshot_after`. properties: id: type: string action: type: string author: type: string deprecated: true description: | Deprecated. The current backend handler does not populate this field; it remains in the schema only to avoid breaking consumers that read the v1 type. timestamp: type: string format: date-time deprecated: true description: | Deprecated. Use `created_at`. The current backend handler does not populate this field. bundle_id: type: string nullable: true deprecated: true description: | Deprecated. Use `bundle_ids` (plural). The current backend handler does not populate this field. snapshot_id: type: string nullable: true deprecated: true description: | Deprecated. Use `snapshot_before` and `snapshot_after`. The current backend handler does not populate this field. message: type: string nullable: true resource_type: type: string nullable: true description: Audited resource kind, e.g. `rule`, `bundle`, `input`, `output`. resource_id: type: string nullable: true resource_name: type: string nullable: true actor_id: type: string nullable: true description: Identifier of the principal who performed the action. role: type: string nullable: true auth_source: $ref: '#/components/schemas/AuthSource' agent_id: type: string nullable: true agent_name: type: string nullable: true agent_risk_tier: type: string nullable: true bundle_ids: type: array nullable: true items: type: string description: | Bundles affected by this audit event. Replaces the legacy singular `bundle_id` field. reason: type: string nullable: true decision: type: string nullable: true matched_rule: type: string nullable: true policy_version: type: string nullable: true extra: type: object nullable: true additionalProperties: type: string description: | Free-form per-event metadata. Keys and values are emitted verbatim from the gateway handler's audit-construction site. snapshot_before: type: string nullable: true description: Audit-chain hash of the resource state before the action. snapshot_after: type: string nullable: true description: Audit-chain hash of the resource state after the action. created_at: type: string description: | RFC3339 timestamp when the audit entry was created. Plain string (not `format: date-time`) because the backend stores the raw value and lex-compares it against `after` / `before` query params. required: - id - action - created_at PolicyAuditEnvelope: type: object description: | Paginated envelope returned by `GET /api/v1/policy/audit`. The `type=output` special path on the same endpoint returns only the `items` field; consumers should treat `total` / `has_more` / `offset` as optional when filtering by that type. properties: items: type: array items: $ref: '#/components/schemas/PolicyAuditEntry' total: type: integer format: int64 description: Total entries matching the filter (pre-pagination). has_more: type: boolean description: True when more entries are available past the current window. offset: type: integer format: int64 description: Echo of the requested offset (default 0). required: - items AuthSource: type: string nullable: true description: | Authentication mechanism that validated the request. Mirrors the `auth.AuthSource` typed enum at `core/controlplane/gateway/auth/types.go`. enum: - api_key - jwt - oidc - session 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 verification: $ref: '#/components/schemas/InstalledPackVerification' description: | Server-verified pack signature state. Pre-existing installs from before signature verification shipped read as absent; clients SHOULD render an absent value as {signed: false}. InstalledPackVerification: type: object description: | Pack-signature verification outcome computed by the gateway at install time. Never client-supplied — the gateway discards any `verification` field on the install payload and computes its own. Pre-existing installs default to {signed: false} when this object is absent. required: [signed] properties: signed: type: boolean description: | True when the gateway successfully verified the pack against a trusted publisher key at install time. publisher_id: type: string description: Publisher identifier derived from the trusted key's metadata. kid: type: string description: Key id of the Ed25519 public key that verified the pack. verified_at: type: string format: date-time description: RFC3339 timestamp of the server-side verification. has_cordum_counter_sig: type: boolean description: | True when a valid Cordum counter-signature envelope (pack.yaml.sig.cordum) is present and verified against the embedded Cordum counter-signing key. signature_algorithm: type: string enum: [ed25519] description: Algorithm advertised by the verified envelope. Always ed25519 today. pack_signature_version: type: integer minimum: 1 description: Canonical manifest version that was signed. warnings: type: array description: | Non-fatal warnings the gateway wants to surface alongside the verification outcome (for example, "pack accepted unsigned — gateway strict mode disabled"). items: type: string 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 AuditVerifyGap: type: object properties: at_seq: type: integer format: int64 description: Sequence number where the gap or mismatch was observed. type: type: string enum: - missing - out_of_order - hash_mismatch - retention_trimmed required: - at_seq - type AuditVerifyResult: type: object properties: status: type: string enum: - ok - compromised - partial total_events: type: integer description: Number of events walked in the requested range. verified_events: type: integer description: Number of events whose hash and linkage verified. gaps: type: array items: $ref: '#/components/schemas/AuditVerifyGap' retention_boundary_seq: type: integer format: int64 description: Lowest sequence number still present in the retained stream. retention_window_hours: type: number format: double description: Configured audit retention window in hours. first_seq: type: integer format: int64 description: First sequence number observed in the verified range. last_seq: type: integer format: int64 description: Last sequence number observed in the verified range. required: - status - total_events - verified_events - gaps - retention_boundary_seq 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 EvalRunRequest: type: object properties: use_current_policy: type: boolean candidate_bundle_id: type: string candidate_content: type: string max_entries: type: integer EvalRunAcceptedResponse: type: object properties: run_id: type: string format: uuid status: type: string enum: [pending, running, failed] poll_url: type: string dataset_id: type: string dataset_name: type: string dataset_version: type: integer tenant: type: string started_at: type: string format: date-time completed_at: type: string format: date-time policy_snapshot: type: string error: type: string required: - run_id - status EvalRunSummary: type: object properties: total: type: integer passed: type: integer failed: type: integer regressions: type: integer errored: type: integer score_percent: type: number format: float nullable: true required: - total - passed - failed - regressions - errored EvalEntryResult: type: object properties: entry_id: type: string input: type: object additionalProperties: true expected_decision: type: string actual_decision: type: string rule_id: type: string reason: type: string status: type: string enum: [pass, fail, regression, error] description: regression = expected was deny/require_approval/throttle/allow_with_constraints AND actual is allow drift_direction: type: string enum: [escalated, relaxed, unchanged] error: type: string required: - entry_id - status - drift_direction EvalRunResult: type: object properties: run_id: type: string format: uuid dataset_id: type: string dataset_name: type: string dataset_version: type: integer tenant: type: string policy_snapshot: type: string started_at: type: string format: date-time completed_at: type: string format: date-time summary: $ref: '#/components/schemas/EvalRunSummary' entries: type: array items: $ref: '#/components/schemas/EvalEntryResult' required: - run_id - dataset_id - dataset_name - dataset_version - tenant - policy_snapshot - started_at - completed_at - summary - entries EvalRunsResponse: type: object properties: items: type: array items: $ref: '#/components/schemas/EvalRunResult' next_cursor: type: string required: - items 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