commit df5ed1acf9e5a2f854d57b420eded2bd2a450dcc
parent 3bbc182d4c51815e8f9bdedf7f32d5c2950f9546
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:
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,
);
}