The problem in the field
I spent a career handing evidence to people who had every reason to doubt it. Chain of custody was the answer: who had it, when, and what they did with it, written down in a way a stranger could check. Agents are now doing things that used to need a person with a badge. Unlocking a door. Moving a camera. Tagging a clip that ends up in a case file. The question I keep coming back to is what must be independently provable about what an agent saw, inferred, was authorized to do, and actually did, before its output triggers physical-world action or becomes evidence.
Why the usual answers fail
The audit trail lives in the platform that did the work, so a compromised or careless platform grades its own homework. Reading the trail often needs an account and a license from the vendor. And JSON logs are easy to write and hard to sign, because two encoders will serialize the same record two ways.
What I built
A small wire spec and a reference verifier. Receipts are deterministic CBOR with a detached COSE_Sign1 signature (ES256), closed schemas, and normative CDDL. The verifier, pyref, is Python 3.11 standard library only. No network, no wall clock, no dependencies. Verifying receipts is free, forever, offline, with no account and no license from anyone. The conformance vectors are byte-pinned known-answer tests generated by a second, clean-room implementation in TypeScript, which I also wrote, and the threat model, v0.1, lists 30 findings. The reference producer is onvif-mcp. Since October 12 it writes v0.2.3, and its first two live bundles, snapshots from two of my cameras, verify as conformant in pyref. Its older receipts stay in the August pre-release format they were signed in.
How it works
A producer signs each receipt and links it to the previous one. The verifier runs twenty checks in a fixed order, from the size of the bundle to the signed verdict, and names the first one that fails. This is a test bundle from the repo, so you can run it yourself:
$ python3 -B -m pyref verify kats/positive/bundle-valid-subset.cbor --at 1735689800 AAR offline verification report Bundle: kats/positive/bundle-valid-subset.cbor Evaluation time: 1735689800 Network: not used (all inputs are local) Trust policy: producer-declared only Result: conformant Steps: 01 PASS Bundle byte limit 02 PASS Bundle CBOR 03 PASS Bundle schema 04 PASS Static resource counts 05 PASS Trust-policy input 06 PASS Envelope mechanics 07 PASS Content commitments and IDs 08 PASS Credential lifecycle 09 PASS Emission identity 10 PASS Receipt schema semantics 11 PASS Replay and freshness 12 PASS Referential closure and graph 13 PASS Authorization dominance 14 PASS Epoch state machine 15 PASS Manifest index 16 PASS Merkle batches 17 PASS Anchors 18 PASS Bundle ranges and coverage 19 PASS Evidence-class qualification 20 PASS Verdict First failure: none
What that gives you: attribution to a signer, tamper evidence after emission, and tamper evidence over the declared contents of an epoch. What it does not give you: it can't stop a compromised signer from lying before it signs, it can't prove an inference was correct, and it doesn't establish legal compliance with anything. I'd rather say that here than have someone find out in a hearing.
Where it stands
A stable experimental spec, not an adopted standard. Both implementations are mine, so nobody independent has built one yet, and that gap matters, because the best bug I've found so far came from a cross-check. The reference CBOR encoder wrote every unsigned integer between 2^24 and 2^32 wrong. That range covers every real Unix timestamp. All 188 fixtures stayed green because they came from the same encoder. The clean-room second implementation caught it on its first pass. One implementation agreeing with itself proves nothing. The two still differ. An October 8 audit found 40 of 232 test fixtures where they sign different values in a failure verdict, and one where they disagree on the result itself. Closing that gap blocks a v1.0.
What's next
v0.2.3 is tagged, as of October 12. Since v0.2.0 in September the wire has had three numbered changes, each closing a hole a review found: the signed verdict now commits to the prior history it was judged against (v0.2.1), every signed object has to be signed by the key it names (v0.2.2), and a receipt can declare where an action's inputs came from, such as a camera, the operator or a third party, which the verifier passes on as a stated limit rather than a fact (v0.2.3). The conformance suite runs in CI on every change. What's left is the part I can't do myself: one external producer emitting receipts that pyref verifies without my help. That external run, or an acknowledgement from the people writing the adjacent standards, is the signal I'm watching for. The wire may still change in response to it.
- github.com/oneshot2001/aar
- onvif-mcp, the reference producer