lib

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

commit 620358ee08898499b1e73ef5c8d0697a0ee6fa2e
parent f677293cae4dda6051c1a10e83da86ba5f84e5da
Author: triesap <tyson@radroots.org>
Date:   Tue, 28 Jul 2026 08:21:01 +0000

event_store: cover food projection row drift

- exercise projection and image corruption through public audits
- reject mismatched columns, counts, digests, and dimensions
- detect FTS content drift independently of row integrity
- restore SQLite constraint enforcement before each audit

Diffstat:
Mcrates/event_store/src/store.rs | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 102 insertions(+), 0 deletions(-)

diff --git a/crates/event_store/src/store.rs b/crates/event_store/src/store.rs @@ -2537,6 +2537,28 @@ mod tests { ) } + async fn food_availability_audit_corruption_store() -> RadrootsEventStore { + let store = RadrootsEventStore::open_memory().await.expect("open"); + let event = food_availability_event( + 200, + "audit-corruption-carrots", + "Audit Corruption Carrots", + "Fresh audit harvest", + "active", + vec![vec![ + "image".to_owned(), + "https://media.example/2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824.webp" + .to_owned(), + "800x600".to_owned(), + ]], + ); + store + .ingest_event(RadrootsEventIngest::new(event, 19_050)) + .await + .expect("FoodAvailability audit fixture ingest"); + store + } + fn calendar_date_event( created_at: u32, d_tag: &str, @@ -7171,6 +7193,86 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);", } #[tokio::test] + async fn food_availability_exhaustive_audit_rejects_projection_and_image_row_drift() { + for (label, guard, bypass_checks, mutation, expected_reason) in [ + ( + "projection column", + Some("DROP TRIGGER radroots_event_store_food_availability_projection_update_guard"), + false, + "UPDATE radroots_event_store_food_availability_projection SET title = 'corrupt title'", + "columns differ", + ), + ( + "image count", + Some("DROP TRIGGER radroots_event_store_food_availability_image_delete_guard"), + false, + "DELETE FROM radroots_event_store_food_availability_image", + "image count differs", + ), + ( + "Blossom digest", + Some("DROP TRIGGER radroots_event_store_food_availability_image_update_guard"), + true, + "UPDATE radroots_event_store_food_availability_image SET blossom_sha256 = 'invalid'", + "stored Blossom digest is invalid", + ), + ( + "image dimensions", + Some("DROP TRIGGER radroots_event_store_food_availability_image_update_guard"), + false, + "UPDATE radroots_event_store_food_availability_image SET width = width + 1", + "stored FoodAvailability image differs", + ), + ( + "FTS content", + None, + false, + "UPDATE radroots_event_store_food_availability_search_fts SET title = 'corrupt title'", + "FTS row differs", + ), + ] { + let store = food_availability_audit_corruption_store().await; + let mut connection = store.pool().acquire().await.expect("trusted connection"); + if let Some(guard) = guard { + sqlx::query(guard) + .execute(&mut *connection) + .await + .expect("trusted FoodAvailability guard removal"); + } + if bypass_checks { + sqlx::query("PRAGMA ignore_check_constraints = ON") + .execute(&mut *connection) + .await + .expect("enable trusted check-constraint bypass"); + } + sqlx::query(mutation) + .execute(&mut *connection) + .await + .expect("trusted FoodAvailability corruption"); + if bypass_checks { + sqlx::query("PRAGMA ignore_check_constraints = OFF") + .execute(&mut *connection) + .await + .expect("restore check-constraint enforcement"); + } + drop(connection); + + let error = store + .audit_food_availability_projection_v1() + .await + .expect_err("corrupt FoodAvailability authority must fail audit"); + assert!( + matches!( + error, + RadrootsEventStoreError::FoodAvailabilityProjectionDrift { ref reason } + if reason.contains(expected_reason) + ), + "{label}: {error}", + ); + } + } + + #[tokio::test] async fn food_availability_exhaustive_audit_rejects_wrong_source_transition_authority() { let store = RadrootsEventStore::open_memory().await.expect("open"); for event in [