upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/nostr/policy/state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/nostr/policy/state.rs')
-rw-r--r--src/nostr/policy/state.rs75
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 @@
1use std::collections::HashSet;
1use std::path::{Path, PathBuf}; 2use std::path::{Path, PathBuf};
2 3
3use anyhow::{Context, Result}; 4use anyhow::{Context, Result};
@@ -10,7 +11,7 @@ use nostr_relay_builder::prelude::Event;
10 11
11use super::PolicyContext; 12use super::PolicyContext;
12use crate::git; 13use crate::git;
13use crate::git::authorization::fetch_repository_data; 14use crate::git::authorization::fetch_repository_data_with_purgatory;
14use crate::nostr::events::{validate_state, RepositoryAnnouncement, RepositoryState}; 15use 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 {