sdk

Radroots SDK and bindings
git clone https://radroots.dev/git/sdk.git
Log | Files | Refs | README

commit fdf5e1bcbd9d6173408e949580112240d861bbbc
parent fde2294678edc117625e94c6735fac1250fe6b5c
Author: triesap <tyson@radroots.org>
Date:   Sun,  5 Jul 2026 22:07:07 +0000

runtime: split storage checkpoint evidence

- make storage status report passive WAL observation state
- add explicit storage checkpoint request and receipt DTOs
- map SQLite helper failures through store-specific SDK errors
- cover checkpoint edge mapping and public DTO serialization

Diffstat:
Mcrates/sdk/src/lib.rs | 4++--
Mcrates/sdk/src/runtime.rs | 251++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mcrates/sdk/tests/runtime_foundation.rs | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mcrates/sdk/tests/unit/runtime_tests.rs | 148++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
4 files changed, 291 insertions(+), 195 deletions(-)

diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -204,8 +204,8 @@ pub use crate::runtime::{ RadrootsSdkTimestamp, RestoreArchive, RestoreReceipt, RestoreRequest, SdkBackupManifest, SdkBackupManifestKind, SdkBackupState, SdkBackupVerification, SdkEventStoreStorageStatus, SdkOutboxStorageStatus, SdkPrivateStoreStorageStatus, SdkPublishTransport, SdkRestoreState, - SdkSqliteStoreStatus, SdkSqliteWalCheckpointStatus, SdkStorageKind, StorageStatusReceipt, - StorageStatusRequest, + SdkSqliteStoreStatus, SdkSqliteWalCheckpointReceipt, SdkSqliteWalStatus, SdkStorageKind, + StorageCheckpointReceipt, StorageCheckpointRequest, StorageStatusReceipt, StorageStatusRequest, }; #[cfg(all(feature = "runtime", feature = "signer-adapters"))] pub use crate::signer_provider::{ diff --git a/crates/sdk/src/runtime.rs b/crates/sdk/src/runtime.rs @@ -141,6 +141,18 @@ impl StorageStatusRequest { } #[cfg(feature = "runtime")] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[non_exhaustive] +pub struct StorageCheckpointRequest {} + +#[cfg(feature = "runtime")] +impl StorageCheckpointRequest { + pub fn new() -> Self { + Self::default() + } +} + +#[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct StorageStatusReceipt { pub storage: SdkStorageKind, @@ -161,19 +173,35 @@ pub enum SdkStorageKind { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct StorageCheckpointReceipt { + pub storage: SdkStorageKind, + pub paths: Option<RadrootsSdkStoragePaths>, + pub event_store: SdkSqliteWalCheckpointReceipt, + pub outbox: SdkSqliteWalCheckpointReceipt, + pub private_store: SdkSqliteWalCheckpointReceipt, +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct SdkSqliteStoreStatus { pub schema_version: i64, pub journal_mode: String, pub foreign_keys_enabled: bool, pub busy_timeout_ms: i64, - pub wal_checkpoint: SdkSqliteWalCheckpointStatus, + pub wal_status: SdkSqliteWalStatus, pub integrity_ok: bool, pub integrity_result: String, } #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct SdkSqliteWalCheckpointStatus { +pub struct SdkSqliteWalStatus { + pub wal_enabled: bool, +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct SdkSqliteWalCheckpointReceipt { pub wal_enabled: bool, pub busy: i64, pub log_frame_count: i64, @@ -659,14 +687,48 @@ impl RadrootsClient { }) } + pub async fn storage_checkpoint( + &self, + _request: StorageCheckpointRequest, + ) -> Result<StorageCheckpointReceipt, RadrootsSdkError> { + let event_store = sqlite_wal_checkpoint( + self._event_store.pool(), + &self._event_store.pragma_journal_mode().await?, + SqliteStoreRole::EventStore, + ) + .await?; + let outbox = sqlite_wal_checkpoint( + self._outbox.pool(), + &self._outbox.pragma_journal_mode().await?, + SqliteStoreRole::Outbox, + ) + .await?; + let private_store = sqlite_wal_checkpoint( + self._private_store.pool(), + &self._private_store.pragma_journal_mode().await?, + SqliteStoreRole::PrivateStore, + ) + .await?; + Ok(StorageCheckpointReceipt { + storage: self.storage_kind(), + paths: self.storage_paths.clone(), + event_store, + outbox, + private_store, + }) + } + pub async fn integrity( &self, _request: IntegrityRequest, ) -> Result<IntegrityReceipt, RadrootsSdkError> { - let event_store_integrity = sqlite_integrity_result(self._event_store.pool()).await?; - let outbox_integrity = sqlite_integrity_result(self._outbox.pool()).await?; + let event_store_integrity = + sqlite_integrity_result(self._event_store.pool(), SqliteStoreRole::EventStore).await?; + let outbox_integrity = + sqlite_integrity_result(self._outbox.pool(), SqliteStoreRole::Outbox).await?; let private_store_integrity = - private_store_sqlite_integrity_result(self._private_store.pool()).await?; + sqlite_integrity_result(self._private_store.pool(), SqliteStoreRole::PrivateStore) + .await?; let checked_paths = self .storage_paths .as_ref() @@ -791,6 +853,7 @@ async fn event_store_sqlite_status( event_store.pragma_journal_mode().await?, event_store.pragma_foreign_keys().await? != 0, event_store.pragma_busy_timeout().await?, + SqliteStoreRole::EventStore, ) .await } @@ -805,6 +868,7 @@ async fn outbox_sqlite_status( outbox.pragma_journal_mode().await?, outbox.pragma_foreign_keys().await? != 0, outbox.pragma_busy_timeout().await?, + SqliteStoreRole::Outbox, ) .await } @@ -813,12 +877,13 @@ async fn outbox_sqlite_status( async fn private_store_sqlite_status( private_store: &SdkPrivateStore, ) -> Result<SdkSqliteStoreStatus, RadrootsSdkError> { - private_sqlite_store_status( + sqlite_store_status( private_store.pool(), SDK_PRIVATE_STORE_SCHEMA_VERSION_CURRENT, private_store.pragma_journal_mode().await?, private_store.pragma_foreign_keys().await? != 0, private_store.pragma_busy_timeout().await?, + SqliteStoreRole::PrivateStore, ) .await } @@ -848,14 +913,19 @@ async fn backup_sqlite_stores( sqlite_vacuum_into( event_store_pool, &backup_paths.event_store_path, - "event store", + SqliteStoreRole::EventStore, + ) + .await?; + sqlite_vacuum_into( + outbox_pool, + &backup_paths.outbox_path, + SqliteStoreRole::Outbox, ) .await?; - sqlite_vacuum_into(outbox_pool, &backup_paths.outbox_path, "outbox").await?; sqlite_vacuum_into( private_store_pool, &backup_paths.private_store_path, - "private store", + SqliteStoreRole::PrivateStore, ) .await?; verify_backup_paths(backup_paths).await @@ -1398,6 +1468,33 @@ async fn open_directory_storage(path: &Path) -> Result<OpenedRuntimeStorage, Rad } #[cfg(feature = "runtime")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum SqliteStoreRole { + EventStore, + Outbox, + PrivateStore, +} + +#[cfg(feature = "runtime")] +impl SqliteStoreRole { + fn label(self) -> &'static str { + match self { + Self::EventStore => "event store", + Self::Outbox => "outbox", + Self::PrivateStore => "private store", + } + } + + fn error(self, message: String) -> RadrootsSdkError { + match self { + Self::EventStore => RadrootsSdkError::EventStore { message }, + Self::Outbox => RadrootsSdkError::Outbox { message }, + Self::PrivateStore => RadrootsSdkError::PrivateStore { message }, + } + } +} + +#[cfg(feature = "runtime")] struct SqliteIntegrityResult { ok: bool, result: String, @@ -1410,102 +1507,48 @@ async fn sqlite_store_status( journal_mode: String, foreign_keys_enabled: bool, busy_timeout_ms: i64, + store_role: SqliteStoreRole, ) -> Result<SdkSqliteStoreStatus, RadrootsSdkError> { - let wal_checkpoint = sqlite_wal_checkpoint_status(pool, &journal_mode).await?; - let integrity = sqlite_integrity_result(pool).await?; + let wal_status = sqlite_wal_status(&journal_mode); + let integrity = sqlite_integrity_result(pool, store_role).await?; Ok(SdkSqliteStoreStatus { schema_version, journal_mode, foreign_keys_enabled, busy_timeout_ms, - wal_checkpoint, + wal_status, integrity_ok: integrity.ok, integrity_result: integrity.result, }) } #[cfg(feature = "runtime")] -async fn private_sqlite_store_status( - pool: &SqlitePool, - schema_version: i64, - journal_mode: String, - foreign_keys_enabled: bool, - busy_timeout_ms: i64, -) -> Result<SdkSqliteStoreStatus, RadrootsSdkError> { - let wal_checkpoint = private_store_sqlite_wal_checkpoint_status(pool, &journal_mode).await?; - let integrity = private_store_sqlite_integrity_result(pool).await?; - Ok(SdkSqliteStoreStatus { - schema_version, - journal_mode, - foreign_keys_enabled, - busy_timeout_ms, - wal_checkpoint, - integrity_ok: integrity.ok, - integrity_result: integrity.result, - }) -} - -#[cfg(feature = "runtime")] -async fn sqlite_wal_checkpoint_status( - pool: &SqlitePool, - journal_mode: &str, -) -> Result<SdkSqliteWalCheckpointStatus, RadrootsSdkError> { - let row = sqlx::query("PRAGMA wal_checkpoint(PASSIVE)") - .fetch_one(pool) - .await - .map_err(|error| RadrootsSdkError::EventStore { - message: error.to_string(), - })?; - let busy = row - .try_get(0) - .map_err(|error| RadrootsSdkError::EventStore { - message: error.to_string(), - })?; - let log_frame_count = row - .try_get(1) - .map_err(|error| RadrootsSdkError::EventStore { - message: error.to_string(), - })?; - let checkpointed_frame_count = - row.try_get(2) - .map_err(|error| RadrootsSdkError::EventStore { - message: error.to_string(), - })?; - Ok(sqlite_wal_checkpoint_status_from_values( - journal_mode, - busy, - log_frame_count, - checkpointed_frame_count, - )) +fn sqlite_wal_status(journal_mode: &str) -> SdkSqliteWalStatus { + SdkSqliteWalStatus { + wal_enabled: journal_mode.eq_ignore_ascii_case("wal"), + } } #[cfg(feature = "runtime")] -async fn private_store_sqlite_wal_checkpoint_status( +async fn sqlite_wal_checkpoint( pool: &SqlitePool, journal_mode: &str, -) -> Result<SdkSqliteWalCheckpointStatus, RadrootsSdkError> { + store_role: SqliteStoreRole, +) -> Result<SdkSqliteWalCheckpointReceipt, RadrootsSdkError> { let row = sqlx::query("PRAGMA wal_checkpoint(PASSIVE)") .fetch_one(pool) .await - .map_err(|error| RadrootsSdkError::PrivateStore { - message: error.to_string(), - })?; + .map_err(|error| store_role.error(error.to_string()))?; let busy = row .try_get(0) - .map_err(|error| RadrootsSdkError::PrivateStore { - message: error.to_string(), - })?; + .map_err(|error| store_role.error(error.to_string()))?; let log_frame_count = row .try_get(1) - .map_err(|error| RadrootsSdkError::PrivateStore { - message: error.to_string(), - })?; - let checkpointed_frame_count = - row.try_get(2) - .map_err(|error| RadrootsSdkError::PrivateStore { - message: error.to_string(), - })?; - Ok(sqlite_wal_checkpoint_status_from_values( + .map_err(|error| store_role.error(error.to_string()))?; + let checkpointed_frame_count = row + .try_get(2) + .map_err(|error| store_role.error(error.to_string()))?; + Ok(sqlite_wal_checkpoint_receipt_from_values( journal_mode, busy, log_frame_count, @@ -1514,16 +1557,16 @@ async fn private_store_sqlite_wal_checkpoint_status( } #[cfg(feature = "runtime")] -fn sqlite_wal_checkpoint_status_from_values( +fn sqlite_wal_checkpoint_receipt_from_values( journal_mode: &str, busy: i64, log_frame_count: i64, checkpointed_frame_count: i64, -) -> SdkSqliteWalCheckpointStatus { +) -> SdkSqliteWalCheckpointReceipt { let wal_enabled = journal_mode.eq_ignore_ascii_case("wal"); let checkpoint_complete = busy == 0 && (!wal_enabled || (log_frame_count >= 0 && log_frame_count == checkpointed_frame_count)); - SdkSqliteWalCheckpointStatus { + SdkSqliteWalCheckpointReceipt { wal_enabled, busy, log_frame_count, @@ -1535,30 +1578,12 @@ fn sqlite_wal_checkpoint_status_from_values( #[cfg(feature = "runtime")] async fn sqlite_integrity_result( pool: &SqlitePool, + store_role: SqliteStoreRole, ) -> Result<SqliteIntegrityResult, RadrootsSdkError> { let results = sqlx::query_scalar::<_, String>("PRAGMA integrity_check") .fetch_all(pool) .await - .map_err(|error| RadrootsSdkError::EventStore { - message: error.to_string(), - })?; - let result = results.join("; "); - Ok(SqliteIntegrityResult { - ok: result == "ok", - result, - }) -} - -#[cfg(feature = "runtime")] -async fn private_store_sqlite_integrity_result( - pool: &SqlitePool, -) -> Result<SqliteIntegrityResult, RadrootsSdkError> { - let results = sqlx::query_scalar::<_, String>("PRAGMA integrity_check") - .fetch_all(pool) - .await - .map_err(|error| RadrootsSdkError::PrivateStore { - message: error.to_string(), - })?; + .map_err(|error| store_role.error(error.to_string()))?; let result = results.join("; "); Ok(SqliteIntegrityResult { ok: result == "ok", @@ -1609,11 +1634,14 @@ fn prepare_backup_destination(path: &Path, overwrite: bool) -> Result<(), Radroo async fn sqlite_vacuum_into( pool: &SqlitePool, destination: &Path, - store_name: &'static str, + store_role: SqliteStoreRole, ) -> Result<(), RadrootsSdkError> { let Some(destination) = destination.to_str() else { return Err(RadrootsSdkError::InvalidRequest { - message: format!("{store_name} backup destination must be valid UTF-8"), + message: format!( + "{} backup destination must be valid UTF-8", + store_role.label() + ), }); }; sqlx::query("VACUUM INTO ?") @@ -1621,9 +1649,7 @@ async fn sqlite_vacuum_into( .execute(pool) .await .map(|_| ()) - .map_err(|error| RadrootsSdkError::EventStore { - message: format!("{store_name} backup failed: {error}"), - }) + .map_err(|error| store_role.error(format!("{} backup failed: {error}", store_role.label()))) } #[cfg(feature = "runtime")] @@ -1633,10 +1659,11 @@ async fn verify_backup_paths( let event_store = RadrootsEventStore::open_file(&paths.event_store_path).await?; let outbox = RadrootsOutbox::open_file(&paths.outbox_path).await?; let private_store = SdkPrivateStore::open_file(&paths.private_store_path).await?; - let event_store_integrity = sqlite_integrity_result(event_store.pool()).await?; - let outbox_integrity = sqlite_integrity_result(outbox.pool()).await?; + let event_store_integrity = + sqlite_integrity_result(event_store.pool(), SqliteStoreRole::EventStore).await?; + let outbox_integrity = sqlite_integrity_result(outbox.pool(), SqliteStoreRole::Outbox).await?; let private_store_integrity = - private_store_sqlite_integrity_result(private_store.pool()).await?; + sqlite_integrity_result(private_store.pool(), SqliteStoreRole::PrivateStore).await?; let event_summary = event_store.status_summary().await?; let outbox_summary = outbox.status_summary(i64::MAX).await?; let private_summary = private_store.status_summary().await?; diff --git a/crates/sdk/tests/runtime_foundation.rs b/crates/sdk/tests/runtime_foundation.rs @@ -8,8 +8,9 @@ use radroots_sdk::{ SDK_IDEMPOTENCY_KEY_MAX_LEN, SDK_RELAY_TARGET_MAX_COUNT, SdkBackupState, SdkBackupVerification, SdkEventStoreStorageStatus, SdkIdempotencyKey, SdkOutboxStorageStatus, SdkPrivateStoreStorageStatus, SdkRelayTargetPolicy, SdkRelayTargetSet, SdkRelayUrlPolicy, - SdkRestoreState, SdkSqliteStoreStatus, SdkSqliteWalCheckpointStatus, SdkStorageKind, - StorageStatusReceipt, StorageStatusRequest, + SdkRestoreState, SdkSqliteStoreStatus, SdkSqliteWalCheckpointReceipt, SdkSqliteWalStatus, + SdkStorageKind, StorageCheckpointReceipt, StorageCheckpointRequest, StorageStatusReceipt, + StorageStatusRequest, }; use radroots_trade::identity::RadrootsTradeLocator; use sqlx::Row; @@ -678,16 +679,17 @@ fn storage_backup_and_integrity_contract_dtos_serialize() { journal_mode: "wal".to_owned(), foreign_keys_enabled: true, busy_timeout_ms: 5_000, - wal_checkpoint: SdkSqliteWalCheckpointStatus { - wal_enabled: true, - busy: 0, - log_frame_count: 8, - checkpointed_frame_count: 8, - checkpoint_complete: true, - }, + wal_status: SdkSqliteWalStatus { wal_enabled: true }, integrity_ok: true, integrity_result: "ok".to_owned(), }; + let checkpoint = SdkSqliteWalCheckpointReceipt { + wal_enabled: true, + busy: 0, + log_frame_count: 8, + checkpointed_frame_count: 8, + checkpoint_complete: true, + }; let private_store = SdkSqliteStoreStatus { schema_version: 2, ..store.clone() @@ -735,12 +737,8 @@ fn storage_backup_and_integrity_contract_dtos_serialize() { "journal_mode": "wal", "foreign_keys_enabled": true, "busy_timeout_ms": 5000, - "wal_checkpoint": { - "wal_enabled": true, - "busy": 0, - "log_frame_count": 8, - "checkpointed_frame_count": 8, - "checkpoint_complete": true + "wal_status": { + "wal_enabled": true }, "integrity_ok": true, "integrity_result": "ok" @@ -757,12 +755,8 @@ fn storage_backup_and_integrity_contract_dtos_serialize() { "journal_mode": "wal", "foreign_keys_enabled": true, "busy_timeout_ms": 5000, - "wal_checkpoint": { - "wal_enabled": true, - "busy": 0, - "log_frame_count": 8, - "checkpointed_frame_count": 8, - "checkpoint_complete": true + "wal_status": { + "wal_enabled": true }, "integrity_ok": true, "integrity_result": "ok" @@ -783,12 +777,8 @@ fn storage_backup_and_integrity_contract_dtos_serialize() { "journal_mode": "wal", "foreign_keys_enabled": true, "busy_timeout_ms": 5000, - "wal_checkpoint": { - "wal_enabled": true, - "busy": 0, - "log_frame_count": 8, - "checkpointed_frame_count": 8, - "checkpoint_complete": true + "wal_status": { + "wal_enabled": true }, "integrity_ok": true, "integrity_result": "ok" @@ -798,6 +788,45 @@ fn storage_backup_and_integrity_contract_dtos_serialize() { }) ); assert_eq!( + serde_json::to_value(StorageCheckpointRequest::new()).expect("checkpoint request"), + serde_json::json!({}) + ); + assert_eq!( + serde_json::to_value(StorageCheckpointReceipt { + storage: SdkStorageKind::Directory, + paths: None, + event_store: checkpoint.clone(), + outbox: checkpoint.clone(), + private_store: checkpoint, + }) + .expect("checkpoint receipt"), + serde_json::json!({ + "storage": "directory", + "paths": null, + "event_store": { + "wal_enabled": true, + "busy": 0, + "log_frame_count": 8, + "checkpointed_frame_count": 8, + "checkpoint_complete": true + }, + "outbox": { + "wal_enabled": true, + "busy": 0, + "log_frame_count": 8, + "checkpointed_frame_count": 8, + "checkpoint_complete": true + }, + "private_store": { + "wal_enabled": true, + "busy": 0, + "log_frame_count": 8, + "checkpointed_frame_count": 8, + "checkpoint_complete": true + } + }) + ); + assert_eq!( serde_json::to_value(BackupRequest::new("backup")).expect("backup request"), serde_json::json!({ "destination": "backup", diff --git a/crates/sdk/tests/unit/runtime_tests.rs b/crates/sdk/tests/unit/runtime_tests.rs @@ -45,13 +45,7 @@ fn sqlite_status() -> SdkSqliteStoreStatus { journal_mode: "wal".to_owned(), foreign_keys_enabled: true, busy_timeout_ms: 5_000, - wal_checkpoint: SdkSqliteWalCheckpointStatus { - wal_enabled: true, - busy: 0, - log_frame_count: 0, - checkpointed_frame_count: 0, - checkpoint_complete: true, - }, + wal_status: SdkSqliteWalStatus { wal_enabled: true }, integrity_ok: true, integrity_result: "ok".to_owned(), } @@ -64,16 +58,17 @@ fn private_sqlite_status() -> SdkSqliteStoreStatus { } } -fn assert_wal_checkpoint_ready(status: &SdkSqliteStoreStatus) { +fn assert_wal_status_ready(status: &SdkSqliteStoreStatus) { assert_eq!(status.journal_mode, "wal"); - assert!(status.wal_checkpoint.wal_enabled); - assert_eq!(status.wal_checkpoint.busy, 0); - assert!(status.wal_checkpoint.log_frame_count >= 0); - assert_eq!( - status.wal_checkpoint.log_frame_count, - status.wal_checkpoint.checkpointed_frame_count - ); - assert!(status.wal_checkpoint.checkpoint_complete); + assert!(status.wal_status.wal_enabled); +} + +fn assert_wal_checkpoint_complete(receipt: &SdkSqliteWalCheckpointReceipt) { + assert!(receipt.wal_enabled); + assert_eq!(receipt.busy, 0); + assert!(receipt.log_frame_count >= 0); + assert_eq!(receipt.log_frame_count, receipt.checkpointed_frame_count); + assert!(receipt.checkpoint_complete); } fn storage_status() -> StorageStatusReceipt { @@ -379,30 +374,17 @@ async fn runtime_public_surface_covers_builders_status_integrity_backup_and_rest .await .expect("memory status"); assert_eq!(memory_status.storage, SdkStorageKind::Memory); - assert!(!memory_status.event_store.store.wal_checkpoint.wal_enabled); - assert!(!memory_status.outbox.store.wal_checkpoint.wal_enabled); - assert!(!memory_status.private_store.store.wal_checkpoint.wal_enabled); - assert!( - memory_status - .event_store - .store - .wal_checkpoint - .checkpoint_complete - ); - assert!( - memory_status - .outbox - .store - .wal_checkpoint - .checkpoint_complete - ); - assert!( - memory_status - .private_store - .store - .wal_checkpoint - .checkpoint_complete - ); + assert!(!memory_status.event_store.store.wal_status.wal_enabled); + assert!(!memory_status.outbox.store.wal_status.wal_enabled); + assert!(!memory_status.private_store.store.wal_status.wal_enabled); + let memory_checkpoint = memory_sdk + .storage_checkpoint(StorageCheckpointRequest::new()) + .await + .expect("memory checkpoint"); + assert_eq!(memory_checkpoint.storage, SdkStorageKind::Memory); + assert!(memory_checkpoint.event_store.checkpoint_complete); + assert!(memory_checkpoint.outbox.checkpoint_complete); + assert!(memory_checkpoint.private_store.checkpoint_complete); let memory_integrity = memory_sdk .integrity(IntegrityRequest::new()) .await @@ -424,9 +406,17 @@ async fn runtime_public_surface_covers_builders_status_integrity_backup_and_rest .await .expect("directory status"); assert_eq!(directory_status.storage, SdkStorageKind::Directory); - assert_wal_checkpoint_ready(&directory_status.event_store.store); - assert_wal_checkpoint_ready(&directory_status.outbox.store); - assert_wal_checkpoint_ready(&directory_status.private_store.store); + assert_wal_status_ready(&directory_status.event_store.store); + assert_wal_status_ready(&directory_status.outbox.store); + assert_wal_status_ready(&directory_status.private_store.store); + let directory_checkpoint = directory_sdk + .storage_checkpoint(StorageCheckpointRequest::new()) + .await + .expect("directory checkpoint"); + assert_eq!(directory_checkpoint.storage, SdkStorageKind::Directory); + assert_wal_checkpoint_complete(&directory_checkpoint.event_store); + assert_wal_checkpoint_complete(&directory_checkpoint.outbox); + assert_wal_checkpoint_complete(&directory_checkpoint.private_store); let backup_destination = tempdir.path().join("backup"); let backup = directory_sdk @@ -523,6 +513,41 @@ fn system_time_converters_cover_epoch_success_and_failure_edges() { )); } +#[test] +fn sqlite_wal_checkpoint_receipt_mapping_covers_edge_states() { + let complete = sqlite_wal_checkpoint_receipt_from_values("wal", 0, 8, 8); + assert_eq!( + complete, + SdkSqliteWalCheckpointReceipt { + wal_enabled: true, + busy: 0, + log_frame_count: 8, + checkpointed_frame_count: 8, + checkpoint_complete: true, + } + ); + + let incomplete = sqlite_wal_checkpoint_receipt_from_values("wal", 0, 8, 7); + assert!(incomplete.wal_enabled); + assert!(!incomplete.checkpoint_complete); + + let busy = sqlite_wal_checkpoint_receipt_from_values("wal", 1, 8, 8); + assert!(busy.wal_enabled); + assert!(!busy.checkpoint_complete); + + let recovered_or_invalid = sqlite_wal_checkpoint_receipt_from_values("wal", 0, -1, 0); + assert!(recovered_or_invalid.wal_enabled); + assert!(!recovered_or_invalid.checkpoint_complete); + + let non_wal_idle = sqlite_wal_checkpoint_receipt_from_values("memory", 0, -1, -1); + assert!(!non_wal_idle.wal_enabled); + assert!(non_wal_idle.checkpoint_complete); + + let non_wal_busy = sqlite_wal_checkpoint_receipt_from_values("delete", 1, 0, 0); + assert!(!non_wal_busy.wal_enabled); + assert!(!non_wal_busy.checkpoint_complete); +} + #[tokio::test] async fn storage_status_integrity_and_backup_map_closed_pool_errors() { let event_store_closed = RadrootsClient::builder().build().await.expect("sdk"); @@ -560,7 +585,7 @@ async fn storage_status_integrity_and_backup_map_closed_pool_errors() { assert_outbox_error(outbox_status_summary(&outbox_closed._outbox, 1).await); assert!(matches!( outbox_closed.integrity(IntegrityRequest::new()).await, - Err(RadrootsSdkError::EventStore { .. }) + Err(RadrootsSdkError::Outbox { .. }) )); let private_store_closed = RadrootsClient::builder().build().await.expect("sdk"); @@ -599,12 +624,13 @@ async fn storage_status_integrity_and_backup_map_closed_pool_errors() { private_store_sqlite_status(&private_store_closed._private_store).await, ); assert_private_store_error( - private_sqlite_store_status( + sqlite_store_status( private_store_closed._private_store.pool(), SDK_PRIVATE_STORE_SCHEMA_VERSION_CURRENT, "memory".to_owned(), true, 5_000, + SqliteStoreRole::PrivateStore, ) .await, ); @@ -671,7 +697,7 @@ async fn storage_status_integrity_and_backup_map_closed_pool_errors() { let private_store = SdkPrivateStore::open_memory().await.expect("private store"); private_store.pool().close().await; let tempdir = tempfile::tempdir().expect("tempdir"); - assert_event_store_error( + assert_private_store_error( backup_sqlite_stores( event_store.pool(), outbox.pool(), @@ -1249,7 +1275,12 @@ async fn sqlite_backup_errors_cover_invalid_paths_and_execute_failures() { assert!( invalid_request_message( - sqlite_vacuum_into(storage.event_store.pool(), &non_utf8_path(), "event store",).await + sqlite_vacuum_into( + storage.event_store.pool(), + &non_utf8_path(), + SqliteStoreRole::EventStore, + ) + .await ) .contains("valid UTF-8") ); @@ -1260,7 +1291,7 @@ async fn sqlite_backup_errors_cover_invalid_paths_and_execute_failures() { let error = sqlite_vacuum_into( storage.event_store.pool(), &closed_pool_destination, - "event store", + SqliteStoreRole::EventStore, ) .await .err() @@ -1293,7 +1324,7 @@ async fn sqlite_backup_errors_cover_invalid_paths_and_execute_failures() { outbox_path: tempdir.path().join("closed-outbox-backup.sqlite"), private_store_path: tempdir.path().join("outbox-closed-private.sqlite"), }; - assert_event_store_error( + assert_outbox_error( backup_sqlite_stores( outbox_closed_storage.event_store.pool(), outbox_closed_storage.outbox.pool(), @@ -1316,16 +1347,25 @@ async fn sqlite_backup_errors_cover_invalid_paths_and_execute_failures() { .is_empty() ); - let integrity_error = sqlite_integrity_result(storage.event_store.pool()) - .await - .err() - .expect("integrity error"); + let integrity_error = + sqlite_integrity_result(storage.event_store.pool(), SqliteStoreRole::EventStore) + .await + .err() + .expect("integrity error"); assert!(matches!( integrity_error, RadrootsSdkError::EventStore { .. } )); assert_event_store_error( - sqlite_store_status(storage.event_store.pool(), 1, "wal".to_owned(), true, 5_000).await, + sqlite_store_status( + storage.event_store.pool(), + 1, + "wal".to_owned(), + true, + 5_000, + SqliteStoreRole::EventStore, + ) + .await, ); }