The four invariants
What the human sees, and what executes
granted after a mismatch. It still authorizes the €2,000 action, and
only that, until it expires.
Where the yes comes from
ctrlrun approve <request id> writes the grant. So does a webhook answer from Slack, and so
does an adapter routing the request through LangGraph’s interrupt() or the OpenAI Agents SDK’s
tool-approval interruption: every path ends in the same two store calls, grant_approval and
deny_approval. There is never a second place to say yes, because a second approval path is
one nobody is watching.
Where a framework carries the arguments the human answered against, the adapter hands them
back and CTRLRun rebuilds the hash and compares: that is prevention. Where it carries only
the verdict, CTRLRun records who answered and cannot re-check what they answered about: that
is attribution, and the adapter’s page says which it is, in that word.
The guarantee it supports
Approval binding, verified as G1 (mutated approval refused) and G2 (replayed approval refused) byctrlrun verify against your own policy, wherever it has an approve rule.
What it does not do
An approval does not prove the human was right, or that the human was who they said; CTRLRun records the approver as given and authenticates nobody. It does not survive a policy change in one direction: if the policy now denies the action, the action is refused and the approval is left granted; if the policy now allows it outright, the action runs and the approval is invalidated so it cannot outlive the action it was for.Next
- Action and hash: what the approval binds to.
- Effect keys: the reservation it is consumed with.
- Approve in Slack · Get started · Why.