Files
mxaccessgw/archreview/remediation/00-tracking.md
T
Joseph Doherty a8f86b5336
ci / nightly-windev (push) Has been skipped
ci / java (push) Successful in 2m19s
ci / portable (push) Successful in 23m59s
ci / windows-x86 (push) Failing after 58m42s
test(tst-24): drive the .NET and Python clients against real in-process gRPC servers
TST-24 asked for per-client wire tests against a fake gateway. An audit first
corrected the finding's premise: Go, Rust, and Java already had them — bufconn,
a loopback tonic server, and InProcessServerBuilder respectively — each already
asserting the round trip, the server-observed bearer header, and the ReplayGap
sentinel. The two genuine gaps were .NET (every test substituted the transport
interface; the test project had no server package at all) and Python (stub
monkeypatching everywhere but one opt-in TLS test).

Both now serve mxaccess_gateway.v1.MxAccessGateway over a real transport —
Kestrel h2c and grpc.aio, each on an ephemeral loopback port — and drive the
ordinary public client API against it. Only the gateway's behaviour is canned;
the framing, serialization, metadata, and status codes are genuine. Four shapes
each: full round trip with every reply field asserted, the authorization header
as received by the server (including on the streaming RPC), the ReplayGap
sentinel surfaced as the client's typed signal, and a real PERMISSION_DENIED
mapping to the typed authorization error.

The .NET client was only ever compiled in CI, never tested, so the portable job
gains a dotnet test step.

Fixes a bug the new tests caught on their first run: Python's connect() built the
grpc.aio channel inside asyncio.to_thread, and a grpc.aio channel binds to the
event loop current on the constructing thread, so every non-stub connection
raised 'There is no current event loop in thread'. No mock-based test could see
it, and the test guarding the off-loop behaviour patched create_channel and so
asserted the bug. Split resolve_channel_security (blocking TOFU probe, off-loop)
from create_channel (on-loop); the guard tests now assert both halves.
2026-08-10 08:22:25 -04:00

56 KiB
Raw Blame History

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 Done Alarm monitor and distributor pump both drain the single worker event channel
GWC-02 High M Done Faulted sessions are never swept (worker + slot pinned up to 30 min)
GWC-03 High S Done Documented sparse-array max-length bound is unimplemented
WRK-01 High M Done Long ReadBulk self-faults as StaHung; all replies then dropped
CLI-01 High M Done Go Session.Events() silently closes stream on 16-slot overflow
CLI-03 High M Done Rust invoke never validates HRESULT / MXSTATUS_PROXY
TST-02 High M TST-04 Done Reconnect owner re-validation not implemented (security control)
TST-12 Medium S Done 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 Done Alarm monitor and distributor pump both drain the single worker event channel
GWC-02 High P0 M Done Faulted sessions are never swept
GWC-03 High P0 S Done Documented sparse-array max-length bound is unimplemented
GWC-04 Medium P1 M Done 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 Done Stopwatch allocated per streamed event
GWC-07 Medium P2 S GWC-06,08 Done Every mapped event is deep-cloned
GWC-08 Medium P2 M GWC-06,07 Done 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 Done Replay ring is a LinkedList with a node alloc per event
GWC-15 Low P2 S Done 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 Done 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 Done 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 Done MXSTATUS_PROXY conversion reflects per field, per event
WRK-07 Medium P1 M WRK-04 Done 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 Done Every accepted event is defensively cloned on enqueue
WRK-12 Low P2 M WRK-04 Done 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 Done 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 Done Published client descriptor set 7 weeks stale; nothing enforces freshness
IPC-02 Medium P1 M IPC-03 Done Worker max frame size hard-coded, cannot follow gateway config
IPC-03 Medium P1 M IPC-02 Done gRPC max == pipe max with zero headroom; oversized write faults whole session
IPC-04 Medium P1 S IPC-03 Done DrainEvents max_events=0 packs entire queue into one reply frame
IPC-05 Medium P2 S Done Redundant deep copies on command and event hot paths
IPC-06 Medium P2 S Done gateway.md Worker Envelope sketch no longer matches the contract
IPC-07 Medium P2 S Done 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 Done 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 Done Gateway writer serializes twice and issues two stream writes
IPC-14 Low P2 S IPC-05 Done Gateway reader allocates fresh array per frame; worker rents from ArrayPool
IPC-15 Low P2 M Done 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 Done 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 Done Generated/-must-be-committed rule for net48 undocumented and unguarded
IPC-20 Low P1 S IPC-01 Done Descriptor -Check byte-compares source-info bytes; protoc-version-sensitive
IPC-21 Low P2 S IPC-06 Done 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 Done Windows-absolute default paths become relative files off-Windows
SEC-02 Medium P1 S Done AllowAnonymousLocalhost satisfies AdminOnly, not just Viewer
SEC-03 Medium P2 S Done Dashboard cookie lost its __Host- prefix; four docs still promise it
SEC-04 Medium P1 S SEC-03 Done DisableLogin has no production guard
SEC-05 Medium P1 M Done Hub bearer tokens irrevocable for 30 min, carried in query string
SEC-06 Medium P1 M Done LDAP plaintext-by-default with a committed service password
SEC-07 Medium P1 S Done QueryActiveAlarmsRequest missing from scope resolver; tests mislabeled
SEC-08 Medium P1 L Done Per-RPC SQLite read + last_used_utc write; no verification cache
SEC-09 Medium P2 S Done Dashboard design-doc GroupToRole sample now fails startup validation
SEC-10 Medium P1 L Done No API-key expiry
SEC-11 Medium P1 M SEC-08 Done No rate limiting or lockout on either auth surface
SEC-12 Medium P1 M Done 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 Done 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 Done 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 Done 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 Done Value-logging feature is unwired end-to-end

