From 5f137994850773114d8a4f8ba70f34aaf2eb1992 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 5 Nov 2025 13:32:50 +0000 Subject: [PATCH] tag test events with audit-grasp-test-event --- AGENTS.md | 40 ++++++++++++++++++++ grasp-audit/README.md | 28 ++++++++++---- grasp-audit/src/audit.rs | 40 +++++++++++++++++++- grasp-audit/src/client.rs | 78 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 176 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4e33b2a..f506a12 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -158,6 +158,46 @@ EventBuilder::new(kind, content).tags(tags) See `docs/archive/2025-11-04-nostr-sdk-upgrade.md` for full migration. +### Audit Event Tagging (grasp-audit) + +**All audit events automatically include cleanup tags:** + +The grasp-audit system automatically adds three tags to every event for production cleanup and test isolation. These tags are added transparently via [`AuditEventBuilder::build()`](grasp-audit/src/audit.rs:120-129) with 100% coverage through [`AuditClient::event_builder()`](grasp-audit/src/client.rs:107-138). + +**Automatic Tags (no manual intervention needed):** + +```rust +// These tags are automatically added to EVERY audit event: +["t", "grasp-audit-test-event"] // Identifies all audit test events +["t", "audit-{run_id}"] // Unique ID for this audit run (correlates events) +["t", "audit-cleanup-after-{unix_timestamp}"] // Unix timestamp for cleanup scheduling +``` + +**Tag Format Details:** + +- Uses standard NIP-01 `"t"` (hashtag) tags for maximum compatibility +- Unix timestamps (not ISO 8601) for easier database queries +- All tags added automatically when calling `client.event_builder().build()` +- No manual tag management required + +**Verifying Tags in Tests:** + +```rust +// Test that verifies automatic tag addition: +// See: grasp-audit/src/client.rs:273-302 +#[test] +fn test_audit_tags_automatically_added() { + // Creates event and verifies all three tags are present +} +``` + +**Testing Implications:** + +- All audit events are tagged for easy cleanup +- Use `run_id` tag to correlate events from same audit run +- Tags enable production relay cleanup scripts +- No special handling needed in test code - tags are automatic + ## Documentation **Diátaxis Framework Used:** diff --git a/grasp-audit/README.md b/grasp-audit/README.md index b1dcb15..62f1f03 100644 --- a/grasp-audit/README.md +++ b/grasp-audit/README.md @@ -94,23 +94,35 @@ Basic Nostr relay functionality: ## Audit Event Strategy -All audit events include special tags: +All audit events automatically include special tags for isolation and cleanup: ```json { "tags": [ - ["t", "grasp-audit"], - ["r", "audit-run-id-ci-a1b2c3d4-e5f6-7890-abcd-ef1234567890"], - ["r", "audit-cleanup-2025-11-03T12:00:00Z"] + ["t", "grasp-audit-test-event"], + ["t", "audit-ci-a1b2c3d4-e5f6-7890-abcd-ef1234567890"], + ["t", "audit-cleanup-after-1730822334"] ] } ``` -This allows: +**Tag Format:** -- **Isolation**: Each test run has unique ID -- **Cleanup**: Events marked for cleanup after timestamp -- **No deletion trails**: Direct database cleanup (no NIP-09 deletion events) +- `["t", "grasp-audit-test-event"]` - Identifies all audit-related events +- `["t", "audit-{run_id}"]` - Unique identifier for each audit run + - CI mode: `audit-ci-{uuid}` + - Production mode: `audit-prod-audit-{timestamp}` +- `["t", "audit-cleanup-after-{unix_timestamp}"]` - Cleanup scheduling + - CI mode: Current time + 3600 seconds (1 hour) + - Production mode: Current time + 300 seconds (5 minutes) + +**Benefits:** + +- **Automatic**: Tags added automatically to all events via `AuditEventBuilder` +- **Isolation**: Each test run has unique ID for event filtering +- **Cleanup**: Events marked for cleanup after timestamp (direct database cleanup) +- **No deletion trails**: No NIP-09 deletion events needed +- **Discovery**: Easy to query all audit events via hashtag ## Modes diff --git a/grasp-audit/src/audit.rs b/grasp-audit/src/audit.rs index fad4bf2..105fa00 100644 --- a/grasp-audit/src/audit.rs +++ b/grasp-audit/src/audit.rs @@ -61,7 +61,45 @@ impl AuditConfig { } } - /// Get audit tags for an event + /// Get audit tags that are automatically added to all events + /// + /// These tags are automatically added to all events created via [`AuditEventBuilder`]. + /// They provide isolation, cleanup scheduling, and easy discovery of audit events. + /// + /// # Tag Format + /// + /// All tags use the `"t"` (hashtag) format for maximum relay compatibility: + /// + /// 1. `["t", "grasp-audit-test-event"]` - Identifies all audit-related events + /// 2. `["t", "audit-{run_id}"]` - Unique identifier for this audit run + /// - CI mode: `audit-ci-{uuid}` + /// - Production mode: `audit-prod-audit-{timestamp}` + /// 3. `["t", "audit-cleanup-after-{unix_timestamp}"]` - Cleanup timestamp + /// - CI mode: Current time + 3600 seconds (1 hour) + /// - Production mode: Current time + 300 seconds (5 minutes) + /// + /// # Purpose + /// + /// - **Isolation**: Each test run has a unique ID for event filtering in CI mode + /// - **Cleanup**: Events marked for cleanup after timestamp (enables direct DB cleanup) + /// - **Discovery**: Easy to query all audit events via hashtag + /// - **No deletion trails**: Avoids NIP-09 deletion events by using direct cleanup + /// + /// # Example + /// + /// ```rust + /// use grasp_audit::AuditConfig; + /// + /// let config = AuditConfig::ci(); + /// let tags = config.audit_tags(); + /// + /// // Tags will look like: + /// // [ + /// // ["t", "grasp-audit-test-event"], + /// // ["t", "audit-ci-a1b2c3d4-e5f6-7890-abcd-ef1234567890"], + /// // ["t", "audit-cleanup-after-1730822334"] + /// // ] + /// ``` pub fn audit_tags(&self) -> Vec { use nostr_sdk::prelude::{Alphabet, SingleLetterTag}; diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs index 7706ee3..cbefeb9 100644 --- a/grasp-audit/src/client.rs +++ b/grasp-audit/src/client.rs @@ -104,7 +104,38 @@ impl AuditClient { Ok(event_id) } - /// Create an event builder with audit tags + /// Create an event builder that automatically includes audit tags + /// + /// All events built through this method will automatically have audit tags appended + /// when you call `.build()`. These tags provide isolation, cleanup scheduling, and + /// easy discovery of audit events. + /// + /// # Automatic Tags Added + /// + /// When you call `.build()` on the returned builder, these tags will be automatically added: + /// - `["t", "grasp-audit-test-event"]` - Identifies all audit events + /// - `["t", "audit-{run_id}"]` - Unique ID for this audit run + /// - `["t", "audit-cleanup-after-{timestamp}"]` - Cleanup scheduling + /// + /// # Example + /// + /// ```no_run + /// # use grasp_audit::*; + /// # async fn example() -> anyhow::Result<()> { + /// let config = AuditConfig::ci(); + /// let client = AuditClient::new("ws://localhost:7000", config).await?; + /// + /// // Create event with automatic audit tags + /// let event = client.event_builder(Kind::TextNote, "test content") + /// .tag(Tag::custom(TagKind::custom("custom"), vec!["value"])) + /// .build(client.keys())?; + /// + /// // Event now has both your custom tag AND the 3 audit tags + /// # Ok(()) + /// # } + /// ``` + /// + /// See [`AuditConfig::audit_tags()`] for details on the tag format. pub fn event_builder(&self, kind: Kind, content: impl Into) -> AuditEventBuilder { AuditEventBuilder::new(kind, content, self.config.clone()) } @@ -237,4 +268,49 @@ mod tests { // Builder should be created successfully // (We can't test the internal config field as it's private, which is correct) } + + #[test] + fn test_audit_tags_automatically_added() { + let config = AuditConfig::ci(); + let keys = Keys::generate(); + let client = AuditClient { + client: Client::new(keys.clone()), + config: config.clone(), + keys: keys.clone(), + }; + + // Create an event with a custom tag + let event = client.event_builder(Kind::TextNote, "test content") + .tag(Tag::custom(TagKind::custom("custom"), vec!["value"])) + .build(&keys) + .unwrap(); + + // Should have custom tag (1) + 3 audit tags = at least 4 tags + assert!(event.tags.len() >= 4, "Expected at least 4 tags, got {}", event.tags.len()); + + // Verify audit tags are present by checking tag content + let tag_contents: Vec = event.tags.iter() + .filter_map(|t| t.content().map(|s| s.to_string())) + .collect(); + + // Check for the three required audit tags + assert!( + tag_contents.contains(&"grasp-audit-test-event".to_string()), + "Missing 'grasp-audit-test-event' tag" + ); + assert!( + tag_contents.iter().any(|t| t.starts_with("audit-ci-")), + "Missing 'audit-ci-*' tag" + ); + assert!( + tag_contents.iter().any(|t| t.starts_with("audit-cleanup-after-")), + "Missing 'audit-cleanup-after-*' tag" + ); + + // Verify the custom tag is also present + assert!( + tag_contents.contains(&"value".to_string()), + "Missing custom tag value" + ); + } }