1
Write the policy
In an empty directory, save this as Anything not listed here is denied. There is no default-allow.
ctrlrun.yaml. Amounts are integer minor units: cents,
not euros. Both ends of every band are bound, because an upper bound alone lets a negative
amount through, and a refund of a negative amount is a charge.runnable
2
Protect the function
Save this as Run it once with
agent.py. The decorator names the action and the effect key; the context names
who is acting. stripe here is a stand-in that records calls instead of making them.runnable file=agent.py
python agent.py:3
Approve it from the shell
The request id is what The grant names the hash it authorizes and when it lapses. Ids and hashes are generated per
run; yours differ.
ctrlrun approve takes. The approval is bound to the hash of the
exact action a human would see: stripe.refund, txn_2, €2,000, refund-agent.runnable
4
Present the approval, and try to abuse it
Save this as Run it with One call reached the fake remote in this process, the approved €2,000. The €5,000 never did.
approved.py. The first call presents the approval for the action it was granted
for and runs. The second presents the same approval for a different amount, which matches
nothing: the approval was bound to €2,000 and has already been spent.runnable file=approved.py
python approved.py:5
Read the receipts
runnable
ctrlrun inspect <action_id> shows
one action’s whole history. Both are in .ctrlrun/receipts.jsonl and .ctrlrun/events.jsonl
as one JSON object per line.What you just saw
- Per-action policy. €100 ran, €2,000 waited, €5,000 would have been denied outright.
- Approval binding. The approval matched the exact action it was granted for and nothing else.
- Effect keys.
refund:txn_2was reserved when the approved call ran; a second worker presenting the same key would have been refused. - Receipts. Everything above is in the evidence log, in order.
If it didn’t work
denied: no principal is available: the call ran outsidectrlrun.context(...). Every protected call needs a principal, and a missing one is denied.PolicyError: ... could not be read: there is noctrlrun.yamlin the working directory.ctrlrun initwrites a starter.ActionDenied ... unknown_action: the action name in the decorator does not match a key underactions:. Unknown actions are denied.
Next
- Three ways in: decorator, gateway, adapter.
- Effect keys: what the
effect=template names, and why it is not a request id. - Why.