Clients — 50-clients.md

ID Sev Tier Eff Dep Status Title
CLI-01 High P0 M Done Go Session.Events() silently closes stream on 16-slot overflow
CLI-02 High P1 M Done Rust crate unbuildable outside the repo (build.rs path + --no-verify)
CLI-03 High P0 M Done Rust invoke never validates HRESULT / MXSTATUS_PROXY
CLI-04 High P2 L CLI-15 Done 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 Done .NET/Go/Java treat any nonzero HRESULT as failure (should be < 0) — landed via 2026-07-12 CLI-38
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 Done 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 Done No client handles ReplayGap or offers a reconnect helper
CLI-16 Medium P2 S CLI-12 Done 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 Done .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 Done 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 Done Java MxEventStream single-consumer constraint undocumented (closed 2026-08-07 per 2026-07-12 review old-tracker action; documented at MxEventStream.java:25 "Single consumer")
CLI-25 Low S Not started Java close() does not await channel termination
CLI-26 Low P2 S Done 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 Done Rust CLIENT_VERSION (0.1.0-dev) ≠ Cargo.toml (0.1.2)
CLI-30 Low S CLI-04 Done 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 Done Python build//.pytest_cache/ present on disk (untracked) (closed 2026-08-07 per 2026-07-12 review old-tracker action; both gitignored in clients/python/.gitignore)

Testing, docs & gaps — 60-testing-docs-gaps.md

