diff --git a/CLAUDE.md b/CLAUDE.md index 73c7c0c..a1ddf64 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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`). - **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. -- **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. - **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. diff --git a/docs/plans/2026-06-15-session-resilience.md.tasks.json b/docs/plans/2026-06-15-session-resilience.md.tasks.json index 9880069..4e8f87a 100644 --- a/docs/plans/2026-06-15-session-resilience.md.tasks.json +++ b/docs/plans/2026-06-15-session-resilience.md.tasks.json @@ -13,22 +13,23 @@ {"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": 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": 121, "subject": "Task 14: Client ReplayGap handling - all 5 clients", "status": "pending", "blockedBy": [117]}, - {"id": 122, "subject": "Task 15: Reconnect integration test (fake worker)", "status": "pending", "blockedBy": [119]}, - {"id": 123, "subject": "Task 16: gRPC session-owner gate + all-sessions admin scope", "status": "pending", "blockedBy": [116, 108]}, - {"id": 124, "subject": "Task 17: Session Tag + dashboard group-to-tag config", "status": "pending", "blockedBy": [116]}, - {"id": 125, "subject": "Task 18: EventsHub per-session ACL + hub-token tag claim", "status": "pending", "blockedBy": [124]}, - {"id": 126, "subject": "Task 19: ACL tests incl. live LDAP users", "status": "pending", "blockedBy": [125]}, - {"id": 127, "subject": "Task 20: Stable gateway-instance id + stable pipe naming", "status": "pending", "blockedBy": [126]}, - {"id": 128, "subject": "Task 21: Adoption manifest store (SQLite)", "status": "pending", "blockedBy": [127]}, - {"id": 129, "subject": "Task 22: Proto - worker adopt/reconnect frame", "status": "pending", "blockedBy": [128]}, - {"id": 130, "subject": "Task 23: Worker phone-home reconnect loop + self-terminate", "status": "pending", "blockedBy": [129]}, - {"id": 131, "subject": "Task 24: Gateway adoption - re-open pipes, nonce-validate, reject impostors", "status": "pending", "blockedBy": [130]}, - {"id": 132, "subject": "Task 25: Resync adopted worker + ReplayGap to subscribers", "status": "pending", "blockedBy": [131, 119]}, - {"id": 133, "subject": "Task 26: EnableOrphanReattach flag (default off) + terminator fallback", "status": "pending", "blockedBy": [131]}, - {"id": 134, "subject": "Task 27: Gateway-restart reattach round-trip (WINDEV + live worker)", "status": "pending", "blockedBy": [132, 133]}, - {"id": 135, "subject": "Task 28: Documented-rule reversals + stillpending refresh", "status": "pending", "blockedBy": [134]} + {"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": "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": "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], "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], "note": "Phase 4 / TST-15."}, + {"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], "note": "Phase 4 / TST-15."}, + {"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": "deferred", "blockedBy": [127], "note": "Phase 5 deferred, not planned (TST-04)."}, + {"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": "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": "deferred", "blockedBy": [130], "note": "Phase 5 deferred, not planned (TST-04)."}, + {"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": "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": "deferred", "blockedBy": [132, 133], "note": "Phase 5 deferred, not planned (TST-04)."}, + {"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" } diff --git a/oldtasks.md b/oldtasks.md index d675f45..16fa7f7 100644 --- a/oldtasks.md +++ b/oldtasks.md @@ -15,8 +15,27 @@ The authoritative resume state lives in ## Status -**12 of 28 tasks complete** (Phases 1–2 + reconnect core of Phase 3). All completed -work is merged to `main` (commit `c446bef`, pushed to origin). +**Governance update 2026-07-09 (archreview TST-04).** The epic is resolved into three +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 1–2 + reconnect core of +Phase 3), merged to `main` (commit `c446bef`). ### Completed — Phase 1 (Foundation) - ✅ 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 12 (#119): Replay-on-reconnect + emit ReplayGap -### Pending — Phase 3 finish -- ⏳ Task 13 (#120): Owner re-validation on reconnect — blockedBy 12, 1 -- ⏳ Task 14 (#121): Client ReplayGap handling — all 5 clients — blockedBy 10 - - Carry the per-language presence-check idiom note for `optional` message fields. -- ⏳ Task 15 (#122): Reconnect integration test (fake worker) — blockedBy 12 +### Phase 3 finish — DONE (via archreview P0/P2) +- ✅ Task 13 (#120): Owner re-validation on reconnect — shipped as **TST-02** (P0). +- 🔄 Task 14 (#121): Client ReplayGap handling — shipped as **CLI-15** for 4/5 clients + (.NET/Go/Rust/Python); Java pending (windev batch). Per-language presence-check idiom + 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 + - 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 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 -### Pending — Phase 5 (Orphan-worker reattach) -- ⏳ Task 20 (#127): Stable gateway-instance id + stable pipe naming — blockedBy 19 -- ⏳ Task 21 (#128): Adoption manifest store (SQLite) — blockedBy 20 -- ⏳ Task 22 (#129): Proto — worker adopt/reconnect frame — blockedBy 21 -- ⏳ Task 23 (#130): Worker phone-home reconnect loop + self-terminate — blockedBy 22 (net48/x86, windev) -- ⏳ Task 24 (#131): Gateway adoption — re-open pipes, nonce-validate, reject impostors — blockedBy 23 -- ⏳ Task 25 (#132): Resync adopted worker + ReplayGap to subscribers — blockedBy 24, 12 -- ⏳ Task 26 (#133): EnableOrphanReattach flag (default off) + terminator fallback — blockedBy 24 -- ⏳ Task 27 (#134): Gateway-restart reattach round-trip (WINDEV + live worker) — blockedBy 25, 26 -- ⏳ Task 28 (#135): Documented-rule reversals + stillpending refresh — blockedBy 27 +### Phase 5 (Orphan-worker reattach) — DEFERRED, NOT PLANNED +Deferred unless a concrete requirement appears. It reverses the CLAUDE.md invariant +"Gateway restart does not reattach orphan workers" and adds an adoption manifest store + +worker phone-home protocol. **`EnableOrphanReattach` (Task 26) does not exist** — do not +reference it as if it does until the task lands. +- 🚫 Task 20 (#127): Stable gateway-instance id + stable pipe naming +- 🚫 Task 21 (#128): Adoption manifest store (SQLite) +- 🚫 Task 22 (#129): Proto — worker adopt/reconnect frame +- 🚫 Task 23 (#130): Worker phone-home reconnect loop + self-terminate (net48/x86, windev) +- 🚫 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 -- Phase 5 reverses the documented "Gateway restart does not reattach orphan workers" - rule (CLAUDE.md) — this was explicitly approved during design. +- Phase 5 was designed to reverse the "Gateway restart does not reattach orphan workers" + 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 `DashboardSessionSummary`. - Worker (net48/x86) tasks build/test on windev; everything else builds on macOS.