Adds the 2026-07-08 architecture review (00-overall + six domain reports) and a remediation/ tree: one design+implementation doc per domain covering every finding, plus 00-tracking.md as the master progress tracker. - 153 findings with stable IDs (GWC/WRK/IPC/SEC/CLI/TST), each with design rationale, implementation steps, tests, docs, and verification. - Tracker rolls findings up by severity and P0/P1/P2 roadmap tier, records cross-cutting clusters and per-finding status (all Not started). - Planning docs only; no source changes.
22 KiB
MxAccessGateway — Remediation Tracking
Master progress tracker for the 2026-07-08 architecture review. Generated 2026-07-09.
Source review: ../00-overall.md · Per-domain remediation designs are linked below and hold the full Finding / Impact / Design / Implementation / Verification for every entry here.
How to use this document
- Each finding has a stable ID (
GWC-01,WRK-01, …) that never changes. Cite it in commits, branches, and PRs (e.g.fix(GWC-01): split alarm feed). - The Status column is the single source of truth for progress. Update it in the same change that lands the fix.
- Do the work in roadmap-tier order (P0 → P1 → P2), respecting the
Dep(depends-on) column within a tier. - When a fix lands: flip Status to
Done, and per the repo rule, update the affected docs in the same commit.
Status legend: Not started · In progress · In review · Done · Won't fix (record why in the domain doc) · N/A (informational / positive observation, no action).
Severity roll-up
Every finding from all six domain reports, including Lows and informational/positive notes (which exceed the headline "106" because the domain docs cover each sub-finding individually).
| Domain | Doc | Critical | High | Medium | Low | Info | Total |
|---|---|---|---|---|---|---|---|
| Gateway core | 10-gateway-core.md | 1 | 2 | 7 | 12 | 1 | 23 |
| Worker | 20-worker.md | — | 1 | 6 | 13 | — | 20 |
| Contracts & IPC | 30-contracts-ipc.md | — | 1 | 8 | 13 | — | 22 |
| Security & dashboard | 40-security-dashboard.md | — | — | 12 | 15 | 3 | 30 |
| Clients | 50-clients.md | — | 4 | 13 | 17 | — | 34 |
| Testing, docs & gaps | 60-testing-docs-gaps.md | — | 4 | 13 | 7 | — | 24 |
| Total | 1 | 12 | 59 | 77 | 4 | 153 |
Roadmap-tier roll-up
Tiers follow the roadmap in 00-overall.md. Work top-down; within a tier, Dep gives ordering.
| Tier | Meaning | Count | Findings |
|---|---|---|---|
| P0 | Correctness & safety — before wider rollout | 8 | GWC-01, GWC-02, GWC-03, WRK-01, CLI-01, CLI-03, TST-02, TST-12 |
| P1 | Process & hardening — next few weeks | 26 | GWC-04, WRK-07, IPC-01, IPC-02, IPC-03, IPC-04, IPC-09, IPC-19, IPC-20, SEC-01, SEC-02, SEC-04, SEC-05, SEC-06, SEC-07, SEC-08, SEC-10, SEC-11, SEC-12, SEC-20, CLI-02, TST-03, TST-05, TST-08 |
| P2 | Completeness & polish | 39 | GWC-06/07/08/14/15, WRK-06/11/12/15, IPC-05/06/07/13/14/15/17/21, SEC-03/09/22/25/30, CLI-04/12/15/16/18/21/26/29, TST-01/04/11/13/14/15/23/24 |
| — | Not individually in the roadmap (mostly Low/Info) | 80 | see per-domain registers below |
P0 — do first (8)
| ID | Sev | Eff | Dep | Status | Title |
|---|---|---|---|---|---|
| GWC-01 | Critical | M | — | Not started | Alarm monitor and distributor pump both drain the single worker event channel |
| GWC-02 | High | M | — | Not started | Faulted sessions are never swept (worker + slot pinned up to 30 min) |
| GWC-03 | High | S | — | Not started | Documented sparse-array max-length bound is unimplemented |
| WRK-01 | High | M | — | Not started | Long ReadBulk self-faults as StaHung; all replies then dropped |
| CLI-01 | High | M | — | Not started | Go Session.Events() silently closes stream on 16-slot overflow |
| CLI-03 | High | M | — | Not started | Rust invoke never validates HRESULT / MXSTATUS_PROXY |
| TST-02 | High | M | TST-04 | Not started | Reconnect owner re-validation not implemented (security control) |
| TST-12 | Medium | S | — | Not started | CLAUDE.md misstates default retention behaviour (defaults are on, not off) |
Finding registers by domain
Full design + implementation for each row lives in the linked domain doc under its ID heading.
Gateway core — 10-gateway-core.md
| ID | Sev | Tier | Eff | Dep | Status | Title |
|---|---|---|---|---|---|---|
| GWC-01 | Critical | P0 | M | — | Not started | Alarm monitor and distributor pump both drain the single worker event channel |
| GWC-02 | High | P0 | M | — | Not started | Faulted sessions are never swept |
| GWC-03 | High | P0 | S | — | Not started | Documented sparse-array max-length bound is unimplemented |
| GWC-04 | Medium | P1 | M | — | Not started | Full event channel stalls the worker read loop behind command replies |
| GWC-05 | Medium | — | S | — | Not started | Worker pipe created with no ACL / no CurrentUserOnly |
| GWC-06 | Medium | P2 | S | GWC-07,08 | Not started | Stopwatch allocated per streamed event |
| GWC-07 | Medium | P2 | S | GWC-06,08 | Not started | Every mapped event is deep-cloned |
| GWC-08 | Medium | P2 | M | GWC-06,07 | Not started | Pipe framing allocates per frame and writes twice |
| GWC-09 | Medium | — | M | — | Not started | Startup probe is a no-op; its retry pipeline can never retry |
| GWC-10 | Medium | — | S | — | Not started | Envelope sequence monotonicity specified but not enforced |
| GWC-11 | Low | — | S | — | Not started | _workerClient written under lock but read lock-free |
| GWC-12 | Low | — | S | — | Not started | WorkerClient.DisposeAsync not safe against double-dispose |
| GWC-13 | Low | — | M | — | Not started | Worker-ready wait is a 25 ms poll loop |
| GWC-14 | Low | P2 | M | — | Not started | Replay ring is a LinkedList with a node alloc per event |
| GWC-15 | Low | P2 | S | — | Not started | Per-event gauge reads ChannelReader.Count every event |
| GWC-16 | Low | — | S | — | Not started | Invoke resolves the session twice per command |
| GWC-17 | Low | — | M | — | Not started | Sessions layer throws Grpc.Core.RpcException (layering leak) |
| GWC-18 | Low | — | S | — | Not started | GatewaySession implements DisposeAsync without IAsyncDisposable |
| GWC-19 | Low | — | S | — | Not started | Dead GatewaySession.KillWorker(string) + stale doc |
| GWC-20 | Low | — | S | — | Not started | Heartbeat config semantics conflated (send vs check interval) |
| GWC-21 | Low | — | S | GWC-04 | Not started | EventChannelFullModeTimeout / HeartbeatStuckCeiling not configurable |
| GWC-22 | Low | — | S | — | Not started | StreamDisconnected always labeled "Detached" |
| GWC-23 | Info | — | — | — | N/A | MaxEventSubscribersPerSession dead in default single-subscriber mode |
Worker — 20-worker.md
| ID | Sev | Tier | Eff | Dep | Status | Title |
|---|---|---|---|---|---|---|
| WRK-01 | High | P0 | M | — | Not started | Long ReadBulk self-faults as StaHung; all replies then dropped |
| WRK-02 | Medium | — | M | — | Not started | STA thread death after startup is silent; future work hangs forever |
| WRK-03 | Medium | — | S | WRK-02 | Not started | Commands after shutdown starts are dropped with no reply |
| WRK-04 | Medium | — | M | — | Not started | Envelope sequence can appear out of order on the wire |
| WRK-05 | Medium | — | M | — | Not started | One transient alarm-poll failure kills the whole session |
| WRK-06 | Medium | P2 | S | — | Not started | MXSTATUS_PROXY conversion reflects per field, per event |
| WRK-07 | Medium | P1 | M | WRK-04 | Not started | Documented outbound write priority not implemented |
| WRK-08 | Low | — | S | — | Not started | Residual event queue discarded at graceful shutdown, undocumented |
| WRK-09 | Low | — | S | — | Not started | No AppDomain.UnhandledException hook |
| WRK-10 | Low | — | S | — | Not started | Top-level catch logs exception type but never message |
| WRK-11 | Low | P2 | S | — | Not started | Every accepted event is defensively cloned on enqueue |
| WRK-12 | Low | P2 | M | WRK-04 | Not started | No event batching per envelope; one flush per event; 25 ms poll |
| WRK-13 | Low | — | S | — | Not started | Frame writer allocates a fresh buffer per frame; reader pools |
| WRK-14 | Low | — | M | — | Not started | Private-field naming split _camelCase vs camelCase |
| WRK-15 | Low | P2 | S | — | Not started | Doc drift: STA thread name and heartbeat-counter note |
| WRK-16 | Low | — | S | — | Not started | Boilerplate duplication in IPC envelope/ctor overloads |
| WRK-17 | Low | — | S | — | Not started | Gateway death exits with wrong exit code (6 not 5) |
| WRK-18 | Low | — | S | — | Not started | Event-queue overflow exits as generic UnexpectedFailure |
| WRK-19 | Low | — | S | — | Not started | Command start/end logging with correlation id absent |
| WRK-20 | Low | — | M | WRK-01..04 | Not started | Test-coverage gaps for the failure modes above |
Contracts & IPC — 30-contracts-ipc.md
| ID | Sev | Tier | Eff | Dep | Status | Title |
|---|---|---|---|---|---|---|
| IPC-01 | High | P1 | M | — | Not started | Published client descriptor set 7 weeks stale; nothing enforces freshness |
| IPC-02 | Medium | P1 | M | IPC-03 | Not started | Worker max frame size hard-coded, cannot follow gateway config |
| IPC-03 | Medium | P1 | M | IPC-02 | Not started | gRPC max == pipe max with zero headroom; oversized write faults whole session |
| IPC-04 | Medium | P1 | S | IPC-03 | Not started | DrainEvents max_events=0 packs entire queue into one reply frame |
| IPC-05 | Medium | P2 | S | — | Not started | Redundant deep copies on command and event hot paths |
| IPC-06 | Medium | P2 | S | — | Not started | gateway.md Worker Envelope sketch no longer matches the contract |
| IPC-07 | Medium | P2 | S | — | Not started | docs/Grpc.md says six RPCs; there are seven |
| IPC-08 | Medium | — | M | — | Not started | WorkerCancel defined and handled but never sent |
| IPC-09 | Medium | P1 | M | IPC-01 | Not started | Known codegen fragilities have no in-repo guards |
| IPC-10 | Low | — | S | — | Not started | Envelope sequence is write-only; monotonicity never validated |
| IPC-11 | Low | — | S | — | Not started | No protocol version negotiation despite supported_protocol_version name |
| IPC-12 | Low | — | S | — | Not started | Gateway frame writer has no write lock; integrity rests on undocumented invariant |
| IPC-13 | Low | P2 | S | IPC-05 | Not started | Gateway writer serializes twice and issues two stream writes |
| IPC-14 | Low | P2 | S | IPC-05 | Not started | Gateway reader allocates fresh array per frame; worker rents from ArrayPool |
| IPC-15 | Low | P2 | M | — | Not started | Worker event delivery 25 ms poll with per-event write+flush, no batching |
| IPC-16 | Low | — | S | — | N/A | Correlation id carried twice per reply (Info) |
| IPC-17 | Low | P2 | S | — | Not started | Two docs name the wrong Python generated-output directory |
| IPC-18 | Low | — | S | IPC-01 | N/A | Generated-code hygiene verified good — preserve under change (positive) |
| IPC-19 | Low | P1 | S | IPC-01 | Not started | Generated/-must-be-committed rule for net48 undocumented and unguarded |
| IPC-20 | Low | P1 | S | IPC-01 | Not started | Descriptor -Check byte-compares source-info bytes; protoc-version-sensitive |
| IPC-21 | Low | P2 | S | IPC-06 | Not started | gateway.md presents unimplemented Session RPC as live |
| IPC-22 | Low | — | S | — | N/A | Public error-detail model stops at status codes plus prose (Info) |
Security & dashboard — 40-security-dashboard.md
| ID | Sev | Tier | Eff | Dep | Status | Title |
|---|---|---|---|---|---|---|
| SEC-01 | Medium | P1 | M | — | Not started | Windows-absolute default paths become relative files off-Windows |
| SEC-02 | Medium | P1 | S | — | Not started | AllowAnonymousLocalhost satisfies AdminOnly, not just Viewer |
| SEC-03 | Medium | P2 | S | — | Not started | Dashboard cookie lost its __Host- prefix; four docs still promise it |
| SEC-04 | Medium | P1 | S | SEC-03 | Not started | DisableLogin has no production guard |
| SEC-05 | Medium | P1 | M | — | Not started | Hub bearer tokens irrevocable for 30 min, carried in query string |
| SEC-06 | Medium | P1 | M | — | Not started | LDAP plaintext-by-default with a committed service password |
| SEC-07 | Medium | P1 | S | — | Not started | QueryActiveAlarmsRequest missing from scope resolver; tests mislabeled |
| SEC-08 | Medium | P1 | L | — | Not started | Per-RPC SQLite read + last_used_utc write; no verification cache |
| SEC-09 | Medium | P2 | S | — | Not started | Dashboard design-doc GroupToRole sample now fails startup validation |
| SEC-10 | Medium | P1 | L | — | Not started | No API-key expiry |
| SEC-11 | Medium | P1 | M | SEC-08 | Not started | No rate limiting or lockout on either auth surface |
| SEC-12 | Medium | P1 | M | — | Not started | Dashboard Close/Kill bypass the canonical audit store |
| SEC-13 | Low | — | S | SEC-30 | Not started | Redactor's credential-command list omits secured-bulk variants |
| SEC-14 | Low | — | S | SEC-02,20 | Not started | /metrics + /health unauthenticated; a metric leaks session ids |
| SEC-15 | Low | — | S | — | Not started | GET /logout skips antiforgery |
| SEC-16 | Low | — | S | — | Not started | CLI accepts the pepper as a command-line argument |
| SEC-17 | Low | — | S | — | Not started | Interceptor does not override client-streaming/duplex handlers |
| SEC-18 | Low | — | S | — | Not started | Pepper-unavailable detection matches library message text |
| SEC-19 | Low | — | S | — | Not started | Canonical audit store re-issues CREATE TABLE per write/read |
| SEC-20 | Low | P1 | S | — | Not started | session_id metric tag is unbounded cardinality |
| SEC-21 | Low | — | M | — | Not started | Snapshot publisher works every second regardless of audience |
| SEC-22 | Low | P2 | M | — | Not started | docs/Authentication.md documents types no longer in this repo |
| SEC-23 | Low | — | S | SEC-03,04 | Not started | Validator ignores DisableLogin/AutoLoginUser/RequireHttpsCookie/CookieName |
| SEC-24 | Low | — | S | SEC-04 | Not started | Effective-config view omits the riskiest dashboard flags |
| SEC-25 | Low | P2 | M | SEC-02 | Not started | Per-session EventsHub ACL is an acknowledged TODO |
| SEC-26 | Low | — | M | — | Not started | Audit trail has no retention or pruning |
| SEC-27 | Low | — | S | — | Not started | Dashboard GatewayStatus is hardcoded Healthy |
| SEC-28 | Info | — | S | — | N/A | Positive security observations (preserve under change) |
| SEC-29 | Info | — | S | — | N/A | UI-stack rule verified compliant |
| SEC-30 | Info | P2 | S | SEC-13 | Not started | Value-logging feature is unwired end-to-end |
Clients — 50-clients.md
| ID | Sev | Tier | Eff | Dep | Status | Title |
|---|---|---|---|---|---|---|
| CLI-01 | High | P0 | M | — | Not started | Go Session.Events() silently closes stream on 16-slot overflow |
| CLI-02 | High | P1 | M | — | Not started | Rust crate unbuildable outside the repo (build.rs path + --no-verify) |
| CLI-03 | High | P0 | M | — | Not started | Rust invoke never validates HRESULT / MXSTATUS_PROXY |
| CLI-04 | High | P2 | L | CLI-15 | Not started | Typed-command parity gap across all five clients |
| CLI-05 | Medium | — | S | — | Not started | .NET session cannot be re-attached to an existing session id |
| CLI-06 | Medium | — | S | — | Not started | .NET DisposeAsync blocks/throws on unreachable gateway |
| CLI-07 | Medium | — | S | — | Not started | .NET retry budget self-defeats on DeadlineExceeded |
| CLI-08 | Medium | — | S | CLI-03 | Not started | .NET/Go/Java treat any nonzero HRESULT as failure (should be < 0) |
| CLI-09 | Medium | — | M | — | Not started | Go has no typed auth-error mapping (Unauthenticated vs PermissionDenied) |
| CLI-10 | Medium | — | M | — | Not started | Go uses deprecated grpc.DialContext + grpc.WithBlock() |
| CLI-11 | Medium | — | S | — | Not started | Go CLI cannot opt into strict TLS validation |
| CLI-12 | Medium | P2 | S | — | Not started | Java docs still say "Java 21" after the JDK 17 retarget |
| CLI-13 | Medium | — | M | — | Not started | Java event-stream buffer hardcoded 16, cancel-on-overflow |
| CLI-14 | Medium | — | S | — | Not started | Python default TLS is blocking TOFU pin with silent localhost SNI |
| CLI-15 | Medium | P2 | M | — | Not started | No client handles ReplayGap or offers a reconnect helper |
| CLI-16 | Medium | P2 | S | CLI-12 | Not started | docs/ClientPackaging.md drifted from Python naming and .NET .slnx |
| CLI-17 | Medium | — | M | CLI-14 | Not started | TLS default posture inconsistent across the five clients |
| CLI-18 | Low | P2 | S | — | Not started | .NET csproj records no <Version> |
| CLI-19 | Low | — | S | — | Not started | .NET duplicate InternalsVisibleTo (AssemblyInfo + csproj) |
| CLI-20 | Low | — | S | CLI-17 | Not started | .NET --tls without CA installs accept-all callback |
| CLI-21 | Low | P2 | S | — | Not started | Go ClientVersion = "0.1.0-dev" stale vs tagged releases |
| CLI-22 | Low | — | S | — | Not started | Go newCorrelationID swallows crypto/rand error → empty id |
| CLI-23 | Low | — | S | — | Not started | Go nil-vs-empty bulk short-circuit asymmetry |
| CLI-24 | Low | — | S | — | Not started | Java MxEventStream single-consumer constraint undocumented |
| CLI-25 | Low | — | S | — | Not started | Java close() does not await channel termination |
| CLI-26 | Low | P2 | S | — | Not started | Python version.py (0.1.0) ≠ pyproject.toml (0.1.2) |
| CLI-27 | Low | — | S | — | Not started | Python Session.close() not concurrency-safe; synthesizes reply |
| CLI-28 | Low | — | S | — | Not started | Python circular-import workaround at end of session.py |
| CLI-29 | Low | P2 | S | — | Not started | Rust CLIENT_VERSION (0.1.0-dev) ≠ Cargo.toml (0.1.2) |
| CLI-30 | Low | — | S | CLI-04 | Not started | Rust has no unregister typed helper |
| CLI-31 | Low | — | M | — | Not started | Rust CLI is a single 2,699-line main.rs |
| CLI-32 | Low | — | S | — | Not started | Client-side bulk caps differ (.NET/Java unbounded) |
| CLI-33 | Low | — | S | CLI-01,13 | Not started | Per-language event backpressure semantics undocumented |
| CLI-34 | Low | — | S | — | Not started | Python build//.pytest_cache/ present on disk (untracked) |
Testing, docs & gaps — 60-testing-docs-gaps.md
| ID | Sev | Tier | Eff | Dep | Status | Title |
|---|---|---|---|---|---|---|
| TST-01 | High | P2 | L | TST-04 | Not started | Reconnect/replay has no e2e test and no client consumer |
| TST-02 | High | P0 | M | TST-04 | Not started | Reconnect owner re-validation not implemented |
| TST-03 | High | P1 | M | — | Not started | No CI exists |
| TST-04 | High | P2 | L | — | Not started | Session-resilience epic 16/28 tasks unfinished |
| TST-05 | Medium | P1 | S | TST-03 | Not started | Real-worker control/COM paths verified opt-in only |
| TST-06 | Medium | — | M | — | Not started | Dashboard live-data path untested |
| TST-07 | Medium | — | S | — | Not started | Real-clock sleeps with negative assertions are latent flakes |
| TST-08 | Medium | P1 | M | — | Not started | Full-suite orphaned testhost processes |
| TST-09 | Medium | — | M | — | Not started | Health checks cover only the auth store |
| TST-10 | Medium | — | M | — | Not started | Deployment/upgrade undocumented and hand-rolled |
| TST-11 | Medium | P2 | M | — | Not started | No version discipline on server; client versions drift |
| TST-12 | Medium | P0 | S | — | Not started | CLAUDE.md misstates default retention behaviour |
| TST-13 | Medium | P2 | S | — | Not started | gateway.md carries stale design-era sketches |
| TST-14 | Medium | P2 | S | — | Not started | Repo-root working artifacts need triage |
| TST-15 | Medium | P2 | M | TST-04 | Not started | Dashboard EventsHub has no per-session ACL |
| TST-16 | Medium | — | S | — | Not started | Dashboard:ShowTagValues is a dead flag |
| TST-17 | Medium | — | S | — | Not started | Vendor-gated alarm parity residuals silently lossy |
| TST-18 | Low | — | S | — | Not started | Hosted-service wrappers untested |
| TST-19 | Low | — | S | — | Not started | Keep FakeWorkerHarness canned replies in lockstep |
| TST-20 | Low | — | S | — | Not started | E2E script papers over a real advise-without-consumer sharp edge |
| TST-21 | Low | — | S | — | Not started | Log rotation configured but minimal |
| TST-22 | Low | — | S | — | Not started | Config-shape JSON block omits documented keys |
| TST-23 | Low | P2 | S | — | Not started | Bidirectional Session RPC never built |
| TST-24 | Low | P2 | M | TST-03 | Not started | Client wire behaviour has no automated verification |
Cross-cutting clusters
Findings the review flagged as one coordinated design pass — sequence them together rather than piecemeal.
- Size / backpressure topology: IPC-02, IPC-03, IPC-04 + GWC-04 + WRK-12/WRK-07 + CLI-13/CLI-32/CLI-33. Convey
MaxMessageBytesto the worker, boundDrainEvents, decouple event backlog from command replies, make client buffer-overflow behavior uniform and loud. - Event hot-path performance: GWC-06/07/08/14/15 + WRK-06/11/12/13 + IPC-05/13/14/15. Kill double clones, cache the status converter, batch event frames, pool gateway frame buffers.
- Silent-failure hardening: GWC-02, WRK-01/02/03, CLI-01, CLI-03. Every death or drop must produce an observable error or fault frame.
- CI + codegen freshness (unlocks the rest): TST-03 + IPC-01/09/19/20 + CLI-34. Stand up minimal CI so the descriptor/codegen/version guards actually run.
- Doc-drift sweep: IPC-06/07/17/21 + SEC-03/09/22 + CLI-12/16 + TST-12/13/14 + GWC-19 + WRK-15. Correct load-bearing docs to match shipped behavior.
- Session-resilience epic: TST-01/02/04/15 + SEC-25 + CLI-15. Finish owner re-validation, client
ReplayGaphandling, per-session event ACL, and the replay e2e test.
Change log
| Date | Change |
|---|---|
| 2026-07-09 | Initial tracking doc generated from the six domain remediation designs. All 153 findings Not started. |