upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/nostr/policy/pr_event.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-24 08:02:12 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-24 11:54:18 +0000
commit70d0197e85ae4ef85202781f6d2dc9e76bd508b3 (patch)
tree45efb6565e81ba755acc5955e68d5b7119d1e122 /src/nostr/policy/pr_event.rs
parentf8c3e3920ed2a1bdaab30be912276993449a5476 (diff)
feat(purgatory): add broken purgatory implementation
Diffstat (limited to 'src/nostr/policy/pr_event.rs')
-rw-r--r--src/nostr/policy/pr_event.rs149
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,