commit f74e8272809e9b65863a8b69cda74fbf8f511b91
parent 561d56fde65e6e2817029fc005c959b73f3c38fd
Author: triesap <tyson@radroots.org>
Date: Tue, 28 Jul 2026 10:29:03 +0000
event_store: cover stored food model boundaries
- drive storage from a real signed food projection
- reject an unrepresentable projected image index
- map invalid publication chronology into typed drift
- exercise the governed d-tag alias on stored food
Diffstat:
2 files changed, 71 insertions(+), 0 deletions(-)
diff --git a/crates/event_store/src/model/food_availability_projection_v1.rs b/crates/event_store/src/model/food_availability_projection_v1.rs
@@ -183,6 +183,14 @@ impl RadrootsStoredFoodAvailabilityImageV1 {
})
}
+ #[cfg(test)]
+ pub(crate) fn from_projection_for_test(
+ image_index: usize,
+ image: &RadrootsInboundFoodAvailabilityImage,
+ ) -> Result<Self, RadrootsEventStoreError> {
+ Self::from_projection(image_index, image)
+ }
+
pub const fn image_index(&self) -> u32 {
self.image_index
}
diff --git a/crates/event_store/src/store.rs b/crates/event_store/src/store.rs
@@ -7101,6 +7101,7 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
.expect("food lookup")
.expect("projected carrots");
assert_eq!(projected.event_id().as_str(), carrots.id_str());
+ assert_eq!(projected.d_tag(), &carrot_id);
assert_eq!(projected.title().as_str(), "Nantes Carrots");
assert_eq!(projected.summary().as_str(), "Fresh bunches");
assert_eq!(projected.price().amount(), "3");
@@ -7256,6 +7257,68 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
assert_eq!(replacement_matches[0].event_id().as_str(), sold.id_str());
}
+ #[test]
+ fn stored_food_projection_rejects_unrepresentable_internal_boundaries() {
+ let event = food_availability_event(
+ 200,
+ "bounded-model-carrots",
+ "Bounded Model Carrots",
+ "Fresh model harvest",
+ "active",
+ vec![vec![
+ "image".to_owned(),
+ "https://media.example/2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824.webp"
+ .to_owned(),
+ "800x600".to_owned(),
+ ]],
+ );
+ let ingest = RadrootsEventIngest::new(event.clone(), 10_000);
+ let projection = match radroots_event_codec::food_availability::inbound::registry_v7::project_verified_food_availability_event_registry_v7(
+ ingest.verified_event(),
+ )
+ .expect("food projection")
+ {
+ radroots_event_codec::food_availability::inbound::registry_v7::RadrootsFoodAvailabilityProjectionOutcome::Focused(projection) => projection,
+ _ => panic!("unexpected food projection outcome"),
+ };
+ let image = projection.images().first().expect("projected image");
+ assert!(matches!(
+ crate::RadrootsStoredFoodAvailabilityImageV1::from_projection_for_test(
+ usize::MAX,
+ image,
+ ),
+ Err(RadrootsEventStoreError::FoodAvailabilityProjectionDrift { ref reason })
+ if reason.contains("image index exceeds")
+ ));
+
+ let author = RadrootsPublicKey::parse(event.pubkey_str()).expect("author");
+ let event_id = RadrootsEventId::parse(event.id_str()).expect("event id");
+ assert!(matches!(
+ crate::RadrootsStoredFoodAvailabilityV1::from_projection(
+ RadrootsEventStoreSourceGeneration::from_bytes([0x55; 32]),
+ author.clone(),
+ event_id.clone(),
+ 1,
+ 99,
+ 1,
+ &projection,
+ ),
+ Err(RadrootsEventStoreError::FoodAvailabilityProjectionDrift { .. })
+ ));
+
+ let stored = crate::RadrootsStoredFoodAvailabilityV1::from_projection(
+ RadrootsEventStoreSourceGeneration::from_bytes([0x55; 32]),
+ author,
+ event_id,
+ 1,
+ 200,
+ 1,
+ &projection,
+ )
+ .expect("bounded stored projection");
+ assert_eq!(stored.d_tag().as_str(), "bounded-model-carrots");
+ }
+
#[tokio::test]
async fn food_availability_projection_guards_images_and_exhaustive_audit_detects_fts_drift() {
let store = RadrootsEventStore::open_memory().await.expect("open");