From d76003b629a4a03dba23a8a1c41da6e4ac4c30cf Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 18 Feb 2026 17:12:17 +0000 Subject: feat: upgrade repo to Full sync and trigger PR event subscription after announcement promotion When git data arrives for a purgatory announcement and promotes it to the database, the relay now: 1. Upgrades the announcement's sync level in RepoSyncIndex from StateOnly to Full (git/sync.rs: process_purgatory_announcements) 2. Sends AddFilters actions to SyncManager for all connected relays, using Full sync filters (Layer 2 #a/#A/#q) to subscribe to PR events (purgatory/sync/context.rs: RealSyncContext.process_newly_available_git_data) 3. For user-submitted purgatory announcements, registers the repo in RepoSyncIndex with StateOnly level and sends AddFilters to SyncManager so it discovers and connects to relays listed in the announcement tags (nostr/builder.rs: handle_announcement AcceptPurgatory path) The RealSyncContext now accepts optional repo_sync_index and sync_action_tx parameters. main.rs wires these up from SyncManager. PolicyContext gains repo_sync_index and sync_action_tx fields for the write policy path. --- src/purgatory/sync/context.rs | 116 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) (limited to 'src/purgatory/sync/context.rs') diff --git a/src/purgatory/sync/context.rs b/src/purgatory/sync/context.rs index 3568e89..4dbb402 100644 --- a/src/purgatory/sync/context.rs +++ b/src/purgatory/sync/context.rs @@ -193,6 +193,7 @@ use crate::nostr::builder::SharedDatabase; use crate::nostr::events::RepositoryState; use crate::purgatory::Purgatory; use crate::sync::naughty_list::NaughtyListTracker; +use crate::sync::RepoSyncIndex; use super::functions::extract_domain; @@ -221,6 +222,13 @@ pub struct RealSyncContext { /// Naughty list tracker for git remote domains with persistent errors git_naughty_list: Arc, + + /// Optional repo sync index for upgrading sync level on promotion + repo_sync_index: Option, + + /// Optional sender for AddFilters actions to SyncManager. + /// Used after announcement promotion to trigger PR event subscription on connected relays. + sync_action_tx: Option>, } impl RealSyncContext { @@ -233,6 +241,9 @@ impl RealSyncContext { /// * `our_domain` - Our domain to exclude from clone URLs /// * `local_relay` - Local relay for WebSocket notifications /// * `git_naughty_list` - Naughty list tracker for git remote domains + /// * `repo_sync_index` - Optional repo sync index for upgrading sync level on promotion + /// * `sync_action_tx` - Optional sender for triggering filter recomputation after promotion + #[allow(clippy::too_many_arguments)] pub fn new( purgatory: Arc, database: SharedDatabase, @@ -240,6 +251,8 @@ impl RealSyncContext { our_domain: Option, local_relay: Option, git_naughty_list: Arc, + repo_sync_index: Option, + sync_action_tx: Option>, ) -> Self { Self { purgatory, @@ -248,9 +261,23 @@ impl RealSyncContext { our_domain_value: our_domain, local_relay, git_naughty_list, + repo_sync_index, + sync_action_tx, } } + /// Set the sync action sender for triggering filter recomputation after announcement promotion. + /// + /// When an announcement is promoted from purgatory to Full sync level, the SyncManager + /// needs to subscribe to PR events for that repo on all connected relays. This sender + /// is used to trigger that subscription. + pub fn set_sync_action_tx( + &mut self, + tx: tokio::sync::mpsc::Sender, + ) { + self.sync_action_tx = Some(tx); + } + /// Get reference to the git naughty list tracker pub fn git_naughty_list(&self) -> &Arc { &self.git_naughty_list @@ -482,9 +509,98 @@ impl SyncContext for RealSyncContext { self.local_relay.as_ref(), &self.purgatory, &self.git_data_path, + self.repo_sync_index.clone(), ) .await?; + // If announcements were promoted (now Full sync level), notify SyncManager to + // recompute filters so PR event subscriptions are created on connected relays. + if result.announcements_released > 0 { + if let (Some(ref tx), Some(ref repo_sync_index)) = + (&self.sync_action_tx, &self.repo_sync_index) + { + let index = repo_sync_index.read().await; + for (repo_id, needs) in index.iter() { + if needs.sync_level == crate::sync::SyncLevel::Full + && !needs.root_events.is_empty() + { + // Send AddFilters for Full repos with root events + for relay_url in &needs.relays { + if let Some(ref domain) = self.our_domain_value { + if relay_url.contains(domain.as_str()) { + continue; + } + } + let full_repos: std::collections::HashSet = + std::iter::once(repo_id.clone()).collect(); + let filters = + crate::sync::filters::build_sync_level_aware_filters( + &full_repos, + &std::collections::HashSet::new(), + &needs.root_events, + None, + ); + let action = crate::sync::AddFilters { + relay_url: relay_url.clone(), + items: crate::sync::PendingItems { + repos: full_repos.clone(), + root_events: needs.root_events.clone(), + }, + filters, + }; + if let Err(e) = tx.send(action).await { + debug!( + relay = %relay_url, + error = %e, + "Failed to send AddFilters after announcement promotion" + ); + } else { + debug!( + relay = %relay_url, + repo_id = %repo_id, + "Sent AddFilters to SyncManager after announcement promotion" + ); + } + } + } else if needs.sync_level == crate::sync::SyncLevel::Full { + // Even without root_events, send empty repo filter to ensure + // Layer 2 subscriptions (PR events) are set up + for relay_url in &needs.relays { + if let Some(ref domain) = self.our_domain_value { + if relay_url.contains(domain.as_str()) { + continue; + } + } + let full_repos: std::collections::HashSet = + std::iter::once(repo_id.clone()).collect(); + let filters = + crate::sync::filters::build_sync_level_aware_filters( + &full_repos, + &std::collections::HashSet::new(), + &std::collections::HashSet::new(), + None, + ); + let action = crate::sync::AddFilters { + relay_url: relay_url.clone(), + items: crate::sync::PendingItems { + repos: full_repos.clone(), + root_events: std::collections::HashSet::new(), + }, + filters, + }; + if let Err(e) = tx.send(action).await { + debug!( + relay = %relay_url, + error = %e, + "Failed to send AddFilters (no root_events) after announcement promotion" + ); + } + } + } + } + } + } + // Convert from git::sync::ProcessResult to our ProcessResult Ok(ProcessResult { states_released: result.states_released, -- cgit v1.2.3