Chio/Docs

LearnAnatomy of a Governed Call

Receipts

The kernel signs a receipt for each mediated decision, recording the request, decision, guard evidence, and financial data.

What receipts record

A receipt is a signed JSON document for an agent's tool request. It records the request, the kernel's decision, and the guard evidence. For priced calls, it also records attribution and cost. See Autonomous Commerce for how receipts participate in billing. The kernel produces one receipt per decision, including denials. Receipts answer four questions:

  • Who: which agent, using which capability token, on which tool server
  • What: which tool, with what parameters
  • When: Unix timestamp of the decision
  • Result: the kernel's decision and the evidence from each guard that evaluated the request

Receipt coverage

The kernel signs a receipt for every mediated decision: allow, deny, cancelled, or incomplete. If the kernel mediated a tool call, the log contains a receipt. The same signed format also records trace observations and advisory evaluations without a kernel decision.
Receipt chainappend-only receipt logGenesis receipt — prev_hash is nullr0 · chain rootfile_read · allowReceipt r1 — commits to r0 via prev_hashr1file_write · allowReceipt r2 — denied by egress-allowlist guardr2http_get · denyLatest receipt — head of the append-only logr3 · latestdb_query · allowcontent_hash: 0x1a3f…content_hash: 0x9af2…content_hash: 0x7c0e…content_hash: 0x4b82…prev_hash: nullgenesisprev_hash: 0x1a3f…prev_hash: 0x9af2…prev_hash: 0x7c0e…prev_hashprev_hashprev_hashappendappendappendtamper any field → content_hash changes → every forward signature failsgenesis → appended → latest · signed with kernel ed25519 key
Receipts form a hash-linked append-only log. Each receipt signs its content_hash, and the next receipt stores that value in prev_hash. Editing a past receipt invalidates later signatures.

ChioReceipt fields

The ChioReceipt struct contains all fields necessary for independent verification:

chio-core-types/src/receipt/body.rs
pub struct ChioReceipt {
    /// Content-addressed receipt ID derived from the canonical receipt body.
    pub id: String,
    /// Unix timestamp (seconds) when the receipt was created.
    pub timestamp: u64,
    /// ID of the capability token that was exercised (or presented).
    pub capability_id: String,
    /// Tool server that handled the invocation.
    pub tool_server: String,
    /// Tool that was invoked (or attempted).
    pub tool_name: String,
    /// The action that was evaluated.
    pub action: ToolCallAction,
    /// The Kernel's decision. Present only for mediated decisions.
    pub decision: Option<Decision>,
    /// Signed receipt semantic kind.
    pub receipt_kind: ReceiptKind,
    /// Signed runtime boundary class.
    pub boundary_class: BoundaryClass,
    /// Signed observation outcome for trace and advisory records.
    pub observation_outcome: Option<ObservationOutcome>,
    /// Signed tool-origin classification.
    pub tool_origin: ToolOrigin,
    /// Signed redaction mode.
    pub redaction_mode: RedactionMode,
    /// Signed actor attribution chain.
    pub actor_chain: Vec<ActorRef>,
    /// SHA-256 hash of the evaluated content for this receipt.
    pub content_hash: String,
    /// SHA-256 hash of the policy that was applied.
    pub policy_hash: String,
    /// Per-guard evidence collected during evaluation.
    pub evidence: Vec<GuardEvidence>,
    /// Optional receipt metadata for stream/accounting details.
    pub metadata: Option<serde_json::Value>,
    /// Strength of kernel mediation that produced this receipt.
    pub trust_level: TrustLevel,
    /// Tenant identifier for multi-tenant deployments (None in single-tenant).
    pub tenant_id: Option<String>,
    /// BBS projection version bound into the id when BBS material is present.
    pub bbs_projection_version: Option<String>,
    /// The Kernel's public key (for verification without out-of-band lookup).
    pub kernel_key: PublicKey,
    /// Optional BBS material for selective disclosure over this receipt.
    pub bbs_signature: Option<BbsReceiptSignature>,
    /// Signing algorithm. Informational only; verification dispatches off the
    /// self-describing signature encoding.
    pub algorithm: Option<SigningAlgorithm>,
    /// Signature over canonical JSON of the receipt body.
    pub signature: Signature,
}

