PlatformStatutory Mappings
Federation & Compliance
EU AI Act
A technical mapping from Chio receipts, checkpoints, and DPoP binding to selected EU AI Act logging, oversight, and risk-management provisions.
Technical mapping for self-assessment
docs/compliance/eu-ai-act-article-19.md, which names the test function behind each row.Article 19: Automatically Generated Logs
Article 19(1) requires high-risk AI systems to automatically generate logs of their operation for a period appropriate to the intended purpose, with sufficient detail to enable traceability of the system's actions during its lifetime. Chio can provide technical logging evidence at the tool-invocation layer.
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 evidence: the 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, recordingcost_charged,currency,attempted_cost, andsettlement_status(not_applicable,pending,settled, orfailed).
The field list above is the wire form of ChioReceipt (crates/core/chio-core-types/src/receipt/body.rs). A real denial read back out of a receipt store looks like this. It is a guard refusal against the canonical policy shipped in the repository, captured by running the commands rather than typed:
$ 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"
}Two shapes in that record matter to an auditor. decision is an internally tagged enum, so a verdict is self-describing and a denial carries the reason and the guard alongside it rather than in a separate 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,
},
}And evidence is the per-guard array: one entry per guard that evaluated the call, each naming the guard and whether it passed. Note that verdict here is a boolean, not the string the receipt-level decision uses:
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, with no algorithm prefix. Only the non-Ed25519 curves prefix themselves (p256:, p384:), which is how a verifier dispatches on the encoding alone (crates/core/chio-core-types/src/crypto.rs).
Denials Carry Equal Weight
Article 19 expects logs to record denied operations with sufficient detail for audit. Chio's kernel fails closed: every error path, every guard denial, and every revocation hit produces a signed deny receipt with the same field set as an allow. There is no code path that emits a decision and no receipt.
A local receipt read needs an explicit boundary, so --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, naming the deciding guard and the policy hash in force at the time:
$ 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 reports that the requested-against-granted scope comparison is not embedded in the receipt, so the summary points the reader at the guard and the policy hash instead of inventing a diff it cannot derive.
Article 19(2): Describing the Logging Capability
Article 19(2) expects the technical documentation to describe the system's logging capabilities. The tool surface a governed agent can reach is itself a signed document: a ToolManifest carries one ToolDefinition per tool, each with a human-readable description and a parameter schema, and the manifest is verified by Ed25519 signature before a tool server is registered. The round trip is asserted by sign_and_verify_manifest in crates/platform/chio-manifest/src/lib.rs.
Annex IV: Retention and Archival
Annex IV Section 2(g) expects records to be retained for a period appropriate to the intended purpose and to remain accessible for post-hoc review. Retention is configured by RetentionConfig (crates/kernel/chio-kernel/src/receipt_store.rs), an optional field on KernelConfig. When it is 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. Operators set this to match their regulatory obligation, and retention_rotates_at_time_boundary covers the boundary. |
max_size_bytes | 10737418240 | Size-based archival trigger. Rotation archives older receipts rather than deleting them, covered by retention_rotates_at_size_boundary. |
archive_path | receipts-archive.sqlite3 | Destination file for rotated receipts. It must be writable at the first rotation. |
check_interval_secs | 3600 | How often the kernel maintenance task evaluates whether a rotation is due. |
Archival keeps archived receipts verifiable rather than merely stored: the rotation copies checkpoint rows whose batch end is fully covered by the archived receipts, 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.
Certain high-risk systems require ten-year retention
retention_days to match their obligation. Do not ship a high-risk deployment with the default 90-day setting.Annex IV Section 7: Tamper-Evident Monitoring
Annex IV Section 7 expects technical documentation to describe measures that ensure log integrity. Chio's Merkle checkpoint pipeline is the implementation answer.
- A batch of receipts produces a signed
KernelCheckpointcarrying the Merkle root over the batch.checkpoint_batch_sizeonKernelConfigsets the cadence, defaulting to 100 receipts. It is a deployment setting, not an invariant:checkpoint_batch_size: 0turns automatic checkpointing off (crates/kernel/chio-kernel/src/kernel/kernel_struct.rs). - Individual receipts can be verified to belong to the log via Merkle inclusion proofs, without re-reading the full batch.
build_inclusion_proof(crates/kernel/chio-kernel/src/checkpoint.rs) produces them, and an evidence export writes them toinclusion-proofs.ndjson. - Archived receipts remain verifiable against 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 can verify the signature with public information alone.
build_checkpoint_signature_verifiesincrates/kernel/chio-kernel/src/checkpoint/tests.rsasserts it.
The chain reports its own state. Reading it is the first thing an auditor does with a store they did not produce:
$ 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
The store above holds three committed receipts and no checkpoint yet, so checkpoint_seq reads none and next_range names the window the next checkpoint would cover. An auditor reads committed_entry_seq minus checkpointed_entry_seq as the tail that is durable but not yet tamper-evident.
receipt checkpoint verify walks the whole chain rather than 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
Article 14: Human Oversight
Article 14 requires that high-risk AI systems enable human oversight. Actions must be attributable to the invoking entity, replay of oversight evidence must be prevented, and stale oversight material must be rejected. The rows below map to the DPoP module at crates/kernel/chio-kernel/src/dpop.rs and its test file crates/kernel/chio-kernel/tests/dpop.rs.
| Requirement | Chio Mapping | Test |
|---|---|---|
| Attributable decisions | Every receipt pairs decision with capability_id and policy_hash; the receipt is signed by the kernel keypair. | all_calls_produce_verified_receipts |
| Proof of possession | A DPoP proof body binds capability_id, tool_server, tool_name, action_hash and nonce to the agent's registered keypair. proof.body.agent_key must equal capability.subject. | dpop_valid_proof_accepted |
| Replay prevention | action_hash is the SHA-256 of canonical invocation arguments. A nonce store blocks same-invocation replay within the TTL window. | dpop_nonce_replay_within_ttl_rejected |
| Freshness | DPoP issued_at is checked against the kernel clock. Proofs older than proof_ttl_secs (default 300) are rejected. | dpop_expired_proof_rejected |
| Agent identity binding | A proof signed by any key other than capability.subject is rejected before any action runs. | dpop_wrong_agent_key_rejected |
| Step-up for sensitive tiers | GovernedApprovalToken supplies a signed human-review anchor when GovernedAutonomyTier requires escalation. | governed_approval_token_binds_every_authorization_field_and_time_window in crates/kernel/chio-kernel/src/kernel/tests/approval_flow.rs |
Oversight evidence is portable
Article 9: Risk Management
Article 9 requires risk management that addresses monetary and operational consumption of AI systems. Chio's budget and velocity primitives are the tool-layer implementation.
max_total_costandmax_cost_per_invocationare enforced atomically inside the kernel. Exhaustion produces a signed deny receipt, whichmonetary_full_pipeline_three_invocations_third_deniedwalks end to end.- Velocity guards cap invocation rate and spend rate across time buckets, preventing runaway consumption.
- Allow receipts for monetary actions record the actual charged cost reported by the tool server, so downstream reconciliation can compare intended and actual cost (
monetary_allow_receipt_contains_financial_metadata). - Deny receipts for monetary denials 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 (max_cost_per_invocation and max_total_cost, each a minor-unit amount plus currency). Rate limiting is a separate concern: the velocity guard caps invocation and spend rate from the guard configuration, not from the grant. Autonomy is expressed as a minimum_autonomy_tier constraint, whose value is a GovernedAutonomyTier (direct, delegated, or autonomous); grant lifetime is bounded by the enclosing capability token's expires_at.
Annex III High-Risk Systems
Annex III lists categories of systems that are 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 benefit from Chio's fail-closed posture:
- Fail-closed default: every unknown tool, every expired capability, every revoked capability, every evaluation error produces a deny receipt rather than a silent allow.
- Signed evidence as default: a mediated decision emits a signed receipt as a side effect of normal operation. How durable that record is remains a deployment setting:
allow_ephemeral_receipt_logpermits a process-local log when no durable store is installed, and a deployment that wants durable persistence before any tool side effect leaves it false (crates/kernel/chio-kernel/src/kernel/kernel_struct.rs). - Explicit autonomy tiers:
GovernedAutonomyTiernames the level of human involvement expected for each capability. Sensitive categories stay at tiers that require step-up approval. - Revocation re-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).
Classification is operator-owned
Kernel Stages and the Clauses They Serve
A governed call runs through the kernel in order. Each stage is where a specific clause's evidence is produced, which is the mapping an assessor walks:
| Stage | What it does | Clause |
|---|---|---|
| Validate capability | Checks issuer, expiry, scope, and revocation on the token and its delegation chain. | Article 19(1), through capability_id |
| Check DPoP proof | Runs when dpop_required is set on the matched ToolGrant. | Article 14 |
| Run guard pipeline | Velocity, policy, and budget guards evaluate the call. A guard that records a finding contributes a GuardEvidence entry to the receipt's evidence array; a receipt with an empty array serializes without the field. | Article 9 |
| Dispatch or deny | The one branch in the chain: the call reaches the tool server, or the kernel produces a deny receipt instead. | Article 19(1) |
| Sign the receipt | Allow or deny, signed by the kernel keypair over canonical JSON. | Article 19(1) |
| Append to the receipt store | Durable and sequential. | Annex IV Section 2(g) |
| Merkle checkpoint | Triggered every checkpoint_batch_size receipts. | Annex IV Section 7 |
| Rotate | Archives per RetentionConfig, carrying the covering checkpoint rows across. | Annex IV Section 2(g) |
Verification Commands
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 above because the audit pins the 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
The evidence export assembles the Article 19 package. It reads the local receipt database through the global --receipt-db flag, needs an explicit read boundary, and 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 rather than defaulting to every tenant:
$ chio --receipt-db ./receipts.db evidence export \
--policy-file ./policy.yaml --output ./article-19-unboundederror [urn:chio:error:attest:provenance-missing]: receipt read boundary error: evidence export requires an explicit receipt read boundary
context: {"domain":"attest","severity":"error","stability":"unstable","string_code":"CHIO-ATTEST-PROVENANCE-MISSING"}
suggested fix: Regenerate the evidence bundle and include provenance before submitting the operation.The package is self-describing: every file the export wrote is listed in manifest.json with its SHA-256 and byte count, alongside the policy source hash and the runtime hash it compiled to. Projecting the counts out of that manifest:
$ 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
}
}authorized: 2 against mediatedDecisions: 3 is the denial showing through the counts: three decisions were mediated, two were authorized, 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 the files the manifest hashes, which is every file the export wrote except manifest.json itself. Verification re-derives each hash, so editing one byte of any of them fails the check and names the file that changed.
Non-Goals
- Chio does not perform conformity assessment. It supplies the logs an assessor inspects.
- Chio does not register AI systems in the EU database (Annex VIII). Registration remains the provider's responsibility.
- Chio does not evaluate model-layer properties such as bias, robustness under distribution shift, or hallucination rate.
- Chio's evidence posture does not replace the technical file (Annex IV). It supplies inputs that populate sections about logging, oversight, and monitoring.
- The clauses on this page are the ones the repository's own mapping covers: Article 9, Article 14, Article 19, Annex IV Section 2(g), and Annex IV Section 7. Transparency and post-market monitoring obligations are not mapped here, because no Chio mechanism is documented against them.
For the process-oriented management-system framing of these controls, continue with ISO/IEC 42001. For the operational log surface itself, see Receipts and Receipt Query API.