diff --git a/CHANGELOG.md b/CHANGELOG.md index 4031b87..a4dc4dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -156,6 +156,13 @@ Performance and Security fixes - along with other improvements; immediate upgrad ### Fixed +- Restore cold rejected-maintainer recovery after policy admission pre-saves + the newly authorized announcement for membership reconciliation. An exact + duplicate from that intentional pre-save now still broadcasts the + announcement and expands its state/Git dependencies, while a genuinely + superseded replaceable event remains classified as a duplicate without + downstream fan-out. + - Discover historical repository roots and descendants from the NIP-65 inboxes of accepted repository owners and maintainers. Public Sync+ instances probe exact repository coordinates and every known root on those diff --git a/src/nostr/persistence.rs b/src/nostr/persistence.rs index aa398b9..5831a25 100644 --- a/src/nostr/persistence.rs +++ b/src/nostr/persistence.rs @@ -26,8 +26,14 @@ pub enum SaveContext { pub enum AcceptedEventSaveOutcome { /// The database inserted the event. Saved, - /// The database already had this event or retained a newer replaceable event. + /// The exact event is already stored. + /// + /// Write-policy admission may intentionally pre-save an event when + /// subsequent policy work needs to query it. Callers that established the + /// event was absent before admission must still run their post-save hooks. Duplicate, + /// The database retained a newer replaceable event instead. + Replaced, } /// Nostr-owned facade for accepted-event persistence side effects. @@ -55,9 +61,12 @@ impl<'a> EventPersistence<'a> { let outcome = match status { SaveEventStatus::Success => AcceptedEventSaveOutcome::Saved, - SaveEventStatus::Rejected(RejectedReason::Duplicate | RejectedReason::Replaced) => { + SaveEventStatus::Rejected(RejectedReason::Duplicate) => { AcceptedEventSaveOutcome::Duplicate } + SaveEventStatus::Rejected(RejectedReason::Replaced) => { + AcceptedEventSaveOutcome::Replaced + } SaveEventStatus::Rejected(reason) => { return Err(anyhow::anyhow!( "Database rejected accepted event {}: {reason:?}", diff --git a/src/purgatory/promotion_hooks.rs b/src/purgatory/promotion_hooks.rs index 111ac5c..8bd671a 100644 --- a/src/purgatory/promotion_hooks.rs +++ b/src/purgatory/promotion_hooks.rs @@ -88,15 +88,17 @@ impl NostrPurgatoryPromotionHooks { .save_accepted_event(&state, SaveContext::HotCacheReprocess) .await { - Ok(AcceptedEventSaveOutcome::Saved) => { + Ok( + AcceptedEventSaveOutcome::Saved | AcceptedEventSaveOutcome::Duplicate, + ) => { rejected_events_index.remove(&state.id); relay.notify_event(state.clone()); } - Ok(AcceptedEventSaveOutcome::Duplicate) => { + Ok(AcceptedEventSaveOutcome::Replaced) => { rejected_events_index.remove(&state.id); debug!( event_id = %state.id, - "Re-processed state event was already superseded or stored" + "Re-processed state event was superseded" ); } Err(e) => { @@ -201,7 +203,10 @@ impl PurgatoryPromotionHooks for NostrPurgatoryPromotionHooks { .save_accepted_event(&hot_event, SaveContext::HotCacheReprocess) .await { - Ok(AcceptedEventSaveOutcome::Saved) => { + Ok( + AcceptedEventSaveOutcome::Saved + | AcceptedEventSaveOutcome::Duplicate, + ) => { rejected_events_index.remove(&hot_event.id); relay.notify_event(hot_event.clone()); self.reprocess_state_dependencies( @@ -217,7 +222,7 @@ impl PurgatoryPromotionHooks for NostrPurgatoryPromotionHooks { "Maintainer announcement accepted and saved on re-processing" ); } - Ok(AcceptedEventSaveOutcome::Duplicate) => { + Ok(AcceptedEventSaveOutcome::Replaced) => { rejected_events_index.remove(&hot_event.id); self.reprocess_state_dependencies( write_policy, @@ -229,7 +234,7 @@ impl PurgatoryPromotionHooks for NostrPurgatoryPromotionHooks { .await; debug!( event_id = %hot_event.id, - "Re-processed maintainer announcement was already superseded or stored" + "Re-processed maintainer announcement was superseded" ); } Err(e) => { diff --git a/src/sync/mod.rs b/src/sync/mod.rs index c86b2ac..41cc3d7 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -7568,12 +7568,15 @@ impl SyncManager { WritePolicyResult::Accept => { // Save event to database match write_policy.save_accepted_event(event, save_context).await { - Ok(crate::nostr::persistence::AcceptedEventSaveOutcome::Saved) => {} - Ok(crate::nostr::persistence::AcceptedEventSaveOutcome::Duplicate) => { + Ok( + crate::nostr::persistence::AcceptedEventSaveOutcome::Saved + | crate::nostr::persistence::AcceptedEventSaveOutcome::Duplicate, + ) => {} + Ok(crate::nostr::persistence::AcceptedEventSaveOutcome::Replaced) => { tracing::trace!( event_id = %event.id, relay = %relay_url, - "Database retained an existing event during sync" + "Database retained a newer replaceable event during sync" ); return ProcessResult::Duplicate; }