ID Sev Tier Eff Dep Status Title
TST-01 High P2 L TST-04 Done Reconnect/replay has no e2e test and no client consumer
TST-02 High P0 M TST-04 Done Reconnect owner re-validation not implemented
TST-03 High P1 M Done No CI exists
TST-04 High P2 L Done Session-resilience epic 16/28 tasks unfinished
TST-05 Medium P1 S TST-03 Partially done Real-worker control/COM paths verified opt-in only. Scheduling half closed 2026-08-10 by the TST-25 nightly-windev job (.gitea/workflows/ci.yml, cron 0 6 * * *scripts/ci/run-windev-ci.sh live, which sets MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1, runs WorkerLiveMxAccessSmokeTests, and opens a Gitea issue on failure) — "opt-in, run by memory" is now "runs nightly, reports failures". Residual: the coverage-audit half. The live suite's 8 facts cover all six late-added COM commands but none of the five control commands (Ping, GetSessionState, GetWorkerInfo, DrainEvents, ShutdownWorker), which real workers answer in Worker/Ipc/WorkerPipeSession.cs yet are still only exercised through FakeWorkerHarness canned replies — precisely the masking the finding named
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 Done Full-suite orphaned testhost processes (does not reproduce; doc de-stale)
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 Done No version discipline on server; client versions drift
TST-12 Medium P0 S Done CLAUDE.md misstates default retention behaviour
TST-13 Medium P2 S Done 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 Done Bidirectional Session RPC never built
TST-24 Low P2 M TST-03 Done Client wire behaviour has no automated verification — closed 2026-08-10. Go/Rust/Java already had real-server wire tests (the finding's premise was stale); the genuine gaps were .NET (transport-interface fake everywhere, no server package) and Python (stub monkeypatch everywhere but one opt-in TLS test). Added MxGatewayClientWireTests + WireFakeGatewayServer (Kestrel h2c) and tests/test_wire_fake_gateway.py (grpc.aio loopback), plus a dotnet test step for the .NET client in the portable CI job. Caught a real bug: Python connect() built the grpc.aio channel inside asyncio.to_thread and failed for every non-stub connection

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 MaxMessageBytes to the worker, bound DrainEvents, 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 ReplayGap handling, per-session event ACL, and the replay e2e test.

Change log

Date Change
2026-08-10 TST-24 → Done: per-client wire tests land for the two clients that lacked them (branch feat/tst-24-client-wire-tests). Audit first corrected the finding's premise: Go, Rust, and Java already had real-server wire testsnewBufconnClient/fakeGatewayServer over grpc/test/bufconn, spawn_fake_gateway over a loopback TcpListener with tonic's Server, and InProcessGateway/TestGatewayService over InProcessServerBuilder — each already asserting the round trip, the server-observed authorization bearer header, and the ReplayGap sentinel. The real gaps were .NET (every test substituted FakeGatewayTransport for IMxGatewayClientTransport, and the test project had no server package) and Python (stub monkeypatching everywhere except one opt-in TLS test serving only OpenSession). Added WireFakeGatewayServer + MxGatewayClientWireTests (Kestrel h2c on 127.0.0.1:0 serving MxAccessGatewayBase; new Grpc.AspNetCore.Server 2.76.0 + Microsoft.AspNetCore.App refs on the test project) and clients/python/tests/test_wire_fake_gateway.py (grpc.aio server on 127.0.0.1:0, no new deps). Four shapes each: full round trip with every reply field asserted, the bearer header as received by the server on the streaming RPC too, the ReplayGap sentinel surfaced as the client's typed signal, and a genuine PERMISSION_DENIED mapping to the typed authorization error. CI: the portable job only built the .NET client, so a dotnet test step was added. The new tests immediately caught a shipped bug — Python GatewayClient.connect()/GalaxyRepositoryClient.connect() constructed the grpc.aio channel inside asyncio.to_thread, which raises RuntimeError: There is no current event loop in thread 'asyncio_0' because a grpc.aio channel binds to the loop current on the constructing thread; every non-stub connection failed, and the one test guarding the off-loop behaviour (Client.Python-028) monkeypatched create_channel and so asserted the bug. Fixed by splitting resolve_channel_security (blocking TOFU probe, off-loop) from create_channel (on-loop), with the -028 tests retargeted to assert both halves. Verified: .NET 133 passed/1 skipped (pre-existing live-gateway skip), Python 168 passed/1 skipped plus 6/6 opt-in TLS. Docs: docs/GatewayTesting.md § Client Wire Tests, clients/dotnet/README.md, clients/python/README.md.
2026-08-10 TST-05 revisited under the restored Windows tier → Partially done (branch feat/tst-24-client-wire-tests, doc/tracker-only). The finding's scheduling half is closed: cycle-2 TST-25's nightly-windev job (cron 0 6 * * *) runs scripts/ci/run-windev-ci.sh livewindev-worker-ci.ps1 -Mode live, which sets MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1, runs WorkerLiveMxAccessSmokeTests on windev after the x86 build/Worker.Tests/full-slnx steps, and files a Gitea issue when red. The coverage-audit half is not closed, and the audit the design asked for now has a negative answer: the suite's eight [LiveMxAccessFact]s cover all six late-added COM commands (Suspend, Activate, AuthenticateUser, ArchestrAUserToId, AddBufferedItem, SetBufferedUpdateInterval) but zero of the five control commands — MxCommandKind.{Ping,GetSessionState,GetWorkerInfo,DrainEvents,ShutdownWorker} appear nowhere in WorkerLiveMxAccessSmokeTests.cs, so the exact paths the Finding calls masked are still only proven against FakeWorkerHarness canned replies while the real implementations live in Worker/Ipc/WorkerPipeSession.cs. Residual work (two [LiveMxAccessFact]s, windev-only to author and verify) is specified in 60-testing-docs-gaps.md.
2026-07-10 TST-15 design fleshed out (still Not started — design only, not implementation): docs/plans/2026-07-10-dashboard-session-acl-tst15.md. Resolves the crux the deferral left open — the dashboard is LDAP-identity (Admin/Viewer) while sessions are API-key-owned (OwnerKeyId), two disjoint identity domains — via a session tag sourced from the owning API key (rides in the existing ApiKeyConstraints JSON blob, no SQLite migration). Admin-sees-all; Viewer may SubscribeSession iff session.Tags ∩ viewer.GrantedTags ≠ ∅ (new Dashboard:GroupToTag map → hub-token tag claims); untagged sessions Admin-only by default (Dashboard:UntaggedSessionVisibility). Includes the enforcement path (HubTokenPayload.Tags + IDashboardSessionAcl gate at SubscribeSession), task breakdown (epic Tasks 1619), test plan incl. live-LDAP, and rejected alternatives (client-supplied tag; group→key-id map). Tracker + 60-testing-docs-gaps.md TST-15 section point at the doc. TST-03 investigated: the CI never ran because the repo had zero registered Gitea Actions runners (Actions is enabled; runs are created on push/PR/nightly but fail instantly with nothing to execute them). A Mac runner proved the pipeline executes but cannot clone — this Gitea hands runners the internal http://gitea:3000 URL, reachable only by a runner co-located on the gitea Docker network. Fix = run a co-located runner on the Gitea host (recipe prepared, scratchpad/gitea-runner/setup-gitea-host-runner.sh); pending host access. TST-03 stays In review.
2026-07-09 P2 Epic wrap — user decision: DEFER TST-15 + TST-24, close the epic. Epic bucket result: 5 of 7 findings Done (CLI-15, CLI-04, CLI-30, TST-01, TST-04); TST-15 and TST-24 intentionally deferred to a follow-up (kept Not started, not Won't fix — they are gated, not rejected). TST-15 (dashboard EventsHub per-session ACL) is epic Phase 4 — a real feature needing a new session-"tag" mechanism + dashboard group→tag config, not a mechanical fix; the EventsHub TODO(per-session-acl) stays, and the already-shipped SEC-25 mitigation (tag values redacted from the dashboard mirror by default) means no sensitive payload leaks through the hub today regardless of the missing ACL — so deferring carries no value-leak risk. TST-24 (per-client wire tests) depends on TST-03 (CI), which is In review (YAML authored, never run on a Gitea runner) — no point wiring client tests into a pipeline that isn't live yet. Net P2: 35/38 Done; remaining = TST-15 (deferred feature), TST-24 (deferred, CI-gated), TST-14 (user deletes their own untracked gitignored *-docs-*.md files).
2026-07-09 P2 Epic — Java client completes CLI-15 + CLI-04 locally (commit 1cc0fa4); CLI-15, CLI-04, CLI-30, TST-01 all → Done (5/5 clients + server e2e). Java CLI-15: MxEventStreamItem record + MxEventStream.nextItem() (isReplayGap()/replayGap()/event()); existing Iterator<MxEvent> path unchanged, sentinel never swallowed. Java CLI-04: Phase 1 adviseSupervisory/writeSecured/writeSecured2/authenticateUser/archestrAUserToId + Phase 2 addBufferedItem/setBufferedUpdateInterval/suspend/activate (unregister already present) on MxGatewaySession, each through invokeCommandensureProtocolSuccess+ensureMxAccessSuccess; credentials scrubbed via MxGatewaySecrets.redactCredentials (tests assert absent from message/toString/CLI). gradle test 106/0 (58 client + 48 cli), no generated churn. Built locally with JAVA_HOME=/opt/homebrew/opt/openjdk@17 — Java toolchain now works on the Mac (see prior note). Shared docs ClientLibrariesDesign.md + CLAUDE.md updated to "all five clients". TST-01 → Done (server e2e fed0685 + all 5 client ReplayGap consumers). This closes session-resilience epic Phase 3 fully.
2026-07-09 P2 Epic Wave E2 — typed-command parity (commit bde042b). CLI-04 → In progress (4/5 clients; Java pending in the SAME session — see next note), CLI-30 → Done. Every parity-critical single-item command now has a typed session helper across .NET/Go/Rust/Python: Phase 1 AdviseSupervisory/WriteSecured/WriteSecured2/AuthenticateUser/ArchestrAUserToId, Phase 2 AddBufferedItem/SetBufferedUpdateInterval/Suspend/Activate, plus Unregister (CLI-30: Rust + .NET added; Go/Java/Python already had it — CLI-30 fully Done). Each wraps existing raw-command machinery (no new wire surface) and runs the client's MXAccess-level reply validation (hresult < 0 + MxStatusProxy). MXAccess parity preserved (WriteSecured-before-authenticate surfaces the native failure). Credentials route through each client's secret-redaction seam (never in logs/exceptions/ToString/Debug/Display; each suite asserts a distinctive credential is absent from surfaced errors); new CLI subcommands take credentials via flag/env, never echoed. Verified per toolchain: .NET build clean 102 passed; Go gofmt/vet/build/test clean; Rust fmt/check/test/clippy clean; Python 145 passed. Shared doc: ClientLibrariesDesign.md "Typed Command Parity" section.
2026-07-09 Java client toolchain now works locally on the Mac (user-flagged + verified): homebrew openjdk@17 (17.0.19), @21, @26 are installed (off PATH); JAVA_HOME=/opt/homebrew/opt/openjdk@17 gradle test → BUILD SUCCESSFUL. Retires the "Java client must be built on windev" constraint — the remaining Java halves of CLI-15 + CLI-04 are done locally this session (no windev batch). Memory project_java_build_host rewritten.
2026-07-09 P2 Epic — TST-04 (session-resilience epic governance) → Done (commit ed3c6c6, docs-only umbrella). Resolved the epic's shipped-vs-planned entanglement into three decisions: Phase 3 finished (Task 13=TST-02 owner-scoped attach P0, Task 15=TST-01 reconnect e2e, Task 14=CLI-15 4/5 clients w/ Java pending); Phase 4 scoped as TST-15 with the Viewer-default decision settled (admin-sees-all, Viewer strict per owned/granted session, matching TST-02's gRPC owner binding); Phase 5 (orphan-worker reattach) DEFERRED, not planned — the CLAUDE.md "gateway restart does not reattach orphan workers" invariant stands and the EnableOrphanReattach flag does not exist / must not be referenced. Updated oldtasks.md, the tasks.json mirror (per-task statuses + governance note), and the CLAUDE.md reconnect paragraph (clients consume ReplayGap; reattach deferred). The actionable slices carry their own verification: TST-01 (done), TST-02 (done, P0), TST-15 (pending, E3).
2026-07-09 P2 Epic Wave E1 — reconnect-replay: CLI-15 + TST-01 → In progress (both gated on the Java client, deferred to the windev batch). CLI-15 (commit 0c6e5b3): four clients now surface the gateway's ReplayGap reconnect sentinel as a distinct, typed, non-terminal signal (never synthesized, never swallowed) — .NET MxEventStreamItem/StreamEventItemsAsync (build clean, 87 passed), Go EventResult.ReplayGap+IsReplayGap() (build/vet/test clean), Rust EventItem::ReplayGap enum (EventStream now yields Result<EventItem, Error>; fmt/check/test/clippy clean), Python ReplayGap dataclass yielded from stream_events (131 passed). Shared docs: ClientLibrariesDesign.md non-goals reframed (reconnect-replay protocol is consumable; auto-reconnect stays a non-goal) + CrossLanguageSmokeMatrix.md resume-gap note. Resume contract uniform: after_worker_sequence = oldest_available_sequence - 1. TST-01 server half (commit fed0685): GatewayEndToEndReconnectReplayTests (2 facts) proves the default-on reconnect protocol end-to-end over the real gRPC StreamEvents path via the fake worker — replay-tail-no-gap (capacity 16, mid-batch cursor) and stale-cursor-emits-ReplayGap-first (capacity 3, 6 events force eviction, cursor=1); single-subscriber detach→reconnect, ring survives detach. macOS-verified 2/2 (needs TMPDIR=/tmp — default macOS TMPDIR pushes the CoreFxPipe Unix-socket path past the 104-byte sun_path limit; Windows CI unaffected). No product bugs; server behavior matched the contract exactly. Remaining to close both: Java ReplayGap surface (windev) + Java client consumer for TST-01.
2026-07-09 P2 Wave B — worker event hot-path (commit f61c816), windev-verified. WRK-06 → Done: MxStatusProxyConverter caches the four resolved FieldInfo per status type in a static ConcurrentDictionary (the GetField metadata scan ran 4× per status per event on the STA path); GetValue+Convert.ToInt32 still run per event (late-bound RCW); both exception messages byte-identical (missing-field via ResolveField, not cached on throw through GetOrAdd; null-value unchanged). WRK-11 → Done: MxAccessEventQueue.Enqueue takes ownership of the passed MxEvent (stamps WorkerSequence/WorkerTimestamp in place, no Clone()); audited all 3 callers (base/alarm event sinks + provider-mode handler) — each builds a fresh event, none reuse it; MxAccessValueCache.Set now deep-copies its retained Value/SourceTimestamp/Statuses so the cache snapshot never aliases the queue-owned (later serialized) event. WRK-12 → Done: WorkerFrameWriter coalesces the flush across a drained batch (write each frame, one FlushAsync after the batch, then complete all) — preserves the written+flushed completion contract; a burst of N events costs 1 flush not N; on failure the in-flight batch + queue all fail so no caller hangs. IPC-15 → Done: the multi-event WorkerEnvelope body stays unimplemented (wire still one event per worker_event frame); gateway.md Performance section now distinguishes the shipped flush-coalescing from that deferred additive-proto change. Windev: x86 Worker builds clean (fixed a Windows-only TreatWarningsAsErrors CS8604 in the value-cache null-guard that macOS can't surface); Worker.Tests 356 passed / 0 failed / 11 skipped (+4 new: converter cache-reuse, queue ownership-transfer, value-cache snapshot independence, writer batch-flush-once); gateway Tests 815 passed / 1 failed — the 1 is the known windev-environmental SelfSigned-SAN assertion (passes on macOS), and all pipe-harness tests (which also exercise the earlier G-frame gateway frame change + WRK-12 end-to-end over a real pipe) pass.
2026-07-09 P2 Wave B — quick Mac items (commit ec6f82b). TST-11 → Done: src/Directory.Build.props single-sources the .NET-side version (Server/Worker/Contracts/tests stamped 0.1.2, was SDK-default 1.0.0) and appends the git short SHA to InformationalVersion (0.1.2+<sha>, guarded so a git-less build still works); verified Server assembly stamps 0.1.2+579282f, Server+Tests build clean. Kept 0.1.2 (matches Contracts + aligned Python/Rust/Go); converging all to a single 0.2.0 cadence left as a release decision. WRK-15 → Done (docs-only path, no x86 build): STA thread-name refs corrected to the actual MxGateway.Worker.STA in docs/WorkerSta.md + docs/MxAccessWorkerInstanceDesign.md, and the stale heartbeat-counter note fixed (CaptureHeartbeat populates queue depth + sequence from the live queue). TST-23 → Done: already resolved by the Wave A gateway.md edit — the bidi Session RPC now sits under a "Future work: not implemented" heading (no "best long-term shape" framing), consistent with the proto. TST-14 left open by design: its only concrete step is deleting the user's untracked, gitignored *-docs-*.md working files (zero repo impact) — surfaced to the user rather than deleting files not created here.
2026-07-09 P2 Wave B — dashboard/security (commit e15c3cb). SEC-25 → Done (near-term hardening only; full per-session EventsHub ACL stays deferred to roadmap item 12 / TST-15): DashboardEventBroadcaster redacts tag values from a deep clone of the event before mirroring to SignalR when Dashboard:ShowTagValues is false (default), so the hub seam cannot leak values regardless of the missing ACL. Clears MxEvent.value (the OnDataChange/OnWriteComplete/OperationComplete/OnBufferedDataChange bodies are empty discriminators — values ride in the top-level field, incl. buffered arrays) + the alarm body current_value/limit_value; source event never mutated (shared with gRPC path + replay ring; verified). Makes the formerly-dead ShowTagValues flag live for the mirror. EventsHub TODO(per-session-acl) kept + tied to roadmap item 12. Test DashboardEventBroadcasterTests (3). SEC-30 → Done: docs/Diagnostics.md trimmed to mark opt-in command-value logging NOT YET IMPLEMENTED (no LogCommandValues knob, RedactCommandValue unwired, no values logged); wiring deferred pending secured-bulk redaction (SEC-13), not wired now. Server build clean; Dashboard tests 152/152.
2026-07-09 P2 Wave B — gateway event/frame hot-path performance (8 findings). Frame I/O (commit baae59c): GWC-08/IPC-13 gateway WorkerFrameWriter serializes once into one ArrayPool-rented buffer (LE length prefix + payload) and issues a single write (was: second serialization via ToByteArray, separate prefix array, two writes); IPC-14 WorkerFrameReader rents the payload buffer from ArrayPool instead of new byte[] per frame, read/parse length-bounded, returned in finally. Wire format byte-identical, cross-checked against the worker writer/reader. Event/command hot path (commit 5e2e40a): GWC-06 StreamEvents timing via Stopwatch.GetTimestamp()/GetElapsedTime() (no per-event Stopwatch alloc); GWC-07/IPC-05 MapEvent transfers ownership of the inner MxEvent instead of cloning (safe — single-consumer WorkerEvent, MapEvent runs once pre-fan-out, all downstream consumers read-only; verified no post-mapping mutation) and CreateCommandEnvelope drops its redundant second clone (MapCommand already isolated the graph); every load-bearing isolation clone kept; GWC-15 grpc_stream_queue.depth converted from a per-event push counter to an ObservableGauge summing registered channel sources only at scrape time (name/semantics unchanged). Replay ring (commit cf1b3d4): GWC-14 replaces the LinkedList (node alloc per retained event) with a preallocated circular ReplayEntry[] (head+count), preserving ascending order, capacity eviction, time trim, oldest-read/ReplayGap math, both query paths, and the lock. → all Done. Server build clean (0 warnings); per-cluster tests green (WorkerFrameProtocol 10/10 incl. new near-cap round-trip, EventStream/Metrics/Distributor/Mapper 62/62, Distributor/Replay 33/33 incl. 2 new wrap-around cases). Full-suite macOS checkpoint: 769 passed / 44 failed, all 44 the known named-pipe/fake-worker-harness UDS-104-char limit (0 failures in any touched area). Pending: windev run to exercise the frame I/O over a real pipe (the 44 pipe-harness tests are macOS-blind).
2026-07-09 P2 Wave B — SEC-03 (dashboard __Host- cookie prefix) → Done (commit 7d42c85). Conditionally restore the prefix instead of a pure doc fix: DashboardServiceCollectionExtensions PostConfigure resolves the cookie name as explicit MxGateway:Dashboard:CookieName override → else __Host-MxGatewayDashboard when SecurePolicy==Always (RequireHttpsCookie true) → else the plain MxGatewayDashboard default; guard never applies __Host- without Secure (browsers drop it). DashboardAuthenticationDefaults.SecureCookieName added; plain name kept as the non-secure fallback. Five stale docs corrected to the conditional contract (gateway.md, GatewayProcessDesign, ImplementationPlanGateway, GatewayDashboardDesign, CLAUDE.md; GatewayConfiguration phrasing tightened). Test DashboardCookieOptionsTests asserts the name flips with RequireHttpsCookie and that an override wins. Server build clean; Dashboard tests 149/149.
2026-07-09 P2 Wave A (doc-drift sweep + client version alignment) via parallel agents, on fix/archreview-p2 off main. Version alignment (commit 4fe1917): CLI-18 (.NET <Version>0.1.2</Version>), CLI-21 (Go ClientVersion 0.1.0-dev→0.1.2), CLI-26 (Python __version__→0.1.2), CLI-29 (Rust CLIENT_VERSIONenv!(CARGO_PKG_VERSION)) → Done; verified dotnet build + go build/test + cargo check/test/clippy + pytest 127 pass. Doc-drift (commit 06e1046): IPC-06/07/21 + TST-13 (gateway.md envelope sketch → proto-as-truth, six→seven RPCs incl. QueryActiveAlarms in docs/Grpc.md, Session RPC marked not-implemented, stale sketches removed), SEC-09 (dashboard GroupToRole sample Administrator so it passes the validator), SEC-22 (docs/Authentication.md rewritten to the shipped ZB.MOM.WW.Auth.ApiKeys-package pipeline; 18 stale type names removed, grep-verified absent; named gateway types re-verified present), IPC-17 (wrong Python gen dir mxgatewayzb_mom_ww_mxgateway in CLAUDE.md + 3 docs), CLI-12 (Java 21→17), CLI-16 (docs/ClientPackaging.md reconciled with .slnx/Python package name/gradle project names) → Done. Docs-only claims verified against source. 13 findings closed.
2026-07-09 Initial tracking doc generated from the six domain remediation designs. All 153 findings Not started.
2026-07-09 P0 tier executed via parallel agents. GWC-01/02/03, TST-02, TST-12, CLI-01, CLI-03 → Done (NonWindows build clean; gateway 135/135 targeted tests pass, Go go test ./... + -race pass, Rust test/clippy -D warnings pass).
2026-07-09 P0 tier executed via parallel agents. GWC-01/02/03, TST-02, TST-12, CLI-01, CLI-03 → Done (NonWindows build clean; gateway 135/135 targeted tests pass, Go go test ./... + -race pass, Rust test/clippy -D warnings pass).
2026-07-09 Windows full-suite temp-file-lock flakiness (discovered via TST-08) → fixed (commit 11a716a). Two shared-state parallel collisions that macOS never surfaces: (1) self-signed cert generation — SelfSignedCertificateProvider now stages the PFX in a unique <path>.<guid>.tmp (was a fixed name that concurrent/interrupted writers collided on); TestHostEnvironmentInitializer defaults the cert path to a per-process temp dir (so the suite never writes shared ProgramData or fights the deployed service); GatewayTlsBootstrapTests moved to a DisableParallelization collection so its process-global env-var mutation can't bleed into parallel tests. (2) AuthStoreHealthCheckTests — reuse the existing TempDatabaseDirectory helper (SqliteConnection.ClearAllPools() before delete) so the Microsoft.Data.Sqlite pool releases the temp .db handle. windev-verified: 3 consecutive full-suite runs all 793 passed / 2 failed, 0 new orphans (was flaky 24 fails, count-varying). The 2 stable remaining failures are pre-existing windev-environmental (SelfSignedCertificateProviderTests.GenerateCertificate_HasExpectedSansEkuAndValidity machine-name/FQDN SAN assertion; EventStreamServiceTests…TracksAggregateQueueDepth timing) — both pass on macOS, unrelated to this change.
2026-07-09 P1 TST-08 (orphaned testhost) → Done by evidence: the claimed leak does not reproduce. Full ZB.MOM.WW.MxGateway.Tests suite exits cleanly on macOS (0 survivors) and on the Windows dev box (windev, origin/main worktree: 0 new testhost and 0 new ZB.MOM.WW.MxGateway.Worker processes after a full run, isolated by StartTime). Static audit confirms the prime-suspect fixtures already dispose deterministically (WorkerClient cancels its _stopCts + awaits read/write/heartbeat tasks with a 5 s timeout; GatewaySession disposes the client; E2E/harness fixtures use await using). CLAUDE.md "Source Update Workflow" updated to drop the stale "leaves orphaned testhost processes" rationale (filtered runs kept as a speed guideline). Discovered separately (NOT TST-08, still open): Windows-only temp-file-lock flakiness in ~4 full-host test classes — AuthStoreHealthCheckTests (Microsoft.Data.Sqlite connection-pool holds the temp .db handle after await using, so the finally File.Delete throws "used by another process"), GatewayTlsBootstrapTests/DashboardCookieOptionsTests/DashboardHubsRegistrationTests (a gw.pfx.tmp handle from SelfSignedCertificateProvider's fixed-name path+".tmp" atomic-write racing teardown). Count varies run-to-run (24 fails); macOS never sees it (Unix allows deleting open files). Fix = SqliteConnection.ClearAllPools() before temp delete + a unique/guarded .tmp name; verify on windev.
2026-07-09 SEC-10 polish (gateway-side follow-up to the G-2 auth-lib core) → done. apikey create-key gains an optional --expires (absolute ISO-8601 UTC or relative <N>d/<N>h; omit = non-expiring, opt-in) threaded through ApiKeyAdminCommand/parser/runner into the library's CreateKeyAsync(..., expiresUtc, ...); list-keys shows an expiry column + active/expired/revoked status. Dashboard API Keys page surfaces expiry: an Expires column (Never when unset) and a status badge reading Expired (red) / Expiring (≤7 days, amber) / Revoked / Active (DashboardApiKeySummary.ExpiresUtc projected in DashboardSnapshotService; StatusBadge maps the new states). Server build clean; targeted tests 32/32 (parser abs/relative/invalid + end-to-end past-expiry rejection via the live verifier) + dashboard 28/28. Docs: docs/Authentication.md (verification flow expiry step, CLI table + examples, dashboard badge). SEC-10 was already Done (core); this closes the tracked polish.
2026-07-09 P1 Wave 3 (event-channel decoupling). GWC-04 → Done. WorkerClient's read loop awaited EnqueueWorkerEventAsync inline, which blocks in the bounded event channel's timed WriteAsync (≤ EventChannelFullModeTimeout, 5 s) when the channel is full with a slow/absent StreamEvents consumer — stalling any WorkerCommandReply/heartbeat queued behind an event frame, so an in-flight InvokeAsync could hit CommandTimeout despite a timely worker reply. Fix mirrors the existing outbound WriteLoopAsync: an unbounded event staging channel + a dedicated EventWriteLoopAsync; DispatchEnvelope is now fully synchronous and the WorkerEvent branch hands off with a non-blocking TryWrite, so the read loop never awaits event enqueue. The event write loop owns the timed write + the sustained-overflow ProtocolViolation fault (unchanged contract); registered in WaitForBackgroundTasks, completed on close/fault/dispose. Server build clean (0 warnings); non-pipe event tests 37/38 (the 1 is the known parallel-load EventStreamServiceTests…TracksAggregateQueueDepth timing flake — passes in isolation). New pipe-harness test (reply after events with a full consumer-less channel dispatches without CommandTimeout) verified on windev. Docs: GatewayProcessDesign read/write/event-loop section. Commit 32d6b48. Wave 3 complete.
2026-07-09 P1 Wave 3 (size/backpressure topology + write ordering). IPC-02/03/04 + WRK-04/07 → Done. Size negotiation (IPC-02): added GatewayHello.max_frame_bytes (regen Generated/ + clients/proto descriptor refresh with pinned protoc 34.1); the gateway sends its negotiated worker-frame max and the worker adopts it (WorkerFrameProtocolOptions.AdoptNegotiatedMaxMessageBytes, 0 = keep default, >256 MiB rejected) instead of a hard-coded default. Headroom (IPC-03): the pipe frame max now sits EnvelopeOverheadReserveBytes (64 KiB) above the public gRPC cap (default Worker.MaxMessageBytes 16 MiB→16 MiB+64 KiB), cross-validated at startup; WorkerClient pre-checks command envelope size and fails only the offending correlation (ResourceExhausted) instead of SetFaulteding the session. Drain bound (IPC-04): gateway request validator rejects DrainEvents max_events above 10 000; the worker caps each reply at MaxDrainEventsPerReply (10 000) and treats max_events = 0 as that cap, not "drain all". Sequence (WRK-04): WorkerFrameWriter stamps the envelope Sequence at the point of writing under the write lock, so wire order and stamped sequence always agree under concurrent producers. Priority (WRK-07): the worker writer is now a cooperative priority scheduler — control frames (reply/fault/heartbeat/shutdown-ack) drain ahead of event frames; per-frame validation/size rejections fail only that frame, a stream failure fails all queued. Docs same-change (GatewayConfiguration, WorkerFrameProtocol, gateway.md). Verified: macOS NonWindows build clean + validator/grpc tests green; windev x86 worker builds clean, Worker.Tests 352 passed / 0 failed / 11 skipped (incl. new monotonic-sequence, control-before-event priority, negotiated-max, drain-bound tests), gateway Tests 799 passed / 3 failed — all 3 pre-existing windev-environmental (SelfSigned SAN + 2 EventStreamServiceTests timing, both pass in isolation). Commits c8b3a22 (gateway half), ebe6aea (worker half), 309296f (descriptor + default-expectation refresh). GWC-04 (event-channel decoupling) is the remaining Wave 3 item.
2026-07-09 P1 S-misc (dashboard/observability hardening). SEC-02/12/20 → Done. SEC-02: DashboardAuthorizationHandler restricts the loopback + Authentication:Mode=Disabled bypasses to read-only (they satisfy a Viewer-bearing requirement but never AdminOnly), closing the policy-layer gap where anonymous localhost was authorized for Admin surfaces. SEC-12: DashboardSessionAdminService now emits canonical AuditEvents (dashboard-close-session/dashboard-kill-worker, category SessionAdmin) through IAuditWriter on Success/Failure/Denied, so Close/Kill land durable audit rows. SEC-20: dropped the unbounded session_id tag from the exported mxgateway.heartbeats.failed counter. Docs updated same-change (CLAUDE.md, GatewayDashboardDesign.md, Metrics.md). Server build clean (0 warnings); targeted classes 30/30 pass; broader Dashboard+Security+GatewayApplication+Metrics sweep 295/295 pass.
2026-07-09 P1 Wave 2b (security authz+hub). SEC-05/07/08/11 → Done (hub-token lifetime; QueryActiveAlarms scope arm; gateway-side verification cache + last-used coalescing; login rate limit + per-peer gRPC failure limiter). Full-suite checkpoint caught + fixed regressions the earlier narrow SEC-01/04/06 filter missed: cross-platform path-rooting, an IHostEnvironment fallback for minimal DI containers, a test-assembly ASPNETCORE_ENVIRONMENT=Development default, and a platform-correct default-path assertion. Suite: 747 passed / 42 failed, all 42 pre-existing macOS named-pipe-harness env failures.
2026-07-09 P1 Wave 2a (security). SEC-01/04/06 → Done (config path-rooting + production validator guards; Server build clean, validator+hygiene tests 53/53). SEC-10 → Done: the shared ZB.MOM.WW.Auth.ApiKeys gained optional ExpiresUtc (expired keys rejected, auth DB auto-migrates to schema v3) via a concurrent HistorianGateway-remediation session's "G-2"; this repo consumes it by bumping the four Auth.* refs 0.1.2→0.1.4 (commit 197731a; since bumped to 0.1.5 in e107019 — 0.1.4 plus a transitive SQLitePCLRaw security pin, no API change, expiry enforcement retained). Remaining SEC-10 polish (apikey create --expires + dashboard staleness badge) tracked as a small follow-up.
2026-07-09 P1 Wave 1 (CI + codegen freshness + Rust buildability) via parallel agents. IPC-01/09/19/20, CLI-02 → Done; TST-03 → In review (CI pipeline authored + YAML/layout-validated, but not yet executed on a Gitea runner — proven on first push). Verified on macOS: NonWindows build clean, ClientProtoInputTests 5/5, publish-client-proto-inputs.ps1 -Check exit 0, cargo package (no --no-verify) compiles standalone. Added a vendored-Rust-proto drift guard (Check 3) to check-codegen.ps1 closing the CLI-02 static-copy risk. IPC-09 script guards not executed end-to-end (need pinned python/JRE toolchains); logic is PATH-resolution + version assertion.
2026-07-09 WRK-01 → Done. Verified on Windows host (windev) via an isolated origin/main worktree: worker builds x86 clean, StaRuntimeTests+WorkerPipeSessionTests 33/33 pass. Fixed an xUnit1030 build error (the new worker test used .ConfigureAwait(false) in [Fact] bodies) that the macOS tree could not surface. Also ran GWC-01's Windows-only WorkerClientTests on windev: 18/18 pass (incl. ReadEventsAsync_SecondEnumerator_Throws). All 8 P0 findings now Done. Not yet committed.
2026-07-10 TST-03 → Done: CI is live and green on branch fix/ci-selfhosted-tooling. The pipeline was authored (P1) but had never executed. Root cause it never ran: the co-located gitea-runner on the docker host (10.100.0.35) spawned job containers on an isolated network (container.network: "") that could not resolve Gitea's internal clone URL http://gitea:3000; one-line host fix container.network: "traefik" + docker restart gitea-runner. The self-hosted catthehacker act image also lacks tooling GitHub-hosted runners preinstall — ci.yml now installs pwsh (dotnet global tool, for check-codegen.ps1) and Gradle 9.5.1 directly (act can't resolve the gradle/actions monorepo action; no gradle wrapper in repo). Driving to green surfaced and fixed five real latent defects (TST-03 doing its job): check-codegen.ps1 .Trim()-on-$null on a clean tree; stale vendored rust proto (clients/rust/protos/mxaccess_worker.proto missing canonical max_frame_bytes); OrphanWorkerTerminatorTests hard-coded C:\ path failing Linux Path.GetFullPath (0 kills); stale java generated MxaccessWorker.java (missing max_frame_bytes, regenerated); a sync python test building a grpc.aio.Channel with no current event loop on py3.12 (autouse conftest fixture). Result: portable success (NonWindows build + codegen freshness + 808/808 gateway tests + .NET/Go/Rust/Python clients) and java success. windows + live-mxaccess jobs remain queued pending a self-hosted windev runner (10.100.0.48) with those labels — separate follow-up, does not gate portable/java.