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:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-31 09:18:21 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-31 10:49:09 +0000
commit768fe91caa676e4501aa26e14e01ca47f3ea4ca1 (patch)
treee697becb6b2253909d399073f5c2bd2d571fcf5e /src/nostr/policy/state.rs
parent3d6901831904141166d9ed8f47813c45cba109b6 (diff)
purgatory: fix pr event recieve code
Diffstat (limited to 'src/nostr/policy/state.rs')
-rw-r--r--src/nostr/policy/state.rs187
1 files changed, 2 insertions, 185 deletions
diff --git a/src/nostr/policy/state.rs b/src/nostr/policy/state.rs
index 13f2549..1203890 100644
--- a/src/nostr/policy/state.rs
+++ b/src/nostr/policy/state.rs
@@ -6,15 +6,12 @@ use nostr_relay_builder::builder::WritePolicyResult;
6/// 6///
7/// Handles validation of NIP-34 repository state events (kind 30618) 7/// Handles validation of NIP-34 repository state events (kind 30618)
8/// and aligns git refs with authorized state according to GRASP-01. 8/// and aligns git refs with authorized state according to GRASP-01.
9use nostr_relay_builder::prelude::{Alphabet, Event, Filter, Kind, PublicKey, SingleLetterTag}; 9use nostr_relay_builder::prelude::Event;
10 10
11use super::PolicyContext; 11use super::PolicyContext;
12use crate::git::authorization::{collect_authorized_maintainers, fetch_repository_data}; 12use crate::git::authorization::{collect_authorized_maintainers, fetch_repository_data};
13use crate::git::{self}; 13use crate::git::{self};
14use crate::nostr::events::{ 14use crate::nostr::events::{validate_state, RepositoryAnnouncement, RepositoryState};
15 validate_state, RepositoryAnnouncement, RepositoryState, KIND_REPOSITORY_ANNOUNCEMENT,
16 KIND_REPOSITORY_STATE,
17};
18 15
19/// Result of aligning a repository with authorized state 16/// Result of aligning a repository with authorized state
20#[derive(Debug, Default)] 17#[derive(Debug, Default)]
@@ -168,186 +165,6 @@ impl StatePolicy {
168 } 165 }
169 } 166 }
170 167
171 /// Check if any git repositories exist for the given identifier
172 ///
173 /// Scans the git_data_path for any directories matching the pattern:
174 /// `<any-npub>/<identifier>.git`
175 ///
176 /// This is used to distinguish "no git data yet" from "not authorized".
177 fn has_git_data_for_identifier(&self, identifier: &str) -> bool {
178 let git_data_path = &self.ctx.git_data_path;
179
180 // Check if git_data_path exists
181 if !git_data_path.exists() {
182 return false;
183 }
184
185 // Scan for any npub directories
186 let read_dir = match std::fs::read_dir(git_data_path) {
187 Ok(dir) => dir,
188 Err(_) => return false,
189 };
190
191 for entry in read_dir.flatten() {
192 if let Ok(file_type) = entry.file_type() {
193 if file_type.is_dir() {
194 // Check if <npub>/<identifier>.git exists
195 let repo_path = entry.path().join(format!("{}.git", identifier));
196 if repo_path.exists() {
197 return true;
198 }
199 }
200 }
201 }
202
203 false
204 }
205
206 /// Check if this state event is the latest for its identifier among authorized authors
207 ///
208 /// A state is considered "latest" if no other state event in the database
209 /// from an authorized author has a newer timestamp.
210 async fn is_latest_state_for_identifier(
211 &self,
212 state: &RepositoryState,
213 authorized_pubkeys: &[PublicKey],
214 ) -> Result<bool, String> {
215 let filter = Filter::new()
216 .kind(Kind::from(KIND_REPOSITORY_STATE))
217 .custom_tag(
218 SingleLetterTag::lowercase(Alphabet::D),
219 state.identifier.clone(),
220 );
221
222 match self.ctx.database.query(filter).await {
223 Ok(events) => {
224 for event in events {
225 // Skip comparing to self (same event ID)
226 if event.id == state.event.id {
227 continue;
228 }
229 // Only consider events from authorized authors for this announcement
230 if !authorized_pubkeys.contains(&event.pubkey) {
231 continue;
232 }
233 // If any existing event from an authorized author is newer, this is not the latest
234 if event.created_at > state.event.created_at {
235 tracing::debug!(
236 "State {} is not latest: found newer state {} from {} (ts {} > {})",
237 state.event.id.to_hex(),
238 event.id.to_hex(),
239 event.pubkey.to_hex(),
240 event.created_at.as_secs(),
241 state.event.created_at.as_secs()
242 );
243 return Ok(false);
244 }
245 }
246 Ok(true)
247 }
248 Err(e) => Err(format!("Database query failed: {}", e)),
249 }
250 }
251
252 /// Find all repository announcements where the given pubkey is authorized
253 async fn find_authorized_announcements(
254 &self,
255 identifier: &str,
256 state_author: &PublicKey,
257 ) -> Result<Vec<RepositoryAnnouncement>, String> {
258 let filter = Filter::new()
259 .kind(Kind::from(KIND_REPOSITORY_ANNOUNCEMENT))
260 .custom_tag(
261 SingleLetterTag::lowercase(Alphabet::D),
262 identifier.to_string(),
263 );
264
265 match self.ctx.database.query(filter).await {
266 Ok(events) => {
267 let mut authorized = Vec::new();
268 let state_author_hex = state_author.to_hex();
269
270 for event in events {
271 if let Ok(announcement) = RepositoryAnnouncement::from_event(event.clone()) {
272 // Check if state author is authorized for this announcement
273 let is_owner = event.pubkey == *state_author;
274 let is_maintainer = announcement.maintainers.contains(&state_author_hex);
275
276 if is_owner || is_maintainer {
277 tracing::debug!(
278 "Found authorized announcement for {}: owner={}, maintainer={}",
279 identifier,
280 if is_owner {
281 event.pubkey.to_hex()
282 } else {
283 "n/a".to_string()
284 },
285 is_maintainer
286 );
287 authorized.push(announcement);
288 }
289 }
290 }
291 Ok(authorized)
292 }
293 Err(e) => Err(format!("Database query failed: {}", e)),
294 }
295 }
296
297 /// Identify all owner repositories for which this state event is the latest authorized state
298 async fn identify_owner_repositories(
299 &self,
300 state: &RepositoryState,
301 ) -> Result<Vec<(RepositoryAnnouncement, std::path::PathBuf)>, String> {
302 // Find all announcements where state author is authorized
303 let announcements = self
304 .find_authorized_announcements(&state.identifier, &state.event.pubkey)
305 .await?;
306
307 if announcements.is_empty() {
308 tracing::debug!(
309 "No authorized announcements found for state {} by {}",
310 state.identifier,
311 state.event.pubkey.to_hex()
312 );
313 return Ok(Vec::new());
314 }
315
316 let mut owner_repos = Vec::new();
317
318 for announcement in announcements {
319 // Build the list of authorized pubkeys for this specific announcement
320 let mut authorized_pubkeys = vec![announcement.event.pubkey];
321 for maintainer_hex in &announcement.maintainers {
322 if let Ok(pk) = PublicKey::from_hex(maintainer_hex) {
323 authorized_pubkeys.push(pk);
324 }
325 }
326
327 // Check if this is the latest state event for THIS announcement's context
328 if !self
329 .is_latest_state_for_identifier(state, &authorized_pubkeys)
330 .await?
331 {
332 tracing::debug!(
333 "Skipping {} in {}'s repo - not the latest state event for this context",
334 state.identifier,
335 announcement.event.pubkey.to_hex()
336 );
337 continue;
338 }
339
340 // Build repository path: <git_data_path>/<owner_npub>/<identifier>.git
341 let repo_path = self
342 .ctx
343 .git_data_path
344 .join(announcement.repo_path().clone());
345 owner_repos.push((announcement, repo_path));
346 }
347
348 Ok(owner_repos)
349 }
350
351 /// Align a repository's refs with the authorized state 168 /// Align a repository's refs with the authorized state
352 /// 169 ///
353 /// This function: 170 /// This function: