From b3031800cd95601c2d9cd2d24034364d1496b073 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Tue, 4 Nov 2025 08:50:29 +0000 Subject: Migrate to standard NIP-01 't' tags for audit events - Changed from custom single-letter tags (g, r, c) to standard 't' tags - Tag values now use descriptive prefixes: - 'grasp-audit-test-event' (marker tag) - 'audit-{run-id}' (run identification) - 'audit-cleanup-after-{timestamp}' (cleanup time) - Updated audit_tags() in src/audit.rs - Updated query filtering in src/client.rs - Updated all tests to verify 't' tag usage - All tests passing: 12/12 unit tests, 1/1 integration test - CLI verified working with new tag scheme This follows standard Nostr conventions and avoids potential conflicts with other uses of single-letter tags. The 't' tag is specifically designed for categorization/topics per NIP-01. --- grasp-audit/TAG_MIGRATION.md | 151 +++++++++++++++++++++++++++++++++++++++++++ grasp-audit/src/audit.rs | 54 +++++++--------- grasp-audit/src/client.rs | 13 ++-- 3 files changed, 179 insertions(+), 39 deletions(-) create mode 100644 grasp-audit/TAG_MIGRATION.md (limited to 'grasp-audit') diff --git a/grasp-audit/TAG_MIGRATION.md b/grasp-audit/TAG_MIGRATION.md new file mode 100644 index 0000000..aaba729 --- /dev/null +++ b/grasp-audit/TAG_MIGRATION.md @@ -0,0 +1,151 @@ +# Tag Migration to Standard NIP-01 "t" Tags + +**Date:** November 4, 2025 +**Status:** ✅ Complete + +## Overview + +Migrated audit system tags from custom single-letter tags (`g`, `r`, `c`) to standard NIP-01 "t" tags (hashtags) to avoid conflicts and follow Nostr conventions. + +## Motivation + +The previous tag scheme used: +- `g` tag for `grasp-audit` marker +- `r` tag for `audit-run-id` +- `c` tag for `audit-cleanup` timestamp + +However, this could conflict with other uses of these single-letter tags. The "t" tag is the standard NIP-01 tag type for categorization/topics, making it the appropriate choice for audit event tagging. + +## Changes Made + +### Tag Structure + +**Before:** +```rust +vec![ + Tag::custom(TagKind::SingleLetter(g_tag), vec!["grasp-audit"]), + Tag::custom(TagKind::SingleLetter(r_tag), vec![run_id]), + Tag::custom(TagKind::SingleLetter(c_tag), vec![cleanup_timestamp]), +] +``` + +**After:** +```rust +vec![ + Tag::custom(TagKind::SingleLetter(t_tag), vec!["grasp-audit-test-event"]), + Tag::custom(TagKind::SingleLetter(t_tag), vec![format!("audit-{}", run_id)]), + Tag::custom(TagKind::SingleLetter(t_tag), vec![format!("audit-cleanup-after-{}", timestamp)]), +] +``` + +### Tag Values + +| Purpose | Old Tag | Old Value | New Tag | New Value | +|---------|---------|-----------|---------|-----------| +| Marker | `g` | `grasp-audit` | `t` | `grasp-audit-test-event` | +| Run ID | `r` | `ci-{uuid}` | `t` | `audit-ci-{uuid}` | +| Cleanup | `c` | `{timestamp}` | `t` | `audit-cleanup-after-{timestamp}` | + +### Example Event Tags + +```json +[ + ["t", "grasp-audit-test-event"], + ["t", "audit-ci-a1b2c3d4-e5f6-7890-abcd-ef1234567890"], + ["t", "audit-cleanup-after-1730707200"] +] +``` + +## Files Modified + +### `src/audit.rs` +- Updated `audit_tags()` to use "t" tags +- Updated tests to check for "t" tag kind +- All values now prefixed for clarity + +### `src/client.rs` +- Updated `query()` to filter by "t" tags +- Changed from `.custom_tag(g_tag, ...)` to `.custom_tag(t_tag, ...)` + +## Benefits + +1. **Standards Compliance**: Uses standard NIP-01 hashtag mechanism +2. **No Conflicts**: "t" tag is designed for categorization +3. **Better Namespacing**: Values prefixed with `audit-` to avoid collisions +4. **Queryable**: Standard tag filtering works as expected +5. **Self-Documenting**: Tag values clearly indicate their purpose + +## Testing + +All tests pass with the new tag scheme: + +```bash +# Unit tests +✓ 12/12 tests passing + +# Integration tests +✓ 1/1 test passing (NIP-01 smoke tests) + +# CLI verification +✓ All 6 smoke tests pass +``` + +## Backwards Compatibility + +⚠️ **Breaking Change**: Events created with old tags will not be found by new queries. + +This is acceptable because: +- System is in alpha/development +- Old events are test data only +- Cleanup happens automatically via timestamps +- No production deployments exist yet + +## Migration Path + +For future tag changes: +1. Consider versioning in tag values (e.g., `grasp-audit-v2-test-event`) +2. Support querying both old and new tags during transition +3. Document breaking changes clearly +4. Provide migration tools if needed + +## References + +- **NIP-01**: https://github.com/nostr-protocol/nips/blob/master/01.md +- **Tag Standardization**: "t" tags for topics/categories +- **Previous Implementation**: Commit `8190a3a` (custom g/r/c tags) +- **Current Implementation**: Uses standard "t" tags + +## Verification + +To verify the new tag structure: + +```bash +# Run tests +nix develop -c cargo test --lib +nix develop -c cargo test -- --ignored + +# Run CLI +nix develop -c cargo run -- audit \ + --relay ws://localhost:7000 \ + --mode ci \ + --spec nip01-smoke + +# Check event structure (example) +# Events will have tags like: +# ["t", "grasp-audit-test-event"] +# ["t", "audit-ci-{uuid}"] +# ["t", "audit-cleanup-after-{timestamp}"] +``` + +## Next Steps + +- [ ] Update documentation to reflect new tag scheme +- [ ] Consider adding tag validation helpers +- [ ] Document tag format in API/spec documentation +- [ ] Add examples showing tag usage + +--- + +**Status:** ✅ Migration complete and verified +**All tests passing:** 13/13 (12 unit + 1 integration) +**CLI verified:** ✅ Working correctly diff --git a/grasp-audit/src/audit.rs b/grasp-audit/src/audit.rs index e902ace..fad4bf2 100644 --- a/grasp-audit/src/audit.rs +++ b/grasp-audit/src/audit.rs @@ -65,22 +65,21 @@ impl AuditConfig { pub fn audit_tags(&self) -> Vec { use nostr_sdk::prelude::{Alphabet, SingleLetterTag}; + // Use "t" tags for categorization (standard NIP-01 hashtag type) + let t_tag = SingleLetterTag::lowercase(Alphabet::T); + vec![ - // Use single-letter tags for filtering support - // "g" = grasp-audit marker Tag::custom( - TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::G)), - vec!["grasp-audit"] + TagKind::SingleLetter(t_tag), + vec!["grasp-audit-test-event"] ), - // "r" = audit run ID Tag::custom( - TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::R)), - vec![self.run_id.clone()] + TagKind::SingleLetter(t_tag), + vec![format!("audit-{}", self.run_id)] ), - // "c" = cleanup timestamp Tag::custom( - TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::C)), - vec![self.cleanup_after.to_string()] + TagKind::SingleLetter(t_tag), + vec![format!("audit-cleanup-after-{}", self.cleanup_after.as_u64())] ), ] } @@ -159,35 +158,30 @@ mod tests { assert_eq!(tags.len(), 3); - let g_tag = SingleLetterTag::lowercase(Alphabet::G); - let r_tag = SingleLetterTag::lowercase(Alphabet::R); - let c_tag = SingleLetterTag::lowercase(Alphabet::C); + let t_tag = SingleLetterTag::lowercase(Alphabet::T); - // Check "g" tag (grasp-audit marker) - assert!(tags.iter().any(|t| { - if let TagKind::SingleLetter(letter) = t.kind() { - letter == g_tag + // All tags should be "t" tags (hashtags) + for tag in &tags { + if let TagKind::SingleLetter(letter) = tag.kind() { + assert_eq!(letter, t_tag); } else { - false + panic!("Expected SingleLetter tag"); } + } + + // Check for "t" tag with "grasp-audit-test-event" + assert!(tags.iter().any(|t| { + t.content() == Some("grasp-audit-test-event") })); - // Check "r" tag (audit run ID) + // Check for "t" tag with "audit-{run_id}" assert!(tags.iter().any(|t| { - if let TagKind::SingleLetter(letter) = t.kind() { - letter == r_tag - } else { - false - } + t.content().map(|c| c.starts_with("audit-ci-")).unwrap_or(false) })); - // Check "c" tag (cleanup timestamp) + // Check for "t" tag with "audit-cleanup-after-{timestamp}" assert!(tags.iter().any(|t| { - if let TagKind::SingleLetter(letter) = t.kind() { - letter == c_tag - } else { - false - } + t.content().map(|c| c.starts_with("audit-cleanup-after-")).unwrap_or(false) })); } diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs index d78b33c..4831d3f 100644 --- a/grasp-audit/src/client.rs +++ b/grasp-audit/src/client.rs @@ -95,16 +95,11 @@ impl AuditClient { 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 by "t" tags (hashtags) + let t_tag = SingleLetterTag::lowercase(Alphabet::T); filter = filter - .custom_tag( - SingleLetterTag::lowercase(Alphabet::G), - "grasp-audit" - ) - .custom_tag( - SingleLetterTag::lowercase(Alphabet::R), - &self.config.run_id - ); + .custom_tag(t_tag, "grasp-audit-test-event") + .custom_tag(t_tag, format!("audit-{}", self.config.run_id)); } // In Production mode, see all events (no filter modification) -- cgit v1.2.3