Receipt Verification
An auditor with a saved evidence package and the chio CLI can verify a kernel decision offline. No live trust-control. No app surface. The package is a directory of NDJSON files plus a manifest, and chio evidence verify re-checks every signed receipt and Merkle inclusion proof against the manifest.
Where the code lives
examples/hello-receipt-verify/. Run with ./smoke.sh. The example ships a checked-in fixture under fixtures/minimal-evidence/; no live services are needed.What It Shows
- Receipt verification from a static captured package, with no live kernel and no network calls.
- Local lineage inspection of the capability that produced the receipt.
- Tamper detection: an attacker who edits any file under the package directory breaks verification at the manifest hash check.
Stops at offline verification
chio evidence import. Import is intentionally stricter and requires a signed bilateral federation policy, so it belongs in a federation-focused example.Files
examples/hello-receipt-verify/
README.md
fixtures/minimal-evidence/
README.txt
capability-lineage.ndjson one line per capability on the chain
checkpoints.ndjson merkle checkpoint(s)
child-receipts.ndjson child-request receipts
inclusion-proofs.ndjson merkle inclusion proofs per receipt
manifest.json SHA-256 over every file in the package
query.json bundle metadata
receipts.ndjson one line per signed tool receipt
retention.json retention policy fingerprint
smoke.shRun It
# From the chio workspace root
cargo build --bin chio
cd examples/hello-receipt-verify
./smoke.shOn a successful run the smoke prints:
hello-receipt-verify smoke passed
artifacts: .../examples/hello-receipt-verify/.artifacts/<timestamp>
receipt id: <id>Phase 1: Load the Package
The smoke copies fixtures/minimal-evidence/ into two scratch directories under the run's artifact root: one untouched (input-package/) and one we will mutate later (tampered-package/).
cp -R "${EXAMPLE_ROOT}/fixtures/minimal-evidence" "${INPUT_DIR}"
cp -R "${EXAMPLE_ROOT}/fixtures/minimal-evidence" "${TAMPERED_DIR}"The manifest is the trust anchor for the package. It pins SHA-256 over every file plus the export-time counts the verifier asserts:
{
"schema": "chio.evidence_export_manifest.v1",
"exportedAt": 1776272775,
"query": {},
"counts": {
"toolReceipts": 1,
"childReceipts": 0,
"checkpoints": 0,
"capabilityLineage": 1,
"inclusionProofs": 0,
"uncheckpointedReceipts": 1
},
"files": [
{ "path": "receipts.ndjson", "sha256": "a8f22454...", "bytes": 968 },
{ "path": "capability-lineage.ndjson","sha256": "789ca683...", "bytes": 427 },
{ "path": "checkpoints.ndjson", "sha256": "e3b0c442...", "bytes": 0 },
{ "path": "child-receipts.ndjson", "sha256": "e3b0c442...", "bytes": 0 },
{ "path": "inclusion-proofs.ndjson", "sha256": "e3b0c442...", "bytes": 0 },
{ "path": "query.json", "sha256": "44136fa3...", "bytes": 2 },
{ "path": "retention.json", "sha256": "f92db6f4...", "bytes": 75 },
{ "path": "README.txt", "sha256": "e32367c0...", "bytes": 374 }
]
}Phase 2: Verify Offline
chio evidence verify opens the package directory, parses the manifest, rehashes every listed file, parses every receipt out of receipts.ndjson, verifies each signature against the kernel public key embedded in the package, and confirms every Merkle inclusion proof in inclusion-proofs.ndjson.
"${CHIO_BIN}" evidence verify --input "${INPUT_DIR}" --json \
> "${ARTIFACT_ROOT}/verify.json"Expected stdout from the verifier on the untouched fixture:
{
"verifiedFiles": 8,
"toolReceipts": 1,
"childReceipts": 0,
"checkpoints": 0,
"capabilityLineage": 1,
"inclusionProofs": 0
}The smoke parses the result and asserts the expected counts match the fixture: one tool receipt, one capability lineage record.
verify = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
receipt_record = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8").splitlines()[0])
lineage_record = json.loads(Path(sys.argv[3]).read_text(encoding="utf-8").splitlines()[0])
assert verify["toolReceipts"] == 1, verify
assert verify["capabilityLineage"] == 1, verifyPhase 3: Inspect the Receipt
Once verified, the smoke pulls a few load-bearing fields out of the receipt and the lineage record and writes them into summary.json:
summary = {
"example": "hello-receipt-verify",
"receipt_id": receipt_record["receipt"]["id"],
"capability_id": receipt_record["receipt"]["capability_id"],
"tool_name": receipt_record["receipt"]["tool_name"],
"subject_key": lineage_record["subject_key"],
"issuer_key": lineage_record["issuer_key"],
"verified": True,
}The receipt links to its capability through capability_id; the lineage record carries the subject and issuer keys. Cross-referencing them is what makes the package a complete evidence bundle: the auditor can prove that a specific capability signed a specific tool decision under a specific issuer key.
Phase 4: Tamper Check
The smoke writes a single rogue byte sequence into tampered-package/query.json and runs chio evidence verify against the modified directory. Verification fails closed:
Path(sys.argv[1]).write_text('{"tampered":true}\n', encoding="utf-8")
if "${CHIO_BIN}" evidence verify --input "${TAMPERED_DIR}" --json \
> "${ARTIFACT_ROOT}/tamper-out.json" \
2> "${ARTIFACT_ROOT}/tamper-err.json"; then
echo "expected tampered package verification to fail" >&2
exit 1
fiThe smoke then asserts the structured CLI error includes the manifest hash mismatch, not a generic decode failure:
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
assert payload["code"] == "CHIO-CLI-OTHER", payload
assert "hash mismatch" in payload["message"] or "hash mismatch" in payload["context"]["detail"], payloadReproduce the failure by hand: copy the fixture, write one byte into any file the manifest covers, run chio evidence verify again. The CLI exits non-zero with a structured error:
cp -R fixtures/minimal-evidence /tmp/tampered
echo '{"tampered":true}' > /tmp/tampered/query.json
chio evidence verify --input /tmp/tampered --json
# stderr:
# {
# "code": "CHIO-CLI-OTHER",
# "message": "evidence verification failed",
# "context": {
# "detail": "hash mismatch for query.json: expected 44136fa3..., got <new>"
# }
# }
# exit 1Manifest is the auditor's trust anchor
manifest.json contains SHA-256 of every file in the package. The kernel pre-signs the receipts, but the manifest is what stops a clever attacker from replacing one record with another while keeping every individual signature intact: the manifest hash chain catches the tamper.Auditor Workflow
Real audits work the same way: pull a captured evidence package out of cold storage, run chio evidence verify, and read the receipts. No live kernel access, no trust-control connectivity, no shared secrets beyond the kernel's long-lived public key. A typical session:
# Pull the package from cold storage
tar -xzf evidence-2026-04-15.tar.gz -C ./review
# Verify offline
chio evidence verify --input ./review/evidence-2026-04-15 --json | jq '.'
# Inspect the receipts
cat ./review/evidence-2026-04-15/receipts.ndjson | jq -c '{id: .receipt.id, tool: .receipt.tool_name, verdict: .receipt.verdict}'
# Inspect the capability that authorized them
cat ./review/evidence-2026-04-15/capability-lineage.ndjson | jq '.'
# Spot-check the manifest if you want: each entry is the SHA-256 the verifier rehashes
cat ./review/evidence-2026-04-15/manifest.json | jq '.files'For audits that span multiple parties, both kernels can publish their own packages and the auditor verifies each independently. Bilateral Receipts covers the cross-pair check that confirms two packages commit to the same governed transaction.
Inspect the Artifacts
cd .artifacts/<timestamp>
# Verifier output
cat verify.json # toolReceipts: 1, capabilityLineage: 1
cat summary.json # receipt_id, capability_id, tool_name, ...
# Tamper run
cat tamper-out.json # empty / partial
cat tamper-err.json # CHIO-CLI-OTHER, "hash mismatch ..."
# Inputs we verified against
ls input-package/
ls tampered-package/Smoke Assertions
The smoke pulls four checks out of the verifier output and the fixture:
assert verify["toolReceipts"] == 1, verify
assert verify["capabilityLineage"] == 1, verify
# Tamper run must fail closed
assert payload["code"] == "CHIO-CLI-OTHER", payload
assert "hash mismatch" in payload["message"] \
or "hash mismatch" in payload["context"]["detail"], payloadDecision rule
Where to read more