Skip to content

Commit 9a82b47

Browse files
committed
docs(v1.3): re-audit milestone — all 16/16 requirements satisfied after Phase 18 gap closure
1 parent c0d0384 commit 9a82b47

2 files changed

Lines changed: 55 additions & 71 deletions

File tree

.planning/REQUIREMENTS.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Requirements for per-service P2P targeting. Each maps to roadmap phases.
1111

1212
- [x] **SUB-01**: Node maintains a per-service peer subscription map (`service_id → Set<PeerPubkey>`) updated from announcement messages
1313
- [x] **SUB-02**: Node maintains a reverse index (`PeerPubkey → Set<service_id>`) for efficient cleanup on peer disconnect
14-
- [ ] **SUB-03**: When a peer disconnects, all its subscription entries are removed from both maps
14+
- [x] **SUB-03**: When a peer disconnects, all its subscription entries are removed from both maps
1515

1616
### Announcement Protocol
1717

@@ -36,7 +36,7 @@ Requirements for per-service P2P targeting. Each maps to roadmap phases.
3636

3737
- [x] **COMPAT-01**: Existing secp256k1 e2e tests pass unchanged
3838
- [x] **COMPAT-02**: Existing BLS e2e tests pass unchanged
39-
- [ ] **COMPAT-03**: Nodes without v1.3 targeting are treated as subscribed-to-all (backward compatible during rolling updates)
39+
- [x] **COMPAT-03**: Nodes without v1.3 targeting are treated as subscribed-to-all (backward compatible during rolling updates)
4040

4141
## Future Requirements
4242

@@ -72,7 +72,7 @@ Which phases cover which requirements. Updated during roadmap creation.
7272
|-------------|-------|--------|
7373
| SUB-01 | Phase 14 | Complete |
7474
| SUB-02 | Phase 14 | Complete |
75-
| SUB-03 | Phase 18 | Pending |
75+
| SUB-03 | Phase 18 | Complete |
7676
| ANN-01 | Phase 15 | Complete |
7777
| ANN-02 | Phase 15 | Complete |
7878
| ANN-03 | Phase 15 | Complete |
@@ -85,7 +85,7 @@ Which phases cover which requirements. Updated during roadmap creation.
8585
| OBS-01 | Phase 17 | Complete |
8686
| COMPAT-01 | Phase 16 | Complete |
8787
| COMPAT-02 | Phase 16 | Complete |
88-
| COMPAT-03 | Phase 18 | Pending |
88+
| COMPAT-03 | Phase 18 | Complete |
8989

9090
**Coverage:**
9191
- v1.3 requirements: 16 total

.planning/v1.3-MILESTONE-AUDIT.md

