59856b8c63
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.
24 KiB
24 KiB
Testing, Documentation & Underdeveloped Areas — Architecture Review
Scope & method
This review covers test architecture (src/ZB.MOM.WW.MxGateway.Tests, src/ZB.MOM.WW.MxGateway.Worker.Tests, src/ZB.MOM.WW.MxGateway.IntegrationTests, scripts/), documentation currency (gateway.md, docs/Sessions.md, docs/GatewayConfiguration.md, docs/DesignDecisions.md, docs/GatewayTesting.md, CLAUDE.md, repo-root working artifacts), and repo-wide underdeveloped areas (TODO sweep, half-shipped features, operational gaps). Method is static reading of code and docs on the macOS tree; no test suites were run. Generated code, bin/, and obj/ are excluded. All paths are relative to /Users/dohertj2/Desktop/MxAccessGateway.
Executive summary
- Test coverage of the gateway core is strong and unusually well-layered: 46 gateway test classes, 34 worker test classes, and an 8-scenario opt-in live MXAccess suite map cleanly onto session lifecycle, pipe framing, event fan-out/replay, auth, and dashboard. The
FakeWorkerHarnessreuses the productionWorkerFrameReader/WorkerFrameWriter, so fake tests exercise real frame validation rather than a mock transport. - There is no CI configuration anywhere in the repository — no
.github/,.gitea/, or pipeline files. Every documented verification step (per-task filtereddotnet test, five client toolchains, opt-in live matrices) is manual operator discipline; nothing prevents a cross-component regression from landing onmainuntested. - The session-resilience epic is half-shipped: 12 of 28 tasks are merged (
oldtasks.md), leaving reconnect owner re-validation (a security gap), client-sideReplayGaphandling in all five clients, per-session dashboard ACL, and orphan-worker reattach pending. The server emits a reconnect protocol no client yet understands. - Fake-vs-real fidelity has one structural blind spot by design:
FakeWorkerHarnesscannot simulate STA behavior, COM faults, process crash, or MXAccess semantics — those live only in the opt-inWorkerLiveMxAccessSmokeTestsand dev-rig probes, which never run automatically. - Timing-based tests are mostly disciplined (
ManualTimeProvider, cancellation-token-bounded polls), but a handful of real-clock sleeps followed by negative assertions remain (WorkerClientTests.cs:433,SessionManagerTests.cs:402) and are latent flakes under load. The known macOSSessionWorkerClientFactoryFakeWorkerTeststimeout failures are pre-existing and environmental. - Documentation currency is better than typical:
docs/GatewayConfiguration.mddefaults matchConfiguration/*.csexactly, anddocs/Sessions.md/docs/DesignDecisions.mdreflect the shipped reconnect/fan-out behavior. The stale spots are concentrated ingateway.md(design-eraWorkerEnvelopesketch, single-subscriber policy line, never-builtSessionbidi RPC) and one materially wrong sentence in CLAUDE.md about default retention. - Repo-root hygiene is mixed:
stillpending.mdandoldtasks.mdare tracked, annotated snapshots that still serve as the de-facto backlog (there is no issue tracker in evidence); the five untracked*-docs-{issues,fixed,final}.mdartifacts (15k+ lines) are dead local working files;code-reviews/+REVIEW-PROCESS.mdare a completed, self-consistent review system with zero open findings. - Operationally the gateway is under-instrumented for a service: one health check (auth-store) with no Galaxy SQL, LDAP, alarm-monitor, or worker-launchability checks; hand-rolled NSSM deployment with config living in service env vars outside the repo; and no version stamping on the server/worker assemblies while client packages drift (Java 0.2.0 vs 0.1.2 everywhere else).
Findings
Coverage map
| Component | Unit | Fake-worker integration | Live integration | Gap notes |
|---|---|---|---|---|
| Session lifecycle (open/close/lease/detach-grace) | SessionManagerTests, GatewaySessionTests |
GatewayEndToEndFakeWorkerSmokeTests, SessionWorkerClientFactoryFakeWorkerTests |
WorkerLiveMxAccessSmokeTests |
SessionLeaseMonitorHostedService timer loop itself untested (sweep logic covered via CloseExpiredLeasesAsync tests at SessionManagerTests.cs:859-930) |
| Worker launch / validation / orphan cleanup | WorkerProcessLauncherTests, WorkerExecutableValidatorTests, OrphanWorkerTerminatorTests |
scripted fake launcher | live x86 launch | OrphanWorkerCleanupHostedService wrapper untested; reattach not implemented (epic Phase 5) |
| Pipe framing / IPC envelope | WorkerFrameProtocolTests (both projects), WorkerClientTests, WorkerPipeClientTests, WorkerPipeSessionTests |
FakeWorkerHarnessTests incl. malformed/oversized frames |
live | good — malformed payloads and oversized headers explicitly scripted |
| Worker crash / heartbeat / fault | WorkerClientTests with ManualTimeProvider |
fault frames via harness | abnormal-exit kill test | real process crash covered only opt-in |
| Event streaming / fan-out / replay | EventStreamServiceTests (15 tests), SessionEventDistributorTests (22) |
GatewayEndToEndMultiSubscriberTests |
stream phase of live smoke | no end-to-end reconnect/replay fake-worker test (epic Task 15 pending); no client handles ReplayGap (Task 14) |
| Backpressure | distributor/stream tests | overflow via harness | e2e script drain-loop workaround | policy interplay (FailFast vs multi-subscriber degrade) unit-covered only |
| Auth (API keys, scopes, audit) | ApiKeyVerifierTests, SqliteAuthStoreTests, GatewayGrpcAuthorizationInterceptorTests, ConstraintEnforcerTests, audit tests |
— | e2e script auth-rejection phase (opt-in) | good |
| Dashboard | ~17 test classes under Gateway/Dashboard/ |
— | DashboardLdapLiveTests (opt-in) |
DashboardLiveDataService, EventsHub hub methods, AlarmsHubPublisher, DashboardHubConnectionFactory have no direct tests |
| Alarms (consumer, failover, monitor) | FailoverAlarmConsumerTests, SubtagAlarmStateMachineTests, GatewayAlarmMonitorProviderModeTests, AlarmFailoverEndToEndTests |
fake alarm service | dev-rig probes ([Fact(Skip=...)]) |
live failover undrivable on rig; provider_switches metric never live-exercised; subtag Clear unvalidated |
| Worker STA / COM / conversion | StaMessagePumpTests, StaCommandDispatcherTests, MxAccessCommandExecutorTests, VariantConverterTests, etc. |
— | MxAccessLiveComCreationTests (opt-in) |
multi-sample OnBufferedDataChange conversion never observed live (stillpending.md §3.2) |
| Galaxy browse | GalaxyFilterInputSafetyTests, GalaxyRepositoryHostWiringTests |
— | GalaxyRepositoryLiveTests (opt-in) |
core browse logic now lives in the external ZB.MOM.WW.GalaxyRepository package |
| Clients (×5) | dotnet 13 / go 8 / rust 3 / python 13 / java 7 test files | — | scripts/run-client-e2e-tests.ps1 only |
no per-client wire-level integration tests; all client↔gateway behavior unverified in default runs |
Test Coverage
- High — Reconnect/replay has no end-to-end test and no client-side consumer. The server emits the
ReplayGapsentinel and replays the ring (docs/Sessions.md"Reconnect and replay"), unit-covered insrc/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/EventStreamServiceTests.csandMxAccessGatewayServiceTests.cs, but the epic's fake-worker reconnect integration test (Task 15) and clientReplayGaphandling in all five clients (Task 14) are pending peroldtasks.md:40-44. Impact: the shipped, default-on reconnect protocol (DetachGraceSeconds=30, ReplayBufferCapacity=1024) is unproven end-to-end and unusable by every official client. Recommendation: land Tasks 14–15 before advertising reconnect; until then document it as server-only. - High — Reconnect owner re-validation is not implemented. Epic Task 13 ("Owner re-validation on reconnect",
oldtasks.md:40) is pending, so nothing ties a resumingStreamEventscall to the API key that opened the session beyond theeventscope and knowledge of the session id. Impact: with fan-out or detach-grace enabled, any event-scoped key that learns a session id can attach to another key's session and receive its replayed data. Recommendation: prioritize Task 13; it is a security control, not a resilience feature. - Medium — Real-worker control/COM behavior is only verified opt-in. All eleven late-added command kinds are unit-tested against fakes and live-verified once on the dev rig (
stillpending.md§1.1), but the default suite exercisesPing/GetWorkerInfo/DrainEvents/ShutdownWorkeronly throughFakeWorkerHarness.RespondToControlCommandAsync(src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/Fakes/FakeWorkerHarness.cs:417-513), which returns canned replies. Impact: a worker-side regression in these paths is invisible until someone setsMXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1. Recommendation: schedule the live suite on the windev box on a cadence (see CI finding). - Medium — Dashboard live-data path untested.
DashboardLiveDataService(owns the shared lazily-opened gateway session backing/browselive values, pergateway.md:128-131) has no test class;EventsHub/AlarmsHubPublisherhub methods are likewise untested. Impact: the one dashboard component that holds a real worker session and can fault it has no regression net. Recommendation: add a fake-worker-backed test for session reuse, fault recovery, and disposal. - Low — Hosted-service wrappers untested.
SessionLeaseMonitorHostedServiceandOrphanWorkerCleanupHostedServicedelegate to well-tested cores but their timer/startup wiring has no tests. Impact: low; failure mode is obvious at startup.
Test Quality
- Medium — Real-clock sleeps with negative assertions are latent flakes.
src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs:433sleeps 150 ms of wall time ("give the heartbeat monitor a few real check-intervals") then asserts state did not change;src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs:402races a 50 ms delayed state flip against a 500 ms bounded wait. 47Thread.Sleep/Task.Delayoccurrences exist across 22 test files; most are benign (cancellation-bounded polls,Timeout.InfiniteTimeSpanhang simulators,ManualTimeProviderused for the actual clock), but the fixed-real-time ones can pass spuriously or fail under load. Impact: intermittent failures erode trust in an already manual test regime. Recommendation: convert the negative-assertion sleeps toManualTimeProvider-driven check-interval pumping or polling-until-stable. - Medium — Full-suite orphaned testhost processes remain an unfixed hygiene defect. CLAUDE.md ("Source Update Workflow") documents that the full gateway suite "leaves orphaned testhost processes" and mandates filtered runs as a workaround. Impact: the workaround is procedural, so any future CI or an unaware contributor inherits zombie processes; it also suggests undisposed pipe servers or hosted services in some fixture. Recommendation: identify the leaking fixture (likely a
NamedPipeServerStreamor hosted-service test without disposal) rather than institutionalizing the workaround. The macOSSessionWorkerClientFactoryFakeWorkerTeststimeout-message failures are pre-existing platform noise (wrong timeout message on macOS) and are correctly excluded from this finding. - Low —
FakeWorkerHarnessfidelity is good within its charter. It sharesWorkerFrameReader/WorkerFrameWriter/WorkerEnvelopewith production (FakeWorkerHarness.cs:36-37), scripts malformed payloads and oversized headers (:519-553), heartbeats, faults, and shutdown acks, and mirrors real control-command reply shapes. What it structurally cannot represent — STA pumping, COM HRESULT semantics, process exit codes, event timing under load — is honestly delegated to the live suite bydocs/GatewayTesting.md. No action needed beyond keeping its canned replies in lockstep withWorkerPipeSession(the §1.1 history shows this drifted once: green fakes masked an unimplemented real worker for months). - Low — The e2e script embeds a workaround for a real product sharp edge.
scripts/run-client-e2e-tests.ps1(1,715 lines) must interleaveStreamEventsdrains every 15 advised tags because advising without a consumer overflows the worker event channel and faults the session underFailFast(docs/GatewayTesting.md"Client E2E Scripts" phase 3). Impact: the test harness papering over this documents that real clients doing bulk-advise-then-stream will fault sessions. Recommendation: treat this as product feedback (e.g., subscribe-time channel policy), not just a script detail.
CI & Operations
- High — No CI exists. There is no
.github/,.gitea/,azure-pipelines.yml,Jenkinsfile, or any pipeline file in the repository.docs/ImplementationPlanIndex.mdnames a Gitea repo and apackaging-and-opsmilestone, but nothing automated runs the .NET, Go, Rust, Python, or Java suites on push. Impact: the multi-language, multi-target (net10/net48-x86) build matrix is exactly the kind that silently breaks; the repo's own history (net48 CS0246 on unregenerated protos, Java generated-file churn) shows cross-component breakage is routine. Recommendation: a minimal Gitea Actions (or runner-on-windev) pipeline that buildsNonWindows.slnx+ runs the gateway suite on every push, with a Windows job for the x86 worker, would catch the majority class of regressions; add a scheduled opt-in live-MXAccess job on the dev rig. - Medium — Health checks cover only the auth store.
src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs:71-75registers a singleAuthStoreHealthCheck; there is no readiness check for Galaxy SQL reachability, LDAP bind, alarm-monitor session health, or worker-executable presence/launchability, even though each is a documented startup dependency. Impact:/healthreports ready while the gateway cannot open a session or browse Galaxy. Recommendation: add tagged checks for worker exe validation (cheap, reusesWorkerExecutableValidator) and Galaxy/LDAP with caching. - Medium — Deployment and upgrade are undocumented in-repo and hand-rolled. Deployments are NSSM-wrapped services with configuration held in NSSM environment variables (acknowledged in
docs/GatewayConfiguration.md"Host Endpoints" and the project memory notes);scripts/contains packaging and e2e scripts but no deploy/upgrade script, and there is no runbook for theMxAccessGw→OtOpcUaservice-dependency dance. Impact: deploys are tribal knowledge; a second operator cannot reproduce them from the repo. Recommendation: commit adocs/Deployment.mdplus a publish/deploy script that captures the NSSM env-var config as code. - Medium — No version discipline on server artifacts; client versions drift. The Server and Worker csproj files carry no
Versionproperty (assemblies stamp 1.0.0), Contracts is0.1.2(src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj:10), Python and Rust clients are0.1.2(clients/python/pyproject.toml:9,clients/rust/Cargo.toml:3), while Java is0.2.0(clients/java/build.gradle:16) — and the Java bump required a separately-maintainedCLIENT_VERSIONconstant (commitb6a0d90fixed a mismatch). Impact: support cannot correlate a deployed gateway or a client wheel to a commit; duplicated version constants have already drifted once. Recommendation: single-source versions (Directory.Build.props for the .NET side; generate client version constants at build) and stamp the server assembly informational version with the git SHA. - Low — Log rotation is configured but minimal.
src/ZB.MOM.WW.MxGateway.Server/appsettings.json:8-11rolls the file sink daily (logs/mxgateway-.log, a path relative to the service working directory) with no explicitretainedFileCountLimitor size cap (Serilog defaults to 31 files, unbounded size per day). Impact: acceptable, but a high-rate event day can produce an unbounded single file. Recommendation: setfileSizeLimitBytes+rollOnFileSizeLimitand an absolute log path for the service deployment.
Documentation Currency
- Medium — CLAUDE.md misstates the default retention behavior. CLAUDE.md ("Repository-Specific Conventions") says "Default config preserves the original single-subscriber, no-retention behavior," but the code defaults enable retention:
DetachGraceSeconds = 30(src/ZB.MOM.WW.MxGateway.Server/Configuration/SessionOptions.cs:47) andReplayBufferCapacity = 1024/ReplayRetentionSeconds = 300(Configuration/EventOptions.cs:11-31). OnlyAllowMultipleEventSubscribersdefaults off. Impact: an agent or operator following CLAUDE.md will assume detached sessions die immediately and no events are buffered. Recommendation: reword to "single-subscriber by default; detach-grace and replay retention are on by default." - Medium — gateway.md carries design-era sketches that no longer match the wire contract. The
WorkerEnvelopesnippet (gateway.md:291-309) showsuint64 correlation_id = 4and body tagscommand = 20 … fault = 26with noWorkerShutdownAck; the actual proto usesstring correlation_id = 4andgateway_hello = 10 … worker_fault = 20includingworker_shutdown_ack = 17(src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto:19-38). Similarlygateway.md:738still states the gateway "allow[s] one active client event subscriber per session, reject[s] a second subscriber" as unconditional policy, contradicted by the config-gated fan-out described later in the same file (:1177-1180) and bydocs/Sessions.md. The public-API sketch (gateway.md:328-334) includesrpc Session(stream ClientMessage)which was never implemented (the real service has no bidi RPC —mxaccess_gateway.proto:18-37). Impact: gateway.md is the mandated pre-change reading; internally inconsistent sections cost every reader a reconciliation pass. Recommendation: replace the envelope sketch with a pointer to the proto, mark the single-subscriber paragraph as the default mode, and labelSessionas unbuilt future work. - Low — GatewayConfiguration.md, Sessions.md, DesignDecisions.md, and GatewayTesting.md are current. Spot-checks confirm every table default in
docs/GatewayConfiguration.mdmatchesConfiguration/{SessionOptions,EventOptions,DashboardOptions,AuthenticationOptions,WorkerOptions}.cs;docs/DesignDecisions.md:63-118correctly records the superseded reconnect/fan-out constraints;docs/GatewayTesting.md"all eight tests" matches the 8[LiveMxAccessFact]attributes inWorkerLiveMxAccessSmokeTests.cs. The only nit: the Configuration Shape JSON block (docs/GatewayConfiguration.md:12-83) omitsDisableLogin/AutoLoginUser/CookieName,Tls, and theAlarms:Fallbackblock that the tables below it document. - Medium — Repo-root working artifacts need triage. Classification by evidence:
MxAccessGateway-docs-{issues,fixed,final}.mdandMxGatewayClient-docs-{issues,fixed}.md— untracked (gitignored via*-docs-issues.md,.gitignore:152; the others simply never committed), 15,416 lines in the largest. These are dead local outputs of a finished docs-review pass; delete or move out of the working tree so greps and agents stop tripping over them.stillpending.md(tracked, last touched 2026-06-25) — a 2026-06-15 audit snapshot with resolution annotations that currently serves as the project's only backlog register. Living but structurally a snapshot; either promote it to a maintained register (drop "Generated: … Commit:" framing) or migrate open items (§1.3, §1.4, §3.x, epic remainder) to the Gitea tracker thatdocs/ImplementationPlanIndex.mddescribes.oldtasks.md(tracked, 2026-06-16) — explicitly a "human-readable mirror" ofdocs/plans/2026-06-15-session-resilience.md.tasks.json; keep only until the epic resumes, then delete.REVIEW-PROCESS.md+code-reviews/— a coherent, generated-index review system, all 351 findings resolved,Server/Testsre-reviewed 2026-06-25. Living docs; keep.A2-galaxyrepository-adoption-handoff.md— completed-migration handoff referenced from CLAUDE.md; archive candidate underdocs/plans/once its cross-repo follow-ups land.
Underdeveloped Areas
- High — Session-resilience epic is 16/28 tasks unfinished with security-relevant remainder. Pending per
oldtasks.md:39-63: owner re-validation (Task 13), client ReplayGap handling (14), reconnect integration test (15), per-session dashboard ACL (16–19), orphan-worker reattach (20–28, incl. theEnableOrphanReattachflag that does not exist yet). Impact: the repo advertises reconnect and fan-out as shipped while their trust boundary and client support are absent. Recommendation: finish Phase 3 (13–15) as one unit; re-scope Phases 4–5 explicitly if they are no longer planned. - Medium — Dashboard EventsHub has no per-session ACL.
src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/EventsHub.cs:39(TODO(per-session-acl)): any authenticated dashboard Viewer may join anysession:{id}group and observe that session's event stream. The remark documents why this is accepted today; it is the only production-source TODO in the repo. Impact: acceptable for single-tenant dashboards, wrong the momentGroupToRoleadmits low-trust viewers. Recommendation: covered by epic Phase 4; keep the TODO until then. - Medium —
Dashboard:ShowTagValuesis a dead flag. It is bound (Configuration/DashboardOptions.cs:62), projected into effective configuration, and displayed on the settings page (Dashboard/Components/Pages/SettingsPage.razor:65), but gates no value-display behavior anywhere;docs/GatewayConfiguration.md:174calls it "Reserved". Impact: operators toggling it see no effect. Recommendation: implement or remove; a config option that renders on the settings page implies function. - Medium — Vendor-gated alarm parity residuals are silently lossy. The 8-arg
AlarmAckByNameoperatordomain/full_namefields are accepted on the wire and discarded (src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs:261-278, vendor stub returns −55);AlarmAckByGUIDisE_NOTIMPLon this AVEVA build; theprovider_switchesmetric's reason tagging has never been exercised live (stillpending.md§1.3–1.4, §3.4–3.5). Impact: contract fields that do nothing, invisible to callers. Recommendation: surface the drop in the ack reply diagnostic message until AVEVA implements the stub. - Low — The bidirectional
SessionRPC from the original design was never built.gateway.md:328-345presents it as the "best long-term shape"; the service stopped at unary + server-streaming. Impact: none functionally; it is only a docs-currency and roadmap-clarity issue (see Documentation Currency). - Low — Client wire behavior has no automated verification. All five clients have unit tests (13/8/3/13/7 test files for dotnet/go/rust/python/java) but no in-process or containerized gateway integration tests; the only cross-language verification is the operator-run
scripts/run-client-e2e-tests.ps1against a live rig. Impact: a gateway contract change can pass every default suite and break all five clients (mitigated somewhat by shared-proto codegen andCrossLanguageSmokeMatrixTestsshape checks). Recommendation: an in-process fake gateway (the Java CLI already hasInProcessGatewayHarnessperstillpending.md§8) is the cheapest pattern to replicate per client.
Top 5 recommendations
- Stand up minimal CI. One pipeline that builds
NonWindows.slnxand runs the gateway suite per push, a Windows/windev job for the x86 worker + worker tests, and a scheduled opt-in live-MXAccess run. This single change converts most findings above from "process risk" to "caught automatically." - Finish session-resilience Phase 3 (Tasks 13–15). Owner re-validation is a security control for a default-on retention window; client
ReplayGaphandling and the reconnect integration test make the already-shipped server protocol real. - Fix the two documentation defects that actively mislead: the CLAUDE.md "no-retention default" sentence and gateway.md's stale
WorkerEnvelope/single-subscriber/Sessionsections (replace sketches with pointers to the protos anddocs/Sessions.md). - Triage the repo root: delete the untracked
*-docs-*.mdartifacts, retireoldtasks.mdinto the plan's tasks.json, and either maintainstillpending.mdas a living register or migrate its open items into the Gitea tracker the plans already assume. - Harden operations: add Galaxy/LDAP/worker-executable health checks next to the existing auth-store check, commit a deployment runbook + script capturing the NSSM env-var configuration, single-source versions across server, contracts, and clients, and fix (rather than route around) the orphaned-testhost fixture leak.