docs(archreview): TST-04 governance — Phase 3 done, Phase 4 scoped, Phase 5 deferred

Resolve the session-resilience epic's shipped-vs-planned entanglement:

- Phase 3 (reconnect) finished: Task 13 = TST-02 (owner-scoped attach, P0),
  Task 15 = TST-01 (reconnect integration test), Task 14 = CLI-15 for 4/5
  clients (Java pending, windev batch).
- Phase 4 (per-session dashboard ACL) scoped as TST-15; the open Viewer-default
  decision is settled: admin-sees-all, Viewer strict per owned/granted session
  (matches TST-02's gRPC owner binding).
- Phase 5 (orphan-worker reattach) marked DEFERRED, not planned. The
  EnableOrphanReattach flag does not exist and must not be referenced as if it
  does. The CLAUDE.md "gateway restart does not reattach orphan workers"
  invariant stands.

Updates oldtasks.md, the tasks.json mirror (statuses + governance note), and the
CLAUDE.md reconnect paragraph (clients now consume ReplayGap; reattach deferred).

Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
This commit is contained in:
Joseph Doherty
2026-07-09 16:33:25 -04:00
parent ef498c80c2
commit ed3c6c61c5
3 changed files with 67 additions and 39 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ powershell -ExecutionPolicy Bypass -File scripts/run-client-e2e-tests.ps1
- **Style guides** in `docs/style-guides/` are authoritative. Follow `CSharpStyleGuide.md` for gateway/worker/.NET-client code: file-scoped namespaces, `sealed` by default, `Async` suffix on Task-returning methods, MXAccess-aligned names (`MxStatusProxy`, `ServerHandle`, `ItemHandle`, `HResult`). - **Style guides** in `docs/style-guides/` are authoritative. Follow `CSharpStyleGuide.md` for gateway/worker/.NET-client code: file-scoped namespaces, `sealed` by default, `Async` suffix on Task-returning methods, MXAccess-aligned names (`MxStatusProxy`, `ServerHandle`, `ItemHandle`, `HResult`).
- **MXAccess parity is the contract.** Don't "fix" surprising MXAccess behavior (e.g., `WriteSecured` failing before a value-bearing NMX body, distinct `OperationComplete` semantics, invalid-handle exceptions) unless the client explicitly opts into a non-parity mode. The installed MXAccess COM component is the baseline. - **MXAccess parity is the contract.** Don't "fix" surprising MXAccess behavior (e.g., `WriteSecured` failing before a value-bearing NMX body, distinct `OperationComplete` semantics, invalid-handle exceptions) unless the client explicitly opts into a non-parity mode. The installed MXAccess COM component is the baseline.
- **Don't synthesize events.** The gateway forwards only events the worker emits; it never invents `OperationComplete` from write completion or command replies. - **Don't synthesize events.** The gateway forwards only events the worker emits; it never invents `OperationComplete` from write completion or command replies.
- **One worker per session** (invariant). Multi-subscriber event fan-out and reconnect-with-replay have shipped and are config-gated: `AllowMultipleEventSubscribers` (default `false`) enables fan-out up to `MaxEventSubscribersPerSession` (default `8`); `DetachGraceSeconds` (default `30`) retains a session after its last subscriber drops so clients can reconnect; `ReplayBufferCapacity` / `ReplayRetentionSeconds` control how much event history the replay ring keeps. Default config is single-subscriber (`AllowMultipleEventSubscribers` off), but detach-grace and replay retention are **on** by default (`DetachGraceSeconds=30`, `ReplayBufferCapacity=1024`, `ReplayRetentionSeconds=300`): a detached session is retained for 30 s and recent events are buffered for reconnect. See `docs/DesignDecisions.md` and `docs/Sessions.md`. - **One worker per session** (invariant). Multi-subscriber event fan-out and reconnect-with-replay have shipped and are config-gated: `AllowMultipleEventSubscribers` (default `false`) enables fan-out up to `MaxEventSubscribersPerSession` (default `8`); `DetachGraceSeconds` (default `30`) retains a session after its last subscriber drops so clients can reconnect; `ReplayBufferCapacity` / `ReplayRetentionSeconds` control how much event history the replay ring keeps. Default config is single-subscriber (`AllowMultipleEventSubscribers` off), but detach-grace and replay retention are **on** by default (`DetachGraceSeconds=30`, `ReplayBufferCapacity=1024`, `ReplayRetentionSeconds=300`): a detached session is retained for 30 s and recent events are buffered for reconnect. The reconnect protocol is consumable end-to-end: a resuming `StreamEvents` (via `after_worker_sequence`) that predates the retained ring gets a `ReplayGap` sentinel, and the official clients surface it as a typed signal (shipped for .NET/Go/Rust/Python; the Java client is the remaining one, batched to windev). Orphan-worker reattach after a gateway restart is **deferred, not planned** — see `oldtasks.md` (session-resilience epic Phase 5); the invariant on the next line stands. See `docs/DesignDecisions.md` and `docs/Sessions.md`.
- **Gateway restart does not reattach orphan workers.** The first version terminates orphaned workers on startup; do not design code paths that assume reattachment. - **Gateway restart does not reattach orphan workers.** The first version terminates orphaned workers on startup; do not design code paths that assume reattachment.
- **No Blazor UI component libraries.** Dashboard uses local Bootstrap CSS/JS only — do not introduce MudBlazor, Radzen, FluentUI, etc. - **No Blazor UI component libraries.** Dashboard uses local Bootstrap CSS/JS only — do not introduce MudBlazor, Radzen, FluentUI, etc.
- **Don't log secrets or full tag values by default.** API keys, passwords, `WriteSecured` payloads, and `AuthenticateUser` credentials must never reach logs. Value logging is opt-in and redacted. - **Don't log secrets or full tag values by default.** API keys, passwords, `WriteSecured` payloads, and `AuthenticateUser` credentials must never reach logs. Value logging is opt-in and redacted.
@@ -13,22 +13,23 @@
{"id": 117, "subject": "Task 10: Proto - ReplayGap signal", "status": "completed", "blockedBy": [116]}, {"id": 117, "subject": "Task 10: Proto - ReplayGap signal", "status": "completed", "blockedBy": [116]},
{"id": 118, "subject": "Task 11: Detach-grace session retention", "status": "completed", "blockedBy": [117]}, {"id": 118, "subject": "Task 11: Detach-grace session retention", "status": "completed", "blockedBy": [117]},
{"id": 119, "subject": "Task 12: Replay-on-reconnect + emit ReplayGap", "status": "completed", "blockedBy": [118, 110]}, {"id": 119, "subject": "Task 12: Replay-on-reconnect + emit ReplayGap", "status": "completed", "blockedBy": [118, 110]},
{"id": 120, "subject": "Task 13: Owner re-validation on reconnect", "status": "pending", "blockedBy": [119, 108]}, {"id": 120, "subject": "Task 13: Owner re-validation on reconnect", "status": "completed", "blockedBy": [119, 108], "note": "Shipped as archreview TST-02 (P0): session attach is owner-scoped."},
{"id": 121, "subject": "Task 14: Client ReplayGap handling - all 5 clients", "status": "pending", "blockedBy": [117]}, {"id": 121, "subject": "Task 14: Client ReplayGap handling - all 5 clients", "status": "in_progress", "blockedBy": [117], "note": "Shipped as archreview CLI-15 for .NET/Go/Rust/Python; Java pending (windev batch)."},
{"id": 122, "subject": "Task 15: Reconnect integration test (fake worker)", "status": "pending", "blockedBy": [119]}, {"id": 122, "subject": "Task 15: Reconnect integration test (fake worker)", "status": "completed", "blockedBy": [119], "note": "Shipped as archreview TST-01: GatewayEndToEndReconnectReplayTests."},
{"id": 123, "subject": "Task 16: gRPC session-owner gate + all-sessions admin scope", "status": "pending", "blockedBy": [116, 108]}, {"id": 123, "subject": "Task 16: gRPC session-owner gate + all-sessions admin scope", "status": "pending", "blockedBy": [116, 108], "note": "gRPC owner gate exists (TST-02); Phase 4 adds admin all-sessions scope. Tracked as TST-15."},
{"id": 124, "subject": "Task 17: Session Tag + dashboard group-to-tag config", "status": "pending", "blockedBy": [116]}, {"id": 124, "subject": "Task 17: Session Tag + dashboard group-to-tag config", "status": "pending", "blockedBy": [116], "note": "Phase 4 / TST-15."},
{"id": 125, "subject": "Task 18: EventsHub per-session ACL + hub-token tag claim", "status": "pending", "blockedBy": [124]}, {"id": 125, "subject": "Task 18: EventsHub per-session ACL + hub-token tag claim", "status": "pending", "blockedBy": [124], "note": "Phase 4 / TST-15. Decision settled: admin-sees-all, Viewer strict per owned/granted session."},
{"id": 126, "subject": "Task 19: ACL tests incl. live LDAP users", "status": "pending", "blockedBy": [125]}, {"id": 126, "subject": "Task 19: ACL tests incl. live LDAP users", "status": "pending", "blockedBy": [125], "note": "Phase 4 / TST-15."},
{"id": 127, "subject": "Task 20: Stable gateway-instance id + stable pipe naming", "status": "pending", "blockedBy": [126]}, {"id": 127, "subject": "Task 20: Stable gateway-instance id + stable pipe naming", "status": "deferred", "blockedBy": [126], "note": "Phase 5 deferred, not planned (TST-04)."},
{"id": 128, "subject": "Task 21: Adoption manifest store (SQLite)", "status": "pending", "blockedBy": [127]}, {"id": 128, "subject": "Task 21: Adoption manifest store (SQLite)", "status": "deferred", "blockedBy": [127], "note": "Phase 5 deferred, not planned (TST-04)."},
{"id": 129, "subject": "Task 22: Proto - worker adopt/reconnect frame", "status": "pending", "blockedBy": [128]}, {"id": 129, "subject": "Task 22: Proto - worker adopt/reconnect frame", "status": "deferred", "blockedBy": [128], "note": "Phase 5 deferred, not planned (TST-04)."},
{"id": 130, "subject": "Task 23: Worker phone-home reconnect loop + self-terminate", "status": "pending", "blockedBy": [129]}, {"id": 130, "subject": "Task 23: Worker phone-home reconnect loop + self-terminate", "status": "deferred", "blockedBy": [129], "note": "Phase 5 deferred, not planned (TST-04)."},
{"id": 131, "subject": "Task 24: Gateway adoption - re-open pipes, nonce-validate, reject impostors", "status": "pending", "blockedBy": [130]}, {"id": 131, "subject": "Task 24: Gateway adoption - re-open pipes, nonce-validate, reject impostors", "status": "deferred", "blockedBy": [130], "note": "Phase 5 deferred, not planned (TST-04)."},
{"id": 132, "subject": "Task 25: Resync adopted worker + ReplayGap to subscribers", "status": "pending", "blockedBy": [131, 119]}, {"id": 132, "subject": "Task 25: Resync adopted worker + ReplayGap to subscribers", "status": "deferred", "blockedBy": [131, 119], "note": "Phase 5 deferred, not planned (TST-04)."},
{"id": 133, "subject": "Task 26: EnableOrphanReattach flag (default off) + terminator fallback", "status": "pending", "blockedBy": [131]}, {"id": 133, "subject": "Task 26: EnableOrphanReattach flag (default off) + terminator fallback", "status": "deferred", "blockedBy": [131], "note": "Phase 5 deferred, not planned (TST-04). The EnableOrphanReattach flag does not exist yet."},
{"id": 134, "subject": "Task 27: Gateway-restart reattach round-trip (WINDEV + live worker)", "status": "pending", "blockedBy": [132, 133]}, {"id": 134, "subject": "Task 27: Gateway-restart reattach round-trip (WINDEV + live worker)", "status": "deferred", "blockedBy": [132, 133], "note": "Phase 5 deferred, not planned (TST-04)."},
{"id": 135, "subject": "Task 28: Documented-rule reversals + stillpending refresh", "status": "pending", "blockedBy": [134]} {"id": 135, "subject": "Task 28: Documented-rule reversals + stillpending refresh", "status": "deferred", "blockedBy": [134], "note": "Phase 5 deferred, not planned (TST-04)."}
], ],
"lastUpdated": "2026-06-15" "governance": "2026-07-09 (archreview TST-04): Phase 3 finished (Task 13=TST-02, Task 15=TST-01, Task 14=CLI-15 4/5, Java pending). Phase 4 (Tasks 16-19) scoped as TST-15, Viewer decision settled (admin-sees-all + Viewer strict). Phase 5 (Tasks 20-28) DEFERRED, not planned; EnableOrphanReattach does not exist.",
"lastUpdated": "2026-07-09"
} }
+48 -21
View File
@@ -15,8 +15,27 @@ The authoritative resume state lives in
## Status ## Status
**12 of 28 tasks complete** (Phases 12 + reconnect core of Phase 3). All completed **Governance update 2026-07-09 (archreview TST-04).** The epic is resolved into three
work is merged to `main` (commit `c446bef`, pushed to origin). decisions rather than one open backlog:
- **Phase 3 (reconnect) — finishing now, essentially complete.** Task 13 (owner
re-validation) shipped as archreview **TST-02** (P0, session attach is owner-scoped,
see CLAUDE.md Authentication). Task 15 (reconnect integration test) shipped as
**TST-01** (`GatewayEndToEndReconnectReplayTests`). Task 14 (client `ReplayGap`
handling) shipped as **CLI-15** for four of five clients (.NET/Go/Rust/Python);
the Java client is the only remainder, batched to the windev build host.
- **Phase 4 (per-session dashboard ACL) — scoped, not yet built.** Tracked as archreview
**TST-15**. The previously-open Viewer-default decision is **settled**: admin-sees-all,
Viewer strictly scoped to sessions it owns/is granted — matching TST-02's gRPC owner
binding for consistency.
- **Phase 5 (orphan-worker reattach) — DEFERRED, not planned.** It reverses the CLAUDE.md
invariant "Gateway restart does not reattach orphan workers" and adds an adoption manifest
store + worker phone-home protocol. It stays deferred unless a concrete requirement
appears. **The `EnableOrphanReattach` flag (Task 26) does not exist and must not be
referenced anywhere as if it does** until that task actually lands.
Original snapshot (historical): **12 of 28 tasks complete** (Phases 12 + reconnect core of
Phase 3), merged to `main` (commit `c446bef`).
### Completed — Phase 1 (Foundation) ### Completed — Phase 1 (Foundation)
- ✅ Task 1 (#108): Add OwnerKeyId to the session - ✅ Task 1 (#108): Add OwnerKeyId to the session
@@ -36,33 +55,41 @@ work is merged to `main` (commit `c446bef`, pushed to origin).
- ✅ Task 11 (#118): Detach-grace session retention - ✅ Task 11 (#118): Detach-grace session retention
- ✅ Task 12 (#119): Replay-on-reconnect + emit ReplayGap - ✅ Task 12 (#119): Replay-on-reconnect + emit ReplayGap
### Pending — Phase 3 finish ### Phase 3 finish — DONE (via archreview P0/P2)
- Task 13 (#120): Owner re-validation on reconnect — blockedBy 12, 1 - Task 13 (#120): Owner re-validation on reconnect — shipped as **TST-02** (P0).
- Task 14 (#121): Client ReplayGap handling — all 5 clients — blockedBy 10 - 🔄 Task 14 (#121): Client ReplayGap handling — shipped as **CLI-15** for 4/5 clients
- Carry the per-language presence-check idiom note for `optional` message fields. (.NET/Go/Rust/Python); Java pending (windev batch). Per-language presence-check idiom
- ⏳ Task 15 (#122): Reconnect integration test (fake worker) — blockedBy 12 for `optional` message fields carried in each client's surface.
- ✅ Task 15 (#122): Reconnect integration test (fake worker) — shipped as **TST-01**.
### Pending — Phase 4 (Per-session dashboard ACL) ### Phase 4 (Per-session dashboard ACL) — SCOPED, tracked as archreview TST-15
- ⏳ Task 16 (#123): gRPC session-owner gate + all-sessions admin scope — blockedBy 9, 1 - ⏳ Task 16 (#123): gRPC session-owner gate + all-sessions admin scope — blockedBy 9, 1
- Note: the gRPC owner gate itself already exists (TST-02); Phase 4 adds the admin
all-sessions scope + the dashboard-side twin.
- ⏳ Task 17 (#124): Session Tag + dashboard group-to-tag config — blockedBy 9 - ⏳ Task 17 (#124): Session Tag + dashboard group-to-tag config — blockedBy 9
- ⏳ Task 18 (#125): EventsHub per-session ACL + hub-token tag claim — blockedBy 17 - ⏳ Task 18 (#125): EventsHub per-session ACL + hub-token tag claim — blockedBy 17
- Open decision: Viewer default (admin-sees-all vs strict per-session). - Decision SETTLED: admin-sees-all, Viewer strictly scoped to owned/granted sessions
(matches TST-02 gRPC owner binding).
- ⏳ Task 19 (#126): ACL tests incl. live LDAP users — blockedBy 18 - ⏳ Task 19 (#126): ACL tests incl. live LDAP users — blockedBy 18
### Pending — Phase 5 (Orphan-worker reattach) ### Phase 5 (Orphan-worker reattach) — DEFERRED, NOT PLANNED
- ⏳ Task 20 (#127): Stable gateway-instance id + stable pipe naming — blockedBy 19 Deferred unless a concrete requirement appears. It reverses the CLAUDE.md invariant
- ⏳ Task 21 (#128): Adoption manifest store (SQLite) — blockedBy 20 "Gateway restart does not reattach orphan workers" and adds an adoption manifest store +
- ⏳ Task 22 (#129): Proto — worker adopt/reconnect frame — blockedBy 21 worker phone-home protocol. **`EnableOrphanReattach` (Task 26) does not exist** — do not
- ⏳ Task 23 (#130): Worker phone-home reconnect loop + self-terminate — blockedBy 22 (net48/x86, windev) reference it as if it does until the task lands.
- Task 24 (#131): Gateway adoption — re-open pipes, nonce-validate, reject impostors — blockedBy 23 - 🚫 Task 20 (#127): Stable gateway-instance id + stable pipe naming
- Task 25 (#132): Resync adopted worker + ReplayGap to subscribers — blockedBy 24, 12 - 🚫 Task 21 (#128): Adoption manifest store (SQLite)
- Task 26 (#133): EnableOrphanReattach flag (default off) + terminator fallback — blockedBy 24 - 🚫 Task 22 (#129): Proto — worker adopt/reconnect frame
- Task 27 (#134): Gateway-restart reattach round-trip (WINDEV + live worker) — blockedBy 25, 26 - 🚫 Task 23 (#130): Worker phone-home reconnect loop + self-terminate (net48/x86, windev)
- Task 28 (#135): Documented-rule reversals + stillpending refresh — blockedBy 27 - 🚫 Task 24 (#131): Gateway adoption — re-open pipes, nonce-validate, reject impostors
- 🚫 Task 25 (#132): Resync adopted worker + ReplayGap to subscribers
- 🚫 Task 26 (#133): EnableOrphanReattach flag (default off) + terminator fallback
- 🚫 Task 27 (#134): Gateway-restart reattach round-trip (WINDEV + live worker)
- 🚫 Task 28 (#135): Documented-rule reversals + stillpending refresh
## Notes ## Notes
- Phase 5 reverses the documented "Gateway restart does not reattach orphan workers" - Phase 5 was designed to reverse the "Gateway restart does not reattach orphan workers"
rule (CLAUDE.md) — this was explicitly approved during design. rule (CLAUDE.md), but is now **deferred, not planned** (TST-04) — the invariant stands.
- Two deferred follow-ups noted earlier: dashboard visibility of `DetachedAtUtc` on - Two deferred follow-ups noted earlier: dashboard visibility of `DetachedAtUtc` on
`DashboardSessionSummary`. `DashboardSessionSummary`.
- Worker (net48/x86) tasks build/test on windev; everything else builds on macOS. - Worker (net48/x86) tasks build/test on windev; everything else builds on macOS.