lib

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

commit 6b909cf220e7a3dadb04bbdc9553e0a8e78366cb
parent 97f1ce38f6e9a513e9942a912b19102477a2e452
Author: triesap <tyson@radroots.org>
Date:   Tue, 28 Jul 2026 07:42:26 +0000

event_store: stabilize authority branch coverage

- reject corruption in either transition cursor identity
- cover migration progress and older coordinate head decisions
- classify SQLite locks and exercise journal probes deterministically
- preserve production AST hashes while improving governed coverage

Diffstat:
Mcrates/event_store/src/model/addressable_transition_feed_v1.rs | 15+++++++++++++++
Mcrates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs | 33+++++++++++++++++++++++++++++++++
Mcrates/event_store/src/schema.rs | 35+++++++++++++++++++++++++++++++++++
Mcrates/event_store/src/store.rs | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 188 insertions(+), 0 deletions(-)

diff --git a/crates/event_store/src/model/addressable_transition_feed_v1.rs b/crates/event_store/src/model/addressable_transition_feed_v1.rs @@ -627,6 +627,21 @@ mod tests { ) )); + value["source_generation"] = serde_json::json!("42".repeat(32)); + value["scope_fingerprint"] = serde_json::json!("AA".repeat(32)); + assert!(matches!( + RadrootsAddressableTransitionCursorV1::from_json( + serde_json::to_string(&value) + .expect("scope encoding JSON") + .as_str() + ), + Err( + RadrootsEventStoreError::AddressableTransitionCursorEncoding { + field: "scope_fingerprint" + } + ) + )); + assert!(matches!( RadrootsAddressableTransitionCursorV1::new( cursor.source_generation(), diff --git a/crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs b/crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs @@ -607,6 +607,39 @@ mod tests { } #[test] + fn raw_snapshot_visibility_oracle_keeps_newer_coordinate_head_v1() { + let newer = signed_ingest_with_tags( + 30_402, + 2, + "newer", + vec![vec!["d".to_owned(), "same-coordinate".to_owned()]], + ); + let newer_id = newer.event().id_str().to_owned(); + let older = signed_ingest_with_tags( + 30_402, + 1, + "older", + vec![vec!["d".to_owned(), "same-coordinate".to_owned()]], + ); + let events = [ + reconciled_event(newer, RadrootsEventAdmissionStatus::Admitted), + reconciled_event(older, RadrootsEventAdmissionStatus::Admitted), + ]; + + let winners = oracle_head_winners(&events); + assert_eq!(winners.len(), 1); + assert_eq!( + winners + .values() + .next() + .expect("coordinate winner") + .event_id + .as_str(), + newer_id + ); + } + + #[test] fn raw_snapshot_visibility_oracle_bounds_high_fan_in_evidence_v1() { const REQUEST_COUNT: usize = 512; let target = signed_ingest_with_tags( diff --git a/crates/event_store/src/schema.rs b/crates/event_store/src/schema.rs @@ -1257,6 +1257,41 @@ mod migration_framework { use std::str::FromStr; use std::time::Duration; + #[test] + fn pending_capacity_hooks_follow_schema_progress() { + let uninitialized = RadrootsEventStoreSchemaStatus::Uninitialized; + assert!(!has_pending_source_capacity_hook( + &uninitialized, + EVENT_STORE_MIGRATIONS, + )); + assert!(!has_pending_source_maintenance_hook( + &uninitialized, + EVENT_STORE_MIGRATIONS, + )); + + let baseline = RadrootsEventStoreSchemaStatus::UnledgeredBaseline; + assert!(has_pending_source_capacity_hook( + &baseline, + EVENT_STORE_MIGRATIONS, + )); + assert!(has_pending_source_maintenance_hook( + &baseline, + EVENT_STORE_MIGRATIONS, + )); + + let current = RadrootsEventStoreSchemaStatus::Managed { + version: RADROOTS_EVENT_STORE_SCHEMA_VERSION_CURRENT, + }; + assert!(!has_pending_source_capacity_hook( + &current, + EVENT_STORE_MIGRATIONS, + )); + assert!(!has_pending_source_maintenance_hook( + &current, + EVENT_STORE_MIGRATIONS, + )); + } + const SYNTHETIC_V2_UP: &str = "CREATE TABLE radroots_event_store_v2_parent ( id INTEGER PRIMARY KEY NOT NULL ) STRICT; diff --git a/crates/event_store/src/store.rs b/crates/event_store/src/store.rs @@ -4483,6 +4483,111 @@ mod tests { } #[tokio::test] + async fn sqlite_busy_classification_is_exact() { + let tempdir = tempfile::tempdir().expect("tempdir"); + let path = tempdir.path().join("busy-classification.sqlite"); + let mut holder = SqliteConnection::connect_with( + &SqliteConnectOptions::new() + .filename(&path) + .create_if_missing(true), + ) + .await + .expect("holder connection"); + sqlx::query("CREATE TABLE fixture(value INTEGER NOT NULL)") + .execute(&mut holder) + .await + .expect("fixture table"); + let mut contender = + SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(&path)) + .await + .expect("contender connection"); + sqlx::query("PRAGMA busy_timeout = 0") + .execute(&mut contender) + .await + .expect("disable busy wait"); + sqlx::query("BEGIN EXCLUSIVE") + .execute(&mut holder) + .await + .expect("exclusive holder"); + + let busy = sqlx::query("BEGIN IMMEDIATE") + .execute(&mut contender) + .await + .expect_err("contender must observe SQLITE_BUSY"); + assert!(sqlite_error_is_busy(&busy)); + assert!(sqlite_error_is_busy_or_locked(&busy)); + assert!(!sqlite_error_is_busy(&sqlx::Error::RowNotFound)); + assert!(!sqlite_error_is_busy_or_locked(&sqlx::Error::RowNotFound)); + + sqlx::query("ROLLBACK") + .execute(&mut holder) + .await + .expect("release holder"); + } + + #[tokio::test] + async fn sqlite_busy_journal_mode_probe_fails_closed_until_reader_releases() { + let tempdir = tempfile::tempdir().expect("tempdir"); + let path = tempdir.path().join("busy-journal-mode.sqlite"); + let mut initializer = SqliteConnection::connect_with( + &SqliteConnectOptions::new() + .filename(&path) + .create_if_missing(true), + ) + .await + .expect("initializer connection"); + sqlx::query("CREATE TABLE fixture(value INTEGER NOT NULL)") + .execute(&mut initializer) + .await + .expect("fixture table"); + initializer.close().await.expect("close initializer"); + + let mut reader = + SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(&path)) + .await + .expect("reader connection"); + sqlx::query("BEGIN") + .execute(&mut reader) + .await + .expect("reader transaction"); + let _: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM fixture") + .fetch_one(&mut reader) + .await + .expect("establish read lock"); + + let mut contender = SqliteConnection::connect_with( + &SqliteConnectOptions::new() + .filename(&path) + .busy_timeout(std::time::Duration::ZERO), + ) + .await + .expect("contender connection"); + + let error = configure_file_journal_mode(&mut contender) + .await + .expect_err("exclusive journal-mode probe must respect the read lock"); + assert!(matches!( + error, + RadrootsEventStoreError::Sqlx(ref source) if sqlite_error_is_busy_or_locked(source) + )); + + sqlx::query("ROLLBACK") + .execute(&mut reader) + .await + .expect("release read lock"); + configure_file_journal_mode(&mut contender) + .await + .expect("journal mode after reader release"); + assert_eq!( + sqlx::query_scalar::<_, String>("PRAGMA main.journal_mode") + .fetch_one(&mut contender) + .await + .expect("journal mode"), + "wal" + ); + } + + #[tokio::test] async fn open_file_rejects_utf16_main_database_before_schema_or_journal_mutation() { let tempdir = tempfile::tempdir().expect("tempdir"); let path = tempdir.path().join("open-file-utf16.sqlite");