Chio/Docs

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

The kernel signs a receipt for every decision: allow, deny, cancelled, and incomplete. There is no silent path through the kernel. If a tool call was attempted, a receipt exists.
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's content_hash is covered by the ed25519 signature, and the next receipt quotes the previous content_hash in prev_hash — so any post-hoc edit invalidates every forward signature.

The ChioReceipt Struct

The ChioReceipt struct contains all fields necessary for independent verification:

chio-core-types/src/receipt.rs
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).

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,
    },
}

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:

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>,
}

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.

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

Auditors get the policy hash in every receipt; they don't have to take your word for which rules were live. The hash is the policy at that instant, not a description of it.

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 is critical

The use of RFC 8785 canonical JSON ensures that the same receipt body always produces the same byte sequence, regardless of serializer implementation or field ordering. Without canonical serialization, signature verification would be fragile and non-portable.

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)
    }
}

Full 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"
  },
  "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:

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"
  },
  "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:

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

The receipt log's append-only property is fundamental to the audit guarantee. If receipts could be modified or deleted after creation, the entire non-repudiation property would collapse. The SQLite store enforces this at the application level; production deployments should also enforce it at the infrastructure level (e.g., write-once storage, replication).

Querying

The chio receipt list command queries the receipt log with filtering support:

bash
# 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
FilterFlagDescription
Tool server--tool-serverFilter by tool server identifier
Tool name--tool-nameFilter by tool name
Outcome--outcomeFilter by decision (allow, deny, cancelled, incomplete)
Start time--sinceReceipts created after this timestamp
End time--untilReceipts created before this timestamp
Min cost--min-costMinimum cost charged (financial metadata)
Max cost--max-costMaximum 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-config.yaml
siem:
  adapter: splunk_hec
  endpoint: https://splunk.example.com:8088/services/collector
  token: $SPLUNK_HEC_TOKEN
  index: chio_receipts
  source_type: chio:receipt

Elasticsearch

The Elasticsearch adapter indexes receipts as documents, enabling full-text search across all receipt fields.

siem-config.yaml
siem:
  adapter: elasticsearch
  endpoint: https://elasticsearch.example.com:9200
  index: chio-receipts
  api_key: $ES_API_KEY

One 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).

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.