upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-05 13:32:50 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-05 13:32:50 +0000
commit5f137994850773114d8a4f8ba70f34aaf2eb1992 (patch)
treeaec84ac0536412896826e47de563213c26276988
parent64a86de9fc5ded51a1b5405223fc5dce16839fef (diff)
tag test events with audit-grasp-test-event
-rw-r--r--AGENTS.md40
-rw-r--r--grasp-audit/README.md28
-rw-r--r--grasp-audit/src/audit.rs40
-rw-r--r--grasp-audit/src/client.rs78
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)
158 158
159See `docs/archive/2025-11-04-nostr-sdk-upgrade.md` for full migration. 159See `docs/archive/2025-11-04-nostr-sdk-upgrade.md` for full migration.
160 160
161### Audit Event Tagging (grasp-audit)
162
163**All audit events automatically include cleanup tags:**
164
165The 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).
166
167**Automatic Tags (no manual intervention needed):**
168
169```rust
170// These tags are automatically added to EVERY audit event:
171["t", "grasp-audit-test-event"] // Identifies all audit test events
172["t", "audit-{run_id}"] // Unique ID for this audit run (correlates events)
173["t", "audit-cleanup-after-{unix_timestamp}"] // Unix timestamp for cleanup scheduling
174```
175
176**Tag Format Details:**
177
178- Uses standard NIP-01 `"t"` (hashtag) tags for maximum compatibility
179- Unix timestamps (not ISO 8601) for easier database queries
180- All tags added automatically when calling `client.event_builder().build()`
181- No manual tag management required
182
183**Verifying Tags in Tests:**
184
185```rust
186// Test that verifies automatic tag addition:
187// See: grasp-audit/src/client.rs:273-302
188#[test]
189fn test_audit_tags_automatically_added() {
190 // Creates event and verifies all three tags are present
191}
192```
193
194**Testing Implications:**
195
196- All audit events are tagged for easy cleanup
197- Use `run_id` tag to correlate events from same audit run
198- Tags enable production relay cleanup scripts
199- No special handling needed in test code - tags are automatic
200
161## Documentation 201## Documentation
162 202
163**Diátaxis Framework Used:** 203**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:
94 94
95## Audit Event Strategy 95## Audit Event Strategy
96 96
97All audit events include special tags: 97All audit events automatically include special tags for isolation and cleanup:
98 98
99```json 99```json
100{ 100{
101 "tags": [ 101 "tags": [
102 ["t", "grasp-audit"], 102 ["t", "grasp-audit-test-event"],
103 ["r", "audit-run-id-ci-a1b2c3d4-e5f6-7890-abcd-ef1234567890"], 103 ["t", "audit-ci-a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
104 ["r", "audit-cleanup-2025-11-03T12:00:00Z"] 104 ["t", "audit-cleanup-after-1730822334"]
105 ] 105 ]
106} 106}
107``` 107```
108 108
109This allows: 109**Tag Format:**
110 110
111- **Isolation**: Each test run has unique ID 111- `["t", "grasp-audit-test-event"]` - Identifies all audit-related events
112- **Cleanup**: Events marked for cleanup after timestamp 112- `["t", "audit-{run_id}"]` - Unique identifier for each audit run
113- **No deletion trails**: Direct database cleanup (no NIP-09 deletion events) 113 - CI mode: `audit-ci-{uuid}`
114 - Production mode: `audit-prod-audit-{timestamp}`
115- `["t", "audit-cleanup-after-{unix_timestamp}"]` - Cleanup scheduling
116 - CI mode: Current time + 3600 seconds (1 hour)
117 - Production mode: Current time + 300 seconds (5 minutes)
118
119**Benefits:**
120
121- **Automatic**: Tags added automatically to all events via `AuditEventBuilder`
122- **Isolation**: Each test run has unique ID for event filtering
123- **Cleanup**: Events marked for cleanup after timestamp (direct database cleanup)
124- **No deletion trails**: No NIP-09 deletion events needed
125- **Discovery**: Easy to query all audit events via hashtag
114 126
115## Modes 127## Modes
116 128
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 {
61 } 61 }
62 } 62 }
63 63
64 /// Get audit tags for an event 64 /// Get audit tags that are automatically added to all events
65 ///
66 /// These tags are automatically added to all events created via [`AuditEventBuilder`].
67 /// They provide isolation, cleanup scheduling, and easy discovery of audit events.
68 ///
69 /// # Tag Format
70 ///
71 /// All tags use the `"t"` (hashtag) format for maximum relay compatibility:
72 ///
73 /// 1. `["t", "grasp-audit-test-event"]` - Identifies all audit-related events
74 /// 2. `["t", "audit-{run_id}"]` - Unique identifier for this audit run
75 /// - CI mode: `audit-ci-{uuid}`
76 /// - Production mode: `audit-prod-audit-{timestamp}`
77 /// 3. `["t", "audit-cleanup-after-{unix_timestamp}"]` - Cleanup timestamp
78 /// - CI mode: Current time + 3600 seconds (1 hour)
79 /// - Production mode: Current time + 300 seconds (5 minutes)
80 ///
81 /// # Purpose
82 ///
83 /// - **Isolation**: Each test run has a unique ID for event filtering in CI mode
84 /// - **Cleanup**: Events marked for cleanup after timestamp (enables direct DB cleanup)
85 /// - **Discovery**: Easy to query all audit events via hashtag
86 /// - **No deletion trails**: Avoids NIP-09 deletion events by using direct cleanup
87 ///
88 /// # Example
89 ///
90 /// ```rust
91 /// use grasp_audit::AuditConfig;
92 ///
93 /// let config = AuditConfig::ci();
94 /// let tags = config.audit_tags();
95 ///
96 /// // Tags will look like:
97 /// // [
98 /// // ["t", "grasp-audit-test-event"],
99 /// // ["t", "audit-ci-a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
100 /// // ["t", "audit-cleanup-after-1730822334"]
101 /// // ]
102 /// ```
65 pub fn audit_tags(&self) -> Vec<Tag> { 103 pub fn audit_tags(&self) -> Vec<Tag> {
66 use nostr_sdk::prelude::{Alphabet, SingleLetterTag}; 104 use nostr_sdk::prelude::{Alphabet, SingleLetterTag};
67 105
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 {
104 Ok(event_id) 104 Ok(event_id)
105 } 105 }
106 106
107 /// Create an event builder with audit tags 107 /// Create an event builder that automatically includes audit tags
108 ///
109 /// All events built through this method will automatically have audit tags appended
110 /// when you call `.build()`. These tags provide isolation, cleanup scheduling, and
111 /// easy discovery of audit events.
112 ///
113 /// # Automatic Tags Added
114 ///
115 /// When you call `.build()` on the returned builder, these tags will be automatically added:
116 /// - `["t", "grasp-audit-test-event"]` - Identifies all audit events
117 /// - `["t", "audit-{run_id}"]` - Unique ID for this audit run
118 /// - `["t", "audit-cleanup-after-{timestamp}"]` - Cleanup scheduling
119 ///
120 /// # Example
121 ///
122 /// ```no_run
123 /// # use grasp_audit::*;
124 /// # async fn example() -> anyhow::Result<()> {
125 /// let config = AuditConfig::ci();
126 /// let client = AuditClient::new("ws://localhost:7000", config).await?;
127 ///
128 /// // Create event with automatic audit tags
129 /// let event = client.event_builder(Kind::TextNote, "test content")
130 /// .tag(Tag::custom(TagKind::custom("custom"), vec!["value"]))
131 /// .build(client.keys())?;
132 ///
133 /// // Event now has both your custom tag AND the 3 audit tags
134 /// # Ok(())
135 /// # }
136 /// ```
137 ///
138 /// See [`AuditConfig::audit_tags()`] for details on the tag format.
108 pub fn event_builder(&self, kind: Kind, content: impl Into<String>) -> AuditEventBuilder { 139 pub fn event_builder(&self, kind: Kind, content: impl Into<String>) -> AuditEventBuilder {
109 AuditEventBuilder::new(kind, content, self.config.clone()) 140 AuditEventBuilder::new(kind, content, self.config.clone())
110 } 141 }
@@ -237,4 +268,49 @@ mod tests {
237 // Builder should be created successfully 268 // Builder should be created successfully
238 // (We can't test the internal config field as it's private, which is correct) 269 // (We can't test the internal config field as it's private, which is correct)
239 } 270 }
271
272 #[test]
273 fn test_audit_tags_automatically_added() {
274 let config = AuditConfig::ci();
275 let keys = Keys::generate();
276 let client = AuditClient {
277 client: Client::new(keys.clone()),
278 config: config.clone(),
279 keys: keys.clone(),
280 };
281
282 // Create an event with a custom tag
283 let event = client.event_builder(Kind::TextNote, "test content")
284 .tag(Tag::custom(TagKind::custom("custom"), vec!["value"]))
285 .build(&keys)
286 .unwrap();
287
288 // Should have custom tag (1) + 3 audit tags = at least 4 tags
289 assert!(event.tags.len() >= 4, "Expected at least 4 tags, got {}", event.tags.len());
290
291 // Verify audit tags are present by checking tag content
292 let tag_contents: Vec<String> = event.tags.iter()
293 .filter_map(|t| t.content().map(|s| s.to_string()))
294 .collect();
295
296 // Check for the three required audit tags
297 assert!(
298 tag_contents.contains(&"grasp-audit-test-event".to_string()),
299 "Missing 'grasp-audit-test-event' tag"
300 );
301 assert!(
302 tag_contents.iter().any(|t| t.starts_with("audit-ci-")),
303 "Missing 'audit-ci-*' tag"
304 );
305 assert!(
306 tag_contents.iter().any(|t| t.starts_with("audit-cleanup-after-")),
307 "Missing 'audit-cleanup-after-*' tag"
308 );
309
310 // Verify the custom tag is also present
311 assert!(
312 tag_contents.contains(&"value".to_string()),
313 "Missing custom tag value"
314 );
315 }
240} 316}