Chio/Docs
LOGIN · JOIN

Compliance Certificates

Summarize one agent session into a signed certificate, then decide how much of its receipt evidence to recheck.

Concept chio@5f63e6e

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.

Session to verdictChoose a step.
signsρsession receiptsπconfigurationggeneratechio cert generateσcertificateabortedvverifyρLightweight passcertificate onlyρFull-bundle passN receipts re-verifiedVerification failedexit 1ρreceiptsπconfigggeneratechio cert generateσcertificateabortedvverifyρLightweight passcertificate onlyρFull-bundle passN receipts re-verifiedVerification failedexit 1

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.

One Agent Client Protocol session's receipts summarized into a 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.

Terminalbash
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.db

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

  1. Session and range. Match session_id, receipt_count, first_receipt_at, and last_receipt_at to the material you meant to review.
  2. Configured checks. Read the five compliance booleans and anomalies beside the scopes, budget, required guards, and trusted keys that were supplied.
  3. Verification result. Read passed, summary, receipts_reverified, and receipt_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].seq across 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:

reputation-certs · cert-inspecttranscript
$ chio cert inspect --certificate ./cert.json
Session 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
exit 0

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.

reputation-certs · cert-verify-unsignedtranscript
$ chio cert verify \
    --certificate ./cert.json \
    --trusted-kernel-pubkey ./kernel.pub
FAIL: verification failed: certificate signature invalid
exit 1

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.

reputation-certs · cert-verify-full-no-dbtranscript
$ chio cert verify \
    --certificate ./cert.json \
    --trusted-kernel-pubkey ./kernel.pub \
    --full
error[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
exit 1
reputation-certs · cert-generate-emptytranscript
$ chio cert generate \
    --session-id demo-session \
    --receipt-db ./receipts.db \
    --authority-seed-file ./authority.seed
error[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
exit 1

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

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

Command synopsistext
# 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:

crates/protocol/chio-acp-proxy/src/compliance.rs196-223rust
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:

crates/protocol/chio-acp-proxy/src/compliance.rs227-234rust
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

crates/protocol/chio-acp-proxy/src/compliance.rsrust
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:

crates/protocol/chio-acp-proxy/src/compliance.rs173-192rust
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>,
}
  1. Empty session. No receipts returns EmptySession before anything else runs.
  2. Per-receipt integrity. validate_compliance_receipt checks each receipt's signature, content-addressed receipt id, action parameter hash, signing kernel key against trusted_kernel_keys, session binding from the receipt metadata, tenant binding when expected_tenant_id is set, and that an allow decision carries authorization semantics.
  3. Chain continuity. Consecutive sequence numbers, with a gap reported as the pair it expected and found.
  4. Scope. Skipped when authorized_scopes is empty; otherwise each receipt's tool_name starts with at least one entry in the set.
  5. Budget. Skipped when budget_limit is zero; otherwise the receipt count stays at or under it. The limit counts invocations, not money.
  6. Guard evidence. Every name in required_guards appears in every receipt's evidence.
  7. 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.
  8. Sign, then self-verify. The body is signed over its canonical bytes and run back through verify_compliance_certificate in lightweight mode against a trust set augmented with the certificate's own signer and the receipts' kernel key. A certificate that would fail returns as Signing.

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.

VariantFieldsMessage
EmptySession
session_id
empty session: no receipts found for session {0}
InvalidReceiptSignature
receipt_id
invalid receipt signature: receipt {receipt_id} failed verification
InvalidReceiptId
receipt_id
invalid receipt id: receipt {receipt_id} does not match its canonical body
InvalidActionHash
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}
NonAuthorizingReceipt
receipt_id
non-authorizing allow receipt: receipt {receipt_id} is not mediated/prevent/allow
UntrustedKernelKey
receipt_id, kernel_key
untrusted kernel key: receipt {receipt_id} was signed by {kernel_key}
ChainDiscontinuity
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}
KernelKeyMismatch
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.

crates/protocol/chio-acp-proxy/src/compliance.rsrust
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,
}
ReasonWhat 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-kernel-key set.
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.

Next: export the receipts behind a certificate

Compliance Certificates · Chio Docs