lib

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

commit e528bd0be26bdaa25266859ce139f59903b43f3f
parent 041d7ba7b732ed8f8c1da869172297771435d5ff
Author: triesap <tyson@radroots.org>
Date:   Sun, 13 Sep 2026 16:19:57 +0000

storage: verify signed artifact commit recovery

- Inject a deferred SQLite constraint failure at transaction commit.
- Assert artifact, delivery binding and receipt rollback together.
- Reopen and retry exact signed bytes with idempotent receipt replay.
- Preserve production behavior, public APIs and coverage thresholds.

Diffstat:
Mcrates/storage_sqlite/src/authored.rs | 11++++++++---
Acrates/storage_sqlite/src/authored_signed_durability_tests.rs | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 188 insertions(+), 3 deletions(-)

diff --git a/crates/storage_sqlite/src/authored.rs b/crates/storage_sqlite/src/authored.rs @@ -1270,7 +1270,7 @@ mod tests { .expect("authored plan") } - fn signed(plan: &AuthoredEventPlan) -> SignedEvent { + pub(super) fn signed(plan: &AuthoredEventPlan) -> SignedEvent { let wire = Nip01EventWire { id: plan.expected_event_id().to_hex(), pubkey: plan.author().to_hex(), @@ -1285,7 +1285,7 @@ mod tests { SignedEvent::from_wire_verified_id(wire, raw).expect("signed event") } - fn ids() -> ( + pub(super) fn ids() -> ( OperationInstanceId, AuthoredArtifactId, AuthoredDeliveryPlanId, @@ -1329,7 +1329,7 @@ mod tests { (AuthoredAtomicCommand::Prepare(command), event_plan) } - fn fence(claim: &WorkClaim) -> WorkFence { + pub(super) fn fence(claim: &WorkClaim) -> WorkFence { WorkFence::new(*claim.token(), claim.generation(), claim.row_revision()).unwrap() } @@ -2507,3 +2507,8 @@ mod tests { #[cfg_attr(coverage_nightly, coverage(off))] #[path = "authored_draft_submission_tests.rs"] mod draft_submission_tests; + +#[cfg(test)] +#[cfg_attr(coverage_nightly, coverage(off))] +#[path = "authored_signed_durability_tests.rs"] +mod signed_durability_tests; diff --git a/crates/storage_sqlite/src/authored_signed_durability_tests.rs b/crates/storage_sqlite/src/authored_signed_durability_tests.rs @@ -0,0 +1,180 @@ +use super::{ + tests::{fence, ids, prepare, signed}, + *, +}; +use crate::{OpenMode, OpenOptions, Paths}; +use core::num::NonZeroU64; +use radroots_storage::{ + authored::WorkClaim, + authored_atomic::{ApplySignedArtifact, ClaimAuthoredWork}, + event::SourceGeneration, +}; +use tempfile::TempDir; + +async fn open(temp: &TempDir, mode: OpenMode) -> SqliteStorage { + let options = OpenOptions::new(Paths::from_directory(temp.path()).unwrap(), mode); + let options = if mode == OpenMode::Create { + options + .with_source_generation(SourceGeneration::new([9; 32]).unwrap(), 9) + .unwrap() + } else { + options + }; + SqliteStorage::open(options).await.unwrap() +} + +#[tokio::test] +async fn signed_artifact_commit_failure_rolls_back_and_retries_exactly_after_reopen() { + let temp = TempDir::new().unwrap(); + let store = open(&temp, OpenMode::Create).await; + let (preparation, plan) = prepare(); + store.execute_authored(preparation).await.unwrap(); + let initial = store.authored_artifact(ids().1).await.unwrap().unwrap(); + let claim = WorkClaim::new( + [4; 16], + "sqlite-signer", + NonZeroU64::MIN, + 11, + 50, + initial.revision(), + ) + .unwrap(); + store + .execute_authored(AuthoredAtomicCommand::Claim(ClaimAuthoredWork::new( + ClaimAuthoredTarget::ArtifactSigning(ids().1), + claim.clone(), + ))) + .await + .unwrap(); + let artifact_before = store.authored_artifact(ids().1).await.unwrap().unwrap(); + let delivery_before = store + .authored_delivery_plan(ids().2) + .await + .unwrap() + .unwrap(); + let operation_before = store.authored_operation(ids().0).await.unwrap().unwrap(); + assert!(artifact_before.signed().is_none()); + assert!(delivery_before.request().is_none()); + + // This storage fixture has a verified event ID. Cryptographic signature + // verification belongs to signing/Sync, not this transaction boundary. + let event = signed(&plan); + let command = AuthoredAtomicCommand::ApplySigned( + ApplySignedArtifact::new(ids().1, fence(&claim), event.clone(), 12).unwrap(), + ); + sqlx::query("CREATE TABLE signed_commit_fault (parent BLOB REFERENCES radroots_runtime_authored_operations(operation_id) DEFERRABLE INITIALLY DEFERRED)") + .execute(store.pool()).await.unwrap(); + // Every statement, including the final receipt INSERT, succeeds. Only + // SQLite's actual COMMIT rejects the deferred foreign-key violation. + sqlx::query("CREATE TRIGGER signed_commit_fault_trigger AFTER INSERT ON radroots_runtime_authored_atomic_commits BEGIN INSERT INTO signed_commit_fault VALUES (x'99999999999999999999999999999999'); END") + .execute(store.pool()).await.unwrap(); + assert!(store.execute_authored(command.clone()).await.is_err()); + assert!( + store + .authored_receipt(command.commit_id()) + .await + .unwrap() + .is_none() + ); + assert_eq!( + store.authored_artifact(ids().1).await.unwrap().unwrap(), + artifact_before + ); + assert_eq!( + store + .authored_delivery_plan(ids().2) + .await + .unwrap() + .unwrap(), + delivery_before + ); + assert_eq!( + store.authored_operation(ids().0).await.unwrap().unwrap(), + operation_before + ); + let failed_writes: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM signed_commit_fault") + .fetch_one(store.pool()) + .await + .unwrap(); + assert_eq!(failed_writes, 0); + sqlx::query("DROP TRIGGER signed_commit_fault_trigger") + .execute(store.pool()) + .await + .unwrap(); + sqlx::query("DROP TABLE signed_commit_fault") + .execute(store.pool()) + .await + .unwrap(); + store.close().await.unwrap(); + + let store = open(&temp, OpenMode::ReadWriteExisting).await; + assert!( + store + .authored_receipt(command.commit_id()) + .await + .unwrap() + .is_none() + ); + assert_eq!( + store.authored_artifact(ids().1).await.unwrap().unwrap(), + artifact_before + ); + assert_eq!( + store + .authored_delivery_plan(ids().2) + .await + .unwrap() + .unwrap(), + delivery_before + ); + assert_eq!( + store.authored_operation(ids().0).await.unwrap().unwrap(), + operation_before + ); + let receipt = store.execute_authored(command.clone()).await.unwrap(); + assert_eq!(receipt.disposition(), AtomicCommitDisposition::Committed); + store.close().await.unwrap(); + + let store = open(&temp, OpenMode::ReadWriteExisting).await; + let artifact = store.authored_artifact(ids().1).await.unwrap().unwrap(); + let delivery = store + .authored_delivery_plan(ids().2) + .await + .unwrap() + .unwrap(); + assert_eq!(artifact.signing_state(), SigningState::Signed); + assert_eq!( + artifact.signed().unwrap().event().raw_json(), + event.raw_json() + ); + assert_eq!( + delivery.request().unwrap().payload().event().raw_json(), + event.raw_json() + ); + assert_eq!( + store + .authored_receipt(command.commit_id()) + .await + .unwrap() + .unwrap() + .outcome(), + receipt.outcome() + ); + assert_eq!( + store.execute_authored(command).await.unwrap().disposition(), + AtomicCommitDisposition::Replay + ); + assert_eq!( + store.authored_artifact(ids().1).await.unwrap().unwrap(), + artifact + ); + assert_eq!( + store + .authored_delivery_plan(ids().2) + .await + .unwrap() + .unwrap(), + delivery + ); + store.close().await.unwrap(); +}