diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-24 08:02:12 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-24 11:54:18 +0000 |
| commit | 70d0197e85ae4ef85202781f6d2dc9e76bd508b3 (patch) | |
| tree | 45efb6565e81ba755acc5955e68d5b7119d1e122 /src/nostr/policy/pr_event.rs | |
| parent | f8c3e3920ed2a1bdaab30be912276993449a5476 (diff) | |
feat(purgatory): add broken purgatory implementation
Diffstat (limited to 'src/nostr/policy/pr_event.rs')
| -rw-r--r-- | src/nostr/policy/pr_event.rs | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/src/nostr/policy/pr_event.rs b/src/nostr/policy/pr_event.rs index 53da369..c7602b0 100644 --- a/src/nostr/policy/pr_event.rs +++ b/src/nostr/policy/pr_event.rs | |||
| @@ -19,6 +19,155 @@ impl PrEventPolicy { | |||
| 19 | Self { ctx } | 19 | Self { ctx } |
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | /// Check if git data exists for a PR event | ||
| 23 | /// | ||
| 24 | /// This checks: | ||
| 25 | /// 1. If a placeholder exists (git-data-first scenario) | ||
| 26 | /// 2. If the commit exists in any relevant repository | ||
| 27 | /// | ||
| 28 | /// # Returns | ||
| 29 | /// - `Ok(true)` if git data ready (either placeholder found or commit exists) | ||
| 30 | /// - `Ok(false)` if git data missing (should add to purgatory) | ||
| 31 | /// - `Err(msg)` on errors | ||
| 32 | pub async fn check_git_data_exists(&self, event: &Event) -> Result<bool, String> { | ||
| 33 | let event_id = event.id.to_hex(); | ||
| 34 | |||
| 35 | // Extract the `c` tag (commit hash) from the PR event | ||
| 36 | let commit = event.tags.iter().find_map(|tag| { | ||
| 37 | let tag_vec = tag.clone().to_vec(); | ||
| 38 | if tag_vec.len() >= 2 && tag_vec[0] == "c" { | ||
| 39 | Some(tag_vec[1].clone()) | ||
| 40 | } else { | ||
| 41 | None | ||
| 42 | } | ||
| 43 | }); | ||
| 44 | |||
| 45 | let commit = match commit { | ||
| 46 | Some(c) => c, | ||
| 47 | None => { | ||
| 48 | return Err(format!("PR event {} has no 'c' tag", event_id)); | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | |||
| 52 | // Check for placeholder first (git-data-first scenario) | ||
| 53 | if let Some(placeholder_commit) = self.ctx.purgatory.find_pr_placeholder(&event_id) { | ||
| 54 | if placeholder_commit == commit { | ||
| 55 | // Perfect match - git data arrived first with matching commit | ||
| 56 | tracing::debug!( | ||
| 57 | "Found matching placeholder for PR event {} with commit {}", | ||
| 58 | event_id, | ||
| 59 | commit | ||
| 60 | ); | ||
| 61 | // Remove placeholder - event processing will continue normally | ||
| 62 | self.ctx.purgatory.remove_pr(&event_id); | ||
| 63 | return Ok(true); | ||
| 64 | } else { | ||
| 65 | // Placeholder has different commit - incoming event supersedes | ||
| 66 | tracing::info!( | ||
| 67 | "PR event {} supersedes placeholder: event expects commit {}, placeholder has {}", | ||
| 68 | event_id, | ||
| 69 | commit, | ||
| 70 | placeholder_commit | ||
| 71 | ); | ||
| 72 | // Remove placeholder with old commit data | ||
| 73 | self.ctx.purgatory.remove_pr(&event_id); | ||
| 74 | // TODO: Also remove git data (refs/nostr/<event-id>) - Phase 5 | ||
| 75 | // Fall through to check if new commit exists | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | // Check if commit exists in any repository referenced by this PR | ||
| 80 | // Extract ALL `a` tags (repository references) from the PR event | ||
| 81 | let repo_refs: Vec<String> = event | ||
| 82 | .tags | ||
| 83 | .iter() | ||
| 84 | .filter_map(|tag| { | ||
| 85 | let tag_vec = tag.clone().to_vec(); | ||
| 86 | if tag_vec.len() >= 2 && tag_vec[0] == "a" && tag_vec[1].starts_with("30617:") { | ||
| 87 | Some(tag_vec[1].clone()) | ||
| 88 | } else { | ||
| 89 | None | ||
| 90 | } | ||
| 91 | }) | ||
| 92 | .collect(); | ||
| 93 | |||
| 94 | if repo_refs.is_empty() { | ||
| 95 | // No repo references - cannot check git data | ||
| 96 | // This is unusual but let it through (other validation will catch issues) | ||
| 97 | return Ok(true); | ||
| 98 | } | ||
| 99 | |||
| 100 | // Check each repository to see if commit exists | ||
| 101 | for repo_ref in repo_refs { | ||
| 102 | // Parse the repo reference: 30617:<pubkey>:<identifier> | ||
| 103 | let parts: Vec<&str> = repo_ref.split(':').collect(); | ||
| 104 | if parts.len() < 3 { | ||
| 105 | continue; | ||
| 106 | } | ||
| 107 | |||
| 108 | let repo_pubkey = match PublicKey::from_hex(parts[1]) { | ||
| 109 | Ok(pk) => pk, | ||
| 110 | Err(_) => continue, | ||
| 111 | }; | ||
| 112 | let identifier = parts[2]; | ||
| 113 | |||
| 114 | // Look up repository announcement to get the npub for path | ||
| 115 | let filter = Filter::new() | ||
| 116 | .kind(Kind::from(KIND_REPOSITORY_ANNOUNCEMENT)) | ||
| 117 | .author(repo_pubkey) | ||
| 118 | .custom_tag( | ||
| 119 | SingleLetterTag::lowercase(Alphabet::D), | ||
| 120 | identifier.to_string(), | ||
| 121 | ); | ||
| 122 | |||
| 123 | let announcements: Vec<Event> = match self.ctx.database.query(filter).await { | ||
| 124 | Ok(events) => events.into_iter().collect(), | ||
| 125 | Err(e) => { | ||
| 126 | tracing::warn!( | ||
| 127 | "Failed to query for repository announcement for PR {}: {}", | ||
| 128 | event_id, | ||
| 129 | e | ||
| 130 | ); | ||
| 131 | continue; | ||
| 132 | } | ||
| 133 | }; | ||
| 134 | |||
| 135 | if announcements.is_empty() { | ||
| 136 | continue; | ||
| 137 | } | ||
| 138 | |||
| 139 | // Check each matching announcement | ||
| 140 | for announcement_event in announcements { | ||
| 141 | let announcement = match RepositoryAnnouncement::from_event(announcement_event) { | ||
| 142 | Ok(a) => a, | ||
| 143 | Err(_) => continue, | ||
| 144 | }; | ||
| 145 | |||
| 146 | // Build repository path | ||
| 147 | let repo_path = self.ctx.git_data_path.join(announcement.repo_path()); | ||
| 148 | |||
| 149 | // Check if commit exists | ||
| 150 | if git::commit_exists(&repo_path, &commit) { | ||
| 151 | tracing::debug!( | ||
| 152 | "Found commit {} for PR event {} in repository {}", | ||
| 153 | commit, | ||
| 154 | event_id, | ||
| 155 | repo_path.display() | ||
| 156 | ); | ||
| 157 | return Ok(true); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | // No git data found - should add to purgatory | ||
| 163 | tracing::debug!( | ||
| 164 | "No git data found for PR event {} with commit {}", | ||
| 165 | event_id, | ||
| 166 | commit | ||
| 167 | ); | ||
| 168 | Ok(false) | ||
| 169 | } | ||
| 170 | |||
| 22 | /// Validate refs/nostr/<event-id> ref against a PR or PR Update event's `c` tag | 171 | /// Validate refs/nostr/<event-id> ref against a PR or PR Update event's `c` tag |
| 23 | /// | 172 | /// |
| 24 | /// When a PR event (kind 1618) or PR Update event (kind 1619) is received, | 173 | /// When a PR event (kind 1618) or PR Update event (kind 1619) is received, |