diff options
Diffstat (limited to 'src/nostr/builder.rs')
| -rw-r--r-- | src/nostr/builder.rs | 165 |
1 files changed, 145 insertions, 20 deletions
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index d056e46..7a05348 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs | |||
| @@ -102,6 +102,62 @@ impl Nip34WritePolicy { | |||
| 102 | self.ctx.set_local_relay(relay); | 102 | self.ctx.set_local_relay(relay); |
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | /// Extract repository identifier from event's 'd' tag. | ||
| 106 | /// | ||
| 107 | /// Used for structured logging when parsing fails - we try to extract | ||
| 108 | /// the identifier even if full parsing failed. | ||
| 109 | fn extract_identifier_from_event(event: &Event) -> String { | ||
| 110 | use nostr_relay_builder::prelude::TagKind; | ||
| 111 | event | ||
| 112 | .tags | ||
| 113 | .iter() | ||
| 114 | .find(|t| t.kind() == TagKind::d()) | ||
| 115 | .and_then(|t| t.content()) | ||
| 116 | .map(|s| s.to_string()) | ||
| 117 | .unwrap_or_else(|| "unknown".to_string()) | ||
| 118 | } | ||
| 119 | |||
| 120 | /// Extract ALL repository identifiers from PR event's 'a' tags. | ||
| 121 | /// | ||
| 122 | /// PR events can reference multiple repositories via multiple 'a' tags | ||
| 123 | /// (e.g., when there are multiple maintainers). Each tag has format | ||
| 124 | /// `30617:<owner_pubkey>:<identifier>`. | ||
| 125 | /// | ||
| 126 | /// Returns a vector of unique identifiers, or `["unknown"]` if none found. | ||
| 127 | fn extract_repos_from_pr_event(event: &Event) -> Vec<String> { | ||
| 128 | let repos: Vec<String> = event | ||
| 129 | .tags | ||
| 130 | .iter() | ||
| 131 | .filter_map(|tag| { | ||
| 132 | let tag_vec = tag.clone().to_vec(); | ||
| 133 | if tag_vec.len() >= 2 && tag_vec[0] == "a" && tag_vec[1].starts_with("30617:") { | ||
| 134 | // Format: 30617:<owner_pubkey>:<identifier> | ||
| 135 | let parts: Vec<&str> = tag_vec[1].split(':').collect(); | ||
| 136 | if parts.len() >= 3 { | ||
| 137 | Some(parts[2].to_string()) | ||
| 138 | } else { | ||
| 139 | None | ||
| 140 | } | ||
| 141 | } else { | ||
| 142 | None | ||
| 143 | } | ||
| 144 | }) | ||
| 145 | .collect(); | ||
| 146 | |||
| 147 | // Deduplicate while preserving order | ||
| 148 | let mut seen = std::collections::HashSet::new(); | ||
| 149 | let unique_repos: Vec<String> = repos | ||
| 150 | .into_iter() | ||
| 151 | .filter(|r| seen.insert(r.clone())) | ||
| 152 | .collect(); | ||
| 153 | |||
| 154 | if unique_repos.is_empty() { | ||
| 155 | vec!["unknown".to_string()] | ||
| 156 | } else { | ||
| 157 | unique_repos | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 105 | /// Handle repository announcement event | 161 | /// Handle repository announcement event |
| 106 | async fn handle_announcement(&self, event: &Event) -> WritePolicyResult { | 162 | async fn handle_announcement(&self, event: &Event) -> WritePolicyResult { |
| 107 | let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); | 163 | let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); |
| @@ -133,10 +189,21 @@ impl Nip34WritePolicy { | |||
| 133 | WritePolicyResult::Accept | 189 | WritePolicyResult::Accept |
| 134 | } | 190 | } |
| 135 | Err(e) => { | 191 | Err(e) => { |
| 192 | let npub = event | ||
| 193 | .pubkey | ||
| 194 | .to_bech32() | ||
| 195 | .unwrap_or_else(|_| event.pubkey.to_hex()); | ||
| 196 | let event_id_short = &event.id.to_hex()[..12]; | ||
| 197 | // Try to extract repo identifier from 'd' tag even if parsing failed | ||
| 198 | let repo = Self::extract_identifier_from_event(event); | ||
| 199 | // Structured log for migration scripts | ||
| 136 | tracing::warn!( | 200 | tracing::warn!( |
| 137 | "Failed to parse repository announcement {}: {}", | 201 | "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}", |
| 138 | event_id_str, | 202 | event.kind.as_u16(), |
| 139 | e | 203 | event_id_short, |
| 204 | e, | ||
| 205 | repo, | ||
| 206 | npub | ||
| 140 | ); | 207 | ); |
| 141 | WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) | 208 | WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) |
| 142 | } | 209 | } |
| @@ -185,10 +252,21 @@ impl Nip34WritePolicy { | |||
| 185 | WritePolicyResult::Accept | 252 | WritePolicyResult::Accept |
| 186 | } | 253 | } |
| 187 | Err(e) => { | 254 | Err(e) => { |
| 255 | let npub = event | ||
| 256 | .pubkey | ||
| 257 | .to_bech32() | ||
| 258 | .unwrap_or_else(|_| event.pubkey.to_hex()); | ||
| 259 | let event_id_short = &event.id.to_hex()[..12]; | ||
| 260 | // Try to extract repo identifier from 'd' tag even if parsing failed | ||
| 261 | let repo = Self::extract_identifier_from_event(event); | ||
| 262 | // Structured log for migration scripts | ||
| 188 | tracing::warn!( | 263 | tracing::warn!( |
| 189 | "Failed to parse maintainer announcement {}: {}", | 264 | "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}", |
| 190 | event_id_str, | 265 | event.kind.as_u16(), |
| 191 | e | 266 | event_id_short, |
| 267 | e, | ||
| 268 | repo, | ||
| 269 | npub | ||
| 192 | ); | 270 | ); |
| 193 | WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) | 271 | WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) |
| 194 | } | 272 | } |
| @@ -211,8 +289,6 @@ impl Nip34WritePolicy { | |||
| 211 | /// * `event` - The state event to validate | 289 | /// * `event` - The state event to validate |
| 212 | /// * `is_synced` - True if this event came from proactive sync (vs user-submitted) | 290 | /// * `is_synced` - True if this event came from proactive sync (vs user-submitted) |
| 213 | async fn handle_state(&self, event: &Event, is_synced: bool) -> WritePolicyResult { | 291 | async fn handle_state(&self, event: &Event, is_synced: bool) -> WritePolicyResult { |
| 214 | let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); | ||
| 215 | |||
| 216 | match self.state_policy.validate(event) { | 292 | match self.state_policy.validate(event) { |
| 217 | StateResult::Accept => { | 293 | StateResult::Accept => { |
| 218 | // Process state alignment asynchronously | 294 | // Process state alignment asynchronously |
| @@ -223,7 +299,22 @@ impl Nip34WritePolicy { | |||
| 223 | { | 299 | { |
| 224 | Ok(poilicy_result) => poilicy_result, | 300 | Ok(poilicy_result) => poilicy_result, |
| 225 | Err(e) => { | 301 | Err(e) => { |
| 226 | tracing::warn!("Failed to process state event {}: {}", event_id_str, e); | 302 | let npub = event |
| 303 | .pubkey | ||
| 304 | .to_bech32() | ||
| 305 | .unwrap_or_else(|_| event.pubkey.to_hex()); | ||
| 306 | let event_id_short = &event.id.to_hex()[..12]; | ||
| 307 | // Try to extract repo identifier from 'd' tag even if parsing failed | ||
| 308 | let repo = Self::extract_identifier_from_event(event); | ||
| 309 | // Structured log for migration scripts | ||
| 310 | tracing::warn!( | ||
| 311 | "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}", | ||
| 312 | event.kind.as_u16(), | ||
| 313 | event_id_short, | ||
| 314 | e, | ||
| 315 | repo, | ||
| 316 | npub | ||
| 317 | ); | ||
| 227 | // reject if processing failed | 318 | // reject if processing failed |
| 228 | WritePolicyResult::Reject { | 319 | WritePolicyResult::Reject { |
| 229 | status: false, | 320 | status: false, |
| @@ -233,7 +324,22 @@ impl Nip34WritePolicy { | |||
| 233 | } | 324 | } |
| 234 | } | 325 | } |
| 235 | StateResult::Reject(reason) => { | 326 | StateResult::Reject(reason) => { |
| 236 | tracing::warn!("Rejected repository state {}: {}", event_id_str, reason); | 327 | let npub = event |
| 328 | .pubkey | ||
| 329 | .to_bech32() | ||
| 330 | .unwrap_or_else(|_| event.pubkey.to_hex()); | ||
| 331 | let event_id_short = &event.id.to_hex()[..12]; | ||
| 332 | // Try to extract repo identifier from 'd' tag even if parsing failed | ||
| 333 | let repo = Self::extract_identifier_from_event(event); | ||
| 334 | // Structured log for migration scripts | ||
| 335 | tracing::warn!( | ||
| 336 | "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}", | ||
| 337 | event.kind.as_u16(), | ||
| 338 | event_id_short, | ||
| 339 | reason, | ||
| 340 | repo, | ||
| 341 | npub | ||
| 342 | ); | ||
| 237 | WritePolicyResult::reject(reason) | 343 | WritePolicyResult::reject(reason) |
| 238 | } | 344 | } |
| 239 | } | 345 | } |
| @@ -331,9 +437,12 @@ impl Nip34WritePolicy { | |||
| 331 | ); | 437 | ); |
| 332 | 438 | ||
| 333 | // Add to purgatory | 439 | // Add to purgatory |
| 334 | self.ctx | 440 | self.ctx.purgatory.add_pr( |
| 335 | .purgatory | 441 | event.clone(), |
| 336 | .add_pr(event.clone(), event.id.to_hex(), commit.clone()); | 442 | event.id.to_hex(), |
| 443 | commit.clone(), | ||
| 444 | is_synced, | ||
| 445 | ); | ||
| 337 | 446 | ||
| 338 | WritePolicyResult::Reject { | 447 | WritePolicyResult::Reject { |
| 339 | status: true, // Client sees OK | 448 | status: true, // Client sees OK |
| @@ -351,11 +460,25 @@ impl Nip34WritePolicy { | |||
| 351 | } | 460 | } |
| 352 | Err(e) => { | 461 | Err(e) => { |
| 353 | // Error checking git data - reject event | 462 | // Error checking git data - reject event |
| 354 | tracing::warn!( | 463 | let npub = event |
| 355 | "Failed to check git data for PR event {}: {}", | 464 | .pubkey |
| 356 | event_id_str, | 465 | .to_bech32() |
| 357 | e | 466 | .unwrap_or_else(|_| event.pubkey.to_hex()); |
| 358 | ); | 467 | let event_id_short = &event.id.to_hex()[..12]; |
| 468 | // Extract ALL repo identifiers from 'a' tags for PR events | ||
| 469 | // (PR events can reference multiple repos when there are multiple maintainers) | ||
| 470 | let repos = Self::extract_repos_from_pr_event(event); | ||
| 471 | // Structured log for migration scripts - log once per repo | ||
| 472 | for repo in &repos { | ||
| 473 | tracing::warn!( | ||
| 474 | "[PARSE_FAIL] kind={} event_id={}... reason=\"git data check failed: {}\" repo={} npub={}", | ||
| 475 | event.kind.as_u16(), | ||
| 476 | event_id_short, | ||
| 477 | e, | ||
| 478 | repo, | ||
| 479 | npub | ||
| 480 | ); | ||
| 481 | } | ||
| 359 | WritePolicyResult::reject(format!("Failed to check git data: {}", e)) | 482 | WritePolicyResult::reject(format!("Failed to check git data: {}", e)) |
| 360 | } | 483 | } |
| 361 | } | 484 | } |
| @@ -462,9 +585,11 @@ impl Nip34WritePolicy { | |||
| 462 | let (addressable_refs, event_refs) = | 585 | let (addressable_refs, event_refs) = |
| 463 | RelatedEventPolicy::extract_reference_tags(event); | 586 | RelatedEventPolicy::extract_reference_tags(event); |
| 464 | tracing::info!( | 587 | tracing::info!( |
| 465 | "Rejected orphan {} event {}: no references to accepted repos or events (checked {} addressable, {} event refs)", | 588 | "Rejected orphan {} event {} (kind={}, pubkey={}): no references to accepted repos or events (checked {} addressable, {} event refs)", |
| 466 | event_type, | 589 | event_type, |
| 467 | event_id_str, | 590 | event.id.to_hex(), |
| 591 | event.kind.as_u16(), | ||
| 592 | event.pubkey.to_hex(), | ||
| 468 | addressable_refs.len(), | 593 | addressable_refs.len(), |
| 469 | event_refs.len() | 594 | event_refs.len() |
| 470 | ); | 595 | ); |