Compliance Certificates
Summarize one agent session into a signed certificate, then decide how much of its receipt evidence to recheck.
On this page
One signed record answers whether a whole agent session stayed inside the configuration it ran under. At session end, the operator's kernel key signs a certificate over every receipt the session produced: their signatures, their order, their scope, their budget, and the guard evidence they carry.
The recipient checks the certificate alone, or rechecks every receipt behind it. The example is an Agent Client Protocol session named session-001 and its receipt database.
Generation checks the whole session, then signs
generate_compliance_certificate walks the session's receipts in order: integrity, chain continuity, scope, budget, guard evidence, and one kernel key. It signs the body and runs it back through lightweight verification before returning it. Compliance certificates.
chio.compliance.certificate.v1 certificate and checked by a recipient. The six generation checks are folded into one node; the abort messages and the body fields are left out.Three commands generate, inspect, and verify a session certificate
Generation checks the session and signs the result, inspection renders its fields, and verification decides whether the certificate and the evidence you supply pass.
chio --authority-seed-file ./session-authority.seed cert generate \
--session-id session-001 --receipt-db ./receipts.db \
--output ./session-certificate.json
chio cert inspect --certificate ./session-certificate.json
chio cert verify --certificate ./session-certificate.json \
--trusted-kernel-pubkey ./session-kernel.pub \
--full --receipt-db ./receipts.dbThe CLI uses the generation key's public key as its receipt trust set, so that key matches the one that signed the retained session receipts. It supplies empty authorized-scope and required-guard lists and no expected tenant id, and takes its invocation budget from --budget-limit, where zero disables the budget check. An application that needs a scope, guard, or tenant requirement calls the library with an explicit ComplianceConfig.
Keep the database and the trusted key with the certificate when a reviewer will recheck the bundle, and read three things before interpreting the verdict:
- Session and range. Match
session_id,receipt_count,first_receipt_at, andlast_receipt_atto the material you meant to review. - Configured checks. Read the five compliance booleans and
anomaliesbeside the scopes, budget, required guards, and trusted keys that were supplied. - Verification result. Read
passed,summary,receipts_reverified, andreceipt_failures. Rendering the signed body is inspection, and verification is a separate step.
A passing certificate asserts six facts about one session
- Every receipt carries a signature its own signing key verifies.
- The receipt chain is continuous:
receipts[i].seq + 1 == receipts[i+1].seqacross the session. - Every invocation named a tool inside the authorized scope set.
- The session stayed inside its invocation budget.
- Every required guard left evidence in every receipt.
- One kernel key, the one the body names, signed every receipt.
The body records the first five as typed booleans beside an anomalies array. Generation is all or nothing: the first failed check raises a typed error, so generate_compliance_certificate never produces a false boolean or a non-empty anomalies. Verification still treats either as a failure, which matters for a certificate that arrived from elsewhere.
The certificate is the operator's attestation over the configuration it supplied, for one session, and no legal, regulatory, or third-party certification: a regulator or accredited auditor takes it as input and makes its own finding. It shows that declared policies were honored, and says nothing about whether they suffice for a regime. A check that was never configured is skipped and still reports true: a guard absent from required_guards, scope with an empty authorized_scopes, and budget with budget_limit at zero. A clean certificate covers its session only.
The recipient chooses how much evidence to recheck
Lightweight verification takes the certificate and an independently trusted kernel key. It checks the signature over the canonical body, that the signer is in trusted_kernel_keys, that signer_key equals body.kernel_key, and that the body is consistent: the certificate schema, all five booleans true, and anomalies empty. It reads no receipts, and an untrusted signer fails whatever else holds.
Full-bundle verification adds the session's receipt database and repeats the per-receipt integrity walk from generation: it re-derives each content-addressed id, rechecks the action hash and the kernel key against the trust set, and rechecks session and tenant bindings. It passes only with zero receipt failures, so a third party reaches a verdict without trusting the issuing kernel. A FullBundle call with no receipt slice returns the lightweight result, and the CLI closes that gap by refusing --full without --receipt-db.
A pass reads lightweight verification passed or full-bundle verification passed (N receipts re-verified). A failure reads verification failed: followed by every reason that applied, comma separated, as listed in Verification result.
Verification refuses an unsigned certificate and missing inputs
The captures use a body assembled by hand with a signature that does not verify. inspect renders every field and reaches no verdict, because it verifies nothing:
$ chio cert inspect --certificate ./cert.jsonSession ID: demo-session Schema: chio.compliance.certificate.v1 Issued at: 1756944000 Receipt count: 3 First receipt: 1756943000 Last receipt: 1756944000 Signatures: valid Chain: continuous Scope: compliant Budget: compliant Guards: compliant Signer key: 31debe55d37c722768b137131caa6087080b2e0b60b94bd785d14575cfa498bc Kernel key: 31debe55d37c722768b137131caa6087080b2e0b60b94bd785d14575cfa498bc
verify reaches the verdict. The hand-written signature fails, so the run reports one reason and exits 1; the signer is trusted here because the same key was passed in --trusted-kernel-pubkey.
$ chio cert verify \
--certificate ./cert.json \
--trusted-kernel-pubkey ./kernel.pubFAIL: verification failed: certificate signature invalid
Full-bundle mode refuses before reading anything when it has no receipt database, and generation refuses a session with no receipts, which is what a store without Agent Client Protocol history returns for every session id.
$ chio cert verify \
--certificate ./cert.json \
--trusted-kernel-pubkey ./kernel.pub \
--fullerror[CHIO-CLI-RECEIPT-STORE-REQUIRED]: full-bundle verification re-verifies every receipt signature and needs the receipt store help: pass --receipt-db <path>, or drop --full to check the certificate signature alone code: urn:chio:error:cli:receipt-store-required docs: https://chio.computer/docs/reference/errors#error-cli-receipt-store-required
$ chio cert generate \
--session-id demo-session \
--receipt-db ./receipts.db \
--authority-seed-file ./authority.seederror[CHIO-CLI-SESSION-EMPTY]: no receipts found for session demo-session help: pass the --session-id of a session with receipts in the store code: urn:chio:error:cli:session-empty docs: https://chio.computer/docs/reference/errors#error-cli-session-empty
Each refusal names its input. An unsigned sample needs a generated certificate; a full-bundle request needs the retained session receipts; an empty session means the database holds no matching session, so check the session id and the Agent Client Protocol receipt binding. Configure an untrusted signer through your own channel, never because its key arrived in the certificate.
Each audit question has its own signed record
A certificate answers whether one session's configured checks held. The audit APIs answer which receipts matched a query, and a risk report answers whether the financial evidence behind a Transaction Passport holds together.
- One session
- Were the session's configured checks satisfied?A compliance certificate summarizes receipt integrity, scope, budget, and required guard evidence for one session. Record the verification mode and keep its inputs.
- Recorded history
- Which receipts matched this query?The audit APIs return a signed, bounded export. Check the query, the result count, and the signature before you call the requested history complete.
- Financial claim
- Does the risk evidence hold together?A risk report checks signed authority, lifecycle, accounting, and cross-report consistency against a Transaction Passport.
The certificate carries booleans and no receipts. The regulatory receipt export is the other direction: a read-only projection of the receipt store, filtered by agent and time window, capped at MAX_REGULATORY_EXPORT_LIMIT rows, and wrapped in a SignedExportEnvelope signed by the kernel's receipt-signing keypair. A reviewer holding both checks one certificate signature instead of replaying a guard, and returns to the receipts when the answer matters.
Certificate reference
Search for --full, ComplianceConfig, or ChainDiscontinuity.
Generation, verification, and the certificate types are pure code in crates/protocol/chio-acp-proxy/src/compliance.rs, and chio cert is their CLI caller. The regulatory receipt export lives in crates/platform/chio-http-core/src/regulatory_api.rs.
The chio cert commands
# Generate a certificate for a session. --receipt-db is required.
chio cert generate --session-id <session-id> --receipt-db <path> \
[--budget-limit <n>] [--output <path>]
# Verify a certificate. --full needs --receipt-db alongside it.
chio cert verify --certificate <path> --trusted-kernel-pubkey <path> \
[--full] [--receipt-db <path>]
# Inspect a certificate without verifying anything.
chio cert inspect --certificate <path>generate loads a session from a receipt database and signs with the capability-authority keypair at .chio-authority-seed, unless the global --authority-seed-file names another path; it creates the seed when absent. It selects receipts whose capability_id begins with acp-session:<session-id>. verify builds its trust set from the one key in --trusted-kernel-pubkey, which accepts raw 32-byte key material, bare hex, or algorithm-prefixed hex. inspect prints a certificate with no cryptography.
With the global --json flag, verify prints the whole CertificateVerificationResult, inspect prints the body, and generate prints the certificate to stdout, as it does whenever --output is absent. A failed verification and an unusable input exit 1; an argument the parser rejects exits 2.
The signed body
The body names its schema with COMPLIANCE_CERTIFICATE_SCHEMA, chio.compliance.certificate.v1:
pub struct ComplianceCertificateBody {
/// Schema identifier.
pub schema: String,
/// Session ID the certificate covers.
pub session_id: String,
/// Unix timestamp when the certificate was generated.
pub issued_at: u64,
/// Number of receipts examined.
pub receipt_count: u64,
/// First receipt timestamp in the session.
pub first_receipt_at: u64,
/// Last receipt timestamp in the session.
pub last_receipt_at: u64,
/// Whether all receipts passed signature verification.
pub all_signatures_valid: bool,
/// Whether the receipt chain is continuous (no gaps).
pub chain_continuous: bool,
/// Whether all receipts are within authorized scope.
pub scope_compliant: bool,
/// Whether the invocation budget was respected.
pub budget_compliant: bool,
/// Whether all required guards have evidence in every receipt.
pub guards_compliant: bool,
/// Summary of any anomalies detected (empty if fully compliant).
pub anomalies: Vec<String>,
/// The kernel public key that signed the session receipts.
pub kernel_key: PublicKey,
}The signed certificate wraps that body with the signer key and the detached signature:
pub struct ComplianceCertificate {
/// The unsigned body.
pub body: ComplianceCertificateBody,
/// Public key that signed the certificate.
pub signer_key: PublicKey,
/// Ed25519 signature over canonical JSON of `body`.
pub signature: Signature,
}Neither type carries a rename_all, so the field names above are the wire names: this schema is snake_case on the wire, where most signed Chio records are camelCase. The signature covers RFC 8785 canonical JSON of the body, so a verifier re-canonicalizes before checking it and a non-canonical re-serialization fails. PublicKey and Signature serialize through to_hex: an Ed25519 key is a bare 64-character lowercase hex string and a signature a bare 128-character one; a P-256 key carries a p256: prefix and a P-384 key a p384: prefix.
Generation
pub fn generate_compliance_certificate(
session_id: &str,
receipts: &[ComplianceReceiptEntry],
config: &ComplianceConfig,
keypair: &Keypair,
) -> Result<ComplianceCertificate, ComplianceCertificateError>;The config is what the checks run against:
pub struct ComplianceConfig {
/// Maximum number of invocations allowed (0 = unlimited).
pub budget_limit: u64,
/// Guard names that must appear in every receipt's evidence.
pub required_guards: Vec<String>,
/// Authorized resource scopes (path prefixes).
pub authorized_scopes: Vec<String>,
/// Expected tenant for all receipts. None disables tenant checking.
pub expected_tenant_id: Option<String>,
/// Trusted kernel keys allowed to sign receipts and certificates.
///
/// MUST be populated by the operator with the kernel public keys
/// that are authorized to sign receipts in this deployment. An
/// empty set is a misconfiguration: every receipt will be rejected
/// with `UntrustedKernelKey`, blocking both generation and
/// verification of compliance certificates. The first observed
/// empty-set evaluation logs a one-shot tracing warning to make
/// this contract visible to operators.
pub trusted_kernel_keys: std::collections::BTreeSet<String>,
}- Empty session. No receipts returns
EmptySessionbefore anything else runs. - Per-receipt integrity.
validate_compliance_receiptchecks each receipt's signature, content-addressed receipt id, action parameter hash, signing kernel key againsttrusted_kernel_keys, session binding from the receipt metadata, tenant binding whenexpected_tenant_idis set, and that an allow decision carries authorization semantics. - Chain continuity. Consecutive sequence numbers, with a gap reported as the pair it expected and found.
- Scope. Skipped when
authorized_scopesis empty; otherwise each receipt'stool_namestarts with at least one entry in the set. - Budget. Skipped when
budget_limitis zero; otherwise the receipt count stays at or under it. The limit counts invocations, not money. - Guard evidence. Every name in
required_guardsappears in every receipt's evidence. - One kernel key. Every receipt after the first carries the first receipt's
kernel_key, so a session that mixed two kernels cannot certify under one key. - Sign, then self-verify. The body is signed over its canonical bytes and run back through
verify_compliance_certificatein lightweight mode against a trust set augmented with the certificate's own signer and the receipts' kernel key. A certificate that would fail returns asSigning.
ComplianceConfig derives Default, and the default trusted_kernel_keys set is empty, so every receipt fails membership with UntrustedKernelKey and neither generation nor verification succeeds. The first evaluation against an empty set emits one tracing::warn! naming the misconfiguration.
Abort errors
ComplianceCertificateError has fifteen variants. The thirteen below are compliance aborts: the session is non-compliant and no certificate exists. The other two, Serialization and Signing, each wrap a String and report a construction failure.
| Variant | Fields | Message |
|---|---|---|
EmptySession | session_id | empty session: no receipts found for session {0} |
Invalid | receipt_id | invalid receipt signature: receipt {receipt_id} failed verification |
Invalid | receipt_id | invalid receipt id: receipt {receipt_id} does not match its canonical body |
Invalid | receipt_id | invalid receipt action hash: receipt {receipt_id} failed parameter hash verification |
SessionMismatch | receipt_id, session_id | session mismatch: receipt {receipt_id} is not bound to session {session_id} |
TenantMismatch | receipt_id, tenant_id | tenant mismatch: receipt {receipt_id} is not bound to tenant {tenant_id} |
Non | receipt_id | non-authorizing allow receipt: receipt {receipt_id} is not mediated/ |
Untrusted | receipt_id, kernel_key | untrusted kernel key: receipt {receipt_id} was signed by {kernel_key} |
Chain | expected, found | chain discontinuity: expected seq {expected} but found {found} |
ScopeViolation | receipt_id, resource | scope violation: receipt {receipt_id} accesses {resource} outside authorized scope |
BudgetExceeded | used, limit | budget exceeded: {used} invocations against limit of {limit} |
GuardBypass | guard_name, receipt_id | guard bypass: guard {guard_name} has no evidence in receipt {receipt_id} |
Kernel | receipt_id | kernel key mismatch: receipt {receipt_id} signed by a different kernel than the session's first receipt |
Verification result
verify_compliance_certificate takes the certificate, a VerificationMode, an optional receipt slice, and the same ComplianceConfig, and returns a result instead of an error, so a rejected certificate still says why.
pub enum VerificationMode {
Lightweight,
FullBundle,
}
pub struct CertificateVerificationResult {
pub certificate_signature_valid: bool,
pub body_consistent: bool,
pub receipts_reverified: u64,
pub receipt_failures: u64,
pub passed: bool,
pub summary: String,
}| Reason | What it means |
|---|---|
certificate signature invalid | The signer key did not verify the signature over the canonical body. |
certificate signer is not trusted | The signer key is outside the caller's trusted- |
certificate signer does not match body kernel key | signer_key and body.kernel_key are different keys. |
body consistency check failed | A body boolean is false, the schema is not the certificate schema, or anomalies is non-empty. |
N receipt authority check(s) failed | Full-bundle mode only: N receipts failed the per-receipt integrity walk. |
A signer that differs from the body key is reported by its own reason and never folded into the body-consistency line, so the two never appear together for one fault.