upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-04 07:45:56 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-04 07:45:56 +0000
commit8190a3a1b4541e86692d5e1210f955fc8c8351a8 (patch)
treec6353e2d4756b96f08bf64de7bc66a903cbf392f /grasp-audit
parentc92faa11d669832e9339d8f7707220ff44553008 (diff)
Fix audit system tag filtering and event validation
- Changed from multi-letter custom tags to single-letter tags (g, r, c) for compatibility with Nostr Filter API - Added validation check in send_event() to detect relay rejections by checking output.success and output.failed - Improved connection stability with retry loop - Added debug output for troubleshooting query issues - All tests now pass: 12/12 unit tests, 6/6 integration tests - CLI verified working with Docker relay Fixes issues discovered during Path 1 integration testing.
Diffstat (limited to 'grasp-audit')
-rw-r--r--grasp-audit/src/audit.rs44
-rw-r--r--grasp-audit/src/client.rs36
-rw-r--r--grasp-audit/src/specs/nip01_smoke.rs15
3 files changed, 78 insertions, 17 deletions
diff --git a/grasp-audit/src/audit.rs b/grasp-audit/src/audit.rs
index 9efb61a..e902ace 100644
--- a/grasp-audit/src/audit.rs
+++ b/grasp-audit/src/audit.rs
@@ -63,17 +63,23 @@ impl AuditConfig {
63 63
64 /// Get audit tags for an event 64 /// Get audit tags for an event
65 pub fn audit_tags(&self) -> Vec<Tag> { 65 pub fn audit_tags(&self) -> Vec<Tag> {
66 use nostr_sdk::prelude::{Alphabet, SingleLetterTag};
67
66 vec![ 68 vec![
69 // Use single-letter tags for filtering support
70 // "g" = grasp-audit marker
67 Tag::custom( 71 Tag::custom(
68 TagKind::Custom(std::borrow::Cow::Borrowed("grasp-audit")), 72 TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::G)),
69 vec!["true"] 73 vec!["grasp-audit"]
70 ), 74 ),
75 // "r" = audit run ID
71 Tag::custom( 76 Tag::custom(
72 TagKind::Custom(std::borrow::Cow::Borrowed("audit-run-id")), 77 TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::R)),
73 vec![self.run_id.clone()] 78 vec![self.run_id.clone()]
74 ), 79 ),
80 // "c" = cleanup timestamp
75 Tag::custom( 81 Tag::custom(
76 TagKind::Custom(std::borrow::Cow::Borrowed("audit-cleanup")), 82 TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::C)),
77 vec![self.cleanup_after.to_string()] 83 vec![self.cleanup_after.to_string()]
78 ), 84 ),
79 ] 85 ]
@@ -146,24 +152,42 @@ mod tests {
146 152
147 #[test] 153 #[test]
148 fn test_audit_tags() { 154 fn test_audit_tags() {
155 use nostr_sdk::prelude::{Alphabet, SingleLetterTag};
156
149 let config = AuditConfig::ci(); 157 let config = AuditConfig::ci();
150 let tags = config.audit_tags(); 158 let tags = config.audit_tags();
151 159
152 assert_eq!(tags.len(), 3); 160 assert_eq!(tags.len(), 3);
153 161
154 // Check grasp-audit tag 162 let g_tag = SingleLetterTag::lowercase(Alphabet::G);
163 let r_tag = SingleLetterTag::lowercase(Alphabet::R);
164 let c_tag = SingleLetterTag::lowercase(Alphabet::C);
165
166 // Check "g" tag (grasp-audit marker)
155 assert!(tags.iter().any(|t| { 167 assert!(tags.iter().any(|t| {
156 matches!(t.kind(), TagKind::Custom(k) if k == "grasp-audit") 168 if let TagKind::SingleLetter(letter) = t.kind() {
169 letter == g_tag
170 } else {
171 false
172 }
157 })); 173 }));
158 174
159 // Check audit-run-id tag 175 // Check "r" tag (audit run ID)
160 assert!(tags.iter().any(|t| { 176 assert!(tags.iter().any(|t| {
161 matches!(t.kind(), TagKind::Custom(k) if k == "audit-run-id") 177 if let TagKind::SingleLetter(letter) = t.kind() {
178 letter == r_tag
179 } else {
180 false
181 }
162 })); 182 }));
163 183
164 // Check audit-cleanup tag 184 // Check "c" tag (cleanup timestamp)
165 assert!(tags.iter().any(|t| { 185 assert!(tags.iter().any(|t| {
166 matches!(t.kind(), TagKind::Custom(k) if k == "audit-cleanup") 186 if let TagKind::SingleLetter(letter) = t.kind() {
187 letter == c_tag
188 } else {
189 false
190 }
167 })); 191 }));
168 } 192 }
169 193
diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs
index 7c6cf00..d78b33c 100644
--- a/grasp-audit/src/client.rs
+++ b/grasp-audit/src/client.rs
@@ -18,11 +18,27 @@ impl AuditClient {
18 let keys = Keys::generate(); 18 let keys = Keys::generate();
19 let client = Client::new(keys.clone()); 19 let client = Client::new(keys.clone());
20 20
21 // Add relay and connect
21 client.add_relay(relay_url).await?; 22 client.add_relay(relay_url).await?;
22 client.connect().await; 23 client.connect().await;
23 24
24 // Wait a bit for connection to establish 25 // Wait for connection to establish (with retries)
25 tokio::time::sleep(Duration::from_millis(500)).await; 26 let mut attempts = 0;
27 while attempts < 20 {
28 tokio::time::sleep(Duration::from_millis(100)).await;
29
30 let relays = client.relays().await;
31 let connected = relays.values().any(|r| r.is_connected());
32
33 if connected {
34 break;
35 }
36
37 attempts += 1;
38 }
39
40 // Give it a bit more time to stabilize
41 tokio::time::sleep(Duration::from_millis(200)).await;
26 42
27 Ok(Self { 43 Ok(Self {
28 client, 44 client,
@@ -57,6 +73,11 @@ impl AuditClient {
57 let output = self.client.send_event(&event).await?; 73 let output = self.client.send_event(&event).await?;
58 let event_id = *output.id(); 74 let event_id = *output.id();
59 75
76 // Check if any relay rejected the event
77 if output.success.is_empty() && !output.failed.is_empty() {
78 return Err(anyhow!("All relays rejected the event"));
79 }
80
60 // Wait a bit for event to propagate 81 // Wait a bit for event to propagate
61 tokio::time::sleep(Duration::from_millis(100)).await; 82 tokio::time::sleep(Duration::from_millis(100)).await;
62 83
@@ -70,16 +91,19 @@ impl AuditClient {
70 91
71 /// Query events, optionally filtered to this audit run 92 /// Query events, optionally filtered to this audit run
72 pub async fn query(&self, mut filter: Filter) -> Result<Vec<Event>> { 93 pub async fn query(&self, mut filter: Filter) -> Result<Vec<Event>> {
94 use nostr_sdk::prelude::{Alphabet, SingleLetterTag};
95
73 if self.config.mode == AuditMode::CI { 96 if self.config.mode == AuditMode::CI {
74 // In CI mode, only see our own audit events 97 // In CI mode, only see our own audit events
98 // Filter by "g" tag (grasp-audit marker) and "r" tag (run ID)
75 filter = filter 99 filter = filter
76 .custom_tag( 100 .custom_tag(
77 SingleLetterTag::lowercase(Alphabet::G), 101 SingleLetterTag::lowercase(Alphabet::G),
78 "true" // grasp-audit tag 102 "grasp-audit"
79 ) 103 )
80 .custom_tag( 104 .custom_tag(
81 SingleLetterTag::lowercase(Alphabet::R), 105 SingleLetterTag::lowercase(Alphabet::R),
82 &self.config.run_id // audit-run-id tag 106 &self.config.run_id
83 ); 107 );
84 } 108 }
85 // In Production mode, see all events (no filter modification) 109 // In Production mode, see all events (no filter modification)
diff --git a/grasp-audit/src/specs/nip01_smoke.rs b/grasp-audit/src/specs/nip01_smoke.rs
index cd4ae2b..569997b 100644
--- a/grasp-audit/src/specs/nip01_smoke.rs
+++ b/grasp-audit/src/specs/nip01_smoke.rs
@@ -76,6 +76,9 @@ impl Nip01SmokeTests {
76 )); 76 ));
77 } 77 }
78 78
79 // Wait a bit for event to be indexed
80 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
81
79 // Try to query it back 82 // Try to query it back
80 let filter = Filter::new() 83 let filter = Filter::new()
81 .kind(Kind::TextNote) 84 .kind(Kind::TextNote)
@@ -87,7 +90,17 @@ impl Nip01SmokeTests {
87 .map_err(|e| format!("Failed to query event: {}", e))?; 90 .map_err(|e| format!("Failed to query event: {}", e))?;
88 91
89 if events.is_empty() { 92 if events.is_empty() {
90 return Err("Event not found after sending".to_string()); 93 // Debug: try querying without audit client filtering
94 eprintln!("Event not found with audit client query, trying direct client query...");
95 let direct_filter = Filter::new().kind(Kind::TextNote).id(event_id);
96 let direct_events = client.client().fetch_events(direct_filter, std::time::Duration::from_secs(5)).await
97 .map_err(|e| format!("Direct query failed: {}", e))?;
98 let direct_vec: Vec<Event> = direct_events.into_iter().collect();
99 eprintln!("Direct query found {} events", direct_vec.len());
100 if !direct_vec.is_empty() {
101 eprintln!("Event tags: {:?}", direct_vec[0].tags);
102 }
103 return Err(format!("Event not found after sending (direct query found {})", direct_vec.len()));
91 } 104 }
92 105
93 if events[0].id != event_id { 106 if events[0].id != event_id {