BuildOperations
Trust Control Plane
Use the trust-control HTTP API to issue and revoke a capability, create a receipt, and verify exported evidence offline.
Where the code lives
examples/hello-trust-control/. Run ./smoke.sh for the full flow or ./run-trust.sh to start trust-control alone for manual experimentation.What It Shows
- Capability issuance through the shared trust-control HTTP API.
- Capability status and revocation through the Chio CLI.
- Receipt creation without any HTTP app or framework, using
chio check. - Offline evidence export and verification with
chio evidence exportandchio evidence verify. - Bounded staleness: a revocation through trust-control is checked on subsequent kernel calls.
Files
examples/hello-trust-control/
README.md
policy.yaml minimal HushSpec: allow read_file only
run-trust.sh start trust-control alone (port 8051 by default)
smoke.sh full issue / revoke / receipt / export / verify flowRun It
# From the chio workspace root
cargo build --bin chio
cd examples/hello-trust-control
# Full smoke: issue, revoke, mint receipt, export, verify
./smoke.sh
# Or: start trust-control alone for manual exploration
./run-trust.sh
# trust-control listens on 127.0.0.1:8051
# state under .artifacts/manual-state/On a successful run the smoke prints:
hello-trust-control smoke passed
artifacts: .../examples/hello-trust-control/.artifacts/<timestamp>
capability id: <id>
receipt id: <id>
evidence dir: .../evidencePhase 1: Start Trust-Control
The smoke launches chio trust serve on a free port with all four SQLite stores rooted in the output directory for that run:
"${CHIO_BIN}" trust serve \
--listen "127.0.0.1:${TRUST_PORT}" \
--service-token "${SERVICE_TOKEN}" \
--receipt-db "${TRUST_RECEIPT_DB}" \
--revocation-db "${REVOCATION_DB}" \
--authority-db "${AUTHORITY_DB}" \
--budget-db "${BUDGET_DB}"Once /health returns 200, trust-control is ready to accept capability operations.
Phase 2: Issue a Capability
The shared helper issue_demo_capability posts to /v1/capabilities/issue and writes the capability JSON to disk. The example then materializes a wire-format token (the JSON the kernel passes through the X-Chio-Capability header) under capability.token.
The request body the helper posts:
{
"subjectPublicKey": "0000000000000000000000000000000000000000000000000000000000000000",
"scope": {
"grants": [
{
"server_id": "http-sidecar-client",
"tool_name": "hello_trust_control_invoke",
"operations": ["invoke"],
"constraints": []
}
],
"resource_grants": [],
"prompt_grants": []
},
"ttlSeconds": 3600
}The capability ID is parsed out of the response for the next steps:
CAPABILITY_ID="$(python3 - "${ARTIFACT_ROOT}/capability.json" <<'PY'
import json, sys
from pathlib import Path
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
print(payload["capability"]["id"])
PY
)"Phase 3: Query Status
chio trust status hits GET /v1/capabilities/<id> through the CLI. The smoke asserts that the freshly issued capability is not yet revoked:
"${CHIO_BIN}" \
--control-url "${CONTROL_URL}" \
--control-token "${SERVICE_TOKEN}" \
trust status \
--capability-id "${CAPABILITY_ID}" \
--json \
> "${ARTIFACT_ROOT}/status-before.json"
# Asserts: payload["revoked"] is FalsePhase 4: Revoke
chio trust revoke writes an idempotent revocation record. The first call returns {"revoked": true, "newly_revoked": true}; subsequent calls return "newly_revoked": false. The smoke asserts the first-call shape and re-queries status to confirm the revocation is now visible:
"${CHIO_BIN}" \
--control-url "${CONTROL_URL}" \
--control-token "${SERVICE_TOKEN}" \
trust revoke \
--capability-id "${CAPABILITY_ID}" \
--json \
> "${ARTIFACT_ROOT}/revoke.json"
# Asserts: payload["revoked"] is True
# Asserts: payload["newly_revoked"] is True
"${CHIO_BIN}" \
--control-url "${CONTROL_URL}" \
--control-token "${SERVICE_TOKEN}" \
trust status \
--capability-id "${CAPABILITY_ID}" \
--json \
> "${ARTIFACT_ROOT}/status-after.json"
# Asserts: payload["revoked"] is TrueRevocation checks
Phase 5: Mint a Receipt
Receipts are minted by running an evaluation. The example uses chio check, which runs the same kernel pipeline against a CLI-supplied tool name and params, signs the verdict, and persists it to a receipt sqlite store of your choosing.
"${CHIO_BIN}" check \
--policy "${EXAMPLE_ROOT}/policy.yaml" \
--tool read_file \
--params '{"path":"README.md"}' \
--receipt-db "${CHECK_RECEIPT_DB}" \
--json \
> "${ARTIFACT_ROOT}/check.json"The policy is intentionally narrow: deny by default, allow only the read_file tool.
hushspec: "0.1.0"
name: hello-trust-control
description: Allow one narrow read-style tool so a receipt can be minted without an app surface.
rules:
tool_access:
enabled: true
default: block
allow:
- read_fileThe receipt fields are checked downstream: tool (read_file), server (the wildcard "*" stub), verdict (ALLOW), the receipt id, policy_hash, and policy_source_hash.
The smoke then dumps the check receipt store to receipts.ndjson so the later inspection step and verify_artifacts.py have a file to read. The listing reads across all tenants, so it takes --admin-all for the same fail-closed reason the export does:
"${CHIO_BIN}" receipt \
--receipt-db "${CHECK_RECEIPT_DB}" \
list \
--admin-all \
--limit 20 \
> "${ARTIFACT_ROOT}/receipts.ndjson"Phase 6: Export + Verify Offline
chio evidence export bundles the receipt store into an offline package: receipts as NDJSON, capability lineage, child receipts, inclusion proofs, a manifest with SHA-256 hashes, and a retention record. The reading path fails closed when neither --tenant nor --admin-all is supplied, so this single-tenant demo passes --admin-all to surface the receipt. The example then runs chio evidence verify against the package without contacting trust-control:
"${CHIO_BIN}" evidence export \
--receipt-db "${CHECK_RECEIPT_DB}" \
--admin-all \
--output "${EVIDENCE_DIR}"
"${CHIO_BIN}" evidence verify \
--input "${EVIDENCE_DIR}" \
--json \
> "${ARTIFACT_ROOT}/verify.json"Inspect the Run Output
cd .artifacts/<timestamp>
# Capability JSON and the wire-format token
cat capability.json
cat capability.token
# Status before and after revocation
cat status-before.json # revoked: false
cat revoke.json # revoked: true, newly_revoked: true
cat status-after.json # revoked: true
# Minted receipt and evidence
cat check.json
cat receipts.ndjson
ls evidence/
cat evidence/manifest.json
cat verify.json
cat summary.json
# Persistent state
ls state/
# trust-receipts.sqlite3 receipts ingested by trust-control
# trust-revocations.sqlite3 revocation table
# trust-authority.sqlite3 authority signing seed + history
# trust-budgets.sqlite3 (unused in this example, present for parity)
# check-receipts.sqlite3 receipts written by chio checkManual Experimentation
run-trust.sh starts trust-control alone on 127.0.0.1:8051 with state under .artifacts/manual-state/. Once running you can drive the same flow by hand:
# Terminal 1: start trust-control
./run-trust.sh
# Terminal 2: drive the flow
export CHIO_CONTROL_URL=http://127.0.0.1:8051
export CHIO_SERVICE_TOKEN=demo-token
# Issue
curl -sS -H "Authorization: Bearer $CHIO_SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject_pk":"0000...0000","scope":{"grants":[],"resource_grants":[],"prompt_grants":[]},"ttl":3600}' \
$CHIO_CONTROL_URL/v1/capabilities/issue | jq
# Status
chio --control-url $CHIO_CONTROL_URL --control-token $CHIO_SERVICE_TOKEN \
trust status --capability-id <id> --json | jq
# Revoke
chio --control-url $CHIO_CONTROL_URL --control-token $CHIO_SERVICE_TOKEN \
trust revoke --capability-id <id> --json | jqBounded staleness on the kernel side
Smoke Assertions
smoke.sh drives the phases and captures each output; verify_artifacts.py --write-summary contains the assertions, and test_verify_artifacts.py unit-tests the checker. The checks go past shape into exact scoping and identity:
# Capability schema and exact grant scoping
require(capability.get("schema") == "chio.capability.v1", "capability schema ...")
require(grant.get("server_id") == "http-sidecar-client", "grant server_id ...")
require(grant.get("tool_name") == "hello_trust_control_invoke", "grant tool_name ...")
require(grant.get("operations") == ["invoke"], "grant must allow invoke only")
# The materialized token is compact JSON of the issued capability
expected_token = json.dumps(capability, separators=(",", ":")) + "\n"
require(token == expected_token, "capability.token must be compact issued capability JSON")
# Every status / revoke payload names the local trust-control service
require(backend.startswith("http://127.0.0.1:"), "revocation_backend must be local")
# check.json carries server and policy_source_hash beyond tool/verdict/receipt_id
require(check.get("server") == "*", "check.json.server must stay the wildcard stub")
require(check.get("verdict") == "ALLOW", "check.json.verdict must be ALLOW")
require_string(check, "policy_source_hash", "check.json")Decision rule
Where to read more