Chio/Docs
LOGIN · JOIN

Evidence Exchange Walkthrough

Export receipts under a signed sharing policy, import them with their origin intact, and issue a partner grant from the imported capability.

Guide chio@5f63e6e

On this page

Two operators can audit each other's agent work from signed records, with no access to each other's systems. The origin exports its kernel's receipts under a signed sharing policy; the partner imports them with the origin's signer key intact, then issues its own agents authority that names the imported grant as its parent.

Run the exchange

The partner imports three signed receipts under the origin's policy

Download the federation guide project, produce the origin's evidence, then sign the sharing policy, export under it, and import on the partner's side. Both operators run on this machine, and every command starts in federation-guide/.

Terminalbash
curl --fail --location https://www.chio.computer/examples/federation-guide.zip \
  --output federation-guide.zip
unzip federation-guide.zip
cd federation-guide
cargo run --locked --bin evidence
EVIDENCE_SINCE=$(jq -r .since evidence-run/window.json)
EVIDENCE_UNTIL=$(jq -r .until evidence-run/window.json)
umask 077
openssl rand -hex 32 > sharing.seed
chio evidence federation-policy create \
  --output sharing-policy.json --signing-seed-file sharing.seed \
  --issuer operator-review --partner operator-audit --admin-all \
  --since "$EVIDENCE_SINCE" --until "$EVIDENCE_UNTIL" \
  --expires-at "$((EVIDENCE_UNTIL + 3600))" --require-proofs \
  --purpose 'review the bounded local example run'
chio evidence export --receipt-db evidence-run/receipts.sqlite3 \
  --federation-policy sharing-policy.json --output partner-package
chio evidence import --receipt-db partner-receipts.sqlite3 --input partner-package
Output · exit 0
two allowed calls; one out-of-scope denial; handler calls 2
three receipt signatures verified; checkpoint batch size 1
wrote evidence-run/receipts.sqlite3, window.json, and kernel-public-key.txt
federation policy created
output:              sharing-policy.json
issuer:              operator-review
partner:             operator-audit
signer_public_key:   a79d14964c71eabaa06955b1a3709dc58ebc0e7e8d6f5b9e59194fea184a924e
require_proofs:      true
federated evidence share imported
share_id:            share-6f9d2c1a916c3e3c6a575f14826be511af95c792e89e8d7e81d8350f5c1d4ed6
issuer:              operator-review
partner:             operator-audit
signer_public_key:   a79d14964c71eabaa06955b1a3709dc58ebc0e7e8d6f5b9e59194fea184a924e
tool_receipts:       3
capability_lineage:  1

You should see two allowed calls, one out-of-scope denial, and three verified receipt signatures from the origin, then the signed policy, then federated evidence share imported with tool_receipts: 3 and capability_lineage: 1. Keys, timestamps, and the share id change between runs.

Before you begin

Install Git, curl, unzip, Rust through rustup, a C or C++ build toolchain, jq, and OpenSSL, and install the CLI with curl -fsSL https://chio.computer/install.sh | sh. The project pins its Rust toolchain and a Cargo lockfile, and Cargo fetches the pinned public Chio crates, so the first build compiles the kernel and later programs reuse it. The evidence program refuses to overwrite evidence-run/; move it aside to repeat the run.

One package carries both the receipts and the lineage the partner builds on

The origin operator issues the sharing policy and the primary package. The reviewing operator checks the policy's scope, verifies the package, and applies its own weighting to what it imports.

Evidence exchangeChoose a lens.
Origin operatorPartner operatorOrigin operatorPartner operator

The partner holds the origin’s receipts, still signed by the origin

The origin kernel signs a receipt for each of three calls: two allowed and one refused as out of scope. chio evidence export packages them under the signed sharing policy, and chio evidence import stores the share on the partner’s side with the issuer and signer key it arrived with. Share evidence with a partner.

The federation guide project’s exchange: three receipts from the origin kernel, the signed sharing policy that bounds their export, the partner’s import, and the child grant issued from the imported parent. Operator names come from the captured run; transport, the passport presentation that federated issuance verifies, and checkpoint publication are left out.

