{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.connectsoft.ai/reference/schemas/approval.schema.json",
  "title": "Approval request and decision (approval-request-v1, approval-decision-v1)",
  "description": "An approval binds a human decision to the exact proposed artifact or action version (D21, ADR-0015). A changed proposal makes the pending request stale.",
  "$defs": {
    "ApprovalRequest": {
      "type": "object",
      "required": ["approvalId", "runId", "stepId", "tenantId", "actionClass", "proposal", "policyRevision", "approverRule", "expiresAt", "state", "version"],
      "additionalProperties": false,
      "properties": {
        "approvalId": { "type": "string", "format": "uuid" },
        "runId": { "type": "string", "format": "uuid" },
        "stepId": { "type": "string", "format": "uuid" },
        "tenantId": { "type": "string" },
        "projectId": { "type": "string" },
        "actionClass": { "enum": ["shared-write", "external", "production", "publication", "spend"], "description": "Side-effect class of the gated action; read and sandbox never need approval." },
        "proposal": {
          "type": "object", "required": ["kind", "hash"],
          "properties": {
            "kind": { "enum": ["artifact", "diff", "deployment-manifest", "tool-invocation", "release", "memory-promotion", "spend"] },
            "hash": { "type": "string", "description": "Hash of the exact object; the decision authorizes this hash only." },
            "reference": { "type": "string", "description": "Artifact id, diff id, manifest id or tool invocation id." },
            "summary": { "type": "string" },
            "evidence": { "type": "array", "items": { "type": "string" }, "description": "Artifact references supporting the proposal." }
          }
        },
        "policyRevision": { "type": "string" },
        "approverRule": { "type": "string", "description": "Eligibility resolved by Approval Policy (BC-20): roles, scope, separation of duties." },
        "requestedBy": { "type": "string", "description": "Requester of the run; never eligible to decide." },
        "requestedAt": { "type": "string", "format": "date-time" },
        "expiresAt": { "type": "string", "format": "date-time" },
        "state": { "enum": ["Pending", "Approved", "Rejected", "Expired", "Stale", "Cancelled"] },
        "version": { "type": "integer", "description": "Optimistic-concurrency version; a decision must quote it." }
      }
    },
    "ApprovalDecision": {
      "type": "object",
      "required": ["approvalId", "version", "decision"],
      "additionalProperties": false,
      "properties": {
        "approvalId": { "type": "string", "format": "uuid" },
        "version": { "type": "integer" },
        "decision": { "enum": ["approve", "reject", "request-changes"] },
        "reason": { "type": "string", "maxLength": 2048 },
        "breakGlass": { "type": "boolean", "default": false, "description": "Platform admin override; requires reason and is audited." }
      }
    }
  },
  "oneOf": [{ "$ref": "#/$defs/ApprovalRequest" }, { "$ref": "#/$defs/ApprovalDecision" }]
}
