Proof Room
Serve an evidence bundle through the standalone verifier and inspect its recomputed report, trust inputs, and upload failures.
On this page
Proof Room lets someone outside your organization check an evidence bundle without trusting the party that produced it. When you hand an auditor a bundle, the auditor serves it through Proof Room: the server recomputes the verification result from the bundle's source inputs, refuses to bind if the claimed verdict disagrees, and only then renders the dashboard. Ordinary Chio tool calls never need a Proof Room.
Starts from chio proof fixture generate single-call-authority, a bundle with one allowed call, one guard denial, and ten deliberately broken variants the verifier must reject.
Source
crates/products/chio-proof-room (the chio-proof-room binary, server, and bundle verification modules) and spec/schemas/chio-proof-room/v1.What it verifies
A Proof Room bundle is a signed directory containing a manifest (chio.proof-room.bundle.v1), a root transaction passport, an evidence graph, a claimed verifier report, receipt evidence, and a set of negative-case fixtures. The server sits at an untrusted edge. It reads the bundle a caller supplies and treats the manifest's verdict as an assertion that must be checked before rendering.
The server recomputes the report before displaying it. It routes evidence-graph subsets to the domain verifier crates named by each policy's required_claims, rebuilds the report, and compares the results. The domain verifiers are the same ones the chio CLI uses: chio-transaction-passport (root passport, evidence graph, runtime security), chio-swarm-authority, chio-runtime-proof-parity, chio-web3 settlement proofs, chio-commerce-order, chio-disclosure-lineage, chio-selective-disclosure (BBS), and the enterprise, risk, trust-market, and agent-web verifiers.
Recomputed verdicts
verifier_report_ref that claims verified fails the comparison with the recomputed report and is not served.The Docker Quickstart
Each release publishes a signed image, ghcr.io/backbay-labs/chio-proof-room, that carries the chio-proof-room server with its dashboard, the first-run bundle, and that bundle's fixture trust file. It verifies the bundle before it binds.
docker run -d --name chio-proof-room -p 127.0.0.1:7391:7391 \
ghcr.io/backbay-labs/chio-proof-room:0.2.0
docker logs chio-proof-roomOpen http://127.0.0.1:7391/?view=proof-room after startup. The server binds only after verification succeeds, so an exited container or an unreachable dashboard means the log names the check that failed.
To run it beside other services, Compose takes the same image:
chio-proof-room:
image: ghcr.io/backbay-labs/chio-proof-room:0.2.0
ports:
- "127.0.0.1:7391:7391"The image runs as the non-root chio user (uid/gid 10001) on a digest-pinned Alpine 3.22 base with tini as pid 1. Its command serves the first-run bundle against the fixture trust file at /etc/chio/fixture-trust.toml and writes a doctor report to /opt/chio/proof-doctor-report.json. chio release verify checks the image digest against the release, as Container Images describes.
When finished, stop and remove the container.
docker stop chio-proof-room
docker rm chio-proof-roomThe Binary
The installed CLI serves the same dashboard with chio proof serve. Generate the first-run bundle, then serve it against the trust file the generator wrote beside it:
chio proof fixture generate single-call-authority --out ./first-run
chio proof serve ./first-run/proof-room-bundle --trust ./first-run/trust.toml --opengenerated single-call-authority ./first-run
trust ./first-run/trust.toml (fixture profile, 3 roles)
verified single-call-authority 2 receipts: 1 allow, 1 deny
serving http://127.0.0.1:7391/?view=proof-roomThe verified line comes before serving because the server recomputes the report first. Add --dry-run to run the same checks and exit without opening a listener. Trust always comes from outside the bundle: run the command with no trust file and no pins, and it stops before reading a single claim.
$ chio proof serve ./first-run/proof-room-bundle --dry-run
error[CHIO-TRUST-ROLE-UNPINNED]: no key is pinned for proof-room.bundle-signer, so the bundle signature cannot be checked
help: pass --trust ./first-run/trust.toml, or pin the signer with chio trust pin proof-room.bundle-signer <KEY>
code: urn:chio:error:trust:role-unpinned
docs: https://chio.computer/docs/reference/errors#error-trust-role-unpinnedInside the image, the server is the standalone chio-proof-room binary, marked public_entrypoint = true in its manifest metadata. It takes the same bundle and trust file and has two modes: serve, and verify-only.
# Serve mode: verify, then bind and serve the dashboard.
chio-proof-room --bundle <proof-room-bundle> [--trust <file>] \
[--listen <addr>] [--doctor-report <path>]
# Verify-only: run the pipeline and exit without binding a listener.
chio-proof-room --bundle <proof-room-bundle> --verify-only \
[--trust <file>] [--doctor-report <path>]| Flag | Required | Purpose |
|---|---|---|
--bundle <dir> | always | Bundle directory containing manifest.json. |
--trust <file> | no | Trust file for the bundle signer and receipt-kernel roles. Defaults to the project's .chio/trust.toml, then ~/.chio/trust.toml. |
--listen <addr> | no | Bind address. Defaults to 0.0.0.0:7391. |
--doctor-report <path> | no | Write a quickstart doctor report to this path after verification. |
--verify-only | no | Verify and exit; no listener is bound. |
Verify-only is the CI hook: it verifies a bundle and, with --doctor-report, records the verdict without opening a socket. A verification failure prints a proof-room.* error code to stderr and exits non-zero.
Bundle format, verification pipeline, and server API
Bundle Manifest
The manifest is schema-locked to chio.proof-room.bundle.v1 with hash_algorithm fixed to sha256. Each reference is a bundle reference with a relative path, a 64-hex sha256, and the file's schema.
| Field | Meaning |
|---|---|
bundle_id, fixture_id | Bundle and source fixture identity, echoed back in the verifier report and doctor report. |
transaction_ | Root chio. passport the report is recomputed from. |
evidence_ | The chio. graph subsets are routed from. |
verifier_ | Claimed chio. report, compared with the recomputed report. |
proof_ | UI-facing chio. report, with ui_. |
claims[] | Each has claim_id, required_, checker, and a result of verified, failed, or unsupported. |
receipt_ | The terminal- covered (with artifact_path and terminal_status) or excluded (with exclusion_). |
negative_cases[] | Tamper fixtures, each pinning an expected_ and its observed_. |
signature | kind: detached-dsse plus a signature_ref to the detached DSSE envelope. |
The three receipt-coverage categories are runtime_terminal_allow, runtime_terminal_denial, and runtime_terminal_failure. A covered row's terminal_status is one of the runtime terminal statuses, from allowed_executed and denied_pre_dispatch through failed_timeout_after_tool_entry.
Verification Pipeline
serve_proof_room runs the validation sequence against the bundle, then binds a listener. The sequence runs in order:
- Read
manifest.json, reject any non-regular file in the bundle tree, and validate the manifest againstbundle.schema.json. - Verify the detached DSSE bundle signature against the trust roots declared in the bundle's own
artifacts/authority/trust-roots.json, intersected with the keys pinned for theproof-room.bundle-signerrole. - Verify the transaction passport and evidence graph refs, and, for first-run bundles, the capability-proof, guard-report, and trust-roots artifacts plus their evidence-graph node bindings.
- Recompute the source verifier report from the transaction passport and compare it to the manifest's claimed
verifier_report_ref. - Verify every claim is bundle-scoped or covered by the recomputed report, verify the receipt-coverage matrix (with kernel-signature checks per covered receipt), and replay every negative case against a temp copy of the bundle, checking its exact
expected_failure_code. - Verify the remaining artifacts hash correctly and cross-check the UI-facing report against the recomputed verdict.
Fail closed
proof-room.* error code, and the server refuses to bind. A negative case that fails to reproduce its expected code, or one that unexpectedly passes, fails the whole run. Bundle-relative paths are canonicalized and must stay under the bundle root, ruling out .., absolute paths, and symlink escapes.Served Endpoints
The axum router exposes the assets enumerated in the verified manifest, plus the dashboard SPA and fixture catalog. A bundle path outside the manifest allow-list returns 404.
| Route | Serves |
|---|---|
GET / | Temporary redirect to /proof-room?view=proof-room. |
GET /proof-room, /assets/* | The dashboard index.html and its built assets. |
GET /manifest.json | The verified bundle manifest. |
GET /artifacts/*, /negatives/*, /roots/*, /ui/*, /verifier/* | Allow-listed bundle assets under each namespace. |
GET /proof- | The chio. catalog of fixtures and negative cases. |
GET /proof- | Per-fixture assets; verifier- is recomputed live, one domain verifier per fixture, with the HTTP status reflecting the verdict. |
GET /proof- | The pinned trusted signer set ( chio.); 412 if none are configured. |
POST /proof- | Verify an uploaded bundle. See below. |
The router is wrapped in chio-http-serve hygiene (connection caps and graceful drain). Because assets are read-only there is no receipt store to flush on shutdown.
Trust Configuration
Trust comes from a trust file, never from the bundle. The verifier reads --trust, then the project's .chio/trust.toml, then ~/.chio/trust.toml, and prints which file it used. Each pin names a key and the roles it is trusted for. A role a bundle's claims require but that has no pin is a hard error, not a silent skip. The roles Proof Room reads:
| Role | Configures |
|---|---|
proof- | Signers allowed to sign the bundle DSSE envelope, intersected with the bundle's own trust-roots artifact. |
proof- | Receipt kernel keys accepted for the receipt- |
transaction. | Trusted transaction passport root signers. |
swarm.witness | Trusted swarm-authority witness keys. |
disclosure. | Trusted disclosure- |
disclosure. | Trusted signers for BBS crypto-context reports. |
chio trust roles prints the full list. To trust a real signer instead of the fixture keys, pin it once: chio trust pin proof-room.bundle-signer larkfield.card.
Bundles whose claims reach the public-settlement verifier read the settlement.* roles and the trust file's [settlement] table, including the allowed chain IDs and the reviewed runtime codehashes. The public-settlement independent-chain-head RPC fetch is the server's only outbound network call; it runs through chio-egress-contract's pinned-DNS resolver with redirects denied and a 1 MiB response ceiling.
The Doctor Report
With --doctor-report, the server writes a chio.proof-room.quickstart-doctor-report.v1 JSON file after a passing verification. The quickstart image writes it to /opt/chio/proof-doctor-report.json. It carries the verdict, the bundle and fixture identity, the verifier report ref, and the full receipt-coverage matrix and negative-case list from the manifest.
Selected fields from the first-run bundle's doctor report:
{
"schema": "chio.proof-room.quickstart-doctor-report.v1",
"verdict": "verified",
"bundle": "./first-run/proof-room-bundle",
"bundle_id": "proof-room-single-call-authority",
"fixture_id": "single-call-authority",
"verifier_report_ref": {
"path": "verifier/report.json",
"sha256": "cf7d9522…618db7",
"schema": "chio.transaction.verifier-report.v1"
},
"receipt_coverage": [
{
"category": "runtime_terminal_allow",
"status": "covered",
"artifact_path": "artifacts/receipts/allow-receipt.json",
"terminal_status": "allowed_executed",
"exclusion_reason": null
},
{
"category": "runtime_terminal_denial",
"status": "covered",
"artifact_path": "artifacts/receipts/denial-receipt.json",
"terminal_status": "denied_guard_request",
"exclusion_reason": null
},
{
"category": "runtime_terminal_failure",
"status": "excluded",
"artifact_path": null,
"terminal_status": null,
"exclusion_reason": "Single-call authority fixture covers allow and guard denial terminal receipts only."
}
]
}A bundle that carried only its allow receipt would fail here: runtime_terminal_denial is a coverage category, so the denial receipt is part of what the bundle owes the verifier. Hashes and signatures are shortened here:
{
"kernel_key": "31debe55…cfa498bc",
"policy_digest": "3f562106…1bf005bfe",
"receipt_id": "first-run-single-call-denial",
"request_digest": "aaaaaaaa…aaaaaaaa",
"response_digest": "dddddddd…dddddddd",
"schema": "chio.proof-room.receipt-evidence.v1",
"terminal_status": "denied_guard_request",
"signature": "a7f827af…960d376a0c"
}The second half of the report is the negative-case list. Each entry names a deliberately broken input under negatives/, the failure code the manifest expects from it, and the code the verifier actually produced. The two must agree, which is how the bundle proves its verifier still rejects what it claims to reject.
{
"negative_cases": [
{
"id": "bad-receipt-signature",
"path": "negatives/bad-receipt-signature/transaction-passport.json",
"expected_failure_code": "proof-room.negative.receipt-signature-invalid",
"observed_failure_code": "proof-room.negative.receipt-signature-invalid",
"verifier_context": {}
},
{
"id": "stale-capability",
"path": "negatives/stale-capability/transaction-passport.json",
"expected_failure_code": "proof-room.negative.capability-proof-expired-before-evidence-graph-issuance",
"observed_failure_code": "proof-room.negative.capability-proof-expired-before-evidence-graph-issuance",
"verifier_context": {}
},
{
"id": "missing-denial-receipt",
"path": "negatives/missing-denial-receipt.json",
"expected_failure_code": "proof-room.first-run.denial-missing",
"observed_failure_code": "proof-room.first-run.denial-missing",
"verifier_context": {}
},
{
"id": "guard-deny",
"path": "negatives/guard-deny.json",
"expected_failure_code": "proof-room.first-run.guard-denial-receipt-mismatch",
"observed_failure_code": "proof-room.first-run.guard-denial-receipt-mismatch",
"verifier_context": {}
}
]
}The full list for this fixture carries ten cases. The other six are policy-hash-mismatch, report-hash-mismatch, receipt-coverage-status-mismatch, missing-authority-evidence, missing-authority-graph-node, and missing-receipt-graph-node.
The doctor report records the bundle identity and verification result; it is not itself a signed receipt or a record of the binary and trust configuration used. Retain those separately when reproducing a result. The bundle's source evidence includes the signed receipts checked by the verifier.
Upload Verification
POST /proof-room/upload/verify accepts a multipart bundle upload, runs the same verification pipeline against it in an isolated temp directory, and returns the verdict. The route carries a 32 MiB body limit. Each part's filename is validated as a safe bundle-relative path before it is written; duplicate paths and unsafe segments are rejected.
curl -sS -X POST http://127.0.0.1:7391/proof-room/upload/verify \
-F "manifest.json=@manifest.json" \
-F "artifacts/transaction-passport.json=@artifacts/transaction-passport.json;filename=artifacts/transaction-passport.json"
# ... one -F per bundle member ...The response is a chio.proof-room.upload-verification.v1 JSON body. A passing bundle returns 200 with verdict: verified and its bundle_id; a bundle that fails verification returns 422 with verdict: failed and the proof-room.* error; a malformed multipart body returns 400.