The id is content-addressed: it is a hash of the canonical receipt body, not a random UUID, so the same body always yields the same id and any edit changes it. The decision is optional: it is present for mediated decisions and absent on the trace and advisory receipt kinds described next. See the Receipt Format reference for the complete field-by-field shape and wire semantics.

Receipt kinds and boundary class

Six signed fields identify the record type and the portion of the call Chio observed or enforced. A verifier can assess the record's boundary from the signed fields.

FieldVariantsMeaning
receipt_kindMediatedDecision, TraceObservation, AdvisoryEvaluationWhether the record is an authorization decision or an observation the kernel logged without mediating
boundary_classPrevent, DetectOnly, AdvisoryOnly, CannotSeeWhat Chio could enforce on this call: full prevention down to no visibility at all
observation_outcomeObserved, Evaluated, DroppedFor non-mediated records: whether the observation was seen, evaluated, or dropped
tool_originCallerExecuted, HostExecutedProviderReported, HostExecutedUnmediatedWhere the tool effect ran relative to Chio
redaction_modeNone, Summary, RedactedHow much of the signed detail was redacted before storage or export
actor_chainVec<ActorRef>The signed attribution chain of actors behind the request

The two BBS fields, bbs_projection_version and bbs_signature, carry optional BBS+ material for selective disclosure. A holder can present selected receipt fields without revealing the rest of the receipt.


Decision variants

The Decision enum captures four possible outcomes. On the wire the variants serialize with lowercase tags: allow, deny, cancelled, and incomplete. The deny variant carries both reason and guard (the name of the blocking guard).

chio-core-types/src/receipt.rs
#[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,
    },
}

Each Deny records the guard that rejected the call, allowing a verifier to identify the rule.


ToolCallAction fields

The action field captures the parameters that were passed to the tool, along with a self-verifying hash:

rust
pub struct ToolCallAction {
    /// The parameters that were passed to the tool (or attempted).
    pub parameters: serde_json::Value,
    /// SHA-256 hash of the canonical JSON of parameters.
    pub parameter_hash: String,
}

The parameter_hash is computed over the canonical JSON (RFC 8785) of the parameters. This allows verification that the parameters in the receipt have not been tampered with, independent of the receipt signature.


Guard evidence

Each receipt includes an evidence array documenting what each guard reported during evaluation:

rust
pub struct GuardEvidence {
    /// Name of the guard (e.g. "forbidden-path").
    pub guard_name: String,
    /// Whether the guard passed (true) or denied (false).
    pub verdict: bool,
    /// Optional details about the guard's decision.
    pub details: Option<String>,
}

The pipeline stops at the first denial, so the evidence array omits unevaluated guards. A denial receipt may contain fewer entries than the total number of enabled guards. An allow receipt includes evidence from every guard that was evaluated against the request.

json
[
  {"guard_name": "forbidden-path", "verdict": true, "details": null},
  {"guard_name": "path-allowlist", "verdict": true, "details": null},
  {"guard_name": "shell-command", "verdict": true, "details": null},
  {"guard_name": "egress-allowlist", "verdict": true, "details": null},
  {"guard_name": "mcp-tool", "verdict": true, "details": null},
  {"guard_name": "secret-leak", "verdict": true, "details": "no secrets detected"},
  {"guard_name": "patch-integrity", "verdict": true, "details": null},
  {"guard_name": "velocity", "verdict": true, "details": null}
]

Content hashing

The content_hash field is a SHA-256 hash of the evaluated content: typically the tool's result for allow decisions, or the request body for deny decisions. This creates a tamper-evident link between the receipt and the actual data that was processed.

bash
# Content hash is computed as:
content_hash = SHA-256(tool_result_bytes)

# Example: tool returned {"ok": true}
content_hash = "sha256:a1b2c3d4..."  # SHA-256 of the JSON bytes

Policy hashing

The policy_hash field is a SHA-256 hash of the policy document (HushSpec) that was applied when evaluating the request. This binds the receipt to a specific policy version, enabling auditors to verify that the correct policy was in effect at the time of the decision.

Policy pinning for compliance

Each receipt includes the hash of the policy evaluated for that call. A verifier can compare it with the policy bytes used at the time.

Signing

Receipts are signed with the kernel's ed25519 private key. The signature covers the canonical JSON (RFC 8785) serialization of all fields except the signature itself, known as the ChioReceiptBody.

