lib

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

commit 90cebaffa13b0df983269450d187d037427a9584
parent 8a50cdff9aa67eaed446c3ffdaa4954908bb0c6f
Author: triesap <tyson@radroots.org>
Date:   Tue, 28 Jul 2026 15:19:21 +0000

event_store: cover projection ticket field drift

- reject forged projection versions
- reject forged event sequences
- reject forged update timestamps
- prove generation-bound cursor rebuilds

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

diff --git a/crates/event_store/src/store.rs b/crates/event_store/src/store.rs @@ -4242,6 +4242,54 @@ mod tests { updated_at_ms: 10, } )); + for (label, prior) in [ + ( + "projection version", + RadrootsProjectionRebuildPrior::Cursor { + source_generation: None, + source_revision: 1, + projection_version: 2, + last_event_seq: 0, + updated_at_ms: 10, + }, + ), + ( + "event sequence", + RadrootsProjectionRebuildPrior::Cursor { + source_generation: None, + source_revision: 1, + projection_version: 1, + last_event_seq: 1, + updated_at_ms: 10, + }, + ), + ( + "update timestamp", + RadrootsProjectionRebuildPrior::Cursor { + source_generation: None, + source_revision: 1, + projection_version: 1, + last_event_seq: 0, + updated_at_ms: 11, + }, + ), + ] { + let mut forged_ticket = legacy_ticket.clone(); + forged_ticket.prior = prior; + let error = store + .reset_projection_cursor_after_rebuild(forged_ticket, 19) + .await + .expect_err("forged prior must be rejected"); + assert!( + matches!( + error, + RadrootsEventStoreError::ProjectionRebuildTicketConflict { + ref projection_id, + } if projection_id == "legacy" + ), + "{label}: {error}", + ); + } let replay_ticket = legacy_ticket.clone(); let rebuilt = store .reset_projection_cursor_after_rebuild(legacy_ticket, 20) @@ -4296,6 +4344,26 @@ mod tests { .expect("missing-success cursor"), missing_success ); + let current_success_ticket = store + .prepare_projection_cursor_rebuild("missing-success", 2) + .await + .expect("generation-bound cursor ticket"); + assert!(matches!( + current_success_ticket.prior(), + RadrootsProjectionRebuildPrior::Cursor { + source_generation: Some(actual_generation), + source_revision: 1, + projection_version: 1, + last_event_seq: 1, + updated_at_ms: 29, + } if *actual_generation == generation + )); + let current_success = store + .reset_projection_cursor_after_rebuild(current_success_ticket, 30) + .await + .expect("generation-bound cursor rebuild"); + assert_eq!(current_success.projection_version(), 2); + assert_eq!(current_success.last_event_seq(), 1); let mut high_water_ticket = store .prepare_projection_cursor_rebuild("ahead-ticket", 1)