{
  "schemaVersion": "1.0",
  "title": "Approvals and policy",
  "description": "Use this page to understand how an AIP capability can pause an action for approval and later resume the same queued action from durable, verified authorization. It is for application, runtime, and connector developers who need to preserve a",
  "canonical": "https://getaip.org/docs/concepts/approvals-and-policy",
  "route": "/docs/concepts/approvals-and-policy",
  "source": "docs/concepts/approvals-and-policy.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Core Concepts",
  "documentType": "Concept",
  "language": "en",
  "revision": {
    "lastReviewedRevision": "97be86e9efedf07ecf1783b03800f683f107fb04",
    "documentationSourceRevision": "9192fef3695ad294994f2712f6d156241e5e92fb",
    "basis": "frontmatter"
  },
  "downloads": {
    "md": "/docs/download/concepts/approvals-and-policy.md",
    "txt": "/docs/download/concepts/approvals-and-policy.txt",
    "json": "/docs/download/concepts/approvals-and-policy.json",
    "pdf": "/docs/download/concepts/approvals-and-policy.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Approvals and policy\ndescription: Understand how AIP turns approval policy into durable, verified authorization\nkind: explanation\naudience: developer\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\n---\n\n# Approvals and policy\n\nUse this page to understand how an AIP capability can pause an action for\napproval and later resume the same queued action from durable, verified\nauthorization. It is for application, runtime, and connector developers who\nneed to preserve a policy decision across retries, restarts, or a remote\nconnector-host boundary.\n\nAn approval is not a comment attached to an action. The runtime freezes an\napproval request, admits decisions only from transport-authenticated actors,\nresolves their authority from deployment-owned data, and computes a terminal\nstate from stored policy and verified votes. An approved action still has to\nexecute and may still fail at the provider.\n\nThis page describes the reviewed Rust implementation of AIP 1.0 at source\nrevision `97be86e9efedf07ecf1783b03800f683f107fb04`. It explains the\nimplemented lifecycle and trust boundaries, not a claim that a particular\ndeployment has configured an external policy system or qualified every\napproval path.\n\n## Keep policy, request, decision, and authorization separate\n\nFour objects participate in an approval workflow:\n\n| Object | Created by | What it establishes |\n|---|---|---|\n| `ApprovalPolicy` | Capability owner | The required authority, evidence, time limit, vote rule, and separation-of-duties settings |\n| `ApprovalRequest` | Runtime | The immutable action, identity, risk, policy snapshot, and hash being governed |\n| `ApprovalDecision` | Approver-facing client or system | A claimed outcome, reason, constraints, evidence, stable decision ID, and policy hash |\n| `ApprovalRecord` | Approval backend | The request, every resolver-verified decision, computed status, terminal decision, and timestamps |\n\nThe decision object is not authorization by itself. Before storing it, the\nruntime binds its `approver` to a transport-established identity and attaches\nan `AuthorityMembership` returned by a trusted resolver. The resulting\n`VerifiedApprovalDecision` also carries a hash over the canonical decision,\nthe authority membership, and the resolver revision.\n\nThe complete approved `ApprovalRecord` is the durable authorization. Runtime\nexecution uses it to construct a non-serializable `VerifiedApprovalSet` for a\nconnector attempt. A remote dispatcher transports the record only as signed\ngateway-to-host route metadata.\n\n```mermaid\nflowchart LR\n    P[\"Capability approval policy\"] --> R[\"Frozen approval request\"]\n    R --> D[\"Authenticated decision\"]\n    D --> A[\"Trusted authority resolution\"]\n    A --> V[\"Verified decision journal\"]\n    V -->|\"rule satisfied\"| X[\"Approved authorization\"]\n    V -->|\"deny, expire, or valid revoke\"| T[\"Terminal rejection\"]\n    X --> E[\"Resume queued action\"]\n    X --> H[\"Signed remote-host import\"]\n```\n\n## A capability declares the approval policy\n\nAn `ApprovalPolicy` first states whether approval is required. When it is, the\npolicy can provide a reason, a request TTL, required evidence, a stable policy\nversion, a legacy approver selector, an optional composable rule, a minimum\nnumber of distinct principals, and separation-of-duties controls.\n\nThe legacy `approver_selector` selects one of these authority classes:\n\n| Selector | Trusted membership used for the match |\n|---|---|\n| `principal` | Exact principal ID |\n| `role` | Role name |\n| `group` | Group name |\n| `tenant_policy` | A tenant policy membership, or tenant membership under the legacy selector |\n| `external_system` | External system identifier |\n\nThe optional `rule` provides a more precise expression:\n\n| Rule | Evaluation behavior |\n|---|---|\n| `all` | Every child must match; by default, one decision or one principal cannot satisfy multiple children |\n| `any` | At least one child must match |\n| `quorum` | At least `required` matching decisions must exist; distinct principals are required by default |\n| `principal`, `role`, `group` | Matches the corresponding trusted authority membership |\n| `tenant_policy` | Matches one exact tenant policy ID |\n| `external_system` | Matches one exact external system ID |\n| `delegated_authority` | Matches a live, non-revoked grant with the requested scope and applicable risk or value limits |\n\n`all.allow_decision_reuse` can explicitly permit reuse across child rules.\nIndependent of the expression, `minimum_distinct_principals` is clamped to at\nleast one and must be met before the record can become approved.\n\nRule leaves never trust roles, groups, policy names, external systems, or\ndelegated grants carried only in a decision or action payload. Those values\ncome from the authority resolver.\n\n## The runtime freezes what is being approved\n\nWhen authorization requires human approval and the action has no approved\ndecision, the runtime creates a pending request and queues the action as\n`requires_human`. The request records:\n\n| Frozen value | Purpose |\n|---|---|\n| Approval, action, and capability IDs | Bind the workflow to one invocation and one capability |\n| Requester, subject, and operator | Support ownership, audit, and separation-of-duties checks |\n| Resolved identity context | Retain the tenant and represented identity used for the decision |\n| Capability risk and governed numeric value | Bound delegated authority when those values are available |\n| Policy snapshot and version | Preserve the rule that terminal evaluation will reproduce |\n| Expiration time | Close a pending request after its policy TTL |\n| Policy hash | Bind the policy and governed subject to later decisions |\n\nThe policy hash is SHA-256 over a canonical object containing the full policy,\naction ID, capability ID, requester ID, represented subject ID, and an input\nhash. The input hash covers capability ID, action input, and transaction\ncontext. A decision submitted for a request with a policy hash has to carry the\nsame hash.\n\nThe request can contain evidence references, but it does not copy the raw\naction input. If the policy requires an input snapshot, the runtime stores a\nredacted `input_snapshot` artifact with the canonical input hash. If it\nrequires a policy decision, the runtime stores a hash of the action ID,\ncapability ID, and approval policy.\n\nThe governed value is a bounded implementation convention, not a general\nexpression evaluator. The reviewed runtime looks for the first unsigned\ninteger at `/amount_minor`, `/amount`, `/value`, or `/total_minor`.\n\n## A decision becomes a verified vote\n\nDecision admission follows a fail-closed sequence:\n\n1. The message context has to contain a valid transport-established actor.\n2. The claimed approver ID and kind have to match that actor; the runtime then\n   replaces the payload principal with the authenticated principal.\n3. The runtime loads the durable approval request and resolves authority for\n   the approval ID, actor, and request tenant.\n4. The returned membership has to belong to the actor, remain unexpired and\n   non-revoked, and match the approval tenant when the request is tenant-bound.\n5. The decision has to identify the same approval, carry a non-empty stable\n   `decision_id`, and match the request policy hash. System-generated expiry\n   bypasses the ordinary client admission path but receives its own stable\n   decision ID and trusted runtime authority.\n6. Authority, separation of duties, evidence, revocation target, and supported\n   input constraints are checked before the vote is stored.\n7. The backend appends the verified decision and evaluates the full record\n   atomically.\n\nWhen the registered capability is available during decision admission, its\ncurrent approval policy supplies the authority, evidence, and\nseparation-of-duties checks. Terminal rule evaluation uses the request's stored\n`policy_snapshot`. This makes the snapshot reproducible while also making\ncapability-policy changes during an open request an operational concern.\n\nThe runtime derives `authority_path` from trusted membership: principal,\ntenant, roles, groups, tenant policies, external systems, and the authority\nrevision. It does not retain an authority path asserted by the client.\n\n### Separation of duties\n\nThe policy can prohibit the requester or operator from approving. By default,\nself-approval is not allowed when the requester and approver are the same;\n`allow_self_approval` has to permit it and no stricter requester rule may\nforbid it. `operator_must_differ` independently protects actions operated by a\nprincipal other than the represented subject.\n\n### Required evidence\n\nThe implemented evidence requirements are:\n\n| Requirement | Satisfied by |\n|---|---|\n| `reason` | A non-empty decision reason |\n| `input_snapshot` | An artifact of kind `input_snapshot` on the request or decision |\n| `policy_decision` | A stored policy decision ID or an artifact of kind `policy_decision` |\n| `external_ticket` | An artifact of kind `external_ticket` |\n| `attachment` | An artifact of kind `attachment` |\n\nAn evidence artifact can contain an ID, kind, URI, integrity hash, and redaction\nflag. The generic runtime verifies the required kind or reason. Retrieval,\nmalware scanning, ticket validity, and external-system semantics belong to the\ndeployment integration.\n\n### Decision constraints\n\nAn approved decision can constrain fields in the queued action input. A field\nis interpreted as a JSON Pointer; a name without a leading slash is converted\nto a top-level pointer. The reviewed runtime implements these operators:\n\n| Operator | Supported comparison |\n|---|---|\n| `eq`, `neq` | JSON value equality or inequality |\n| `lt`, `lte`, `gt`, `gte` | Numeric comparison through JSON numbers |\n| `contains` | String contains string |\n| `max_length`, `min_length` | Unicode character count against an unsigned integer |\n| `in` | Actual JSON value occurs in the supplied array |\n\nA missing field, incompatible value type, failed comparison, or unknown\noperator rejects the decision. The check runs against the durable queued\naction before an approved vote is admitted. Connectors receive the same resumed\naction; the generic constraint layer is not an arbitrary policy language and\ndoes not define provider-specific conditions.\n\n## The record computes one terminal state\n\nAn approval starts as `pending`. Approved votes accumulate until both the\nglobal distinct-principal minimum and the selected rule are satisfied. A vote\nthat can contribute to one leaf may still leave a quorum or `all` expression\npending.\n\nThe evaluator gives terminal rejection outcomes precedence over approvals:\n\n1. any admitted `revoked` decision yields `revoked`;\n2. otherwise any `expired` decision yields `expired`;\n3. otherwise any `denied` decision yields `denied`;\n4. otherwise the approval rule determines `approved` or leaves the record\n   `pending`.\n\nThe backend changes status and selects the terminal decision in the same locked\nrecord update. Once terminal, it rejects a new decision. Reusing a stable\ndecision ID with different verified content is also rejected.\n\nThis terminal rule narrows the meaning of revocation in the reviewed\nimplementation. A `revoked` decision has to name an already admitted approved\ndecision, but it can be admitted only while the overall record remains pending,\nsuch as during a multi-vote workflow. It does not revoke an already terminal\napproved record or undo provider work that has executed.\n\nTTL expiry is a runtime-owned lifecycle transition. The expiry worker selects\npending records past `expires_at`, records a system decision with trusted\nruntime authority, and publishes the same durable terminal transition used by\nother outcomes.\n\n## Terminal publication is recoverable\n\nA terminal record is not considered fully published merely because its status\nchanged. The runtime claims a fenced approval-transition lease, writes the\ncanonical receipt and event, then either resumes or terminates the queued\naction. It marks the transition complete only while it still owns the fencing\ntoken.\n\nIf a publisher stops between settlement and publication, a recovery worker can\nclaim the incomplete transition and repeat the publication path. Stable event\nand receipt IDs make their durable writes idempotent.\n\n| Terminal status | Queued-action effect |\n|---|---|\n| `approved` | Atomically lease the `requires_human` action, attach the terminal decision, and re-enter normal action processing |\n| `denied` | Persist and settle a failed action result with `policy.approval_denied` |\n| `expired` | Persist a failed result with `policy.approval_expired` and expire the queue record |\n| `revoked` | Persist and settle a failed result with `policy.approval_revoked` |\n\nResuming is authorization to attempt the original action, not evidence that the\nattempt completed. Normal schema, capability, transaction, idempotency,\ncredential, timeout, connector, and provider failure paths still apply.\n\n## Execution rechecks durable authorization\n\nAn action carrying `Action.approval` passes the runtime gate only when the\ndecision is `approved` and exactly matches a durable record whose status is\n`approved`. The record request must also match the current action ID and\ncapability ID.\n\nThe runtime then projects three compact indexes into `VerifiedApprovalSet`:\napproval IDs, available decision IDs, and policy hashes. It also includes the\ncomplete durable authorization. This execution context is trusted,\nprocess-local data; it is not part of the public AIP schema and is not accepted\nfrom an untrusted action payload.\n\n## Remote connector hosts import the verified record\n\nA remote connector host may not share the control-plane approval database. The\nremote dispatcher therefore serializes the complete approved authorization\nunder `approval_authorization` in the pinned connector route. That route travels\ninside the gateway-signed AIP envelope.\n\nBefore the host invokes its local gateway, it:\n\n1. verifies that the envelope signer and sender match its configured central\n   gateway and that the recipient and route match the admitted host;\n2. requires approval authorization for an action carrying an approval and\n   rejects unused authorization on an action without one;\n3. decodes the record and matches its terminal decision, approval ID, action\n   ID, capability ID, tenant, and policy hash;\n4. reconstructs the record from its resolver-verified decisions and requires\n   the stored policy to reproduce the approved terminal state;\n5. imports the exact record atomically, accepting only an identical existing\n   record and rejecting conflicting durable state;\n6. invokes the local gateway only after the import succeeds.\n\nThe record-validation method deliberately does not authenticate its containing\ntransport. The connector host's configured gateway signature and pinned route\nprovide that boundary. A storage failure returns a retryable unavailable error;\na conflicting authorization returns a non-retryable authorization error.\n\nThis design lets an isolated host verify policy state without direct access to\nthe central approval store. Its trade-off is that the signed envelope carries a\nlarger authorization object and the host explicitly trusts the configured\ngateway for the routed action bytes.\n\n## Read approval state without exposing input by default\n\nThe operational read model can return the original request, terminal decision,\nstatus, timestamps, and optionally linked action status or the approval receipt\nchain. Approval reads pass through transport identity, ownership, tenant,\nscope, delegation, and sensitive-field authorization.\n\nRaw governed input remains in the durable queued action. A caller has to request\n`include_evidence_payload` explicitly. The export path requires approval\nvisibility plus `approval:export` and `approval:sensitive`; it then derives a\npayload containing action ID, capability ID, input hash, exact input,\ntransaction context, and resolved identity. Ordinary approval views, receipts,\nevents, and callbacks do not receive that payload.\n\n## Trust and data boundaries\n\n| Boundary | Trusted input | Rejected assumption |\n|---|---|---|\n| Client to runtime | Authenticated transport actor | Payload `approver`, role, group, tenant, or authority path proves authority |\n| Runtime to authority resolver | Approval ID, authenticated actor, request tenant | Any resolver result is valid without actor, tenant, expiry, or revocation checks |\n| Request to decision | Durable IDs, policy hash, evidence, queued input | A comment or stale approval authorizes changed work |\n| Approval backend | Atomic record update and fenced publication state | Multiple votes can race safely without storage coordination |\n| Gateway to remote host | Configured gateway signature and pinned connector route | A self-consistent approval record authenticates its own transport |\n| Runtime to connector | Non-serializable verified execution context | A raw `Action.approval` is equivalent to verified authorization |\n| Read model to operator | Scoped approval view and explicit sensitive export | Approval visibility automatically grants raw input access |\n\nApproval receipts and evidence hashes make later inspection possible, but they\ndo not replace secure storage, key management, resolver governance, or external\naudit retention. The chosen approval backend also determines whether records\nand transition claims survive process loss; an in-memory backend does not\nprovide process durability.\n\n## Design choices and trade-offs\n\n- Immutable requests and hashes prevent a decision from silently authorizing a\n  different action, at the cost of creating a new workflow when governed intent\n  changes.\n- Resolver-owned authority avoids trusting client-supplied roles and grants, at\n  the cost of operating a current, fail-closed membership source.\n- Composite rules and atomic vote evaluation support independent review, at\n  the cost of more durable state and careful decision identity management.\n- A small constraint vocabulary is deterministic and auditable, but it cannot\n  express every provider policy.\n- Fenced terminal publication supports crash recovery without treating a\n  status write as completed side effects, at the cost of a separate recovery\n  path.\n- Signed full-record import keeps remote hosts isolated from the control-plane\n  database, at the cost of trusting one configured gateway boundary.\n\n## What approvals do not establish\n\nThe reviewed implementation does not establish any of the following:\n\n- that an `external_system` membership contacts or validates an external policy\n  product;\n- that arbitrary constraint operators or provider-specific policy expressions\n  are supported;\n- that an approved record proves connector invocation or provider completion;\n- that approval replaces transaction planning, idempotency, credential,\n  tenancy, or capability authorization checks;\n- that a terminal approval can later be revoked through the same decision\n  journal;\n- that evidence references have been retrieved or independently validated;\n- that every deployment uses a process-durable approval backend;\n- that approval behavior has been live-qualified for every connector.\n\nApproval also differs from escalation. Approval decides whether one frozen\noperation may proceed. Escalation asks another actor to supply input, take over,\nor resolve a broader exception; it has its own lifecycle and resolution model.\n\n## Related documentation\n\n- [Identity and trust](identity-and-trust.md) explains how transport identity,\n  tenant membership, and deployment-owned enrichment become trusted context.\n- [Actions and sessions](actions-and-sessions.md) explains the queued action,\n  idempotency, status, and recovery surfaces that surround approval.\n- [Transactions and compensation](transactions-and-compensation.md) owns plan,\n  commit, reconciliation, and compensation semantics.\n- [Observe and recover](../guides/observe-and-recover.md) covers operational\n  status, events, receipts, and recovery workflows.\n- [HTTP API](../reference/http-api.md) owns concrete approval query and decision\n  transport syntax.\n",
    "text": "Approvals and policy\n\nUse this page to understand how an AIP capability can pause an action for\napproval and later resume the same queued action from durable, verified\nauthorization. It is for application, runtime, and connector developers who\nneed to preserve a policy decision across retries, restarts, or a remote\nconnector-host boundary.\n\nAn approval is not a comment attached to an action. The runtime freezes an\napproval request, admits decisions only from transport-authenticated actors,\nresolves their authority from deployment-owned data, and computes a terminal\nstate from stored policy and verified votes. An approved action still has to\nexecute and may still fail at the provider.\n\nThis page describes the reviewed Rust implementation of AIP 1.0 at source\nrevision 97be86e9efedf07ecf1783b03800f683f107fb04. It explains the\nimplemented lifecycle and trust boundaries, not a claim that a particular\ndeployment has configured an external policy system or qualified every\napproval path.\n\nKeep policy, request, decision, and authorization separate\n\nFour objects participate in an approval workflow:\n\n| Object | Created by | What it establishes |\n\n| ApprovalPolicy | Capability owner | The required authority, evidence, time limit, vote rule, and separation-of-duties settings |\n| ApprovalRequest | Runtime | The immutable action, identity, risk, policy snapshot, and hash being governed |\n| ApprovalDecision | Approver-facing client or system | A claimed outcome, reason, constraints, evidence, stable decision ID, and policy hash |\n| ApprovalRecord | Approval backend | The request, every resolver-verified decision, computed status, terminal decision, and timestamps |\n\nThe decision object is not authorization by itself. Before storing it, the\nruntime binds its approver to a transport-established identity and attaches\nan AuthorityMembership returned by a trusted resolver. The resulting\nVerifiedApprovalDecision also carries a hash over the canonical decision,\nthe authority membership, and the resolver revision.\n\nThe complete approved ApprovalRecord is the durable authorization. Runtime\nexecution uses it to construct a non-serializable VerifiedApprovalSet for a\nconnector attempt. A remote dispatcher transports the record only as signed\ngateway-to-host route metadata.\n\nflowchart LR\n    P[\"Capability approval policy\"] --> R[\"Frozen approval request\"]\n    R --> D[\"Authenticated decision\"]\n    D --> A[\"Trusted authority resolution\"]\n    A --> V[\"Verified decision journal\"]\n    V -->|\"rule satisfied\"| X[\"Approved authorization\"]\n    V -->|\"deny, expire, or valid revoke\"| T[\"Terminal rejection\"]\n    X --> E[\"Resume queued action\"]\n    X --> H[\"Signed remote-host import\"]\n\nA capability declares the approval policy\n\nAn ApprovalPolicy first states whether approval is required. When it is, the\npolicy can provide a reason, a request TTL, required evidence, a stable policy\nversion, a legacy approver selector, an optional composable rule, a minimum\nnumber of distinct principals, and separation-of-duties controls.\n\nThe legacy approverselector selects one of these authority classes:\n\n| Selector | Trusted membership used for the match |\n\n| principal | Exact principal ID |\n| role | Role name |\n| group | Group name |\n| tenantpolicy | A tenant policy membership, or tenant membership under the legacy selector |\n| externalsystem | External system identifier |\n\nThe optional rule provides a more precise expression:\n\n| Rule | Evaluation behavior |\n\n| all | Every child must match; by default, one decision or one principal cannot satisfy multiple children |\n| any | At least one child must match |\n| quorum | At least required matching decisions must exist; distinct principals are required by default |\n| principal, role, group | Matches the corresponding trusted authority membership |\n| tenantpolicy | Matches one exact tenant policy ID |\n| externalsystem | Matches one exact external system ID |\n| delegatedauthority | Matches a live, non-revoked grant with the requested scope and applicable risk or value limits |\n\nall.allowdecisionreuse can explicitly permit reuse across child rules.\nIndependent of the expression, minimumdistinctprincipals is clamped to at\nleast one and must be met before the record can become approved.\n\nRule leaves never trust roles, groups, policy names, external systems, or\ndelegated grants carried only in a decision or action payload. Those values\ncome from the authority resolver.\n\nThe runtime freezes what is being approved\n\nWhen authorization requires human approval and the action has no approved\ndecision, the runtime creates a pending request and queues the action as\nrequireshuman. The request records:\n\n| Frozen value | Purpose |\n\n| Approval, action, and capability IDs | Bind the workflow to one invocation and one capability |\n| Requester, subject, and operator | Support ownership, audit, and separation-of-duties checks |\n| Resolved identity context | Retain the tenant and represented identity used for the decision |\n| Capability risk and governed numeric value | Bound delegated authority when those values are available |\n| Policy snapshot and version | Preserve the rule that terminal evaluation will reproduce |\n| Expiration time | Close a pending request after its policy TTL |\n| Policy hash | Bind the policy and governed subject to later decisions |\n\nThe policy hash is SHA-256 over a canonical object containing the full policy,\naction ID, capability ID, requester ID, represented subject ID, and an input\nhash. The input hash covers capability ID, action input, and transaction\ncontext. A decision submitted for a request with a policy hash has to carry the\nsame hash.\n\nThe request can contain evidence references, but it does not copy the raw\naction input. If the policy requires an input snapshot, the runtime stores a\nredacted inputsnapshot artifact with the canonical input hash. If it\nrequires a policy decision, the runtime stores a hash of the action ID,\ncapability ID, and approval policy.\n\nThe governed value is a bounded implementation convention, not a general\nexpression evaluator. The reviewed runtime looks for the first unsigned\ninteger at /amountminor, /amount, /value, or /totalminor.\n\nA decision becomes a verified vote\n\nDecision admission follows a fail-closed sequence:\n1. The message context has to contain a valid transport-established actor.\n2. The claimed approver ID and kind have to match that actor; the runtime then\n   replaces the payload principal with the authenticated principal.\n3. The runtime loads the durable approval request and resolves authority for\n   the approval ID, actor, and request tenant.\n4. The returned membership has to belong to the actor, remain unexpired and\n   non-revoked, and match the approval tenant when the request is tenant-bound.\n5. The decision has to identify the same approval, carry a non-empty stable\n   decisionid, and match the request policy hash. System-generated expiry\n   bypasses the ordinary client admission path but receives its own stable\n   decision ID and trusted runtime authority.\n6. Authority, separation of duties, evidence, revocation target, and supported\n   input constraints are checked before the vote is stored.\n7. The backend appends the verified decision and evaluates the full record\n   atomically.\n\nWhen the registered capability is available during decision admission, its\ncurrent approval policy supplies the authority, evidence, and\nseparation-of-duties checks. Terminal rule evaluation uses the request's stored\npolicysnapshot. This makes the snapshot reproducible while also making\ncapability-policy changes during an open request an operational concern.\n\nThe runtime derives authoritypath from trusted membership: principal,\ntenant, roles, groups, tenant policies, external systems, and the authority\nrevision. It does not retain an authority path asserted by the client.\n\nSeparation of duties\n\nThe policy can prohibit the requester or operator from approving. By default,\nself-approval is not allowed when the requester and approver are the same;\nallowselfapproval has to permit it and no stricter requester rule may\nforbid it. operatormustdiffer independently protects actions operated by a\nprincipal other than the represented subject.\n\nRequired evidence\n\nThe implemented evidence requirements are:\n\n| Requirement | Satisfied by |\n\n| reason | A non-empty decision reason |\n| inputsnapshot | An artifact of kind inputsnapshot on the request or decision |\n| policydecision | A stored policy decision ID or an artifact of kind policydecision |\n| externalticket | An artifact of kind externalticket |\n| attachment | An artifact of kind attachment |\n\nAn evidence artifact can contain an ID, kind, URI, integrity hash, and redaction\nflag. The generic runtime verifies the required kind or reason. Retrieval,\nmalware scanning, ticket validity, and external-system semantics belong to the\ndeployment integration.\n\nDecision constraints\n\nAn approved decision can constrain fields in the queued action input. A field\nis interpreted as a JSON Pointer; a name without a leading slash is converted\nto a top-level pointer. The reviewed runtime implements these operators:\n\n| Operator | Supported comparison |\n\n| eq, neq | JSON value equality or inequality |\n| lt, lte, gt, gte | Numeric comparison through JSON numbers |\n| contains | String contains string |\n| maxlength, minlength | Unicode character count against an unsigned integer |\n| in | Actual JSON value occurs in the supplied array |\n\nA missing field, incompatible value type, failed comparison, or unknown\noperator rejects the decision. The check runs against the durable queued\naction before an approved vote is admitted. Connectors receive the same resumed\naction; the generic constraint layer is not an arbitrary policy language and\ndoes not define provider-specific conditions.\n\nThe record computes one terminal state\n\nAn approval starts as pending. Approved votes accumulate until both the\nglobal distinct-principal minimum and the selected rule are satisfied. A vote\nthat can contribute to one leaf may still leave a quorum or all expression\npending.\n\nThe evaluator gives terminal rejection outcomes precedence over approvals:\n1. any admitted revoked decision yields revoked;\n2. otherwise any expired decision yields expired;\n3. otherwise any denied decision yields denied;\n4. otherwise the approval rule determines approved or leaves the record\n   pending.\n\nThe backend changes status and selects the terminal decision in the same locked\nrecord update. Once terminal, it rejects a new decision. Reusing a stable\ndecision ID with different verified content is also rejected.\n\nThis terminal rule narrows the meaning of revocation in the reviewed\nimplementation. A revoked decision has to name an already admitted approved\ndecision, but it can be admitted only while the overall record remains pending,\nsuch as during a multi-vote workflow. It does not revoke an already terminal\napproved record or undo provider work that has executed.\n\nTTL expiry is a runtime-owned lifecycle transition. The expiry worker selects\npending records past expiresat, records a system decision with trusted\nruntime authority, and publishes the same durable terminal transition used by\nother outcomes.\n\nTerminal publication is recoverable\n\nA terminal record is not considered fully published merely because its status\nchanged. The runtime claims a fenced approval-transition lease, writes the\ncanonical receipt and event, then either resumes or terminates the queued\naction. It marks the transition complete only while it still owns the fencing\ntoken.\n\nIf a publisher stops between settlement and publication, a recovery worker can\nclaim the incomplete transition and repeat the publication path. Stable event\nand receipt IDs make their durable writes idempotent.\n\n| Terminal status | Queued-action effect |\n\n| approved | Atomically lease the requireshuman action, attach the terminal decision, and re-enter normal action processing |\n| denied | Persist and settle a failed action result with policy.approvaldenied |\n| expired | Persist a failed result with policy.approvalexpired and expire the queue record |\n| revoked | Persist and settle a failed result with policy.approvalrevoked |\n\nResuming is authorization to attempt the original action, not evidence that the\nattempt completed. Normal schema, capability, transaction, idempotency,\ncredential, timeout, connector, and provider failure paths still apply.\n\nExecution rechecks durable authorization\n\nAn action carrying Action.approval passes the runtime gate only when the\ndecision is approved and exactly matches a durable record whose status is\napproved. The record request must also match the current action ID and\ncapability ID.\n\nThe runtime then projects three compact indexes into VerifiedApprovalSet:\napproval IDs, available decision IDs, and policy hashes. It also includes the\ncomplete durable authorization. This execution context is trusted,\nprocess-local data; it is not part of the public AIP schema and is not accepted\nfrom an untrusted action payload.\n\nRemote connector hosts import the verified record\n\nA remote connector host may not share the control-plane approval database. The\nremote dispatcher therefore serializes the complete approved authorization\nunder approvalauthorization in the pinned connector route. That route travels\ninside the gateway-signed AIP envelope.\n\nBefore the host invokes its local gateway, it:\n1. verifies that the envelope signer and sender match its configured central\n   gateway and that the recipient and route match the admitted host;\n2. requires approval authorization for an action carrying an approval and\n   rejects unused authorization on an action without one;\n3. decodes the record and matches its terminal decision, approval ID, action\n   ID, capability ID, tenant, and policy hash;\n4. reconstructs the record from its resolver-verified decisions and requires\n   the stored policy to reproduce the approved terminal state;\n5. imports the exact record atomically, accepting only an identical existing\n   record and rejecting conflicting durable state;\n6. invokes the local gateway only after the import succeeds.\n\nThe record-validation method deliberately does not authenticate its containing\ntransport. The connector host's configured gateway signature and pinned route\nprovide that boundary. A storage failure returns a retryable unavailable error;\na conflicting authorization returns a non-retryable authorization error.\n\nThis design lets an isolated host verify policy state without direct access to\nthe central approval store. Its trade-off is that the signed envelope carries a\nlarger authorization object and the host explicitly trusts the configured\ngateway for the routed action bytes.\n\nRead approval state without exposing input by default\n\nThe operational read model can return the original request, terminal decision,\nstatus, timestamps, and optionally linked action status or the approval receipt\nchain. Approval reads pass through transport identity, ownership, tenant,\nscope, delegation, and sensitive-field authorization.\n\nRaw governed input remains in the durable queued action. A caller has to request\nincludeevidencepayload explicitly. The export path requires approval\nvisibility plus approval:export and approval:sensitive; it then derives a\npayload containing action ID, capability ID, input hash, exact input,\ntransaction context, and resolved identity. Ordinary approval views, receipts,\nevents, and callbacks do not receive that payload.\n\nTrust and data boundaries\n\n| Boundary | Trusted input | Rejected assumption |\n\n| Client to runtime | Authenticated transport actor | Payload approver, role, group, tenant, or authority path proves authority |\n| Runtime to authority resolver | Approval ID, authenticated actor, request tenant | Any resolver result is valid without actor, tenant, expiry, or revocation checks |\n| Request to decision | Durable IDs, policy hash, evidence, queued input | A comment or stale approval authorizes changed work |\n| Approval backend | Atomic record update and fenced publication state | Multiple votes can race safely without storage coordination |\n| Gateway to remote host | Configured gateway signature and pinned connector route | A self-consistent approval record authenticates its own transport |\n| Runtime to connector | Non-serializable verified execution context | A raw Action.approval is equivalent to verified authorization |\n| Read model to operator | Scoped approval view and explicit sensitive export | Approval visibility automatically grants raw input access |\n\nApproval receipts and evidence hashes make later inspection possible, but they\ndo not replace secure storage, key management, resolver governance, or external\naudit retention. The chosen approval backend also determines whether records\nand transition claims survive process loss; an in-memory backend does not\nprovide process durability.\n\nDesign choices and trade-offs\n• Immutable requests and hashes prevent a decision from silently authorizing a\n  different action, at the cost of creating a new workflow when governed intent\n  changes.\n• Resolver-owned authority avoids trusting client-supplied roles and grants, at\n  the cost of operating a current, fail-closed membership source.\n• Composite rules and atomic vote evaluation support independent review, at\n  the cost of more durable state and careful decision identity management.\n• A small constraint vocabulary is deterministic and auditable, but it cannot\n  express every provider policy.\n• Fenced terminal publication supports crash recovery without treating a\n  status write as completed side effects, at the cost of a separate recovery\n  path.\n• Signed full-record import keeps remote hosts isolated from the control-plane\n  database, at the cost of trusting one configured gateway boundary.\n\nWhat approvals do not establish\n\nThe reviewed implementation does not establish any of the following:\n• that an externalsystem membership contacts or validates an external policy\n  product;\n• that arbitrary constraint operators or provider-specific policy expressions\n  are supported;\n• that an approved record proves connector invocation or provider completion;\n• that approval replaces transaction planning, idempotency, credential,\n  tenancy, or capability authorization checks;\n• that a terminal approval can later be revoked through the same decision\n  journal;\n• that evidence references have been retrieved or independently validated;\n• that every deployment uses a process-durable approval backend;\n• that approval behavior has been live-qualified for every connector.\n\nApproval also differs from escalation. Approval decides whether one frozen\noperation may proceed. Escalation asks another actor to supply input, take over,\nor resolve a broader exception; it has its own lifecycle and resolution model.\n\nRelated documentation\n• Identity and trust (identity-and-trust.md) explains how transport identity,\n  tenant membership, and deployment-owned enrichment become trusted context.\n• Actions and sessions (actions-and-sessions.md) explains the queued action,\n  idempotency, status, and recovery surfaces that surround approval.\n• Transactions and compensation (transactions-and-compensation.md) owns plan,\n  commit, reconciliation, and compensation semantics.\n• Observe and recover (../guides/observe-and-recover.md) covers operational\n  status, events, receipts, and recovery workflows.\n• HTTP API (../reference/http-api.md) owns concrete approval query and decision\n  transport syntax.\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "06663f9db26fa0e6e2c42c112678e0fc167dea54f4b92babe0b9d477029ab4d7"
  }
}
