Files
ngit-grasp/docs/learnings/grasp-audit.md
T
DanConwayDev 51751d59f3 Change default port from 8080 to 7334 (NGIT on phone keypad)
- Update default bind address in src/config.rs to 127.0.0.1:7334
- Update all four critical config sources per AGENTS.md:
  - src/config.rs (code default and tests)
  - .env.example (development template)
  - docs/reference/configuration.md (user documentation)
  - nix/module.nix (NixOS deployment)
- Update all documentation examples and references:
  - README.md (with note about phone keypad mnemonic)
  - docs/how-to/*.md (deploy, prometheus-setup, test-compliance)
  - docs/explanation/*.md (architecture, comparison)
  - docs/learnings/grasp-audit.md

Port 7334 spells NGIT on a phone keypad, making it memorable and
project-specific.

All tests pass (336 lib tests + 51 integration tests).
2026-01-12 22:51:43 +00:00

11 KiB

GRASP Audit Tool - Patterns and Learnings

Purpose: Document grasp-audit architecture, patterns, and lessons learned
Last Updated: December 4, 2025


Overview

grasp-audit is a fully implemented compliance testing tool for GRASP (Git Relays Authorized via Signed-Nostr Proofs) protocol implementations. It tests both Nostr relay compliance (NIP-01) and GRASP-specific functionality.


Architecture Decisions

Separate Crate Strategy

Decision: Build grasp-audit as a separate crate from ngit-grasp

Why:

  1. Parallel Development: Can build tests before implementation
  2. Isolated Testing: Tests run in isolation (CI/CD safe)
  3. Production Auditing: Can audit live production services
  4. Reusability: Other GRASP implementations can use it

Location: grasp-audit/ subdirectory with own Cargo.toml and flake.nix


Audit Event Tagging Strategy

Problem: Test events pollute the relay and need cleanup without deletion events.

Solution: Use special tags to mark audit events (implemented in grasp-audit/src/audit.rs):

// Every audit event includes these tags (added automatically)
[
    ["t", "grasp-audit-test-event"],           // Marker
    ["t", "audit-{run-id}"],                   // Run isolation
    ["t", "audit-cleanup-after-{timestamp}"]   // Cleanup time
]

Benefits:

  • ✅ Queryable: Can find all audit events via tag filter
  • ✅ Isolated: Each test run has unique run ID
  • ✅ Self-cleaning: Cleanup timestamp indicates when to delete
  • ✅ No deletion events: Direct database cleanup, no KIND 5 events
  • ✅ Production safe: Won't interfere with real events

Reference: See docs/archive/2025-11-04-tag-migration.md


Standard "t" Tags vs Custom Tags

Evolution:

  1. Original: Custom single-letter tags (g, r, c)
  2. Current: Standard NIP-01 "t" tags with prefixed values

Why we changed:

  • ❌ Custom tags could conflict with other systems
  • ✅ "t" tag is standard for categorization/topics
  • ✅ Multiple "t" tags are expected and supported
  • ✅ Self-documenting values (audit-{run-id} vs just {run-id})
  • ✅ Better namespacing with prefixes

Migration: Completed November 4, 2025


Code Patterns

Audit Configuration

From grasp-audit/src/audit.rs:

use grasp_audit::audit::AuditConfig;

// CI mode - isolated test runs
let config = AuditConfig::isolated();
// Generates UUID run ID: "ci-{uuid}"
// Cleanup after 1 hour

// Production mode - persistent run ID
let config = AuditConfig::shared();
// Uses provided run ID
// Cleanup after 24 hours

When to use:

  • CI mode: Automated testing, parallel runs, temporary
  • Production mode: Manual audits, monitoring, persistent

Creating Audit Events

From grasp-audit/src/client.rs:

use grasp_audit::client::AuditClient;
use grasp_audit::audit::AuditConfig;

let config = AuditConfig::isolated();
let client = AuditClient::new("ws://localhost:7334", config).await?;

// Create and send an event - cleanup tags are added automatically
let event = client.event_builder()
    .kind(Kind::TextNote)
    .content("test content")
    .build(&keys)?;

client.send_event(event).await?;

Test Suites

From grasp-audit/src/specs/grasp01/mod.rs:

grasp-audit/src/specs/grasp01/
├── mod.rs                    # Module exports
├── nip01_smoke.rs            # NIP-01 basic functionality
├── nip11_document.rs         # NIP-11 document tests
├── event_acceptance_policy.rs # GRASP-01 event rules
├── cors.rs                   # CORS header tests
├── git_clone.rs              # Git clone operations
├── push_authorization.rs     # Push validation tests
├── repository_creation.rs    # Repository lifecycle
└── spec_requirements.rs      # Requirement definitions

Unit vs Integration Tests

Unit Tests (no relay required):

#[cfg(test)]
mod tests {
    #[test]
    fn test_audit_config() {
        let config = AuditConfig::isolated();
        assert!(config.run_id.starts_with("ci-"));
    }
}

Integration Tests (relay required):

#[cfg(test)]
mod tests {
    #[tokio::test]
    #[ignore]  // Requires relay
    async fn test_smoke_tests_against_relay() {
        // Test against real relay
    }
}

Running tests:

# Unit tests (fast, no dependencies)
cd grasp-audit && nix develop -c cargo test --lib

# Integration tests (requires relay via test-ngit-relay.sh)
cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test

Test Result Reporting

From grasp-audit/src/result.rs:

use grasp_audit::result::AuditResult;

// Run tests
let results = vec![
    AuditResult::pass("websocket_connection", "Connected successfully"),
    AuditResult::fail("invalid_event", "Expected rejection, got acceptance"),
];

// Report
for result in &results {
    println!("{}", result);
}

// Summary
let passed = results.iter().filter(|r| r.passed).count();
let total = results.len();
println!("Results: {}/{} passed ({:.1}%)",
    passed, total, (passed as f64 / total as f64) * 100.0);

CLI Design

Command Structure

grasp-audit audit [OPTIONS]

Options:
  --relay <URL>        Relay to test (required)
  --mode <MODE>        ci or production (default: ci)
  --run-id <ID>        Custom run ID (production mode only)
  --spec <SPEC>        Test spec to run (default: all)
  --verbose            Detailed output

Usage Examples

# CI mode - quick smoke test
grasp-audit audit \
  --relay ws://localhost:7000 \
  --mode ci \
  --spec nip01-smoke

# Production mode - full compliance audit
grasp-audit audit \
  --relay wss://relay.example.com \
  --mode production \
  --run-id "audit-2025-12-04" \
  --verbose

# Test all specs
grasp-audit audit --relay ws://localhost:7000

Lessons Learned

1. Tag Migration is Breaking

Lesson: Changing tag structure breaks event queries.

Impact: Events created with old tags won't be found by new queries.

Mitigation:

  • ✅ Accept breaking changes in alpha stage
  • ✅ Document migration clearly
  • ✅ Old events auto-expire via cleanup
  • ✅ No production deployments affected

Reference: docs/archive/2025-11-04-tag-migration.md


2. Test Data Lifecycle Matters

Lesson: Test events accumulate and pollute relay.

Solution: Built-in cleanup strategy from day one.

Implementation:

  • Every event has cleanup timestamp
  • Relay can cleanup expired events
  • No deletion event pollution (direct DB cleanup)

3. Isolation Enables Parallel Testing

Lesson: Unique run IDs enable parallel test execution.

Benefit: CI/CD can run multiple test suites simultaneously.

Pattern:

// Each CI run gets unique ID
let config = AuditConfig::isolated();
// run_id = "ci-{uuid}"

// Tests isolated by run ID
let events = client.query().await?;
// Only returns events for this run

4. Standards Compliance Reduces Friction

Lesson: Using standard NIP-01 "t" tags instead of custom tags.

Benefits:

  • ✅ No conflicts with other systems
  • ✅ Standard relay filtering works
  • ✅ Better interoperability
  • ✅ Self-documenting

What's Implemented

Completed Features

  • ✅ GRASP-01 Test Suites: All NIP-01, NIP-11, CORS, event acceptance tests
  • ✅ Spec Requirements Database: Machine-readable requirements in spec_requirements.rs
  • ✅ Automatic Cleanup Tags: Production-safe event tagging
  • ✅ Test Isolation: UUID run IDs for parallel execution
  • ✅ AuditClient: Nostr client wrapper with audit features
  • ✅ Fixture Helpers: Event creation helpers in fixtures.rs

Future Enhancements

  • GRASP-02 Test Suite: Proactive sync tests
  • HTML Report Generation: Rich CI/CD reports
  • Performance Benchmarks: Measure relay performance
  • Relay Comparison: Side-by-side compliance comparison

Common Issues

Issue: Integration Tests Fail

Symptoms: Tests timeout or fail to connect

Causes:

  1. No relay running
  2. Wrong relay URL
  3. Firewall blocking connection

Solution:

# Use test-ngit-relay.sh for automated relay management
cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test

# Or manually:
docker run --rm -p 18081:8081 ghcr.io/danconwaydev/ngit-relay:latest
RELAY_URL="ws://localhost:18081" nix develop -c cargo test --lib -- --ignored

Issue: Events Not Found in Query

Symptoms: Query returns empty even though events were sent

Causes:

  1. Wrong run ID (querying different run)
  2. Connection timing (query before event propagated)
  3. Tag mismatch (uppercase vs lowercase)

Solution:

// Use same config for send and query
let config = AuditConfig::isolated();

// Wait for event to propagate
tokio::time::sleep(Duration::from_millis(500)).await;

// Verify tags match exactly
let t_tag = SingleLetterTag::lowercase(Alphabet::T);  // Lowercase!

Issue: Build Fails in CI

Symptoms: cargo build fails with dependency errors

Cause: Not in Nix dev environment

Solution:

# Enter Nix environment first
cd grasp-audit
nix develop

# Then build
cargo build

Quick Reference

Configuration

// CI mode
let config = AuditConfig::isolated();

// Production mode
let config = AuditConfig::shared();

Client Usage

let client = AuditClient::new("ws://localhost:7000", config).await?;
assert!(client.is_connected().await);

Running Tests

# Unit tests (from grasp-audit/)
nix develop -c cargo test --lib

# Integration tests with ngit-relay
nix develop -c bash test-ngit-relay.sh --mode test

# CLI audit
nix develop -c cargo run -- audit --relay ws://localhost:7000

Key Files

File Purpose
grasp-audit/src/lib.rs Public API
grasp-audit/src/client.rs AuditClient implementation
grasp-audit/src/audit.rs AuditConfig, cleanup tags
grasp-audit/src/specs/grasp01/mod.rs Test suite registry
grasp-audit/src/specs/grasp01/spec_requirements.rs Requirement database