upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/sync
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-13 13:24:46 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-13 17:29:23 +0000
commit1d09e4bdea7e328cf2740818df9df660c5532a99 (patch)
treedcb758a70a2e9b84709df247cc685a2f6423094e /src/sync
parenta2a99d5a4137b57e4141cf2840f2f51b38035cfa (diff)
feat: implement announcement purgatory core (breaks archive sync test)
Route new announcements to purgatory instead of accepting immediately. Announcements are promoted to the database when git data arrives, ensuring we only serve announcements for repos with actual content. Implemented: - AnnouncementPurgatoryEntry type and DashMap store - Route new announcements to purgatory (replacement announcements skip) - Promote announcements on git data arrival (process_purgatory_announcements) - Authorization checks purgatory announcements (fetch_repository_data_with_purgatory) - State policy uses purgatory announcements for maintainer validation - Cleanup task handles announcement expiry - Updated count()/cleanup() to 3-tuples Known broken: - test_archive_read_only_creates_bare_repo fails: sync module does not treat purgatory announcements as confirmed repos, so per-repo sync (state events, PRs) is never triggered for purgatory announcements - Announcement persistence (save/restore) not implemented - SyncLevel (StateOnly vs Full) not implemented - Soft expiry two-phase not implemented - Expiry extension on state event / git auth not wired up
Diffstat (limited to 'src/sync')
-rw-r--r--src/sync/mod.rs68
1 files changed, 66 insertions, 2 deletions
diff --git a/src/sync/mod.rs b/src/sync/mod.rs
index 1ee1872..872df66 100644
--- a/src/sync/mod.rs
+++ b/src/sync/mod.rs
@@ -1719,8 +1719,50 @@ impl SyncManager {
1719 // For sync-triggered events that go to purgatory, trigger immediate sync 1719 // For sync-triggered events that go to purgatory, trigger immediate sync
1720 // (instead of the default 3-minute delay for user-submitted events) 1720 // (instead of the default 3-minute delay for user-submitted events)
1721 if result == ProcessResult::Purgatory { 1721 if result == ProcessResult::Purgatory {
1722 // Announcements (kind 30617) - re-process rejected state events
1723 // When an announcement goes to purgatory, state events that were
1724 // previously rejected ("no announcement exists") can now be authorized
1725 // via fetch_repository_data_with_purgatory.
1726 if event.kind == Kind::GitRepoAnnouncement {
1727 use crate::nostr::events::RepositoryAnnouncement;
1728
1729 if let Ok(announcement) = RepositoryAnnouncement::from_event((*event).clone()) {
1730 // Re-process rejected state events for this announcement
1731 let (removed, hot_events) = rejected_events_index.invalidate_and_get(
1732 &event.pubkey,
1733 &announcement.identifier,
1734 Some(rejected_index::EventType::State),
1735 );
1736
1737 if removed > 0 {
1738 tracing::info!(
1739 pubkey = %event.pubkey,
1740 identifier = %announcement.identifier,
1741 removed_from_cold_index = removed,
1742 hot_cache_events = hot_events.len(),
1743 "Invalidated rejected state events (announcement now in purgatory)"
1744 );
1745 }
1746
1747 // Re-process state events from hot cache immediately
1748 if !hot_events.is_empty() {
1749 let _stats = Self::reprocess_events_from_hot_cache(
1750 hot_events,
1751 "state event (announcement in purgatory)",
1752 &event.pubkey,
1753 &announcement.identifier,
1754 &relay_url_clone,
1755 &database,
1756 &write_policy,
1757 &local_relay,
1758 &rejected_events_index,
1759 )
1760 .await;
1761 }
1762 }
1763 }
1722 // State events (kind 30618) - extract identifier and trigger immediate sync 1764 // State events (kind 30618) - extract identifier and trigger immediate sync
1723 if event.kind.as_u16() == 30618 { 1765 else if event.kind.as_u16() == 30618 {
1724 if let Some(identifier) = event.tags.iter().find_map(|tag| { 1766 if let Some(identifier) = event.tags.iter().find_map(|tag| {
1725 let tag_vec = tag.clone().to_vec(); 1767 let tag_vec = tag.clone().to_vec();
1726 if tag_vec.len() >= 2 && tag_vec[0] == "d" { 1768 if tag_vec.len() >= 2 && tag_vec[0] == "d" {
@@ -1754,7 +1796,9 @@ impl SyncManager {
1754 1796
1755 // Track pagination state for this subscription (REQ+EOSE) 1797 // Track pagination state for this subscription (REQ+EOSE)
1756 // and received event IDs for negentropy batches 1798 // and received event IDs for negentropy batches
1757 if result == ProcessResult::Saved || result == ProcessResult::Duplicate { 1799 // Include Purgatory results so announcements in purgatory still trigger
1800 // per-repo sync (state events, PR events) from the source relay.
1801 if result == ProcessResult::Saved || result == ProcessResult::Duplicate || result == ProcessResult::Purgatory {
1758 let mut pending = pending_sync_index.write().await; 1802 let mut pending = pending_sync_index.write().await;
1759 if let Some(batches) = pending.get_mut(&relay_url_clone) { 1803 if let Some(batches) = pending.get_mut(&relay_url_clone) {
1760 for batch in batches.iter_mut() { 1804 for batch in batches.iter_mut() {
@@ -2506,6 +2550,26 @@ impl SyncManager {
2506 "{} added to purgatory (waiting for git data)", 2550 "{} added to purgatory (waiting for git data)",
2507 context 2551 context
2508 ); 2552 );
2553 // Trigger immediate sync for re-processed events that go to purgatory
2554 // (same as sync-triggered events in the main event loop)
2555 if event.kind.as_u16() == 30618 {
2556 // State event - extract identifier from 'd' tag
2557 if let Some(id) = event.tags.iter().find_map(|tag| {
2558 let tag_vec = tag.clone().to_vec();
2559 if tag_vec.len() >= 2 && tag_vec[0] == "d" {
2560 Some(tag_vec[1].clone())
2561 } else {
2562 None
2563 }
2564 }) {
2565 write_policy.purgatory().enqueue_sync_immediate(&id);
2566 }
2567 } else if event.kind.as_u16() == 1617 || event.kind.as_u16() == 1618 {
2568 // PR event - extract identifier from 'a' tag
2569 if let Some(id) = crate::git::sync::extract_identifier_from_pr_event(&event) {
2570 write_policy.purgatory().enqueue_sync_immediate(&id);
2571 }
2572 }
2509 } 2573 }
2510 ProcessResult::Rejected => { 2574 ProcessResult::Rejected => {
2511 stats.rejected += 1; 2575 stats.rejected += 1;