From 8190a3a1b4541e86692d5e1210f955fc8c8351a8 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Tue, 4 Nov 2025 07:45:56 +0000 Subject: 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. --- grasp-audit/src/audit.rs | 44 ++++++++++++++++++++++++++++-------- grasp-audit/src/client.rs | 36 ++++++++++++++++++++++++----- grasp-audit/src/specs/nip01_smoke.rs | 15 +++++++++++- 3 files changed, 78 insertions(+), 17 deletions(-) (limited to 'grasp-audit/src') 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 { /// Get audit tags for an event pub fn audit_tags(&self) -> Vec { + use nostr_sdk::prelude::{Alphabet, SingleLetterTag}; + vec![ + // Use single-letter tags for filtering support + // "g" = grasp-audit marker Tag::custom( - TagKind::Custom(std::borrow::Cow::Borrowed("grasp-audit")), - vec!["true"] + TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::G)), + vec!["grasp-audit"] ), + // "r" = audit run ID Tag::custom( - TagKind::Custom(std::borrow::Cow::Borrowed("audit-run-id")), + TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::R)), vec![self.run_id.clone()] ), + // "c" = cleanup timestamp Tag::custom( - TagKind::Custom(std::borrow::Cow::Borrowed("audit-cleanup")), + TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::C)), vec![self.cleanup_after.to_string()] ), ] @@ -146,24 +152,42 @@ mod tests { #[test] fn test_audit_tags() { + use nostr_sdk::prelude::{Alphabet, SingleLetterTag}; + let config = AuditConfig::ci(); let tags = config.audit_tags(); assert_eq!(tags.len(), 3); - // Check grasp-audit tag + let g_tag = SingleLetterTag::lowercase(Alphabet::G); + let r_tag = SingleLetterTag::lowercase(Alphabet::R); + let c_tag = SingleLetterTag::lowercase(Alphabet::C); + + // Check "g" tag (grasp-audit marker) assert!(tags.iter().any(|t| { - matches!(t.kind(), TagKind::Custom(k) if k == "grasp-audit") + if let TagKind::SingleLetter(letter) = t.kind() { + letter == g_tag + } else { + false + } })); - // Check audit-run-id tag + // Check "r" tag (audit run ID) assert!(tags.iter().any(|t| { - matches!(t.kind(), TagKind::Custom(k) if k == "audit-run-id") + if let TagKind::SingleLetter(letter) = t.kind() { + letter == r_tag + } else { + false + } })); - // Check audit-cleanup tag + // Check "c" tag (cleanup timestamp) assert!(tags.iter().any(|t| { - matches!(t.kind(), TagKind::Custom(k) if k == "audit-cleanup") + if let TagKind::SingleLetter(letter) = t.kind() { + letter == c_tag + } else { + false + } })); } 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 { let keys = Keys::generate(); let client = Client::new(keys.clone()); + // Add relay and connect client.add_relay(relay_url).await?; client.connect().await; - // Wait a bit for connection to establish - tokio::time::sleep(Duration::from_millis(500)).await; + // Wait for connection to establish (with retries) + let mut attempts = 0; + while attempts < 20 { + tokio::time::sleep(Duration::from_millis(100)).await; + + let relays = client.relays().await; + let connected = relays.values().any(|r| r.is_connected()); + + if connected { + break; + } + + attempts += 1; + } + + // Give it a bit more time to stabilize + tokio::time::sleep(Duration::from_millis(200)).await; Ok(Self { client, @@ -57,6 +73,11 @@ impl AuditClient { let output = self.client.send_event(&event).await?; let event_id = *output.id(); + // Check if any relay rejected the event + if output.success.is_empty() && !output.failed.is_empty() { + return Err(anyhow!("All relays rejected the event")); + } + // Wait a bit for event to propagate tokio::time::sleep(Duration::from_millis(100)).await; @@ -70,16 +91,19 @@ impl AuditClient { /// Query events, optionally filtered to this audit run pub async fn query(&self, mut filter: Filter) -> Result> { + use nostr_sdk::prelude::{Alphabet, SingleLetterTag}; + if self.config.mode == AuditMode::CI { // In CI mode, only see our own audit events + // Filter by "g" tag (grasp-audit marker) and "r" tag (run ID) filter = filter .custom_tag( - SingleLetterTag::lowercase(Alphabet::G), - "true" // grasp-audit tag + SingleLetterTag::lowercase(Alphabet::G), + "grasp-audit" ) .custom_tag( - SingleLetterTag::lowercase(Alphabet::R), - &self.config.run_id // audit-run-id tag + SingleLetterTag::lowercase(Alphabet::R), + &self.config.run_id ); } // 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 { )); } + // Wait a bit for event to be indexed + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + // Try to query it back let filter = Filter::new() .kind(Kind::TextNote) @@ -87,7 +90,17 @@ impl Nip01SmokeTests { .map_err(|e| format!("Failed to query event: {}", e))?; if events.is_empty() { - return Err("Event not found after sending".to_string()); + // Debug: try querying without audit client filtering + eprintln!("Event not found with audit client query, trying direct client query..."); + let direct_filter = Filter::new().kind(Kind::TextNote).id(event_id); + let direct_events = client.client().fetch_events(direct_filter, std::time::Duration::from_secs(5)).await + .map_err(|e| format!("Direct query failed: {}", e))?; + let direct_vec: Vec = direct_events.into_iter().collect(); + eprintln!("Direct query found {} events", direct_vec.len()); + if !direct_vec.is_empty() { + eprintln!("Event tags: {:?}", direct_vec[0].tags); + } + return Err(format!("Event not found after sending (direct query found {})", direct_vec.len())); } if events[0].id != event_id { -- cgit v1.2.3