Policy Rollout
Release a policy change across Chio nodes, check its decisions before traffic, and confirm the receipts carry the new identity.
On this page
At the end of a rollout, every edge that serves the affected operation runs the new policy, its receipts carry the new runtime hash, and a pinned release record names every input it loaded.
Each edge loads its Chio YAML or HushSpec policy from local files when it starts. Peers replicate authority, revocations, and other cluster state; policy stays on each node. Your deployment platform delivers the files and replaces the processes, and Replication & Convergence covers the state that does travel.
StartTwo hashes name a policy, and the guards can change under both
Chio records two identities for a loaded policy. The source hash follows the root file’s bytes; the runtime hash follows the normalized inputs the kernel fingerprints, and receipts carry it as policy_hash.
Every edit reaches the compiled guards
Each row is one edit to a HushSpec release. All six change what the kernel enforces; only some of them move either hash. A filled cell means the edit changes that column. Policy identities.
Read a matching hash as evidence about the inputs it covers. Parent files, rules outside the fingerprint, and the binary’s own default guards can change enforcement with both hashes fixed, so a release pins them separately and tests the controls it changes.
1. Pin every input in a release record
Write a release manifest for the change. It is your deployment record, and it holds five things:
- Binary or image. An immutable build identity, the enabled features, and the configuration that launches each edge.
- Policy document. The exact root file, its format, and the path the process receives.
- Inherited policies and assets. Every HushSpec parent and referenced asset with its own file digest, in a stable filesystem layout where the layout affects identity.
- Policy identities. The source and runtime hashes the release binary produces from those inputs.
- Behavior checks. Allowed, refused, approval-sensitive, and output-sensitive cases with their expected results.
Keep the complete previous release beside it for rollback. A pinned root file alone leaves out the parents, the assets, and the default guards compiled into the binary.
2. Check the candidate’s decisions before traffic
Run the release binary against the server, tool, and arguments your workflow uses, with its own admission and receipt databases. Here the candidate is your pricing policy with the refund-ceiling guard added, checked against a quote for sku-118 that it must keep admitting.
mkdir -p ./rollout-check
chio --session-db ./rollout-check/admission.db \
--receipt-db ./rollout-check/receipts.db --json \
check --policy ./candidate.yaml \
--server pricing --tool quote \
--params '{"sku":"sku-118","percent":10}'{
"verdict": "ALLOW",
"tool": "quote",
"server": "pricing",
"params": { "sku": "sku-118", "percent": 10 },
"reason": null,
"receipt_id": "rcpt-01a8…9c04",
"policy_hash": "9a41…d3b6",
"policy_source_hash": "e5f0…11c8",
"check_mode": "preflight",
"output_fixture": false
}You should see the verdict, the reason, and both identities. The check evaluates against an internal tool server, so the tool itself never runs. Record policy_hash and policy_source_hash with each test case.
| Verdict | Exit | Next action |
|---|---|---|
ALLOW | 0 | Also run the refusal cases |
DENY | 2 | Compare the reason with the expected refusal |
PENDING_APPROVAL | 3 | Exercise the approval and resume workflow |
Keep the JSON from a refusal before you treat exit 2 as a failed test. Preflight refuses a policy with a compiled post-invocation pipeline; check those with --mode full --output-fixture ./tool-result.json and a representative result. Include one case the new policy changes on purpose and one it leaves alone, and follow approval cases through Human-in-the-Loop.
3. Replay captured traffic against the candidate
Replay runs a compatible traffic capture through a fresh kernel under the candidate and compares each recorded decision. Supply the tenant key you trust independently.
chio replay traffic \
--from ./capture.ndjson \
--against path:./candidate.yaml \
--tenant-pubkey ./tenant.pub --jsonThe comparison covers the verdict, the attributed guard, and the reason text, and the report is clean when its drift and error counts are both zero. A deliberate policy change produces expected drift; review those cases one by one.
Replay refuses policies with compiled post-invocation hooks or default grants for a specific server. A capture holds a response digest, without response bytes or the original tool-server id, so output-sensitive cases use explicit fixtures or an integration environment. Replay receipts take identifiers from their own namespace, and the run is an offline comparison before live traffic.
4. Replace the processes that enforce the operation
Deliver the complete release to each target and start a new process with the intended policy path. A running edge keeps the policy it loaded at startup, so the new rules take effect only in the new process. Use the platform’s rollout and rollback mechanisms.
During a rolling change, old and new processes enforce different rules. When a tighter rule must cover every request at once, drain the old execution paths before admitting affected traffic, and keep in-flight state as the operation’s contract requires.
List every edge that can execute the affected operation and test each one; leader order covers only the writes routed through the leader. Coordinate any authority or admission change with Leader & Failover and Authority & Rotation.
5. Confirm the receipts carry the new policy hash
Process health leaves the policy question open, so send the allowed and refused probes through every execution path. On the standard loaded-policy path, build_kernel assigns the runtime hash to the kernel’s policy_hash; a receipt’s content-addressed id includes that field and its signature binds the body. Group a receipt-store snapshot by hash, with :rollout_started_at bound to the start time in Unix seconds:
SELECT policy_hash,
COUNT(*) AS receipts,
MIN(timestamp) AS first_seen,
MAX(timestamp) AS last_seen
FROM chio_tool_receipts
WHERE timestamp >= :rollout_started_at
GROUP BY policy_hash
ORDER BY last_seen DESC;policy_hash receipts first_seen last_seen 9a41…d3b6 212 1791712944 1791716380 3c7b…8010 38 1791712801 1791713420
You should see the new hash take over from the old one. The query groups the records in one store, so read it beside your list of execution paths: a quiet process writes no fresh receipt, and one store can hold replicated records from several paths. On durable admission paths, terminal validation also compares each receipt’s policy hash with the operation’s admission binding.
Read more in Evidence Export, which preserves and verifies the decision records.
An inherited edit can change enforcement with both hashes fixed
Take an unchanged root file that extends a parent. Tighten only the parent’s enabled velocity rule. The compiler builds a different velocity guard, and the root’s source digest and the runtime fingerprint stay as they were, because the parent sits outside one and velocity outside the other.
Make the same edit in the root file and the source hash changes. Kernel construction also registers the binary’s default runtime guard profile before the policy-compiled pipeline and appends its post-invocation hooks, from outside both hashes. Hash the parent files independently, pin the build, and test the changed control.
Roll back by restarting on the previous release
Restart the affected edges on the previous release’s complete inputs through the same platform rollout, then repeat the probes and the hash query. The release record from step 1 is what makes that restart exact.
Receipts still show the old hash after replacement
An edge that loaded the old files still serves the operation. Find it from your execution-path list, confirm the policy path it started with, and replace it. Embedders that manage their own lifecycle replace their kernels by their own procedure; in-process WASM guard replacement swaps one module and distributes no fleet policy.
An exported policy file disagrees with past receipts
An evidence export can attach a policy root file with metadata holding its source and runtime hashes. The loader reads that file at export time, and the export verifier checks the attachment without recompiling it into a policy identity. Compare the receipts with the preserved release files, which record what the past process loaded.
API Protect receipts carry a different identity
The API Protect route evaluator derives its policy identity from OpenAPI specification bytes, and without a spec file it discovers the spec from the upstream server at startup. Pass a pinned local --spec file for repeatable releases, and compare identities within the same receipt-producing path.
Policy identity reference
Search for a hash, a rule block, or a loader limit.
Source and runtime hashes by format
- Source hash, Chio YAML. SHA-256 of the root file bytes.
- Source hash, HushSpec. Starts with the root-file digest. An enabled threat-intel pattern database adds its resolved path and content digest through an auxiliary-asset fingerprint.
- Runtime hash, Chio YAML. A serialized fingerprint of the format, kernel configuration, guards configuration, and compiled default capabilities.
- Runtime hash, HushSpec. A serialized fingerprint of the format, default kernel configuration, compiled default capabilities, selected resolved rule blocks, reputation extension, and auxiliary assets.
The source hash covers the root file, without a recursive digest of HushSpec parents. The runtime hash covers its listed inputs, without a digest of the complete compiled pipeline or the binary. A path inside the auxiliary-asset identity makes identical asset bytes produce different identities on different filesystem layouts.
Root file and format detection
The loader detects HushSpec from the top-level format marker and otherwise parses Chio YAML. Any change to the root bytes changes the source digest, including an edit to a rule the runtime fingerprint omits.
Inherited policy resolution
The HushSpec resolver follows filesystem parents and merges them before compilation. Its default limits allow a chain of 32 documents and 4 MiB per document, with cycle detection. List the parents in the release manifest even when the root file is unchanged.
Runtime fingerprint coverage
- Included rule blocks.
forbidden_paths,path_allowlist,egress,secret_patterns,patch_integrity,shell_commands, andtool_access. - Rule blocks outside the fingerprint.
velocity,computer_use,remote_desktop_channels,input_injection,browser_automation, andcode_execution, which still compile into guards. - Human approval rule. Serialized only through its effect on the default capabilities when compiled with enabled
tool_access; coverage follows the resulting scope. - Extensions. The reputation extension and detected auxiliary assets; the rest of the extensions tree stays outside.
Source files
crates/platform/chio-control-plane/src/policy/loader.rsandutil.rs: source bytes, auxiliary assets, and runtime fingerprint inputs.crates/guards/chio-policy/src/resolve.rsandcompiler/rules.rs: inherited inputs and rule compilation.crates/products/chio-cli/src/cli/runtime.rsandreplay/execute.rs: check modes, identity output, and replay limits.