Only the package crosses the boundary. Each kernel keeps its own keys, receipt store, and policy, and a third operator can mirror published artifacts that the reviewing operator reads without trusting the mirror.

The origin's kernel signs a receipt for every call, the refusal included

evidence.rs sends two allowed calls and one call to a tool outside the grant through a kernel with a SQLite receipt store and a checkpoint for every receipt, then writes the time window it used.

src/bin/evidence.rsrust
use chio_kernel::Verdict;
use federation_guide::*;
use serde_json::json;
use std::{path::Path, sync::atomic::Ordering};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let directory = Path::new("evidence-run");
    let host = host(directory)?;
    let since = now();
    for id in ["review-a", "review-b"] {
        let response = host
            .kernel
            .evaluate_tool_call(&request(&host.capability, id))
            .await?;
        assert_eq!(response.verdict, Verdict::Allow);
        record(directory, id, &response)?;
    }
    let mut forbidden = request(&host.capability, "review-denied");
    forbidden.tool_name = "delete".into();
    let denied = host.kernel.evaluate_tool_call(&forbidden).await?;
    assert_ne!(denied.verdict, Verdict::Allow);
    record(directory, "denied", &denied)?;
    assert_eq!(host.calls.load(Ordering::SeqCst), 2);
    let until = now();
    std::fs::write(
        directory.join("window.json"),
        serde_json::to_vec_pretty(
            &json!({"since":since,"until":until,"boundary":"admin-all","capabilityId":host.capability.id,"expectedAllow":2,"expectedDeny":1}),
        )?,
    )?;
    println!("two allowed calls; one out-of-scope denial; handler calls 2");
    println!("three receipt signatures verified; checkpoint batch size 1");
    println!("wrote evidence-run/receipts.sqlite3, window.json, and kernel-public-key.txt");
    Ok(())
}

The run's first lines are the evidence: three receipt signatures verified; checkpoint batch size 1. The program also saves window.json, whose since and until bound every later command, and kernel-public-key.txt, the key a partner verifies the receipts against.

Read more in Receipts.

An export leaves only through a declared read boundary

Without a boundary, chio evidence export refuses the export.

Terminalbash
chio evidence export --receipt-db evidence-run/receipts.sqlite3 --output missing-boundary
Output · exit 1
error[CHIO-KERNEL-RECEIPT-READ-CONTEXT-MISSING]: evidence export needs a read boundary
  help: pass --tenant <id> to export one tenant's receipts, or --admin-all to export every tenant as the operator
  code: urn:chio:error:kernel:receipt-read-context-missing
  docs: https://chio.computer/docs/reference/errors#error-kernel-receipt-read-context-missing

The sharing policy is the boundary a partner receives. It names the issuer operator-review and the partner operator-audit, reads every receipt inside the run's window, expires an hour after the window closes, requires inclusion proofs, and records its purpose.

The signing seed stays with the origin, written under umask 077. The policy's signer_public_key appears again in the partner's import, so the partner can tie the share to the policy that bounded it.

Every exported receipt arrives with its inclusion proof and its origin's signature

With --require-proofs, the export carries a checkpoint and an inclusion proof for each receipt, and chio evidence verify checks the package before it is shared.

Terminalbash
EVIDENCE_SINCE=$(jq -r .since evidence-run/window.json)
EVIDENCE_UNTIL=$(jq -r .until evidence-run/window.json)
chio evidence export --receipt-db evidence-run/receipts.sqlite3 --admin-all \
  --since "$EVIDENCE_SINCE" --until "$EVIDENCE_UNTIL" \
  --require-proofs --output evidence-package
chio evidence verify --input evidence-package
Output · exit 0
evidence package verified
tool_receipts:          3
child_receipts:         0
checkpoints:            3
checkpoint_publications: 3
checkpoint_witnesses:   2
checkpoint_consistency_proofs: 2
checkpoint_equivocations: 0
capability_lineage:     1
inclusion_proofs:       3
uncheckpointed_receipts: 0
authorized_receipts:     2
trace_observations:      0
advisory_evaluations:    0
verified_files:         12
child_receipt_scope:    FullQueryWindow
transparency_preview_logs: 1
publication_state:      transparency_preview

