tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit e04d25d43113a7b3bb6cfc4d8362a7dba18f93bf
parent 3abf26aff686e1c5990ee5773afa04c3b8589fb8
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 05:43:35 -0700

groups: cover stored generated recovery

- Add restart coverage for pending outbox rows whose generated events are already in Pocket.
- Assert recovery marks the rows stored without duplicating generated metadata or admin snapshots.
- Compare stored generated event ids before and after replay to lock the crash recovery behavior.
- Validated with cargo fmt --all -- --check, cargo check --workspace --all-targets, cargo test --workspace, and cargo clippy --workspace --all-targets -- -D warnings.

Diffstat:
Mcrates/tangle_runtime/tests/base_relay_v2.rs | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 65 insertions(+), 0 deletions(-)

diff --git a/crates/tangle_runtime/tests/base_relay_v2.rs b/crates/tangle_runtime/tests/base_relay_v2.rs @@ -1083,6 +1083,39 @@ fn pending_and_retryable_group_outbox_records_materialize_on_restart() { } #[test] +fn already_stored_generated_events_mark_outbox_stored_without_duplication_on_restart() { + let config = test_store_config("outbox-generated-already-stored"); + let owner_auth = authenticated(FixtureKey::Owner); + { + let mut relay = BaseRelay::open_with_groups(&config, 8, &group_config()).expect("relay"); + accept_group_create(&mut relay, "StoredGeneratedFarm", &[], 1, &owner_auth); + relay.shutdown().expect("shutdown"); + } + let metadata_before = stored_event_ids_for_kind(&config, KIND_GROUP_METADATA); + let admins_before = stored_event_ids_for_kind(&config, KIND_GROUP_ADMINS); + assert_eq!(metadata_before.len(), 1); + assert_eq!(admins_before.len(), 1); + + regress_outbox_records_to_pending(&config); + assert_eq!(outbox_status_counts(&config).pending, 2); + + let relay = BaseRelay::open_with_groups(&config, 8, &group_config()).expect("reopen"); + assert_eq!(count_kind(&relay, KIND_GROUP_METADATA), 1); + assert_eq!(count_kind(&relay, KIND_GROUP_ADMINS), 1); + assert_eq!( + stored_event_ids_for_kind(&config, KIND_GROUP_METADATA), + metadata_before + ); + assert_eq!( + stored_event_ids_for_kind(&config, KIND_GROUP_ADMINS), + admins_before + ); + let counts = outbox_status_counts(&config); + assert_eq!(counts.pending, 0); + assert_eq!(counts.stored, 2); +} + +#[test] fn same_timestamp_conflicts_are_deterministic_across_ingest_order() { let first = tangle_v2_group_metadata_event(FixtureKey::Owner, "ClockFarm", "Alpha", 100, &[]) .expect("first"); @@ -1293,6 +1326,38 @@ fn regress_outbox_records_to_retryable(config: &PocketStoreConfig) { store.sync().expect("sync"); } +fn regress_outbox_records_to_pending(config: &PocketStoreConfig) { + let store = PocketStoreHandle::open(config).expect("store"); + for (_, value) in store + .scan_extra_records(TANGLE_GROUP_OUTBOX_TABLE) + .expect("outbox records") + { + let record = GroupOutboxRecord::from_json_bytes(&value).expect("outbox record"); + let pending = GroupOutboxRecord::pending(record.key().clone(), record.payload().clone()); + store + .put_extra_record( + TANGLE_GROUP_OUTBOX_TABLE, + &pending.key().storage_key(), + &pending.to_json_bytes().expect("pending bytes"), + ) + .expect("put pending"); + } + store.sync().expect("sync"); +} + +fn stored_event_ids_for_kind(config: &PocketStoreConfig, kind: u32) -> Vec<String> { + let store = PocketStoreHandle::open(config).expect("store"); + let mut ids = store + .scan_events() + .expect("events") + .into_iter() + .filter(|stored| u32::from(stored.event().kind().as_u16()) == kind) + .map(|stored| stored.event().id().as_hex_string()) + .collect::<Vec<_>>(); + ids.sort(); + ids +} + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] struct OutboxStatusCounts { pending: usize,