Skip to main content

chio_kernel/
receipt_store.rs

1use chio_core::capability::CapabilityToken;
2use chio_core::credit::CreditBondRow;
3use chio_core::receipt::{ChildRequestReceipt, ChioReceipt};
4
5use crate::capability_lineage::CapabilitySnapshot;
6use crate::checkpoint::KernelCheckpoint;
7
8/// Configuration for receipt retention and archival.
9///
10/// When set on `KernelConfig`, the kernel can archive aged-out or oversized
11/// receipt databases to a separate read-only SQLite file while keeping archived
12/// receipts verifiable against their Merkle checkpoint roots.
13#[derive(Debug, Clone)]
14pub struct RetentionConfig {
15    /// Number of days to retain receipts in the live database. Default: 90.
16    pub retention_days: u64,
17    /// Maximum size in bytes before the live database is rotated. Default: 10 GB.
18    pub max_size_bytes: u64,
19    /// Path for the archive SQLite file. Must be writable on first rotation.
20    pub archive_path: String,
21    /// Optional tenant scope for retention. When set, rotation only archives
22    /// receipts for this tenant and leaves other tenant evidence untouched.
23    pub tenant_id: Option<String>,
24}
25
26impl Default for RetentionConfig {
27    fn default() -> Self {
28        Self {
29            retention_days: 90,
30            max_size_bytes: 10_737_418_240,
31            archive_path: "receipts-archive.sqlite3".to_string(),
32            tenant_id: None,
33        }
34    }
35}
36
37#[derive(Debug, thiserror::Error)]
38pub enum ReceiptStoreError {
39    #[error("sqlite error: {0}")]
40    Sqlite(#[from] rusqlite::Error),
41
42    #[error("sqlite pool error: {0}")]
43    Pool(String),
44
45    #[error("serialization error: {0}")]
46    Json(#[from] serde_json::Error),
47
48    #[error("failed to prepare receipt store directory: {0}")]
49    Io(#[from] std::io::Error),
50
51    #[error("crypto decode error: {0}")]
52    CryptoDecode(String),
53
54    #[error("canonical json error: {0}")]
55    Canonical(String),
56
57    #[error("invalid outcome filter: {0}")]
58    InvalidOutcome(String),
59
60    #[error("conflict: {0}")]
61    Conflict(String),
62
63    #[error("not found: {0}")]
64    NotFound(String),
65}
66
67pub trait ReceiptStore: Send {
68    fn append_chio_receipt(&mut self, receipt: &ChioReceipt) -> Result<(), ReceiptStoreError>;
69    fn append_chio_receipt_returning_seq(
70        &mut self,
71        receipt: &ChioReceipt,
72    ) -> Result<Option<u64>, ReceiptStoreError> {
73        self.append_chio_receipt(receipt)?;
74        Ok(None)
75    }
76    fn append_child_receipt(
77        &mut self,
78        receipt: &ChildRequestReceipt,
79    ) -> Result<(), ReceiptStoreError>;
80
81    fn receipts_canonical_bytes_range(
82        &self,
83        _start_seq: u64,
84        _end_seq: u64,
85    ) -> Result<Vec<(u64, Vec<u8>)>, ReceiptStoreError> {
86        Ok(Vec::new())
87    }
88
89    fn store_checkpoint(
90        &mut self,
91        _checkpoint: &KernelCheckpoint,
92    ) -> Result<(), ReceiptStoreError> {
93        Ok(())
94    }
95
96    fn load_checkpoint_by_seq(
97        &self,
98        _checkpoint_seq: u64,
99    ) -> Result<Option<KernelCheckpoint>, ReceiptStoreError> {
100        Ok(None)
101    }
102
103    fn supports_kernel_signed_checkpoints(&self) -> bool {
104        false
105    }
106
107    fn record_capability_snapshot(
108        &mut self,
109        _token: &CapabilityToken,
110        _parent_capability_id: Option<&str>,
111    ) -> Result<(), ReceiptStoreError> {
112        Ok(())
113    }
114
115    fn get_capability_snapshot(
116        &self,
117        _capability_id: &str,
118    ) -> Result<Option<CapabilitySnapshot>, ReceiptStoreError> {
119        Ok(None)
120    }
121
122    fn get_capability_delegation_chain(
123        &self,
124        _capability_id: &str,
125    ) -> Result<Vec<CapabilitySnapshot>, ReceiptStoreError> {
126        Ok(Vec::new())
127    }
128
129    fn resolve_credit_bond(
130        &self,
131        _bond_id: &str,
132    ) -> Result<Option<CreditBondRow>, ReceiptStoreError> {
133        Ok(None)
134    }
135
136    /// Persist a serialized `SessionAnchor` while the concrete P1-A type remains in flight.
137    fn record_session_anchor(
138        &mut self,
139        _session_id: &str,
140        _anchor_id: &str,
141        _auth_context_fingerprint: &str,
142        _issued_at: u64,
143        _supersedes_anchor_id: Option<&str>,
144        _anchor_json: &serde_json::Value,
145    ) -> Result<(), ReceiptStoreError> {
146        Ok(())
147    }
148
149    /// Persist a serialized `RequestLineageRecord` while the concrete P1-A type remains in flight.
150    #[allow(clippy::too_many_arguments)]
151    fn record_request_lineage(
152        &mut self,
153        _session_id: &str,
154        _request_id: &str,
155        _parent_request_id: Option<&str>,
156        _session_anchor_id: Option<&str>,
157        _recorded_at: u64,
158        _request_fingerprint: Option<&str>,
159        _lineage_json: &serde_json::Value,
160    ) -> Result<(), ReceiptStoreError> {
161        Ok(())
162    }
163
164    /// Persist a serialized `ReceiptLineageStatement` while the concrete P1-A type remains in flight.
165    #[allow(clippy::too_many_arguments)]
166    fn record_receipt_lineage_statement(
167        &mut self,
168        _child_receipt_id: &str,
169        _request_id: Option<&str>,
170        _session_id: Option<&str>,
171        _session_anchor_id: Option<&str>,
172        _parent_request_id: Option<&str>,
173        _parent_receipt_id: Option<&str>,
174        _chain_id: Option<&str>,
175        _recorded_at: u64,
176        _statement_json: &serde_json::Value,
177    ) -> Result<(), ReceiptStoreError> {
178        Ok(())
179    }
180
181    fn get_receipt_lineage_verification(
182        &self,
183        _receipt_id: &str,
184    ) -> Result<Option<ReceiptLineageVerification>, ReceiptStoreError> {
185        Ok(None)
186    }
187
188    fn list_receipt_lineage_statement_links(
189        &self,
190        _receipt_id: &str,
191    ) -> Result<Vec<ReceiptLineageStatementLink>, ReceiptStoreError> {
192        Ok(Vec::new())
193    }
194
195    fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
196        None
197    }
198}
199
200#[derive(Debug, Clone)]
201pub struct StoredToolReceipt {
202    pub seq: u64,
203    pub receipt: ChioReceipt,
204}
205
206#[derive(Debug, Clone)]
207pub struct StoredChildReceipt {
208    pub seq: u64,
209    pub receipt: ChildRequestReceipt,
210}
211
212#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
213#[serde(rename_all = "camelCase")]
214pub struct ReceiptLineageVerification {
215    pub receipt_id: String,
216    #[serde(default, skip_serializing_if = "Option::is_none")]
217    pub request_id: Option<String>,
218    #[serde(default, skip_serializing_if = "Option::is_none")]
219    pub session_id: Option<String>,
220    #[serde(default, skip_serializing_if = "Option::is_none")]
221    pub session_anchor_id: Option<String>,
222    pub session_anchor_verified: bool,
223    pub parent_request_verified: bool,
224    pub parent_receipt_verified: bool,
225    pub replay_protected: bool,
226}
227
228impl ReceiptLineageVerification {
229    #[must_use]
230    pub fn delegated_call_chain_bound(&self) -> bool {
231        self.parent_receipt_verified
232            || (self.session_anchor_verified && self.parent_request_verified)
233    }
234}
235
236#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
237#[serde(rename_all = "camelCase")]
238pub struct ReceiptLineageStatementLink {
239    #[serde(default, skip_serializing_if = "Option::is_none")]
240    pub statement_id: Option<String>,
241    pub child_receipt_id: String,
242    #[serde(default, skip_serializing_if = "Option::is_none")]
243    pub child_request_id: Option<String>,
244    #[serde(default, skip_serializing_if = "Option::is_none")]
245    pub parent_receipt_id: Option<String>,
246    #[serde(default, skip_serializing_if = "Option::is_none")]
247    pub parent_request_id: Option<String>,
248    #[serde(default, skip_serializing_if = "Option::is_none")]
249    pub session_id: Option<String>,
250    #[serde(default, skip_serializing_if = "Option::is_none")]
251    pub session_anchor_id: Option<String>,
252    #[serde(default, skip_serializing_if = "Option::is_none")]
253    pub chain_id: Option<String>,
254    pub recorded_at: u64,
255}
256
257#[derive(Debug, Clone)]
258pub struct FederatedEvidenceShareImport {
259    pub share_id: String,
260    pub manifest_hash: String,
261    pub exported_at: u64,
262    pub issuer: String,
263    pub partner: String,
264    pub signer_public_key: String,
265    pub require_proofs: bool,
266    pub query_json: String,
267    pub tool_receipts: Vec<StoredToolReceipt>,
268    pub capability_lineage: Vec<CapabilitySnapshot>,
269}
270
271#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
272#[serde(rename_all = "camelCase")]
273pub struct FederatedEvidenceShareSummary {
274    pub share_id: String,
275    pub manifest_hash: String,
276    pub imported_at: u64,
277    pub exported_at: u64,
278    pub issuer: String,
279    pub partner: String,
280    pub signer_public_key: String,
281    pub require_proofs: bool,
282    pub tool_receipts: u64,
283    pub capability_lineage: u64,
284}