lib

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

commit 1a689a1d2be5167dd1699217d58d5ba4b7b98499
parent 6b909cf220e7a3dadb04bbdc9553e0a8e78366cb
Author: triesap <tyson@radroots.org>
Date:   Tue, 28 Jul 2026 07:49:23 +0000

event_store: cover food projection cursor authority

- validate each generation, version, fingerprint, and manifest mismatch
- reject malformed generation blobs and negative projected row counts
- prove exact signed storage conversion boundaries
- preserve production behavior while expanding governed coverage

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

diff --git a/crates/event_store/src/store/food_availability_projection_v1.rs b/crates/event_store/src/store/food_availability_projection_v1.rs @@ -976,6 +976,30 @@ fn i64_from_u64(field: &'static str, value: u64) -> Result<i64, RadrootsEventSto #[cfg(test)] mod tests { use super::*; + use sqlx::Connection; + + async fn cursor_identity_row( + connection: &mut SqliteConnection, + source_generation: Vec<u8>, + feed_version: i64, + projection_version: i64, + scope_fingerprint: Vec<u8>, + hook_manifest_sha256: &str, + projected_row_count: i64, + ) -> sqlx::sqlite::SqliteRow { + sqlx::query( + "SELECT ? AS source_generation, ? AS feed_version, ? AS projection_version, ? AS scope_fingerprint, ? AS hook_manifest_sha256, ? AS projected_row_count", + ) + .bind(source_generation) + .bind(feed_version) + .bind(projection_version) + .bind(scope_fingerprint) + .bind(hook_manifest_sha256) + .bind(projected_row_count) + .fetch_one(connection) + .await + .expect("cursor identity row") + } #[test] fn projection_generation_corruption_uses_the_projection_error_surface() { @@ -989,4 +1013,163 @@ mod tests { && reason.contains("31 bytes instead of 32") )); } + + #[tokio::test] + async fn cursor_identity_validator_rejects_each_authority_mismatch() { + let mut connection = SqliteConnection::connect("sqlite::memory:") + .await + .expect("in-memory SQLite connection"); + let generation = RadrootsEventStoreSourceGeneration::from_bytes([0x42; 32]); + let scope = RadrootsAddressableTransitionScopeV1::food_availability(); + let valid_generation = generation.as_bytes().to_vec(); + let valid_feed_version = i64::from(RADROOTS_ADDRESSABLE_TRANSITION_FEED_VERSION_V1); + let valid_projection_version = i64::from(RADROOTS_FOOD_AVAILABILITY_PROJECTION_VERSION_V1); + let valid_fingerprint = scope.fingerprint().as_bytes().to_vec(); + let valid_manifest = food_manifest::FOOD_AVAILABILITY_PROJECTION_MANIFEST_SHA256; + + let valid = cursor_identity_row( + &mut connection, + valid_generation.clone(), + valid_feed_version, + valid_projection_version, + valid_fingerprint.clone(), + valid_manifest, + 0, + ) + .await; + validate_cursor_identity(&valid, generation, &scope).expect("valid cursor identity"); + + for (label, row) in [ + ( + "source generation", + cursor_identity_row( + &mut connection, + vec![0x24; 32], + valid_feed_version, + valid_projection_version, + valid_fingerprint.clone(), + valid_manifest, + 0, + ) + .await, + ), + ( + "feed version", + cursor_identity_row( + &mut connection, + valid_generation.clone(), + valid_feed_version + 1, + valid_projection_version, + valid_fingerprint.clone(), + valid_manifest, + 0, + ) + .await, + ), + ( + "projection version", + cursor_identity_row( + &mut connection, + valid_generation.clone(), + valid_feed_version, + valid_projection_version + 1, + valid_fingerprint.clone(), + valid_manifest, + 0, + ) + .await, + ), + ( + "scope fingerprint", + cursor_identity_row( + &mut connection, + valid_generation.clone(), + valid_feed_version, + valid_projection_version, + vec![0x24; valid_fingerprint.len()], + valid_manifest, + 0, + ) + .await, + ), + ( + "hook manifest", + cursor_identity_row( + &mut connection, + valid_generation, + valid_feed_version, + valid_projection_version, + valid_fingerprint, + "invalid-manifest", + 0, + ) + .await, + ), + ] { + assert!( + matches!( + validate_cursor_identity(&row, generation, &scope), + Err(RadrootsEventStoreError::FoodAvailabilityProjectionDrift { ref reason }) + if reason == "projection cursor identity is inconsistent" + ), + "{label} mismatch was accepted", + ); + } + + let malformed_generation = cursor_identity_row( + &mut connection, + vec![0x42; 31], + valid_feed_version, + valid_projection_version, + scope.fingerprint().as_bytes().to_vec(), + valid_manifest, + 0, + ) + .await; + assert!(matches!( + validate_cursor_identity(&malformed_generation, generation, &scope), + Err(RadrootsEventStoreError::FoodAvailabilityProjectionDrift { reason }) + if reason.contains("stored cursor generation is invalid") + && reason.contains("31 bytes instead of 32") + )); + + let negative_row_count = cursor_identity_row( + &mut connection, + generation.as_bytes().to_vec(), + valid_feed_version, + valid_projection_version, + scope.fingerprint().as_bytes().to_vec(), + valid_manifest, + -1, + ) + .await; + assert!(matches!( + validate_cursor_identity(&negative_row_count, generation, &scope), + Err(RadrootsEventStoreError::FoodAvailabilityProjectionDrift { reason }) + if reason == "projection cursor has negative row count -1" + )); + } + + #[test] + fn projection_numeric_guards_enforce_exact_boundaries() { + validate_projected_row_count(0).expect("zero projected rows"); + validate_projected_row_count(i64::MAX).expect("maximum projected rows"); + assert!(matches!( + validate_projected_row_count(-1), + Err(RadrootsEventStoreError::FoodAvailabilityProjectionDrift { reason }) + if reason == "projection cursor has negative row count -1" + )); + + assert_eq!( + i64_from_u64("food.boundary", i64::MAX as u64).expect("maximum signed value"), + i64::MAX, + ); + assert!(matches!( + i64_from_u64("food.boundary", i64::MAX as u64 + 1), + Err(RadrootsEventStoreError::UnsignedIntegerRange { + field: "food.boundary", + value, + }) if value == i64::MAX as u64 + 1 + )); + } }