rust
impl ChioReceipt {
    /// Sign a receipt body with the Kernel's keypair.
    pub fn sign(body: ChioReceiptBody, keypair: &Keypair) -> Result<Self> {
        let (signature, _bytes) = keypair.sign_canonical(&body)?;
        Ok(Self {
            id: body.id,
            timestamp: body.timestamp,
            // ...all body fields...
            signature,
        })
    }
}

The signing process:

  1. Construct the ChioReceiptBody (all fields except signature)
  2. Serialize the body to canonical JSON per RFC 8785 (deterministic key ordering, no insignificant whitespace)
  3. Sign the canonical bytes with the kernel's ed25519 private key
  4. Attach the resulting signature to produce the final ChioReceipt

Canonical JSON defines signed bytes

RFC 8785 maps the same receipt body to the same byte sequence across implementations and field orderings. Signature verification uses those canonical bytes.

Receipt verification

Third parties can verify receipts independently without contacting the kernel. The receipt is self-contained: it includes the kernel's public key.

rust
impl ChioReceipt {
    /// Verify the receipt signature against the embedded kernel key.
    pub fn verify_signature(&self) -> Result<bool> {
        let body = self.body();
        self.kernel_key.verify_canonical(&body, &self.signature)
    }
}

Verification steps:

  1. Extract the ChioReceiptBody from the receipt (all fields except signature)
  2. Serialize the body to canonical JSON (RFC 8785)
  3. Verify the ed25519 signature using the embedded kernel_key
  4. Optionally verify the kernel_key against a trusted key registry to confirm the receipt was produced by a recognized kernel

You can also independently verify the parameter_hash inside the action field by recomputing the SHA-256 of the canonical JSON of the parameters:

rust
impl ToolCallAction {
    /// Verify that parameter_hash matches the canonical hash of parameters.
    pub fn verify_hash(&self) -> Result<bool> {
        let canonical = canonical_json_bytes(&self.parameters)?;
        let expected = sha256_hex(&canonical);
        Ok(self.parameter_hash == expected)
    }
}

Receipt example

Here is a complete receipt as it appears in the receipt log. This example shows an allowed file read operation:

receipt-allow.json
{
  "id": "rcpt-001",
  "timestamp": 1710000000,
  "capability_id": "cap-001",
  "tool_server": "srv-files",
  "tool_name": "file_read",
  "action": {
    "parameters": {
      "path": "/app/src/main.rs"
    },
    "parameter_hash": "sha256:e3b0c44298fc1c14..."
  },
  "decision": {
    "verdict": "allow"
  },
  "receipt_kind": "mediated_decision",
  "boundary_class": "prevent",
  "tool_origin": "caller_executed",
  "redaction_mode": "none",
  "content_hash": "sha256:d7e8f9a0b1c2d3e4...",
  "policy_hash": "abc123def456",
  "evidence": [
    {"guard_name": "forbidden-path", "verdict": true, "details": null},
    {"guard_name": "secret-leak", "verdict": true, "details": "no secrets detected"}
  ],
  "metadata": {
    "sandbox": {
      "enforced": true
    }
  },
  "trust_level": "mediated",
  "kernel_key": "9c7b3f2e8a1c4d5b6f0a9e8d7c6b5a4f...",
  "signature": "e5f6a7b8c9d0e1f2a3b4c5d6e7f8091a..."
}

And a denied request:

receipt-deny.json
{
  "id": "rcpt-002",
  "timestamp": 1710000005,
  "capability_id": "cap-001",
  "tool_server": "srv-files",
  "tool_name": "file_read",
  "action": {
    "parameters": {
      "path": "/etc/passwd"
    },
    "parameter_hash": "sha256:f4a5b6c7d8e9f0a1..."
  },
  "decision": {
    "verdict": "deny",
    "reason": "path /etc/passwd is forbidden",
    "guard": "forbidden-path"
  },
  "receipt_kind": "mediated_decision",
  "boundary_class": "prevent",
  "tool_origin": "caller_executed",
  "redaction_mode": "none",
  "content_hash": "sha256:0000000000000000...",
  "policy_hash": "abc123def456",
  "evidence": [
    {"guard_name": "forbidden-path", "verdict": false, "details": "path /etc/passwd matches pattern /etc/passwd"}
  ],
  "trust_level": "mediated",
  "kernel_key": "9c7b3f2e8a1c4d5b6f0a9e8d7c6b5a4f...",
  "signature": "a1b2c3d4e5f6071829303a4b5c6d7e8f..."
}

