1#![forbid(unsafe_code)]
26
27use std::path::{Path, PathBuf};
28
29pub mod admission_operation_store;
30pub mod approval_store;
31pub mod authority;
32pub mod batch_approval_store;
33pub mod budget_store;
34pub mod capability_lineage;
35pub mod channel_lifecycle_store;
36pub mod channel_release_publisher_store;
37pub mod clearing_lifecycle_store;
38pub mod dead_letters;
39pub mod economic_state_cache;
40pub mod encrypted_blob;
41pub mod evidence_export;
42pub mod execution_nonce_store;
43pub mod fiscal_store;
44pub mod frost_store;
45pub mod iou_store;
46#[cfg(feature = "lineage")]
47pub mod lineage_cte;
48pub mod memory_provenance_store;
49pub mod receipt_query;
50pub mod receipt_store;
51pub mod revocation_store;
52pub mod schema_version;
53pub mod serving_owner;
54pub mod settle_attempts;
55pub mod tool_outcome_store;
56
57pub use chio_core::crypto::SharedCanonicalBytes;
58pub use chio_core::{CanonicalBytes, CanonicalJsonWitness};
59pub use chio_kernel::{EvidenceChildReceiptScope, EvidenceExportQuery};
60
61pub const DEFAULT_READER_POOL_MAX_SIZE: u32 = 8;
65
66pub const DEFAULT_WRITER_POOL_MAX_SIZE: u32 = 1;
71
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
75pub struct SqlitePoolConfig {
76 pub reader_pool_max_size: u32,
77 pub writer_pool_max_size: u32,
78 pub max_page_count: Option<u32>,
87}
88
89impl Default for SqlitePoolConfig {
90 fn default() -> Self {
91 Self {
92 reader_pool_max_size: DEFAULT_READER_POOL_MAX_SIZE,
93 writer_pool_max_size: DEFAULT_WRITER_POOL_MAX_SIZE,
94 max_page_count: None,
95 }
96 }
97}
98
99#[derive(Clone, Copy, Debug, Eq, PartialEq)]
101pub struct SqliteStoreOptions {
102 pub pool: SqlitePoolConfig,
103 pub incremental_verification: bool,
108}
109
110impl Default for SqliteStoreOptions {
111 fn default() -> Self {
112 Self {
113 pool: SqlitePoolConfig::default(),
114 incremental_verification: true,
115 }
116 }
117}
118
119#[must_use]
126pub fn is_in_memory_sqlite_path(path: &str) -> bool {
127 if path.eq_ignore_ascii_case(":memory:") {
128 return true;
129 }
130 let Some(rest) = path.strip_prefix("file:") else {
131 return false;
132 };
133 let (name, query) = match rest.split_once('?') {
134 Some((name, query)) => (name, Some(query)),
135 None => (rest, None),
136 };
137 if name.eq_ignore_ascii_case(":memory:") {
138 return true;
139 }
140 query.is_some_and(|query| {
141 query
142 .split('&')
143 .any(|param| param.eq_ignore_ascii_case("mode=memory"))
144 })
145}
146
147#[must_use]
159pub(crate) fn sqlite_parent_dir_to_create(path: &Path) -> Option<PathBuf> {
160 let Some(text) = path.to_str() else {
161 return non_empty_parent(path);
163 };
164 if is_in_memory_sqlite_path(text) {
165 return None;
166 }
167 non_empty_parent(&sqlite_filesystem_path(text))
168}
169
170fn non_empty_parent(path: &Path) -> Option<PathBuf> {
173 path.parent()
174 .filter(|parent| !parent.as_os_str().is_empty())
175 .map(Path::to_path_buf)
176}
177
178#[must_use]
182pub fn sqlite_filesystem_path(text: &str) -> PathBuf {
183 let Some(rest) = text.strip_prefix("file:") else {
184 return PathBuf::from(text);
185 };
186 let without_query = rest.split_once('?').map_or(rest, |(name, _query)| name);
189 let filesystem = match without_query.strip_prefix("//") {
193 Some(after_authority_marker) => match after_authority_marker.find('/') {
194 Some(path_start) => &after_authority_marker[path_start..],
195 None => "",
196 },
197 None => without_query,
198 };
199 PathBuf::from(filesystem)
200}
201
202pub use admission_operation_store::{
203 CreditExposureAccountSnapshot, DurableObligationV1, SqliteAdmissionOperationStore,
204};
205pub use approval_store::SqliteApprovalStore;
206pub use authority::SqliteCapabilityAuthority;
207pub use batch_approval_store::SqliteBatchApprovalStore;
208pub use budget_store::{BudgetStoreSnapshot, SqliteBudgetStore};
209pub use channel_lifecycle_store::{
210 ChannelLifecycleStoreError, ChannelPreparedAdmissionRecordV1, ChannelPreparedBeginResult,
211 ChannelReservationDispositionV1, ChannelReservationStageRecordV1, SqliteChannelLifecycleStore,
212};
213pub use channel_release_publisher_store::{
214 ChannelReleasePublicationRecordV1, ChannelReleasePublicationStatusV1,
215 ChannelReleasePublisherError, ChannelReleaseSubmissionOutcomeV1,
216 SqliteChannelReleasePublisherStore, VerifiedChannelReleasePublicationV1,
217};
218pub use clearing_lifecycle_store::{ClearingLifecycleStoreError, SqliteClearingLifecycleStore};
219pub use economic_state_cache::{
220 admission_terminal_projection_effect_result, EconomicOperationStageBinding,
221 EconomicOperationStageContext, EconomicStateCacheError, EconomicStateStageDescriptor,
222 EconomicStateStageRecord, EconomicStateStageStatus, SqliteEconomicStateCache,
223};
224pub use encrypted_blob::{
225 decrypt_blob, encrypt_blob, BlobHandle, BlobStoreError, DecryptError, EncryptError,
226 EncryptedBlob, SqliteEncryptedBlobStore, TenantId, TenantKey,
227};
228pub use execution_nonce_store::{SqliteExecutionNonceStore, SqliteExecutionNonceStoreError};
229pub use frost_store::{
230 FrostActiveRosterRecord, FrostCeremonyRecord, FrostCeremonyRound1Record,
231 FrostCeremonyRound2Record, FrostCeremonyState, FrostCoordinatorCancellation,
232 FrostCoordinatorCommitment, FrostCoordinatorLease, FrostCoordinatorSessionRecord,
233 FrostCoordinatorSessionRequest, FrostCoordinatorSessionState, FrostCoordinatorShare,
234 FrostCoordinatorSigningPackage, FrostCustodyKey, FrostRotationRecord, FrostRotationState,
235 FrostSignerCommitment, FrostSignerSessionRecord, FrostSignerSessionRequest,
236 FrostSignerSessionState, FrostSignerShare, FrostStoreError, SqliteFrostStore,
237 StagedFrostRotation, StoredFrostCeremonyCompletion,
238};
239pub use iou_store::{SqliteIouEnvelopeStore, IOU_ENVELOPE_MIGRATION};
240pub use memory_provenance_store::{SqliteMemoryProvenanceStore, SqliteMemoryProvenanceStoreError};
241pub use receipt_store::{BackgroundCheckpointSigner, SqliteReceiptStore};
242pub use revocation_store::SqliteRevocationStore;
243pub use schema_version::{
244 check_schema_version, stamp_schema_version, SchemaVersionError, CHIO_SQLITE_APPLICATION_ID,
245};
246pub use serving_owner::{
247 scope_fixed_authority_ids_for_current_thread, FixedAuthorityIdScope, SqliteAuthorityStore,
248 SqliteServingOwnerError,
249};
250
251impl chio_kernel::QualifiedAdmissionProjectionStore
252 for admission_operation_store::SqliteAdmissionOperationStore
253{
254 fn load_payment_journal(
255 &self,
256 operation_id: &str,
257 active_fence: &chio_kernel::admission_operation::StoreMutationFence,
258 ) -> Result<
259 Option<chio_kernel::payment::PaymentJournalRecord>,
260 chio_kernel::AdmissionPaymentJournalError,
261 > {
262 admission_operation_store::SqliteAdmissionOperationStore::load_payment_journal(
263 self,
264 operation_id,
265 active_fence,
266 )
267 }
268
269 fn advance_payment_journal(
270 &self,
271 advance: chio_kernel::AdmissionPaymentJournalAdvance<'_>,
272 ) -> Result<chio_kernel::payment::PaymentJournalRecord, chio_kernel::AdmissionPaymentJournalError>
273 {
274 admission_operation_store::SqliteAdmissionOperationStore::advance_payment_journal(
275 self, advance,
276 )
277 }
278
279 fn begin_payment_settlement(
280 &self,
281 begin: chio_kernel::AdmissionPaymentSettlementBegin<'_>,
282 ) -> Result<chio_kernel::AdmissionPaymentSettlement, chio_kernel::AdmissionPaymentJournalError>
283 {
284 admission_operation_store::SqliteAdmissionOperationStore::begin_payment_settlement(
285 self, begin,
286 )
287 }
288
289 fn authorize_budget_and_commit_admission(
290 &self,
291 operation: &chio_kernel::admission_operation::AdmissionOperationV1,
292 recovery_lease: &chio_kernel::admission_operation::AdmissionRecoveryLease,
293 request: chio_kernel::budget_store::BudgetAuthorizeHoldRequest,
294 payment_journal: Option<chio_kernel::payment::PaymentJournalRecord>,
295 credit_exposure: Option<chio_kernel::CreditExposureReservationRequest>,
296 active_fence: &chio_kernel::admission_operation::StoreMutationFence,
297 trusted_now_unix_ms: u64,
298 ) -> Result<
299 chio_kernel::AdmissionBudgetAuthorization,
300 chio_kernel::AdmissionBudgetAuthorizationError,
301 > {
302 admission_operation_store::SqliteAdmissionOperationStore::authorize_budget_and_commit_admission(
303 self,
304 operation,
305 recovery_lease,
306 request,
307 payment_journal,
308 credit_exposure,
309 active_fence,
310 trusted_now_unix_ms,
311 )
312 .map(|(decision, operation)| chio_kernel::AdmissionBudgetAuthorization {
313 decision,
314 operation,
315 })
316 .map_err(|error| match error {
317 chio_kernel::admission_operation::AdmissionCaptureError::Unavailable(detail) => {
318 chio_kernel::AdmissionBudgetAuthorizationError::Unavailable(detail)
319 }
320 chio_kernel::admission_operation::AdmissionCaptureError::Fenced => {
321 chio_kernel::AdmissionBudgetAuthorizationError::Fenced
322 }
323 chio_kernel::admission_operation::AdmissionCaptureError::OutcomeUnknown(detail) => {
324 chio_kernel::AdmissionBudgetAuthorizationError::OutcomeUnknown(detail)
325 }
326 chio_kernel::admission_operation::AdmissionCaptureError::Invariant(detail) => {
327 chio_kernel::AdmissionBudgetAuthorizationError::Invariant(detail)
328 }
329 chio_kernel::admission_operation::AdmissionCaptureError::Operation(error) => {
330 chio_kernel::AdmissionBudgetAuthorizationError::Operation(error)
331 }
332 })
333 }
334
335 fn capture_invocation_and_commit_dispatch(
336 &self,
337 operation: &chio_kernel::admission_operation::AdmissionOperationV1,
338 recovery_lease: &chio_kernel::admission_operation::AdmissionRecoveryLease,
339 request: chio_kernel::budget_store::BudgetCaptureInvocationRequest,
340 active_fence: &chio_kernel::admission_operation::StoreMutationFence,
341 trusted_now_unix_ms: u64,
342 ) -> Result<
343 chio_kernel::AdmissionBudgetCapture,
344 chio_kernel::admission_operation::AdmissionCaptureError,
345 > {
346 admission_operation_store::SqliteAdmissionOperationStore::capture_invocation_and_commit_dispatch(
347 self,
348 operation,
349 recovery_lease,
350 request,
351 active_fence,
352 trusted_now_unix_ms,
353 )
354 .map(|(decision, operation)| chio_kernel::AdmissionBudgetCapture {
355 decision,
356 operation,
357 })
358 }
359
360 fn reserve_threshold_approval_and_commit_admission(
361 &self,
362 command: &chio_kernel::admission_operation::AdmissionOperationCommand,
363 reservation: &chio_kernel::ThresholdApprovalReplayReservationV1,
364 trusted_now_unix_ms: u64,
365 ) -> Result<
366 chio_kernel::admission_operation::AdmissionCommandResult,
367 chio_kernel::admission_operation::AdmissionOperationStoreError,
368 > {
369 admission_operation_store::SqliteAdmissionOperationStore::reserve_threshold_approval_and_commit_admission(
370 self,
371 command,
372 reservation,
373 trusted_now_unix_ms,
374 )
375 }
376
377 fn list_admission_receipts_after(
378 &self,
379 after_receipt_id: Option<&str>,
380 limit: usize,
381 ) -> Result<Vec<chio_core::receipt::body::ChioReceipt>, chio_kernel::ReceiptStoreError> {
382 self.list_terminal_receipts_after(after_receipt_id, limit)
383 }
384}
385
386impl chio_credit::obligation::CreditAdmissionStore
387 for admission_operation_store::SqliteAdmissionOperationStore
388{
389 fn lookup_record_by_operation(
390 &self,
391 operation_id: &str,
392 ) -> Result<
393 Option<chio_credit::obligation::CreditExposureReservationRecordV1>,
394 chio_credit::obligation::CreditAdmissionError,
395 > {
396 self.load_credit_exposure_reservation(operation_id)
397 .map_err(|error| {
398 chio_credit::obligation::CreditAdmissionError::Store(error.to_string())
399 })
400 }
401}
402
403impl chio_kernel::receipt_store::AnchoredAdmissionProjectionStore
404 for admission_operation_store::SqliteAdmissionOperationStore
405{
406 fn stage_anchored_terminal_projection(
407 &self,
408 advance: &chio_core::economic_continuity::VerifiedEconomicStateBatchAdvance,
409 recovery_lease: &chio_kernel::admission_operation::AdmissionRecoveryLease,
410 envelope: &chio_kernel::admission_operation::SignedAdmissionTerminalProjectionV1,
411 active_fence: &chio_kernel::admission_operation::StoreMutationFence,
412 trusted_now_unix_ms: u64,
413 ) -> Result<(), chio_kernel::ReceiptStoreError> {
414 admission_operation_store::SqliteAdmissionOperationStore::stage_anchored_terminal_projection(
415 self,
416 advance,
417 recovery_lease,
418 envelope,
419 active_fence,
420 trusted_now_unix_ms,
421 )
422 .map_err(admission_operation_store::receipt_projection_error)
423 }
424
425 fn qualify_anchored_terminal_projection(
426 &self,
427 batch_id: &str,
428 active_fence: &chio_kernel::admission_operation::StoreMutationFence,
429 trusted_now_unix_ms: u64,
430 ) -> Result<(), chio_kernel::ReceiptStoreError> {
431 admission_operation_store::SqliteAdmissionOperationStore::qualify_anchored_terminal_projection(
432 self,
433 batch_id,
434 active_fence,
435 trusted_now_unix_ms,
436 )
437 .map_err(admission_operation_store::receipt_projection_error)
438 }
439
440 fn record_anchored_terminal_projection(
441 &self,
442 advance: &chio_core::economic_continuity::VerifiedEconomicStateBatchAdvance,
443 committed: &chio_core::economic_continuity::VerifiedEconomicStateView,
444 pins: &chio_core::economic_continuity::EconomicStateAnchorPins,
445 active_fence: &chio_kernel::admission_operation::StoreMutationFence,
446 trusted_now_unix_ms: u64,
447 ) -> Result<(), chio_kernel::ReceiptStoreError> {
448 admission_operation_store::SqliteAdmissionOperationStore::record_anchored_terminal_projection(
449 self,
450 advance,
451 committed,
452 pins,
453 active_fence,
454 trusted_now_unix_ms,
455 )
456 .map_err(admission_operation_store::receipt_projection_error)
457 }
458
459 fn commit_anchored_terminal_projection(
460 &self,
461 batch_id: &str,
462 active_fence: &chio_kernel::admission_operation::StoreMutationFence,
463 trusted_now_unix_ms: u64,
464 ) -> Result<chio_kernel::admission_operation::AdmissionTerminal, chio_kernel::ReceiptStoreError>
465 {
466 admission_operation_store::SqliteAdmissionOperationStore::commit_anchored_terminal_projection(
467 self,
468 batch_id,
469 active_fence,
470 trusted_now_unix_ms,
471 )
472 .map_err(admission_operation_store::receipt_projection_error)
473 }
474}
475pub use settle_attempts::{SqliteSettlementOutcomeStore, SETTLE_ATTEMPTS_MIGRATION};
476pub use tool_outcome_store::SqliteToolOutcomeStore;
477
478#[cfg(test)]
479mod tests {
480 use super::{is_in_memory_sqlite_path, sqlite_parent_dir_to_create};
481 use std::path::{Path, PathBuf};
482
483 #[test]
484 fn resolves_parent_dir_from_file_uris_and_plain_paths() {
485 assert_eq!(
488 sqlite_parent_dir_to_create(Path::new(
489 "file:/var/lib/chio/receipts.db.revocations?mode=rwc"
490 )),
491 Some(PathBuf::from("/var/lib/chio"))
492 );
493 assert_eq!(
495 sqlite_parent_dir_to_create(Path::new("file:///var/lib/chio/db?cache=shared")),
496 Some(PathBuf::from("/var/lib/chio"))
497 );
498 assert_eq!(
500 sqlite_parent_dir_to_create(Path::new("/var/lib/chio/receipts.db")),
501 Some(PathBuf::from("/var/lib/chio"))
502 );
503 assert_eq!(sqlite_parent_dir_to_create(Path::new("receipts.db")), None);
505 assert_eq!(
506 sqlite_parent_dir_to_create(Path::new("file:receipts.db?mode=rwc")),
507 None
508 );
509 assert_eq!(sqlite_parent_dir_to_create(Path::new(":memory:")), None);
511 assert_eq!(
512 sqlite_parent_dir_to_create(Path::new("file:receipts.db?mode=memory")),
513 None
514 );
515 }
516
517 #[test]
518 fn classifies_in_memory_sqlite_paths() {
519 for path in [
520 ":memory:",
521 ":MEMORY:",
522 "file::memory:",
523 "file:receipts.db?mode=memory",
524 "file:receipts.db?cache=shared&mode=memory",
525 ] {
526 assert!(
527 is_in_memory_sqlite_path(path),
528 "{path} must classify as in-memory"
529 );
530 }
531 }
532
533 #[test]
534 fn classifies_durable_sqlite_paths() {
535 for path in [
536 "receipts.db",
537 "/var/lib/chio/receipts.db",
538 "file:/var/lib/chio/receipts.db?mode=rwc",
539 "file:receipts.db",
540 "memory-notes.db",
541 ] {
542 assert!(
543 !is_in_memory_sqlite_path(path),
544 "{path} must classify as durable"
545 );
546 }
547 }
548}