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 x-cordum-error-codes: - AUTH_OIDC_CALLBACK_FAILED 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 x-cordum-error-codes: - AUTH_INVALID_CREDENTIALS 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 x-cordum-error-codes: - AUTH_TOKEN_INVALID 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 x-cordum-error-codes: - AUTH_REQUEST_INVALID - AUTH_INVALID_CREDENTIALS - AUTH_PASSWORD_INVALID - AUTH_USER_NOT_FOUND 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 x-cordum-error-codes: - AUTH_REQUEST_INVALID - AUTH_USER_CONFLICT 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 x-cordum-error-codes: - AUTH_TOKEN_INVALID - AUTH_REQUEST_INVALID 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 x-cordum-error-codes: - AUTH_REQUEST_INVALID - AUTH_USER_NOT_FOUND 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 x-cordum-error-codes: - AUTH_REQUEST_INVALID - AUTH_USER_NOT_FOUND 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 x-cordum-error-codes: - AUTH_REQUEST_INVALID - AUTH_PASSWORD_INVALID - AUTH_USER_NOT_FOUND 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 x-cordum-error-codes: - AUTH_KEY_INVALID 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 x-cordum-error-codes: - AUTH_KEY_INVALID - AUTH_KEY_NOT_FOUND 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/mcp/upstreams: get: operationId: listEdgeMCPUpstreams summary: List approved upstream MCP servers description: Lists tenant-scoped upstream MCP registry entries plus system-wide entries. Secret material is never resolved; `auth_secret_ref` is returned only as a redacted `secret://` reference. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: enabled in: query required: false schema: type: boolean description: Optional visibility filter. Omit to return enabled and disabled records for admin visibility. responses: '200': description: Upstream MCP registry entries content: application/json: schema: $ref: '#/components/schemas/MCPUpstreamListResponse' '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: createEdgeMCPUpstream summary: Create or validate an upstream MCP server entry description: Creates a tenant-scoped upstream MCP registry entry. With `validate_only=true`, validates the entry without storing it and returns a validation verdict. Raw secrets are rejected; use `secret://` auth references. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: validate_only in: query required: false schema: type: boolean default: false requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/MCPUpstreamServerWriteRequest' responses: '200': description: Validation-only verdict when `validate_only=true` content: application/json: schema: $ref: '#/components/schemas/MCPUpstreamValidationResponse' '201': description: Upstream MCP registry entry created content: application/json: schema: $ref: '#/components/schemas/MCPUpstreamServer' '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/EdgeConflict' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/mcp/upstreams/list: get: operationId: listEdgeMCPUpstreamsLegacy summary: List approved upstream MCP servers (compatibility path) description: Compatibility alias for `GET /api/v1/edge/mcp/upstreams`. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: enabled in: query required: false schema: type: boolean description: Optional visibility filter. Omit to return enabled and disabled records for admin visibility. responses: '200': description: Upstream MCP registry entries content: application/json: schema: $ref: '#/components/schemas/MCPUpstreamListResponse' '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/mcp/upstreams/{name}: get: operationId: getEdgeMCPUpstream summary: Get an upstream MCP server entry tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: name in: path required: true schema: type: string responses: '200': description: Upstream MCP registry entry content: application/json: schema: $ref: '#/components/schemas/MCPUpstreamServer' '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' put: operationId: updateEdgeMCPUpstream summary: Update an upstream MCP server entry description: Updates a tenant-scoped upstream MCP registry entry and stores a best-effort backup of the prior entry before overwrite. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: name in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/MCPUpstreamServerWriteRequest' responses: '200': description: Updated upstream MCP registry entry content: application/json: schema: $ref: '#/components/schemas/MCPUpstreamServer' '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/mcp/upstreams/{name}/disable: post: operationId: disableEdgeMCPUpstream summary: Disable an upstream MCP server entry tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: name in: path required: true schema: type: string responses: '200': description: Disabled upstream MCP registry entry content: application/json: schema: $ref: '#/components/schemas/MCPUpstreamServer' '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/mcp/upstreams/{name}/enable: post: operationId: enableEdgeMCPUpstream summary: Enable an upstream MCP server entry tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: name in: path required: true schema: type: string responses: '200': description: Enabled upstream MCP registry entry content: application/json: schema: $ref: '#/components/schemas/MCPUpstreamServer' '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/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/runtime/events: post: operationId: ingestEdgeRuntimeEvents summary: Ingest bounded runtime telemetry from a trusted sidecar description: >- Disabled by default. When `CORDUM_EDGE_RUNTIME_INGEST_ENABLED` is unset (or set to a non-truthy value), the route returns 503 `service_unavailable` and persists nothing. When enabled, the endpoint accepts a bounded, redacted runtime event batch (process exec, file read/write, network connect, DNS query) from an authenticated runtime collector holding `edge.runtime.ingest`; generic job writers are forbidden. A bounded `nonce` is required by default and is checked against a Redis replay window scoped to `(tenant, collector)`; duplicate nonce submissions return idempotent success without appending another copy. The request `source.source_id` must match the authenticated collector principal and the referenced session/execution must be bound to that collector before mapped `AgentActionEvent` records with `layer=runtime` and `decision=RECORDED` are persisted through the existing Edge store. Raw argv, file contents, packet payloads, DNS response bodies, request bodies, headers, secrets, and tokens are rejected at the strict-schema decode boundary. All-or-nothing batch acceptance — a single invalid envelope aborts the whole batch. See `docs/edge/runtime-ingestion.md` for the full contract. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EdgeRuntimeIngestRequest' responses: '200': description: Duplicate nonce detected; replay suppressed idempotently content: application/json: schema: $ref: '#/components/schemas/EdgeRuntimeIngestResponse' '201': description: Runtime events appended content: application/json: schema: $ref: '#/components/schemas/EdgeRuntimeIngestResponse' '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': description: Per-execution event cap exceeded, or runtime replay window cardinality exceeded (`replay_window_full`) content: application/json: schema: $ref: '#/components/schemas/EdgeError' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/llm/events: post: operationId: ingestEdgeLLMEvents summary: Ingest intercepted LLM chat turns from a trusted proxy description: >- Disabled by default. When `CORDUM_EDGE_LLM_INGEST_ENABLED` is unset (or non-truthy) the route returns 503 `service_unavailable` and persists nothing. When enabled, an authenticated LLM proxy holding `edge.llm.ingest` submits a bounded batch of intercepted model interactions (prompt/response/cost). The gateway redacts prompt and response content via the Edge redactor, classifies and records each turn as an `AgentActionEvent` with `layer=llm` and `decision=RECORDED`, and returns a per-event advisory decision (`record` | `redact`) plus the redacted content and detected secret finding types. The ALLOW/DENY policy decision is NOT made here — a proxy that must block a prompt pairs this call with `POST /api/v1/edge/evaluate` (layer-agnostic, already classifies `layer=llm`). The `source.source_id` must match the authenticated proxy principal and the referenced session/execution must exist in the tenant under the `llm-proxy` execution adapter. An optional bounded `nonce` is deduplicated against a Redis replay window scoped to `(tenant, llm-proxy)`. Raw provider keys, headers, cookies, and authorization values are rejected at the strict-schema decode boundary. All-or-nothing batch acceptance. See `docs/edge/llm-proxy-governance.md`. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EdgeLLMIngestRequest' responses: '200': description: Duplicate nonce detected; replay suppressed idempotently content: application/json: schema: $ref: '#/components/schemas/EdgeLLMIngestResponse' '201': description: LLM events redacted and appended content: application/json: schema: $ref: '#/components/schemas/EdgeLLMIngestResponse' '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': description: Per-execution event cap exceeded (`event_cap_exceeded`), or replay window cardinality exceeded (`replay_window_full`) content: application/json: schema: $ref: '#/components/schemas/EdgeError' '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/edge/shadow-agents: post: operationId: createShadowAgentFinding summary: Ingest one redacted shadow-agent finding description: Persists a single ShadowAgentFinding lifecycle record. Evidence must be either a bounded redacted summary or an artifact pointer; raw payloads are rejected. Observe/warn only — no enforcement, no remediation execution, no Cordum Job creation. Audit event `shadow_agent.detected` is emitted on success. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateShadowAgentFindingRequest' responses: '201': description: Shadow finding persisted content: application/json: schema: $ref: '#/components/schemas/ShadowAgentFinding' '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' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' get: operationId: listShadowAgentFindings summary: List shadow-agent findings description: Returns a tenant-scoped cursor-paginated page of ShadowAgentFinding records. Filters narrow the result set; the narrowest filter is selected as the primary index path. Resolved/suppressed records past terminal retention are hidden. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: status in: query description: Filter by triage status. Managed-skip rows are not filterable with `status=managed_skip`; request `include_managed_skip=true` to include findings whose response status is `managed_skip`. schema: type: string enum: [detected, resolved, suppressed] - name: risk in: query schema: type: string enum: [low, medium, high, critical] - name: agent in: query description: Alias for `agent_product`. The two parameter names are interchangeable. schema: type: string - name: agent_product in: query schema: type: string - name: owner in: query description: Filter by `owner_principal_id`. schema: type: string - name: limit in: query schema: type: integer minimum: 1 maximum: 200 default: 50 - name: cursor in: query schema: type: string - name: source_type in: query description: EDGE-143.5 — restrict to findings emitted by one detector family. `local` falls back to a broad tenant index + post-filter so legacy EDGE-141 findings without `source_type` surface (defaults-on-read map to `local`). schema: type: string enum: [local, kubernetes, ci, network] - name: cluster_id in: query description: EDGE-143.5 — Kubernetes cluster id (Q7 cross-cluster slicing). Shared (cross-tenant) index; tenant isolation enforced at read time. schema: type: string maxLength: 64 - name: namespace in: query description: EDGE-143.5 — Kubernetes namespace. schema: type: string maxLength: 63 - name: ci_provider in: query description: EDGE-143.5 — CI provider enum; required when `repo` is set. schema: type: string enum: [github_actions, gitlab_ci, jenkins, buildkite, circleci, other] - name: repo in: query description: EDGE-143.5 — `org/repo` for CI findings; `ci_provider` MUST also be set or the request is rejected with 400. schema: type: string maxLength: 256 - name: signal in: query description: EDGE-143.5 — any-of filter on `signal_set`. Repeatable up to 16 times; each entry must match `[a-z0-9_]{1,32}`. Multi-signal queries do not support cursor pagination. explode: true schema: type: array maxItems: 16 items: type: string pattern: '^[a-z0-9_]{1,32}$' - name: confidence_min in: query description: EDGE-143.5 — minimum detector self-confidence in [0, 1]. schema: type: number format: float minimum: 0 maximum: 1 - name: first_seen_after in: query description: EDGE-143.5 — return findings whose `first_seen` is strictly after this RFC3339 timestamp. schema: type: string format: date-time - name: last_seen_before in: query description: EDGE-143.5 — return findings whose `last_seen` is strictly before this RFC3339 timestamp. schema: type: string format: date-time - name: exception_id in: query description: EDGE-143.5 — filter to findings joined to a specific operator exception declaration. schema: type: string maxLength: 64 - name: include_managed_skip in: query description: EDGE-143.5 — include findings carrying `false_positive_reason` (§10.3 managed_skip). Defaults to false; default behavior excludes them from operator dashboards. When true, responses may contain `status=managed_skip`. schema: type: boolean default: false responses: '200': description: Shadow finding page content: application/json: schema: $ref: '#/components/schemas/ShadowAgentFindingPage' '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/shadow-agents/{finding_id}: get: operationId: getShadowAgentFinding summary: Get a shadow-agent finding by id description: Returns one ShadowAgentFinding scoped to the caller's tenant. Cross-tenant lookups return 404 (not 403) to prevent tuple-existence probing. tags: - Edge parameters: - name: finding_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' responses: '200': description: Shadow finding content: application/json: schema: $ref: '#/components/schemas/ShadowAgentFinding' '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/shadow-agents/{finding_id}/resolve: post: operationId: resolveShadowAgentFinding summary: Resolve a shadow-agent finding description: Flips a detected finding to resolved. Idempotent on a resolved finding; returns 409 when called on a suppressed terminal record. Emits `shadow_agent.resolved` audit event on success. tags: - Edge parameters: - name: finding_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/ResolveShadowAgentFindingRequest' responses: '200': description: Updated shadow finding content: application/json: schema: $ref: '#/components/schemas/ShadowAgentFinding' '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' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/shadow-agents/{finding_id}/suppress: post: operationId: suppressShadowAgentFinding summary: Suppress a shadow-agent finding description: Flips a detected finding to suppressed. Idempotent on a suppressed finding; returns 409 when called on a resolved terminal record. Optional `suppressed_until` records a time-bound exception for downstream re-evaluation. Emits `shadow_agent.suppressed` audit event on success. tags: - Edge parameters: - name: finding_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/SuppressShadowAgentFindingRequest' responses: '200': description: Updated shadow finding content: application/json: schema: $ref: '#/components/schemas/ShadowAgentFinding' '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' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/shadow-agents/{finding_id}/ignore: post: operationId: ignoreShadowAgentFinding summary: Suppress a shadow-agent finding (compat alias for /suppress) description: Compatibility alias for POST /api/v1/edge/shadow-agents/{finding_id}/suppress. Shares the same handler and emits the same audit event; new clients should use /suppress. tags: - Edge parameters: - name: finding_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/SuppressShadowAgentFindingRequest' responses: '200': description: Updated shadow finding content: application/json: schema: $ref: '#/components/schemas/ShadowAgentFinding' '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' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/shadow-agents/{finding_id}/remediation: post: operationId: generateShadowAgentRemediation summary: Generate an advisory remediation plan for a shadow-agent finding (EDGE-142) description: | Returns a deterministic, redacted RemediationPlan for the referenced finding. Read-only: the handler does not mutate finding state, does not enqueue Cordum Jobs, does not emit audit events, and does not call the Safety Kernel. All commands inside the plan use literal placeholders (``, ``, etc.) and never carry live secrets or developer paths. tags: - Edge parameters: - name: finding_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/ShadowAgentRemediationRequest' responses: '200': description: Advisory remediation plan for the referenced finding. content: application/json: schema: $ref: '#/components/schemas/ShadowAgentRemediationResponse' '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/shadow/exception: post: operationId: createShadowException summary: Create an operator-defined shadow exception (EDGE-143.6) description: | Persists an operator-signed exception declaration that suppresses future ShadowAgent findings matching its scope predicate (source_type + source_id + risk_level + signal_set). Matching is applied at finding emit time: matching findings are stamped with exception_id, false_positive_reason="operator_exception", and status=managed_skip; they are excluded from default-filter list queries. Q8 step-up auth: when scope_risk_level is "high", the caller MUST hold the `admin` legacy role OR the `shadow.exception.high_risk` permission. Failure returns 403 with code `step_up_required` and details.required = "mfa_recent|signed_admin_token". The persisted Exception records which factor satisfied the gate so SIEM rules can pivot on the auth tier at the time of action. expires_at MUST be in the future and within 90 days (§10.3 "longer requires re-affirmation"). tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateShadowExceptionRequest' responses: '201': description: Exception accepted and persisted. content: application/json: schema: $ref: '#/components/schemas/ShadowException' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '429': description: Per-tenant exception cap reached (code=limit_exceeded). content: application/json: schema: $ref: '#/components/schemas/EdgeError' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/shadow/exception/{exception_id}: get: operationId: getShadowException summary: Read a single shadow exception (EDGE-143.6) description: | Returns the exception record scoped to the caller's tenant. Cross-tenant probes return 404 (never 403) to avoid leaking tuple existence. tags: - Edge parameters: - name: exception_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' responses: '200': description: Exception record. content: application/json: schema: $ref: '#/components/schemas/ShadowException' '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' delete: operationId: revokeShadowException summary: Revoke an active shadow exception (EDGE-143.6) description: | Transitions an active exception to revoked. Revoke uses the SAME auth tier as the original create: if the exception's scope_risk_level is "high", the caller MUST satisfy the step-up gate. Failure returns 403 with code `step_up_required`. Idempotent when the exception is already revoked AND the caller principal matches the original revoker; conflicting double-revoke returns 409. tags: - Edge parameters: - name: exception_id in: path required: true schema: type: string - $ref: '#/components/parameters/TenantID' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/RevokeShadowExceptionRequest' responses: '204': description: Exception revoked. '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' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' /api/v1/edge/shadow/exceptions: get: operationId: listShadowExceptions summary: List shadow exceptions for the caller's tenant (EDGE-143.6) description: | Returns a bounded, cursor-paginated page of exceptions for the requested tenant. Optional filters: status (active|revoked| expired), source_type (local|kubernetes|ci|network), risk (low|medium|high|critical). tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: status in: query required: false schema: type: string enum: [active, revoked, expired] - name: source_type in: query required: false schema: type: string enum: [local, kubernetes, ci, network] - name: risk in: query required: false schema: type: string enum: [low, medium, high, critical] - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 1000 - name: cursor in: query required: false schema: type: string responses: '200': description: Page of exceptions. content: application/json: schema: $ref: '#/components/schemas/ListShadowExceptionsResponse' '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/binary-integrity/events: post: operationId: ingestBinaryVerify summary: Ingest install-time binary-verify outcomes description: | Persists structured binary-verify outcomes emitted by `tools/scripts/install.{sh,ps1}` (the pre-activation integrity gate documented in `docs/security/binary-signing.md` §8). Operators capture install-script stderr (one JSON line per binary verified), batch it into the `events` array, and POST here. Each event is validated against `model.BinaryVerifyEvent` and persisted through the existing audit chain as a SIEMEvent with `EventType = binary-verify-ok | binary-verify-fail`. Partial-success semantics: per-event validation failures are reported in the response `errors[]` and counted in `rejected`; accepted events are persisted. A request with zero accepted events returns 400. Auth: requires `audit.export` permission or `admin` role, plus tenant access enforced via `X-Tenant-ID`. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/IngestBinaryVerifyRequest' responses: '202': description: At least one event was accepted and persisted content: application/json: schema: $ref: '#/components/schemas/IngestBinaryVerifyResponse' '400': $ref: '#/components/responses/EdgeBadRequest' '401': $ref: '#/components/responses/EdgeUnauthorized' '403': $ref: '#/components/responses/EdgeForbidden' '413': $ref: '#/components/responses/EdgePayloadTooLarge' '503': $ref: '#/components/responses/EdgeServiceUnavailable' '500': $ref: '#/components/responses/EdgeInternalServerError' get: operationId: listBinaryVerify summary: List binary-verify outcomes for a tenant description: | Returns a tenant-scoped reverse-chronological page of binary-verify outcomes recovered from the tenant audit stream. Filters narrow the page; values that fall outside the accepted enums return 400. The response envelope mirrors `/api/v1/audit/events` (items / next_cursor / returned) but each item carries the original BinaryVerifyEvent shape plus a server-side timestamp and endpoint label. Auth: requires `audit.read` permission or `admin` role, plus tenant access enforced via `X-Tenant-ID`. tags: - Edge parameters: - $ref: '#/components/parameters/TenantID' - name: tenant in: query required: false description: | Tenant id whose audit stream should be read. Must match caller scope; defaults to the authenticated/header tenant. schema: type: string - name: limit in: query required: false description: | Maximum number of events in the response page. Default 100; values above 200 are silently capped to 200 to bound the Redis fetch cost. schema: type: integer default: 100 minimum: 1 maximum: 200 - name: cursor in: query required: false description: | Opaque stream-id cursor returned as `next_cursor` from the previous page. Format `-`; empty `next_cursor` means end-of-stream. schema: type: string - name: event in: query required: false description: | Filter by outcome. Short forms `ok` / `fail` are accepted and normalized to the canonical `binary-verify-ok` / `binary-verify-fail` event types. schema: type: string enum: [ok, fail, binary-verify-ok, binary-verify-fail] - name: sig_scheme in: query required: false description: Filter by signing scheme. schema: type: string enum: [gpg, codesign, authenticode, dev] - name: endpoint in: query required: false description: | Filter by the endpoint label captured at ingest time (operator-supplied; max 256 chars). schema: type: string maxLength: 256 responses: '200': description: Binary-verify events page content: application/json: schema: $ref: '#/components/schemas/BinaryVerifyEventsEnvelope' '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/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' '403': description: > Job submit was rejected by a semantic work-orchestration policy. Stable error `code`: `MEMORY_POLICY_VIOLATION`. content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: > Job submit idempotency key is already reserved without a replayable job mapping. Stable error `code`: `IDEMPOTENCY_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '413': $ref: '#/components/responses/PayloadTooLarge' '429': description: > Job submit backpressure or generic rate limit. Backpressure returns stable error `code`: `BACKPRESSURE`. headers: Retry-After: description: Seconds until retry is permitted when present. schema: type: integer content: application/json: schema: $ref: '#/components/schemas/Error' '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' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalServerError' '503': $ref: '#/components/responses/ServiceUnavailable' /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' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' '503': $ref: '#/components/responses/ServiceUnavailable' /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' '403': description: > Workflow run start was rejected by a semantic work-orchestration policy. Stable error `code`: `MEMORY_POLICY_VIOLATION`. content: application/json: schema: $ref: '#/components/schemas/Error' '404': $ref: '#/components/responses/NotFound' '409': description: > Workflow run start idempotency key is already reserved without a replayable run mapping. Stable error `code`: `RUN_IDEMPOTENCY_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '429': description: > Workflow run cannot be started because the active-run admission limit is full. Stable error `code`: `RUN_NOT_RUNNABLE`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Workflow rerun request is not runnable, for example an invalid resume step. Stable error `code`: `RUN_NOT_RUNNABLE`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': description: > Workflow rerun cannot be admitted because the active-run limit is full. Stable error `code`: `RUN_NOT_RUNNABLE`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Workflow run is busy and cannot be cancelled right now. Stable error `code`: `RUN_NOT_CANCELLABLE`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Approval cannot be performed. Self-approval returns existing stable `code`: `self_approval_denied`; other 4xx string-result failures return `RESULT_INVALID_STATUS`. content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: > Approval target job was not found. Stable error `code`: `RESULT_INVALID_STATUS`. content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: > Approval is not currently actionable or changed concurrently. Stable conflict `code` values include `approval_already_resolved`, `approval_retryable_lock`, `approval_terminal_run`, `approval_stale_snapshot`, `approval_stale_request`, and `approval_not_actionable`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Rejection cannot be performed. Self-rejection returns existing stable `code`: `self_approval_denied`; other 4xx string-result failures return `RESULT_INVALID_STATUS`. content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: > Approval target job was not found. Stable error `code`: `RESULT_INVALID_STATUS`. content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: > Rejection is not currently actionable or changed concurrently. Stable conflict `code` values include `approval_already_resolved`, `approval_retryable_lock`, `approval_terminal_run`, and `approval_not_actionable`. content: application/json: schema: $ref: '#/components/schemas/Error' '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' '409': $ref: '#/components/responses/EdgeConflict' '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' '409': $ref: '#/components/responses/EdgeConflict' '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': description: > Output rule validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Global policy section or content validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': description: > snapshot_version mismatch (optimistic concurrency). Stable error `code`: `POLICY_VERSION_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: > Strict policy signing is required but signer output is unavailable. Stable error `code`: `POLICY_SIGNING_REQUIRED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy bundle validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '503': description: > Strict policy signing is required but signer output is unavailable. Stable error `code`: `POLICY_SIGNING_REQUIRED`. content: application/json: schema: $ref: '#/components/schemas/Error' '500': $ref: '#/components/responses/InternalServerError' delete: operationId: deletePolicyBundle summary: Delete a policy bundle tags: - Policy responses: '204': description: Policy bundle deleted '400': description: > Policy bundle id validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy simulation request or candidate content validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy shadow bundle/content validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': description: > Policy shadow write conflict. Stable error `code`: `POLICY_SHADOW_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy shadow bundle id validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy shadow bundle id validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: > Policy shadow delete conflict. Stable error `code`: `POLICY_SHADOW_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy shadow summary query validation failed. Stable error `code`: `POLICY_SHADOW_QUERY_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy shadow comparison query validation failed. Stable error `code`: `POLICY_SHADOW_QUERY_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy shadow timeseries query validation failed. Stable error `code`: `POLICY_SHADOW_QUERY_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy publish validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy rollback validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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 x-cordum-error-codes: - CONFIG_REQUEST_INVALID - CONFIG_KEY_FORBIDDEN - CONFIG_NOT_FOUND 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 x-cordum-error-codes: - CONFIG_SCHEMA_VIOLATION - CONFIG_KEY_FORBIDDEN - CONFIG_VERSION_CONFLICT 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 x-cordum-error-codes: - CONFIG_SCHEMA_VIOLATION - CONFIG_KEY_FORBIDDEN - CONFIG_VERSION_CONFLICT 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 x-cordum-error-codes: - CONFIG_SCHEMA_VIOLATION 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 x-cordum-error-codes: - CONFIG_SCHEMA_VIOLATION - CONFIG_SCHEMA_NOT_FOUND 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 x-cordum-error-codes: - CONFIG_SCHEMA_VIOLATION - CONFIG_SCHEMA_NOT_FOUND 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': description: > Pack install validation failed. Stable error `code` values include `PACK_INSTALL_INVALID`, `PACK_DEPENDENCY_MISSING`, and `PACK_INVALID_SIGNATURE`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': description: > Pack is already installed or locked. Stable error `code`: `PACK_ALREADY_INSTALLED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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' '409': description: > Pack uninstall lock conflict. Stable error `code`: `PACK_ALREADY_INSTALLED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Marketplace pack install validation failed. Stable error `code` values include `PACK_MARKETPLACE_INVALID`, `PACK_INSTALL_INVALID`, and `PACK_INVALID_SIGNATURE`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: > Marketplace pack was not found. Stable error `code`: `PACK_DEPENDENCY_MISSING`. content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: > Marketplace pack is already installed or locked. Stable error `code`: `PACK_ALREADY_INSTALLED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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/memory/resolve: post: operationId: resolveMemoryResource summary: Resolve a structured resource in an authenticated job scope tags: - Memory requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - job_id - reference properties: job_id: type: string minLength: 1 maxLength: 256 reference: type: object additionalProperties: false required: - resolverId - uri - sha256 - mediaType - sizeBytes - expiresAt - purpose properties: resolverId: type: string uri: type: string maxLength: 2048 sha256: type: string format: byte mediaType: type: string sizeBytes: type: integer format: uint64 minimum: 1 expiresAt: type: string format: date-time purpose: type: string responses: '200': description: Integrity-verified resource content and safe metadata content: application/json: schema: type: object additionalProperties: false required: - media_type - size_bytes - base64 properties: media_type: type: string size_bytes: type: integer base64: type: string format: byte '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '413': $ref: '#/components/responses/PayloadTooLarge' '503': $ref: '#/components/responses/ServiceUnavailable' /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': description: > MCP HTTP transport is unavailable. Stable error `code`: `MCP_HTTP_TRANSPORT_UNAVAILABLE`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > MCP HTTP transport is unavailable. Stable error `code`: `MCP_HTTP_TRANSPORT_UNAVAILABLE`. content: application/json: schema: $ref: '#/components/schemas/Error' '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 x-cordum-error-codes: - RBAC_REQUEST_INVALID - RBAC_ROLE_NOT_FOUND 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 x-cordum-error-codes: - RBAC_REQUEST_INVALID - RBAC_PERMISSION_INVALID - RBAC_ROLE_IN_USE 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 x-cordum-error-codes: - RBAC_REQUEST_INVALID - RBAC_ROLE_NOT_FOUND - RBAC_ROLE_IN_USE 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 x-cordum-error-codes: - WORKER_SESSION_INVALID - WORKER_NOT_FOUND 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 x-cordum-error-codes: - WORKER_SESSION_INVALID - WORKER_NOT_FOUND 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/{id}/revoke-session: parameters: - $ref: '#/components/parameters/WorkerID' - $ref: '#/components/parameters/TenantID' post: operationId: revokeWorkerSession x-cordum-error-codes: - WORKER_SESSION_INVALID summary: Revoke an active worker session tags: - Workers responses: '200': description: Worker session revoked content: application/json: schema: type: object properties: worker_id: type: string tenant: type: string revoked: type: boolean required: - worker_id - tenant - revoked '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/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 x-cordum-error-codes: - WORKER_CRED_BINDING_INVALID - POOL_NOT_FOUND - TOPIC_NOT_FOUND summary: Create or rotate a worker credential tags: - WorkerCredentials parameters: - $ref: '#/components/parameters/TenantID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateWorkerCredentialRequest' 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 x-cordum-error-codes: - WORKER_CRED_BINDING_INVALID - WORKER_CRED_NOT_FOUND 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 x-cordum-error-codes: - POOL_INVALID_CONFIG - POOL_NAME_CONFLICT - POOL_VERSION_CONFLICT 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 x-cordum-error-codes: - POOL_INVALID_CONFIG - POOL_NOT_FOUND - POOL_VERSION_CONFLICT 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 x-cordum-error-codes: - POOL_INVALID_CONFIG - POOL_NOT_FOUND - POOL_VERSION_CONFLICT 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 x-cordum-error-codes: - POOL_INVALID_CONFIG - POOL_NOT_FOUND - POOL_VERSION_CONFLICT 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 x-cordum-error-codes: - POOL_INVALID_CONFIG - POOL_NOT_FOUND - POOL_VERSION_CONFLICT 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 x-cordum-error-codes: - POOL_INVALID_CONFIG - TOPIC_NOT_FOUND - POOL_VERSION_CONFLICT 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 x-cordum-error-codes: - TOPIC_SCHEMA_VIOLATION - POOL_NOT_FOUND 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 x-cordum-error-codes: - TOPIC_SCHEMA_VIOLATION - TOPIC_NOT_FOUND 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/events: get: operationId: getAuditEvents summary: List audit events from the SIEM feed description: | Returns a paginated list of SIEM audit events for a tenant — the full chained feed (MCP, edge, worker, output policy, delegation, license, ...) sourced from the per-tenant Redis Stream populated by the audit chainer. Distinct from `/api/v1/policy/audit`, which only surfaces the policy-bundle audit subset. Reverse-chronological by chain sequence. Filters (event_type / severity / from / to / search) apply in-process after the stream read. Defense-in-depth redaction strips Extra-map keys matching the secret-key pattern (token / password / api_key / private_key / secret) before serialization. Every successful read appends an `audit.read.events` event to the same chain so the read surface is itself auditable. Permission: `audit.read` (admin/operator/viewer with RBAC; admin via legacy-role fallback). 503 when the audit chainer is not configured. tags: - AuditExport parameters: - $ref: '#/components/parameters/TenantID' - name: tenant in: query required: false description: | Tenant id whose audit stream should be read. Must match caller scope; defaults to the authenticated/header tenant. schema: type: string - name: limit in: query required: false description: | Maximum number of events in the response page. Default 100; values above 200 are clamped to 200 silently to bound Redis fetch cost. schema: type: integer default: 100 minimum: 1 maximum: 200 example: 50 - name: cursor in: query required: false description: | Opaque cursor returned as `next_cursor` by the previous page. Pass back verbatim to fetch the next page. Empty `next_cursor` means end-of-stream. schema: type: string - name: event_type in: query required: false description: Case-insensitive exact match against the event type (e.g. `mcp.tool_invocation`). schema: type: string example: mcp.tool_invocation - name: severity in: query required: false description: Case-insensitive exact match against severity (CRITICAL / HIGH / MEDIUM / LOW / INFO). schema: type: string example: HIGH - name: from in: query required: false description: Inclusive lower-bound on event timestamp (RFC3339). schema: type: string format: date-time example: '2026-05-15T00:00:00Z' - name: to in: query required: false description: Inclusive upper-bound on event timestamp (RFC3339). schema: type: string format: date-time example: '2026-05-15T23:59:59Z' - name: search in: query required: false description: | Case-insensitive substring search over the lowercased concatenation of `action + event_type + agent_id + job_id + identity + reason`. schema: type: string responses: '200': description: Audit events envelope content: application/json: schema: $ref: '#/components/schemas/AuditEventsEnvelope' '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': description: > Approval repair cannot be performed for this caller or tenant. Stable 4xx string-result error `code`: `RESULT_INVALID_STATUS`. content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: > Approval repair target job was not found. Stable error `code`: `RESULT_INVALID_STATUS`. content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: > Approval repair conflict. Stable conflict `code` values include `approval_retryable_lock`. 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': description: > Velocity rule validation failed. Stable error `code`: `VELOCITY_RULE_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/TierLimit' '409': description: > Velocity rule already exists. Stable error `code`: `VELOCITY_RULE_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Velocity rule validation failed. Stable error `code`: `VELOCITY_RULE_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/TierLimit' '404': description: > Velocity rule was not found. Stable error `code`: `VELOCITY_RULE_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '500': $ref: '#/components/responses/InternalServerError' delete: operationId: deleteVelocityRule summary: Delete a velocity rule tags: - Policy responses: '204': description: Velocity rule deleted '400': description: > Velocity rule validation failed. Stable error `code`: `VELOCITY_RULE_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/TierLimit' '404': description: > Velocity rule was not found. Stable error `code`: `VELOCITY_RULE_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Policy replay request or candidate content validation failed. Stable error `code`: `POLICY_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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_servers: type: array items: {type: string} description: MCP server-name glob allowlist. Empty or omitted fail-closes server-guarded MCP actions. allowed_tools: type: array items: {type: string} allowed_resources: type: array items: {type: string} description: cordum:// resource URI glob allowlist. Empty or omitted fail-closes resource-guarded MCP actions. entitlements: type: array items: {type: string} description: Capability tokens the identity holds for MCP required-entitlement checks. preapproved_mutating_tools: type: array items: {type: string} description: Mutating MCP tool names/globs this identity may call without human approval. Empty or omitted requires approval for every mutating tool. 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 x-cordum-error-codes: - AGENT_REQUEST_INVALID 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_servers: type: array items: {type: string} description: MCP server-name glob allowlist. Empty or omitted fail-closes server-guarded MCP actions. allowed_tools: type: array items: {type: string} allowed_resources: type: array items: {type: string} description: cordum:// resource URI glob allowlist. Empty or omitted fail-closes resource-guarded MCP actions. entitlements: type: array items: {type: string} description: Capability tokens the identity holds for MCP required-entitlement checks. preapproved_mutating_tools: type: array items: {type: string} description: Mutating MCP tool names/globs this identity may call without human approval. Empty or omitted requires approval for every mutating tool. 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 x-cordum-error-codes: - AGENT_REQUEST_INVALID - AGENT_NOT_FOUND 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 x-cordum-error-codes: - AGENT_REQUEST_INVALID - AGENT_NOT_FOUND 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_servers: type: array items: {type: string} description: Full replacement MCP server-name glob allowlist. Send [] to clear. allowed_tools: type: array items: {type: string} allowed_resources: type: array items: {type: string} description: Full replacement cordum:// resource URI glob allowlist. Send [] to clear. entitlements: type: array items: {type: string} description: Full replacement capability-token list. Send [] to clear. preapproved_mutating_tools: type: array items: {type: string} description: Full replacement list of mutating MCP tool names/globs allowed without human approval. Send [] to clear. 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 x-cordum-error-codes: - AGENT_REQUEST_INVALID - AGENT_NOT_FOUND 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 x-cordum-error-codes: - AGENT_REQUEST_INVALID - AGENT_NOT_FOUND 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': description: > Invalid delegation issue request. Stable error `code` values: `DELEGATION_REQUEST_INVALID`, `DELEGATION_SCOPE_EXCEEDED`, `DELEGATION_CHAIN_TOO_DEEP`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: > Delegating or target agent identity was not found. Stable error `code`: `DELEGATION_AGENT_NOT_FOUND`. content: application/json: schema: $ref: '#/components/schemas/Error' '409': $ref: '#/components/responses/Conflict' '429': description: > Delegation issue quota exceeded. Stable error `code`: `DELEGATION_RATE_LIMITED`. headers: Retry-After: schema: type: integer description: Seconds until the rate limit resets content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Invalid delegation verify request. Stable error `code`: `DELEGATION_REQUEST_INVALID`. Token verification denials continue to return HTTP 200 with response `error_code`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Invalid delegation revoke request. Stable error `code`: `DELEGATION_REQUEST_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: > Delegation token was not found. Stable error `code`: `DELEGATION_TOKEN_NOT_FOUND`. content: application/json: schema: $ref: '#/components/schemas/Error' '422': description: > Delegation revoke cascade exceeded the safe traversal depth. Stable error `code`: `DELEGATION_CASCADE_TOO_DEEP`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Invalid delegation list filter or pagination parameter. Stable error `code`: `DELEGATION_REQUEST_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Invalid delegation list filter or pagination parameter. Stable error `code`: `DELEGATION_REQUEST_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '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/gateway/health: get: operationId: getMcpGatewayHealth summary: EDGE-100 MCP Gateway skeleton — health probe tags: - MCP parameters: - $ref: '#/components/parameters/TenantID' responses: '200': description: Gateway component reachable; body includes per-tenant gateway_enabled flag. content: application/json: schema: type: object additionalProperties: true properties: status: type: string gateway_enabled: type: boolean component: type: string '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/mcp/gateway/config: get: operationId: getMcpGatewayConfig summary: EDGE-100 MCP Gateway skeleton — effective per-tenant config (redacted) tags: - MCP parameters: - $ref: '#/components/parameters/TenantID' responses: '200': description: Effective gateway config; never echoes secret-shaped fields. content: application/json: schema: type: object additionalProperties: true properties: gateway_enabled: type: boolean upstream_count: type: integer upstream_forwarding: type: string '401': $ref: '#/components/responses/Unauthorized' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/mcp/gateway/upstream: # `x-subroute-dispatch` tells openapi-audit about the internal prefix # catch-all `/api/v1/mcp/gateway/upstream/*` (Go ServeMux pattern with # trailing slash) without forcing the spec to model a path that # spectral's no-path-trailing-slash rule would reject. The canonical # POST op below documents the public surface; the Go gateway dispatches # any subpath under it to the same skeleton handler. x-subroute-dispatch: true post: operationId: postMcpGatewayUpstream summary: EDGE-100 MCP Gateway skeleton — upstream forwarding (disabled by default) description: | Catch-all for `/api/v1/mcp/gateway/upstream/*`. Always returns 503 in the P1 skeleton: `gateway_disabled` when MCPPolicy.GatewayEnabled is false (default), `no_upstream_configured` when true but the EDGE-101 upstream registry is empty. tags: - MCP parameters: - $ref: '#/components/parameters/TenantID' responses: '401': $ref: '#/components/responses/Unauthorized' '503': $ref: '#/components/responses/ServiceUnavailable' /api/v1/mcp/gateway/clients/connect: post: operationId: postMcpGatewayClientsConnect summary: EDGE-100 MCP Gateway skeleton — register an MCP client connect description: | Creates an EdgeSession + AgentExecution attributed to the resolved tenant + authenticated principal (never to request-body claims) and emits an `mcp.server.connected` event on success or `mcp.server.failed` on the failure path. tags: - MCP parameters: - $ref: '#/components/parameters/TenantID' responses: '200': description: Session + execution created; body returns their IDs. content: application/json: schema: type: object properties: session_id: type: string execution_id: type: string tenant_id: type: string '401': $ref: '#/components/responses/Unauthorized' '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': description: > Invalid MCP verify-signature request body. Stable error `code`: `MCP_VERIFY_REQUEST_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': description: > MCP verifier trust store is not configured. Existing stable error `code`: `MCP_VERIFIER_UNAVAILABLE`. content: application/json: schema: $ref: '#/components/schemas/Error' /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 '400': description: > Invalid outbound MCP query parameter. Stable error `code` values: `MCP_RANGE_INVALID`, `MCP_SIGNATURE_STATUS_INVALID`, `MCP_LIMIT_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '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 '400': description: > Invalid MCP usage time range. Stable error `code`: `MCP_RANGE_INVALID`. content: application/json: schema: $ref: '#/components/schemas/Error' '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' '404': description: > Agent identity referenced by `agent_id` was not found. Stable error `code`: `MCP_AGENT_IDENTITY_NOT_FOUND`. content: application/json: schema: $ref: '#/components/schemas/Error' /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 '400': description: > Missing path agent id. Stable error `code`: `MCP_AGENT_ID_REQUIRED`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: > Agent identity was not found. Stable error `code`: `MCP_AGENT_IDENTITY_NOT_FOUND`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Eval extraction request validation failed. Stable error `code`: `EVAL_EXTRACTION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: > No matching incidents were found. Stable error `code`: `EVAL_EXTRACTION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: > Eval dataset version already exists. Stable error `code`: `EVAL_DATASET_VERSION_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Eval dataset validation failed. Stable error `code`: `EVAL_DATASET_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': description: > Eval dataset version already exists. Stable error `code`: `EVAL_DATASET_VERSION_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '413': description: > Eval dataset payload exceeded route limits. Stable error `code`: `EVAL_DATASET_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Eval dataset successor validation failed. Stable error `code`: `EVAL_DATASET_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: > Eval dataset successor version already exists. Stable error `code`: `EVAL_DATASET_VERSION_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '413': description: > Eval dataset successor payload exceeded route limits. Stable error `code`: `EVAL_DATASET_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Eval dataset delete validation failed. Stable error `code`: `EVAL_DATASET_VALIDATION_FAILED`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Eval run request is not runnable. Stable error `code`: `EVAL_RUN_NOT_RUNNABLE`. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: > Eval dataset run is already in progress. Stable error `code`: `EVAL_RUN_CONFLICT`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Eval run list query validation failed. Stable error `code`: `EVAL_RUN_NOT_RUNNABLE`. content: application/json: schema: $ref: '#/components/schemas/Error' '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': description: > Eval run delete validation failed. Stable error `code`: `EVAL_RUN_NOT_RUNNABLE`. content: application/json: schema: $ref: '#/components/schemas/Error' '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: MCPUpstreamServer: type: object required: [name, transport, tenant_id, risk, enabled] properties: name: type: string pattern: '^[A-Za-z0-9.-]+$' transport: type: string enum: [stdio, http, sse] endpoint: type: string format: uri description: HTTPS endpoint for http/sse transports; unsafe local endpoints are rejected in strict policy modes. command: type: array description: Shell-free argv vector for stdio transport. Shell metacharacters are rejected. items: type: string tenant_id: type: string description: Tenant scope for the upstream; `*` is reserved for system-wide entries. auth_secret_ref: type: string pattern: '^secret://' description: Reference to auth material. Raw secrets are rejected and never resolved in registry responses. labels: type: object additionalProperties: type: string risk: type: string enum: [low, medium, high, critical] enabled: type: boolean resolved_ips: type: array description: | Registration-time DNS resolution of the upstream hostname. Server-managed; clients MUST NOT submit values here on write. Use-time callers (`RevalidateMCPUpstreamAtUse`) re-resolve the hostname and refuse the dial if any current IP falls into the SSRF deny-set (loopback, RFC1918, link-local incl. 169.254.169.254, IPv6 ULA, multicast, unspecified). Omitted for `stdio` transports and for hostnames that fail to resolve at registration. items: type: string created_at: type: string format: date-time updated_at: type: string format: date-time MCPUpstreamServerWriteRequest: type: object description: Write payload for an upstream MCP entry. `tenant_id` may be omitted and is resolved from X-Tenant-ID; `created_at` and `updated_at` are server-managed. properties: name: type: string pattern: '^[A-Za-z0-9.-]+$' transport: type: string enum: [stdio, http, sse] endpoint: type: string format: uri command: type: array items: type: string tenant_id: type: string auth_secret_ref: type: string pattern: '^secret://' labels: type: object additionalProperties: type: string risk: type: string enum: [low, medium, high, critical] enabled: type: boolean MCPUpstreamListResponse: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/MCPUpstreamServer' MCPUpstreamValidationResponse: type: object required: [valid] properties: valid: type: boolean reason: type: string description: Redacted validation failure reason, never a raw secret. 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 EdgeRuntimeIngestSource: type: object required: [source_id] description: Authenticated trusted sidecar identity that produced the batch. properties: source_id: type: string minLength: 1 maxLength: 256 description: Stable identifier for the runtime collector instance; must match the authenticated collector principal. EdgeRuntimeProcessSummary: type: object description: Bounded redacted summary of a process exec event. Raw argv / env / cmdline are forbidden. properties: executable_basename: type: string maxLength: 256 executable_sha256: type: string maxLength: 80 argument_count: type: integer minimum: 0 EdgeRuntimeFileSummary: type: object properties: operation: type: string enum: [read, write] path_redacted: type: string maxLength: 256 EdgeRuntimeNetworkSummary: type: object properties: host_redacted: type: string maxLength: 256 ip_prefix: type: string maxLength: 64 port: type: integer minimum: 0 maximum: 65535 protocol: type: string enum: [tcp, udp] EdgeRuntimeDNSSummary: type: object properties: qname_redacted: type: string maxLength: 256 qtype: type: string maxLength: 16 EdgeRuntimeEventEnvelope: type: object required: [tenant_id, session_id, execution_id, source_event_id, observed_at, kind] description: One bounded, redacted runtime telemetry record. Forbidden top-level keys (argv, args, cmdline, command_line, env, environment, file_content, file_contents, packet, payload, body, request_body, response_body, headers, header, cookie, cookies, secret, secrets, token, tokens, password, passwords, api_key, apikey, private_key, dns_response, response) are rejected at the strict-schema decode boundary. additionalProperties: false properties: tenant_id: type: string session_id: type: string execution_id: type: string source_event_id: type: string minLength: 1 observed_at: type: string format: date-time kind: type: string enum: - runtime.process.exec - runtime.file.read - runtime.file.write - runtime.network.connect - runtime.dns.query outcome_status: type: string enum: ["", ok, failed, degraded] process: $ref: '#/components/schemas/EdgeRuntimeProcessSummary' file: $ref: '#/components/schemas/EdgeRuntimeFileSummary' network: $ref: '#/components/schemas/EdgeRuntimeNetworkSummary' dns: $ref: '#/components/schemas/EdgeRuntimeDNSSummary' labels: type: object additionalProperties: type: string maxProperties: 16 artifact_ptrs: type: array maxItems: 32 items: $ref: '#/components/schemas/EdgeArtifactPointer' EdgeRuntimeIngestRequest: type: object required: [source, nonce, events] additionalProperties: false properties: source: $ref: '#/components/schemas/EdgeRuntimeIngestSource' nonce: type: string minLength: 16 maxLength: 64 pattern: '^[A-Za-z0-9-]{16,64}$' description: Required by default replay-protection nonce (UUIDv7 or random 128-bit value). Set `CORDUM_EDGE_RUNTIME_REPLAY_REQUIRED=false` only for transitional non-production clients that cannot yet send this field. batch_id: type: string maxLength: 256 description: Operator correlation identifier only; it is not used for replay protection. events: type: array minItems: 1 maxItems: 256 items: $ref: '#/components/schemas/EdgeRuntimeEventEnvelope' EdgeRuntimeIngestDropReport: type: object properties: source_event_id: type: string kind: type: string reason: type: string enum: [sampled_out] EdgeRuntimeIngestResponse: type: object required: [accepted_count, dropped_count] properties: accepted_count: type: integer minimum: 0 dropped_count: type: integer minimum: 0 dropped: type: array items: $ref: '#/components/schemas/EdgeRuntimeIngestDropReport' replayed: type: boolean default: false description: True when a duplicate nonce was suppressed and no events were appended. EdgeLLMIngestSource: type: object required: [source_id] additionalProperties: false properties: source_id: type: string minLength: 1 maxLength: 256 description: Stable identifier of the trusted LLM proxy; must match the authenticated proxy principal. EdgeLLMMessage: type: object required: [role, content] additionalProperties: false description: One role-tagged chat message; content is redacted before persistence. properties: role: type: string maxLength: 256 content: type: string maxLength: 1048576 description: Message text; bounded by the 1 MiB raw-envelope cap (MaxLLMRawEnvelopeBytes in core/edge/llmingest) and redacted by the gateway before persistence. EdgeLLMTokens: type: object additionalProperties: false description: Optional token accounting for usage/cost evidence. properties: input_tokens: type: integer minimum: 0 output_tokens: type: integer minimum: 0 total_tokens: type: integer minimum: 0 EdgeLLMEventEnvelope: type: object required: [tenant_id, session_id, execution_id, source_event_id, observed_at, kind] additionalProperties: false description: One intercepted LLM interaction. Smuggled keys (authorization, headers, cookies, api_key, provider keys) are rejected at the strict-schema decode boundary; content and messages are redacted by the gateway before persistence. properties: tenant_id: type: string session_id: type: string execution_id: type: string source_event_id: type: string minLength: 1 observed_at: type: string format: date-time kind: type: string enum: - llm.request.pre - llm.request.post - llm.stream.chunk - llm.cost.recorded description: | llm.stream.chunk carries one delta of a streamed response and is scanned in isolation — a secret split across a chunk boundary can evade per-chunk redaction. A chunk is redaction-complete (see EdgeLLMEventDecision.redaction_complete) ONLY when submitted with final=true and the full aggregated content/messages for the stream. See docs/edge/llm-proxy-governance.md "Streaming chunk redaction limits". outcome_status: type: string enum: ["", ok, failed, degraded] agent_product: type: string maxLength: 256 provider: type: string maxLength: 256 model: type: string maxLength: 256 direction: type: string enum: ["", prompt, response] content: type: string maxLength: 1048576 description: Prompt or completion text; bounded by the 1 MiB raw-envelope cap and redacted by the gateway before persistence. messages: type: array maxItems: 64 items: $ref: '#/components/schemas/EdgeLLMMessage' tokens: $ref: '#/components/schemas/EdgeLLMTokens' cost_usd: type: number minimum: 0 labels: type: object additionalProperties: type: string maxProperties: 16 artifact_ptrs: type: array maxItems: 32 items: $ref: '#/components/schemas/EdgeArtifactPointer' stream_id: type: string maxLength: 256 description: Groups the chunks of one streamed response. Only meaningful for kind=llm.stream.chunk; reserved as the key for a future server-side reassembly pass. sequence: type: integer minimum: 0 description: 0-based chunk position within stream_id. Only meaningful for kind=llm.stream.chunk. final: type: boolean description: Marks the last chunk of a stream. When true, content (or messages) MUST carry the full aggregated response text, not just the last delta — required for the mandatory redaction scan to be complete. A final=true chunk with no content or messages is rejected. EdgeLLMIngestRequest: type: object required: [source, events] additionalProperties: false properties: source: $ref: '#/components/schemas/EdgeLLMIngestSource' nonce: type: string minLength: 16 maxLength: 64 pattern: '^[A-Za-z0-9-]{16,64}$' description: Optional replay-protection nonce. When present it is deduplicated against a Redis replay window scoped to `(tenant, llm-proxy)`. Set `CORDUM_EDGE_LLM_REPLAY_REQUIRED=true` to mandate it. batch_id: type: string maxLength: 256 description: Operator correlation identifier only; not used for replay protection. events: type: array minItems: 1 maxItems: 64 items: $ref: '#/components/schemas/EdgeLLMEventEnvelope' EdgeLLMEventDecision: type: object required: [source_event_id, kind, decision, redacted, redaction_complete] additionalProperties: false description: Per-event advisory outcome. `decision=redact` means a secret was detected and the proxy should forward `redacted_content` (subject to `truncated`) instead of the original. This is NOT a policy allow/deny decision. properties: source_event_id: type: string kind: type: string decision: type: string enum: [record, redact] redacted: type: boolean truncated: type: boolean redacted_content: type: string redacted_messages: type: array description: Role-preserving redacted chat messages when the submitted event used `messages`. Absent for content-only (single-string) envelopes. items: $ref: '#/components/schemas/EdgeLLMMessage' findings: type: array items: type: string description: Detected secret finding TYPES (never values), e.g. aws_credential, bearer_token, private_key. redaction_complete: type: boolean description: | Whether this decision reflects a scan of the FULL turn content. Always true except for kind=llm.stream.chunk, where it is true ONLY when the chunk was submitted with final=true (which requires the full aggregated content). A non-final chunk is scanned in isolation and can miss a secret split across a chunk boundary — proxies MUST NOT treat redaction_complete=false as a governance verdict for forwarding purposes. EdgeLLMIngestResponse: type: object required: [accepted_count] properties: accepted_count: type: integer minimum: 0 decisions: type: array items: $ref: '#/components/schemas/EdgeLLMEventDecision' replayed: type: boolean default: false description: True when a duplicate nonce was suppressed and no events were appended. BinaryVerifyEvent: type: object description: | One structured outcome from the pre-activation integrity gate in `tools/scripts/install.{sh,ps1}`. Field names and order are frozen to match `install.sh` `emit_audit` (line 24-32) and `model.BinaryVerifyEvent` (`core/model/binary_verify.go`) — downstream SIEM mappings pin to this shape. properties: event: type: string enum: [binary-verify-ok, binary-verify-fail] description: Outcome kind. Maps to SIEMEvent.EventType. hash: type: string pattern: '^[a-f0-9]{64}$' description: SHA-256 of the verified binary, lowercase hex. path: type: string minLength: 1 maxLength: 256 description: | Manifest-relative basename of the verified binary. Absolute paths, drive-rooted (Windows) paths, and any segment containing `..` are rejected. sig_scheme: type: string enum: [gpg, codesign, authenticode, dev] description: Signing scheme that produced the verified signature. fingerprint: type: string description: | Empty when sig_scheme is codesign/authenticode/dev, or a 40-char uppercase-hex GPG key fingerprint when sig_scheme is gpg. When non-empty, must match `^[A-F0-9]{40}$`. reason: type: string maxLength: 512 description: | Human-readable explanation of the outcome. Empty on success; install-script controlled failure text on failure. Capped to 512 chars defensively (operator relays may splice in raw gpg stderr). exit_code: type: integer description: | Exit code of the verifier. MUST be 0 when event is `binary-verify-ok` and non-zero when event is `binary-verify-fail`. required: - event - hash - path - sig_scheme - fingerprint - reason - exit_code BinaryVerifyEventsEnvelope: type: object description: | Paginated envelope returned by `GET /api/v1/edge/binary-integrity/events`. `next_cursor` is opaque; pass it back as `?cursor=` on the next request. Empty `next_cursor` means end-of-stream — clients should stop polling. properties: items: type: array items: $ref: '#/components/schemas/BinaryVerifyListItem' next_cursor: type: string description: Opaque cursor for the next page; empty when no more events. returned: type: integer description: Number of items in this page (== `items.length`). required: - items - next_cursor - returned BinaryVerifyListItem: type: object description: | One row in the binary-verify list response. Carries the original `BinaryVerifyEvent` shape plus the server-side audit-row metadata captured at ingest time. allOf: - $ref: '#/components/schemas/BinaryVerifyEvent' - type: object properties: timestamp: type: string format: date-time description: Server-side ingest timestamp (UTC). tenant_id: type: string description: Tenant that owns the event. endpoint: type: string description: | Operator-supplied label captured at ingest time. Empty when the ingester did not provide one. required: - timestamp - tenant_id IngestBinaryVerifyError: type: object description: | One per-event rejection from a partial-success `ingestBinaryVerify` request. The accepted events have already been persisted. properties: index: type: integer description: | Zero-based index of the rejected event within the request `events` array. error: type: string description: | Human-readable validation reason, sourced from `model.BinaryVerifyEvent.Validate`. required: - index - error IngestBinaryVerifyRequest: type: object description: | Batch envelope for binary-verify outcomes. Up to 1000 events per request; the body byte size is capped at 2 MB. properties: endpoint: type: string maxLength: 256 description: | Optional operator-supplied label identifying the host that ran the install script. Captured into every persisted event. events: type: array minItems: 1 maxItems: 1000 items: $ref: '#/components/schemas/BinaryVerifyEvent' required: - events IngestBinaryVerifyResponse: type: object description: | Result of a binary-verify batch ingest. `accepted + rejected` may be less than the submitted batch only when a hard error prevented decoding; per-event validation failures are surfaced in `errors`. properties: accepted: type: integer description: Number of events persisted to the audit chain. rejected: type: integer description: Number of events rejected by per-event validation. errors: type: array items: $ref: '#/components/schemas/IngestBinaryVerifyError' description: Per-event rejections. Omitted when rejected is 0. required: - accepted - rejected ShadowAgentFinding: type: object description: Persisted lifecycle record for one detected shadow agent. Distinct from the scanner-only `Finding` shape; this is the operator-visible triage record consumed by /api/v1/edge/shadow-agents. required: [finding_id, tenant_id, owner_principal_id, principal_id, agent_product, risk, status, evidence_type, detected_at, created_at, updated_at] properties: finding_id: type: string description: Server-generated id prefixed with `edge_shadow_`. tenant_id: type: string owner_principal_id: type: string principal_id: type: string description: Detector identity (scanner principal); falls back to authenticated caller when omitted. agent_product: type: string agent_id: type: string hostname: type: string risk: type: string enum: [low, medium, high, critical] status: type: string enum: [detected, resolved, suppressed, managed_skip] evidence_type: type: string evidence_summary: type: string description: Bounded (≤2 KiB) redacted summary safe to persist and return. Secret-shaped values are stripped at ingest. evidence_artifact_ptr: $ref: '#/components/schemas/ShadowEvidencePointer' redacted_path: type: string description: Home-prefix-stripped path; never an absolute developer-machine path. detected_at: type: string format: date-time created_at: type: string format: date-time updated_at: type: string format: date-time resolved_at: type: string format: date-time nullable: true resolved_by: type: string resolution_reason: type: string description: Bounded (≤512 bytes) human reason supplied at resolve/suppress; secret markers stripped. suppressed_until: type: string format: date-time nullable: true description: Optional hint for a time-bound suppression. The store does NOT auto-revert when the timestamp lapses. metadata: type: object additionalProperties: type: string description: Small free-form map (≤16 entries, ≤64-byte keys, ≤256-byte values). source_type: type: string enum: [local, kubernetes, ci, network] description: EDGE-143.5 §10.1 — detector-family identifier. Defaults to `local` on read for legacy EDGE-141 records. source_id: type: string maxLength: 128 description: Detector instance identifier. cluster_id: type: string maxLength: 64 description: Operator-configured K8s cluster name; empty for non-K8s sources. namespace: type: string maxLength: 63 description: K8s namespace; empty for non-K8s sources. workload_kind: type: string maxLength: 32 description: K8s kind (Deployment, StatefulSet, DaemonSet, ...) or empty. workload_name: type: string maxLength: 253 pod_uid: type: string maxLength: 36 description: Optional pod UUID when the finding pins a specific pod. ci_provider: type: string enum: [github_actions, gitlab_ci, jenkins, buildkite, circleci, other] description: EDGE-143.5 §10.1 — CI provider; empty for non-CI sources. repo: type: string maxLength: 256 description: '`org/repo` for CI sources; composite-indexed with ci_provider.' ref: type: string maxLength: 256 workflow_id: type: string maxLength: 128 job_id: type: string maxLength: 128 run_id: type: string maxLength: 128 runner_id: type: string maxLength: 128 tenant_source: type: string maxLength: 64 description: Audit trail for tenant-attribution decisions (see §6.1 / §6.3). principal_source: type: string maxLength: 64 description: Audit trail for principal-attribution decisions (see §6.2 / §6.4). signal_set: type: array maxItems: 16 items: type: string pattern: '^[a-z0-9_]{1,32}$' description: Bounded enum-shape signal identifiers from §7.1 / §8.x. Supports any-of filtering via the `signal` query param. confidence: type: number format: float minimum: 0 maximum: 1 description: Detector self-rated confidence in [0, 1]. first_seen: type: string format: date-time nullable: true description: Distinct from `observed_at`; populated by detectors with longitudinal tracking. last_seen: type: string format: date-time nullable: true description: Updated on every re-observation. false_positive_reason: type: string maxLength: 64 description: Populated when `status` would be `managed_skip` per §10.3. exception_id: type: string maxLength: 64 description: Joins to operator-defined exception declarations (§10.3). retention_class: type: string enum: [shadow_short, shadow_default, shadow_long] description: EDGE-143.5 §10.5 — per-finding terminal-TTL class. Empty falls back to the store's `terminalRetention`. ShadowEvidencePointer: type: object description: Reference to redacted evidence stored outside the finding record. Distinct from `ArtifactPointer` because shadow findings have no session/execution context. required: [tenant_id, uri, sha256, retention_class, redaction_level, created_at] properties: tenant_id: type: string description: MUST match the parent finding's tenant_id; cross-tenant pointers are rejected. uri: type: string sha256: type: string size_bytes: type: integer format: int64 minimum: 0 retention_class: type: string enum: [short, standard, audit] redaction_level: type: string enum: [standard, strict] created_at: type: string format: date-time ShadowAgentFindingPage: type: object properties: findings: type: array items: $ref: '#/components/schemas/ShadowAgentFinding' next_cursor: type: string nullable: true CreateShadowAgentFindingRequest: type: object required: [owner_principal_id, agent_product, risk, evidence_type, detected_at] anyOf: - required: [evidence_summary] - required: [evidence_artifact_ptr] properties: finding_id: type: string description: Optional caller-supplied id. When omitted, server generates with `edge_shadow_` prefix. tenant_id: type: string description: Optional, must match X-Tenant-ID header when supplied. owner_principal_id: type: string principal_id: type: string description: Detector identity; falls back to authenticated caller. agent_product: type: string agent_id: type: string hostname: type: string risk: type: string enum: [low, medium, high, critical] evidence_type: type: string evidence_summary: type: string evidence_artifact_ptr: $ref: '#/components/schemas/ShadowEvidencePointer' redacted_path: type: string detected_at: type: string format: date-time metadata: type: object additionalProperties: type: string source_type: type: string enum: [local, kubernetes, ci, network] description: EDGE-143.5 §10.1; defaults to `local` when omitted. source_id: type: string maxLength: 128 cluster_id: type: string maxLength: 64 namespace: type: string maxLength: 63 workload_kind: type: string maxLength: 32 workload_name: type: string maxLength: 253 pod_uid: type: string maxLength: 36 ci_provider: type: string enum: [github_actions, gitlab_ci, jenkins, buildkite, circleci, other] repo: type: string maxLength: 256 ref: type: string maxLength: 256 workflow_id: type: string maxLength: 128 job_id: type: string maxLength: 128 run_id: type: string maxLength: 128 runner_id: type: string maxLength: 128 tenant_source: type: string maxLength: 64 principal_source: type: string maxLength: 64 signal_set: type: array maxItems: 16 items: type: string pattern: '^[a-z0-9_]{1,32}$' confidence: type: number format: float minimum: 0 maximum: 1 first_seen: type: string format: date-time last_seen: type: string format: date-time false_positive_reason: type: string maxLength: 64 exception_id: type: string maxLength: 64 retention_class: type: string enum: [shadow_short, shadow_default, shadow_long] ResolveShadowAgentFindingRequest: type: object properties: reason: type: string description: Bounded reason for resolution; ≤512 bytes, secret markers stripped. SuppressShadowAgentFindingRequest: type: object properties: reason: type: string description: Bounded reason for suppression; ≤512 bytes, secret markers stripped. suppressed_until: type: string format: date-time description: Optional time-bound hint; the store records but does not auto-revert. ShadowException: type: object required: [exception_id, tenant_id, created_by, created_at, expires_at, scope_source_type, scope_risk_level, status, step_up_factor] description: | Operator-defined exception declaration (EDGE-143.6 / §10.3). Suppresses future ShadowAgent findings matching its scope predicate. The step_up_factor records which auth tier satisfied the Q8 step-up gate at creation time so SIEM rules can pivot on authority-at-time-of-action. properties: exception_id: type: string description: Opaque id with `shadow_exc_` prefix. tenant_id: type: string created_by: type: string description: Authenticated principal at create time. Not trusted from the wire body. created_at: type: string format: date-time expires_at: type: string format: date-time description: Maximum 90 days from creation (longer requires re-affirmation per §10.3). reason: type: string description: Free-text operator rationale, up to 512 bytes. scope_source_type: type: string enum: [local, kubernetes, ci, network] scope_source_id: type: string description: Optional detector instance id; further narrows scope. scope_signal_set: type: array items: type: string description: At most 16 detector signal names; any-of overlap with a finding's signal_set satisfies the predicate. scope_risk_level: type: string enum: [low, medium, high, critical] status: type: string enum: [active, revoked, expired] step_up_factor: type: string enum: [mfa_recent, signed_admin_token, none] description: | Auth tier that satisfied the Q8 step-up gate at create time. "signed_admin_token" when the legacy admin role matched; "mfa_recent" when the explicit shadow.exception.high_risk permission matched; "none" when the gate was not required. revoked_by: type: string revoked_at: type: string format: date-time revocation_reason: type: string CreateShadowExceptionRequest: type: object required: [expires_at, scope_source_type, scope_risk_level] properties: expires_at: type: string format: date-time description: MUST be in the future and within 90 days of now. reason: type: string maxLength: 512 scope_source_type: type: string enum: [local, kubernetes, ci, network] scope_source_id: type: string maxLength: 128 scope_signal_set: type: array maxItems: 16 items: type: string pattern: '^[a-z0-9_]{1,32}$' scope_risk_level: type: string enum: [low, medium, high, critical] RevokeShadowExceptionRequest: type: object description: Optional body for revoking an exception. Empty body is allowed. properties: reason: type: string maxLength: 512 ListShadowExceptionsResponse: type: object required: [exceptions] properties: exceptions: type: array items: $ref: '#/components/schemas/ShadowException' next_cursor: type: string ShadowAgentRemediationRequest: type: object description: Optional body for generating a remediation plan. Both fields default to the generator's documented defaults when omitted. properties: audience: type: string enum: [dev, enterprise, both] description: Selects wording + step layering. Defaults to "both" when omitted. omit_commands: type: boolean description: When true, strips the Command and APIRequest.Body fields from emitted steps. Defaults to false. ShadowAgentRemediationResponse: type: object required: [finding_id, tenant_id, remediation] properties: finding_id: type: string tenant_id: type: string remediation: $ref: '#/components/schemas/ShadowRemediationPlan' ShadowRemediationPlan: type: object required: [audience, severity, action_kind, summary, risk_explanation, recommended_action, steps, generator_version, generated_at, advisory_only] description: Deterministic advisory remediation plan. All commands inside steps use literal placeholders and never carry live secrets or developer paths. properties: finding_id: type: string description: Empty when generated from a scanner-shape Finding (no persistent ID). tenant_id: type: string audience: type: string enum: [dev, enterprise, both] severity: type: string enum: [info, low, medium, high] action_kind: $ref: '#/components/schemas/ShadowRemediationActionKind' summary: type: string risk_explanation: type: string recommended_action: type: string safety_notes: type: array items: type: string steps: type: array items: $ref: '#/components/schemas/ShadowRemediationStep' generator_version: type: string generated_at: type: string format: date-time advisory_only: type: boolean description: Always true in this generator. Reserved field — a future enforcement mode (out of scope) may flip it without changing the type signature. ShadowRemediationStep: type: object required: [id, title, kind] properties: id: type: string description: Stable, deterministic identifier within the plan. title: type: string kind: $ref: '#/components/schemas/ShadowRemediationActionKind' command: type: string description: Shell-runnable suggestion with literal placeholders. Empty when the step is API-only or when omit_commands=true. api_request: $ref: '#/components/schemas/ShadowRemediationAPIRequest' requires_backup: type: boolean preview_only: type: boolean destructive: type: boolean docs_url: type: string conditions: type: array items: type: string ShadowRemediationAPIRequest: type: object required: [method, path] properties: method: type: string path: type: string body: type: string description: Stripped when omit_commands=true. ShadowRemediationActionKind: type: string enum: - attach_mcp_gateway - use_cordumctl_edge_claude - deploy_managed_settings - disable_unmanaged_config - route_through_llm_proxy - run_edge_doctor - investigate_process - manual_review 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 - step_up_required - self_approval_denied - idempotency_conflict - idempotency_key_invalid - idempotency_window_expired - limit_exceeded - max_executions_exceeded - event_cap_exceeded - replay_window_full 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 code: type: string description: Optional machine-readable error code 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 AuditEvent: type: object description: | One SIEM audit event returned by `GET /api/v1/audit/events`. Mirrors the gateway handler's `auditEventResponseItem` (which itself mirrors `audit.SIEMEvent` in `core/audit/exporter.go`). The `extra` map's secret-shaped keys (token / password / api_key / private_key / secret) are stripped server-side as defense-in-depth before the response is serialized; their absence does NOT imply the source event lacked them. properties: id: type: string description: | Opaque per-event handle (Redis Stream message ID under the hood). Stable across pagination — safe to use as a row key. seq: type: integer format: int64 description: | Per-tenant monotonic chain sequence. First event for a tenant has seq=1. Pairs with `/api/v1/audit/verify` for forensic re-check. timestamp: type: string format: date-time event_type: type: string severity: type: string description: One of CRITICAL / HIGH / MEDIUM / LOW / INFO. tenant_id: type: string agent_id: type: string agent_name: type: string agent_risk_tier: type: string job_id: type: string action: type: string decision: type: string matched_rule: type: string reason: type: string risk_tags: type: array items: type: string capabilities: type: array items: type: string policy_version: type: string identity: type: string description: Identity of the actor who produced the event. extra: type: object additionalProperties: type: string description: | Free-form per-event metadata. Secret-shaped keys are stripped before serialization (see schema-level description). event_hash: type: string description: SHA-256 of the canonical event payload, hex-encoded. prev_hash: type: string description: | EventHash of the tenant's predecessor event, or empty for genesis. required: - id - seq - timestamp - event_type - severity - tenant_id - action AuditEventsEnvelope: type: object description: | Paginated envelope returned by `GET /api/v1/audit/events`. `next_cursor` is opaque; pass it back as `?cursor=` on the next request. Empty `next_cursor` means end-of-stream — clients should stop polling. properties: items: type: array items: $ref: '#/components/schemas/AuditEvent' next_cursor: type: string description: Opaque cursor for the next page; empty when no more events. returned: type: integer description: Number of items in this page (== `items.length`). required: - items - next_cursor - returned 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 CreateWorkerCredentialRequest: type: object description: >- Creates or rotates a worker credential. Proof-key fields are optional for legacy credentials, but when any proof-key field is supplied all three and agent_id must be supplied together. Rotating an active proof credential without proof-key fields preserves its enrolled key and authoritative agent link after revalidation; changing agent_id for a proof-bearing credential requires a full proof-key replacement bundle. properties: worker_id: type: string allowed_pools: type: array items: type: string allowed_topics: type: array items: type: string agent_id: type: string description: Active agent identity in the same tenant; required for proof-key enrollment. proof_key_id: type: string minLength: 1 maxLength: 128 pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]*$ proof_algorithm: type: string enum: - ECDSA_P256_SHA256 proof_public_key_pem: type: string maxLength: 4096 writeOnly: true description: ECDSA P-256 public key encoded as SubjectPublicKeyInfo PEM. required: - worker_id WorkerCredential: type: object properties: worker_id: type: string tenant_id: type: string allowed_pools: type: array items: type: string allowed_topics: type: array items: type: string pack_id: type: string agent_id: type: string proof_key_id: type: string minLength: 1 maxLength: 128 pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]*$ proof_algorithm: type: string enum: - ECDSA_P256_SHA256 created_by: type: string created_at: type: string format: date-time revoked_at: type: string format: date-time required: - worker_id - tenant_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