lib

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

commit e1486d9ffb588ff4650bbb8bfef66feaee4f5d51
parent ae8d5a18416000c96498f460866ae3b1b45ab104
Author: triesap <tyson@radroots.org>
Date:   Sun, 16 Aug 2026 06:25:56 +0000

service-sqlite: qualify control edge coverage

Diffstat:
Mcrates/service_sqlite/src/authority.rs | 7+++++++
Mcrates/service_sqlite/src/migration.rs | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/service_sqlite/src/sqlite_native_backup.rs | 33+++++++++++++++++++++++++++++++++
Mcrates/service_sqlite/src/statement_policy.rs | 27+++++++++++++++++++++++++++
Mcrates/service_sqlite/src/transaction_control.rs | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 238 insertions(+), 0 deletions(-)

diff --git a/crates/service_sqlite/src/authority.rs b/crates/service_sqlite/src/authority.rs @@ -460,6 +460,13 @@ mod tests { first.release().expect("release"); assert!(!first.is_held()); + assert_eq!( + first + .validate_for(&paths) + .expect_err("released authority cannot validate") + .kind(), + ServiceSqliteErrorKind::Authority + ); first.release().expect("idempotent release"); let next = WriterAuthority::acquire(&paths, OpenMode::ReadWriteExisting) .expect("reacquire") diff --git a/crates/service_sqlite/src/migration.rs b/crates/service_sqlite/src/migration.rs @@ -2776,6 +2776,94 @@ mod tests { } #[cfg(any(target_os = "linux", target_os = "macos"))] + #[test] + fn callback_binding_validation_rejects_each_independent_identity_drift() { + let callback = MigrationDescriptor::callback( + 2, + "rebuild_projection", + CALLBACK_THREE, + CALLBACK_THREE_CHECKSUM, + ) + .expect("callback descriptor"); + let callback_catalog = MigrationCatalog::new([callback.clone()]).expect("catalog"); + + for binding in [ + MigrationCallbackBinding::new( + 2, + MigrationName::new("wrong_projection").expect("name"), + callback.checksum(), + insert_projection_callback, + ), + MigrationCallbackBinding::new( + 2, + callback.name(), + MigrationChecksum::from_bytes([0x55; 32]), + insert_projection_callback, + ), + ] { + assert_eq!( + validate_callback_bindings(&callback_catalog, &[binding]) + .expect_err("binding drift must fail") + .kind(), + ServiceSqliteErrorKind::Migration + ); + } + + let sql = MigrationDescriptor::sql(2, "create_alpha", SQL_TWO, SQL_TWO_CHECKSUM) + .expect("SQL descriptor"); + let callback_three = MigrationDescriptor::callback( + 3, + "rebuild_projection", + CALLBACK_THREE, + CALLBACK_THREE_CHECKSUM, + ) + .expect("callback descriptor"); + let mixed_catalog = MigrationCatalog::new([sql.clone(), callback_three]).expect("catalog"); + let wrong_kind = MigrationCallbackBinding::new( + 2, + sql.name(), + sql.checksum(), + insert_projection_callback, + ); + assert_eq!( + validate_callback_bindings(&mixed_catalog, &[wrong_kind]) + .expect_err("SQL descriptor cannot bind a callback") + .kind(), + ServiceSqliteErrorKind::Migration + ); + + const CALLBACK_FOUR: &[u8] = b"callback:rebuild_secondary_projection:v1"; + let callback_two = MigrationDescriptor::callback( + 2, + "rebuild_projection", + CALLBACK_THREE, + CALLBACK_THREE_CHECKSUM, + ) + .expect("callback descriptor"); + let callback_four = MigrationDescriptor::callback( + 3, + "rebuild_secondary_projection", + CALLBACK_FOUR, + MigrationChecksum::for_callback(CALLBACK_FOUR), + ) + .expect("callback descriptor"); + let duplicate_target_catalog = + MigrationCatalog::new([callback_two.clone(), callback_four]).expect("catalog"); + let duplicate = MigrationCallbackBinding::new( + 2, + callback_two.name(), + callback_two.checksum(), + insert_projection_callback, + ); + assert_eq!( + validate_callback_bindings(&duplicate_target_catalog, &[duplicate, duplicate]) + .expect_err("duplicate callback target must fail") + .kind(), + ServiceSqliteErrorKind::Migration + ); + } + + #[cfg(any(target_os = "linux", target_os = "macos"))] #[tokio::test(flavor = "current_thread")] async fn callback_bindings_and_history_mismatches_fail_before_replay() { let callback_descriptor = MigrationDescriptor::callback( diff --git a/crates/service_sqlite/src/sqlite_native_backup.rs b/crates/service_sqlite/src/sqlite_native_backup.rs @@ -165,4 +165,37 @@ mod tests { NativeBackupFailureKind::Step ); } + + #[test] + fn empty_and_misused_native_handles_fail_or_finish_deterministically() { + let mut backup = NativeBackup { + handle: None, + _locked_handles: PhantomData, + }; + for pages in [i32::MIN, -1, 0, 1] { + let error = backup.step(pages).expect_err("invalid native handle"); + assert_eq!(error.kind, NativeBackupFailureKind::Step); + assert_eq!(error.code, ffi::SQLITE_MISUSE); + } + backup.finish().expect("empty handle is already finalized"); + + for (kind, expected) in [ + ( + NativeBackupFailureKind::Initialize, + "native SQLite backup initialization failed", + ), + ( + NativeBackupFailureKind::Step, + "native SQLite backup step failed", + ), + ( + NativeBackupFailureKind::Finish, + "native SQLite backup finalization failed", + ), + ] { + let error = NativeBackupError { kind, code: 1 }; + assert_eq!(error.to_string(), expected); + assert!(format!("{error:?}").contains("code: 1")); + } + } } diff --git a/crates/service_sqlite/src/statement_policy.rs b/crates/service_sqlite/src/statement_policy.rs @@ -204,4 +204,31 @@ mod tests { assert!(!contains_forbidden_statement_control(allowed)); } } + + #[test] + fn lexical_edges_remain_bounded_and_do_not_invent_statement_control() { + for allowed in [ + "", + "-", + "-- unterminated comment", + "/", + "/* unterminated comment", + "/* interior * is not a terminator */ SELECT 1", + "''", + "'unterminated value", + "'escaped''quote'", + "[]", + "[unterminated identifier", + "SELECT 1;", + "CREATE TABLE items (value TEXT)", + "CREATE TEMP TABLE items (value TEXT)", + "CREATE TEMPORARY TRIGGER audit AFTER INSERT ON items BEGIN SELECT 1; END;", + "CREATE TRIGGER incomplete; SELECT 1", + ] { + assert!( + !contains_forbidden_statement_control(allowed), + "lexical edge was misclassified: {allowed:?}" + ); + } + } } diff --git a/crates/service_sqlite/src/transaction_control.rs b/crates/service_sqlite/src/transaction_control.rs @@ -107,3 +107,86 @@ impl Drop for TransactionRollbackPermit { self.allow_runner_rollback.store(false, Ordering::Release); } } + +#[cfg(all(test, any(target_os = "linux", target_os = "macos")))] +mod tests { + use sqlx::{Connection, SqliteConnection}; + + use super::TransactionControlGate; + + async fn memory_connection() -> SqliteConnection { + let mut connection = SqliteConnection::connect("sqlite::memory:") + .await + .expect("memory SQLite connection"); + sqlx::query("CREATE TABLE gate_probe (value INTEGER NOT NULL)") + .execute(&mut connection) + .await + .expect("gate probe table"); + connection + } + + #[tokio::test(flavor = "current_thread")] + async fn denied_commit_and_unpermitted_rollback_are_observed_exactly() { + let mut connection = memory_connection().await; + let gate = TransactionControlGate::install(&mut connection) + .await + .expect("transaction gate"); + let mut transaction = connection.begin().await.expect("transaction"); + sqlx::query("INSERT INTO gate_probe (value) VALUES (1)") + .execute(&mut *transaction) + .await + .expect("mutate denied transaction"); + transaction + .commit() + .await + .expect_err("commit must be denied"); + assert!(gate.control_violation_observed()); + assert!(gate.rejected_commit_rolled_back()); + gate.remove(&mut connection).await.expect("remove gate"); + + let mut connection = memory_connection().await; + let gate = TransactionControlGate::install(&mut connection) + .await + .expect("transaction gate"); + let mut transaction = connection.begin().await.expect("transaction"); + sqlx::query("INSERT INTO gate_probe (value) VALUES (2)") + .execute(&mut *transaction) + .await + .expect("mutate rolled back transaction"); + transaction.rollback().await.expect("SQLite rollback"); + assert!(gate.control_violation_observed()); + assert!(!gate.rejected_commit_rolled_back()); + gate.remove(&mut connection).await.expect("remove gate"); + } + + #[tokio::test(flavor = "current_thread")] + async fn runner_permits_are_scoped_and_do_not_record_violations() { + let mut connection = memory_connection().await; + let gate = TransactionControlGate::install(&mut connection) + .await + .expect("transaction gate"); + + let mut transaction = connection.begin().await.expect("transaction"); + sqlx::query("INSERT INTO gate_probe (value) VALUES (3)") + .execute(&mut *transaction) + .await + .expect("mutate committed transaction"); + let permit = gate.permit_outer_commit(); + transaction.commit().await.expect("permitted commit"); + drop(permit); + assert!(!gate.control_violation_observed()); + assert!(!gate.rejected_commit_rolled_back()); + + let mut transaction = connection.begin().await.expect("transaction"); + sqlx::query("INSERT INTO gate_probe (value) VALUES (4)") + .execute(&mut *transaction) + .await + .expect("mutate runner rollback transaction"); + let permit = gate.permit_runner_rollback(); + transaction.rollback().await.expect("permitted rollback"); + drop(permit); + assert!(!gate.control_violation_observed()); + assert!(!gate.rejected_commit_rolled_back()); + gate.remove(&mut connection).await.expect("remove gate"); + } +}