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:
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(
+ ¤t,
+ EVENT_STORE_MIGRATIONS,
+ ));
+ assert!(!has_pending_source_maintenance_hook(
+ ¤t,
+ 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");