Architecture remediation: P2 tier (completeness & polish) #122

Open
dohertj2 wants to merge 25 commits from fix/archreview-p2 into main
3 changed files with 67 additions and 39 deletions
Showing only changes of commit ed3c6c61c5 - Show all commits
+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.