Lines changed: 51 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,15 @@
11
---
22
milestone: v1.3
33
audited: 2026-04-04
4-
status: gaps_found
4+
status: tech_debt
55
scores:
6-
requirements: 14/16
7-
phases: 4/4
8-
integration: 14/16
6+
requirements: 16/16
7+
phases: 5/5
8+
integration: 16/16
99
flows: 1/1
1010
gaps:
11-
requirements:
12-
- id: "SUB-03"
13-
status: "partial"
14-
phase: "Phase 14"
15-
claimed_by_plans: ["14-01-PLAN.md"]
16-
completed_by_plans: ["14-01-SUMMARY.md"]
17-
verification_status: "missing"
18-
evidence: "remove_peer() is implemented and unit-tested but never triggered by peer disconnect in either bridge loop. No peer-disconnect event arm exists in tokio::select! loops. Stale entries persist until heartbeat replace-not-merge (2s interval) or node restart."
19-
- id: "COMPAT-03"
20-
status: "partial"
21-
phase: "Phase 15"
22-
claimed_by_plans: ["15-01-PLAN.md", "15-02-PLAN.md"]
23-
completed_by_plans: ["15-01-SUMMARY.md", "15-02-SUMMARY.md"]
24-
verification_status: "missing"
25-
evidence: "has_announced() is defined at p2p.rs:422 but never called in production bridge loops (clippy dead_code). get_recipients() falls back to Recipients::All at service level (zero subscribers), but excludes pre-v1.3 peers when at least one v1.3 peer subscribes to the same service."
26-
integration:
27-
- from: "Phase 14 (PeerSubscriptionMap.remove_peer)"
28-
to: "Bridge loops (disconnect handling)"
29-
issue: "remove_peer() has no caller on peer disconnect — no disconnect event arm in either bridge loop"
30-
affected_requirements: ["SUB-03"]
31-
- from: "Phase 15 (has_announced)"
32-
to: "Phase 16 (get_recipients)"
33-
issue: "has_announced() is dead production code — get_recipients() does not check per-peer announcement status"
34-
affected_requirements: ["COMPAT-03"]
11+
requirements: []
12+
integration: []
3513
flows: []
3614
tech_debt:
3715
- phase: 14-subscription-data-structures
@@ -42,7 +20,6 @@ tech_debt:
4220
items:
4321
- "No VERIFICATION.md created during execution"
4422
- "No VALIDATION.md sign-off (nyquist_compliant: false)"
45-
- "has_announced() dead_code warning (test-only method)"
4623
- phase: 16-targeted-delivery
4724
items:
4825
- "No VERIFICATION.md created during execution"
@@ -53,26 +30,30 @@ tech_debt:
5330
- "No VERIFICATION.md created during execution"
5431
- "No VALIDATION.md sign-off (nyquist_compliant: false)"
5532
- "peer_subscriptions data not rendered in P2pPage.tsx UI (display gap only — API requirement OBS-01 satisfied)"
33+
- phase: 18-peer-state-correctness
34+
items:
35+
- "No VERIFICATION.md created during execution"
36+
- "No VALIDATION.md sign-off (nyquist_compliant: false)"
5637
nyquist:
5738
compliant_phases: 0
5839
partial_phases: 0
59-
missing_phases: 4
60-
overall: "All 4 phases have VALIDATION.md with nyquist_compliant: false and wave_0_complete: false"
40+
missing_phases: 5
41+
overall: "All 5 phases have VALIDATION.md with nyquist_compliant: false and wave_0_complete: false"
6142
---
6243

6344
# Milestone v1.3 Audit: Per-Service P2P Targeting
6445

65-
**Audited:** 2026-04-04
66-
**Status:** gaps_found (2 partial requirements)
67-
**Scores:** 14/16 requirements satisfied, 4/4 phases complete, 14/16 integration paths wired, 1/1 E2E flows complete
46+
**Audited:** 2026-04-04 (re-audit after Phase 18 gap closure)
47+
**Status:** tech_debt (all requirements satisfied, no critical gaps)
48+
**Scores:** 16/16 requirements satisfied, 5/5 phases complete, 16/16 integration paths wired, 1/1 E2E flows complete
6849

6950
## Requirements Coverage (3-Source Cross-Reference)
7051

7152
| REQ-ID | Description | Phase | SUMMARY frontmatter | REQUIREMENTS.md | Integration | Final Status |
7253
|--------|-------------|-------|--------------------|-----------------|----|---|
7354
| SUB-01 | Per-service peer subscription map | 14 | listed (14-01) | [x] | WIRED | satisfied |
7455
| SUB-02 | Reverse index for cleanup | 14 | listed (14-01) | [x] | WIRED | satisfied |
75-
| SUB-03 | Peer disconnect removes subscriptions | 14 | listed (14-01) | [x] | UNWIRED | **partial** |
56+
| SUB-03 | Peer disconnect removes subscriptions | 18 | listed (18-01) | [x] | WIRED | **satisfied** |
7657
| ANN-01 | Subscribe announcement on service add | 15 | listed (15-02) | [x] | WIRED | satisfied |
7758
| ANN-02 | Unsubscribe announcement on service remove | 15 | listed (15-02) | [x] | WIRED | satisfied |
7859
| ANN-03 | Subscription piggybacked on heartbeats | 15 | listed (15-01, 15-02) | [x] | WIRED | satisfied |
@@ -82,51 +63,52 @@ nyquist:
8263
| TGT-02 | Empty subscriber set falls back to All | 16 | listed (16-01, 16-02) | [x] | WIRED | satisfied |
8364
| TGT-03 | Engine channel stays Recipients::All | 16 | listed (16-01, 16-02) | [x] | WIRED | satisfied |
8465
| TGT-04 | Retry queue re-resolves recipients | 16 | listed (16-01, 16-02) | [x] | WIRED | satisfied |
85-
| OBS-01 | /p2p/status includes peer counts | 17 | provides (17-01) | [x] | WIRED | satisfied |
66+
| OBS-01 | /p2p/status includes peer counts | 17 | listed (17-01) | [x] | WIRED | satisfied |
8667
| COMPAT-01 | secp256k1 e2e tests pass | 16 | listed (16-01, 16-02) | [x] | WIRED | satisfied |
8768
| COMPAT-02 | BLS e2e tests pass | 16 | listed (16-02) | [x] | WIRED | satisfied |
88-
| COMPAT-03 | Pre-v1.3 nodes treated as subscribed-to-all | 15 | listed (15-01, 15-02) | [x] | PARTIAL | **partial** |
69+
| COMPAT-03 | Pre-v1.3 nodes treated as subscribed-to-all | 18 | listed (18-01) | [x] | WIRED | **satisfied** |
8970

