upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/nostr
diff options
context:
space:
mode:
Diffstat (limited to 'src/nostr')
-rw-r--r--src/nostr/builder.rs159
-rw-r--r--src/nostr/events.rs16
-rw-r--r--src/nostr/policy/state.rs9
3 files changed, 155 insertions, 29 deletions
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs
index 34014db..3baa2ff 100644
--- a/src/nostr/builder.rs
+++ b/src/nostr/builder.rs
@@ -98,6 +98,62 @@ impl Nip34WritePolicy {
98 self.ctx.set_local_relay(relay); 98 self.ctx.set_local_relay(relay);
99 } 99 }
100 100
101 /// Extract repository identifier from event's 'd' tag.
102 ///
103 /// Used for structured logging when parsing fails - we try to extract
104 /// the identifier even if full parsing failed.
105 fn extract_identifier_from_event(event: &Event) -> String {
106 use nostr_relay_builder::prelude::TagKind;
107 event
108 .tags
109 .iter()
110 .find(|t| t.kind() == TagKind::d())
111 .and_then(|t| t.content())
112 .map(|s| s.to_string())
113 .unwrap_or_else(|| "unknown".to_string())
114 }
115
116 /// Extract ALL repository identifiers from PR event's 'a' tags.
117 ///
118 /// PR events can reference multiple repositories via multiple 'a' tags
119 /// (e.g., when there are multiple maintainers). Each tag has format
120 /// `30617:<owner_pubkey>:<identifier>`.
121 ///
122 /// Returns a vector of unique identifiers, or `["unknown"]` if none found.
123 fn extract_repos_from_pr_event(event: &Event) -> Vec<String> {
124 let repos: Vec<String> = event
125 .tags
126 .iter()
127 .filter_map(|tag| {
128 let tag_vec = tag.clone().to_vec();
129 if tag_vec.len() >= 2 && tag_vec[0] == "a" && tag_vec[1].starts_with("30617:") {
130 // Format: 30617:<owner_pubkey>:<identifier>
131 let parts: Vec<&str> = tag_vec[1].split(':').collect();
132 if parts.len() >= 3 {
133 Some(parts[2].to_string())
134 } else {
135 None
136 }
137 } else {
138 None
139 }
140 })
141 .collect();
142
143 // Deduplicate while preserving order
144 let mut seen = std::collections::HashSet::new();
145 let unique_repos: Vec<String> = repos
146 .into_iter()
147 .filter(|r| seen.insert(r.clone()))
148 .collect();
149
150 if unique_repos.is_empty() {
151 vec!["unknown".to_string()]
152 } else {
153 unique_repos
154 }
155 }
156
101 /// Handle repository announcement event 157 /// Handle repository announcement event
102 async fn handle_announcement(&self, event: &Event) -> WritePolicyResult { 158 async fn handle_announcement(&self, event: &Event) -> WritePolicyResult {
103 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); 159 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex());
@@ -129,10 +185,21 @@ impl Nip34WritePolicy {
129 WritePolicyResult::Accept 185 WritePolicyResult::Accept
130 } 186 }
131 Err(e) => { 187 Err(e) => {
188 let npub = event
189 .pubkey
190 .to_bech32()
191 .unwrap_or_else(|_| event.pubkey.to_hex());
192 let event_id_short = &event.id.to_hex()[..12];
193 // Try to extract repo identifier from 'd' tag even if parsing failed
194 let repo = Self::extract_identifier_from_event(event);
195 // Structured log for migration scripts
132 tracing::warn!( 196 tracing::warn!(
133 "Failed to parse repository announcement {}: {}", 197 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
134 event_id_str, 198 event.kind.as_u16(),
135 e 199 event_id_short,
200 e,
201 repo,
202 npub
136 ); 203 );
137 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) 204 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e))
138 } 205 }
@@ -157,10 +224,21 @@ impl Nip34WritePolicy {
157 WritePolicyResult::Accept 224 WritePolicyResult::Accept
158 } 225 }
159 Err(e) => { 226 Err(e) => {
227 let npub = event
228 .pubkey
229 .to_bech32()
230 .unwrap_or_else(|_| event.pubkey.to_hex());
231 let event_id_short = &event.id.to_hex()[..12];
232 // Try to extract repo identifier from 'd' tag even if parsing failed
233 let repo = Self::extract_identifier_from_event(event);
234 // Structured log for migration scripts
160 tracing::warn!( 235 tracing::warn!(
161 "Failed to parse maintainer announcement {}: {}", 236 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
162 event_id_str, 237 event.kind.as_u16(),
163 e 238 event_id_short,
239 e,
240 repo,
241 npub
164 ); 242 );
165 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) 243 WritePolicyResult::reject(format!("Failed to parse announcement: {}", e))
166 } 244 }
@@ -183,8 +261,6 @@ impl Nip34WritePolicy {
183 /// * `event` - The state event to validate 261 /// * `event` - The state event to validate
184 /// * `is_synced` - True if this event came from proactive sync (vs user-submitted) 262 /// * `is_synced` - True if this event came from proactive sync (vs user-submitted)
185 async fn handle_state(&self, event: &Event, is_synced: bool) -> WritePolicyResult { 263 async fn handle_state(&self, event: &Event, is_synced: bool) -> WritePolicyResult {
186 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex());
187
188 match self.state_policy.validate(event) { 264 match self.state_policy.validate(event) {
189 StateResult::Accept => { 265 StateResult::Accept => {
190 // Process state alignment asynchronously 266 // Process state alignment asynchronously
@@ -195,7 +271,22 @@ impl Nip34WritePolicy {
195 { 271 {
196 Ok(poilicy_result) => poilicy_result, 272 Ok(poilicy_result) => poilicy_result,
197 Err(e) => { 273 Err(e) => {
198 tracing::warn!("Failed to process state event {}: {}", event_id_str, e); 274 let npub = event
275 .pubkey
276 .to_bech32()
277 .unwrap_or_else(|_| event.pubkey.to_hex());
278 let event_id_short = &event.id.to_hex()[..12];
279 // Try to extract repo identifier from 'd' tag even if parsing failed
280 let repo = Self::extract_identifier_from_event(event);
281 // Structured log for migration scripts
282 tracing::warn!(
283 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
284 event.kind.as_u16(),
285 event_id_short,
286 e,
287 repo,
288 npub
289 );
199 // reject if processing failed 290 // reject if processing failed
200 WritePolicyResult::Reject { 291 WritePolicyResult::Reject {
201 status: false, 292 status: false,
@@ -205,7 +296,22 @@ impl Nip34WritePolicy {
205 } 296 }
206 } 297 }
207 StateResult::Reject(reason) => { 298 StateResult::Reject(reason) => {
208 tracing::warn!("Rejected repository state {}: {}", event_id_str, reason); 299 let npub = event
300 .pubkey
301 .to_bech32()
302 .unwrap_or_else(|_| event.pubkey.to_hex());
303 let event_id_short = &event.id.to_hex()[..12];
304 // Try to extract repo identifier from 'd' tag even if parsing failed
305 let repo = Self::extract_identifier_from_event(event);
306 // Structured log for migration scripts
307 tracing::warn!(
308 "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}",
309 event.kind.as_u16(),
310 event_id_short,
311 reason,
312 repo,
313 npub
314 );
209 WritePolicyResult::reject(reason) 315 WritePolicyResult::reject(reason)
210 } 316 }
211 } 317 }
@@ -303,9 +409,12 @@ impl Nip34WritePolicy {
303 ); 409 );
304 410
305 // Add to purgatory 411 // Add to purgatory
306 self.ctx 412 self.ctx.purgatory.add_pr(
307 .purgatory 413 event.clone(),
308 .add_pr(event.clone(), event.id.to_hex(), commit.clone()); 414 event.id.to_hex(),
415 commit.clone(),
416 is_synced,
417 );
309 418
310 WritePolicyResult::Reject { 419 WritePolicyResult::Reject {
311 status: true, // Client sees OK 420 status: true, // Client sees OK
@@ -323,11 +432,25 @@ impl Nip34WritePolicy {
323 } 432 }
324 Err(e) => { 433 Err(e) => {
325 // Error checking git data - reject event 434 // Error checking git data - reject event
326 tracing::warn!( 435 let npub = event
327 "Failed to check git data for PR event {}: {}", 436 .pubkey
328 event_id_str, 437 .to_bech32()
329 e 438 .unwrap_or_else(|_| event.pubkey.to_hex());
330 ); 439 let event_id_short = &event.id.to_hex()[..12];
440 // Extract ALL repo identifiers from 'a' tags for PR events
441 // (PR events can reference multiple repos when there are multiple maintainers)
442 let repos = Self::extract_repos_from_pr_event(event);
443 // Structured log for migration scripts - log once per repo
444 for repo in &repos {
445 tracing::warn!(
446 "[PARSE_FAIL] kind={} event_id={}... reason=\"git data check failed: {}\" repo={} npub={}",
447 event.kind.as_u16(),
448 event_id_short,
449 e,
450 repo,
451 npub
452 );
453 }
331 WritePolicyResult::reject(format!("Failed to check git data: {}", e)) 454 WritePolicyResult::reject(format!("Failed to check git data: {}", e))
332 } 455 }
333 } 456 }
diff --git a/src/nostr/events.rs b/src/nostr/events.rs
index 718633e..a441742 100644
--- a/src/nostr/events.rs
+++ b/src/nostr/events.rs
@@ -419,14 +419,14 @@ pub fn validate_announcement(
419 // GRASP-01: Normal mode - accept if announcement lists our service AND matches repository whitelist (if enabled) 419 // GRASP-01: Normal mode - accept if announcement lists our service AND matches repository whitelist (if enabled)
420 if lists_service && !archive_config.read_only { 420 if lists_service && !archive_config.read_only {
421 // Check repository whitelist if enabled 421 // Check repository whitelist if enabled
422 if repository_config.enabled() { 422 if repository_config.enabled()
423 if !repository_config.matches(&npub, &announcement.identifier) { 423 && !repository_config.matches(&npub, &announcement.identifier)
424 return AnnouncementResult::Reject(format!( 424 {
425 "Announcement lists service but does not match repository whitelist. \ 425 return AnnouncementResult::Reject(format!(
426 Repository {}/{} not in whitelist", 426 "Announcement lists service but does not match repository whitelist. \
427 npub, announcement.identifier 427 Repository {}/{} not in whitelist",
428 )); 428 npub, announcement.identifier
429 } 429 ));
430 } 430 }
431 return AnnouncementResult::Accept; 431 return AnnouncementResult::Accept;
432 } 432 }
diff --git a/src/nostr/policy/state.rs b/src/nostr/policy/state.rs
index f94f004..3411077 100644
--- a/src/nostr/policy/state.rs
+++ b/src/nostr/policy/state.rs
@@ -205,9 +205,12 @@ impl StatePolicy {
205 205
206 // If no git data - add to purgatory 206 // If no git data - add to purgatory
207 // (add_state automatically enqueues for background sync) 207 // (add_state automatically enqueues for background sync)
208 self.ctx 208 self.ctx.purgatory.add_state(
209 .purgatory 209 event.clone(),
210 .add_state(event.clone(), state.identifier.clone(), event.pubkey); 210 state.identifier.clone(),
211 event.pubkey,
212 is_synced,
213 );
211 214
212 tracing::info!( 215 tracing::info!(
213 "state event added to purgatory: eventid: {}, identifier: {}", 216 "state event added to purgatory: eventid: {}, identifier: {}",