lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit fd6a0ef6368d7010f69d7cabfff22c2e05c8697d
parent f70c151433a82cda12e6060df7c981991ebd9630
Author: triesap <tyson@radroots.org>
Date:   Sun,  2 Aug 2026 01:05:35 +0000

storage: complete package conformance coverage

- enforce the exact root export and feature contracts
- expose the aggregate storage and backup capabilities
- qualify no-default individual and all-feature builds
- prove dependency leakage docs tests and strict lint gates

Diffstat:
Mcrates/storage/src/lib.rs | 28+++++++++++++++++++++++++---
Mcrates/storage/src/memory.rs | 9++++-----
Mcrates/storage/tests/atomic.rs | 4++--
Mcrates/storage/tests/backup.rs | 4++--
Mcrates/storage/tests/conformance.rs | 6++++--
Mcrates/storage/tests/conformance/suite.rs | 22+++++++++++++++-------
Mcrates/storage/tests/event_store.rs | 11++++++++++-
Acrates/storage/tests/feature_contract.rs | 27+++++++++++++++++++++++++++
Mcrates/storage/tests/memory.rs | 22+++++++++++++++++-----
Mcrates/storage/tests/outbox.rs | 11++++++++++-
Mcrates/storage/tests/package_boundary.rs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/storage/tests/private_artifact.rs | 5+++--
12 files changed, 181 insertions(+), 30 deletions(-)

diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs @@ -14,11 +14,33 @@ pub mod private_artifact; pub mod projection; pub mod status; -pub use atomic::AtomicStorage; -pub use backup::StorageReliability; +pub use backup::StorageReliability as BackupSource; pub use error::Error; pub use event::EventStore; pub use journal::Journal; pub use outbox::Outbox; -pub use private_artifact::PrivateArtifactStore; pub use projection::ProjectionStore; +pub use status::StorageStatus; + +/// Complete backend-neutral storage capability implemented by concrete stores. +pub trait Storage: + EventStore + + Journal + + Outbox + + ProjectionStore + + private_artifact::PrivateArtifactStore + + backup::StorageReliability + + atomic::AtomicStorage +{ +} + +impl<T> Storage for T where + T: EventStore + + Journal + + Outbox + + ProjectionStore + + private_artifact::PrivateArtifactStore + + backup::StorageReliability + + atomic::AtomicStorage +{ +} diff --git a/crates/storage/src/memory.rs b/crates/storage/src/memory.rs @@ -6,15 +6,14 @@ use radroots_transport::{BoxFuture, source::EventProvenance}; use std::sync::{Mutex, MutexGuard}; use crate::{ - AtomicStorage, Error, EventStore, Journal, Outbox, PrivateArtifactStore, ProjectionStore, - StorageReliability, + Error, EventStore, Journal, Outbox, ProjectionStore, atomic::{ AtomicCommit, AtomicCommitDisposition, AtomicCommitId, AtomicCommitOutcome, - AtomicCommitReceipt, AtomicWorkflow, + AtomicCommitReceipt, AtomicStorage, AtomicWorkflow, }, backup::{ BackupId, BackupOperation, BackupPlan, BackupTransition, ReliabilityRevision, - RestoreOperation, RestorePlan, RestoreTransition, + RestoreOperation, RestorePlan, RestoreTransition, StorageReliability, }, event::{ AdmissionDisposition, AdmissionReceipt, AdmissionStage, EventAdmission, EventPage, @@ -33,7 +32,7 @@ use crate::{ private_artifact::{ DeletionReason, EXPIRED_ARTIFACT_QUERY_LIMIT_MAX, PrivateArtifactId, PrivateArtifactMetadata, PrivateArtifactRevision, PrivateArtifactStage, - PrivateArtifactStatus, + PrivateArtifactStatus, PrivateArtifactStore, }, projection::{ EventIndexCheckpoint, EventIndexManifest, ProjectionCheckpoint, ProjectionGeneration, diff --git a/crates/storage/tests/atomic.rs b/crates/storage/tests/atomic.rs @@ -1,10 +1,10 @@ use futures_executor::block_on; use radroots_protocol::runtime::v1::OperationId; use radroots_storage::{ - AtomicStorage, Error, + Error, atomic::{ AtomicCommit, AtomicCommitDigest, AtomicCommitDisposition, AtomicCommitId, - AtomicCommitOutcome, AtomicCommitReceipt, AtomicWorkflow, + AtomicCommitOutcome, AtomicCommitReceipt, AtomicStorage, AtomicWorkflow, }, journal::{IdempotencyDigest, IdempotencyKey, OperationInstanceId, PrepareOperation}, }; diff --git a/crates/storage/tests/backup.rs b/crates/storage/tests/backup.rs @@ -1,10 +1,10 @@ use radroots_storage::{ - Error, StorageReliability, + Error, backup::{ BackupFormatVersion, BackupId, BackupManifest, BackupMember, BackupMemberKind, BackupOperation, BackupPlan, BackupSecretPolicy, BackupStage, BackupTransition, MemberDigest, MemberVerification, ReliabilityRevision, RestoreMemberStatus, - RestoreOperation, RestorePlan, RestoreStage, RestoreTransition, + RestoreOperation, RestorePlan, RestoreStage, RestoreTransition, StorageReliability, }, status::{ IntegrityHealth, IntegrityStatus, ShutdownState, StorageBackend, StorageOpenMode, diff --git a/crates/storage/tests/conformance.rs b/crates/storage/tests/conformance.rs @@ -1,9 +1,11 @@ +#![cfg(feature = "memory")] + #[path = "conformance/suite.rs"] mod suite; use radroots_storage::{ - AtomicStorage, EventStore, Journal, Outbox, PrivateArtifactStore, ProjectionStore, - StorageReliability, memory::MemoryStorage, + EventStore, Journal, Outbox, ProjectionStore, atomic::AtomicStorage, + backup::StorageReliability, memory::MemoryStorage, private_artifact::PrivateArtifactStore, }; use suite::StorageConformanceHarness; diff --git a/crates/storage/tests/conformance/suite.rs b/crates/storage/tests/conformance/suite.rs @@ -2,16 +2,15 @@ use futures_executor::block_on; use radroots_event::{SignedEvent, wire::Nip01EventWire}; use radroots_protocol::runtime::v1::OperationId; use radroots_storage::{ - AtomicStorage, EventStore, Journal, Outbox, PrivateArtifactStore, ProjectionStore, - StorageReliability, + EventStore, Journal, Outbox, ProjectionStore, atomic::{ - AtomicCommit, AtomicCommitDigest, AtomicCommitDisposition, AtomicCommitId, AtomicWorkflow, - CommitEnqueued, CommitIngested, CommitSigned, + AtomicCommit, AtomicCommitDigest, AtomicCommitDisposition, AtomicCommitId, AtomicStorage, + AtomicWorkflow, CommitEnqueued, CommitIngested, CommitSigned, }, backup::{ BackupFormatVersion, BackupId, BackupManifest, BackupMember, BackupMemberKind, BackupPlan, BackupSecretPolicy, BackupStage, BackupTransition, MemberDigest, MemberVerification, - RestoreMemberStatus, RestorePlan, RestoreStage, RestoreTransition, + RestoreMemberStatus, RestorePlan, RestoreStage, RestoreTransition, StorageReliability, }, event::{EventAdmission, EventQuery, EventQueryBounds}, journal::{ @@ -24,7 +23,7 @@ use radroots_storage::{ }, private_artifact::{ ArtifactCommitment, ArtifactKind, ArtifactSchemaId, DurableSecretReference, - PrivateArtifactId, PrivateArtifactMetadata, RetentionPolicy, + PrivateArtifactId, PrivateArtifactMetadata, PrivateArtifactStore, RetentionPolicy, }, projection::{ProjectionCheckpoint, ProjectionGeneration, ProjectionId}, status::{ShutdownState, StorageBackend}, @@ -435,7 +434,16 @@ fn signed_event(content: &str) -> SignedEvent { extra: Default::default(), }; wire.id = wire.computed_event_id().expect("event id").to_hex(); - let raw = serde_json::to_string(&wire).expect("event JSON"); + let raw = serde_json::json!({ + "id": &wire.id, + "pubkey": &wire.pubkey, + "created_at": wire.created_at, + "kind": wire.kind, + "tags": &wire.tags, + "content": &wire.content, + "sig": &wire.sig, + }) + .to_string(); SignedEvent::from_wire_verified_id(wire, raw).expect("signed event") } diff --git a/crates/storage/tests/event_store.rs b/crates/storage/tests/event_store.rs @@ -274,7 +274,16 @@ fn signed_event_with_signature(signature_byte: &str) -> SignedEvent { .computed_event_id() .expect("canonical event id") .to_hex(); - let raw_json = serde_json::to_string(&wire).expect("event JSON"); + let raw_json = serde_json::json!({ + "id": &wire.id, + "pubkey": &wire.pubkey, + "created_at": wire.created_at, + "kind": wire.kind, + "tags": &wire.tags, + "content": &wire.content, + "sig": &wire.sig, + }) + .to_string(); SignedEvent::from_wire_verified_id(wire, raw_json).expect("signed event") } diff --git a/crates/storage/tests/feature_contract.rs b/crates/storage/tests/feature_contract.rs @@ -0,0 +1,27 @@ +#[cfg(feature = "memory")] +use radroots_storage::Storage; + +#[cfg(feature = "memory")] +#[test] +fn memory_feature_provides_the_complete_storage_capability() { + fn accepts(_: &dyn Storage) {} + accepts(&radroots_storage::memory::MemoryStorage::default()); +} + +#[cfg(not(feature = "memory"))] +#[test] +fn storage_spi_builds_without_the_memory_backend() { + fn accepts_spi(_: Option<&dyn radroots_storage::EventStore>) {} + accepts_spi(None); +} + +#[cfg(feature = "serde")] +#[test] +fn serde_feature_round_trips_backend_status_discriminants() { + let encoded = serde_json::to_string(&radroots_storage::status::StorageBackend::Memory) + .expect("serialize backend"); + assert_eq!(encoded, "\"memory\""); + let decoded: radroots_storage::status::StorageBackend = + serde_json::from_str(&encoded).expect("deserialize backend"); + assert_eq!(decoded, radroots_storage::status::StorageBackend::Memory); +} diff --git a/crates/storage/tests/memory.rs b/crates/storage/tests/memory.rs @@ -1,11 +1,13 @@ +#![cfg(feature = "memory")] + use futures_executor::block_on; use radroots_event::{SignedEvent, wire::Nip01EventWire}; use radroots_protocol::runtime::v1::OperationId; use radroots_storage::{ - AtomicStorage, EventStore, Journal, Outbox, PrivateArtifactStore, ProjectionStore, + EventStore, Journal, Outbox, ProjectionStore, atomic::{ - AtomicCommit, AtomicCommitDigest, AtomicCommitDisposition, AtomicCommitId, AtomicWorkflow, - CommitIngested, CommitSigned, + AtomicCommit, AtomicCommitDigest, AtomicCommitDisposition, AtomicCommitId, AtomicStorage, + AtomicWorkflow, CommitIngested, CommitSigned, }, event::{EventAdmission, EventQuery, EventQueryBounds, SourceGeneration}, journal::{ @@ -19,7 +21,8 @@ use radroots_storage::{ }, private_artifact::{ ArtifactCommitment, ArtifactKind, ArtifactSchemaId, DurableSecretReference, - PrivateArtifactId, PrivateArtifactMetadata, PrivateArtifactRevision, RetentionPolicy, + PrivateArtifactId, PrivateArtifactMetadata, PrivateArtifactRevision, PrivateArtifactStore, + RetentionPolicy, }, projection::{ InvalidationReason, ProjectionCheckpoint, ProjectionGeneration, ProjectionHealth, @@ -46,7 +49,16 @@ fn signed_event() -> SignedEvent { extra: Default::default(), }; wire.id = wire.computed_event_id().expect("event id").to_hex(); - let raw = serde_json::to_string(&wire).expect("event JSON"); + let raw = serde_json::json!({ + "id": &wire.id, + "pubkey": &wire.pubkey, + "created_at": wire.created_at, + "kind": wire.kind, + "tags": &wire.tags, + "content": &wire.content, + "sig": &wire.sig, + }) + .to_string(); SignedEvent::from_wire_verified_id(wire, raw).expect("signed event") } diff --git a/crates/storage/tests/outbox.rs b/crates/storage/tests/outbox.rs @@ -171,7 +171,16 @@ fn signed_event() -> SignedEvent { .computed_event_id() .expect("canonical event id") .to_hex(); - let raw_json = serde_json::to_string(&wire).expect("event JSON"); + let raw_json = serde_json::json!({ + "id": &wire.id, + "pubkey": &wire.pubkey, + "created_at": wire.created_at, + "kind": wire.kind, + "tags": &wire.tags, + "content": &wire.content, + "sig": &wire.sig, + }) + .to_string(); SignedEvent::from_wire_verified_id(wire, raw_json).expect("signed event") } diff --git a/crates/storage/tests/package_boundary.rs b/crates/storage/tests/package_boundary.rs @@ -24,6 +24,20 @@ fn manifest_matches_the_release_v1_package_boundary() { "storage SPI must not depend on {forbidden}" ); } + + for feature in [ + "default = [\"memory\", \"serde\"]", + "memory = []", + "serde = [", + ] { + assert!(manifest.contains(feature), "missing feature law: {feature}"); + } + for forbidden_feature in ["sqlite =", "tokio =", "nostr =", "runtime ="] { + assert!( + !manifest.contains(forbidden_feature), + "storage SPI declares forbidden feature {forbidden_feature}" + ); + } } #[test] @@ -46,3 +60,51 @@ fn release_v1_public_module_skeleton_is_declared() { ); } } + +#[test] +fn root_exports_are_exact_and_implementation_types_do_not_leak() { + let root = include_str!("../src/lib.rs"); + for required in [ + "pub trait Storage:", + "pub use backup::StorageReliability as BackupSource;", + "pub use error::Error;", + "pub use event::EventStore;", + "pub use journal::Journal;", + "pub use outbox::Outbox;", + "pub use projection::ProjectionStore;", + "pub use status::StorageStatus;", + ] { + assert!(root.contains(required), "missing root contract: {required}"); + } + for forbidden in [ + "pub use atomic::AtomicStorage", + "pub use backup::StorageReliability;", + "pub use private_artifact::PrivateArtifactStore", + ] { + assert!( + !root.contains(forbidden), + "forbidden root export: {forbidden}" + ); + } + + let source_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src"); + let mut source = String::new(); + for entry in fs::read_dir(source_root).expect("source directory") { + let path = entry.expect("source entry").path(); + if path.extension().and_then(|value| value.to_str()) == Some("rs") { + source.push_str(&fs::read_to_string(path).expect("source file")); + } + } + for forbidden in [ + "sqlx::", + "rusqlite::", + "nostr_sdk::", + "std::path::Path", + "tokio::runtime", + ] { + assert!( + !source.contains(forbidden), + "public storage source leaks implementation path {forbidden}" + ); + } +} diff --git a/crates/storage/tests/private_artifact.rs b/crates/storage/tests/private_artifact.rs @@ -1,9 +1,9 @@ use radroots_storage::{ - Error, PrivateArtifactStore, + Error, private_artifact::{ ArtifactCommitment, ArtifactKind, ArtifactSchemaId, DeletionReason, DurableSecretReference, PrivateArtifactId, PrivateArtifactMetadata, PrivateArtifactRevision, PrivateArtifactStage, - RetentionPolicy, + PrivateArtifactStore, RetentionPolicy, }, }; @@ -77,6 +77,7 @@ fn tombstones_preserve_commitment_and_enforce_retention() { } #[test] +#[cfg(feature = "serde")] fn secret_references_are_bounded_redacted_and_round_trip() { let reference = DurableSecretReference::new("keyring", "opaque-key-token", 3).expect("secret reference");