Receipts
A receipt is a "tamper-evident" signed record, but it comes in five levels, and every receipt must declare for itself which level it reaches. An audit on 2026-09-20 found that "refused delivery" was only a flag — since then we have stopped saying "stopped at the exit" at all.
#The five-level vocabulary
On 2026-09-20, an internal audit found that in one batch of experiments, "refused delivery" was only a flag and an exit code — the outer script did not read the exit code, and copied the rejected files out anyway. Of 391 audited cells, 7 of the 59 refused-delivery cells had their files copied out and scored.
After that audit, the project switched to a five-level vocabulary to describe "what the system has proven," and requires each level to declare its own coverage.
| Level | What the system has proven | Status as of 2026-09 |
|---|---|---|
| Observed | It saw certain behavior, traffic or artifacts | Holds: the conversation seen by mediation has no gaps (all 2,755 rounds match up, 195/195 chains verify) |
| Decided | It independently judged whether this delivery passes or not | Holds: when the agent claims it is done, the system can still rule refused delivery and sign it |
| Withheld | An artifact that did not pass was not sent out | Does not hold by default; filled in by separating the release / publish capabilities |
| Mediated | The agent cannot find an alternative path that bypasses the system | Does not hold by default; holds experimentally with the bwrap sandbox wall |
| Attested | The facts above are bound tamper-proof to the artifact, the policy and the identity | Partly holds: expressed as receipt levels A / B / B′ / C |
The benefit of this vocabulary is that it breaks up binary statements like "is there a gate or not." It's not that we have no gate; it's that we cannot assume the gate holds the line.
#Event chain format
Each resident (participant) maintains an append-only event chain. Every event contains exactly eight fields:
{
"stream_id": "z…",
"branch_id": "…",
"seq": 42,
"prev_hash": "…",
"ts_ms": 1758…,
"type": "DELIVERED",
"payload": { },
"sig": "…"
}
Verification checks three things: sequence numbers are contiguous, the previous-entry hash matches, and the Ed25519 signature is valid.
#What a signature can prove, and what it can't
Every signature covers the bytes of canonical JSON (sorted keys, no extra whitespace, UTF-8), not a programming language's data structure — otherwise the bytes would not match when signatures are verified across machines.
#Three things chain verification cannot catch
These three are structural limits of the chain, not configuration problems.
| Cannot catch | Why |
|---|---|
| Truncation | The chain has no trusted chain head. Cut off the tail and it still passes. |
| Forks | If the same party signs two chains with different contents for the same event, both verify. |
| Omissions | For an event that was never written down, no receipt can claim to be one of its entries. |
So the term for receipts is "tamper-evident," not "impossible to change afterwards."
#Measured: chain verification passes
| Batch | Chains | Entries | Result |
|---|---|---|---|
| R460R (five replications) | — | 6,674 | All passed |
| R529 (four sets across task banks) | — | 3,167 | All passed |
| Total | 194 | 9,841 | All passed independent signature verification |
| R454 (multi-party execution, real run) | — | 5,519 attestations | 0 signature verification failures |
| E10 (after the channel-separation revision) | — | 180/180 reviewer envelopes | Still verifiable |
| ABPI audit | 195 | — | All passed |
Lying keys named: 273/273; honest participants falsely accused: 0/1,840
#Receipt levels
The level is decided by probe results, not by the inference "we installed it."
| Level | Condition | What the receipt can say |
|---|---|---|
| A | Sandbox wall in place, framework-hook probe triggered, 0 unexplained calls | Every model call went through Vacant, and each one matches a tool event |
| B | Sandbox wall in place, no hook | Every byte that left is recorded, but it can't tell which call was made by the model |
| B′ | The model channel is not (or not fully) in Vacant's view, but the tool layer is recorded | It can only account for tool-layer events |
| C | Neither | Refuses to issue a receipt |
Every coverage field is three-state (true / false / not measured). A value that was not measured must not be written as zero or false. The first A-level receipt for a real agent was signed on 2026-09-20 (pi 0.85.1, 0 unexplained calls).
#The exit: capability separation
"Ruled a refusal" is not the same as "impossible to publish." The exit problem is handled by capability separation:
- The
releasemodule mints a release credential only on a pass (its structure is taken from SLSA's verification summary attestation). - The
publishmodule is the only component with the ability to write outward. It verifies the credential's signature, the artifact hash, the verdict, the validity period and the coverage. publishdoes not read exit codes, and deliberately has no force-publish option.
From this the project wrote down a design principle:
A gate that can be bypassed will be bypassed on the day it is needed.
#The offline viewer's structural boundary
Our own single-file offline viewer (exhibit/receipts/receipt_viewer.html) embeds the receipts in the file, with zero external resources and zero network requests; even opened via file://, it runs the full chain verification.
But the R455 experiment found its limit:
If an attacker re-mints the whole chain with their own key and edits the embedded receipts to match, it still verifies.
This is a structural limit of the offline viewer, not a bug — a signature identifies a key, not a subject. The mitigation is to print the three vacant_ids on a physical label, so that there is an anchor outside that cannot be re-minted along with the chain.
#Next
- How receipts are produced: The acceptance gate
- Details of the signature and identity layer: Identity and the signed logbook
- How multiple parties point at each other: Multi-party execution

