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#[derive(Debug, Clone)]
14pub struct RetentionConfig {
15 pub retention_days: u64,
17 pub max_size_bytes: u64,
19 pub archive_path: String,
21 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 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 #[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 #[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}