Receipts
Every tool invocation, whether allowed or denied, produces a signed receipt. Receipts are the immutable audit trail of the chio protocol: non-repudiable, cryptographically verifiable evidence of every kernel decision. The receipt log is Merkle-committed in batched checkpoints, so any single receipt can be independently verified via an inclusion proof without replaying the full log.
What Are Receipts
A receipt is a signed JSON document that records exactly what happened when an agent attempted to use a tool. The kernel produces one receipt per decision, regardless of outcome. 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
Receipts are always produced
allow, deny, cancelled, and incomplete. There is no silent path through the kernel. If a tool call was attempted, a receipt exists.The ChioReceipt Struct
The ChioReceipt struct contains all fields necessary for independent verification:
pub struct ChioReceipt {
/// Unique receipt ID (UUIDv7 recommended).
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.
pub decision: Decision,
/// 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>,
/// The Kernel's public key (for verification without out-of-band lookup).
pub kernel_key: PublicKey,
/// Signing algorithm. Absent means Ed25519 for backward compatibility.
pub algorithm: Option<SigningAlgorithm>,
/// Signature over canonical JSON of all fields above.
pub signature: Signature,
}See the Receipt Format reference for the complete field-by-field shape and wire semantics.
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).
#[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,
},
}Every Deny carries the name of the guard that triggered it, so a receipt tells you not just that the call was blocked, but exactly which rule blocked it.
ToolCallAction
The action field captures the parameters that were passed to the tool, along with a self-verifying hash:
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:
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>,
}Because the pipeline short-circuits on the first deny, 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.
[
{"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.
# 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 bytesPolicy 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
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.
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:
- Construct the
ChioReceiptBody(all fields except signature) - Serialize the body to canonical JSON per RFC 8785 (deterministic key ordering, no insignificant whitespace)
- Sign the canonical bytes with the kernel's ed25519 private key
- Attach the resulting signature to produce the final
ChioReceipt
Canonical JSON is critical
Receipt Verification
Third parties can verify receipts independently without contacting the kernel. The receipt is self-contained: it includes the kernel's public key.
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:
- Extract the
ChioReceiptBodyfrom the receipt (all fields except signature) - Serialize the body to canonical JSON (RFC 8785)
- Verify the ed25519 signature using the embedded
kernel_key - Optionally verify the
kernel_keyagainst 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:
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)
}
}Full Receipt Example
Here is a complete receipt as it appears in the receipt log. This example shows an allowed file read operation:
{
"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"
},
"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
}
},
"kernel_key": "ed25519:pub:9c7b3f...",
"signature": "ed25519:e5f6a7b8..."
}And a denied request:
{
"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"
},
"content_hash": "sha256:0000000000000000...",
"policy_hash": "abc123def456",
"evidence": [
{"guard_name": "forbidden-path", "verdict": false, "details": "path /etc/passwd matches pattern /etc/passwd"}
],
"metadata": null,
"kernel_key": "ed25519:pub:9c7b3f...",
"signature": "ed25519:a1b2c3d4..."
}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:
| Field | Type | Purpose |
|---|---|---|
grant_index | u32 | Index of the matched grant within the capability token |
cost_charged | u64 | Cost actually charged (minor units) |
currency | String | ISO 4217 currency code of the charge |
budget_remaining | u64 | Remaining budget after this invocation |
budget_total | u64 | Total grant budget |
delegation_depth | u32 | Depth in the delegation chain (0 = root) |
root_budget_holder | String | Identity of the root budget holder |
payment_reference | Option<String> | External payment reference for settlement |
settlement_status | SettlementStatus | One of NotApplicable, Pending, Settled, Failed |
cost_breakdown | Option<Value> | Optional itemized cost breakdown reported by the tool |
oracle_evidence | Option<OracleConversionEvidence> | Cross-currency conversion evidence, when applicable |
attempted_cost | Option<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).
{
"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 the OracleConversionEvidence record: from_currency, to_currency, rate_numerator (u64), rate_denominator (u64), margin_bps (u32), oracle_source, and rate_timestamp (u64).
Storage
Receipts are stored in an append-only log backed by a SQLite store. The append-only constraint is enforced at the storage layer: receipts can be inserted but never updated or deleted. The log is Merkle-committed in batched checkpoints, and inclusion proofs let any individual receipt be verified without replaying the entire log.
Append-only is a trust invariant
Querying
The chio receipt list command queries the receipt log with filtering support:
# List all receipts for a specific tool server
$ chio receipt list --tool-server srv-files
# Filter by tool name and outcome
$ chio receipt list --tool-name file_read --outcome allow
# Time range queries
$ chio receipt list --since 2025-01-01T00:00:00Z --until 2025-01-02T00:00:00Z
# Filter by cost
$ chio receipt list --min-cost 100 --max-cost 500
# Combine filters
$ chio receipt list \
--tool-server srv-files \
--tool-name file_read \
--outcome deny \
--since 2025-01-01T00:00:00Z| Filter | Flag | Description |
|---|---|---|
| Tool server | --tool-server | Filter by tool server identifier |
| Tool name | --tool-name | Filter by tool name |
| Outcome | --outcome | Filter by decision (allow, deny, cancelled, incomplete) |
| Start time | --since | Receipts created after this timestamp |
| End time | --until | Receipts created before this timestamp |
| Min cost | --min-cost | Minimum cost charged (financial metadata) |
| Max cost | --max-cost | Maximum cost charged (financial metadata) |
SIEM Export
Receipts can be exported to external security information and event management (SIEM) systems for centralized monitoring and alerting. Chio supports two built-in adapters:
Splunk HEC
The Splunk HTTP Event Collector adapter streams receipts as structured events to a Splunk instance. Each receipt becomes a Splunk event with full field extraction.
siem:
adapter: splunk_hec
endpoint: https://splunk.example.com:8088/services/collector
token: $SPLUNK_HEC_TOKEN
index: chio_receipts
source_type: chio:receiptElasticsearch
The Elasticsearch adapter indexes receipts as documents, enabling full-text search across all receipt fields.
siem:
adapter: elasticsearch
endpoint: https://elasticsearch.example.com:9200
index: chio-receipts
api_key: $ES_API_KEYOne artifact, four readers
The same signed receipt does four jobs at once. Security reads the evidence array and reconstructs every action an agent took, with cryptographic proof nothing has been edited. Finance reads the financial metadata and reconciles charges against budgets, with both sides able to verify the chain independently. Compliance reads the policy hash and proves which rules were live at the moment of each decision, the kind of evidence SOC 2 and ISO 27001 demand. Reputation systems read the deny rate, the spending pattern, and the cancellation rate, and assemble portable scorecards for agents and tool servers alike. None of these readers need a translation layer: they are all looking at the same canonical bytes.
Receipt DAG: Child Request Receipts
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, Failed, Cancelled).
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.