diff options
Diffstat (limited to 'src/nostr/policy/state.rs')
| -rw-r--r-- | src/nostr/policy/state.rs | 75 |
1 files changed, 73 insertions, 2 deletions
diff --git a/src/nostr/policy/state.rs b/src/nostr/policy/state.rs index 3411077..df743ae 100644 --- a/src/nostr/policy/state.rs +++ b/src/nostr/policy/state.rs | |||
| @@ -1,3 +1,4 @@ | |||
| 1 | use std::collections::HashSet; | ||
| 1 | use std::path::{Path, PathBuf}; | 2 | use std::path::{Path, PathBuf}; |
| 2 | 3 | ||
| 3 | use anyhow::{Context, Result}; | 4 | use anyhow::{Context, Result}; |
| @@ -10,7 +11,7 @@ use nostr_relay_builder::prelude::Event; | |||
| 10 | 11 | ||
| 11 | use super::PolicyContext; | 12 | use super::PolicyContext; |
| 12 | use crate::git; | 13 | use crate::git; |
| 13 | use crate::git::authorization::fetch_repository_data; | 14 | use crate::git::authorization::fetch_repository_data_with_purgatory; |
| 14 | use crate::nostr::events::{validate_state, RepositoryAnnouncement, RepositoryState}; | 15 | use crate::nostr::events::{validate_state, RepositoryAnnouncement, RepositoryState}; |
| 15 | 16 | ||
| 16 | /// Result of state policy evaluation | 17 | /// Result of state policy evaluation |
| @@ -76,7 +77,13 @@ impl StatePolicy { | |||
| 76 | } | 77 | } |
| 77 | 78 | ||
| 78 | // Get all repositories and state events from db with identifier | 79 | // Get all repositories and state events from db with identifier |
| 79 | let db_repo_data = fetch_repository_data(&self.ctx.database, &state.identifier).await?; | 80 | // Include purgatory announcements for authorization |
| 81 | let db_repo_data = fetch_repository_data_with_purgatory( | ||
| 82 | &self.ctx.database, | ||
| 83 | &self.ctx.purgatory, | ||
| 84 | &state.identifier, | ||
| 85 | ) | ||
| 86 | .await?; | ||
| 80 | 87 | ||
| 81 | // CRITICAL: Check if author is authorized via maintainer set | 88 | // CRITICAL: Check if author is authorized via maintainer set |
| 82 | // State events MUST be rejected if author is not in maintainer set of any accepted announcement | 89 | // State events MUST be rejected if author is not in maintainer set of any accepted announcement |
| @@ -139,6 +146,34 @@ impl StatePolicy { | |||
| 139 | "State event author authorized via maintainer set" | 146 | "State event author authorized via maintainer set" |
| 140 | ); | 147 | ); |
| 141 | 148 | ||
| 149 | // Extend expiry for any purgatory announcements for this identifier. | ||
| 150 | // | ||
| 151 | // Per design doc decision #4: state event arrival extends the purgatory | ||
| 152 | // announcement's expiry (reset the 30-minute protocol timer). This prevents | ||
| 153 | // premature expiry during slow sync operations — the repo is actively receiving | ||
| 154 | // metadata so it should stay alive. | ||
| 155 | // | ||
| 156 | // We extend for all owners that authorized this state event, since the state | ||
| 157 | // event proves the repo is active regardless of which owner's announcement | ||
| 158 | // authorized it. | ||
| 159 | for owner_hex in &authorized_owners { | ||
| 160 | if let Ok(owner_pk) = nostr_sdk::PublicKey::from_hex(owner_hex) { | ||
| 161 | if self.ctx.purgatory.has_purgatory_announcement(&owner_pk, &state.identifier) { | ||
| 162 | self.ctx.purgatory.extend_announcement_expiry( | ||
| 163 | &owner_pk, | ||
| 164 | &state.identifier, | ||
| 165 | std::time::Duration::from_secs(1800), | ||
| 166 | ); | ||
| 167 | tracing::debug!( | ||
| 168 | event_id = %event.id, | ||
| 169 | identifier = %state.identifier, | ||
| 170 | owner = %owner_hex, | ||
| 171 | "Extended purgatory announcement expiry due to state event arrival" | ||
| 172 | ); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 142 | // Duplicate check in db | 177 | // Duplicate check in db |
| 143 | if db_repo_data.states.iter().any(|e| e.event.id.eq(&event.id)) { | 178 | if db_repo_data.states.iter().any(|e| e.event.id.eq(&event.id)) { |
| 144 | tracing::debug!("processed state event duplicate (in db): {}", event.id); | 179 | tracing::debug!("processed state event duplicate (in db): {}", event.id); |
| @@ -186,6 +221,42 @@ impl StatePolicy { | |||
| 186 | } | 221 | } |
| 187 | } | 222 | } |
| 188 | 223 | ||
| 224 | // After copying OIDs to other owner repos, promote any purgatory announcements | ||
| 225 | // for those repos. This handles the case where two maintainers push to the same | ||
| 226 | // identifier on the same relay with identical commit hashes: the second maintainer's | ||
| 227 | // announcement sits in purgatory, and when their state event arrives the relay copies | ||
| 228 | // commits from the first maintainer's repo — but without this call the announcement | ||
| 229 | // would stay in purgatory indefinitely. | ||
| 230 | let local_relay = self.ctx.get_local_relay(); | ||
| 231 | let empty_oids: HashSet<String> = HashSet::new(); | ||
| 232 | for announcement in &db_repo_data.announcements { | ||
| 233 | let target_repo_path = self.ctx.git_data_path.join(announcement.repo_path()); | ||
| 234 | if target_repo_path != repo_with_git_data { | ||
| 235 | // OIDs were copied to this repo by process_state_with_git_data; | ||
| 236 | // check if there's a purgatory announcement waiting for it. | ||
| 237 | if let Err(e) = crate::git::sync::process_newly_available_git_data( | ||
| 238 | &target_repo_path, | ||
| 239 | &empty_oids, | ||
| 240 | &self.ctx.database, | ||
| 241 | local_relay.as_ref(), | ||
| 242 | &self.ctx.purgatory, | ||
| 243 | &self.ctx.git_data_path, | ||
| 244 | None, | ||
| 245 | None, | ||
| 246 | ) | ||
| 247 | .await | ||
| 248 | { | ||
| 249 | tracing::warn!( | ||
| 250 | identifier = %state.identifier, | ||
| 251 | event_id = %event.id, | ||
| 252 | repo_path = %target_repo_path.display(), | ||
| 253 | error = %e, | ||
| 254 | "Failed to process purgatory announcements for target repo after git sync copy" | ||
| 255 | ); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 189 | // Event will be saved and broadcast by relay builder | 260 | // Event will be saved and broadcast by relay builder |
| 190 | Ok(WritePolicyResult::Accept) | 261 | Ok(WritePolicyResult::Accept) |
| 191 | } else { | 262 | } else { |