90-
## Gap Details
71+
## Gap Closure: Phase 18
9172

92-
### SUB-03: Peer disconnect cleanup not triggered (medium severity)
73+
Phase 18 was created to close the two gaps identified in the initial audit:
9374

94-
**What:** `PeerSubscriptionMap.remove_peer()` is implemented at `p2p.rs:407` and fully unit-tested, but never called from the bridge loops when a peer disconnects. Neither `run_lookup_network` nor `run_discovery_network` has a peer-disconnect event arm in their `tokio::select!` loop.
75+
### SUB-03: Peer disconnect cleanup — CLOSED
9576

96-
**Impact:** Stale subscription entries accumulate for disconnected peers. `get_recipients()` may include departed peers in `Recipients::Some(...)`, causing sends to unreachable peers (silently dropped by commonware-p2p).
77+
**Prior gap:** `remove_peer()` was implemented and unit-tested but never triggered by peer disconnect. No disconnect event arm existed in either bridge loop.
9778

98-
**Mitigation:** Heartbeat replace-not-merge (every 2s) overwrites stale entries when a peer reconnects. For truly departed peers, entries persist until node restart. The Engine broadcast channel (channel 0) still delivers to all peers as catch-up reliability.
79+
**Fix (Phase 18):** Heartbeat-driven pruning via `tracked_peers().difference(&connected_peer_set)` in both bridge loops. On each 2-second heartbeat tick, peers tracked in `PeerSubscriptionMap` but absent from the connected peer set are pruned via `remove_peer()`. `known_peers` is also pruned to allow ANN-04 re-hello on reconnect.
9980

100-
**Root cause:** commonware-p2p does not expose a peer-disconnect callback/event channel — no mechanism to trigger `remove_peer()` directly.
81+
**Evidence:** `tracked_peers()` called at lines 1126 (lookup) and 1624 (discovery). `remove_peer(departed)` called at lines 1128 and 1626. 45/45 unit tests pass including `test_heartbeat_prune_departed_peer`.
10182

102-
### COMPAT-03: Per-peer backward compatibility incomplete (low severity)
83+
### COMPAT-03: Pre-v1.3 backward compatibility — CLOSED
10384

104-
**What:** `has_announced()` at `p2p.rs:422` was designed for per-peer COMPAT-03 checking but is never called in production code (dead_code warning). `get_recipients()` falls back to `Recipients::All` only when the service has zero subscribers — correct for the zero-subscriber case, but excludes pre-v1.3 peers when at least one v1.3 peer has subscribed to the same service.
85+
**Prior gap:** `has_announced()` was dead production code. `get_recipients()` excluded pre-v1.3 peers when at least one v1.3 peer subscribed to the same service.
10586

106-
**Impact:** During rolling upgrade with mixed v1.3 + pre-v1.3 operators, legacy peers are excluded from targeted delivery for services where at least one v1.3 peer has subscribed. Engine channel (channel 0) catch-up mitigates this by delivering via `Recipients::All` on the broadcast channel.
87+
**Fix (Phase 18):** `get_recipients()` now takes a `connected_peers: &HashSet<ed25519::PublicKey>` parameter. For each connected peer that has never announced (`!has_announced(peer)`), the peer is unconditionally included in the recipient set. All 6 production call sites pass `&connected_peer_set`.
10788

108-
**Fix path:** Call `has_announced(peer)` per connected peer in `get_recipients()` and include un-announced peers unconditionally in the recipient set.
89+
**Evidence:** `has_announced()` called at line 473 inside `get_recipients()`. No dead_code clippy warning. Tests `test_get_recipients_includes_unannounced_connected_peers` and `test_get_recipients_all_announced_no_legacy` verify behavior.
10990

