1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
|
# GRASP-01 Event Relationship Smoke Tests Design
**Version:** 1.0
**Date:** 2025-11-05
**Status:** Ready for Implementation
## Overview
This document specifies a focused suite of **smoke tests** for GRASP-01 event reference validation (lines 7-9). These tests validate the basic acceptance/rejection behavior based on event tagging relationships, separate from the comprehensive test suite.
**Key Principle:** Events are accepted if they tag OR are tagged by accepted repositories.
---
## File Location
**Proposed Path:** `grasp-audit/src/specs/grasp01/event-acceptance-policy.rs`
**Rationale:**
- Separate from comprehensive suite
- Clear naming indicates purpose (smoke tests for event acceptance policy)
- Lives in `grasp01/` subdirectory for organization
- Can be run independently or as part of full suite
---
## Test Scenarios
### Scenario Group 1: Accept Events Tagging Accepted Repositories
Events that reference an already-accepted repo should be accepted.
#### Test 1.1: `test_accept_issue_via_a_tag`
**Tags Issue → Repo via `a` tag**
```rust
Setup:
1. Create and send repo announcement (kind 30617)
2. Create issue (kind 1621) with:
- ["a", "30617:{pubkey}:{repo-id}"]
3. Send issue
Expected: Issue SHOULD be stored (query returns it)
```
---
#### Test 1.2: `test_accept_comment_via_A_tag`
**Tags Comment → Repo via `A` tag (NIP-22 root)**
```rust
Setup:
1. Create and send repo announcement
2. Create comment (kind 1111) with:
- ["A", "30617:{pubkey}:{repo-id}"] // Root
- ["K", "30617"]
- ["P", "{repo-pubkey}"]
3. Send comment
Expected: Comment SHOULD be stored
```
---
#### Test 1.3: `test_accept_kind1_via_q_tag`
**Tags Kind 1 → Repo via `q` tag (quote)**
```rust
Setup:
1. Create and send repo announcement
2. Create kind 1 text note with:
- ["q", "30617:{pubkey}:{repo-id}"]
- content: "Check out this repo!"
3. Send kind 1
Expected: Kind 1 SHOULD be stored
```
---
### Scenario Group 2: Accept Events Tagging Accepted Events
Events that reference other accepted events should be accepted (transitive acceptance).
#### Test 2.1: `test_accept_issue_quoting_issue_via_q`
**Issue referencing unaccepted repo but quoting accepted issue**
```rust
Setup:
1. Create and send repo A announcement
2. Create and send issue A (for repo A)
3. Create repo B announcement (DO NOT send - not accepted)
4. Create issue B (for repo B) with:
- ["a", "30617:{pubkey}:{repo-b-id}"] // References unaccepted repo B
- ["q", "{issue-a-id}"] // Quote accepted issue A
5. Send issue B
Expected: Issue B SHOULD be stored (related via quote to accepted issue A,
even though its own repo reference is not accepted)
```
---
#### Test 2.2: `test_accept_comment_via_E_tag`
**Comment on issue via `E` tag (NIP-22)**
```rust
Setup:
1. Create and send repo announcement
2. Create and send issue (kind 1621)
3. Create comment (kind 1111) with:
- ["E", "{issue-id}"] // Root
- ["K", "1621"]
- ["P", "{issue-author}"]
- ["e", "{issue-id}"] // Parent (same as root for top-level)
- ["k", "1621"]
- ["p", "{issue-author}"]
4. Send comment
Expected: Comment SHOULD be stored (related to accepted issue)
```
---
#### Test 2.3: `test_accept_kind1_via_e_tag`
**Kind 1 referencing another kind 1 via `e` tag**
```rust
Setup:
1. Create and send repo announcement
2. Create kind 1 note A with ["q", "30617:{pubkey}:{repo-id}"]
3. Send kind 1 A
4. Create kind 1 note B with:
- ["e", "{kind1-a-id}", "", "reply"]
- content: "Great point!"
5. Send kind 1 B
Expected: Kind 1 B SHOULD be stored (related via e tag to accepted kind 1 A)
```
---
### Scenario Group 3: Accept Events Tagged by Accepted Events
Events that are referenced BY accepted events should be accepted (forward references).
#### Test 3.1: `test_accept_kind1_referenced_in_issue`
**Kind 1 referenced in issue via `q` tag**
```rust
Setup:
1. Create kind 1 note (NOT sent yet)
2. Create and send repo announcement
3. Create issue with:
- ["a", "30617:{pubkey}:{repo-id}"]
- ["q", "{kind1-id}"] // Reference the not-yet-sent kind 1
4. Send issue
5. Send kind 1 note
Expected: Kind 1 SHOULD be stored (referenced by accepted issue)
```
---
#### Test 3.2: `test_accept_comment_referenced_in_comment`
**Comment referenced in another comment via `q` tag**
```rust
Setup:
1. Create and send repo announcement
2. Create and send issue
3. Create comment A (NOT sent yet)
4. Create comment B with:
- ["E", "{issue-id}"] // Root
- ["e", "{issue-id}"] // Parent
- ["q", "{comment-a-id}"] // Quote comment A
5. Send comment B
6. Send comment A
Expected: Comment A SHOULD be stored (referenced by accepted comment B)
```
---
#### Test 3.3: `test_accept_kind1_referenced_in_kind1`
**Kind 1 referenced in accepted kind 1 via `e` tag**
```rust
Setup:
1. Create and send repo announcement
2. Create kind 1 A (NOT sent yet)
3. Create kind 1 B with:
- ["q", "30617:{pubkey}:{repo-id}"]
- ["e", "{kind1-a-id}", "", "mention"]
4. Send kind 1 B
5. Send kind 1 A
Expected: Kind 1 A SHOULD be stored (referenced by accepted kind 1 B)
```
---
### Scenario Group 4: Reject Unrelated Events
Events with no relationship to accepted repositories should be rejected.
#### Test 4.1: `test_reject_orphan_issue`
**Issue from unrelated repository**
```rust
Setup:
1. Create issue (kind 1621) with:
- ["a", "30617:{other-pubkey}:{other-repo-id}"] // Different repo
2. Send issue
Expected: Issue SHOULD NOT be stored (no accepted repo)
```
---
#### Test 4.2: `test_reject_orphan_kind1`
**Kind 1 from unrelated context**
```rust
Setup:
1. Create kind 1 note with generic content (no tags)
2. Send kind 1
Expected: Kind 1 SHOULD NOT be stored (no relationship to any repo)
```
---
#### Test 4.3: `test_reject_comment_quoting_other_repo`
**Comment quoting announcement from different repository**
```rust
Setup:
1. Create repo A announcement (sent)
2. Create repo B announcement (NOT sent - different owner)
3. Create comment with:
- ["A", "30617:{other-pubkey}:{repo-b-id}"] // Root
- ["q", "30617:{other-pubkey}:{repo-b-id}"] // Quote unaccepted repo
4. Send comment
Expected: Comment SHOULD NOT be stored (references unaccepted repo)
```
---
## Helper Functions
Keep helpers minimal and focused on smoke test needs.
**Implementation Note:** Reference [`nostr-sdk`](https://docs.rs/nostr-sdk) (rust-nostr) for event generation patterns. The SDK provides robust helpers for creating events with proper signatures and tags. Use these patterns rather than building everything from scratch.
### `create_test_repo(client, repo_id) -> Event`
Creates a basic repo announcement with required tags.
```rust
async fn create_test_repo(client: &AuditClient, repo_id: &str) -> Result<Event> {
client.create_repo_announcement(repo_id).await
}
```
---
### `create_issue_for_repo(client, repo_event, subject) -> Event`
Creates issue referencing repo via `a` tag.
```rust
async fn create_issue_for_repo(
client: &AuditClient,
repo_event: &Event,
subject: &str,
) -> Result<Event> {
let repo_id = extract_d_tag(repo_event)?;
let a_tag = Tag::parse(&["a", &format!("30617:{}:{}", repo_event.pubkey, repo_id)])?;
client.event_builder()
.kind(Kind::Custom(1621))
.content(format!("Issue: {}", subject))
.tag(a_tag)
.build()
.await
}
```
---
### `create_comment_for_event(client, root_event, content) -> Event`
Creates NIP-22 comment for an event.
```rust
async fn create_comment_for_event(
client: &AuditClient,
root_event: &Event,
content: &str,
) -> Result<Event> {
client.event_builder()
.kind(Kind::Custom(1111))
.content(content)
.tag(Tag::parse(&["E", &root_event.id.to_string()])?)
.tag(Tag::parse(&["K", &root_event.kind.to_string()])?)
.tag(Tag::parse(&["P", &root_event.pubkey.to_string()])?)
.tag(Tag::parse(&["e", &root_event.id.to_string()])?)
.tag(Tag::parse(&["k", &root_event.kind.to_string()])?)
.tag(Tag::parse(&["p", &root_event.pubkey.to_string()])?)
.build()
.await
}
```
---
### `send_and_verify_accepted(client, event) -> Result<()>`
Sends event and verifies it was stored.
```rust
async fn send_and_verify_accepted(client: &AuditClient, event: Event) -> Result<()> {
let event_id = client.send_event(event.clone()).await?;
// Small delay for propagation
tokio::time::sleep(Duration::from_millis(100)).await;
let filter = Filter::new()
.id(event_id)
.limit(1);
let results = client.query(filter).await?;
if results.is_empty() {
return Err("Event was not stored".into());
}
Ok(())
}
```
---
### `send_and_verify_rejected(client, event) -> Result<()>`
Sends event and verifies it was NOT stored.
```rust
async fn send_and_verify_rejected(client: &AuditClient, event: Event) -> Result<()> {
let event_id = event.id;
// Attempt to send
let _ = client.send_event(event).await;
// Small delay for propagation
tokio::time::sleep(Duration::from_millis(100)).await;
let filter = Filter::new()
.id(event_id)
.limit(1);
let results = client.query(filter).await?;
if !results.is_empty() {
return Err("Event was stored but should have been rejected".into());
}
Ok(())
}
```
---
### `extract_d_tag(event) -> Result<String>`
Extracts `d` tag value from event.
```rust
fn extract_d_tag(event: &Event) -> Result<String> {
event.tags
.iter()
.find(|t| t.kind() == TagKind::d())
.and_then(|t| t.content())
.ok_or("Missing d tag")?
.to_string()
}
```
---
## Module Structure
```rust
//! GRASP-01 Event Relationship Smoke Tests
//!
//! Focused smoke tests validating basic event acceptance/rejection
//! based on tagging relationships with accepted repositories.
use crate::{AuditClient, AuditResult, TestResult};
use nostr_sdk::prelude::*;
use std::time::Duration;
pub struct EventAcceptancePolicyTests;
impl EventAcceptancePolicyTests {
pub async fn run_all(client: &AuditClient) -> AuditResult {
let mut results = AuditResult::new("GRASP-01 Event Acceptance Policy Tests");
// Group 1: Events tagging repos
results.add(Self::test_accept_issue_via_a_tag(client).await);
results.add(Self::test_accept_comment_via_A_tag(client).await);
results.add(Self::test_accept_kind1_via_q_tag(client).await);
// Group 2: Events tagging accepted events
results.add(Self::test_accept_issue_quoting_issue_via_q(client).await);
results.add(Self::test_accept_comment_via_E_tag(client).await);
results.add(Self::test_accept_kind1_via_e_tag(client).await);
// Group 3: Events tagged by accepted events
results.add(Self::test_accept_kind1_referenced_in_issue(client).await);
results.add(Self::test_accept_comment_referenced_in_comment(client).await);
results.add(Self::test_accept_kind1_referenced_in_kind1(client).await);
// Group 4: Reject unrelated events
results.add(Self::test_reject_orphan_issue(client).await);
results.add(Self::test_reject_orphan_kind1(client).await);
results.add(Self::test_reject_comment_quoting_other_repo(client).await);
results
}
// Test implementations follow...
}
// Helper functions follow...
```
---
## Integration with Test Suite
Add to `grasp-audit/src/specs/grasp01/mod.rs`:
```rust
pub mod event_acceptance_policy;
pub use event_acceptance_policy::EventAcceptancePolicyTests;
```
Add to main test runner if desired, or run independently:
```rust
// In grasp01_nostr_relay.rs or separate test file
#[tokio::test]
#[ignore]
async fn test_event_acceptance_policy_suite() {
let client = AuditClient::new_for_relay(&relay_url()).await.unwrap();
let results = EventAcceptancePolicyTests::run_all(&client).await;
// Assert all tests passed
assert!(results.all_passed(), "Some tests failed:\n{}", results);
}
```
---
## Implementation Notes
1. **Simplicity First:** Keep test logic straightforward - setup, send, verify
2. **Independent Tests:** Each test should be runnable standalone
3. **Clear Failures:** Use descriptive error messages for debugging
4. **Minimal Helpers:** Only create helpers that reduce significant duplication
5. **Fast Execution:** Smoke tests should run quickly (use minimal delays)
---
## Expected Outcomes
When implemented, this suite should:
- ✅ Run in under 5 seconds total
- ✅ Clearly show which relationship types work/fail
- ✅ Provide quick validation during development
- ✅ Act as regression tests for basic GRASP-01 compliance
- ✅ Be easy to understand and modify
---
## Next Steps
1. Create `grasp-audit/src/specs/grasp01/event-acceptance-policy.rs`
2. Implement helper functions (referencing nostr-sdk patterns)
3. Implement each test function following the specifications above
4. Add module declaration to `grasp01/mod.rs`
5. Run tests: `cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test`
6. Verify all tests pass or show expected "Not implemented yet" status
---
## Success Criteria
- [ ] All 12 tests compile without errors
- [ ] Tests run independently and as a suite
- [ ] Accept tests verify events ARE stored
- [ ] Reject tests verify events are NOT stored
- [ ] Helper functions eliminate code duplication
- [ ] Test output clearly indicates pass/fail/not-implemented
|