You should see inclusion_proofs: 3 beside tool_receipts: 3 and uncheckpointed_receipts: 0. On the partner's side, the import in the run stores the share under its own share_id and keeps issuer: operator-review and the origin's signer key. The imported receipts stay attributed to the origin, and the partner's own receipt history is unchanged.

Read more in Share evidence with a partner.

The partner issues downstream authority from the imported parent

chio trust federated-issue --upstream-capability-id makes an imported capability the parent of one the partner issues under its own delegation anchor. The record below is the child's entry in a lineage export.

federated-delegation · lineagetranscript
$ jq 'del(.signed_capability)' \
    evidence-export-package/capability-lineage.ndjson
{
  "capability_id": "cap-ioa-web3-provider",
  "subject_key": "bff663535a5cf58658cc38595d20dc4501f6dfc076ddf07e20571d413a3ceb5a",
  "issuer_key": "a090e1a43efcd3a3bd5ea5b015e6014d20e33e0e2d480194802720e4d38f108b",
  "issued_at": 1776995579,
  "expires_at": 1776996779,
  "grants_json": "{\"grants\":[{\"constraints\":[],\"operations\":[\"invoke\"],\"server_id\":\"provider-review\",\"tool_name\":\"inspect_service_order\"},{\"constraints\":[],\"operations\":[\"invoke\"],\"server_id\":\"provider-review\",\"tool_name\":\"evaluate_provider_reputation\"},{\"constraints\":[],\"operations\":[\"invoke\"],\"server_id\":\"provider-review\",\"tool_name\":\"issue_review_attestation\"}],\"prompt_grants\":[],\"resource_grants\":[]}",
  "delegation_depth": 1,
  "parent_capability_id": "cap-019dbd30-b376-7b92-ba54-d00aec973369"
}
exit 0

parent_capability_id names the imported grant and delegation_depth counts the hop, so the chain back to the origin stays visible. federated-issue verifies a challenge-bound passport presentation before it issues, and the qualification test for this path builds a four-entry lineage with the imported upstream parent intact.

Read more in Cross-operator delegation.

A changed byte stops the package at verification

Copy the package, append one newline to receipts.ndjson, and verify the copy.

Terminalbash
cp -R evidence-package altered-package
printf '\n' >> altered-package/receipts.ndjson
chio evidence verify --input altered-package
Output · exit 1
error[CHIO-ATTEST-PROVENANCE-MISSING]: evidence package file hash mismatch for receipts.ndjson
  help: receipts.ndjson changed after the package was exported; verify the package as exported, or export it again
  code: urn:chio:error:attest:provenance-missing
  docs: https://chio.computer/docs/reference/errors#error-attest-provenance-missing

You should see evidence package file hash mismatch for receipts.ndjson and exit 1. The file hashes fail before any signature is checked, so a partner who verifies each package before importing it never imports altered records.

Keep a refused package as it arrived, compare its signer, scope, and lineage with what you expected, and ask the origin for a fresh export. Changing local trust until the check passes changes the relationship being verified.

Revoking the imported parent stops the child and keeps its history

Revoke the upstream capability in the partner's revocation store.

federated-delegation · revoke-parenttranscript
$ chio --revocation-db ./revocations.sqlite3 \
    trust revoke --capability-id cap-019dbd30-b376-7b92-ba54-d00aec973369
capability_id: cap-019dbd30-b376-7b92-ba54-d00aec973369
revoked:       true
newly_revoked: true
backend:       ./revocations.sqlite3
exit 0

You should see revoked: true. Revoking the parent stops further use of the child, and the receipts the child already produced keep their lineage. In a deployment each operator runs its own kernel and stores, adds the other's partner card to pin its kernel key, and exchanges packages over HTTPS with mTLS or signed bodies.

Next: retain and inspect the records an exchange leaves

Evidence Exchange Walkthrough · Chio Docs