EU AI Act
Map signed receipts, checkpoints, approval tokens, and retention settings to the EU AI Act articles an assessor checks.
On this page
The EU AI Act asks a provider of a high-risk AI system to record its events automatically, keep those logs, and let people oversee and halt it. Chio supplies signed records of every kernel decision on a governed tool call, with checkpoints and approval tokens, that an assessor verifies offline.
Mapped against Regulation (EU) 2024/1689, reviewed 10 September 2026. The rows cover Chio's technical contributions to risk management, oversight, event recording, and retention. The operator determines its provider or deployer role and whether the regulation applies to the system.
Each row reads requirement, technical contribution, operator action, evidence. A signature proves who signed and binds the recorded content; the truth of a business claim and the completion of an organizational process stay with the operator. Build and verify an evidence package.
Article 9: risk management
A lifecycle risk-management system covering risks to health, safety, and fundamental rights. Budget and rate controls address selected operational risks inside it. Answered by the guard pipeline and budget caps.
Operator. Run the Article 9 process; the kernel's caps and guards are inputs to it.
Read the mapping in EU AI Act.
The map covers the tool-invocation boundary: what an agent asked a tool to do, what the kernel decided, and who approved it. Articles drawn against the neutral node are obligations the operator keeps, with Chio records as inputs.
Clause mapping
Clause ids follow Regulation (EU) 2024/1689, and the titles beside them are paraphrased.
| Clause | Chio control | Evidence record | Owner |
|---|---|---|---|
Art. 9 risk management | Guard pipeline, budget caps | Deny receipt with Financial | Operator |
Art. 12 automatic recording | Capability token, signed receipt | ChioReceipt | Chio kernel |
Art. 14 human oversight | Approval token, revocation, emergency stop | Governed, chio emergency-stop | Operator |
Art. 18 documentation keeping | Operator process | Provider documentation archive | Operator |
Art. 19 log retention | Retention and archive, Merkle checkpoint | RetentionConfig archive, Kernel | Chio kernel |
Annex III high-risk classification | Operator process | Classification record | Operator |
Annex IV technical documentation | Tool manifest, evidence export | ToolManifest, evidence package | Operator |
Annex VIII registration | Operator process | Registration record | Operator |
Articles 12 and 19 are where the kernel does the work: it signs a receipt for each decision and archives receipts with the checkpoints that keep them verifiable, while the operator chooses the retention schedule. Everywhere else a Chio record is an input to a process the operator runs, such as an approval token inside an Article 14 oversight procedure.
A governed call produces each record in a fixed order
An assessor can walk one call through the kernel and name the article each stage serves.
- Validate the capability. The kernel checks issuer, expiry, scope, and revocation on the token and its delegation chain. The
capability_idit records serves Article 12. - Check the DPoP proof. This runs when
dpop_requiredis set on the matchedToolGrant. It supports authentication; human oversight is a separate application flow. - Run the guard pipeline. Velocity, policy, and budget guards evaluate the call for Article 9. A guard that records a finding adds a
GuardEvidenceentry to the receipt’sevidencearray; an empty array serializes without the field. - Dispatch or refuse. The one branch: the call reaches the tool server, or the kernel produces a deny receipt instead (Article 12).
- Sign the receipt. Allow or deny, signed by the kernel keypair over canonical JSON (Article 12, retained under Article 19).
- Append to the receipt store. Durable and sequential, for Article 19 retention.
- Checkpoint. Every
checkpoint_batch_sizereceipts the kernel signs a Merkle checkpoint, an integrity control the Act leaves to the implementation. - Rotate.
RetentionConfigarchives aged receipts and carries the covering checkpoint rows across (Article 19).
Read more in Receipts.
Review evidence comes from one verified package
The complete evidence workflow produces three signed receipts, samples allow and deny decisions from the same recorded window, exports them with checkpoint proofs, and verifies the package. It carries the kernel host, every command, the captured results, and two deliberate refusals.
For a deployment review, substitute its receipt database and the approved tenant and time boundary. Preserve the original package, record the trusted kernel public key through an independent channel, and attach the framework owner, assessment scope, findings, and open work. A passing cryptographic check is one input to that review.
Produce, sample, and export the example receipts
Produce the receipts
Complete the project setup, then run these steps from federation-guide/ in a fresh evidence-run directory. The host and the program ship in the project download.
cargo run --locked --bin evidencetwo 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
Collect samples from the same window
EVIDENCE_SINCE=$(jq -r .since evidence-run/window.json)
EVIDENCE_UNTIL=$(jq -r .until evidence-run/window.json)
mkdir -p samples
chio receipt list --receipt-db evidence-run/receipts.sqlite3 --admin-all \
--since "$EVIDENCE_SINCE" --until "$EVIDENCE_UNTIL" \
--outcome allow --limit 100 > samples/allow.jsonl
chio receipt list --receipt-db evidence-run/receipts.sqlite3 --admin-all \
--since "$EVIDENCE_SINCE" --until "$EVIDENCE_UNTIL" \
--outcome deny --limit 100 > samples/deny.jsonl
jq -s length samples/allow.jsonl
jq -s length samples/deny.jsonl2 1
Export with complete checkpoint coverage
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-packageevidence 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
Verify security evidence covers how to pin the trusted kernel key and what each verifier check establishes.
An Article 19 package verifies without the original store
The evidence export reads the receipt database through the global --receipt-db flag and needs an explicit read boundary. It refuses to write into a directory that already holds files. Narrow the window with --since and --until, both Unix seconds.
$ chio --receipt-db ./receipts.db evidence export \
--policy-file ./policy.yaml --admin-all --output ./article-19-package
$ ls ./article-19-packageREADME.txt capability-lineage.ndjson checkpoint-consistency-proofs.ndjson checkpoint-equivocations.ndjson checkpoint-publications.ndjson checkpoint-witnesses.ndjson checkpoints.ndjson child-receipts.ndjson inclusion-proofs.ndjson manifest.json policy query.json receipts.ndjson retention.json
Without a boundary, the export refuses instead of defaulting to every tenant:
$ chio --receipt-db ./receipts.db evidence export \
--policy-file ./policy.yaml --output ./article-19-unboundederror[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
manifest.json lists every file the export wrote with its SHA-256 and byte count, beside the policy source hash and the runtime hash it compiled to. Projecting the counts:
$ jq '{schema, counts, proofCoverage, receiptSemantics, policy}' ./article-19-package/manifest.json{
"schema": "chio.evidence_export_manifest.v1",
"counts": {
"toolReceipts": 3,
"childReceipts": 0,
"checkpoints": 0,
"capabilityLineage": 3,
"inclusionProofs": 0,
"uncheckpointedReceipts": 3
},
"proofCoverage": {
"checkpointedReceipts": 0,
"uncheckpointedReceipts": 3
},
"receiptSemantics": {
"mediatedDecisions": 3,
"traceObservations": 0,
"advisoryEvaluations": 0,
"prevent": 3,
"detectOnly": 0,
"advisoryOnly": 0,
"cannotSee": 0,
"authorized": 2
},
"policy": {
"format": "hushspec",
"sourceHash": "11e1aa04a3696c42034ae77afb4f9aee4e5c16c4e38a58f2a21fc2fa137779d8",
"runtimeHash": "89e8cd101e3fb0c311cceb9bd7b988a6a65d371310b4fccceb08a015333885da",
"sourcePath": "policy/source.yaml",
"sourceBytes": 1739
}
}You should see authorized: 2 against mediatedDecisions: 3: three decisions were mediated, two were authorized, and one was refused. A recipient re-checks the package without the original store:
$ chio evidence verify --input ./article-19-packageevidence package verified tool_receipts: 3 child_receipts: 0 checkpoints: 0 checkpoint_publications: 0 checkpoint_witnesses: 0 checkpoint_consistency_proofs: 0 checkpoint_equivocations: 0 capability_lineage: 3 inclusion_proofs: 0 uncheckpointed_receipts: 3 authorized_receipts: 2 trace_observations: 0 advisory_evaluations: 0 verified_files: 14 child_receipt_scope: FullQueryWindow transparency_preview_logs: 0 publication_state: transparency_preview
verified_files: 14 counts every file the manifest hashes, which is every file the export wrote except manifest.json. Verification re-derives each hash, so a changed byte in any of them fails the check and names the file that changed.
The operator owns classification, the technical file, and registration
Chio supplies the logs an assessor inspects. These obligations stay with the operator, who decides its provider or deployer role first:
- Classification. Decide whether the system falls under Annex III and which obligations attach, with legal review against the Official Journal text before filing.
- Risk management. Run the Article 9 process. The kernel’s caps and guards are inputs to it.
- Oversight. Assign the human roles, supply the interface and procedures, and evaluate whether oversight works in context.
- Technical file. Write and maintain the Annex IV file. Chio records populate its sections on logging, oversight, and monitoring.
- Documentation and logs. Keep the Article 18 documentation for ten years, separately from the receipt log, and set rotation and archive retention to the Article 19 schedule.
- Conformity and registration. Complete the conformity assessment, which reads these logs, and register the system in the EU database under Annex VIII.
Model-layer properties such as bias, robustness under distribution shift, and hallucination rate are evaluated above the tool-invocation boundary. Transparency and post-market monitoring obligations stay with the operator, outside this mapping.
Evidence by article
Try RetentionConfig, DPoP, checkpoint, or GovernedAutonomyTier.
The clause-by-clause source for these entries is docs/compliance/eu-ai-act-article-19.md in the Chio repository, which names the test function behind each row. The official text is Regulation (EU) 2024/1689.
Articles 12 and 19: automatically generated logs
Article 12(1): “High-risk AI systems shall technically allow for the automatic recording of events (logs) over the lifetime of the system.” Article 19 covers provider retention of the logs it controls, for at least six months unless other applicable law provides otherwise. The kernel records each governed call with a signed receipt:
- Automatic. The kernel emits a receipt for allow and deny decisions in the governed-call path, which
all_calls_produce_verified_receiptsasserts (crates/kernel/chio-kernel/src/kernel/tests/receipts.rs). - Tamper evident. Receipt batches are Merkle-committed into signed kernel checkpoints. A changed receipt no longer matches its recorded Merkle root.
- Attributable. Every receipt carries
capability_id,tool_server,tool_name,policy_hash,content_hash, adecision, and thekernel_keythat signed it. The hash of the canonical invocation parameters sits one level down, asaction.parameter_hash. - Cost tracked. Monetary invocations carry a
FinancialReceiptMetadatablock under the reservedmetadata.financialkey:cost_charged,currency,attempted_cost, andsettlement_status(not_applicable,pending,settled, orfailed).
These fields are the wire form of ChioReceipt (crates/core/chio-core-types/src/receipt/body.rs). This refusal was read back out of a receipt store after a guard refused a call under the canonical policy shipped in the repository:
$ chio --receipt-db ./receipts.db receipt list --outcome deny --admin-all | jq .{
"id": "2e4541210abe95054175567578dda22730020c91bc0f036321582cae2d978bbc",
"timestamp": 1788581158,
"capability_id": "cap-01a06fbe-9f9a-7902-a2ef-07175cc81ed9",
"tool_server": "*",
"tool_name": "read_file",
"action": {
"parameters": {
"path": "/workspace/.env"
},
"parameter_hash": "1708328df9dddb4147395f5741ee7df3cf5dc4922554c08feb6a73bc0d82f245"
},
"decision": {
"verdict": "deny",
"reason": "guard denied the request: guard \"guard-pipeline\" denied the request",
"guard": "kernel"
},
"receipt_kind": "mediated_decision",
"boundary_class": "prevent",
"tool_origin": "caller_executed",
"redaction_mode": "none",
"content_hash": "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b",
"policy_hash": "89e8cd101e3fb0c311cceb9bd7b988a6a65d371310b4fccceb08a015333885da",
"evidence": [
{
"guard_name": "forbidden-path",
"verdict": false,
"details": "action=deny; reason=guard denied request"
}
],
"metadata": {
"attribution": {
"delegation_depth": 0,
"issuer_key": "12438121ff668472c6bc58dd9ba9dc702913cbb57f560fe7961d5a88c0d92073",
"subject_key": "f697768309183dd63ce40927fb163e9d95409e2101a40c3ff2ba66c3531f2915"
},
"budget_authority": {
"authority_profile": "authoritative_hold_event",
"guarantee_level": "single_node_atomic",
"metering_profile": "max_cost_preauthorize_then_reconcile_actual"
},
"chio_receipt_signing_nonce": "rcpt-01a06fbe-a089-79f3-8a2c-823863da5d74",
"receipt_context": {
"request_id": "check-001"
}
},
"trust_level": "mediated",
"kernel_key": "12438121ff668472c6bc58dd9ba9dc702913cbb57f560fe7961d5a88c0d92073",
"signature": "cd1752cdf468e72dc61530d142a8f670a9c4c9ea1ab3b06e3c013b6bfe933c937e277c3c68210f097499be4c5f01b069fe0d9ab699cf9cb48967d66e83a4e302"
}decision is an internally tagged enum, so a verdict is self-describing and a refusal carries its reason and guard in the same record:
/// The Kernel's verdict on a tool call.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "verdict", rename_all = "snake_case")]
pub enum Decision {
/// The tool call was allowed and executed.
Allow,
/// The tool call was denied.
Deny {
/// Human-readable reason for the denial.
reason: String,
/// The guard or validation step that triggered the denial.
guard: String,
},
/// The tool call was interrupted by explicit cancellation.
Cancelled {
/// Human-readable reason for the cancellation.
reason: String,
},
/// The tool call did not reach a complete terminal result.
Incomplete {
/// Human-readable reason for the incomplete terminal state.
reason: String,
},
}evidence is the per-guard array: one entry per guard that evaluated the call, naming the guard and whether it passed. Its verdict is a boolean, while the receipt-level decision uses a string.
pub struct GuardEvidence {
/// Name of the guard (e.g. "ForbiddenPathGuard").
pub guard_name: String,
/// Whether the guard passed (true) or denied (false).
pub verdict: bool,
/// Optional details about the guard's decision.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub details: Option<String>,
}kernel_key and signature render as bare lowercase hex for Ed25519. Only the non-Ed25519 curves carry a prefix (p256:, p384:), so a verifier dispatches on the encoding alone (crates/core/chio-core-types/src/crypto.rs).
Refusal records
The kernel records refusals with the same field set as allowed decisions, so a refusal names the control that stopped the request. Each executed call ends in exactly one terminal receipt.
A local receipt read takes an explicit boundary: --admin-all reads across every tenant as an audit operation, and --tenant narrows to one. --outcome takes allow, deny, cancelled, or incomplete.
$ chio --receipt-db ./receipts.db receipt list --outcome deny --admin-all \
| jq -c '{id, tool_name, verdict: .decision.verdict, reason: .decision.reason}'{"id":"2e4541210abe95054175567578dda22730020c91bc0f036321582cae2d978bbc","tool_name":"read_file","verdict":"deny","reason":"guard denied the request: guard \"guard-pipeline\" denied the request"}A single receipt renders as a triage summary that names the deciding guard and the policy hash in force:
$ DENY=$(chio --receipt-db ./receipts.db receipt list --outcome deny --admin-all | jq -r .id)
$ chio --receipt-db ./receipts.db receipt explain "$DENY" --admin-allreceipt: 2e4541210abe95054175567578dda22730020c91bc0f036321582cae2d978bbc schema: chio.receipt.v1 identity: 2e4541210abe95054175567578dda22730020c91bc0f036321582cae2d978bbc decision: deny reason: guard denied the request: guard "guard-pipeline" denied the request guard: kernel policy_hash: 89e8cd101e3fb0c311cceb9bd7b988a6a65d371310b4fccceb08a015333885da scope_diff: requested scope vs granted scope is not embedded in this receipt parents: 0 repair_hint: inspect the guard and policy_hash, then mint or narrow a matching capability
scope_diff states that the comparison of requested and granted scope is outside the receipt, so the summary points to the guard and the policy hash. The receipt query API serves the same records to an operator’s tooling.
Annex IV: admitted tool interface
Tool manifests support the operator’s technical documentation. A ToolManifest carries one ToolDefinition per tool, each with a human-readable description and a parameter schema, and the kernel verifies the manifest’s Ed25519 signature before it registers the tool server. sign_and_verify_manifest in crates/platform/chio-manifest/src/lib.rs asserts the round trip.
Articles 18 and 19: retention and archival
Configure live rotation and archive retention together to meet the required period. Chio uses RetentionConfig (crates/kernel/chio-kernel/src/receipt_store.rs), an optional field on KernelConfig. With the field absent, retention is off and receipts accumulate.
impl Default for RetentionConfig {
fn default() -> Self {
Self {
retention_days: 90,
max_size_bytes: 10_737_418_240,
archive_path: "receipts-archive.sqlite3".to_string(),
tenant_id: None,
check_interval_secs: 3_600,
explicit_cutoff_unix_secs: None,
}
}
}| Field | Default | Behavior |
|---|---|---|
retention_days | 90 | Time-based rotation ceiling, set to the obligation; tested by retention_ |
max_size_bytes | 10737418240 | Size trigger that archives aged receipts; tested by retention_ |
archive_path | receipts- | Archive file, writable by the first rotation |
check_ | 3600 | How often the maintenance task checks whether rotation is due |
Archived receipts stay verifiable. Rotation copies the checkpoint rows whose batch end the archived receipts fully cover, so a partially archived batch leaves its checkpoint in the live database. archive_preserves_checkpoint_rows in crates/kernel/chio-kernel/tests/retention.rs asserts that split.
Article 18 requires the provider to keep specified documentation for ten years after a high-risk system is placed on the market or put into service. Article 19 requires provider-controlled automatic logs for an appropriate period of at least six months, subject to other applicable Union or national law. Set live rotation and archive retention to the deployment’s schedule; see Articles 18 and 19 in the official regulation.
Checkpoint integrity
A signed Merkle checkpoint supports log-integrity checks. The Act prescribes no mechanism here; the operator describes this one as part of the implemented logging design.
- A batch of receipts produces a signed
KernelCheckpointcarrying the Merkle root over the batch.checkpoint_batch_sizeonKernelConfigsets the cadence, 100 receipts by default, andcheckpoint_batch_size: 0turns automatic checkpointing off (crates/kernel/chio-kernel/src/kernel/kernel_struct.rs). - A Merkle inclusion proof shows one receipt belongs to the log without re-reading the batch.
build_inclusion_proof(crates/kernel/chio-kernel/src/checkpoint.rs) produces them, and an evidence export writes them toinclusion-proofs.ndjson. - Archived receipts verify against the checkpoint rows copied into the archive database.
- The checkpoint signature is canonical JSON (RFC 8785) signed by the kernel’s Ed25519 keypair, so an auditor checks it with public information alone.
build_checkpoint_signature_verifiesincrates/kernel/chio-kernel/src/checkpoint/tests.rsasserts it.
The chain reports its own state, which is the first thing an auditor reads in a store someone else produced:
$ chio receipt checkpoint status --receipt-db ./receipts.dbstatus: healthy committed_entry_seq: 3 checkpoint_seq: none checkpointed_entry_seq: 0 next_range: 1..=3 retention_watermark_entry_seq: none
This store holds three committed receipts and no checkpoint, so checkpoint_seq reads none and next_range names the window the next checkpoint covers. committed_entry_seq minus checkpointed_entry_seq is the durable tail that no checkpoint covers.
receipt checkpoint verify walks the whole chain instead of reporting its head, and prints the same report when the walk finds nothing wrong:
$ chio receipt checkpoint verify --receipt-db ./receipts.dbstatus: healthy committed_entry_seq: 3 checkpoint_seq: none checkpointed_entry_seq: 0 next_range: 1..=1 retention_watermark_entry_seq: none
The full receipt-log audit runs the claim-log projection validation and a complete checkpoint-chain verification in one pass. Its next_range differs from the status command’s because the audit pins checkpoint status to one receipt per batch, while receipt checkpoint status takes it from --max-batch, which defaults to 1024.
$ chio receipt audit --receipt-db ./receipts.dbstatus: healthy committed_entry_seq: 3 checkpoint_seq: none checkpointed_entry_seq: 0 next_range: 1..=1 retention_watermark_entry_seq: none
Article 14: human oversight
Article 14 concerns effective oversight by natural persons: understanding the system’s limits, monitoring and interpreting its output, deciding when to override it, and intervening or bringing it to a safe halt. Chio supplies the technical parts of an application’s approval and intervention path; the operator assigns the roles, supplies the interface and procedures, and evaluates whether oversight works in context. See Article 14 in the official regulation.
Review, approve, deny, and stop an action
Create a pending approval, bind a reviewer’s signed decision to the exact request, dispatch only after approval, and revoke future use of the capability.
Complete the project setup, then run the program from federation-guide/. The download includes every constructor and input.
cargo run --locked --bin oversight -- approveapproval requested; handler calls 0 changed action binding: refused; request remains pending approved action: ALLOW; handler calls 1 decision replay: refused operator revoked capability: next call denied; handler count unchanged
The example uses a local reviewer key and process-local approval store. Captures pass approve or deny explicitly; omit the argument for a terminal prompt. An application must authenticate its reviewer, protect their key, provide the review interface, and retain decisions durably. Revocation does not reverse a completed effect or cancel arbitrary remote work.
Read the complete program 125 lines · complete file
use chio_core_types::{
capability::governance::{
GovernedApprovalDecision, GovernedApprovalToken, GovernedApprovalTokenBody,
},
Keypair,
};
use chio_kernel::{approval::*, Verdict};
use federation_guide::*;
use std::{
io::{self, Write},
path::PathBuf,
sync::{atomic::Ordering, Arc},
};
#[tokio::main]
async fn main() -> Result<(), Error> {
let choice = match std::env::args().nth(1) {
Some(v) => v,
None => {
println!("Review proposal-7: record the release decision. This changes the local review record.");
print!("Type approve or deny: ");
io::stdout().flush()?;
let mut v = String::new();
io::stdin().read_line(&mut v)?;
v.trim().into()
}
};
if choice != "approve" && choice != "deny" {
return Err("expected approve or deny; nothing dispatched".into());
}
let directory = PathBuf::from(format!("oversight-{choice}"));
let host = host(&directory)?;
let request = request(&host.capability, "review-proposal-7");
let approver = Keypair::generate(); // Local reviewer identity for this example.
let store = Arc::new(InMemoryApprovalStore::new());
let guard = ApprovalGuard::new(store.clone());
let trusted = [approver.public_key()];
let result = guard.evaluate(
ApprovalContext {
request: &request,
constraints: &[],
policy_id: "review-policy",
trusted_approvers: &trusted,
presented_token: None,
force_approval: true,
approval_id_override: Some("review-7".into()),
},
now(),
)?;
let pending = match result {
HitlVerdict::Pending { request, .. } => request,
other => return Err(format!("expected pending: {other:?}").into()),
};
assert_eq!(host.calls.load(Ordering::SeqCst), 0);
std::fs::write(
directory.join("approval-request.json"),
serde_json::to_vec_pretty(&pending)?,
)?;
println!("approval requested; handler calls 0");
let token = GovernedApprovalToken::sign(
GovernedApprovalTokenBody {
id: "review-token-7".into(),
approver: approver.public_key(),
subject: host.capability.subject.clone(),
governed_intent_hash: pending.parameter_hash.clone(),
request_id: pending.approval_id.clone(),
threshold_proposal_hash: None,
issued_at: now(),
expires_at: now() + 120,
decision: if choice == "approve" {
GovernedApprovalDecision::Approved
} else {
GovernedApprovalDecision::Denied
},
},
&approver,
)?;
let decision = ApprovalDecision {
approval_id: pending.approval_id.clone(),
outcome: if choice == "approve" {
ApprovalOutcome::Approved
} else {
ApprovalOutcome::Denied
},
reason: Some(format!("local reviewer chose {choice}")),
approver: approver.public_key(),
token,
received_at: now(),
};
let mut wrong = decision.clone();
let mut wrong_body = decision.token.body();
wrong_body.governed_intent_hash = "0".repeat(64);
wrong.token = GovernedApprovalToken::sign(wrong_body, &approver)?;
assert!(resume_with_decision(store.as_ref(), &wrong, now()).is_err());
assert!(store.get_pending(&pending.approval_id)?.is_some());
println!("changed action binding: refused; request remains pending");
let outcome = resume_with_decision(store.as_ref(), &decision, now())?;
std::fs::write(
directory.join("reviewer-decision.json"),
serde_json::to_vec_pretty(&decision)?,
)?;
// The embedder owns this branch. Only a verified Approved resolution dispatches.
if outcome == ApprovalOutcome::Approved {
let response = host.kernel.evaluate_tool_call(&request).await?;
assert_eq!(response.verdict, Verdict::Allow);
record(&directory, "approved", &response)?;
assert_eq!(host.calls.load(Ordering::SeqCst), 1);
println!("approved action: ALLOW; handler calls 1");
} else {
assert_eq!(host.calls.load(Ordering::SeqCst), 0);
println!("reviewer denied: no dispatch; handler calls 0");
}
assert!(resume_with_decision(store.as_ref(), &decision, now()).is_err());
println!("decision replay: refused");
// Stop future governed use of this capability. This does not undo an effect
// already completed, cancel a remote task, or stop another capability.
host.kernel.revoke_capability(&host.capability.id)?;
let count = host.calls.load(Ordering::SeqCst);
let stopped = host.kernel.evaluate_tool_call(&request).await?;
assert_ne!(stopped.verdict, Verdict::Allow);
assert_eq!(host.calls.load(Ordering::SeqCst), count);
record(&directory, "stopped", &stopped)?;
println!("operator revoked capability: next call denied; handler count unchanged");
Ok(())
}Open the source file. The shared kernel host registers the tool, issues the capability, and persists receipts. Each program asserts its expected refusals; keys, timestamps, and digests change between runs.
The same program with the reviewer’s denial dispatches nothing:
cargo run --locked --bin oversight -- denyapproval requested; handler calls 0 changed action binding: refused; request remains pending reviewer denied: no dispatch; handler calls 0 decision replay: refused operator revoked capability: next call denied; handler count unchanged
For a safe halt, an operator runs chio emergency-stop from the CLI, the process-host socket, or the HTTP route. The stop is durable: a stopped kernel restarts stopped and refuses every governed call until chio emergency-resume.
These checks protect attribution and the handling of evidence. DPoP establishes possession of an agent key; human review comes from the approval token.
| Requirement | Chio mapping | Test |
|---|---|---|
Attributable decisions | decision with capability_id and policy_hash, signed by the kernel keypair | all_ |
Proof of possession | Proof binds capability_id, tool_server, tool_name, action_hash, nonce | dpop_ |
Replay prevention | Nonce store refuses the same invocation within the TTL | dpop_ |
Freshness | Proof refused once issued_at is past proof_ttl_secs (default 300) | dpop_ |
Agent identity binding | proof. must equal capability. | dpop_ |
Step-up for sensitive tiers | Governed when Governed requires escalation | governed_ |
action_hash is the SHA-256 of the canonical invocation arguments, and the kernel checks issued_at against its own clock. A proof signed by any key other than capability.subject is refused before any action runs. The approval-token test lives in crates/kernel/chio-kernel/src/kernel/tests/approval_flow.rs.
DPoP proofs and approval tokens are signed independently of the receipt they authorize, so an auditor inspects oversight material without access to live session state. The approval token is the structured anchor for “a human approved this step”.
Article 9: risk management
Article 9 addresses a lifecycle risk-management system, including risks to health, safety, and fundamental rights. Budget and rate controls address selected operational risks inside that process:
max_total_costandmax_cost_per_invocationare enforced atomically inside the kernel. Exhaustion produces a signed deny receipt, whichmonetary_full_pipeline_three_invocations_third_deniedwalks from issue to refusal.- Velocity guards cap invocation rate and spend rate across time buckets.
- Allow receipts for monetary actions record the charged cost the tool server reported, so reconciliation compares intended and charged cost (
monetary_allow_receipt_contains_financial_metadata). - Deny receipts for monetary refusals record the attempted cost and the reason, so non-execution is evidenced (
monetary_denial_receipt_contains_financial_metadata).
{
"server_id": "billing-api",
"tool_name": "charge.create",
"operations": ["invoke"],
"constraints": [
{ "type": "minimum_autonomy_tier", "value": "delegated" },
{ "type": "require_approval_above", "value": { "threshold_units": 200000 } }
],
"max_invocations": 2000,
"max_cost_per_invocation": { "units": 2000, "currency": "USD" },
"max_total_cost": { "units": 500000, "currency": "USD" },
"dpop_required": true
}A ToolGrant carries the budget caps directly, each a minor-unit amount plus currency. The velocity guard takes its invocation and spend rates from the guard configuration. Autonomy is a minimum_autonomy_tier constraint whose value is a GovernedAutonomyTier (direct, delegated, or autonomous), and the enclosing capability token’s expires_at bounds the grant’s lifetime.
Annex III: high-risk categories
Annex III lists categories of systems treated as high-risk: biometrics, critical infrastructure, education, employment, essential services, law enforcement, migration, and administration of justice. Agents that call tools in these domains run under these kernel defaults:
- Fail closed. An unknown tool, an expired or revoked capability, and an evaluation error each produce a deny receipt.
- Signed evidence by default. A mediated decision emits a signed receipt in normal operation.
allow_ephemeral_receipt_logpermits a process-local log when no durable store is installed; a deployment that requires durable persistence before any tool side effect leaves it false (crates/kernel/chio-kernel/src/kernel/kernel_struct.rs). - Explicit autonomy tiers.
GovernedAutonomyTiernames the human involvement expected for each capability. Sensitive categories stay at tiers that require step-up approval. - Revocation checked per call. Evaluation checks the revocation status of the capability and its whole delegation chain on every governed call, before the guard pipeline runs (
crates/kernel/chio-kernel/src/kernel/evaluation/evaluation_entry.rs).
Whether a specific agent system falls under Annex III, and which obligations attach, is a legal decision the operator records in its classification.