Chio/Docs

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 export and chio evidence verify.
  • Bounded staleness: a revocation through trust-control is checked on subsequent kernel calls.

Files

text
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 flow

Run It

bash
# 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:

text
hello-trust-control smoke passed
artifacts: .../examples/hello-trust-control/.artifacts/<timestamp>
capability id: <id>
receipt id: <id>
evidence dir: .../evidence

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

examples/hello-trust-control/smoke.sh
"${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:

POST /v1/capabilities/issue
{
  "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:

smoke.sh
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:

smoke.sh
"${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 False

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

smoke.sh
"${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 True

Revocation checks

Edge kernels re-check revocation at the start of every tool call against trust-control or the local revocation store. See Trust Control Plane for the cluster repair-sync interval that bounds cross-node propagation.

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.

smoke.sh
"${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.

examples/hello-trust-control/policy.yaml
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_file

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

smoke.sh
"${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:

smoke.sh
"${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

bash
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 check

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

bash
# 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 | jq

Bounded staleness on the kernel side

Edge kernels honor a revocation as soon as the next call hits the revocation store. If you run an edge kernel against this same trust-control, revoking through this example denies subsequent calls within the kernel's revocation re-check window. The deployment doc covers the cluster sync interval for multi-node setups.

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:

verify_artifacts.py
# 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

Use this example when you want to drive trust-control directly: issue, revoke, create a receipt, and verify offline evidence without an HTTP app. Use hello-tool when you also need a tool endpoint with capability binding on each call. Use federation-bootstrap when you need authority signing across two kernels with a signed bilateral policy.

Where to read more

Trust Control Plane for HA topology, leader rule, replication, and key rotation. Rotate Keys and Revoke for the operator playbook.