diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-18 16:50:02 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-18 16:50:02 +0000 |
| commit | 98c6fa4bfa897ff0b8f9c95ea698d4d065b5e9f3 (patch) | |
| tree | 14e94bbbfa1658229dbcd6ce9a03b3eb5cce4aef /docs/archive/2025-11-05-grasp01-event-reference-test-design.md | |
| parent | 1fe9c179d5dd73d443ab4792d4c2fbd19690afcb (diff) | |
docs: switch focus onto grasp implementation
Diffstat (limited to 'docs/archive/2025-11-05-grasp01-event-reference-test-design.md')
| -rw-r--r-- | docs/archive/2025-11-05-grasp01-event-reference-test-design.md | 987 |
1 files changed, 987 insertions, 0 deletions
diff --git a/docs/archive/2025-11-05-grasp01-event-reference-test-design.md b/docs/archive/2025-11-05-grasp01-event-reference-test-design.md new file mode 100644 index 0000000..dd805b8 --- /dev/null +++ b/docs/archive/2025-11-05-grasp01-event-reference-test-design.md | |||
| @@ -0,0 +1,987 @@ | |||
| 1 | # GRASP-01 Event Reference Validation Test Design | ||
| 2 | |||
| 3 | **Version:** 1.0 | ||
| 4 | **Date:** 2025-11-05 | ||
| 5 | **Status:** Design Phase - Ready for Review | ||
| 6 | |||
| 7 | ## Executive Summary | ||
| 8 | |||
| 9 | This document provides a comprehensive test design for GRASP-01 lines 7-9 compliance, covering event reference validation. The design reshapes existing test stubs to implement proper event relationship testing across all NIP-34 event types (issues, patches, PRs, comments, status updates, and text notes). | ||
| 10 | |||
| 11 | ## 1. Analysis Section | ||
| 12 | |||
| 13 | ### 1.1 NIP-34 Event Structures | ||
| 14 | |||
| 15 | From `/persistent/dcdev/clones/nips/34.md`, we have these git-related event types: | ||
| 16 | |||
| 17 | #### Repository Announcements (kind 30617) | ||
| 18 | ```json | ||
| 19 | { | ||
| 20 | "kind": 30617, | ||
| 21 | "tags": [ | ||
| 22 | ["d", "<repo-id>"], | ||
| 23 | ["a", "30617:<pubkey>:<repo-id>"], | ||
| 24 | ["clone", "<url>", ...], | ||
| 25 | ["relays", "<relay-url>", ...], | ||
| 26 | ["maintainers", "<pubkey>", ...] | ||
| 27 | ] | ||
| 28 | } | ||
| 29 | ``` | ||
| 30 | |||
| 31 | #### Patches (kind 1617) | ||
| 32 | ```json | ||
| 33 | { | ||
| 34 | "kind": 1617, | ||
| 35 | "tags": [ | ||
| 36 | ["a", "30617:<base-repo-owner-pubkey>:<base-repo-id>"], | ||
| 37 | ["e", "<parent-patch-id>", "", "reply"], // NIP-10 threading | ||
| 38 | ["p", "<repository-owner>"], | ||
| 39 | ["r", "<earliest-unique-commit-id>"] | ||
| 40 | ] | ||
| 41 | } | ||
| 42 | ``` | ||
| 43 | |||
| 44 | #### Pull Requests (kind 1618) | ||
| 45 | ```json | ||
| 46 | { | ||
| 47 | "kind": 1618, | ||
| 48 | "tags": [ | ||
| 49 | ["a", "30617:<base-repo-owner-pubkey>:<base-repo-id>"], | ||
| 50 | ["e", "<root-patch-event-id>"], // Optional revision reference | ||
| 51 | ["p", "<repository-owner>"], | ||
| 52 | ["c", "<current-commit-id>"] | ||
| 53 | ] | ||
| 54 | } | ||
| 55 | ``` | ||
| 56 | |||
| 57 | #### Issues (kind 1621) | ||
| 58 | ```json | ||
| 59 | { | ||
| 60 | "kind": 1621, | ||
| 61 | "tags": [ | ||
| 62 | ["a", "30617:<base-repo-owner-pubkey>:<base-repo-id>"], | ||
| 63 | ["p", "<repository-owner>"] | ||
| 64 | ] | ||
| 65 | } | ||
| 66 | ``` | ||
| 67 | |||
| 68 | #### Comments (kind 1111 - NIP-22) | ||
| 69 | ```json | ||
| 70 | { | ||
| 71 | "kind": 1111, | ||
| 72 | "tags": [ | ||
| 73 | ["E", "<root-event-id>"], // Root scope (uppercase) | ||
| 74 | ["K", "<root-kind>"], | ||
| 75 | ["P", "<root-pubkey>"], | ||
| 76 | ["e", "<parent-event-id>"], // Parent (lowercase) | ||
| 77 | ["k", "<parent-kind>"], | ||
| 78 | ["p", "<parent-pubkey>"] | ||
| 79 | ] | ||
| 80 | } | ||
| 81 | ``` | ||
| 82 | |||
| 83 | ### 1.2 GRASP-01 Lines 7-9 Requirements | ||
| 84 | |||
| 85 | Based on test stub comments in [`grasp01_nostr_relay.rs:29-36`](grasp-audit/src/specs/grasp01_nostr_relay.rs:29-36): | ||
| 86 | |||
| 87 | **Line 7-9 (inferred):** Events that **tag** OR **are tagged by** accepted repository announcements SHOULD be stored. | ||
| 88 | |||
| 89 | This breaks down into three scenarios: | ||
| 90 | |||
| 91 | 1. **Events NOT referenced** by or referencing other events → SHOULD NOT be stored (orphans) | ||
| 92 | 2. **Events referenced BY** an existing stored event → SHOULD be stored (forward reference) | ||
| 93 | 3. **Events referencing** an existing stored event → SHOULD be stored (backward reference) | ||
| 94 | |||
| 95 | ### 1.3 Reference Tag Types and Semantics | ||
| 96 | |||
| 97 | #### Standard Nostr Reference Tags | ||
| 98 | |||
| 99 | | Tag | Purpose | Format | NIP | | ||
| 100 | |-----|---------|--------|-----| | ||
| 101 | | `e` | Event ID reference | `["e", "<event-id>", "<relay>", "<marker>", "<pubkey>"]` | NIP-10 | | ||
| 102 | | `a` | Addressable event reference | `["a", "<kind>:<pubkey>:<d-tag>", "<relay>"]` | NIP-01 | | ||
| 103 | | `p` | Pubkey reference | `["p", "<pubkey>", "<relay>"]` | NIP-01 | | ||
| 104 | | `q` | Quote reference | `["q", "<event-id or address>", "<relay>", "<pubkey>"]` | NIP-10 | | ||
| 105 | |||
| 106 | #### NIP-22 Comment Tags (Uppercase = Root, Lowercase = Parent) | ||
| 107 | |||
| 108 | | Tag | Purpose | Format | | ||
| 109 | |-----|---------|--------| | ||
| 110 | | `E` | Root event ID | `["E", "<event-id>", "<relay>", "<pubkey>"]` | | ||
| 111 | | `A` | Root addressable event | `["A", "<kind>:<pubkey>:<d-tag>", "<relay>"]` | | ||
| 112 | | `K` | Root event kind | `["K", "<kind>"]` | | ||
| 113 | | `P` | Root author pubkey | `["P", "<pubkey>", "<relay>"]` | | ||
| 114 | | `e` | Parent event ID | `["e", "<event-id>", "<relay>", "<pubkey>"]` | | ||
| 115 | | `k` | Parent event kind | `["k", "<kind>"]` | | ||
| 116 | | `p` | Parent author pubkey | `["p", "<pubkey>", "<relay>"]` | | ||
| 117 | |||
| 118 | #### NIP-10 Threading Tags | ||
| 119 | |||
| 120 | | Marker | Purpose | | ||
| 121 | |--------|---------| | ||
| 122 | | `root` | First event in thread | | ||
| 123 | | `reply` | Direct reply to parent | | ||
| 124 | |||
| 125 | ### 1.4 Event Type Coverage Requirements | ||
| 126 | |||
| 127 | Tests must cover: | ||
| 128 | |||
| 129 | - ✅ **Issues** (kind 1621) - referencing repos via `a` tag | ||
| 130 | - ✅ **Patches** (kind 1617) - referencing repos via `a` tag, threading via `e` tags | ||
| 131 | - ✅ **Pull Requests** (kind 1618) - referencing repos via `a` tag | ||
| 132 | - ✅ **Comments** (kind 1111) - replying via NIP-22 structure | ||
| 133 | - ✅ **Status updates** (kinds 1630-1633) - referencing issues/PRs via `e` tag (may also use `E` tag for root references) | ||
| 134 | - ✅ **Text notes** (kind 1) - may reference announcements/issues/patches/comments OR be referenced by them | ||
| 135 | |||
| 136 | ## 2. Test Architecture Design | ||
| 137 | |||
| 138 | ### 2.1 Overall Test Suite Structure | ||
| 139 | |||
| 140 | To manage the growing number of tests, we'll organize them into separate test module files: | ||
| 141 | |||
| 142 | ``` | ||
| 143 | grasp-audit/src/specs/ | ||
| 144 | ├── mod.rs (module declarations) | ||
| 145 | ├── grasp01_nostr_relay.rs (main entry point, existing tests) | ||
| 146 | └── grasp01/ | ||
| 147 | ├── mod.rs (test suite registration) | ||
| 148 | ├── helpers.rs (shared helper functions) | ||
| 149 | ├── issues.rs (issue reference tests) | ||
| 150 | ├── patches.rs (patch reference tests) | ||
| 151 | ├── pull_requests.rs (PR reference tests) | ||
| 152 | ├── comments.rs (NIP-22 comment tests) | ||
| 153 | ├── status_updates.rs (status change tests) | ||
| 154 | └── text_notes.rs (kind 1 reference tests) | ||
| 155 | ``` | ||
| 156 | |||
| 157 | **Benefits:** | ||
| 158 | - Better code organization and navigation | ||
| 159 | - Isolated test contexts | ||
| 160 | - Easier to maintain and extend | ||
| 161 | - Clear separation of concerns | ||
| 162 | |||
| 163 | ### 2.2 Test Organization Strategy | ||
| 164 | |||
| 165 | **Group by relationship type:** | ||
| 166 | |||
| 167 | 1. **Forward References** - Event A exists, send Event B that references A | ||
| 168 | 2. **Backward References** - Send Event A that references B, then send B | ||
| 169 | 3. **Bidirectional** - Events that both reference each other | ||
| 170 | 4. **Orphans** - Events with no references (should be rejected) | ||
| 171 | 5. **Transitive** - Multi-hop references (A → B → C) | ||
| 172 | |||
| 173 | **Group by event type:** | ||
| 174 | |||
| 175 | 1. Issues referencing repos | ||
| 176 | 2. Patches referencing repos (with threading) | ||
| 177 | 3. PRs referencing repos | ||
| 178 | 4. Comments replying to issues/patches/PRs | ||
| 179 | 5. Status updates for issues/PRs | ||
| 180 | 6. Text notes being tagged by repos | ||
| 181 | |||
| 182 | ## 3. Helper Function Specifications | ||
| 183 | |||
| 184 | ### 3.1 Core Event Creation Helpers | ||
| 185 | |||
| 186 | ```rust | ||
| 187 | /// Create a NIP-34 issue event | ||
| 188 | async fn create_issue( | ||
| 189 | client: &AuditClient, | ||
| 190 | repo_announcement: &Event, | ||
| 191 | subject: &str, | ||
| 192 | content: &str, | ||
| 193 | ) -> Result<Event> | ||
| 194 | ``` | ||
| 195 | |||
| 196 | **Purpose:** Create properly formatted issue (kind 1621) with `a` tag to repo | ||
| 197 | **Returns:** Signed event ready to send | ||
| 198 | **Usage:** | ||
| 199 | ```rust | ||
| 200 | let issue = create_issue(&client, &repo_event, "Bug: Test", "Description").await?; | ||
| 201 | ``` | ||
| 202 | |||
| 203 | --- | ||
| 204 | |||
| 205 | ```rust | ||
| 206 | /// Create a NIP-34 patch event | ||
| 207 | async fn create_patch( | ||
| 208 | client: &AuditClient, | ||
| 209 | repo_announcement: &Event, | ||
| 210 | parent_patch: Option<&Event>, | ||
| 211 | patch_content: &str, | ||
| 212 | ) -> Result<Event> | ||
| 213 | ``` | ||
| 214 | |||
| 215 | **Purpose:** Create patch (kind 1617) with optional NIP-10 threading | ||
| 216 | **Returns:** Signed event with proper `a` tag and optional `e` reply tag | ||
| 217 | **Usage:** | ||
| 218 | ```rust | ||
| 219 | // First patch in series | ||
| 220 | let patch1 = create_patch(&client, &repo, None, "diff...").await?; | ||
| 221 | |||
| 222 | // Reply patch | ||
| 223 | let patch2 = create_patch(&client, &repo, Some(&patch1), "diff...").await?; | ||
| 224 | ``` | ||
| 225 | |||
| 226 | --- | ||
| 227 | |||
| 228 | ```rust | ||
| 229 | /// Create a NIP-34 pull request event | ||
| 230 | async fn create_pull_request( | ||
| 231 | client: &AuditClient, | ||
| 232 | repo_announcement: &Event, | ||
| 233 | branch_name: &str, | ||
| 234 | commit_id: &str, | ||
| 235 | ) -> Result<Event> | ||
| 236 | ``` | ||
| 237 | |||
| 238 | **Purpose:** Create PR (kind 1618) with proper repo reference | ||
| 239 | **Returns:** Signed event with `a` tag | ||
| 240 | **Usage:** | ||
| 241 | ```rust | ||
| 242 | let pr = create_pull_request(&client, &repo, "feature-x", "abc123").await?; | ||
| 243 | ``` | ||
| 244 | |||
| 245 | --- | ||
| 246 | |||
| 247 | ```rust | ||
| 248 | /// Create a NIP-22 comment event | ||
| 249 | async fn create_comment( | ||
| 250 | client: &AuditClient, | ||
| 251 | root_event: &Event, // The root (issue, patch, or PR) | ||
| 252 | parent_event: Option<&Event>, // None for top-level, Some for replies | ||
| 253 | content: &str, | ||
| 254 | ) -> Result<Event> | ||
| 255 | ``` | ||
| 256 | |||
| 257 | **Purpose:** Create comment (kind 1111) with proper NIP-22 tags | ||
| 258 | **Returns:** Signed event with E/K/P (root) and e/k/p (parent) tags | ||
| 259 | **Usage:** | ||
| 260 | ```rust | ||
| 261 | // Top-level comment | ||
| 262 | let comment1 = create_comment(&client, &issue, None, "Great idea!").await?; | ||
| 263 | |||
| 264 | // Reply to comment | ||
| 265 | let comment2 = create_comment(&client, &issue, Some(&comment1), "Thanks!").await?; | ||
| 266 | ``` | ||
| 267 | |||
| 268 | --- | ||
| 269 | |||
| 270 | ```rust | ||
| 271 | /// Create a status event | ||
| 272 | async fn create_status( | ||
| 273 | client: &AuditClient, | ||
| 274 | target_event: &Event, // Issue, patch, or PR | ||
| 275 | status_kind: Kind, // 1630 (Open), 1631 (Resolved), 1632 (Closed), 1633 (Draft) | ||
| 276 | reason: &str, | ||
| 277 | ) -> Result<Event> | ||
| 278 | ``` | ||
| 279 | |||
| 280 | **Purpose:** Create status change event | ||
| 281 | **Returns:** Signed event with `e` tag to target | ||
| 282 | **Usage:** | ||
| 283 | ```rust | ||
| 284 | let status = create_status(&client, &issue, Kind::Custom(1631), "Fixed in v1.0").await?; | ||
| 285 | ``` | ||
| 286 | |||
| 287 | ### 3.2 Test Orchestration Helpers | ||
| 288 | |||
| 289 | ```rust | ||
| 290 | /// Send event and verify acceptance by querying back | ||
| 291 | async fn send_and_verify_stored( | ||
| 292 | client: &AuditClient, | ||
| 293 | event: Event, | ||
| 294 | ) -> Result<()> | ||
| 295 | ``` | ||
| 296 | |||
| 297 | **Purpose:** Send event, wait for propagation, query to confirm storage | ||
| 298 | **Reduces:** Duplication of send → wait → query → verify pattern | ||
| 299 | **Usage:** | ||
| 300 | ```rust | ||
| 301 | send_and_verify_stored(&client, issue_event).await?; | ||
| 302 | ``` | ||
| 303 | |||
| 304 | --- | ||
| 305 | |||
| 306 | ```rust | ||
| 307 | /// Send event and verify it was NOT stored (rejection test) | ||
| 308 | async fn send_and_verify_rejected( | ||
| 309 | client: &AuditClient, | ||
| 310 | event: Event, | ||
| 311 | ) -> Result<()> | ||
| 312 | ``` | ||
| 313 | |||
| 314 | **Purpose:** Send event, verify it's not in relay storage | ||
| 315 | **Reduces:** Duplication in negative tests | ||
| 316 | **Usage:** | ||
| 317 | ```rust | ||
| 318 | send_and_verify_rejected(&client, orphan_event).await?; | ||
| 319 | ``` | ||
| 320 | |||
| 321 | --- | ||
| 322 | |||
| 323 | ```rust | ||
| 324 | /// Extract repo identifier from announcement event | ||
| 325 | fn extract_repo_id(repo_announcement: &Event) -> Result<String> | ||
| 326 | ``` | ||
| 327 | |||
| 328 | **Purpose:** Get `d` tag value from repo announcement | ||
| 329 | **Reduces:** Tag parsing duplication | ||
| 330 | **Usage:** | ||
| 331 | ```rust | ||
| 332 | let repo_id = extract_repo_id(&repo_event)?; | ||
| 333 | ``` | ||
| 334 | |||
| 335 | --- | ||
| 336 | |||
| 337 | ```rust | ||
| 338 | /// Build addressable event tag (a tag) for repo | ||
| 339 | fn build_repo_atag(repo_announcement: &Event) -> Result<Tag> | ||
| 340 | ``` | ||
| 341 | |||
| 342 | **Purpose:** Create properly formatted `a` tag for repo reference | ||
| 343 | **Reduces:** Tag construction errors | ||
| 344 | **Usage:** | ||
| 345 | ```rust | ||
| 346 | let a_tag = build_repo_atag(&repo_announcement)?; | ||
| 347 | ``` | ||
| 348 | |||
| 349 | ## 4. Test Case Specifications | ||
| 350 | |||
| 351 | ### 4.1 Issues Referencing Repositories | ||
| 352 | |||
| 353 | #### Test: `test_accept_issue_for_repo` | ||
| 354 | **Validates:** GRASP-01 lines 8-9 - Accept issues referencing accepted repos | ||
| 355 | **Reference Tags:** `a` tag (repo) | ||
| 356 | **Expected:** Issue event SHOULD be stored | ||
| 357 | |||
| 358 | **Setup:** | ||
| 359 | 1. Create and send kind 30617 repo announcement | ||
| 360 | 2. Verify repo is stored | ||
| 361 | 3. Create kind 1621 issue with: | ||
| 362 | - `["a", "30617:{pubkey}:{d-tag}"]` | ||
| 363 | - `["subject", "Bug: Something broken"]` | ||
| 364 | 4. Send issue event | ||
| 365 | |||
| 366 | **Verification:** | ||
| 367 | - Query for kind 1621 with author filter | ||
| 368 | - Verify issue event was stored | ||
| 369 | - Verify `a` tag correctly references repo | ||
| 370 | |||
| 371 | --- | ||
| 372 | |||
| 373 | #### Test: `test_reject_issue_for_nonexistent_repo` | ||
| 374 | **Validates:** GRASP-01 line 7 - Reject orphaned issues | ||
| 375 | **Reference Tags:** `a` tag (nonexistent repo) | ||
| 376 | **Expected:** Issue event SHOULD NOT be stored | ||
| 377 | |||
| 378 | **Setup:** | ||
| 379 | 1. Create kind 1621 issue with `a` tag referencing non-existent repo | ||
| 380 | 2. Send issue event | ||
| 381 | |||
| 382 | **Verification:** | ||
| 383 | - Query for issue event | ||
| 384 | - Verify it was NOT stored (empty result) | ||
| 385 | |||
| 386 | ### 4.2 Patches Referencing Repositories | ||
| 387 | |||
| 388 | #### Test: `test_accept_patch_for_repo` | ||
| 389 | **Validates:** GRASP-01 lines 8-9 - Accept patches for accepted repos | ||
| 390 | **Reference Tags:** `a` tag (repo), `p` tag, `r` tag | ||
| 391 | **Expected:** Patch event SHOULD be stored | ||
| 392 | |||
| 393 | **Setup:** | ||
| 394 | 1. Create and send repo announcement | ||
| 395 | 2. Create kind 1617 patch with: | ||
| 396 | - `["a", "30617:{pubkey}:{d-tag}"]` | ||
| 397 | - `["p", "{repo-owner}"]` | ||
| 398 | - `["r", "{commit-id}"]` | ||
| 399 | - `["t", "root"]` (first patch marker) | ||
| 400 | 3. Send patch | ||
| 401 | |||
| 402 | **Verification:** | ||
| 403 | - Query for kind 1617 | ||
| 404 | - Verify patch stored | ||
| 405 | -Verify proper repo reference | ||
| 406 | |||
| 407 | --- | ||
| 408 | |||
| 409 | #### Test: `test_accept_patch_series_threading` | ||
| 410 | **Validates:** NIP-10 threading in patches | ||
| 411 | **Reference Tags:** `e` reply tag for threading | ||
| 412 | **Expected:** All patches in series SHOULD be stored | ||
| 413 | |||
| 414 | **Setup:** | ||
| 415 | 1. Send repo announcement | ||
| 416 | 2. Create and send patch 1 with `["t", "root"]` | ||
| 417 | 3. Create patch 2 with `["e", "{patch1-id}", "", "reply"]` | ||
| 418 | 4. Create patch 3 with `["e", "{patch2-id}", "", "reply"]` | ||
| 419 | 5. Send patches 2 and 3 | ||
| 420 | |||
| 421 | **Verification:** | ||
| 422 | - Query all 3 patches | ||
| 423 | - Verify threading structure via `e` tags | ||
| 424 | - Verify all stored | ||
| 425 | |||
| 426 | ### 4.3 Pull Requests Referencing Repositories | ||
| 427 | |||
| 428 | #### Test: `test_accept_pull_request_for_repo` | ||
| 429 | **Validates:** GRASP-01 lines 8-9 - Accept PRs for accepted repos | ||
| 430 | **Reference Tags:** `a` tag, `c` tag (commit) | ||
| 431 | **Expected:** PR event SHOULD be stored | ||
| 432 | |||
| 433 | **Setup:** | ||
| 434 | 1. Send repo announcement | ||
| 435 | 2. Create kind 1618 PR with: | ||
| 436 | - `["a", "30617:{pubkey}:{d-tag}"]` | ||
| 437 | - `["c", "{commit-id}"]` | ||
| 438 | - `["subject", "Add feature X"]` | ||
| 439 | 3. Send PR | ||
| 440 | |||
| 441 | **Verification:** | ||
| 442 | - Query kind 1618 | ||
| 443 | - Verify PR stored with correct repo reference | ||
| 444 | |||
| 445 | --- | ||
| 446 | |||
| 447 | #### Test: `test_accept_pr_update` | ||
| 448 | **Validates:** PR updates (kind 1619) reference original PR | ||
| 449 | **Reference Tags:** `E` tag (NIP-22 root), `P` tag | ||
| 450 | **Expected:** PR update SHOULD be stored | ||
| 451 | |||
| 452 | **Setup:** | ||
| 453 | 1. Create and send repo + original PR | ||
| 454 | 2. Create kind 1619 update with: | ||
| 455 | - `["E", "{pr-event-id}"]` | ||
| 456 | - `["P", "{pr-author}"]` | ||
| 457 | - `["c", "{new-commit-id}"]` | ||
| 458 | 3. Send update | ||
| 459 | |||
| 460 | **Verification:** | ||
| 461 | - Query kind 1619 | ||
| 462 | - Verify update references original PR | ||
| 463 | |||
| 464 | ### 4.4 Comments (NIP-22) | ||
| 465 | |||
| 466 | #### Test: `test_accept_reply_to_issue` | ||
| 467 | **Validates:** Comments on issues using NIP-22 | ||
| 468 | **Reference Tags:** `E`, `K`, `P` (root), `e`, `k`, `p` (parent) | ||
| 469 | **Expected:** Comment SHOULD be stored | ||
| 470 | |||
| 471 | **Setup:** | ||
| 472 | 1. Send repo + issue | ||
| 473 | 2. Create kind 1111 comment with: | ||
| 474 | - `["E", "{issue-id}"]` (root) | ||
| 475 | - `["K", "1621"]` (issue kind) | ||
| 476 | - `["P", "{issue-author}"]` | ||
| 477 | - `["e", "{issue-id}"]` (parent, same as root for top-level) | ||
| 478 | - `["k", "1621"]` | ||
| 479 | - `["p", "{issue-author}"]` | ||
| 480 | 3. Send comment | ||
| 481 | |||
| 482 | **Verification:** | ||
| 483 | - Query kind 1111 | ||
| 484 | - Verify proper NIP-22 tag structure | ||
| 485 | |||
| 486 | --- | ||
| 487 | |||
| 488 | #### Test: `test_accept_nested_comment_thread` | ||
| 489 | **Validates:** Multi-level comment threading | ||
| 490 | **Reference Tags:** E/K/P (constant root), e/k/p (changing parent) | ||
| 491 | **Expected:** All comments SHOULD be stored | ||
| 492 | |||
| 493 | **Setup:** | ||
| 494 | 1. Send repo + issue | ||
| 495 | 2. Send comment 1 (to issue) | ||
| 496 | 3. Send comment 2 (reply to comment 1): | ||
| 497 | - Root tags point to issue | ||
| 498 | - Parent tags point to comment 1 | ||
| 499 | 4. Send comment 3 (reply to comment 2): | ||
| 500 | - Root tags still point to issue | ||
| 501 | - Parent tags point to comment 2 | ||
| 502 | |||
| 503 | **Verification:** | ||
| 504 | - Query all 3 comments | ||
| 505 | - Verify root tags always reference issue | ||
| 506 | - Verify parent tags form chain | ||
| 507 | |||
| 508 | --- | ||
| 509 | |||
| 510 | #### Test: `test_accept_comment_on_patch` | ||
| 511 | **Validates:** Comments work on patches | ||
| 512 | **Reference Tags:** NIP-22 tags for kind 1617 | ||
| 513 | **Expected:** Comment on patch SHOULD be stored | ||
| 514 | |||
| 515 | **Setup:** | ||
| 516 | 1. Send repo + patch | ||
| 517 | 2. Send kind 1111 comment referencing patch | ||
| 518 | 3. Verify stored | ||
| 519 | |||
| 520 | --- | ||
| 521 | |||
| 522 | #### Test: `test_accept_comment_on_pr` | ||
| 523 | **Validates:** Comments work on PRs | ||
| 524 | **Reference Tags:** NIP-22 tags for kind 1618 | ||
| 525 | **Expected:** Comment on PR SHOULD be stored | ||
| 526 | |||
| 527 | ### 4.5 Status Updates | ||
| 528 | |||
| 529 | #### Test: `test_accept_status_for_issue` | ||
| 530 | **Validates:** Status changes for issues | ||
| 531 | **Reference Tags:** `e` tag, `p` tag | ||
| 532 | **Expected:** Status event SHOULD be stored | ||
| 533 | |||
| 534 | **Setup:** | ||
| 535 | 1. Send repo + issue | ||
| 536 | 2. Create kind 1631 (Resolved) status with: | ||
| 537 | - `["e", "{issue-id}", "", "root"]` | ||
| 538 | - `["p", "{issue-author}"]` | ||
| 539 | - `["a", "30617:{pubkey}:{repo-id}"]` (optional) | ||
| 540 | 3. Send status | ||
| 541 | |||
| 542 | **Verification:** | ||
| 543 | - Query kind 1631 | ||
| 544 | - Verify references issue | ||
| 545 | |||
| 546 | ### 4.6 Text Notes and Cross-References | ||
| 547 | |||
| 548 | #### Test: `test_accept_kind1_quoted_by_issue` | ||
| 549 | **Validates:** Kind 1 text notes referenced by issues using `q` tag | ||
| 550 | **Reference Tags:** Issue's `q` tag pointing to kind 1 note | ||
| 551 | **Expected:** Kind 1 note SHOULD be accepted when issue quotes it | ||
| 552 | |||
| 553 | **Setup:** | ||
| 554 | 1. Create kind 1 text note about project | ||
| 555 | 2. Send text note (may initially be rejected) | ||
| 556 | 3. Send repo announcement | ||
| 557 | 4. Create kind 1621 issue with: | ||
| 558 | - `["a", "30617:{pubkey}:{d-tag}"]` (repo reference) | ||
| 559 | - `["q", "{note-id}"]` (quote reference to kind 1) | ||
| 560 | - `["subject", "Discussion: Feature Request"]` | ||
| 561 | 5. Send issue | ||
| 562 | 6. Re-query for text note | ||
| 563 | |||
| 564 | **Verification:** | ||
| 565 | - Text note should now be stored | ||
| 566 | - Verifies kind 1 being referenced by issue scenario | ||
| 567 | |||
| 568 | ## 5. Implementation Phases | ||
| 569 | |||
| 570 | ### Phase 1: Module Structure Setup (Priority: HIGH) | ||
| 571 | **Goal:** Create new test suite file structure | ||
| 572 | **Duration:** 0.5 days | ||
| 573 | |||
| 574 | **Tasks:** | ||
| 575 | 1. Create `grasp-audit/src/specs/grasp01/` directory | ||
| 576 | 2. Set up module files: | ||
| 577 | - `mod.rs` (test registration) | ||
| 578 | - `helpers.rs` (shared functions) | ||
| 579 | - `issues.rs` | ||
| 580 | - `patches.rs` | ||
| 581 | - `pull_requests.rs` | ||
| 582 | - `comments.rs` | ||
| 583 | - `status_updates.rs` | ||
| 584 | - `text_notes.rs` | ||
| 585 | 3. Update `grasp-audit/src/specs/mod.rs` to include new module | ||
| 586 | |||
| 587 | **Acceptance Criteria:** | ||
| 588 | - Module structure compiles | ||
| 589 | - Tests can be run from new location | ||
| 590 | - No duplicate code | ||
| 591 | |||
| 592 | ### Phase 2: Helper Functions (Priority: HIGH) | ||
| 593 | **Goal:** Core helper functions in `helpers.rs` | ||
| 594 | **Duration:** 1 day | ||
| 595 | |||
| 596 | **Tasks:** | ||
| 597 | 1. Implement core event creation helpers: | ||
| 598 | - `create_issue()` | ||
| 599 | - `create_patch()` | ||
| 600 | - `create_pull_request()` | ||
| 601 | - `create_comment()` | ||
| 602 | - `create_status()` | ||
| 603 | |||
| 604 | 2. Implement test orchestration helpers: | ||
| 605 | - `send_and_verify_stored()` | ||
| 606 | - `send_and_verify_rejected()` | ||
| 607 | - `extract_repo_id()` | ||
| 608 | - `build_repo_atag()` | ||
| 609 | |||
| 610 | **Acceptance Criteria:** | ||
| 611 | - All helper functions documented | ||
| 612 | - Unit tests for helpers | ||
| 613 | - Functions follow nostr-sdk 0.43 API | ||
| 614 | |||
| 615 | ### Phase 3: Core Event Type Tests (Priority: HIGH) | ||
| 616 | **Goal:** Implement tests for issues, patches, PRs | ||
| 617 | **Duration:** 1.5 days | ||
| 618 | |||
| 619 | **Tasks:** | ||
| 620 | 1. Implement in `issues.rs`: | ||
| 621 | - `test_accept_issue_for_repo` | ||
| 622 | - `test_reject_issue_for_nonexistent_repo` | ||
| 623 | |||
| 624 | 2. Implement in `patches.rs`: | ||
| 625 | - `test_accept_patch_for_repo` | ||
| 626 | - `test_accept_patch_series_threading` | ||
| 627 | |||
| 628 | 3. Implement in `pull_requests.rs`: | ||
| 629 | - `test_accept_pull_request_for_repo` | ||
| 630 | - `test_accept_pr_update` | ||
| 631 | |||
| 632 | **Acceptance Criteria:** | ||
| 633 | - All tests pass against ngit-relay | ||
| 634 | - Proper event tagging | ||
| 635 | - Clear test documentation | ||
| 636 | |||
| 637 | ### Phase 4: Comment Threading (Priority: HIGH) | ||
| 638 | **Goal:** NIP-22 comment support in `comments.rs` | ||
| 639 | **Duration:** 1 day | ||
| 640 | |||
| 641 | **Tasks:** | ||
| 642 | 1. Implement comment tests: | ||
| 643 | - `test_accept_reply_to_issue` | ||
| 644 | - `test_accept_nested_comment_thread` | ||
| 645 | - `test_accept_comment_on_patch` | ||
| 646 | - `test_accept_comment_on_pr` | ||
| 647 | |||
| 648 | **Acceptance Criteria:** | ||
| 649 | - Multi-level threading works | ||
| 650 | - Uppercase/lowercase tag handling correct | ||
| 651 | - All comment tests pass | ||
| 652 | |||
| 653 | ### Phase 5: Status Updates and Text Notes (Priority: MEDIUM) | ||
| 654 | **Goal:** Complete remaining event types | ||
| 655 | **Duration:** 1 day | ||
| 656 | |||
| 657 | **Tasks:** | ||
| 658 | 1. Implement in `status_updates.rs`: | ||
| 659 | - `test_accept_status_for_issue` | ||
| 660 | |||
| 661 | 2. Implement in `text_notes.rs`: | ||
| 662 | - `test_accept_kind1_quoted_by_issue` | ||
| 663 | |||
| 664 | **Acceptance Criteria:** | ||
| 665 | - Status updates work correctly | ||
| 666 | - Kind 1 quote references validated | ||
| 667 | - All tests documented | ||
| 668 | |||
| 669 | ### Phase 6: Documentation and Finalization (Priority: HIGH) | ||
| 670 | **Goal:** Complete documentation and code review | ||
| 671 | **Duration:** 0.5 days | ||
| 672 | |||
| 673 | **Tasks:** | ||
| 674 | 1. Add comprehensive doc comments to all modules | ||
| 675 | 2. Create migration guide from old structure | ||
| 676 | 3. Update main README with new structure | ||
| 677 | 4. Code review and refactoring | ||
| 678 | 5. Run full test suite verification | ||
| 679 | |||
| 680 | **Acceptance Criteria:** | ||
| 681 | - All modules documented | ||
| 682 | - Clear organization | ||
| 683 | - No compiler warnings | ||
| 684 | - All tests pass | ||
| 685 | |||
| 686 | ## 6. Edge Cases and Considerations | ||
| 687 | |||
| 688 | ### 6.1 Potential Edge Cases | ||
| 689 | |||
| 690 | 1. **Event Arrival Order:** | ||
| 691 | - Issue arrives before repo announcement | ||
| 692 | - Comment arrives before target event | ||
| 693 | - **Mitigation:** Test both orders, document relay behavior | ||
| 694 | |||
| 695 | 2. **Reference Ambiguity:** | ||
| 696 | - Multiple `a` tags to different repos | ||
| 697 | - Conflicting `e` tags | ||
| 698 | - **Mitigation:** Document which reference takes precedence | ||
| 699 | |||
| 700 | 3. **Deleted Events:** | ||
| 701 | - Event references something that gets deleted | ||
| 702 | - **Mitigation:** Test and document behavior | ||
| 703 | |||
| 704 | 4. **Malformed Tags:** | ||
| 705 | - Invalid `a` tag format | ||
| 706 | - Missing required tag components | ||
| 707 | - **Mitigation:** Test rejection with clear errors | ||
| 708 | |||
| 709 | 5. **Threading Depth:** | ||
| 710 | - Very deep reply chains (100+ levels) | ||
| 711 | - **Mitigation:** Set reasonable limits, test performance | ||
| 712 | |||
| 713 | 6. **Circular References:** | ||
| 714 | - A references B, B references A | ||
| 715 | - **Mitigation:** Prevent infinite loops, document handling | ||
| 716 | |||
| 717 | ### 6.2 Performance Considerations | ||
| 718 | |||
| 719 | 1. **Query Efficiency:** | ||
| 720 | - Use specific filters (kind + author) | ||
| 721 | - Avoid full relay scans | ||
| 722 | - Timeout after 5 seconds | ||
| 723 | |||
| 724 | 2. **Event Batching:** | ||
| 725 | - Send multiple events efficiently | ||
| 726 | - Wait between sends (100ms) for propagation | ||
| 727 | |||
| 728 | 3. **Cleanup:** | ||
| 729 | - All events have audit tags for cleanup | ||
| 730 | - Use `run_id` for isolation | ||
| 731 | |||
| 732 | ### 6.3 Test Isolation Requirements | ||
| 733 | |||
| 734 | 1. **Unique Identifiers:** | ||
| 735 | - Use UUIDs for repo IDs | ||
| 736 | - Avoid collisions between test runs | ||
| 737 | |||
| 738 | 2. **Audit Tags:** | ||
| 739 | - Automatic via `AuditClient::event_builder()` | ||
| 740 | - Enable production cleanup | ||
| 741 | |||
| 742 | 3. **Relay State:** | ||
| 743 | - Assume shared relay (ngit-relay) | ||
| 744 | - Don't depend on empty state | ||
| 745 | |||
| 746 | ## 7. Implementation Guidelines | ||
| 747 | |||
| 748 | ### 7.1 Code Style | ||
| 749 | |||
| 750 | Follow existing patterns in [`grasp01_nostr_relay.rs`](grasp-audit/src/specs/grasp01_nostr_relay.rs): | ||
| 751 | |||
| 752 | ```rust | ||
| 753 | /// Test: <description> | ||
| 754 | /// | ||
| 755 | /// Spec: Line X of ../grasp/01.md | ||
| 756 | /// Requirement: <exact or paraphrased requirement> | ||
| 757 | async fn test_name(client: &AuditClient) -> TestResult { | ||
| 758 | TestResult::new( | ||
| 759 | "test_name", | ||
| 760 | "GRASP-01:nostr-relay:X", | ||
| 761 | "Human-readable requirement description", | ||
| 762 | ) | ||
| 763 | .run(|| async { | ||
| 764 | // Test implementation | ||
| 765 | Ok(()) | ||
| 766 | }) | ||
| 767 | .await | ||
| 768 | } | ||
| 769 | ``` | ||
| 770 | |||
| 771 | ### 7.2 nostr-sdk 0.43 API Usage | ||
| 772 | |||
| 773 | **Field Access (NOT method calls):** | ||
| 774 | ```rust | ||
| 775 | event.id // ✅ Correct | ||
| 776 | event.tags // ✅ Correct | ||
| 777 | event.tags.iter() // ✅ Correct | ||
| 778 | |||
| 779 | event.id() // ❌ Wrong (0.35 API) | ||
| 780 | ``` | ||
| 781 | |||
| 782 | **Tag Construction:** | ||
| 783 | ```rust | ||
| 784 | Tag::custom(TagKind::custom("a"), vec!["30617:pubkey:repo-id"]) // ✅ | ||
| 785 | Tag::identifier("repo-id") // ✅ | ||
| 786 | Tag::from_standardized(TagStandard::PublicKey { ... }) // ✅ | ||
| 787 | ``` | ||
| 788 | |||
| 789 | **Event Building:** | ||
| 790 | ```rust | ||
| 791 | client.event_builder(kind, content) | ||
| 792 | .tag(tag1) | ||
| 793 | .tag(tag2) | ||
| 794 | .build(client.keys())? | ||
| 795 | ``` | ||
| 796 | |||
| 797 | ### 7.3 Test Naming Convention | ||
| 798 | |||
| 799 | Pattern: `test_{action}_{subject}_{condition}` | ||
| 800 | |||
| 801 | Examples: | ||
| 802 | - `test_accept_issue_for_repo` (positive) | ||
| 803 | - `test_reject_orphan_issue` (negative) | ||
| 804 | - `test_accept_nested_comment_thread` (complex) | ||
| 805 | |||
| 806 | ### 7.4 Error Handling | ||
| 807 | |||
| 808 | ```rust | ||
| 809 | .run(|| async { | ||
| 810 | // Create events | ||
| 811 | let repo = client.create_repo_announcement("test").await | ||
| 812 | .map_err(|e| format!("Failed to create repo: {}", e))?; | ||
| 813 | |||
| 814 | // Send events | ||
| 815 | client.send_event(repo.clone()).await | ||
| 816 | .map_err(|e| format!("Failed to send to relay: {}", e))?; | ||
| 817 | |||
| 818 | // Verify results | ||
| 819 | let events = client.query(filter).await | ||
| 820 | .map_err(|e| format!("Failed to query: {}", e))?; | ||
| 821 | |||
| 822 | if events.is_empty() { | ||
| 823 | return Err("Event not stored".to_string()); | ||
| 824 | } | ||
| 825 | |||
| 826 | Ok(()) | ||
| 827 | }) | ||
| 828 | ``` | ||
| 829 | |||
| 830 | ## 8. Test Data Patterns | ||
| 831 | |||
| 832 | ### 8.1 Sample Event IDs | ||
| 833 | Use realistic hex event IDs: | ||
| 834 | ```rust | ||
| 835 | "abc123def456789012345678901234567890abcd" // 40 hex characters | ||
| 836 | ``` | ||
| 837 | |||
| 838 | ### 8.2 Sample Pubkeys | ||
| 839 | Use proper npub format: | ||
| 840 | ```rust | ||
| 841 | client.public_key().to_bech32()? // Real key from client | ||
| 842 | ``` | ||
| 843 | |||
| 844 | ### 8.3 Sample Repo IDs | ||
| 845 | Use test name + UUID: | ||
| 846 | ```rust | ||
| 847 | format!("test-{}-{}", test_name, Timestamp::now().as_u64()) | ||
| 848 | ``` | ||
| 849 | |||
| 850 | ## 9. Acceptance Criteria | ||
| 851 | |||
| 852 | ### 9.1 Code Quality | ||
| 853 | |||
| 854 | - ✅ All functions have doc comments | ||
| 855 | - ✅ No compiler warnings | ||
| 856 | - ✅ Follows existing code patterns | ||
| 857 | - ✅ Uses nostr-sdk 0.43 API correctly | ||
| 858 | - ✅ Proper error messages | ||
| 859 | |||
| 860 | ### 9.2 Test Coverage | ||
| 861 | |||
| 862 | - ✅ All 7 test stubs implemented | ||
| 863 | - ✅ All NIP-34 event types covered | ||
| 864 | - ✅ All reference tag types tested | ||
| 865 | - ✅ Both positive and negative cases | ||
| 866 | - ✅ Edge cases documented | ||
| 867 | |||
| 868 | ### 9.3 Passing Tests | ||
| 869 | |||
| 870 | - ✅ All tests pass against ngit-relay | ||
| 871 | - ✅ Tests properly isolated | ||
| 872 | - ✅ No flaky tests | ||
| 873 | - ✅ Clear failure messages | ||
| 874 | |||
| 875 | ## 10. References | ||
| 876 | |||
| 877 | - **NIP-34:** `/persistent/dcdev/clones/nips/34.md` (Git Stuff) | ||
| 878 | - **NIP-10:** `/persistent/dcdev/clones/nips/10.md` (Threading) | ||
| 879 | - **NIP-22:** `/persistent/dcdev/clones/nips/22.md` (Comments) | ||
| 880 | - **Current Implementation:** [`grasp01_nostr_relay.rs:29-36`](grasp-audit/src/specs/grasp01_nostr_relay.rs:29-36) | ||
| 881 | - **Client Helpers:** [`client.rs:193-235`](grasp-audit/src/client.rs:193-235) | ||
| 882 | - **AGENTS.md:** Code patterns and testing guidelines | ||
| 883 | |||
| 884 | ## 11. Next Steps | ||
| 885 | |||
| 886 | 1. **Review this design document with user** | ||
| 887 | 2. **Get approval or iterate on design** | ||
| 888 | 3. **Switch to Code mode for implementation** | ||
| 889 | 4. **Implement Phase 1 (Foundation)** | ||
| 890 | 5. **Test against ngit-relay** | ||
| 891 | 6. **Iterate through remaining phases** | ||
| 892 | |||
| 893 | ## Appendix A: Test Flow Diagram | ||
| 894 | |||
| 895 | ``` | ||
| 896 | Event Reference Testing Flow | ||
| 897 | ============================ | ||
| 898 | |||
| 899 | ┌─────────────────────────────────────────────────┐ | ||
| 900 | │ Setup: Create Repo Announcement │ | ||
| 901 | │ - Send kind 30617 with clone/relays tags │ | ||
| 902 | │ - Verify acceptance and storage │ | ||
| 903 | └────────────────┬────────────────────────────────┘ | ||
| 904 | │ | ||
| 905 | ▼ | ||
| 906 | ┌─────────────────────────────────────────────────┐ | ||
| 907 | │ Test 1: Issues (kind 1621) │ | ||
| 908 | │ ┌─────────────────────────────────────────────┐│ | ||
| 909 | │ │ → Create issue with 'a' tag to repo ││ | ||
| 910 | │ │ → Send to relay ││ | ||
| 911 | │ │ → Query back ││ | ||
| 912 | │ │ → Verify stored ││ | ||
| 913 | │ └─────────────────────────────────────────────┘│ | ||
| 914 | └────────────────┬────────────────────────────────┘ | ||
| 915 | │ | ||
| 916 | ▼ | ||
| 917 | ┌─────────────────────────────────────────────────┐ | ||
| 918 | │ Test 2: Patches (kind 1617) │ | ||
| 919 | │ ┌─────────────────────────────────────────────┐│ | ||
| 920 | │ │ → Create patch with 'a' tag to repo ││ | ||
| 921 | │ │ → Optionally thread with 'e' tag ││ | ||
| 922 | │ │ → Send and verify ││ | ||
| 923 | │ └─────────────────────────────────────────────┘│ | ||
| 924 | └────────────────┬────────────────────────────────┘ | ||
| 925 | │ | ||
| 926 | ▼ | ||
| 927 | ┌─────────────────────────────────────────────────┐ | ||
| 928 | │ Test 3: Pull Requests (kind 1618) │ | ||
| 929 | │ ┌─────────────────────────────────────────────┐│ | ||
| 930 | │ │ → Create PR with 'a' tag and 'c' commit ││ | ||
| 931 | │ │ → Send and verify ││ | ||
| 932 | │ └─────────────────────────────────────────────┘│ | ||
| 933 | └────────────────┬────────────────────────────────┘ | ||
| 934 | │ | ||
| 935 | ▼ | ||
| 936 | ┌─────────────────────────────────────────────────┐ | ||
| 937 | │ Test 4: Comments (kind 1111 - NIP-22) │ | ||
| 938 | │ ┌─────────────────────────────────────────────┐│ | ||
| 939 | │ │ Top-level: ││ | ||
| 940 | │ │ E/K/P → Issue ││ | ||
| 941 | │ │ e/k/p → Issue (same as root) ││ | ||
| 942 | │ │ Nested: ││ | ||
| 943 | │ │ E/K/P → Issue (unchanged) ││ | ||
| 944 | │ │ e/k/p → Parent Comment ││ | ||
| 945 | │ └─────────────────────────────────────────────┘│ | ||
| 946 | └────────────────┬────────────────────────────────┘ | ||
| 947 | │ | ||
| 948 | ▼ | ||
| 949 | ┌─────────────────────────────────────────────────┐ | ||
| 950 | │ Test 5: Status Updates (kinds 1630-1633) │ | ||
| 951 | │ ┌─────────────────────────────────────────────┐│ | ||
| 952 | │ │ → Create status with 'e' tag to issue/PR ││ | ||
| 953 | │ │ → Test state transitions ││ | ||
| 954 | │ └─────────────────────────────────────────────┘│ | ||
| 955 | └────────────────┬────────────────────────────────┘ | ||
| 956 | │ | ||
| 957 | ▼ | ||
| 958 | ┌─────────────────────────────────────────────────┐ | ||
| 959 | │ Test 6: Negative Cases │ | ||
| 960 | │ ┌─────────────────────────────────────────────┐│ | ||
| 961 | │ │ → Orphan events (no references) ││ | ||
| 962 | │ │ → Invalid references ││ | ||
| 963 | │ │ → Verify rejection ││ | ||
| 964 | │ └──────────────────────────── ────────────────┘│ | ||
| 965 | └─────────────────────────────────────────────────┘ | ||
| 966 | ``` | ||
| 967 | |||
| 968 | ## Appendix B: Helper Function Dependency Graph | ||
| 969 | |||
| 970 | ``` | ||
| 971 | Helper Functions | ||
| 972 | ================ | ||
| 973 | |||
| 974 | create_repo_announcement() (exists in AuditClient) | ||
| 975 | │ | ||
| 976 | ├─→ extract_repo_id() | ||
| 977 | └─→ build_repo_atag() | ||
| 978 | │ | ||
| 979 | ├─→ create_issue() | ||
| 980 | ├─→ create_patch() | ||
| 981 | ├─→ create_pull_request() | ||
| 982 | ├─→ create_comment() | ||
| 983 | └─→ create_status() | ||
| 984 | │ | ||
| 985 | ├─→ send_and_verify_stored() | ||
| 986 | └─→ send_and_verify_rejected() | ||
| 987 | ``` | ||