{
  "components": {
    "parameters": {
      "ProtocolVersionHeader": {
        "description": "Required sender CP↔agent wire-schema version. This control plane accepts protocol v6 only. An absent header resolves to legacy v1 and is rejected (400), as are duplicate, malformed, or unsupported values.",
        "in": "header",
        "name": "x-ryuki-protocol-version",
        "required": true,
        "schema": {
          "enum": [
            6
          ],
          "format": "int64",
          "type": "integer"
        }
      }
    },
    "schemas": {
      "AckBody": {
        "properties": {
          "attempt_id": {
            "format": "uuid",
            "type": "string"
          },
          "fencing_token": {
            "type": "string"
          }
        },
        "required": [
          "attempt_id",
          "fencing_token"
        ],
        "type": "object"
      },
      "AckResponse": {
        "properties": {
          "job_id": {
            "format": "uuid",
            "type": "string"
          },
          "status": {
            "example": "Running",
            "type": "string"
          }
        },
        "required": [
          "job_id",
          "status"
        ],
        "type": "object"
      },
      "AlertItem": {
        "properties": {
          "acknowledged": {
            "type": "boolean"
          },
          "acknowledged_at": {
            "oneOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "acknowledged_by": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "actor": {
            "type": "string"
          },
          "aggregate_id": {
            "type": "string"
          },
          "aggregate_type": {
            "type": "string"
          },
          "environment": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "event_type": {
            "type": "string"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "occurred_at": {
            "format": "date-time",
            "type": "string"
          },
          "payload": {
            "description": "The event's structured payload — an arbitrary JSON value (the column is a serde_json::Value); conventionally a JSON object but NOT type-constrained, so an untyped schema (per OpenAPI 3.1, any type)."
          },
          "severity": {
            "example": "critical",
            "type": "string"
          },
          "site": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "event_type",
          "aggregate_type",
          "aggregate_id",
          "actor",
          "severity",
          "payload",
          "occurred_at",
          "acknowledged"
        ],
        "type": "object"
      },
      "AlertsResponse": {
        "properties": {
          "alerts": {
            "items": {
              "$ref": "#/components/schemas/AlertItem"
            },
            "type": "array"
          },
          "durable": {
            "description": "False when no database is configured (empty list).",
            "type": "boolean"
          }
        },
        "required": [
          "alerts",
          "durable"
        ],
        "type": "object"
      },
      "ApproveReviewedLivePlanBody": {
        "additionalProperties": false,
        "description": "Exact reviewed LivePlan selection. The control plane compare-and-swaps this immutable job/attempt/digest tuple against the signed stored result before minting any whole-request grant. The per-step route accepts the same closed shape but remains fail-closed in this release.",
        "properties": {
          "approved_plan_attempt_id": {
            "description": "Exact leased attempt recorded in the reviewed plan's signed envelope.",
            "format": "uuid",
            "type": "string"
          },
          "approved_plan_digest": {
            "description": "Lowercase SHA-256 digest of the complete canonical raw Terraform plan. This is distinct from the safe-projection evidence_digest.",
            "pattern": "^[0-9a-f]{64}$",
            "type": "string"
          },
          "approved_plan_job_id": {
            "description": "agent_jobs.id of the exact LivePlan projection the administrator reviewed.",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "approved_plan_job_id",
          "approved_plan_attempt_id",
          "approved_plan_digest"
        ],
        "type": "object"
      },
      "AuthSession": {
        "description": "The resolved session for the caller. `site_scope` / `environment_scope` are omitted when the admitted axis is explicitly Global; interactive Unknown/Revoked authority is never returned.",
        "properties": {
          "display_name": {
            "type": "string"
          },
          "environment_scope": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "provider_mode": {
            "type": "string"
          },
          "roles": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "site_scope": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "token_valid": {
            "type": "boolean"
          },
          "user_id": {
            "type": "string"
          }
        },
        "required": [
          "user_id",
          "display_name",
          "roles",
          "token_valid",
          "provider_mode"
        ],
        "type": "object"
      },
      "AuthStatusResponse": {
        "properties": {
          "client_configured": {
            "type": "boolean"
          },
          "enabled": {
            "type": "boolean"
          },
          "instance": {
            "type": "string"
          },
          "tenant_configured": {
            "type": "boolean"
          }
        },
        "required": [
          "tenant_configured",
          "client_configured",
          "enabled",
          "instance"
        ],
        "type": "object"
      },
      "Capabilities": {
        "description": "Full capability set self-declared by an agent at registration. NOT trusted for authz decisions — reconciled against a trusted inventory by an admin.",
        "properties": {
          "ansible": {
            "$ref": "#/components/schemas/ToolCapability"
          },
          "terraform": {
            "$ref": "#/components/schemas/ToolCapability"
          }
        },
        "type": "object"
      },
      "CpPublicKeyResponse": {
        "properties": {
          "protocol_version": {
            "format": "int64",
            "minimum": 1,
            "type": "integer"
          },
          "public_key": {
            "description": "Base64-encoded Ed25519 CP public key.",
            "type": "string"
          }
        },
        "required": [
          "public_key",
          "protocol_version"
        ],
        "type": "object"
      },
      "ErrorBody": {
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "type": "object"
      },
      "EventItem": {
        "properties": {
          "actor": {
            "type": "string"
          },
          "aggregate_id": {
            "type": "string"
          },
          "aggregate_type": {
            "type": "string"
          },
          "environment": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "event_type": {
            "type": "string"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "occurred_at": {
            "format": "date-time",
            "type": "string"
          },
          "payload": {
            "description": "The event's structured payload — an arbitrary JSON value (the column is a serde_json::Value); conventionally a JSON object but NOT type-constrained, so an untyped schema (per OpenAPI 3.1, any type)."
          },
          "site": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "event_type",
          "aggregate_type",
          "aggregate_id",
          "actor",
          "payload",
          "occurred_at"
        ],
        "type": "object"
      },
      "EventsResponse": {
        "properties": {
          "durable": {
            "description": "False when no database is configured (empty list).",
            "type": "boolean"
          },
          "events": {
            "items": {
              "$ref": "#/components/schemas/EventItem"
            },
            "type": "array"
          }
        },
        "required": [
          "events",
          "durable"
        ],
        "type": "object"
      },
      "ExecutionTrustProfile": {
        "additionalProperties": false,
        "description": "Closed-schema, non-secret execution-authority snapshot signed by the planning agent. Raw credentials, backend HCL, and provider-returned identifiers are forbidden.",
        "properties": {
          "allowlist_version": {
            "type": "string"
          },
          "backend_authority_digest": {
            "pattern": "^[0-9a-f]{64}$",
            "type": "string"
          },
          "backend_credential_authority_id": {
            "description": "Stable, non-secret provisioning-record reference for the backend credential principal. Its backend-kind path component must equal backend_kind.",
            "pattern": "^backend-credential-authority/[a-z0-9_-]+/[a-z0-9._/-]+$",
            "type": "string"
          },
          "backend_credential_authority_mode": {
            "type": "string"
          },
          "backend_credential_authority_revision": {
            "description": "Immutable authority revision; rotates whenever the backend principal, destination, or credential set changes.",
            "pattern": "^v[a-z0-9._-]+$",
            "type": "string"
          },
          "backend_kind": {
            "type": "string"
          },
          "containment_policy_version": {
            "type": "string"
          },
          "executable_kind": {
            "type": "string"
          },
          "executable_path": {
            "type": "string"
          },
          "executable_provenance_policy_version": {
            "type": "string"
          },
          "executable_sha256": {
            "oneOf": [
              {
                "pattern": "^[0-9a-f]{64}$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "executable_version": {
            "type": "string"
          },
          "iac_digest": {
            "pattern": "^[0-9a-f]{64}$",
            "type": "string"
          },
          "offering": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "provider_authority_id": {
            "description": "Stable, non-secret provisioning-record reference for the exact provider destination/account credential set.",
            "pattern": "^provider-authority/vsphere/[a-z0-9._/-]+$",
            "type": "string"
          },
          "provider_authority_version": {
            "description": "Immutable authority version; rotates whenever any destination, account, or credential member changes.",
            "pattern": "^v[a-z0-9._-]+$",
            "type": "string"
          },
          "provider_credential_authority_mode": {
            "type": "string"
          },
          "provider_source": {
            "type": "string"
          },
          "provider_version": {
            "type": "string"
          },
          "runner_kind": {
            "enum": [
              "terraform"
            ],
            "type": "string"
          },
          "schema_version": {
            "type": "string"
          },
          "state_key": {
            "type": "string"
          }
        },
        "required": [
          "schema_version",
          "allowlist_version",
          "platform",
          "offering",
          "runner_kind",
          "provider_source",
          "provider_version",
          "provider_authority_id",
          "provider_authority_version",
          "backend_kind",
          "backend_credential_authority_id",
          "backend_credential_authority_revision",
          "backend_authority_digest",
          "executable_kind",
          "executable_path",
          "executable_version",
          "executable_sha256",
          "executable_provenance_policy_version",
          "provider_credential_authority_mode",
          "backend_credential_authority_mode",
          "containment_policy_version",
          "iac_digest",
          "state_key"
        ],
        "type": "object"
      },
      "ExecutionView": {
        "properties": {
          "detail": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "finished_at": {
            "oneOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "id": {
            "type": "string"
          },
          "job_kind": {
            "type": "string"
          },
          "schedule_id": {
            "type": "string"
          },
          "started_at": {
            "format": "date-time",
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "schedule_id",
          "job_kind",
          "status",
          "started_at"
        ],
        "type": "object"
      },
      "HealthResponse": {
        "description": "Liveness probe result. `status` is `degraded` (still 200) when the database is disconnected or hard config validation fails.",
        "properties": {
          "auth_mode": {
            "type": "string"
          },
          "config": {
            "properties": {
              "errors": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "valid": {
                "type": "boolean"
              },
              "warnings": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "valid",
              "errors",
              "warnings"
            ],
            "type": "object"
          },
          "database": {
            "properties": {
              "connected": {
                "type": "boolean"
              },
              "provider": {
                "type": "string"
              }
            },
            "required": [
              "connected",
              "provider"
            ],
            "type": "object"
          },
          "rate_limit_enabled": {
            "type": "boolean"
          },
          "status": {
            "enum": [
              "healthy",
              "degraded"
            ],
            "type": "string"
          }
        },
        "required": [
          "status",
          "database",
          "config",
          "auth_mode",
          "rate_limit_enabled"
        ],
        "type": "object"
      },
      "HeartbeatBody": {
        "description": "Idle heartbeats leave every field null. A running heartbeat must supply the complete current lease fence; partial fences are rejected.",
        "properties": {
          "attempt_id": {
            "oneOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "fencing_token": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "lease_generation": {
            "oneOf": [
              {
                "format": "int64",
                "minimum": 0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "running_job_id": {
            "description": "Currently running job id, if any.",
            "oneOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object"
      },
      "HeartbeatResponse": {
        "properties": {
          "agent_id": {
            "type": "string"
          },
          "last_seen_at": {
            "format": "date-time",
            "type": "string"
          },
          "lease_deadline": {
            "description": "Database-clock deadline returned only after an exact running-job lease renewal.",
            "oneOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "agent_id",
          "last_seen_at",
          "lease_deadline"
        ],
        "type": "object"
      },
      "Job": {
        "description": "A dispatchable unit of work, as returned by GET /api/agents/{agent_id}/jobs.",
        "properties": {
          "agent_enrollment_id": {
            "description": "Immutable enrollment row of the authenticated assignee returning this job.",
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lease": {
            "description": "Present when the job has been leased.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/JobLease"
              },
              {
                "type": "null"
              }
            ]
          },
          "live_context": {
            "description": "CP-signed approval grant; required for live_apply and live_destroy, absent otherwise.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/VerifiedLiveContext"
              },
              {
                "type": "null"
              }
            ]
          },
          "platform": {
            "type": "string"
          },
          "spec": {
            "$ref": "#/components/schemas/JobSpec"
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus"
          }
        },
        "required": [
          "id",
          "agent_enrollment_id",
          "platform",
          "spec",
          "status"
        ],
        "type": "object"
      },
      "JobLease": {
        "description": "Fencing lease issued when an agent polls and wins a job row.",
        "properties": {
          "attempt_id": {
            "format": "uuid",
            "type": "string"
          },
          "cp_nonce": {
            "description": "Per-lease one-time nonce; copied verbatim into SignedEnvelope.cp_nonce.",
            "type": "string"
          },
          "deadline": {
            "format": "date-time",
            "type": "string"
          },
          "fencing_token": {
            "type": "string"
          },
          "lease_generation": {
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "attempt_id",
          "lease_generation",
          "fencing_token",
          "deadline",
          "cp_nonce"
        ],
        "type": "object"
      },
      "JobMode": {
        "description": "Execution mode. The agent MUST NOT elevate the mode on its own.",
        "enum": [
          "offline_dry_run",
          "live_plan",
          "live_apply",
          "live_destroy"
        ],
        "type": "string"
      },
      "JobResult": {
        "description": "Posted by the agent as part of ResultBody. The triple (job_id, attempt_id, result_id) is the idempotency key.",
        "properties": {
          "attempt_id": {
            "format": "uuid",
            "type": "string"
          },
          "evidence_digest": {
            "description": "SHA-256 hex digest of the (redacted) evidence pack.",
            "type": "string"
          },
          "job_id": {
            "format": "uuid",
            "type": "string"
          },
          "raw_plan_digest": {
            "description": "Must equal SignedEnvelope.raw_plan_digest. Present only for LivePlan + Planned.",
            "oneOf": [
              {
                "pattern": "^[0-9a-f]{64}$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "result_id": {
            "format": "uuid",
            "type": "string"
          },
          "signed_envelope": {
            "$ref": "#/components/schemas/SignedEnvelope"
          },
          "status": {
            "$ref": "#/components/schemas/JobResultStatus"
          }
        },
        "required": [
          "job_id",
          "attempt_id",
          "result_id",
          "status",
          "raw_plan_digest",
          "evidence_digest",
          "signed_envelope"
        ],
        "type": "object"
      },
      "JobResultStatus": {
        "description": "Terminal execution outcomes an agent may report. Narrower than JobStatus; the CP rejects any value outside this set (in particular `verified` is CP-internal and is rejected if submitted by an agent).",
        "enum": [
          "check_ok",
          "planned",
          "applied",
          "failed",
          "live_refused"
        ],
        "type": "string"
      },
      "JobSpec": {
        "description": "References IaC artefacts by stable identifier + digest, never by inline content. Credentials are never included.",
        "properties": {
          "iac_digest": {
            "description": "SHA-256 hex digest of the IaC template.",
            "type": "string"
          },
          "iac_ref": {
            "type": "string"
          },
          "mode": {
            "$ref": "#/components/schemas/JobMode"
          },
          "offering_id": {
            "format": "uuid",
            "type": "string"
          },
          "request_id": {
            "format": "uuid",
            "type": "string"
          },
          "state_key": {
            "description": "Opaque control-plane-owned Terraform state key. New specs always carry it; absent legacy specs remain decodable but are refused for live Terraform execution.",
            "pattern": "^[A-Za-z0-9_-]{1,128}$",
            "type": "string"
          },
          "vars": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Non-secret variable overrides.",
            "type": "object"
          }
        },
        "required": [
          "request_id",
          "offering_id",
          "iac_ref",
          "iac_digest",
          "mode"
        ],
        "type": "object"
      },
      "JobStatus": {
        "description": "CP-side lifecycle status of a job (agent-dispatchable subset).",
        "enum": [
          "pending",
          "leased",
          "running",
          "succeeded",
          "failed",
          "expired",
          "reconcile_required",
          "live_refused"
        ],
        "type": "string"
      },
      "LiveExecutionAuthority": {
        "additionalProperties": false,
        "description": "Exact successful-plan owner and execution-profile authority carried by a CP-signed live grant.",
        "properties": {
          "assigned_agent_enrollment_id": {
            "format": "uuid",
            "type": "string"
          },
          "assigned_agent_id": {
            "type": "string"
          },
          "assigned_agent_key_fingerprint": {
            "type": "string"
          },
          "execution_trust_profile_digest": {
            "pattern": "^[0-9a-f]{64}$",
            "type": "string"
          }
        },
        "required": [
          "assigned_agent_id",
          "assigned_agent_enrollment_id",
          "assigned_agent_key_fingerprint",
          "execution_trust_profile_digest"
        ],
        "type": "object"
      },
      "PlatformSummaryResponse": {
        "properties": {
          "browserIsolation": {
            "type": "boolean"
          },
          "components": {
            "description": "Platform component descriptors.",
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "guardrails": {
            "description": "Guardrail descriptors.",
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "lifecycleStages": {
            "description": "Ordered lifecycle stage descriptors.",
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "localAuthorization": {
            "properties": {
              "authenticationMode": {
                "type": "string"
              },
              "configuredForProduction": {
                "type": "boolean"
              },
              "entraGroupsConfigured": {
                "type": "boolean"
              },
              "requiredProductionProvider": {
                "type": "string"
              },
              "roleHeader": {
                "type": "string"
              }
            },
            "required": [
              "authenticationMode",
              "configuredForProduction",
              "entraGroupsConfigured",
              "roleHeader",
              "requiredProductionProvider"
            ],
            "type": "object"
          },
          "productName": {
            "type": "string"
          }
        },
        "required": [
          "productName",
          "lifecycleStages",
          "components",
          "guardrails",
          "browserIsolation",
          "localAuthorization"
        ],
        "type": "object"
      },
      "ProblemDetails": {
        "description": "RFC 9457-shaped error body returned by the public infra probes (`/health`, `/ready`) on failure.",
        "properties": {
          "detail": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "error": {
            "example": "DATABASE_UNAVAILABLE",
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "error",
          "message"
        ],
        "type": "object"
      },
      "RbacRole": {
        "properties": {
          "description": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "permissions": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "name",
          "description",
          "permissions"
        ],
        "type": "object"
      },
      "ReadyResponse": {
        "additionalProperties": true,
        "description": "Readiness probe result. Returned only on success (200); failure returns a 503 ProblemDetails instead (DRAINING, READINESS_CHECK_FAILED, CONFIG_INVALID, DATABASE_UNAVAILABLE, and related database-unusable/migration codes).",
        "properties": {
          "status": {
            "example": "ready",
            "type": "string"
          }
        },
        "required": [
          "status"
        ],
        "type": "object"
      },
      "RegisterBody": {
        "additionalProperties": false,
        "properties": {
          "agent_id": {
            "description": "Stable agent identifier, e.g. \"defra-vcenter-01\".",
            "type": "string"
          },
          "capabilities": {
            "$ref": "#/components/schemas/Capabilities"
          },
          "enrollment_challenge": {
            "description": "One-time bootstrap secret. The control plane stores only its SHA-256 hash and consumes it atomically.",
            "pattern": "^ryc_[0-9a-f]{64}$",
            "type": "string"
          },
          "enrollment_challenge_id": {
            "description": "Short-lived grant preprovisioned by a trusted administrator.",
            "format": "uuid",
            "type": "string"
          },
          "enrollment_proof": {
            "description": "Base64 Ed25519 signature over the domain-separated challenge and exact identity fields, proving possession of public_key.",
            "type": "string"
          },
          "platform": {
            "description": "Platform / site this agent serves, e.g. \"defra\".",
            "type": "string"
          },
          "public_key": {
            "description": "Canonical padded-base64 Ed25519 verifying key selected by trusted provisioning.",
            "type": "string"
          }
        },
        "required": [
          "enrollment_challenge_id",
          "enrollment_challenge",
          "agent_id",
          "platform",
          "capabilities",
          "public_key",
          "enrollment_proof"
        ],
        "type": "object"
      },
      "RegisterResponse": {
        "description": "Returned once on successful registration. The token is never stored or returned again.",
        "properties": {
          "agent_id": {
            "type": "string"
          },
          "token": {
            "example": "rya_<hex>",
            "type": "string"
          }
        },
        "required": [
          "agent_id",
          "token"
        ],
        "type": "object"
      },
      "ResultBody": {
        "description": "The outer JobResult fields are untrusted; only the embedded SignedEnvelope is authoritative — the CP equality-checks every outer field against it before persisting anything.",
        "properties": {
          "evidence": {
            "default": [],
            "description": "Raw evidence bytes, serialized as a JSON array of u8 (the wire shape of the server's `Vec<u8>` field — NOT base64). Its SHA-256 is evidence_digest. May be empty for modes producing no evidence, but the digest must still match.",
            "items": {
              "maximum": 255,
              "minimum": 0,
              "type": "integer"
            },
            "type": "array"
          },
          "evidence_json": {
            "description": "Optional structured evidence parsed from the evidence bytes (ANY JSON value — the field is a serde_json::Value; an untyped schema, per OpenAPI 3.1, permits any type including null). Never trusted for authz."
          },
          "job_result": {
            "$ref": "#/components/schemas/JobResult"
          }
        },
        "required": [
          "job_result"
        ],
        "type": "object"
      },
      "ScheduleView": {
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "id": {
            "type": "string"
          },
          "interval_secs": {
            "format": "int64",
            "type": "integer"
          },
          "job_kind": {
            "type": "string"
          },
          "last_run_at": {
            "oneOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "name": {
            "type": "string"
          },
          "next_run_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "job_kind",
          "interval_secs",
          "enabled",
          "next_run_at"
        ],
        "type": "object"
      },
      "SchedulerExecutionsResponse": {
        "properties": {
          "durable": {
            "description": "False when no database is configured (empty list).",
            "type": "boolean"
          },
          "executions": {
            "items": {
              "$ref": "#/components/schemas/ExecutionView"
            },
            "type": "array"
          }
        },
        "required": [
          "executions",
          "durable"
        ],
        "type": "object"
      },
      "SchedulerSchedulesResponse": {
        "properties": {
          "durable": {
            "description": "False when no database is configured (empty list).",
            "type": "boolean"
          },
          "schedules": {
            "items": {
              "$ref": "#/components/schemas/ScheduleView"
            },
            "type": "array"
          }
        },
        "required": [
          "schedules",
          "durable"
        ],
        "type": "object"
      },
      "SignedEnvelope": {
        "description": "Binds the full execution context for a posted result; tamper-evident via an Ed25519 signature over the fixed-order signable fields.",
        "properties": {
          "agent_enrollment_id": {
            "description": "Immutable UUID of the exact authenticated agent enrollment that signed the result.",
            "format": "uuid",
            "type": "string"
          },
          "agent_id": {
            "type": "string"
          },
          "approved_plan_digest": {
            "description": "SHA-256 hex digest of the approved plan; null for non-live_apply modes.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "attempt_id": {
            "format": "uuid",
            "type": "string"
          },
          "cp_nonce": {
            "description": "Copied verbatim from JobLease.cp_nonce.",
            "type": "string"
          },
          "evidence_digest": {
            "description": "SHA-256 hex digest of the (post-redaction) evidence pack.",
            "type": "string"
          },
          "execution_trust_profile": {
            "description": "Canonical non-secret execution profile for successful live results; null for offline or refused outcomes.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/ExecutionTrustProfile"
              },
              {
                "type": "null"
              }
            ]
          },
          "job_id": {
            "format": "uuid",
            "type": "string"
          },
          "job_spec_digest": {
            "description": "SHA-256 hex digest of the canonical JobSpec bytes.",
            "type": "string"
          },
          "key_id": {
            "type": "string"
          },
          "lease_generation": {
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "mode": {
            "$ref": "#/components/schemas/JobMode"
          },
          "platform": {
            "type": "string"
          },
          "raw_plan_digest": {
            "description": "Signed digest of the complete canonical raw Terraform plan. Required for LivePlan + Planned and null for every other mode/status; never interchangeable with evidence_digest.",
            "oneOf": [
              {
                "pattern": "^[0-9a-f]{64}$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "redaction_policy_version": {
            "example": "ryuki-redaction-v2",
            "type": "string"
          },
          "request_id": {
            "format": "uuid",
            "type": "string"
          },
          "result_id": {
            "description": "Must equal JobResult.result_id.",
            "format": "uuid",
            "type": "string"
          },
          "signature": {
            "description": "Base64-encoded Ed25519 signature.",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/JobResultStatus"
          },
          "timestamp": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "agent_id",
          "agent_enrollment_id",
          "platform",
          "job_id",
          "attempt_id",
          "lease_generation",
          "request_id",
          "result_id",
          "mode",
          "status",
          "job_spec_digest",
          "approved_plan_digest",
          "raw_plan_digest",
          "execution_trust_profile",
          "evidence_digest",
          "redaction_policy_version",
          "timestamp",
          "key_id",
          "cp_nonce",
          "signature"
        ],
        "type": "object"
      },
      "ToolCapability": {
        "description": "Version info for a single tool (Terraform or Ansible).",
        "properties": {
          "provider_versions": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Terraform-local provider name → version (for example vsphere, not registry source vmware/vsphere). Empty/absent for Ansible.",
            "type": "object"
          },
          "version": {
            "example": "1.9.5",
            "type": "string"
          }
        },
        "required": [
          "version"
        ],
        "type": "object"
      },
      "VerifiedLiveContext": {
        "description": "Protocol-v6 CP-signed approval grant for a live_apply or live_destroy job. It binds the exact successful plan row and attempt; live_destroy grants are also step-bound.",
        "properties": {
          "approved_plan_attempt_id": {
            "description": "Exact leased attempt that produced the reviewed plan result.",
            "format": "uuid",
            "type": "string"
          },
          "approved_plan_digest": {
            "description": "Raw canonical Terraform plan digest selected from the exact successful LivePlan job and attempt.",
            "pattern": "^[0-9a-f]{64}$",
            "type": "string"
          },
          "approved_plan_job_id": {
            "description": "Immutable agent_jobs.id of the exact successful LivePlan result reviewed for this grant.",
            "format": "uuid",
            "type": "string"
          },
          "approver": {
            "type": "string"
          },
          "execution_authority": {
            "$ref": "#/components/schemas/LiveExecutionAuthority"
          },
          "expiry": {
            "format": "date-time",
            "type": "string"
          },
          "job_spec_digest": {
            "description": "SHA-256 digest of the exact canonical JobSpec authorized by this grant, including mode and state_key.",
            "pattern": "^[0-9a-f]{64}$",
            "type": "string"
          },
          "platform": {
            "description": "Signed destination platform/site; must equal Job.platform before live mutation.",
            "type": "string"
          },
          "request_id": {
            "format": "uuid",
            "type": "string"
          },
          "signature": {
            "description": "Base64-encoded Ed25519 signature.",
            "type": "string"
          },
          "step_job_id": {
            "description": "Present only for a step-scoped grant: binds this grant to one dispatched step job id, preventing replay across steps or re-dispatches. Absent for a whole-request LiveApply grant.",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "request_id",
          "platform",
          "job_spec_digest",
          "approved_plan_digest",
          "approved_plan_job_id",
          "approved_plan_attempt_id",
          "approver",
          "expiry",
          "execution_authority",
          "signature"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "agentBearer": {
        "bearerFormat": "rya_<hex>",
        "description": "Agent-issued bearer token returned once by POST /api/agents/register. Sent as `Authorization: Bearer rya_...`.",
        "scheme": "bearer",
        "type": "http"
      },
      "apiSessionHeader": {
        "description": "Opaque 256-bit operator session bearer (rys_ plus canonical unpadded base64url), retained under the compatibility header name. Authorization: Bearer rys_... and the mode-selected HttpOnly session cookie are alternate single carriers. HTTPS uses __Host-ryuki_session; explicit non-Secure loopback development uses ryuki_session. Supplying more than one credential carrier or cookie name is rejected. The UUID returned by admin session inventory is management metadata and can never authenticate.",
        "in": "header",
        "name": "X-Ryuki-Session-Id",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "description": "Machine-readable contract for four route groups. (1) The CP↔agent wire protocol (registration, job polling/lease, ack, result submission, heartbeat): poll, ack, result, and heartbeat require an agent bearer token (`Authorization: Bearer rya_...`); registration has no agent bearer yet and instead requires a preprovisioned one-time challenge plus Ed25519 proof of possession; `cp-public-key` is unauthenticated. Every agent-protocol request MUST carry the supported `x-ryuki-protocol-version` header. This build is protocol-v6-only; an absent header resolves to legacy v1 and is rejected. (2) PUBLIC endpoints (no auth) — infra probes plus the pre-login portal bootstrap reads. (3) A bounded READ-ONLY operational surface (events/alerts feed, scheduler introspection) that requires an operator session and a permission tier, documented for operational tooling, not external agents. (4) The human live-approval surface, whose request body compare-and-swaps the exact reviewed plan job, attempt, and digest; per-step approval remains intentionally fail-closed.",
    "title": "Ryuki Agent Protocol API",
    "version": "0.1.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/agents/cp-public-key": {
      "get": {
        "description": "Unauthenticated — a public key is not a secret. Agents use this to pin the CP public key for verifying VerifiedLiveContext grants, and to read the CP's advertised protocol_version.",
        "operationId": "cpPublicKey",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProtocolVersionHeader"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CpPublicKeyResponse"
                }
              }
            },
            "description": "CP public key + advertised protocol version."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Missing, malformed, duplicate, or unsupported x-ryuki-protocol-version header."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "CP signing key not initialised (degraded startup)."
          }
        },
        "security": [],
        "summary": "Fetch the control plane's Ed25519 public key",
        "tags": [
          "agents"
        ]
      }
    },
    "/api/agents/register": {
      "post": {
        "description": "Consumes a short-lived, single-use trusted provisioning challenge bound to the exact agent id, platform, and Ed25519 public key after verifying proof of possession. It then generates a bearer token, stores its SHA-256 hash, and returns the plaintext token ONCE. The agent remains in `pending` status until an admin approves that cryptographically admitted identity.",
        "operationId": "registerAgent",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProtocolVersionHeader"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegisterResponse"
                }
              }
            },
            "description": "Agent registered (pending approval). The one-time token response is marked Cache-Control: no-store.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store` because the response contains a one-time plaintext bearer.",
                "schema": {
                  "const": "no-store",
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Malformed bounded fields, non-canonical/weak Ed25519 public key, or an invalid/unsupported x-ryuki-protocol-version header."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "The one-time challenge is absent, expired, consumed, mismatched, or lacks a valid proof of possession."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "agent_id already registered."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Database unavailable."
          }
        },
        "summary": "Enroll a new agent (pending admin approval)",
        "tags": [
          "agents"
        ]
      }
    },
    "/api/agents/{agent_id}/heartbeat": {
      "post": {
        "description": "Idle heartbeats update agent liveness. Running heartbeats atomically renew only the exact unexpired job/attempt/generation/fencing ownership tuple.",
        "operationId": "heartbeat",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProtocolVersionHeader"
          },
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HeartbeatBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HeartbeatResponse"
                }
              }
            },
            "description": "Heartbeat recorded."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Malformed or incomplete running-job lease fence, or unsupported protocol version."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Missing/malformed bearer token."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Token does not match agent_id."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Running-job lease ownership was superseded or expired."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Database unavailable."
          }
        },
        "security": [
          {
            "agentBearer": []
          }
        ],
        "summary": "Report liveness or renew a running job lease",
        "tags": [
          "agents"
        ]
      }
    },
    "/api/agents/{agent_id}/jobs": {
      "get": {
        "description": "Authenticated (bearer token + approved). Atomically leases the next Pending job for this agent's platform via SELECT ... FOR UPDATE SKIP LOCKED, and returns the full Job with its JobLease (including cp_nonce + fencing_token). Returns 204 when no Pending job is available.",
        "operationId": "pollJob",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProtocolVersionHeader"
          },
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            },
            "description": "A job was leased."
          },
          "204": {
            "description": "No Pending job available for this agent's platform."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Missing/malformed bearer token."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Token does not match any approved agent, or does not match the path agent_id."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Database unavailable."
          }
        },
        "security": [
          {
            "agentBearer": []
          }
        ],
        "summary": "Poll for and atomically lease the next pending job",
        "tags": [
          "agents"
        ]
      }
    },
    "/api/agents/{agent_id}/jobs/{job_id}/ack": {
      "post": {
        "description": "The caller must supply the fencing_token and attempt_id that match the current lease. A mismatch, expired lease, or wrong status returns 409.",
        "operationId": "ackJob",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProtocolVersionHeader"
          },
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AckBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AckResponse"
                }
              }
            },
            "description": "Job transitioned to Running."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Token does not match agent_id, or job is not assigned to this agent."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Job not found."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Status/attempt_id/fencing_token mismatch or expired lease."
          }
        },
        "security": [
          {
            "agentBearer": []
          }
        ],
        "summary": "Acknowledge a leased job (Leased -> Running)",
        "tags": [
          "agents"
        ]
      }
    },
    "/api/agents/{agent_id}/jobs/{job_id}/result": {
      "post": {
        "description": "Verifies and records the signed JobResult from an agent. The full verifier runs fail-closed: every check that fails returns 4xx and mutates nothing. A repeat POST with the same (job_id, attempt_id, result_id) returns an idempotent 200.",
        "operationId": "postJobResult",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProtocolVersionHeader"
          },
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResultBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Result recorded (or idempotently re-returned for a repeat submission)."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Malformed body, digest mismatch, or an invalid/unsupported x-ryuki-protocol-version header."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Missing/malformed bearer token."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Token does not match path agent_id, or job is not assigned to this agent."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Stale attempt/lease_generation, or a result conflicting with an already-recorded terminal outcome."
          }
        },
        "security": [
          {
            "agentBearer": []
          }
        ],
        "summary": "Submit a signed terminal job result",
        "tags": [
          "agents"
        ]
      }
    },
    "/api/auth/roles": {
      "get": {
        "description": "Unauthenticated — role/permission names are not secret; the sign-in page uses this to describe access levels before login.",
        "operationId": "authRoles",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/RbacRole"
                  },
                  "type": "array"
                }
              }
            },
            "description": "All configured RBAC roles."
          }
        },
        "security": [],
        "summary": "List the platform's RBAC roles",
        "tags": [
          "public"
        ]
      }
    },
    "/api/auth/session": {
      "get": {
        "description": "Unauthenticated route (exempt from the session-required gate) so the portal can probe its own auth state pre-login; an unauthenticated caller gets back an unverified/anonymous session shape, not a 401.",
        "operationId": "authSession",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthSession"
                }
              }
            },
            "description": "The resolved (or unverified/anonymous) session."
          }
        },
        "security": [],
        "summary": "Read the caller's resolved session",
        "tags": [
          "public"
        ]
      }
    },
    "/api/auth/status": {
      "get": {
        "description": "Unauthenticated pre-login read — the sign-in page's bootstrap.",
        "operationId": "authStatus",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthStatusResponse"
                }
              }
            },
            "description": "Entra ID configuration presence."
          }
        },
        "security": [],
        "summary": "Report whether Entra ID SSO is configured",
        "tags": [
          "public"
        ]
      }
    },
    "/api/events": {
      "get": {
        "description": "Requires an operator session and the `request` (or higher, e.g. `audit`) permission tier. Newest first, site/environment-scoped: a scoped principal sees only its own scope's events plus platform-wide (NULL on both axes) events. Returns an empty list with `durable: false` when no database is configured.",
        "operationId": "opsEvents",
        "parameters": [
          {
            "description": "Filter to one dotted event type, e.g. `request.approve`.",
            "in": "query",
            "name": "event_type",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filter to one aggregate's history (e.g. a request id).",
            "in": "query",
            "name": "aggregate_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size. Default 50, clamped to 1..500.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventsResponse"
                }
              }
            },
            "description": "The event feed page."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Missing/invalid session."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Session lacks the required permission tier."
          }
        },
        "security": [
          {
            "apiSessionHeader": []
          }
        ],
        "summary": "Read the operational domain-event feed",
        "tags": [
          "ops-read"
        ]
      }
    },
    "/api/events/alerts": {
      "get": {
        "description": "Requires an operator session and the `request` (or higher, e.g. `audit`) permission tier. Same scoping as `/api/events`, filtered to events the alert classifier flags (request reaching a negative terminal state), annotated with severity and acknowledgement state. Returns an empty list with `durable: false` when no database is configured.",
        "operationId": "opsEventsAlerts",
        "parameters": [
          {
            "description": "Filter to one aggregate's history (e.g. a request id).",
            "in": "query",
            "name": "aggregate_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size. Default 50, clamped to 1..500.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlertsResponse"
                }
              }
            },
            "description": "The alert feed page."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Missing/invalid session."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Session lacks the required permission tier."
          }
        },
        "security": [
          {
            "apiSessionHeader": []
          }
        ],
        "summary": "Read the alert-worthy slice of the domain-event feed",
        "tags": [
          "ops-read"
        ]
      }
    },
    "/api/ops/scheduler/executions": {
      "get": {
        "description": "Requires an operator session and the `execute` permission tier. Read-only. Returns an empty list with `durable: false` when no database is configured.",
        "operationId": "opsSchedulerExecutions",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SchedulerExecutionsResponse"
                }
              }
            },
            "description": "Recent job executions."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Missing/invalid session."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Session lacks the `execute` permission tier."
          }
        },
        "security": [
          {
            "apiSessionHeader": []
          }
        ],
        "summary": "List recent durable-scheduler job runs",
        "tags": [
          "ops-read"
        ]
      }
    },
    "/api/ops/scheduler/schedules": {
      "get": {
        "description": "Requires an operator session and the `execute` permission tier. Read-only. Returns an empty list with `durable: false` when no database is configured.",
        "operationId": "opsSchedulerSchedules",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SchedulerSchedulesResponse"
                }
              }
            },
            "description": "The registered schedules."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Missing/invalid session."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Session lacks the `execute` permission tier."
          }
        },
        "security": [
          {
            "apiSessionHeader": []
          }
        ],
        "summary": "List the durable scheduler's registered recurring jobs",
        "tags": [
          "ops-read"
        ]
      }
    },
    "/api/platform/summary": {
      "get": {
        "description": "Unauthenticated pre-login read — the sign-in page's bootstrap for choosing sign-in copy (authenticationMode). Non-sensitive branding/mode metadata only: no request data, secrets, tenant ids, or network values.",
        "operationId": "platformSummary",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlatformSummaryResponse"
                }
              }
            },
            "description": "Platform summary."
          }
        },
        "security": [],
        "summary": "Read platform branding/mode metadata",
        "tags": [
          "public"
        ]
      }
    },
    "/api/requests/{id}/approve-live-apply": {
      "post": {
        "description": "Admin-only, scope- and separation-of-duties-gated compare-and-swap. The server locks and re-verifies the exact signed LivePlan job, attempt, and safe-projection digest before minting one CP-signed LiveApply grant. A later same-digest row cannot replace the reviewed row. Production external execution remains blocked by the descendant-containment gate.",
        "operationId": "approveReviewedLivePlan",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApproveReviewedLivePlanBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "approver": {
                      "type": "string"
                    },
                    "job_id": {
                      "format": "uuid",
                      "type": "string"
                    },
                    "mode": {
                      "const": "LiveApply",
                      "type": "string"
                    },
                    "status": {
                      "const": "Pending",
                      "type": "string"
                    }
                  },
                  "required": [
                    "job_id",
                    "approver",
                    "status",
                    "mode"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "The single whole-request LiveApply job was minted from the exact reviewed plan selection."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Malformed selection or stored plan data."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Missing or invalid operator session."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Not a verified human admin, or separation of duties failed."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Request missing or outside the principal's scope."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Reviewed selection is stale/mismatched, no reviewable plan exists, request state/site is ineligible, or the permanent apply slot is consumed."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "JSON extraction or field decoding failed."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Database or CP signing authority unavailable."
          }
        },
        "security": [
          {
            "apiSessionHeader": []
          }
        ],
        "summary": "Approve the exact reviewed whole-request LivePlan",
        "tags": [
          "live-approval"
        ]
      }
    },
    "/api/requests/{id}/steps/{step_key}/approve-live-apply": {
      "post": {
        "description": "The route preserves admin, request-scope, and separation-of-duties checks, validates the same exact reviewed-plan body, then returns 409 without minting. It remains disabled until an exact digest-bound step review surface exists.",
        "operationId": "rejectDisabledStepLiveApproval",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "step_key",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApproveReviewedLivePlanBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Malformed reviewed-plan selection."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Missing or invalid operator session."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Not a verified human admin, or separation of duties failed."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Request missing or outside the principal's scope."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Per-step live approval is disabled until an exact digest-bound step review is available."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "JSON extraction or field decoding failed."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorBody"
                }
              }
            },
            "description": "Database unavailable."
          }
        },
        "security": [
          {
            "apiSessionHeader": []
          }
        ],
        "summary": "Fail closed for human per-step LiveApply approval",
        "tags": [
          "live-approval"
        ]
      }
    },
    "/health": {
      "get": {
        "description": "Unauthenticated. Always 200 unless `?simulate=error` is passed, which forces a 503 ProblemDetails for testing the error contract. `status` is `degraded` (still 200) when the database is disconnected or hard config validation fails.",
        "operationId": "health",
        "parameters": [
          {
            "description": "Set to `error` to force a 503 ProblemDetails response.",
            "in": "query",
            "name": "simulate",
            "required": false,
            "schema": {
              "enum": [
                "error"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            },
            "description": "Health check result (healthy or degraded)."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Simulated failure via `?simulate=error`."
          }
        },
        "security": [],
        "summary": "Liveness probe",
        "tags": [
          "public"
        ]
      }
    },
    "/ready": {
      "get": {
        "description": "Unauthenticated. Returns 200 only when the server is not draining, hard config validation passes, and the database is reachable with migrations applied. `?simulate=error` forces a 503 ProblemDetails for testing the error contract.",
        "operationId": "ready",
        "parameters": [
          {
            "description": "Set to `error` to force a 503 ProblemDetails response.",
            "in": "query",
            "name": "simulate",
            "required": false,
            "schema": {
              "enum": [
                "error"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadyResponse"
                }
              }
            },
            "description": "Server is ready to accept traffic."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not ready: draining, simulated failure, invalid config, or the database is unavailable/unusable/mid-migration."
          }
        },
        "security": [],
        "summary": "Readiness probe",
        "tags": [
          "public"
        ]
      }
    }
  },
  "servers": [
    {
      "description": "Same-origin control plane",
      "url": "/"
    }
  ]
}
