Verify Commerce Evidence
Run one verifier over a signed commerce bundle, read each record it checks, and watch four tampered copies fail.
On this page
A reviewer can check an agent's spending record on their own machine, with keys they chose. A Proof Room bundle carries the mandate, the kernel's receipts, and the settlement packet for one purchase, and chio proof verify recomputes each claim from those records.
The fixture is a USD 42 single-use allowance, a signed refusal, a signed allowed call, and an offline settlement projection. Verification reads recorded evidence: it contacts no payment rail and holds or moves no funds.
Run the verifierThe verifier accepts the signed commerce bundle
Generate the built-in fixture with the installed chio, then verify it with five claim families required. The commands here use jq, and its filter narrows the chio.transaction.verifier-report.v1 report to its verdict.
chio proof fixture generate commerce-transaction-passport --out ./commerce
BUNDLE=./commerce/proof-room-bundle
chio proof verify "$BUNDLE" --trust ./commerce/trust.toml \
--require denials --require commerce --require settlement \
--require risk --require trust-marketgenerated commerce-transaction-passport ./commerce trust ./commerce/trust.toml (fixture profile) VERIFIED passport-public-settlement-valid 23 claims required denials, commerce, settlement, risk, trust-market: all covered trust ./commerce/trust.toml (fixture keys)
You should see VERIFIED and 23 claims. Add --json for the full chio.transaction.verifier-report.v1 report, whose verified_claims names each claim. The verifier takes its keys from the trust file you pass, never from the bundle, so a bundle cannot vouch for itself. Each --require names a family the report must cover: denials for the terminal refusal, commerce for the mandate and order, settlement for the packet, and risk and trust-market for the risk-comptroller and trust-scorecard checks. A missing record, a broken binding, or a failed signature rejects the bundle.
The mandate bounds what the agent may spend
mandate-commerce-001 caps spend at max_amount_minor 4200 (USD 42.00) with max_occurrences 1. The commerce requirement checks the ledger schema, the bounds, and the hash of each entry in protocol_projections. Verify commerce evidence.
chio.proof-room.bundle.v1 commerce fixture meeting the verifier under keys the reviewer configures. The receipts are fixture receipts signed with a deterministic test key, and no rail is contacted; the risk and trust-market records are left out.The fixture's records are signed with deterministic test keys suited only to this exercise, and the bundle is the same chio.proof-room.bundle.v1 format the Proof Room server verifies.
The mandate caps the purchase at USD 42.00 and one use
mandate-allowance-ledger.json (chio.commerce.mandate-allowance-ledger.v1) holds the allowance the agent spent against.
jq '{id, max_amount_minor, max_occurrences, currency}' \
"$BUNDLE/mandate-allowance-ledger.json"{
"id": "mandate-commerce-001",
"max_amount_minor": 4200,
"max_occurrences": 1,
"currency": "USD"
}You should see max_amount_minor 4200, which is USD 42.00, and max_occurrences 1 on a single_use allowance. The commerce requirement checks the ledger schema, these bounds, and that each entry in protocol_projections hashes to its payload under protocol-payloads/: x402, AP2, and ACP-Commerce payloads, each hashed and checked, none dispatched.
Read the full mandate and its five protocol projections
The ledger also carries an issued_at and one top-level digest per projected protocol; hashes are abbreviated.
{
"schema": "chio.commerce.mandate-allowance-ledger.v1",
"id": "mandate-commerce-001",
"currency": "USD",
"max_amount_minor": 4200,
"max_occurrences": 1,
"single_use": true,
"used_occurrences": 1,
"merchant_subject": "merchant:stripe:coffee-shop",
"order_id": "order-public-settlement-valid",
"valid_from": "2026-06-10T00:00:00Z",
"expires_at": "2026-06-10T01:00:00Z",
"quote_sha256": "9ead9dbbe19e0e2428a6bc6f..."
}{
"protocol_projections": [
{ "protocol": "ap2", "purpose": "checkout_mandate",
"amount_minor": 4200, "currency": "USD",
"payload_path": "protocol-payloads/ap2-checkout-mandate.json" },
{ "protocol": "ap2", "purpose": "payment_mandate",
"payload_path": "protocol-payloads/ap2-payment-mandate.json" },
{ "protocol": "acp-commerce", "purpose": "delegated_payment_token",
"payload_path": "protocol-payloads/acp-commerce-delegated-payment-token.json" },
{ "protocol": "x402", "purpose": "payment_requirements",
"payload_path": "protocol-payloads/x402-payment-requirements.json" },
{ "protocol": "chio", "purpose": "authority_projection",
"payload_path": "protocol-payloads/chio-authority-projection.json" }
]
}Two receipts record a refusal and an allowed call under one key
Both receipts use chio.receipt.v1 and the same signing key, with independent ids and signatures.
for r in denial allow; do
jq -c '{terminal_status, receipt_id, kernel_key}' \
"$BUNDLE/commerce-terminal-$r-receipt.json"
done{"terminal_status":"denied_guard_request","receipt_id":"receipt-commerce-terminal-denial","kernel_key":"e8da63a40ca687c87cfce05cb24a786c7e75cc49c70db5573f026f1c6a86ceaa"}
{"terminal_status":"allowed_executed","receipt_id":"receipt-commerce-terminal-allow","kernel_key":"e8da63a40ca687c87cfce05cb24a786c7e75cc49c70db5573f026f1c6a86ceaa"}You should see denied_guard_request and allowed_executed under one kernel_key and one policy_digest. The denials requirement checks that the refusal verifies under its key and that the receipt-coverage matrix records a runtime_terminal_denial row for it. Each receipt carries a receipt_id, a policy_digest, the kernel key, and a signature, and neither carries an amount or a mandate reference, so the two are separate records rather than two uses of one mandate. Their terminal statuses come from the fixture generator.
Read both abbreviated receipts
Hashes and signatures are abbreviated; the verifier reads the full values in the bundle.
{
"schema": "chio.receipt.v1",
"receipt_id": "receipt-commerce-terminal-denial",
"terminal_status": "denied_guard_request",
"policy_digest": "824f6c706f1092e0e84e5c9b...",
"kernel_key": "e8da63a40ca687c87cfce05c...",
"signature": "425dc80cbb68c93823659daa..."
}
{
"schema": "chio.receipt.v1",
"receipt_id": "receipt-commerce-terminal-allow",
"terminal_status": "allowed_executed",
"policy_digest": "824f6c706f1092e0e84e5c9b...",
"kernel_key": "e8da63a40ca687c87cfce05c...",
"signature": "7036a4e3310e43037ef358a4..."
}The settlement packet binds to the order and the quote
The settlement requirement checks the packet schema and that its order_id and quote_sha256 match the mandate.
jq '{schema, status, psp, settlement_rail}' "$BUNDLE/settlement-packet.json"{
"schema": "chio.commerce.settlement-packet.v1",
"status": "settled",
"psp": "stripe-shaped-offline",
"settlement_rail": "offline-psp-ledger"
}You should see status settled on an offline projection: psp is stripe-shaped-offline and settlement_rail is offline-psp-ledger. The packet settlement-packet-commerce-001 also records amount_minor 4200 in USD, payment_intent_id pi_commerce_001, order_id order-public-settlement-valid, dispatch_receipt_ref receipt-settlement-dispatch-commerce-001, and reconciliation_ref reconciliation-commerce-001.
A live deployment records the same packet from an observed rail outcome, and holds and caps stop spend before dispatch. Authoritative spend states the predicate a receipt meets to count as authoritative spend.
Four tampered bundles each fail on the rule they break
The fixture ships a negative catalog under negatives/catalog/: complete bundles with one record altered. Verify the four commerce cases in one run and state that you expect each to be refused.
NEG="$BUNDLE/negatives/catalog"
chio proof verify "$NEG/commerce-payment-before-budget" "$NEG/commerce-mandate-occurrence-limit" \
"$NEG/commerce-expired-mandate" "$NEG/commerce-payment-amount-mismatch" \
--trust ./commerce/trust.toml --require commerce --expect refuseREFUSED commerce-payment-before-budget commerce replay failed: unknown commerce transition: provider_admitted -> payment_verified via verify_payment REFUSED commerce-mandate-occurrence-limit commerce mandate failed: mandate occurrence limit exceeded REFUSED commerce-expired-mandate commerce mandate failed: mandate expired before payment capture REFUSED commerce-payment-amount-mismatch commerce payment failed: payment amount or currency mismatch 4 of 4 refused, as expected trust ./commerce/trust.toml (fixture keys)
You should see four REFUSED lines and an exit of 0, because every input met --expect refuse. A refusal is a result, not an error: without --expect, the same run exits 1, and a case that verified would print VERIFIED with expected refuse and fail the run. The occurrence case raises used_occurrences to 2 against a max_occurrences of 1 on the same mandate-commerce-001 at 4200 minor units. It is a byte-for-byte copy of the offline-psp-valid commerce fixture's ledger with that one field changed, which is why its order_id and payload digests differ from the bundle's ledger.
| Case | Tampered record | Failure |
|---|---|---|
commerce-payment-before-budget | event-log.json | unknown commerce transition |
commerce-mandate-occurrence-limit | mandate-allowance-ledger.json | mandate occurrence limit exceeded |
commerce-expired-mandate | mandate-allowance-ledger.json | mandate expired before payment capture |
commerce-payment-amount-mismatch | payment-lifecycle.json | payment amount or currency mismatch |
Every fixture chio proof fixture generate writes is verified by the release's qualification run, including each negative, so a case that refuses for the wrong reason, or passes, fails the release.
Verify a bundle another operator produced
Run the same command over a bundle your partner produced, with the partner's keys pinned from a channel you trust: its partner card, exchanged when the two kernels paired. A key that arrives inside the bundle establishes self-consistency, not the producer's identity.
chio trust pin proof-room.bundle-signer ./halden.card
chio trust pin proof-room.receipt-kernel ./halden.card
chio proof verify ./halden-bounty-417 --require commerce --require settlementpinned halden proof-room.bundle-signer ed25519:9b07…d31c pinned halden proof-room.receipt-kernel ed25519:9b07…d31c file ~/.chio/trust.toml VERIFIED passport-halden-bounty-417 19 claims required commerce, settlement: all covered trust ~/.chio/trust.toml
- A signature fails or the signer is untrusted. Compare the expected signer configuration with the original signed bytes, then restore the intended trust or obtain an intact bundle.
- A required claim is absent. The bundle lacks a record that family needs; obtain it before accepting the claim.
- A tampered bundle fails with a different error. Compare it with the table above; an unrelated error leaves the intended rejection unshown.
- A tampered bundle is accepted. Stop using the result as evidence for that check and investigate the fixture and the verifier together.