Financial metadata

When a tool call exercises a monetary grant, the receipt's metadata field includes a FinancialReceiptMetadata record under the "financial" key. The full field set is:

FieldTypePurpose
grant_indexu32Index of the matched grant within the capability token
cost_chargedu64Cost actually charged (minor units)
currencyStringISO 4217 currency code of the charge
budget_remainingu64Remaining budget after this invocation
budget_totalu64Total grant budget
delegation_depthu32Depth in the delegation chain (0 = root)
root_budget_holderStringIdentity of the root budget holder
payment_referenceOption<String>External payment reference for settlement
settlement_statusSettlementStatusOne of NotApplicable, Pending, Settled, Failed
cost_breakdownOption<Value>Optional itemized cost breakdown reported by the tool
oracle_evidenceOption<OracleConversionEvidence>Cross-currency conversion evidence, when applicable
attempted_costOption<u64>Cost that would have been charged (present on denials)

The SettlementStatus enum has four variants: NotApplicable (free-tier grant), Pending (awaiting settlement), Settled (completed), and Failed (e.g., cost overrun).

financial-metadata.json
{
  "metadata": {
    "financial": {
      "grant_index": 0,
      "cost_charged": 150,
      "currency": "USD",
      "budget_remaining": 850,
      "budget_total": 1000,
      "delegation_depth": 1,
      "root_budget_holder": "agent-root-001",
      "payment_reference": "ref-abc123",
      "settlement_status": "pending",
      "cost_breakdown": {"compute": 100, "io": 50},
      "oracle_evidence": null,
      "attempted_cost": null
    }
  }
}

On denial receipts caused by budget exhaustion, attempted_cost records the cost that would have been charged while cost_charged is zero. For cross-currency invocations, the oracle_evidence field holds an OracleConversionEvidence record: the base and quote currencies, the integer rate_numerator/rate_denominator pair, the oracle source and feed_address, an updated_at timestamp with its max_age_seconds/cache_age_seconds freshness bounds, and the converted and original cost in units. The record is itself a signed oracle attestation: when the quote is signed it carries its own oracle_public_key and signature, so a verifier can check the rate against the oracle's key without trusting the kernel. See Economics for the full struct.

FinancialReceiptMetadata is the settled summary. Alongside it the same module defines the budget-hold lineage records that back it ( FinancialBudgetHoldAuthorityMetadata, FinancialBudgetAuthorizeReceiptMetadata, and FinancialBudgetTerminalReceiptMetadata ) which capture the authorize, lease, and terminal steps of the budget hold that check_and_increment_budget opens and later reconciles. Economics walks that lifecycle.


Storage

Receipts are stored in an append-only SQLite log. The storage layer accepts inserts and rejects updates and deletes. The log is Merkle-committed in batched checkpoints, and inclusion proofs let any individual receipt be verified without replaying the entire log.

Protect the receipt log

The SQLite store rejects receipt updates and deletes. Deployments that need stronger retention guarantees should add infrastructure controls, such as write-once storage or replication.

Querying

The chio receipt list command queries the receipt log with filtering support. Every read requires an explicit tenant boundary: pass --tenant <id> to scope the listing to one tenant, or --admin-all to read across all tenants as an administrative operation. With neither, the command fails closed. Output is JSON Lines: one receipt per line.

bash
# List all receipts for a specific tool server
$ chio receipt list --admin-all --tool-server srv-files

# Filter by tool name and outcome
$ chio receipt list --admin-all --tool-name file_read --outcome allow

# Time range: --since / --until take Unix seconds
$ chio receipt list --admin-all --since 1735689600 --until 1735776000

# Filter by cost (minor currency units)
$ chio receipt list --admin-all --min-cost 100 --max-cost 500

# Page through results
$ chio receipt list --admin-all --limit 20 --cursor 1042

# Combine filters
$ chio receipt list \
    --admin-all \
    --tool-server srv-files \
    --tool-name file_read \
    --outcome deny \
    --since 1735689600
