From 05d4fa4530e9656e92318d5b0cf7f61a12bf130a Mon Sep 17 00:00:00 2001 From: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> Date: Tue, 1 Sep 2026 16:30:15 -0400 Subject: [PATCH] test(nip-fi): fix four PG tests shadowed by DuplicateEvent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four orchestrator_postgres_tests used make_kind9_event for both the first and second calls. Nostr event IDs are deterministic over (pubkey, created_at, kind, tags, content), so two calls with identical inputs within the same second produce the same event ID. Step 3c (event duplicate precheck) fires before the intended guard — lifecycle guard, epoch guard, or proof-replay uniqueness — causing the tests to assert the wrong error. Fix: add make_kind9_event_msg(keys, channel_id, content) helper and use distinct content strings for every second call that is not deliberately testing step-3c behaviour. orchestrator_pg_concurrent_enrollment_converges: format content with task index so each spawned task produces a unique event. orchestrator_pg_lifecycle_advance_rejects_final_use: second call uses "lifecycle-advance-second-msg" → NoActiveBinding or BindingRetired fires as intended. orchestrator_pg_epoch_guard_catches_zero_row_update: second call uses "epoch-guard-second-msg" → epoch UPDATE guard runs and passes. orchestrator_pg_proof_replay_is_rejected: second call uses "proof-replay-second-msg" → step-3d proof-owner mismatch fires (ProofReplayed) instead of step-3c DuplicateEvent. After this change all 22 NIP-FI ignored PG tests pass (15/15 orchestrator_postgres_tests, 7/7 postgres_tests). Co-authored-by: Will Pfleger Signed-off-by: Will Pfleger --- crates/buzz-relay/src/nip_fi/admission.rs | 36 ++++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/buzz-relay/src/nip_fi/admission.rs b/crates/buzz-relay/src/nip_fi/admission.rs index c0516a4d5a..46feac3e0c 100644 --- a/crates/buzz-relay/src/nip_fi/admission.rs +++ b/crates/buzz-relay/src/nip_fi/admission.rs @@ -3078,7 +3078,17 @@ mod orchestrator_postgres_tests { /// Build a signed kind-9 event with an `h` channel tag. fn make_kind9_event(keys: &Keys, channel_id: Uuid) -> nostr::Event { - EventBuilder::new(Kind::from(9u16), "test message") + make_kind9_event_msg(keys, channel_id, "test message") + } + + /// Build a signed kind-9 event with a specific content string and an `h` channel tag. + /// + /// Use distinct content strings for different calls within the same second: nostr + /// event IDs are deterministic over (pubkey, created_at, kind, tags, content), so two + /// calls with identical inputs in the same second produce the same event ID, which + /// triggers the step-3c `DuplicateEvent` precheck before the intended guard fires. + fn make_kind9_event_msg(keys: &Keys, channel_id: Uuid, content: &str) -> nostr::Event { + EventBuilder::new(Kind::from(9u16), content) .tag(Tag::custom( nostr::TagKind::SingleLetter(nostr::SingleLetterTag { character: nostr::Alphabet::H, @@ -3145,7 +3155,7 @@ mod orchestrator_postgres_tests { .await .expect("orchestrator success path must not fail"); - let (stored, _) = result; + let (stored, _, _) = result; // The returned event_id must match the submitted event. assert_eq!( stored.event.id, event.id, @@ -3367,7 +3377,11 @@ mod orchestrator_postgres_tests { let fx_channel_id = fx.channel_id; let actor_clone = actor; let deadline_clone = deadline; - let event = make_kind9_event(&keys, fx_channel_id); + let event = make_kind9_event_msg( + &keys, + fx_channel_id, + &format!("concurrent-enrollment-msg-{i}"), + ); let assertion = make_assertion(deadline_clone); let proof_event_id = [0x30u8 + i; 32]; // distinct proof per task tokio::spawn(async move { @@ -3613,7 +3627,10 @@ mod orchestrator_postgres_tests { // Second call: same actor, different proof_event_id. // The lifecycle_revision mismatch must be caught at authorize_protected_use_in_tx. let orch2 = make_orchestrator(db); - let event2 = make_kind9_event(&keys, fx.channel_id); + // Distinct content ensures a different event ID from event1 — same keys+channel + // within the same second would produce the same nostr event ID, firing step-3c + // DuplicateEvent before the lifecycle guard. + let event2 = make_kind9_event_msg(&keys, fx.channel_id, "lifecycle-advance-second-msg"); let proof_event_id_2 = [0x41u8; 32]; let result = orch2 .commit_kind9_atomic(crate::nip_fi::Kind9Params { @@ -3702,7 +3719,10 @@ mod orchestrator_postgres_tests { // proves the epoch UPDATE predicate matches every time. let orch2 = make_orchestrator(db); let proof_event_id_2 = [0x51u8; 32]; - let event2 = make_kind9_event(&keys, fx.channel_id); + // Distinct content ensures a different event ID from event1 — same keys+channel + // within the same second would produce the same nostr event ID, firing step-3c + // DuplicateEvent before the epoch guard. + let event2 = make_kind9_event_msg(&keys, fx.channel_id, "epoch-guard-second-msg"); let result = orch2 .commit_kind9_atomic(crate::nip_fi::Kind9Params { community_id: fx.community_id, @@ -3779,7 +3799,9 @@ mod orchestrator_postgres_tests { // Second call: same proof_event_id — must fail with ProofReplayed. let orch2 = make_orchestrator(db); - let event2 = make_kind9_event(&keys, fx.channel_id); + // Distinct content ensures a different nostr event ID from event1: the proof-replay + // uniqueness guard (step 3d) must fire, not the step-3c DuplicateEvent precheck. + let event2 = make_kind9_event_msg(&keys, fx.channel_id, "proof-replay-second-msg"); let result = orch2 .commit_kind9_atomic(crate::nip_fi::Kind9Params { community_id: fx.community_id, @@ -4781,7 +4803,7 @@ mod orchestrator_postgres_tests { // Build a single signed event. let event = make_kind9_event(&keys, fx.channel_id); - let signed_event_id: [u8; 32] = event.id.to_bytes(); + let _signed_event_id: [u8; 32] = event.id.to_bytes(); // ── Invariant 1: deterministic_admission_op_id is stable ───────────── // Same inputs → same UUID, always. This is the property UUID-v4 breaks.