Skip to main content
A receipt is the portable JSON record of one action that reached the executor: who proposed it, what it was, what the policy and authority decided, which approval it used, which effect key it reserved, how it ended, and the hash and declared version of the policy that decided it. Every executed action leaves one. Every step on the way, proposed, decided, approved, reserved, committed, refused, leaves an event.

Where they go

The store is authoritative: receipts and events are rows, and ctrlrun receipts, inspect and stats read them from there. A JSONLEventSink also appends each to .ctrlrun/receipts.jsonl and .ctrlrun/events.jsonl beside the store, one object per line, in order, so the evidence survives the process and the file can be shipped anywhere. An OTelEventSink exports one span per action and one span event per step to a tracing backend, with argument values kept out unless you ask for them.
The field names above are the receipt’s; the values are the shape of a real one. The receipt and event schemas page lists every field with its type.

Which policy decided it

policy_hash is the SHA-256 of the policy’s canonical decision inputs: the rules, the mode, the environment and the authority grants, not the file’s bytes, so a reformat changes nothing and a changed rule changes everything. policy_version is the operator’s own label, recorded and never authoritative. A receipt from six months ago therefore says what the rules were rather than what they are now, and where a policy changes between a human approving and an agent executing, the approval is re-checked against the policy in force at execution.

The chain

Each receipt carries seq and prev_hash, the hash of the receipt before it. An edit to a receipt, a deletion from the middle, a reordering: each is detected and named by seq with ctrlrun receipts --verify-chain. The chain detects alteration. It does not prove authorship: receipts are not signed, and an administrator who can rewrite every row including the chain head can rewrite history undetected. The receipt chain says exactly what it covers and what it does not.

The guarantee it supports

Receipts: G11 (an altered receipt is detected) in ctrlrun verify, and every demo receipt is asserted to carry every field the specification names.

What it does not do

A receipt records what CTRLRun saw, not what the remote did after the reply was lost; an AMBIGUOUS outcome is recorded as ambiguous, and the resolution, when it comes, is a further event naming who resolved it. A receipt that failed to write leaves no gap in seq; the events log is where that is reconciled. And receipts are evidence, not a dashboard: there is no UI, by design.

Next