Skip to content

ADR 0003: Signed execution grants bind policy decisions to executions

Status: Accepted, 2026-09-04

Context

The design requires that only the isolated executor changes simulated state and that the orchestrator cannot bypass the control gateway. In a single process, "cannot bypass" needs a mechanism, not a convention. Anything that can construct a proposal can also call an executor method unless the executor demands something only the gateway can produce.

Decision

The gateway issues an HMAC-SHA256 signed ExecutionGrant after the policy decision, obligations and approval binding succeed. The grant binds:

  • the proposal id and a hash of the proposal's incident, tool, arguments and identities;
  • the policy version that produced the decision;
  • the approval id, when one was required;
  • the obligations that were fulfilled;
  • an issue time and a short expiry (60 seconds by default).

The executor verifies the signature, the expiry, the proposal binding and the scope, and refuses to honor a grant id twice. Every refusal raises AuthorizationError, which the gateway records as failed_closed.

The signing key is shared between the gateway and the executor only. The orchestrator receives a ToolProposalPort and nothing else.

Consequences

  • Tampering with arguments between decision and execution invalidates the grant because the hash no longer matches.
  • Replaying a grant, reusing a consumed approval or presenting an expired approval all fail closed and appear in the gateway event log.
  • In the Docker profile the executor runs as a separate container with its own copy of the key from the environment, which makes the boundary a process boundary as well as a cryptographic one.
  • The grant is not a substitute for the audit chain. It proves authorization at execution time; the evidence store proves what happened afterwards.