11091
## Cross-Phase Integration
11192

112-
### E2E Flow: Service → Subscription → Targeting → Observability
113-
114-
| Step | Status |
115-
|------|--------|
116-
| Service add → AggregatorCommand::SubscribeService | CONNECTED |
117-
| Subscribe arm → SubscriptionAnnouncement broadcast via direct_sender | CONNECTED (ANN-01) |
118-
| Remote peer → is_subscription_announcement() → handle_announcement() | CONNECTED (SUB-01, ANN-05) |
119-
| Heartbeat → full_state=true → set_peer_subscriptions() | CONNECTED (ANN-03) |
120-
| New peer → known_peers check → hello via Recipients::One | CONNECTED (ANN-04) |
121-
| Publish → get_recipients() → targeted send | CONNECTED (TGT-01, TGT-02) |
122-
| Retry drain → re-resolve get_recipients() | CONNECTED (TGT-04) |
123-
| mailbox.broadcast() → Recipients::All (unchanged) | CONNECTED (TGT-03) |
124-
| GetStatus → peer_subscription_counts() → P2pStatus | CONNECTED (OBS-01) |
125-
| /p2p/status → JSON response with peer_subscriptions | CONNECTED |
93+
### E2E Flow: Service → Subscription → Targeting → Observability → Cleanup
94+
95+
| Step | Status | Requirements |
96+
|------|--------|-------------|
97+
| Service add → AggregatorCommand::SubscribeService | CONNECTED ||
98+
| Subscribe arm → SubscriptionAnnouncement broadcast | CONNECTED | ANN-01 |
99+
| Remote peer → is_subscription_announcement() → handle_announcement() | CONNECTED | SUB-01, ANN-05 |
100+
| Heartbeat → full_state=true → set_peer_subscriptions() | CONNECTED | ANN-03 |
101+
| New peer → known_peers check → hello via Recipients::One | CONNECTED | ANN-04 |
102+
| Publish → get_recipients(svc, connected) → targeted send | CONNECTED | TGT-01, TGT-02, COMPAT-03 |
103+
| Retry drain → re-resolve get_recipients() | CONNECTED | TGT-04 |
104+
| mailbox.broadcast() → Recipients::All (unchanged) | CONNECTED | TGT-03 |
105+
| Heartbeat tick → tracked_peers().difference() → prune departed | CONNECTED | SUB-03 |
106+
| GetStatus → peer_subscription_counts() → P2pStatus | CONNECTED | OBS-01 |
107+
| /p2p/status → JSON response with peer_subscriptions | CONNECTED ||
126108

127109
### Orphaned Code
128110

129-
- `has_announced()` — defined, tested, never called in production (dead_code)
111+
None. `has_announced()` dead_code resolved by Phase 18.
130112

131113
## Nyquist Compliance
132114

@@ -136,18 +118,20 @@ nyquist:
136118
| 15 | exists | false | `/gsd:validate-phase 15` |
137119
| 16 | exists | false | `/gsd:validate-phase 16` |
138120
| 17 | exists | false | `/gsd:validate-phase 17` |
121+
| 18 | exists | false | `/gsd:validate-phase 18` |
139122

140-
All 4 phases have VALIDATION.md but none are signed off (nyquist_compliant: false, wave_0_complete: false). Validation strategies were defined but not executed during phase work.
123+
All 5 phases have VALIDATION.md but none are signed off (nyquist_compliant: false, wave_0_complete: false).
141124

142125
## Tech Debt Summary
143126

144127
| Phase | Items |
145128
|-------|-------|
146-
| All phases | No VERIFICATION.md files (process gap) |
129+
| All phases | No VERIFICATION.md files (verifier disabled in config) |
147130
| All phases | VALIDATION.md unsigned (nyquist_compliant: false) |
148-
| Phase 15 | has_announced() dead_code warning |
149131
| Phase 16 | Pre-existing clippy warnings (too_many_arguments, let_underscore_future, etc.) |
150-
| Phase 17 | peer_subscriptions not rendered in Tauri P2P dashboard UI |
132+
| Phase 17 | peer_subscriptions not rendered in Tauri P2P dashboard UI (API satisfied) |
133+
134+
### Total: 4 distinct items across 5 phases
151135

152136
---
153-
*Audit generated: 2026-04-04*
137+
*Re-audit generated: 2026-04-04 after Phase 18 gap closure*

0 commit comments

Comments
 (0)