FilterFlagDescription
Read boundary--tenant / --admin-allRequired: scope to one tenant, or read across all tenants (mutually exclusive)
Capability--capabilityFilter by capability token identifier
Tool server--tool-serverFilter by tool server identifier
Tool name--tool-nameFilter by tool name
Outcome--outcomeFilter by decision: allow, deny, cancelled, or incomplete
Start time--sinceReceipts with timestamp ≥ this Unix-seconds value
End time--untilReceipts with timestamp ≤ this Unix-seconds value
Min cost--min-costMinimum cost charged, minor units (financial receipts)
Max cost--max-costMaximum cost charged, minor units (financial receipts)
Page size--limitMaximum receipts per page (default 50)
Cursor--cursorPagination cursor: sequence value to start after

SIEM export

Receipts can be exported to external security information and event management (SIEM) systems for centralized monitoring and alerting. chio-siem ships exporters for seven backends: Splunk HEC, Elasticsearch, CEF, Datadog, Sumo Logic, OCSF, and a generic webhook. Each implements a common Exporter trait and is configured with a Rust config struct. SIEM exporters are not configured through policy YAML. Each exporter requires TLS and rejects a plain http:// endpoint.

Splunk HEC

The Splunk HTTP Event Collector exporter streams receipt batches as newline-separated JSON event envelopes, each wrapping the full ChioReceipt under the event key.

rust
pub struct SplunkConfig {
    /// Splunk HEC endpoint URL (e.g. "https://splunk.example.com:8088").
    pub endpoint: String,
    /// HEC authentication token.
    pub hec_token: String,
    /// Splunk sourcetype for all exported events. Default: "chio:receipt".
    pub sourcetype: String,
    /// Optional index. Omit to use the HEC token's default index.
    pub index: Option<String>,
    /// Optional host field sent with each event envelope.
    pub host: Option<String>,
    /// HTTP request timeout. Default: 30 seconds.
    pub timeout: Duration,
    /// Typed HTTP egress contract enforced on every dispatch and redirect.
    pub egress_contract: Option<HttpEgressContract>,
}

Elasticsearch

The Elasticsearch exporter posts receipt batches through the /_bulk API, using receipt.id as the document _id so retries are idempotent.

rust
pub struct ElasticConfig {
    /// Elasticsearch endpoint URL (e.g. "https://es.example.com:9200").
    pub endpoint: String,
    /// Target index for all exported receipts. Default: "chio-receipts".
    pub index_name: String,
    /// Authentication method and credentials.
    pub auth: ElasticAuthConfig,
    /// HTTP request timeout. Default: 30 seconds.
    pub timeout: Duration,
    /// Typed HTTP egress contract enforced on every dispatch and redirect.
    pub egress_contract: Option<HttpEgressContract>,
}

pub enum ElasticAuthConfig {
    /// Sends `Authorization: ApiKey <key>`.
    ApiKey(String),
    /// HTTP Basic auth; the password is zeroized on drop.
    Basic { username: String, password: Zeroizing<String> },
}

How receipts are used

Security teams inspect guard evidence and request history. Finance teams reconcile charges against budgets. Compliance teams compare the policy hash with the policy in force for a decision. Reputation systems can derive denial, spending, and cancellation rates for agents and tool servers. These uses share the signed receipt format.

For the budget lifecycle, see Economics which explains how a priced receipt reconciles against a budget. Session-Aware Guards explains how to use deny-rate and spending signals from receipt records.


Receipt DAG: child request records

Nested operations within a parent tool call produce ChildRequestReceipt records. These link back to the parent via parent_request_id, forming a DAG of parent/child receipt chains across nested operations. Each child records its operation kind (e.g., CreateMessage) and terminal state: Completed, Cancelled, or Incomplete. A denied request still reaches a terminal Completed state; the terminal state tracks lifecycle completion, not the authorization verdict.

rust
pub struct ChildRequestReceipt {
    pub id: String,
    pub timestamp: u64,
    pub session_id: SessionId,
    pub parent_request_id: RequestId,
    pub request_id: RequestId,
    pub operation_kind: OperationKind,
    pub terminal_state: OperationTerminalState,
    pub outcome_hash: String,
    pub policy_hash: String,
    pub metadata: Option<serde_json::Value>,
    pub kernel_key: PublicKey,
    pub signature: Signature,
}

Child request receipts are signed and verified identically to standard receipts, maintaining the same non-repudiation guarantees across nested operations.