Chio/Docs
LOGIN · JOIN

ProductsProducts

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

Verified against 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

The dashboard displays the server's verdict derived from source inputs. A tampered 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.

Run the published imagebash
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-room

Open 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:

compose.yamlyaml
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.

Stop the quickstartbash
docker stop chio-proof-room
docker rm chio-proof-room

The 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:

Serve the first-run bundlebash
chio proof fixture generate single-call-authority --out ./first-run
chio proof serve ./first-run/proof-room-bundle --trust ./first-run/trust.toml --open
Outputtext
generated  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-room

The 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.

No key pinned for the bundle signer (exit 2)text
$ 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-unpinned

Inside 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.

Command synopsistext
# 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>]
FlagRequiredPurpose
--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.

FieldMeaning
bundle_id, fixture_id
Bundle and source fixture identity, echoed back in the verifier report and doctor report.
transaction_passport_ref
Root chio.transaction-passport.v1 passport the report is recomputed from.
evidence_graph_ref
The chio.transaction.evidence-graph.v1 graph subsets are routed from.
verifier_report_ref
Claimed chio.transaction.verifier-report.v1 report, compared with the recomputed report.
proof_room_verifier_report_ref
UI-facing chio.proof-room.verifier-report.v1 report, with ui_verdict_source: verifier_report_ref.
claims[]
Each has claim_id, required_artifacts, checker, and a result of verified, failed, or unsupported.
receipt_coverage[]
The terminal-receipt matrix: one row per category, each covered (with artifact_path and terminal_status) or excluded (with exclusion_reason).
negative_cases[]
Tamper fixtures, each pinning an expected_failure_code and its observed_failure_code.
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:

  1. Read manifest.json, reject any non-regular file in the bundle tree, and validate the manifest against bundle.schema.json.
  2. 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 the proof-room.bundle-signer role.
  3. 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.
  4. Recompute the source verifier report from the transaction passport and compare it to the manifest's claimed verifier_report_ref.
  5. 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.
  6. Verify the remaining artifacts hash correctly and cross-check the UI-facing report against the recomputed verdict.

Fail closed

Any schema, hash, signature, or claim mismatch rejects the bundle with a 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.

RouteServes
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-room-fixture-catalog.json
The chio.proof-room.fixture-catalog.v1 catalog of fixtures and negative cases.
GET /proof-room-fixtures/{id}/{asset}
Per-fixture assets; verifier-report.json is recomputed live, one domain verifier per fixture, with the HTTP status reflecting the verdict.
GET /proof-room-trusted-bundle-signers.json
The pinned trusted signer set (chio.proof-room.trusted-bundle-signers.v1); 412 if none are configured.
POST /proof-room/upload/verify
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:

RoleConfigures
proof-room.bundle-signer
Signers allowed to sign the bundle DSSE envelope, intersected with the bundle's own trust-roots artifact.
proof-room.receipt-kernel
Receipt kernel keys accepted for the receipt-coverage matrix.
transaction.root
Trusted transaction passport root signers.
swarm.witness
Trusted swarm-authority witness keys.
disclosure.lineage-signer
Trusted disclosure-lineage signers.
disclosure.crypto-report-signer
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:

doctor.json: selected receipt-coverage fieldsjson
{
  "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:

first-run/proof-room-bundle/artifacts/receipts/denial-receipt.jsonjson
{
  "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.

doctor.json: four selected negative cases, reordered for readingjson
{
  "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.

Multipart request outline; include every bundle membertext
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.


Next Steps