docs(audit): cluster findings fragments (13 clusters, read-only verification)

This commit is contained in:
Joseph Doherty
2026-06-03 15:35:46 -04:00
parent 117936e6fd
commit 3081b80efc
13 changed files with 5286 additions and 0 deletions
+443
View File
@@ -0,0 +1,443 @@
# Cluster 01 — Architecture
DOC: gateway.md
LINES: 737769
CLAIM: Project layout lists `src/MxGateway.Server`, `src/MxGateway.Worker`, `src/MxGateway.Contracts`, `src/MxGateway.Tests`, `src/MxGateway.Worker.Tests`, `src/MxGateway.IntegrationTests` as suggested path names.
CLAIM_TYPE: path
VERDICT: stale
EVIDENCE: src/ directory listing — actual project directories are `ZB.MOM.WW.MxGateway.Server`, `ZB.MOM.WW.MxGateway.Worker`, `ZB.MOM.WW.MxGateway.Contracts`, `ZB.MOM.WW.MxGateway.Tests`, `ZB.MOM.WW.MxGateway.Worker.Tests`, `ZB.MOM.WW.MxGateway.IntegrationTests`
CODE_AREA: arch.layout
SEVERITY: medium
PROPOSED_FIX: Replace all short project names in the layout block with the fully-qualified names (e.g. `src/ZB.MOM.WW.MxGateway.Server/`, `src/ZB.MOM.WW.MxGateway.Worker/`, etc.).
---
DOC: gateway.md
LINES: 231248
CLAIM: `WorkerEnvelope` has `uint64 correlation_id = 4` and oneof body field numbers: `worker_hello=10, gateway_hello=11, worker_ready=12, command=20, command_reply=21, event=22, heartbeat=23, cancel=24, shutdown=25, fault=26`.
CLAIM_TYPE: rpc/proto
VERDICT: wrong
EVIDENCE: src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto:4,2038 — actual proto has `string correlation_id = 4` (not uint64); body fields are `gateway_hello=10, worker_hello=11, worker_ready=12, worker_command=13, worker_command_reply=14, worker_cancel=15, worker_shutdown=16, worker_shutdown_ack=17, worker_event=18, worker_heartbeat=19, worker_fault=20`; field names also differ (e.g. `command``worker_command`, `event``worker_event`).
CODE_AREA: arch.ipc
SEVERITY: high
PROPOSED_FIX: Replace the WorkerEnvelope protobuf block in gateway.md with the actual proto content from `mxaccess_worker.proto`, including the correct field type for `correlation_id` (string), the correct field numbers, and the correct field names. Also add the missing `WorkerShutdownAck worker_shutdown_ack = 17` entry.
---
DOC: gateway.md
LINES: 898913
CLAIM: Session state machine is `Creating -> StartingWorker -> WaitingForPipe -> InitializingWorker -> Ready -> Closing -> Closed -> Faulted`.
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs:75 — code transitions to `SessionState.Handshaking` between `WaitingForPipe` and `InitializingWorker`; this state also appears in the generated proto enum (`MxaccessGateway.cs:726`, `SESSION_STATE_HANDSHAKING = 4`).
CODE_AREA: arch.session
SEVERITY: medium
PROPOSED_FIX: Add `-> Handshaking` between `WaitingForPipe` and `InitializingWorker` in the state machine diagram, and add a description: "`Handshaking`: pipe is connected and protocol hello is being verified."
---
DOC: gateway.md
LINES: 119121
CLAIM: Blazor dashboard mounts at the host root and renders pages at `/`, `/sessions`, `/workers`, `/events`, `/galaxy`, `/alarms`, `/apikeys`, and `/settings`.
CLAIM_TYPE: path
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Pages/BrowsePage.razor:1 — there is also a `/browse` page (`@page "/browse"`) that is not listed. `/login` is also present.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: Add `/browse` (and `/login`) to the list of documented dashboard routes.
---
DOC: gateway.md
LINES: 662663
CLAIM: Rejects valid keys lacking the required `session, invoke, event, metadata, or admin` scope with gRPC `PermissionDenied`.
CLAIM_TYPE: config-key
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayScopes.cs:512 — actual scopes are `session:open`, `session:close`, `invoke:read`, `invoke:write`, `invoke:secure`, `events:read`, `metadata:read`, `admin`. The simplified short-form names (`session`, `invoke`, `event`) do not match the canonical scope strings.
CODE_AREA: arch.auth
SEVERITY: medium
PROPOSED_FIX: Replace the simplified scope names with the canonical forms: `session:open`, `session:close`, `invoke:read`, `invoke:write`, `invoke:secure`, `events:read`, `metadata:read`, `admin`.
---
DOC: docs/DesignDecisions.md
LINES: 360363
CLAIM: "Dashboard access should require API-key-backed dashboard authentication with `admin` scope when enabled."
CLAIM_TYPE: behavior-rule
VERDICT: wrong
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs:9 — dashboard authentication is LDAP-backed (bind + group-to-role mapping), not API-key-backed. This is also confirmed in `GatewayProcessDesign.md` lines 291299 and `gateway.md` lines 147156.
CODE_AREA: arch.auth
SEVERITY: high
PROPOSED_FIX: Replace "API-key-backed dashboard authentication with `admin` scope" with "LDAP-backed authentication with `GroupToRole` mapping to `Admin` or `Viewer` roles." Keep the note about `AllowAnonymousLocalhost` for local development.
---
DOC: docs/GatewayProcessDesign.md
LINES: 249255
CLAIM: Dashboard suggested routes use a `/dashboard` prefix: `/dashboard`, `/dashboard/sessions`, `/dashboard/sessions/{sessionId}`, `/dashboard/workers`, `/dashboard/events`, `/dashboard/settings`.
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Pages/ — actual Blazor pages are mounted at `/` (DashboardHome.razor), `/sessions` (SessionsPage.razor), `/sessions/{SessionId}` (SessionDetailsPage.razor), `/workers` (WorkersPage.razor), `/events` (EventsPage.razor), `/settings` (SettingsPage.razor), `/alarms` (AlarmsPage.razor), `/galaxy` (GalaxyPage.razor), `/browse` (BrowsePage.razor), `/apikeys` (ApiKeysPage.razor). None have a `/dashboard` prefix.
CODE_AREA: arch.layout
SEVERITY: high
PROPOSED_FIX: Replace the `/dashboard`-prefixed route table with the actual routes: `/`, `/sessions`, `/sessions/{sessionId}`, `/workers`, `/events`, `/alarms`, `/galaxy`, `/browse`, `/apikeys`, `/settings`.
---
DOC: docs/GatewayProcessDesign.md
LINES: 689
CLAIM: "`Dashboard:AllowAnonymousLocalhost` permits loopback requests to bypass the cookie requirement."
CLAIM_TYPE: config-key
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs:9 — property is `AllowAnonymousLocalhost` under `DashboardOptions`, which maps to `MxGateway:Dashboard:AllowAnonymousLocalhost`. The shorthand `Dashboard:AllowAnonymousLocalhost` omits the root `MxGateway:` prefix used throughout the project (also confirmed in GatewayProcessDesign.md line 298 which correctly uses `MxGateway:Dashboard:AllowAnonymousLocalhost`).
CODE_AREA: arch.config
SEVERITY: low
PROPOSED_FIX: Standardize to `MxGateway:Dashboard:AllowAnonymousLocalhost` (the form used in GatewayOptions / the configuration section name) everywhere this key is referenced.
---
DOC: docs/GatewayProcessDesign.md
LINES: 854855
CLAIM: Worker `ExecutablePath` default is `src/ZB.MOM.WW.MxGateway.Worker/bin/x86/Release/ZB.MOM.WW.MxGateway.Worker.exe` (forward-slash path shown in JSON block).
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs:7 — actual default is `src\ZB.MOM.WW.MxGateway.Worker\bin\x86\Release\ZB.MOM.WW.MxGateway.Worker.exe` (backslashes on Windows). The path and filename match; only the separator style differs between the JSON doc sample and the C# literal.
CODE_AREA: arch.config
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/DesignDecisions.md
LINES: 36
CLAIM: Interop assembly identity: `ArchestrA.MxAccess, Version=3.2.0.0, PublicKeyToken=23106a86e706d0ae`.
CLAIM_TYPE: version
VERDICT: unverifiable
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessInteropInfo.cs — the file records the assembly path and name (`ArchestrA.MxAccess`) but does not hard-code the version or public key token; `InteropAssemblyVersion` is read dynamically from the loaded assembly at runtime (`typeof(LMXProxyServerClass).Assembly.GetName().Version`). Cannot verify the exact version string without MXAccess installed.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/DesignDecisions.md
LINES: 3648
CLAIM: COM class `ArchestrA.MxAccess.LMXProxyServerClass`, CLSID `{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}`, ProgID `LMXProxy.LMXProxyServer.1`, version-independent ProgID `LMXProxy.LMXProxyServer`, registered server `C:\Program Files (x86)\ArchestrA\Framework\Bin\LmxProxy.dll`, interop assembly `C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll`.
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessInteropInfo.cs:14,19,24,2930,3536,41 — `ComClassName = "ArchestrA.MxAccess.LMXProxyServerClass"`, `Clsid = "{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}"`, `ProgId = "LMXProxy.LMXProxyServer.1"`, `VersionIndependentProgId = "LMXProxy.LMXProxyServer"`, `RegisteredServerPath = @"C:\Program Files (x86)\ArchestrA\Framework\Bin\LmxProxy.dll"`, `InteropAssemblyPath = @"C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll"`. All match.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/DesignDecisions.md
LINES: 55
CLAIM: Worker should reference `ArchestrA.MXAccess.dll` (upper-case MXAccess in filename).
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/ZB.MOM.WW.MxGateway.Worker.csproj:27 — `<HintPath>C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll</HintPath>`. Matches.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 8894
CLAIM: Gateway runtime is `.NET 10`, `C#`, `x64 preferred`, `ASP.NET Core gRPC server`.
CLAIM_TYPE: version
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj:4 — `<TargetFramework>net10.0</TargetFramework>`; no explicit `<PlatformTarget>` is set (so the default is AnyCPU/x64-preferred on .NET 10). Grpc.AspNetCore is referenced. Matches.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 162165
CLAIM: Worker runtime is `.NET Framework 4.8`, `C#`, `x86 build by default`.
CLAIM_TYPE: version
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/ZB.MOM.WW.MxGateway.Worker.csproj:57 — `<TargetFramework>net48</TargetFramework>`, `<PlatformTarget>x86</PlatformTarget>`, `<Prefer32Bit>true</Prefer32Bit>`. Matches.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 198210
CLAIM: Pipe name format is `mxaccess-gateway-{gatewayProcessId}-{sessionId}` and framing is `uint32 little-endian payload_length` followed by `payload_length bytes protobuf WorkerEnvelope`.
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:433 — `string pipeName = $"mxaccess-gateway-{Environment.ProcessId}-{sessionId}"`. Framing confirmed by `WorkerFrameReader.cs` and `WorkerFrameWriter.cs` in `src/ZB.MOM.WW.MxGateway.Server/Workers/`.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 108
CLAIM: "The gateway must never instantiate or call MXAccess directly."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj — no reference to `ArchestrA.MXAccess.dll`. MXAccess COM is only referenced in the Worker project csproj (line 2629).
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 646650
CLAIM: Gateway restart does not reattach old workers; `OrphanWorkerCleanupHostedService` runs `OrphanWorkerTerminator` once on startup to kill leftover `ZB.MOM.WW.MxGateway.Worker.exe` processes.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Workers/OrphanWorkerCleanupHostedService.cs:7 — class exists and references `OrphanWorkerTerminator`. `OrphanWorkerTerminator.cs:19` is present. Worker executable name `ZB.MOM.WW.MxGateway.Worker.exe` confirmed in `IntegrationTestEnvironment.cs:66`.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GatewayProcessDesign.md
LINES: 420428
CLAIM: Pipe name format is `mxaccess-gateway-{gatewayProcessId}-{sessionId}` and framing is `uint32 little-endian payload_length` followed by `payload_length bytes protobuf WorkerEnvelope`.
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:433 — confirmed matching.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GatewayProcessDesign.md
LINES: 459475
CLAIM: `IWorkerClient` has methods `StartAsync`, `InvokeAsync(WorkerCommand, TimeSpan, CancellationToken)`, `ReadEventsAsync(CancellationToken)`, `ShutdownAsync(TimeSpan, CancellationToken)`, `Kill(string)`.
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Workers/IWorkerClient.cs:22,2831,35,40,44 — all five methods are present with matching signatures.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GatewayProcessDesign.md
LINES: 713719
CLAIM: API-key admin CLI subcommands are `init-db`, `create-key`, `list-keys`, `revoke-key`, `rotate-key` on `ZB.MOM.WW.MxGateway.Server apikey`.
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyAdminCommandLineParser.cs:121135 — all five subcommands are parsed. Matches.
CODE_AREA: arch.auth
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GatewayProcessDesign.md
LINES: 408410
CLAIM: Nonce is passed via `MXGATEWAY_WORKER_NONCE` environment variable so the command line remains safe to log.
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerProcessLauncher.cs:1718 — `public const string WorkerNonceEnvironmentVariableName = "MXGATEWAY_WORKER_NONCE"`. Matches.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GatewayProcessDesign.md
LINES: 223229
CLAIM: `EventStreamService` rejects a second subscriber with `EventSubscriberAlreadyActive`; faults the session with `EventQueueOverflow` if the queue fills.
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManagerErrorCode.cs:78 — enum values `EventSubscriberAlreadyActive` and `EventQueueOverflow` present. Also used at `MxAccessGatewayService.cs:929930` and `EventStreamService.cs:150,160`.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GatewayProcessDesign.md
LINES: 291299
CLAIM: Dashboard auth uses LDAP bind + role mapping (`MxGateway:Dashboard:GroupToRole`), issues HTTP-only secure cookie, allows `Dashboard:AllowAnonymousLocalhost` to default to `true`.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs:9 (LDAP-backed); `DashboardOptions.cs:9` (`AllowAnonymousLocalhost` defaults to `true`). Matches.
CODE_AREA: arch.auth
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GatewayProcessDesign.md
LINES: 527530
CLAIM: "During shutdown the worker client treats `WorkerShutdownAck` as the protocol close signal."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto:34,80 — `WorkerShutdownAck` is field 17 in the oneof body and its message is defined at line 80.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 301314
CLAIM: Session state machine (in the "Session Manager" section): `Creating -> StartingWorker -> WaitingForPipe -> InitializingWorker -> Ready -> Closing -> Closed -> Faulted`.
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs:75 — `session.TransitionTo(SessionState.Handshaking)` is called between `WaitingForPipe` and `InitializingWorker`. The `Handshaking` state also exists in the public `SessionState` proto enum (`MxaccessGateway.cs:726`). The state machine in gateway.md at this location (the Gateway Implementation Plan / Session Manager section) is missing the `Handshaking` state exactly as in the earlier reference at lines 898913.
CODE_AREA: arch.session
SEVERITY: medium
PROPOSED_FIX: Add `-> Handshaking` between `WaitingForPipe` and `InitializingWorker` in both state machine diagrams in gateway.md.
---
DOC: gateway.md
LINES: 10231025
CLAIM: "MXAccess COM target is `ArchestrA.MxAccess.LMXProxyServerClass` / `LMXProxy.LMXProxyServer.1` from the installed 32-bit `LmxProxy.dll`."
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessInteropInfo.cs:14,41 — `ComClassName = "ArchestrA.MxAccess.LMXProxyServerClass"`, `ProgId = "LMXProxy.LMXProxyServer.1"`, registered server `LmxProxy.dll`. Matches.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GatewayProcessDesign.md
LINES: 6293
CLAIM: High-level component list references namespace `ZB.MOM.WW.MxGateway.Server` with sub-components including `GatewayMetrics` (under `Metrics`) and `HealthChecks` (under `Diagnostics`).
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs:4 — `namespace ZB.MOM.WW.MxGateway.Server.Metrics`; src/ZB.MOM.WW.MxGateway.Server/Diagnostics/AuthStoreHealthCheck.cs:5 — `namespace ZB.MOM.WW.MxGateway.Server.Diagnostics`. Matches.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 110116
CLAIM: Gateway observability foundation lives in `ZB.MOM.WW.MxGateway.Server.Diagnostics` and `ZB.MOM.WW.MxGateway.Server.Metrics`; `GatewayMetrics` exposes counters/gauges/histograms through .NET `Meter`; `DashboardSnapshotService` projects sessions/workers/metrics into immutable DTOs.
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs:4; src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardSnapshotService.cs:8. Both namespaces confirmed. Matches.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 119121
CLAIM: SignalR hubs at `/hubs/{snapshot,alarms,events}` accept either the cookie or a 30-minute bearer minted at `/hubs/token`.
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs:6365,73 — `MapHub<DashboardSnapshotHub>("/hubs/snapshot")`, `MapHub<AlarmsHub>("/hubs/alarms")`, `MapHub<EventsHub>("/hubs/events")`, `/hubs/token` endpoint mapped at line 73. Matches.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 121122
CLAIM: "`/hubs/events` mirrors per-session `MxEvent` traffic from `EventStreamService` to clients subscribed to `session:{id}`."
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/EventsHub.cs:27 — `public static string GroupName(string sessionId) => $"session:{sessionId}"`. Matches.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GatewayProcessDesign.md
LINES: 864893
CLAIM: Configuration JSON block shows `MxGateway:Worker:ExecutablePath`, `MxGateway:Sessions:AllowMultipleEventSubscribers`, `MxGateway:Events:QueueCapacity`, `MxGateway:Protocol:WorkerProtocolVersion`, etc.
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs:67,13 — `ExecutablePath` and `RequiredArchitecture` match; `SessionOptions.cs` and `EventsOptions` confirm the other keys through bound configuration.
CODE_AREA: arch.config
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/DesignDecisions.md
LINES: 8595
CLAIM: The single-subscriber rule for `StreamEvents` no longer applies to alarms. `GatewayAlarmMonitor` owns one gateway-managed worker session, fans alarm state to any number of clients through session-less `StreamAlarms`. `AcknowledgeAlarm` is session-less and routes through the monitor.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs:17 — class exists. `MxAccessGatewayService.cs:167``StreamAlarms` and `AcknowledgeAlarm` are session-less. Matches.
CODE_AREA: arch.session
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/DesignDecisions.md
LINES: 217225
CLAIM: Bulk commands are `AddItemBulk`, `AdviseItemBulk`, `RemoveItemBulk`, `UnAdviseItemBulk`, `SubscribeBulk`, `UnsubscribeBulk`, `WriteBulk`, `Write2Bulk`, `WriteSecuredBulk`, `WriteSecured2Bulk`, `ReadBulk`. Each runs single-item MXAccess COM calls sequentially on the STA; per-entry failures are non-throwing.
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto — all eleven bulk command kinds are present in the `MxCommandKind` enum and corresponding request/reply messages. Verified by cross-referencing `GatewayGrpcScopeResolver.cs:39` which maps `WriteBulk`, `Write2Bulk`, etc.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 129130
CLAIM: "`/browse` walks the `IGalaxyHierarchyCache` tree and reads subscribed tag values live through `IDashboardLiveDataService`."
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Dashboard/IDashboardBrowseService.cs — `IDashboardBrowseService` references `IGalaxyHierarchyCache`. `IDashboardLiveDataService.cs` exists in the same Dashboard directory. `/browse` page confirmed in `BrowsePage.razor:1`.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 219
CLAIM: Gateway preserves MXAccess behavior first, including public MXAccess command semantics, native MXAccess event families, STA/message-pump delivery behavior, HRESULT/status/value marshaling, and per-client isolation. "Installed MXAccess COM component is the compatibility baseline."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessInteropInfo.cs (installs/references real COM interop); docs/DesignDecisions.md:2628 — "target the installed MXAccess COM interop surface directly from the x86 worker." Consistent across all three docs.
CODE_AREA: arch.layout
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GatewayProcessDesign.md
LINES: 100105
CLAIM: gRPC service surface at this stage is limited to `OpenSession`, `CloseSession`, `Invoke`, `StreamEvents` (with `Session(stream ClientMessage) returns (stream ServerMessage)` deferred).
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto — `MxAccessGateway` service defines `OpenSession`, `CloseSession`, `Invoke`, `StreamEvents`, and additional alarm/galaxy RPCs. The bidirectional `Session` RPC is not present in the current proto, consistent with the deferral noted in the doc.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: gateway.md
LINES: 266273
CLAIM: Public gRPC service is `MxAccessGateway` with `OpenSession`, `CloseSession`, `Invoke`, `StreamEvents`, and deferred bidirectional `Session` RPC.
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto — confirmed. The `Session` bidirectional RPC is absent as expected for deferred rollout.
CODE_AREA: arch.ipc
SEVERITY: low
PROPOSED_FIX: flag only
+580
View File
@@ -0,0 +1,580 @@
# Cluster 02 — Worker
Auditor: automated prose-documentation audit
Docs audited: WorkerBootstrap.md, WorkerConversion.md, WorkerFrameProtocol.md, WorkerProcessLauncher.md, WorkerSta.md, MxAccessWorkerInstanceDesign.md
Code verified against: src/ZB.MOM.WW.MxGateway.Worker/**, src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto
---
DOC: WorkerSta.md
LINES: 23-31
CLAIM: `StaRuntime`'s constructor configures a background `Thread` named `ZB.MOM.WW.MxGateway.Worker.STA` and the code snippet shows `Name = "ZB.MOM.WW.MxGateway.Worker.STA"`.
CLAIM_TYPE: term
VERDICT: wrong
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Sta/StaRuntime.cs:61 — actual thread name is `"MxGateway.Worker.STA"` (no `ZB.MOM.WW.` prefix).
CODE_AREA: worker.sta
SEVERITY: medium
PROPOSED_FIX: Change every occurrence of `ZB.MOM.WW.MxGateway.Worker.STA` in WorkerSta.md (prose on line 23 and code snippet on line 29) to `MxGateway.Worker.STA`.
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 254
CLAIM: `StaRuntime` "starts one background thread named `ZB.MOM.WW.MxGateway.Worker.STA`".
CLAIM_TYPE: term
VERDICT: wrong
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Sta/StaRuntime.cs:61 — thread is named `"MxGateway.Worker.STA"`.
CODE_AREA: worker.sta
SEVERITY: medium
PROPOSED_FIX: Replace `ZB.MOM.WW.MxGateway.Worker.STA` with `MxGateway.Worker.STA` in the STA Runtime section.
---
DOC: WorkerSta.md
LINES: 144
CLAIM: "`InvokeAsync` rejects new work with `InvalidOperationException`" when shutdown is requested.
CLAIM_TYPE: term
VERDICT: wrong
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Sta/StaRuntime.cs:170 — actually throws `StaRuntimeShutdownException`. That class inherits from `InvalidOperationException` (StaRuntimeShutdownException.cs:16) but is a distinct type callers are expected to distinguish.
CODE_AREA: worker.sta
SEVERITY: medium
PROPOSED_FIX: Change "rejects new work with `InvalidOperationException`" to "rejects new work with `StaRuntimeShutdownException` (a subtype of `InvalidOperationException`)". The distinction matters because MxAccessStaSession uses it to separate graceful stop from programming errors (e.g., STA-affinity assertions).
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 122
CLAIM: Exit code `0` / `Success` meaning = "Required bootstrap options are valid."
CLAIM_TYPE: behavior-rule
VERDICT: wrong
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerExitCode.cs:5; WorkerBootstrap.md:113 states the authoritative meaning: "The pipe session ran to a clean close." The design-doc description conflates parse success with process-lifetime success.
CODE_AREA: worker.launcher
SEVERITY: high
PROPOSED_FIX: Update the Success row to: "`Success` | 0 | The pipe session ran to a clean close." Add a note that `WorkerBootstrapResult.Succeeded` is a parse-phase gate distinct from process exit code 0.
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 119-128
CLAIM: Exit code table lists only five codes (04). Codes 5 (`PipeConnectionFailed`) and 6 (`ProtocolViolation`) are absent.
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerExitCode.cs:5-12 — enum has seven values (06); WorkerBootstrap.md:112-120 documents all seven.
CODE_AREA: worker.launcher
SEVERITY: high
PROPOSED_FIX: Add rows for `PipeConnectionFailed = 5` ("An `IOException` or `TimeoutException` escapes the pipe client") and `ProtocolViolation = 6` ("A `WorkerFrameProtocolException` escapes the pipe client") to the exit-code table.
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 134-160
CLAIM: Internal component tree lists class names including `WorkerHost`, `PipeClient`, `FrameReader`, `FrameWriter`, `WorkerProtocol`, `StaCommandQueue`, `MessagePump`, `StaWatchdog`, `MxAccessCommandDispatcher`, `SafeArrayConverter`, `StatusProxyConverter`, `HResultMapper`.
CLAIM_TYPE: term
VERDICT: stale
EVIDENCE: Actual source files in the worker project:
- `WorkerHost` does not exist; entry point is `WorkerApplication` (WorkerApplication.cs).
- `PipeClient` exists as `WorkerPipeClient` (Ipc/WorkerPipeClient.cs).
- `FrameReader`/`FrameWriter` exist as `WorkerFrameReader`/`WorkerFrameWriter` (Ipc/).
- `WorkerProtocol` does not exist; closest is `WorkerContractInfo` (Ipc/WorkerContractInfo.cs).
- `StaCommandQueue` does not exist; queue logic lives in `StaCommandDispatcher` (Sta/StaCommandDispatcher.cs).
- `MessagePump` exists as `StaMessagePump` (Sta/StaMessagePump.cs).
- `StaWatchdog` does not exist; watchdog logic lives in `WorkerPipeSession` (Ipc/WorkerPipeSession.cs).
- `MxAccessCommandDispatcher` does not exist; actual class is `MxAccessCommandExecutor` (MxAccess/MxAccessCommandExecutor.cs).
- `SafeArrayConverter` does not exist; SAFEARRAY conversion is part of `VariantConverter`.
- `StatusProxyConverter` does not exist; actual class is `MxStatusProxyConverter` (Conversion/MxStatusProxyConverter.cs).
- `HResultMapper` does not exist; actual class is `HResultConverter` (Conversion/HResultConverter.cs).
CODE_AREA: worker.sta
SEVERITY: high
PROPOSED_FIX: Rewrite the component tree to match actual class names. This section appears to be a design-phase placeholder that was never updated after implementation.
---
DOC: WorkerBootstrap.md
LINES: 146
CLAIM: "Standard error is used rather than standard output because the gateway side reads worker stdout for diagnostic capture only, while stderr is reserved for log output that does not interfere with any future stdout-based channel."
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerProcessLauncher.cs:166-174 — `ProcessStartInfo` does not set `RedirectStandardOutput = true` or `RedirectStandardError = true`; the gateway currently reads neither stream. The stated reason (gateway reads stdout) is not implemented.
CODE_AREA: worker.launcher
SEVERITY: medium
PROPOSED_FIX: Replace the stdout-capture rationale with the accurate reason: "Environment variables of another process are not visible to other users, unlike command-line arguments; stdout/stderr redirect is not currently wired by the launcher." Alternatively, if stdout capture is a planned feature, label it as such.
---
DOC: WorkerConversion.md
LINES: 178
CLAIM: "`MapCategory` and `MapSource` translate the integer codes documented for `MXSTATUS_PROXY` (for example `0 = Ok`, `3 = CommunicationError`, `0 = RequestingLmx`, `5 = RespondingAutomationObject`)".
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Conversion/MxStatusProxyConverter.cs:103-133 — `MapCategory(0)``MxStatusCategory.Ok`; `MapCategory(3)``MxStatusCategory.CommunicationError`; `MapSource(0)``MxStatusSource.RequestingLmx`; `MapSource(5)``MxStatusSource.RespondingAutomationObject`.
CODE_AREA: worker.convert
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerConversion.md
LINES: 225
CLAIM: "The mapping covers the engine-error range documented for MXAccess (16-50, 56-61, 541-542, 8017)."
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Conversion/MxStatusDetailText.cs:7-48 — the dictionary has gaps within those ranges: keys 35, 45, 46 are absent from 1650; keys 58, 59 are absent from 5661. The doc implies contiguous ranges.
CODE_AREA: worker.convert
SEVERITY: low
PROPOSED_FIX: Replace the continuous-range description with "selected detail codes in the ranges 1650, 5661, 541542, and 8017 (not all values in those ranges are populated)."
---
DOC: WorkerBootstrap.md
LINES: 7-8
CLAIM: "`WorkerApplication.Run` constructs the bootstrap dependencies (`EnvironmentVariableWorkerEnvironment`, `WorkerConsoleLogger` writing to `Console.Error`, and a `WorkerPipeClient`)".
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/WorkerApplication.cs:16-19.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerBootstrap.md
LINES: 113-120
CLAIM: Exit code table with seven rows 06.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerExitCode.cs:5-12.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerBootstrap.md
LINES: 181-193
CLAIM: `WorkerLogRedactor` `SensitiveFieldNameParts` list (seven entries: nonce, secret, password, token, credential, apikey, api_key).
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerLogRedactor.cs:16-25.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerBootstrap.md
LINES: 105
CLAIM: "`Succeeded` is defined as `ExitCode == WorkerExitCode.Success` rather than as a separate flag, so the exit code and the success state cannot disagree."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Bootstrap/WorkerBootstrapResult.cs:36.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerFrameProtocol.md
LINES: 14-19
CLAIM: Each frame starts with a four-byte little-endian unsigned payload length followed by the serialized `WorkerEnvelope` payload. Zero-length payloads and payloads larger than the configured maximum are rejected before allocating the payload buffer. The default maximum is 16 MiB.
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameReader.cs:32-50; WorkerFrameProtocolOptions.cs:11 (`DefaultMaxMessageBytes = 16 * 1024 * 1024`).
CODE_AREA: worker.frameproto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerFrameProtocol.md
LINES: 22-34
CLAIM: Envelope validation checks: `protocol_version` must match configured version; `session_id` must match owning session; envelope must contain one typed `body` value. Violations throw `WorkerFrameProtocolException` with a `WorkerFrameProtocolErrorCode`.
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerEnvelopeValidator.cs:16-36.
CODE_AREA: worker.frameproto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerFrameProtocol.md
LINES: 38-41
CLAIM: "The frame protocol lives in `ZB.MOM.WW.MxGateway.Worker.Ipc` (`WorkerFrameReader`, `WorkerFrameWriter`, `WorkerFrameProtocolOptions`)".
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: Namespaces in WorkerFrameReader.cs:9, WorkerFrameWriter.cs:8, WorkerFrameProtocolOptions.cs:6.
CODE_AREA: worker.frameproto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerFrameProtocol.md
LINES: 44-47
CLAIM: Test file path is `src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs`.
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: File confirmed at that path.
CODE_AREA: worker.frameproto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerProcessLauncher.md
LINES: 18-25
CLAIM: Launcher passes `SessionId`, `PipeName`, and `ProtocolVersion` as `--session-id`, `--pipe-name`, `--protocol-version` CLI arguments; nonce travels via `MXGATEWAY_WORKER_NONCE` environment variable; nonce is excluded from `WorkerProcessCommandLine`.
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerProcessLauncher.cs:156-184.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerProcessLauncher.md
LINES: 30-34
CLAIM: Launcher validates that the configured worker path exists, has `.exe` extension, contains a valid Windows Portable Executable header, and matches `RequiredArchitecture`.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerProcessLauncher.cs:189-220 calls `WorkerExecutableValidator.Validate`.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerProcessLauncher.md
LINES: 35-45
CLAIM: Default probe (`IWorkerStartupProbe`) "only verifies that the worker did not exit immediately." Retry policy configured by `WorkerOptions.StartupProbeRetryAttempts` and `WorkerOptions.StartupProbeRetryDelayMilliseconds`; counter recorded as `mxgateway.retries.attempted` with `area=worker_startup`.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: WorkerProcessStartedProbe.cs:10-24 (exits check only); WorkerOptions.cs:18-22; GatewayMetrics.cs:70 (`mxgateway.retries.attempted`); WorkerProcessLauncher.cs:279 (area label `"worker_startup"`).
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerProcessLauncher.md
LINES: 48-55
CLAIM: Launcher also passes `MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS` from `WorkerOptions.PipeConnectAttemptTimeoutMilliseconds`. On failure, kills the worker process tree, disposes the process handle, disposes the optional pipe reservation, records a worker kill metric, and reports `WorkerProcessLaunchException`.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: WorkerProcessLauncher.cs:181-182, 253-267.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerProcessLauncher.md
LINES: 60-64
CLAIM: Test command: `dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter WorkerProcessLauncherTests`.
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: Project file confirmed at `src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj`; test class `WorkerProcessLauncherTests` confirmed at `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerProcessLauncherTests.cs`.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerSta.md
LINES: 14
CLAIM: Type table shows `StaCommandDispatcher` as "Bounded asynchronous queue in front of `StaRuntime`…".
CLAIM_TYPE: term
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Sta/StaCommandDispatcher.cs:15 — uses `Queue<QueuedStaCommand>`, a plain synchronous non-concurrent `Queue<T>` guarded by `lock(gate)`. There is no async channel or channel-based backpressure; `DrainAsync` is fire-and-forget but the queue itself is not an async queue.
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: Change "Bounded asynchronous queue" to "Bounded queue with an async drain loop" to avoid implying the underlying data structure is an async channel.
---
DOC: WorkerSta.md
LINES: 56
CLAIM: "`The idlePumpInterval` defaults to 50 ms so the pump still services Windows messages even when no commands are queued".
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Sta/StaRuntime.cs:30 — `TimeSpan.FromMilliseconds(50)`.
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerSta.md
LINES: 82-99
CLAIM: `InvokeAsync<T>` wraps the delegate in a `StaWorkItem<T>`, enqueues it on a `ConcurrentQueue<IStaWorkItem>`, and signals `commandWakeEvent`. `StaWorkItem<T>` uses an `Interlocked.CompareExchange` on `started` so exactly one of three outcomes happens.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: StaRuntime.cs:12 (`ConcurrentQueue<IStaWorkItem>`); StaRuntime.cs:164-177; StaWorkItem.cs:31,47,57.
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerSta.md
LINES: 141-148
CLAIM: Shutdown sequence step 1: sets `shutdownRequested` under `gate`; step 2: signals `commandWakeEvent`; step 3: waits up to `timeout` on `stoppedEvent`, which the STA sets after leaving `ThreadMain`; step 4: drains the queue through `CancelQueuedCommands` calling `CancelBeforeExecution`.
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: StaRuntime.cs:261-273 — `CancelQueuedCommands()` is called inside `ThreadMain`'s `finally` block *before* `stoppedEvent.Set()`, meaning the drain happens on the STA thread, not after `stoppedEvent` is observed by `Shutdown()`. `Shutdown()` calls `CancelQueuedCommands()` a *second* time after observing `stoppedEvent`, but the doc implies a single post-stop drain.
CODE_AREA: worker.sta
SEVERITY: medium
PROPOSED_FIX: Revise step 3 to note that `stoppedEvent` is set from within `ThreadMain`'s `finally` block (before the thread exits) after `CoUninitialize`. Revise step 4 to note the queue is drained *twice*: once by `ThreadMain` in its `finally` (to cancel items enqueued before shutdown) and once by `Shutdown()` after `stoppedEvent` (to cancel any items enqueued in the gap).
---
DOC: WorkerSta.md
LINES: 149
CLAIM: "`Dispose` calls `Shutdown` with a five-second budget and only disposes the wait handles when shutdown actually completed".
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: StaRuntime.cs:224-233 — `Shutdown(TimeSpan.FromSeconds(5))`; handles disposed only when `stopped` is true.
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerSta.md
LINES: 108
CLAIM: "when `commandQueue.Count` reaches `maxPendingCommands` (default `DefaultMaxPendingCommands = 128`) the dispatcher returns a synthetic `WorkerUnavailable` reply".
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: StaCommandDispatcher.cs:11 (`DefaultMaxPendingCommands = 128`); lines 125-132 (count check and WorkerUnavailable reply).
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 97
CLAIM: Expected protected environment values include `MXGATEWAY_WORKER_LOG_CONTEXT=<optional context>`.
CLAIM_TYPE: config-key
VERDICT: wrong
EVIDENCE: No occurrence of `MXGATEWAY_WORKER_LOG_CONTEXT` anywhere in `src/ZB.MOM.WW.MxGateway.Worker/**`. The only worker environment variable in code is `MXGATEWAY_WORKER_NONCE` (WorkerOptions.cs:7) and `MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS` (WorkerProcessLauncher.cs:22).
CODE_AREA: worker.launcher
SEVERITY: high
PROPOSED_FIX: Remove `MXGATEWAY_WORKER_LOG_CONTEXT` from the bootstrap environment table, or add a note that it is not yet implemented if it is intended for a future slice.
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 86-99
CLAIM: Bootstrap sequence lists `MXGATEWAY_WORKER_LOG_CONTEXT` as an optional protected environment value alongside `MXGATEWAY_WORKER_NONCE`.
CLAIM_TYPE: config-key
VERDICT: wrong
EVIDENCE: Same as above — `MXGATEWAY_WORKER_LOG_CONTEXT` is not read anywhere in the worker bootstrap code.
CODE_AREA: worker.launcher
SEVERITY: high
PROPOSED_FIX: flag only (same fix as prior entry).
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 368-375
CLAIM: "`MxAccessEventQueue` is the bounded outbound event queue for one worker session. It assigns the monotonic `WorkerSequence` and `WorkerTimestamp` when an event is accepted. The default capacity is `10000`. When the queue reaches capacity it records a `WorkerFaultCategory.QueueOverflow` fault and rejects further events."
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: MxAccessEventQueue.cs:115-132 — `Enqueue` throws `MxAccessEventQueueOverflowException` in addition to recording the fault. Callers in `MxAccessBaseEventSink` catch this exception. The doc's phrase "rejects further events" omits the thrown exception, which callers must handle.
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: Add that `Enqueue` raises `MxAccessEventQueueOverflowException` on overflow, in addition to recording the fault, so that callers know to catch this exception rather than only observing the fault via `DrainFault()`.
---
DOC: WorkerConversion.md
LINES: 1-262 (entire doc)
CLAIM: Documents `VariantConverter`, `HResultConverter`/`HResultConversion`, `MxStatusProxyConverter`, `MxStatusDetailText`, `MxStatusConversionException`.
CLAIM_TYPE: term
VERDICT: gap
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/Conversion/VariantConverter.cs:129-177 — `ConvertToComValue(MxValue)` and `ConvertToComArray(MxArray)` are fully implemented methods that convert protobuf values back to CLR objects for COM write calls. These inverse-projection paths are nowhere mentioned in WorkerConversion.md, leaving integrators unaware of the write path.
CODE_AREA: worker.convert
SEVERITY: medium
PROPOSED_FIX: Add a section "Inverse projection for COM writes" describing `ConvertToComValue`, its dispatch on `MxValue.KindOneofCase`, the `ConvertToComArray` helper, and that raw or unset `MxValue` payloads throw `ArgumentException`.
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 134-160
CLAIM: Internal component tree for `MxAccess` subtree lists: `MxAccessSession`, `MxAccessCommandDispatcher`, `MxAccessEventSink`, `MxAccessHandleRegistry`.
CLAIM_TYPE: term
VERDICT: stale
EVIDENCE: Actual classes: `MxAccessSession` (internal session state), `MxAccessStaSession` (owner of the STA session lifecycle), `MxAccessCommandExecutor` (implements `IStaCommandExecutor`), `MxAccessBaseEventSink`/`MxAccessAlarmEventSink` (event sinks), `MxAccessHandleRegistry`. The class `MxAccessCommandDispatcher` does not exist.
CODE_AREA: worker.sta
SEVERITY: medium
PROPOSED_FIX: Update MxAccess subtree to reflect actual class names. Note that `MxAccessStaSession` owns `StaCommandDispatcher` (in the Sta namespace) and `MxAccessCommandExecutor`; they are separate concerns.
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 134-160 (entire component tree)
CLAIM: No mention of the alarm subsystem.
CLAIM_TYPE: term
VERDICT: gap
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/MxAccess/ contains a complete alarm subsystem: `AlarmCommandHandler.cs`, `AlarmDispatcher.cs`, `AlarmRecordTransitionMapper.cs`, `IAlarmCommandHandler.cs`, `IMxAccessAlarmConsumer.cs`, `MxAccessAlarmEventSink.cs`, `WnWrapAlarmConsumer.cs`, `MxAlarmSnapshot.cs`, `MxAlarmStateKind.cs`, `MxAlarmTransitionEvent.cs`. None of these appear in any of the six audited docs. `MxAccessStaSession.cs` shows an `alarmCommandHandlerFactory` parameter and an alarm poll loop (lines 14-312).
CODE_AREA: worker.sta
SEVERITY: high
PROPOSED_FIX: Add an "Alarm Subsystem" section to MxAccessWorkerInstanceDesign.md (or create docs/WorkerAlarms.md) covering: `IAlarmCommandHandler`/`AlarmCommandHandler`, the `WnWrapAlarmConsumer` STA-affinity requirement, the 500 ms alarm poll loop in `MxAccessStaSession.RunAlarmPollLoopAsync`, `AlarmDispatcher`, and the `MxAccessAlarmEventSink`. Update the event-sink list in the "Event Sink" section to include alarm events.
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 336-338
CLAIM: Event sink must subscribe to `OnDataChange`, `OnWriteComplete`, `OperationComplete`, `OnBufferedDataChange`.
CLAIM_TYPE: behavior-rule
VERDICT: gap
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessAlarmEventSink.cs exists alongside `MxAccessBaseEventSink.cs`, indicating a fifth event family (alarm events) is handled. The four-family list is incomplete.
CODE_AREA: worker.sta
SEVERITY: medium
PROPOSED_FIX: Add alarm events to the event sink subscription list and clarify that alarm events are handled via `MxAccessAlarmEventSink` on the same STA thread.
---
DOC: WorkerConversion.md
LINES: 17-18
CLAIM: "It accepts an optional `expectedDataType` so that an MXAccess attribute hint (for example `MxDataType.Time` for a 64-bit FILETIME) overrides the default CLR-driven projection."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: VariantConverter.cs:262-291 (`ConvertInt64Scalar` checks `expectedDataType == MxDataType.Time && value is long`).
CODE_AREA: worker.convert
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerConversion.md
LINES: 112-135
CLAIM: "`HResultConverter.Convert` prefers `COMException.ErrorCode` over `Exception.HResult` because the runtime sometimes overwrites `Exception.HResult` while marshalling".
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: HResultConverter.cs:21-26.
CODE_AREA: worker.convert
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerBootstrap.md
LINES: 48-54
CLAIM: Three fields arrive on the command line (`--session-id`, `--pipe-name`, `--protocol-version`) and one via environment variable (`MXGATEWAY_WORKER_NONCE`).
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: WorkerOptionsParser.cs:12-14, 78.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerBootstrap.md
LINES: 155-159
CLAIM: "`IWorkerLogger` exposes only `Information` and `Error`. There is no `Debug` or `Trace` level."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: IWorkerLogger.cs:8-19.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerSta.md
LINES: 34
CLAIM: "`StaComApartmentInitializer.Initialize` calls `CoInitializeEx` with `COINIT_APARTMENTTHREADED` (`0x2`) and treats both `S_OK` and `S_FALSE` as success because `S_FALSE` indicates the apartment was already initialized on this thread."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: StaComApartmentInitializer.cs:8-18.
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerSta.md
LINES: 63-78
CLAIM: "`StaMessagePump.WaitForWorkOrMessages` calls `MsgWaitForMultipleObjectsEx` with `QS_ALLINPUT` and `MWMO_INPUTAVAILABLE`. `PumpPendingMessages` drains the queue with `PM_REMOVE`."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: StaMessagePump.cs:13-15 (`MwmoInputAvailable = 0x0004`, `PmRemove = 0x0001`, `QsAllInput = 0x04FF`); lines 31-36, 50-57.
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 271-286
CLAIM: COM details: interop assembly path, assembly identity (`ArchestrA.MxAccess, Version=3.2.0.0, PublicKeyToken=23106a86e706d0ae`), COM class `ArchestrA.MxAccess.LMXProxyServerClass`, CLSID `{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}`, ProgID `LMXProxy.LMXProxyServer.1`, version-independent ProgID `LMXProxy.LMXProxyServer`, registered server `LmxProxy.dll`, threading model `Apartment`.
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessInteropInfo.cs — ProgId, VersionIndependentProgId, Clsid, InteropAssemblyPath, RegisteredServerPath, ComClassName all match. Assembly identity and threading model are from MXAccess analysis sources and are unverifiable in this repo but consistent with design sources cited in CLAUDE.md.
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 656-660
CLAIM: "HeartbeatStuckCeiling (default 75 seconds = 5 × HeartbeatGrace)".
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: WorkerPipeSessionOptions.cs:19 (`DefaultHeartbeatStuckCeiling = TimeSpan.FromSeconds(75)`); DefaultHeartbeatGrace = 15 s (line 11); 5 × 15 = 75. ✓
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerBootstrap.md
LINES: 5-6
CLAIM: "The worker process is a short-lived child of the gateway."
CLAIM_TYPE: term
VERDICT: stale
EVIDENCE: No functional error, but "short-lived" is context-dependent; workers persist for the entire duration of a gateway session (which may be hours). Integrators might misread this as expecting sub-minute lifetimes.
CODE_AREA: worker.launcher
SEVERITY: low
PROPOSED_FIX: Replace "short-lived child" with "per-session child process" or "child process that lives for the duration of one gateway session."
---
DOC: MxAccessWorkerInstanceDesign.md
LINES: 151
CLAIM: Component tree lists `MxAccessSession` as a class under `MxAccess`.
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessSession.cs exists. The tree is incomplete (missing `MxAccessStaSession`, alarm classes, etc.) but `MxAccessSession` itself is real.
CODE_AREA: worker.sta
SEVERITY: low
PROPOSED_FIX: flag only (incompleteness covered by the component-tree stale entry above).
---
DOC: WorkerConversion.md
LINES: 18
CLAIM: `VariantConverter` is in namespace `ZB.MOM.WW.MxGateway.Worker.Conversion`.
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: VariantConverter.cs:8 (`namespace ZB.MOM.WW.MxGateway.Worker.Conversion;`).
CODE_AREA: worker.convert
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: WorkerFrameProtocol.md
LINES: 49-53
CLAIM: Build command `dotnet build src/ZB.MOM.WW.MxGateway.Worker/ZB.MOM.WW.MxGateway.Worker.csproj -p:Platform=x86`.
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: Project file exists at that path.
CODE_AREA: worker.frameproto
SEVERITY: low
PROPOSED_FIX: flag only
+380
View File
@@ -0,0 +1,380 @@
# Cluster 03 — Sessions/Runtime
Auditor: automated (claude-sonnet-4-6)
Date: 2026-06-03
Source doc: docs/Sessions.md
Verified against: src/ZB.MOM.WW.MxGateway.Server/Sessions/**, src/ZB.MOM.WW.MxGateway.Server/Workers/**
---
DOC / LINES / 9
CLAIM: "All four interfaces (`ISessionManager`, `ISessionRegistry`, `ISessionWorkerClientFactory`) plus `SessionShutdownHostedService` are wired as singletons by `SessionServiceCollectionExtensions.AddGatewaySessions`."
CLAIM_TYPE: term
VERDICT: wrong
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionServiceCollectionExtensions.cs:9-18 — only three interfaces exist (confirmed by `ls I*.cs` in Sessions/). The doc claims "four interfaces" but names only three. Additionally the DI registration also registers `SessionLeaseMonitorHostedService` as a hosted service, which is omitted from this sentence.
CODE_AREA: session.di
SEVERITY: medium
PROPOSED_FIX: Change "All four interfaces" to "All three interfaces". Separately note that two hosted services are registered: `SessionLeaseMonitorHostedService` and `SessionShutdownHostedService`.
---
DOC / LINES / 265-276
CLAIM: Code snippet for `AddGatewaySessions` shows only `SessionShutdownHostedService` registered; `SessionLeaseMonitorHostedService` is absent from the snippet.
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionServiceCollectionExtensions.cs:14-15 — actual code registers both `AddHostedService<SessionLeaseMonitorHostedService>()` and `AddHostedService<SessionShutdownHostedService>()`. The snippet in the doc is missing the lease-monitor line.
CODE_AREA: session.di
SEVERITY: medium
PROPOSED_FIX: Add `services.AddHostedService<SessionLeaseMonitorHostedService>();` to the code snippet (between the `ISessionManager` singleton line and the shutdown service line).
---
DOC / LINES / 232-259
CLAIM: The `ShutdownAsync` code snippet shown calls `session.KillWorker(GatewayShutdownReason)` and `await RemoveSessionAsync(session)` directly in the catch block.
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:296-331 — the actual `ShutdownAsync` fallback calls `await KillWorkerAsync(session.SessionId, GatewayShutdownReason, cancellationToken)` (which routes through `KillWorkerWithCloseGateAsync` and then `RemoveSessionAsync`), not a direct `session.KillWorker` + `RemoveSessionAsync`. The old snippet predates the Server-045/Server-046 refactor that unified the kill path through `KillWorkerAsync`.
CODE_AREA: session.shutdown
SEVERITY: medium
PROPOSED_FIX: Replace the ShutdownAsync snippet with the current implementation, which checks `_registry.TryGet` then calls `KillWorkerAsync` (wrapped in its own try/catch) instead of directly calling `session.KillWorker` and `RemoveSessionAsync`.
---
DOC / LINES / 55-59
CLAIM: "`KillWorkerAsync` is the forceful path used by the dashboard's admin Kill button: it calls `GatewaySession.KillWorker` directly, which kills the worker process immediately with no graceful-shutdown attempt and transitions the session to `Closed`."
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:216-264 — `KillWorkerAsync` now calls `session.KillWorkerWithCloseGateAsync` (not `GatewaySession.KillWorker` directly). The `KillWorkerWithCloseGateAsync` method acquires `_closeLock` before killing, serializing concurrent close/kill attempts (Server-045 fix). The old description of a direct `KillWorker` call is stale.
CODE_AREA: session.lifecycle
SEVERITY: medium
PROPOSED_FIX: Update description to state that `KillWorkerAsync` calls `session.KillWorkerWithCloseGateAsync`, which acquires the per-session close lock before killing the worker, so concurrent close and kill callers serialize.
---
DOC / LINES / 59
CLAIM: "Both paths converge on the same registry/metrics cleanup, so the open-session slot is released and `mxgateway.sessions.closed` is incremented either way."
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs:59 — counter name `mxgateway.sessions.closed` confirmed. Both `CloseSessionCoreAsync` and `KillWorkerAsync` call `_metrics.SessionClosed()` and `RemoveSessionAsync` (which calls `ReleaseSessionSlot`).
CODE_AREA: session.metrics
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 60-72
CLAIM: Code snippet for `EnsureSessionCapacity` throws `SessionManagerException` with `SessionLimitExceeded`; open requests that exceed the bound "throw ... rather than queuing".
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:388-396 — `_sessionSlots.Wait(0)` (zero timeout = non-blocking) confirms the no-queue, immediate-throw behavior.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 61
CLAIM: "Concurrency is bounded by a `SemaphoreSlim` initialized to `GatewayOptions.Sessions.MaxSessions`."
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:53 — `new SemaphoreSlim(_options.Sessions.MaxSessions, _options.Sessions.MaxSessions)`.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 75
CLAIM: "three close-reason constants — `DefaultCloseReason` (`\"client-close\"`), `GatewayShutdownReason` (`\"gateway-shutdown\"`), and `LeaseExpiredReason` (`\"lease-expired\"`)"
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:17-19 — all three constants confirmed with exact string values.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 79-81
CLAIM: "`SessionRegistry` is a thin wrapper over a `ConcurrentDictionary<string, GatewaySession>` keyed by session id with `StringComparer.Ordinal`."
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionRegistry.cs:12 — `new ConcurrentDictionary<string, GatewaySession>(StringComparer.Ordinal)` confirmed.
CODE_AREA: session.registry
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 81
CLAIM: "`ActiveCount` filters out sessions whose state is `Closed`"
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionRegistry.cs:22 — `_sessions.Values.Count(session => session.State is not SessionState.Closed)` confirmed.
CODE_AREA: session.registry
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 15-19
CLAIM: "The session id is an opaque string in the form `session-{guid:N}` and the per-session pipe name is `mxaccess-gateway-{ProcessId}-{SessionId}`."
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:433 (`pipeName = $"mxaccess-gateway-{Environment.ProcessId}-{sessionId}"`) and :479 (`$"session-{Guid.NewGuid():N}"`).
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 19
CLAIM: "`SessionState` itself is the protobuf-generated enum from `ZB.MOM.WW.MxGateway.Contracts.Proto`"
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/GatewaySession.cs:1 — `using ZB.MOM.WW.MxGateway.Contracts.Proto;` and the state field is typed `SessionState`.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 85-87
CLAIM: "`SessionWorkerClientFactory.CreateAsync` … drives the session through the protobuf `SessionState` substates in order: `StartingWorker`, `WaitingForPipe`, `Handshaking`, `InitializingWorker`."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs:60-105 — `TransitionTo(SessionState.StartingWorker)``TransitionTo(SessionState.WaitingForPipe)``TransitionTo(SessionState.Handshaking)``TransitionTo(SessionState.InitializingWorker)` in sequence.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 87-98
CLAIM: Startup timeout wrapped as `TimeoutException` with the exact catch pattern shown — `OperationCanceledException` where `startupCancellation.IsCancellationRequested` and `!cancellationToken.IsCancellationRequested`.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs:145-153 — identical predicate confirmed.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 100
CLAIM: "The named pipe is created with `maxNumberOfServerInstances: 1`"
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs:166 — `maxNumberOfServerInstances: 1` confirmed.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 104
CLAIM: "`SessionShutdownHostedService` … catches `OperationCanceledException` triggered by the host shutdown timeout and logs a warning so that an over-running shutdown does not surface as an unhandled exception."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionShutdownHostedService.cs:18-28 — exact catch confirmed.
CODE_AREA: session.shutdown
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 109-127
CLAIM: `SessionOpenRequest` is a `sealed record` with fields `RequestedBackend`, `ClientSessionName`, `ClientCorrelationId`, `CommandTimeout`, and a `FromContract` factory.
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionOpenRequest.cs:6-24 — confirmed. Note: the doc snippet includes a `ClientCorrelationId` field in the record definition, but the actual `SessionManager.CreateSession` derives `clientCorrelationId` internally rather than forwarding the field from the request. This is a minor mismatch between what the record holds vs. how it is used, but does not constitute an error in the doc's description of the record type itself.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 134-139
CLAIM: `SessionCloseResult` is a `sealed record` with `SessionId`, `FinalState`, `AlreadyClosed`.
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionCloseResult.cs:5-8 — confirmed.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 143
CLAIM: "`SessionCloseStartedException` is `internal`"
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionCloseStartedException.cs:3 — `internal sealed class SessionCloseStartedException` confirmed.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 148-157
CLAIM: Error code table for `SessionManagerException` — seven codes listed: `SessionNotFound`, `SessionNotReady`, `EventSubscriberAlreadyActive`, `EventQueueOverflow`, `SessionLimitExceeded`, `OpenFailed`, `CloseFailed`.
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManagerErrorCode.cs:1-12 — all seven members confirmed in order.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 163-188
CLAIM: Open failure rollback order: "fault, deregister, dispose, release slot, record metric, log, rethrow".
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:97-123 — actual order is: MarkFaulted → TryRemove (deregister) → DisposeAsync → (conditionally) SessionRemoved metric if sessionOpenedRecorded → ReleaseSessionSlot → Fault metric → LogWarning → rethrow. The doc omits the `sessionOpenedRecorded` conditional `SessionRemoved()` call that was added in the Server-006 fix, making the described order incomplete. The doc text says "release slot, record metric" but the actual code calls `SessionRemoved` before `ReleaseSessionSlot` when `sessionOpenedRecorded` is true.
CODE_AREA: session.lifecycle
SEVERITY: medium
PROPOSED_FIX: Update the rollback description to note the conditional `SessionRemoved()` metric call that precedes `ReleaseSessionSlot` when `SessionOpened()` was already recorded (guards against mxgateway.sessions.open gauge leak on late failures such as auto-subscribe rejection).
---
DOC / LINES / 193-195
CLAIM: "`GatewaySession` also exposes typed bulk helpers (`AddItemBulkAsync`, `SubscribeBulkAsync`, etc.) that wrap `WorkerCommand` round-trips and translate non-`Ok` `ProtocolStatus` replies into `SessionManagerException` with `SessionNotReady`."
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/GatewaySession.cs:490, 590 (AddItemBulkAsync, SubscribeBulkAsync) and :1017-1023 (ProtocolStatusCode.Ok guard throwing SessionManagerException(SessionNotReady)).
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 195-197
CLAIM: "Event streaming uses `AttachEventSubscriber` which returns a disposable lease. When `allowMultipleSubscribers` is false the second attach throws `EventSubscriberAlreadyActive`; this prevents two gRPC streams from racing on the same worker event channel. Active event subscribers keep the session lease from expiring until the stream is disposed."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/GatewaySession.cs:387-407 (AttachEventSubscriber guard and lease) and :373-380 (IsLeaseExpired checks `_activeEventSubscriberCount == 0`).
CODE_AREA: session.subscriber
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 197
CLAIM: "Sessions open with `MxGateway:Sessions:DefaultLeaseSeconds` (default 1800)"
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Configuration/SessionOptions.cs:21 — `public int DefaultLeaseSeconds { get; init; } = 1800`.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 197
CLAIM: "`SessionLeaseMonitorHostedService` runs that sweep every `MxGateway:Sessions:LeaseSweepIntervalSeconds` seconds (default 30)."
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Configuration/SessionOptions.cs:24 — `public int LeaseSweepIntervalSeconds { get; init; } = 30`; src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionLeaseMonitorHostedService.cs:19 — `TimeSpan.FromSeconds(Math.Max(1, options.Value.Sessions.LeaseSweepIntervalSeconds))`.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC / LINES / 230
CLAIM: "`GatewaySession.KillWorker` is the unconditional forced-close path used by shutdown when graceful close itself throws, and also by `SessionManager.KillWorkerAsync` — the explicit kill path that the dashboard's admin Kill button invokes."
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:233 — `KillWorkerAsync` now calls `session.KillWorkerWithCloseGateAsync` (not `session.KillWorker`). The shutdown fallback (line 319) also routes through `KillWorkerAsync` rather than calling `session.KillWorker` + `RemoveSessionAsync` directly. `GatewaySession.KillWorker` is still present (line 874) but is no longer the entry point from `SessionManager.KillWorkerAsync`.
CODE_AREA: session.lifecycle
SEVERITY: medium
PROPOSED_FIX: Update to reflect that `SessionManager.KillWorkerAsync` delegates to `session.KillWorkerWithCloseGateAsync` (which serializes concurrent kill/close via `_closeLock` — Server-045 fix) and that `GatewaySession.KillWorker` is now only the internal terminal action inside `KillWorkerWithCloseGateAsync`.
---
DOC / LINES / 230
CLAIM: "`KillCount` increments while `ShutdownCount` does not"
CLAIM_TYPE: term
VERDICT: wrong
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs:56-79 — no metrics named `KillCount` or `ShutdownCount` exist. The actual worker-kill metric is `mxgateway.workers.killed` (counter). The doc invents non-existent metric names.
CODE_AREA: session.metrics
SEVERITY: high
PROPOSED_FIX: Replace "KillCount increments while ShutdownCount does not" with "the `mxgateway.workers.killed` counter is incremented (via `GatewayMetrics.WorkerKilled`) while the graceful-shutdown path does not increment it".
---
DOC / LINES / 265
CLAIM: "registers the four singletons and the hosted service" (singular "the hosted service")
CLAIM_TYPE: behavior-rule
VERDICT: wrong
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionServiceCollectionExtensions.cs:14-15 — two hosted services are registered: `SessionLeaseMonitorHostedService` and `SessionShutdownHostedService`.
CODE_AREA: session.di
SEVERITY: medium
PROPOSED_FIX: Change "registers the four singletons and the hosted service" to "registers the three singletons and two hosted services (`SessionLeaseMonitorHostedService`, `SessionShutdownHostedService`)".
---
DOC / LINES / 279
CLAIM: "Registering `SessionShutdownHostedService` last ensures it is constructed after `ISessionManager` and therefore drains sessions during host stop."
CLAIM_TYPE: behavior-rule
VERDICT: stale
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionServiceCollectionExtensions.cs:14-15 — `SessionLeaseMonitorHostedService` is now registered before `SessionShutdownHostedService`. The shutdown service is still last of the two hosted services, but the reasoning in the doc no longer fully applies because construction order of hosted services relative to singletons is governed by ASP.NET Core's DI container, not purely registration order.
CODE_AREA: session.di
SEVERITY: low
PROPOSED_FIX: Update to note that two hosted services are registered in order (lease monitor first, shutdown second) and that both depend on `ISessionManager` which is registered as a singleton.
---
DOC / LINES / (none — gap)
CLAIM: (gap) `GatewaySession` holds an item registration dictionary (`_items`, keyed by `(ServerHandle, ItemHandle)`) tracking all successfully added/subscribed items. The session tracks and prunes these registrations via `TrackCommandReply`, `TryGetItemRegistration`, and the per-command `TrackItem`/`RemoveItems` helpers. This bookkeeping is undocumented.
CLAIM_TYPE: behavior-rule
VERDICT: gap
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/GatewaySession.cs:17 (_items field), :425-481 (TrackCommandReply), :1059-1090 (TrackItem, TrackBulkItems, RemoveItems). src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionItemRegistration.cs:3 (SessionItemRegistration record).
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: Add a subsection or paragraph noting that `GatewaySession` maintains an in-session item registry keyed by `(ServerHandle, ItemHandle)`, updated after successful `AddItem`, `AddItem2`, `AddBufferedItem`, `AddItemBulk`, `SubscribeBulk`, `RemoveItem`, `RemoveItemBulk`, and `UnsubscribeBulk` replies.
---
DOC / LINES / (none — gap)
CLAIM: (gap) `SessionOptions` exposes `AllowMultipleEventSubscribers` (default `false`). Setting it `true` is **rejected at startup** by `GatewayOptionsValidator` with the message "AllowMultipleEventSubscribers is not supported until event fan-out is implemented." This validator-level enforcement of the v1 constraint is undocumented.
CLAIM_TYPE: config-key
VERDICT: gap
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Configuration/SessionOptions.cs:29 and src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs:181-184.
CODE_AREA: session.subscriber
SEVERITY: medium
PROPOSED_FIX: Add a note to the "Run" section explaining that `MxGateway:Sessions:AllowMultipleEventSubscribers` exists but is actively refused by the validator in v1; operators who set it to `true` will see a startup validation failure, not a runtime error.
---
DOC / LINES / (none — gap)
CLAIM: (gap) Gateway-restart orphan cleanup is performed by `OrphanWorkerCleanupHostedService` (wrapping `OrphanWorkerTerminator.TerminateOrphans`) on `StartAsync`, before the gateway accepts sessions. Cleanup is best-effort (a failure logs a warning but does not block startup). The `Sessions.md` doc does not mention this, yet it directly affects the "gateway restart does not reattach orphan workers" contract.
CLAIM_TYPE: behavior-rule
VERDICT: gap
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Workers/OrphanWorkerCleanupHostedService.cs:7-30; src/ZB.MOM.WW.MxGateway.Server/Workers/OrphanWorkerTerminator.cs:49-95; src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerServiceCollectionExtensions.cs:19.
CODE_AREA: session.orphan
SEVERITY: high
PROPOSED_FIX: Add a "Gateway Restart / Orphan Cleanup" section to Sessions.md (or cross-reference from Shutdown Coordination) noting that `OrphanWorkerCleanupHostedService` runs `OrphanWorkerTerminator.TerminateOrphans` on startup, kills any running worker executables matching the configured `MxGateway:Worker:ExecutablePath`, and that failures are non-fatal to startup.
---
DOC / LINES / (none — gap)
CLAIM: (gap) `SessionOptions.MaxPendingCommandsPerSession` (default 128) is passed to `WorkerClientOptions.MaxPendingCommands` during session construction. This per-session command concurrency cap is not documented in Sessions.md.
CLAIM_TYPE: config-key
VERDICT: gap
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Configuration/SessionOptions.cs:18; src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs:92.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: Add a note in the "Key Types — SessionManager" or "Run" section that each session is bounded to `MxGateway:Sessions:MaxPendingCommandsPerSession` (default 128) concurrent in-flight worker commands.
---
DOC / LINES / (none — gap)
CLAIM: (gap) `GatewaySession` exposes a `KillWorkerWithCloseGateAsync` method that acquires `_closeLock` before killing, introduced to serialize concurrent close/kill callers (Server-045). This method is not mentioned; the doc describes only `KillWorker` as the unconditional kill path from `SessionManager`.
CLAIM_TYPE: term
VERDICT: gap
EVIDENCE: src/ZB.MOM.WW.MxGateway.Server/Sessions/GatewaySession.cs:896-917; src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs:233.
CODE_AREA: session.lifecycle
SEVERITY: low
PROPOSED_FIX: Mention `KillWorkerWithCloseGateAsync` in the "Close" section as the locked kill path now used by `SessionManager.KillWorkerAsync`, distinguishing it from the bare `KillWorker` still used as the internal terminal action.
+437
View File
@@ -0,0 +1,437 @@
# Cluster 04 — Auth
Auditor: Claude Code (claude-sonnet-4-6)
Date: 2026-06-03
Docs audited: docs/Authentication.md, docs/Authorization.md, glauth.md
Code verified against: src/ZB.MOM.WW.MxGateway.Server/Security/** and Dashboard/**
---
DOC / Authentication.md / LINES 253271
CLAIM / `AuthStoreServiceCollectionExtensions.AddSqliteAuthStore` wires services via direct `AddSingleton` calls for `IApiKeyParser`, `IApiKeySecretHasher`, `IApiKeyVerifier`, `IApiKeyStore`/`SqliteApiKeyStore`, `IApiKeyAdminStore`/`SqliteApiKeyAdminStore`, `IApiKeyAuditStore`/`SqliteApiKeyAuditStore`, `AuthSqliteConnectionFactory`, `IAuthStoreMigrator`/`SqliteAuthStoreMigrator`, `AuthStoreMigrationHostedService`.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:67 — the shared library `ZB.MOM.WW.Auth.ApiKeys` is registered via `services.AddZbApiKeyAuth(effectiveConfig, AuthenticationSectionPath)`, which owns all of those types. The local method no longer registers them individually. The doc code block is a fabricated snapshot of pre-migration code that no longer matches any method in the codebase.
CODE_AREA / auth.apikeys
SEVERITY / high
PROPOSED_FIX / Replace the Registration section code block with the actual method body from AuthStoreServiceCollectionExtensions.cs (calls AddZbApiKeyAuth, then registers CanonicalForwardingApiKeyAuditStore, SqliteCanonicalAuditStore, IAuditWriter, ApiKeyAdminCommands, ApiKeyAdminCliRunner). Remove the statement that AddSqliteAuthStore "registers the migration hosted service" — the hosted service is registered by AddZbApiKeyAuth, not by local code.
---
DOC / Authentication.md / LINES 5368
CLAIM / `ApiKeySecretHasher` (registered behind `IApiKeySecretHasher`) hashes secrets with `HMACSHA256` keyed by a server-side pepper. The pepper is resolved by `IConfiguration` lookup against `PepperSecretName`. `ApiKeyPepperUnavailableException` is thrown when the pepper is missing.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:58 — these types (`ApiKeySecretHasher`, `IApiKeySecretHasher`, `ApiKeyPepperUnavailableException`) now live in the shared package `ZB.MOM.WW.Auth.ApiKeys` (PackageReference in .csproj line 11). The behavior is correct but the doc presents them as if they are local gateway types. The interceptor's return type is `ApiKeyVerification` not `ApiKeyVerificationResult` (AuthStoreServiceCollectionExtensions.cs context; GatewayGrpcAuthorizationInterceptor.cs:69).
CODE_AREA / auth.apikeys
SEVERITY / medium
PROPOSED_FIX / Clarify that `ApiKeySecretHasher`, `IApiKeySecretHasher`, and `ApiKeyPepperUnavailableException` are provided by the `ZB.MOM.WW.Auth.ApiKeys` shared library, not gateway-local types. Correct `ApiKeyVerificationResult``ApiKeyVerification` (the type returned by `IApiKeyVerifier.VerifyAsync` in the interceptor).
---
DOC / Authentication.md / LINES 7298
CLAIM / `ApiKeyVerifier` (`IApiKeyVerifier`) step 5: "Compare hashes with `CryptographicOperations.FixedTimeEquals`." Step 6: "Record a `LastUsedUtc` timestamp via `MarkKeyUsedAsync` and return an `ApiKeyIdentity`." Code block shows `ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.SecretMismatch)` and `ApiKeyVerificationResult.Success(new ApiKeyIdentity(...))`.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs:69 — the interceptor receives `ApiKeyVerification verification`, not `ApiKeyVerificationResult`. These types are from the shared package `ZB.MOM.WW.Auth.ApiKeys` which was migrated to. The types, method signatures, and return types shown in the code block may have been renamed or restructured during the migration to the shared library; the gateway no longer owns or contains these implementations.
CODE_AREA / auth.apikeys
SEVERITY / medium
PROPOSED_FIX / Update type names to match the shared library (`ApiKeyVerification` instead of `ApiKeyVerificationResult`). Add note that `ApiKeyVerifier` is from `ZB.MOM.WW.Auth.ApiKeys`. Verify failure enum values against the shared library.
---
DOC / Authentication.md / LINES 108122
CLAIM / "`AuthSqliteConnectionFactory` reads `GatewayOptions.Authentication.SqlitePath`"
CLAIM_TYPE / term
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:67 — `AuthSqliteConnectionFactory` is now registered by `AddZbApiKeyAuth` from the shared package. The doc implies it is a local type that reads the gateway's `GatewayOptions`, but it is actually from `ZB.MOM.WW.Auth.ApiKeys` and reads `ApiKeyOptions.SqlitePath` (bound from `MxGateway:Authentication` section). The behavior is equivalent but the doc is misleading about the type ownership.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / Note that `AuthSqliteConnectionFactory` is from `ZB.MOM.WW.Auth.ApiKeys` and reads `ApiKeyOptions.SqlitePath` (bound via `MxGateway:Authentication:SqlitePath`).
---
DOC / Authentication.md / LINES 126133
CLAIM / "`SqliteAuthSchema` declares table names and the current schema version as constants. Three tables are involved: `api_keys`, `api_key_audit`, `schema_version`."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:6974 — a new `audit_event` table now exists in the same SQLite file, written by `SqliteCanonicalAuditStore`. The `api_key_audit` table is left in place but nothing writes to it once the `CanonicalForwardingApiKeyAuditStore` adapter overrides the library's audit store. The doc says only three tables; there are now at minimum four.
CODE_AREA / auth.apikeys
SEVERITY / medium
PROPOSED_FIX / Add `audit_event` as a fourth table (from `SqliteCanonicalAuditStore`). Note that `api_key_audit` is retained by the schema but is no longer written to at runtime (the `CanonicalForwardingApiKeyAuditStore` adapter redirects all writes to `audit_event` via `IAuditWriter`).
---
DOC / Authentication.md / LINES 134153
CLAIM / "`SqliteApiKeyStore` (`IApiKeyStore`) handles the two reads needed at request time: `FindByKeyIdAsync` and `FindActiveByKeyIdAsync`. `MarkKeyUsedAsync` updates `last_used_utc` only for non-revoked rows." Shows `ApiKeyRecordReader.Read` code block with column-ordinal reader.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:67 — `SqliteApiKeyStore` is in the shared package `ZB.MOM.WW.Auth.ApiKeys`. The code block shown is from the package, not local gateway code. If the package's internal implementation has changed, the doc may be inaccurate. The doc presents this as if it is local gateway source.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / Clarify that `SqliteApiKeyStore`, `ApiKeyRecord`, and `ApiKeyRecordReader` are in the shared `ZB.MOM.WW.Auth.ApiKeys` package and are not directly modifiable in this repository. Remove or label the code block as "from shared library."
---
DOC / Authentication.md / LINES 156164
CLAIM / "`SqliteApiKeyAdminStore` (`IApiKeyAdminStore`) implements administrative mutations: `CreateAsync`, `RevokeAsync`, `RotateAsync`, `DeleteAsync`."
CLAIM_TYPE / term
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:67 — `SqliteApiKeyAdminStore` is in `ZB.MOM.WW.Auth.ApiKeys`. The gateway now wraps admin operations through `ApiKeyAdminCommands` (from the same package), not by injecting `IApiKeyAdminStore` directly in the CLI runner. `DashboardSnapshotService` and `DashboardApiKeyManagementService` do consume `IApiKeyAdminStore` directly, which is fine.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / Note that `SqliteApiKeyAdminStore` is from the shared library. Note that the gateway CLI runner delegates through `ApiKeyAdminCommands` (shared library), not by calling `IApiKeyAdminStore` directly.
---
DOC / Authentication.md / LINES 165183
CLAIM / "`SqliteAuthStoreMigrator` executes the migration inside a single transaction so a partial failure leaves the database untouched, refuses to start when the on-disk schema version is newer than the binary supports, and idempotently creates the v1 schema." "Operators who manage schema out-of-band can disable the hosted run and use the admin CLI's `init-db` command instead."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:104 — `SqliteAuthStoreMigrator` is from `ZB.MOM.WW.Auth.ApiKeys` (resolved via `sp.GetRequiredService<SqliteAuthStoreMigrator>()`). The description of its behavior is likely still accurate but is presented as locally-owned code. `AuthStoreMigrationHostedService` is also from the shared package (registered by `AddZbApiKeyAuth`). The code block shown at lines 171179 is from the package.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / Clarify that `SqliteAuthStoreMigrator`, `IAuthStoreMigrator`, and `AuthStoreMigrationHostedService` are from the shared library.
---
DOC / Authentication.md / LINES 187208
CLAIM / CLI subcommand table lists: `init-db`, `create-key`, `list-keys`, `revoke-key`, `rotate-key`. CLI example uses `mxgateway apikey create-key --key-id ops.alice --display-name "Alice (ops)" --scopes read,write`.
CLAIM_TYPE / command
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayScopes.cs:513 — `GatewayScopes.All` contains `session:open`, `session:close`, `invoke:read`, `invoke:write`, `invoke:secure`, `events:read`, `metadata:read`, `admin`. The values `read` and `write` are not in the scope catalog. `ApiKeyAdminCommandLineParser.ValidateScopes` at line 170177 would reject `--scopes read,write` as unknown scopes.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / Replace `--scopes read,write` with valid scope strings, e.g. `--scopes invoke:read,invoke:write`. Update all CLI examples in Authentication.md to use canonical scope strings from `GatewayScopes.All`.
---
DOC / Authentication.md / LINES 229248
CLAIM / "`ApiKeyScopeSerializer.Serialize` writes a JSON array sorted with `StringComparer.Ordinal`." Code block shown.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:5 — `ApiKeyScopeSerializer` is from the shared `ZB.MOM.WW.Auth.ApiKeys` package. The behavior described is likely correct but is presented as local gateway code.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / Note that `ApiKeyScopeSerializer` is in the shared `ZB.MOM.WW.Auth.ApiKeys` library.
---
DOC / Authorization.md / LINES 107113
CLAIM / Scope resolver code block includes `TestConnectionRequest or GetLastDeployTimeRequest or DiscoverHierarchyRequest or WatchDeployEventsRequest => GatewayScopes.MetadataRead`.
CLAIM_TYPE / rpc/proto
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcScopeResolver.cs:2328 — the actual resolver also includes `BrowseChildrenRequest => GatewayScopes.MetadataRead` in the same arm. `BrowseChildrenRequest` was added (per docs/plans/2026-05-28-lazy-browse-implementation.md) but the code block in Authorization.md was not updated.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / Add `BrowseChildrenRequest` to the `MetadataRead` arm of the scope resolver code block. Update the scope catalog table at line 212 to include `GalaxyRepository.BrowseChildren` in the `MetadataRead` row.
---
DOC / Authorization.md / LINE 212
CLAIM / Scope catalog table row: `MetadataRead` / `metadata:read` / "`MxCommandKind.ArchestraUserToId`, `MxCommandKind.GetSessionState`, `MxCommandKind.GetWorkerInfo`, `GalaxyRepository.TestConnection`, `GalaxyRepository.GetLastDeployTime`, `GalaxyRepository.DiscoverHierarchy`, `GalaxyRepository.WatchDeployEvents`".
CLAIM_TYPE / rpc/proto
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcScopeResolver.cs:27 — `BrowseChildrenRequest` is also mapped to `metadata:read` but is absent from the table.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / Add `GalaxyRepository.BrowseChildren` to the `MetadataRead` row of the scope catalog table.
---
DOC / Authorization.md / LINES 260270
CLAIM / Registration code block for `AddGatewayGrpcAuthorization` shows three `AddSingleton` calls: `GatewayGrpcScopeResolver`, `IGatewayRequestIdentityAccessor`/`GatewayRequestIdentityAccessor`, `GatewayGrpcAuthorizationInterceptor`, then `AddGrpc`.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GrpcAuthorizationServiceCollectionExtensions.cs:1831 — the actual method also registers `IConstraintEnforcer`/`ConstraintEnforcer` as a singleton (line 20) and configures `GrpcServiceOptions` with `MaxReceiveMessageSize`/`MaxSendMessageSize` from `MxGateway:Protocol`. The doc code block omits both.
CODE_AREA / auth.scopes
SEVERITY / medium
PROPOSED_FIX / Update the Registration code block to include `services.AddSingleton<IConstraintEnforcer, ConstraintEnforcer>()` and the `AddOptions<GrpcServiceOptions>` configuration block for message size limits.
---
DOC / Authorization.md / LINE 273
CLAIM / "none of the three classes hold per-request state on instance fields"
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GrpcAuthorizationServiceCollectionExtensions.cs:20 — there are now four singleton classes registered by `AddGatewayGrpcAuthorization` (`GatewayGrpcScopeResolver`, `GatewayRequestIdentityAccessor`, `GatewayGrpcAuthorizationInterceptor`, `ConstraintEnforcer`), not three.
CODE_AREA / auth.scopes
SEVERITY / low
PROPOSED_FIX / Update "three classes" to "four classes."
---
DOC / glauth.md / LINES 6366
CLAIM / "`LdapOptions.RequiredGroup` defaults to `GwAdmin`, so the dashboard login and `DashboardLdapLiveTests` require `admin` to be a member of a `GwAdmin` group."
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs — no `RequiredGroup` field exists on the gateway's `LdapOptions`. The gateway enforces group membership via `MxGateway:Dashboard:GroupToRole` (a dictionary mapping LDAP group names to dashboard roles) in `DashboardOptions`. Authorization succeeds if the user's LDAP groups map to at least one role — there is no `RequiredGroup` concept in the current architecture.
CODE_AREA / auth.ldap
SEVERITY / high
PROPOSED_FIX / Remove the sentence "`LdapOptions.RequiredGroup` defaults to `GwAdmin`." Replace with: the dashboard enforces that at least one of the user's LDAP groups appears in `MxGateway:Dashboard:GroupToRole` (e.g. `GwAdmin: Administrator`); a login with no matching group is rejected. `DashboardLdapLiveTests` seeds the role map with `GwAdmin -> Administrator`.
---
DOC / glauth.md / LINES 181182
CLAIM / "the authenticator strips to `GwAdmin` and matches against `RequiredGroup`"
CLAIM_TYPE / behavior-rule
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardGroupRoleMapping.cs:3548 — the shared `ILdapAuthService` strips the leading RDN value from each group DN, and the gateway's `DashboardGroupRoleMapper` looks up the short name in `GroupToRole`. There is no `RequiredGroup` property or concept anywhere in the codebase.
CODE_AREA / auth.ldap
SEVERITY / high
PROPOSED_FIX / Replace "matches against `RequiredGroup`" with "looks up the short RDN name (e.g. `GwAdmin`) in `MxGateway:Dashboard:GroupToRole`."
---
DOC / glauth.md / LINES 113136
CLAIM / "Suggested mxgw configuration shape" YAML block uses config keys `useTls`, `allowInsecureLdap`, `userNameAttribute`.
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs:49,52,64 — the current config keys (as bound by the shared `LdapOptions` and the gateway's shadow `LdapOptions`) are `Transport` (an enum: `None`/`Ldaps`/`StartTls`), `AllowInsecure` (bool), `UserNameAttribute` (string, default `"cn"` not `"uid"`). The YAML block uses stale camelCase key names from a pre-migration configuration shape.
CODE_AREA / auth.ldap
SEVERITY / high
PROPOSED_FIX / Update the YAML config example to use `Transport: None` (or `Ldaps`/`StartTls`) instead of `useTls: false`, `AllowInsecure: true` instead of `allowInsecureLdap: true`, `UserNameAttribute: "cn"` (gateway default; note GLAuth populates `cn` not `uid` per the gateway default). Rename the section header from `ldap:` to `MxGateway: Ldap:` to match the actual config path.
---
DOC / glauth.md / LINE 128
CLAIM / `userNameAttribute: "uid" # GLAuth populates this; AD uses sAMAccountName`
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs:64 — the gateway `LdapOptions` default for `UserNameAttribute` is `"cn"`, not `"uid"`. GLAuth does populate both `uid` and `cn`, but the gateway ships `"cn"` as default.
CODE_AREA / auth.ldap
SEVERITY / medium
PROPOSED_FIX / Change example to `UserNameAttribute: "cn"` with a note that the gateway default is `cn`; to use `uid` instead set `MxGateway:Ldap:UserNameAttribute: uid`.
---
DOC / glauth.md / LINES 261269
CLAIM / AD migration cheat-sheet uses field names `UseTls` and `AllowInsecureLdap`.
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs:49,52 — these fields were renamed: `UseTls``Transport` (enum), `AllowInsecureLdap``AllowInsecure`.
CODE_AREA / auth.ldap
SEVERITY / high
PROPOSED_FIX / Update the AD migration table: rename `UseTls` row to `Transport` (GLAuth dev value: `None`, AD value: `Ldaps`); rename `AllowInsecureLdap` row to `AllowInsecure` (GLAuth dev: `true`, AD: `false`).
---
DOC / CLAUDE.md / LINE 119
CLAIM / "maps the user's LDAP groups to `Admin` or `Viewer` via `MxGateway:Dashboard:GroupToRole`, then issues an HTTP-only secure `__Host-MxGatewayDashboard` cookie"
CLAIM_TYPE / term
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticationDefaults.cs:38 — the cookie name constant is `CookieName = "MxGatewayDashboard"` (no `__Host-` prefix). `__Host-` is a browser security prefix that requires `Path=/`, no `Domain`, and `Secure` — the code sets `Path = "/"` and `SecurePolicy = Always` by default, satisfying the requirements, but the actual cookie name in the constant and in `ZbCookieDefaults.Apply` is `MxGatewayDashboard`, not `__Host-MxGatewayDashboard`. Additionally, `Admin` should be `Administrator` (the renamed role value per `DashboardRoles.Admin = "Administrator"`).
CODE_AREA / auth.cookie
SEVERITY / high
PROPOSED_FIX / Change `__Host-MxGatewayDashboard` to `MxGatewayDashboard` in CLAUDE.md. Change `Admin` to `Administrator`.
---
DOC / CLAUDE.md / LINE 119
CLAIM / "maps the user's LDAP groups to `Admin` or `Viewer`"
CLAIM_TYPE / term
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs:14 — `DashboardRoles.Admin = "Administrator"` (not `"Admin"`). The role value was renamed in Task 1.7. CLAUDE.md was not updated.
CODE_AREA / auth.roles
SEVERITY / high
PROPOSED_FIX / Change `Admin` to `Administrator` in the CLAUDE.md authentication paragraph.
---
DOC / CLAUDE.md / LINE 35
CLAIM / `dotnet run --project src/MxGateway.Server/MxGateway.Server.csproj -- apikey create --display-name "dev" --scopes session,invoke,event,metadata,admin`
CLAIM_TYPE / command
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayScopes.cs:513 — canonical scopes are `session:open`, `session:close`, `invoke:read`, `invoke:write`, `invoke:secure`, `events:read`, `metadata:read`, `admin`. The shorthand values `session`, `invoke`, `event`, `metadata` are not recognized and would be rejected by `ApiKeyAdminCommandLineParser.ValidateScopes` as unknown scopes. Also, the subcommand is `create-key` not `create`.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / Replace the example with a valid invocation, e.g.: `dotnet run --project src/MxGateway.Server/MxGateway.Server.csproj -- apikey create-key --key-id dev --display-name "dev" --scopes session:open,session:close,invoke:read,invoke:write,events:read,metadata:read,admin`
---
DOC / CLAUDE.md / LINE 117
CLAIM / "Keys are stored hashed (with a peppered SHA) in a gateway-owned SQLite DB (default `C:\ProgramData\MxGateway\gateway-auth.db`). Scopes (`session`, `invoke`, `event`, `metadata`, `admin`) gate specific RPCs"
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/AuthenticationOptions.cs:9 — SQLite path default is correct. However, scope names `session`, `invoke`, `event`, `metadata` are not the canonical scope strings. Actual scopes are `session:open`, `session:close`, `invoke:read`, `invoke:write`, `invoke:secure`, `events:read`, `metadata:read`, `admin`.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / Replace the scope shorthand list with the full canonical scope strings from `GatewayScopes.All`. The SQLite path is accurate and should be kept.
---
DOC / glauth.md / LINES 7074
CLAIM / "> **Dashboard role value (Task 1.7):** the LDAP `GwAdmin` group now maps to the canonical dashboard role **`Administrator`** (was `Admin`); `GwReader` maps to `Viewer`."
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs:14 — `DashboardRoles.Admin = "Administrator"`, `DashboardRoles.Viewer = "Viewer"`. src/ZB.MOM.WW.MxGateway.Server/appsettings.json:6364 confirms `"GwAdmin": "Administrator"`, `"GwReader": "Viewer"`.
CODE_AREA / auth.roles
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / glauth.md / LINES 2126
CLAIM / Connection details: Protocol LDAP, Host `localhost`, Port `3893`, Base DN `dc=zb,dc=local`, Bind DN format `cn={username},dc=zb,dc=local`, Group OU `ou=<groupname>,ou=groups,dc=zb,dc=local`.
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs:36,39,55,58 — defaults: `Server=localhost`, `Port=3893`, `SearchBase=dc=zb,dc=local`, `ServiceAccountDn=cn=serviceaccount,dc=zb,dc=local`.
CODE_AREA / auth.ldap
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authentication.md / LINES 130
CLAIM / Token format `mxgw_<keyId>_<secret>`, prefix `mxgw_`, parser is `ApiKeyParser` behind `IApiKeyParser`.
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:30,33 — `TokenPrefix = "mxgw"`, `PepperSecretName = "MxGateway:ApiKeyPepper"`. The token format claim is accurate; `IApiKeyParser`/`ApiKeyParser` are from the shared package but the behavior description matches.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authentication.md / LINE 110
CLAIM / "`AuthSqliteConnectionFactory` reads `GatewayOptions.Authentication.SqlitePath`"
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/AuthenticationOptions.cs:9 — `SqlitePath` default is `C:\ProgramData\MxGateway\gateway-auth.db`. The factory reads from `ApiKeyOptions.SqlitePath` which is bound from `MxGateway:Authentication:SqlitePath`, so the effective config key path matches `GatewayOptions.Authentication.SqlitePath`.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authentication.md / LINES 189208
CLAIM / CLI subcommands: `init-db`, `create-key`, `list-keys`, `revoke-key`, `rotate-key`.
CLAIM_TYPE / command
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyAdminCommandKind.cs — enum has `InitDb`, `CreateKey`, `ListKeys`, `RevokeKey`, `RotateKey`. ApiKeyAdminCommandLineParser.cs maps these to exactly those string values.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authentication.md / LINES 220225
CLAIM / "Every destructive dashboard action is gated by a confirmation dialog and emits its own audit event (`dashboard-create-key`, `dashboard-rotate-key`, `dashboard-revoke-key`, `dashboard-delete-key`)."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardApiKeyManagementService.cs:69,201 — audit event strings `dashboard-create-key` and `dashboard-delete-key` confirmed in code.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authorization.md / LINES 94116
CLAIM / Scope resolver switches on request type; `_ => GatewayScopes.Admin` fallback for unrecognized types.
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcScopeResolver.cs:1329 — the pattern and fallback match exactly.
CODE_AREA / auth.scopes
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authorization.md / LINE 85
CLAIM / "If `GatewayOptions.Authentication.Mode` is `AuthenticationMode.Disabled`, the helper returns `null` immediately. No identity is pushed onto the accessor and the continuation runs without scope enforcement. This matches the `AuthenticationMode` enum, which only defines `ApiKey` and `Disabled`."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs:59 — confirmed.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authorization.md / LINE 215
CLAIM / "The `Admin` constant is also referenced by `DashboardAuthenticator` and `DashboardAuthorizationHandler` so that the dashboard and the gRPC layer agree on what 'admin' means."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs — `DashboardAuthenticator` does not reference `GatewayScopes.Admin`. The `admin` gRPC scope and the `Administrator` dashboard role are separate concepts. The dashboard authorization policy uses `DashboardRoles.Admin = "Administrator"`, not `GatewayScopes.Admin = "admin"`. These are distinct and do not share a constant.
CODE_AREA / auth.roles
SEVERITY / medium
PROPOSED_FIX / Correct or remove the claim that `GatewayScopes.Admin` is referenced by `DashboardAuthenticator`. The dashboard and gRPC "admin" are deliberately separate concepts — the dashboard role is `Administrator` (a role claim value on the ClaimsPrincipal), while the gRPC scope is the literal string `"admin"` (a scope string on ApiKeyIdentity).
---
DOC / docs/Authorization.md / LINE 116
CLAIM / "`AcknowledgeAlarm` is treated as a write — it mutates alarm state, mirroring `MxCommandKind.Write*` — and `StreamAlarms` shares the alarm/event surface with `StreamEvents` and `MxCommandKind.DrainEvents`, so it carries `events:read`. Both alarm RPCs are session-less."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcScopeResolver.cs:21,22 — `AcknowledgeAlarmRequest => GatewayScopes.InvokeWrite`, `StreamAlarmsRequest => GatewayScopes.EventsRead`. Confirmed.
CODE_AREA / auth.scopes
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authorization.md / LINES 205215
CLAIM / Scope catalog table — all scope strings and their `Required For` mappings.
CLAIM_TYPE / rpc/proto
VERDICT / stale
EVIDENCE / GatewayGrpcScopeResolver.cs:27 — `BrowseChildrenRequest` is missing from the `MetadataRead` row (already captured above). All other rows are accurate.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / (Same as finding above — add `GalaxyRepository.BrowseChildren` to `MetadataRead` row.)
---
## GAP FINDINGS (auth behavior in code but undocumented)
DOC / (none — gap)
CLAIM / `DashboardAuthenticationDefaults.CookieName` is the default cookie name `"MxGatewayDashboard"`, but `DashboardOptions.CookieName` allows a per-deployment override via `MxGateway:Dashboard:CookieName`. Auth docs do not mention this override.
CLAIM_TYPE / config-key
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs:9197, src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs:33.
CODE_AREA / auth.cookie
SEVERITY / medium
PROPOSED_FIX / Add documentation of `MxGateway:Dashboard:CookieName` override and when to use it (multiple gateway instances sharing a hostname).
---
DOC / (none — gap)
CLAIM / The dashboard cookie idle timeout is 8 hours (set by `ZbCookieDefaults.Apply` with `idleTimeout: TimeSpan.FromHours(8)`). The hub bearer token expires in 30 minutes (`HubTokenService.TokenLifetime = TimeSpan.FromMinutes(30)`). Neither timeout is documented in Authentication.md.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs:66, src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs:29.
CODE_AREA / auth.hub
SEVERITY / medium
PROPOSED_FIX / Add a section in Authentication.md (or GatewayDashboardDesign.md) documenting the 8-hour dashboard cookie idle timeout and the 30-minute hub bearer token lifetime.
---
DOC / (none — gap)
CLAIM / The `CanonicalForwardingApiKeyAuditStore` overrides the shared library's `IApiKeyAuditStore`. As a result, the `api_key_audit` table in the SQLite DB is written by the shared library's migration but is NOT written to at runtime — all audit records go to `audit_event` via `IAuditWriter`. This is operationally important for anyone reading the DB directly but is not documented.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:8594, src/ZB.MOM.WW.MxGateway.Server/Security/Audit/CanonicalForwardingApiKeyAuditStore.cs.
CODE_AREA / auth.apikeys
SEVERITY / medium
PROPOSED_FIX / Document in Authentication.md that `api_key_audit` exists in the schema but is unused at runtime; all audit events flow to `audit_event` via `IAuditWriter`/`SqliteCanonicalAuditStore`.
---
DOC / (none — gap)
CLAIM / `DashboardOptions.RequireHttpsCookie` (default `true`) controls whether the dashboard cookie uses `SecurePolicy.Always` or `SameAsRequest`. Setting it `false` is required for plain-HTTP dev deployments. This config key is not mentioned in auth docs.
CLAIM_TYPE / config-key
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs:22, src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs:87.
CODE_AREA / auth.cookie
SEVERITY / low
PROPOSED_FIX / Reference `MxGateway:Dashboard:RequireHttpsCookie` in the auth cookie documentation.
---
DOC / (none — gap)
CLAIM / `ZbClaimTypes` and `ZbCookieDefaults` (from `ZB.MOM.WW.Auth.AspNetCore` package) are now used for claim and cookie setup. Authentication.md does not mention the shared library claim types (`zb:username`, `zb:displayname`) or that cookie hardening defaults come from `ZbCookieDefaults.Apply`.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs:111115, src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs:66.
CODE_AREA / auth.cookie
SEVERITY / low
PROPOSED_FIX / Add a brief note in dashboard auth documentation about `ZbClaimTypes` (`zb:username`, `zb:displayname`, `zb:name`, `zb:role`) and `ZbCookieDefaults.Apply` providing cookie security defaults.
+332
View File
@@ -0,0 +1,332 @@
# Cluster 05 — Dashboard
Audited docs: `docs/DashboardInterfaceDesign.md`, `docs/GatewayDashboardDesign.md`
Verified against: `src/ZB.MOM.WW.MxGateway.Server/Dashboard/**`, `src/ZB.MOM.WW.MxGateway.Server/wwwroot/**`
Audit date: 2026-06-03
---
DOC / DashboardInterfaceDesign.md / LINES / 3957
CLAIM / "The shell does not use a sidebar. A horizontal navigation bar is enough…" with a `<div class="dashboard-shell">` / `<nav class="navbar navbar-expand-lg bg-body border-bottom dashboard-navbar">` HTML skeleton
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Layout/MainLayout.razor:7 — layout now delegates to `<ThemeShell Product="MXAccess Gateway" Accent="#2f5fd0">` with `NavRailSection`/`NavRailItem` kit components; there is no local navbar, no `dashboard-shell` class, no `dashboard-navbar` class, and no `container-fluid` content area anywhere in the codebase
CODE_AREA / dashboard.theme
SEVERITY / high
PROPOSED_FIX / Replace the HTML skeleton and prose description with the current ThemeShell side-rail pattern (`<ThemeShell>``<Nav>``<NavRailSection>` / `<NavRailItem>`). Update the note about "horizontal navigation bar" — the nav is now a collapsible side rail managed by the ZB.MOM.WW.Theme kit.
---
DOC / DashboardInterfaceDesign.md / LINES / 115123
CLAIM / Navigation uses `NavLink` and labels: `Overview`, `Sessions`, `Workers`, `Events`, `Settings`
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Layout/MainLayout.razor:923 — actual nav items are `Dashboard` (not "Overview"), `Sessions`, `Workers`, `Events`, `Alarms`, `Repository`, `Browse`, `API Keys`, `Settings`; the old flat list of five labels has been replaced by three grouped NavRailSections (`Runtime`, `Galaxy`, `Admin`) with eight leaf items
CODE_AREA / dashboard.theme
SEVERITY / high
PROPOSED_FIX / Update the nav-label list to match the current ThemeShell Nav: Dashboard / [Runtime: Sessions, Workers, Events, Alarms] / [Galaxy: Repository, Browse] / [Admin: API Keys, Settings].
---
DOC / DashboardInterfaceDesign.md / LINES / 6379
CLAIM / Four local CSS tokens: `--mxgw-surface: #f7f8fa`, `--mxgw-border: #d8dee6`, `--mxgw-ink-muted: #667085`, `--mxgw-accent: #146c64`
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/wwwroot/css/site.css:17 — no `--mxgw-*` tokens are defined anywhere in `site.css`; the file's own header states "Every colour … resolves to a theme.css token — no hard-coded hex." All colour expressions in site.css use theme kit tokens: `var(--card)`, `var(--rule)`, `var(--ink)`, `var(--ink-faint)`, `var(--accent)`, `var(--accent-deep)`, `var(--bad)`, `var(--bad-bg)`, etc.
CODE_AREA / dashboard.css
SEVERITY / high
PROPOSED_FIX / Remove the `--mxgw-*` token table entirely. Replace with a note that the dashboard's view-layer CSS (`site.css`) resolves all colour via the `ZB.MOM.WW.Theme` kit tokens (`--card`, `--rule`, `--ink`, `--ink-faint`, `--accent`, `--accent-deep`, etc.) and defines no local colour tokens.
---
DOC / DashboardInterfaceDesign.md / LINES / 8797
CLAIM / Page headings use `1.35rem`, weight `650`. Metric labels use `uppercase text at .78rem` and weight `650`. Metric values use `1.7rem`, weight `700`, and the accent color.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / site.css:3031 (page h1: `font-size: 1.15rem; font-weight: 600`), site.css:6465 (agg-label: `font-size: 0.68rem; font-weight: 600`), site.css:7677 (agg-value: `font-size: 1.5rem; font-weight: 600; color: var(--ink)`) — page headings are `1.15rem/600` (not `1.35rem/650`); metric values are `1.5rem/600` in `var(--ink)` (not `1.7rem/700` in accent color)
CODE_AREA / dashboard.css
SEVERITY / medium
PROPOSED_FIX / Update typography table: h1 → 1.15rem/600, agg-label → 0.68rem/600/uppercase, agg-value → 1.5rem/600/var(--ink). Note metric values render in ink (not the accent colour) per the post-theme-migration design.
---
DOC / DashboardInterfaceDesign.md / LINES / 99111
CLAIM / Page content has `1.25rem` padding on desktop and `.75rem` on small screens. Metric grids use `.75rem` gaps. Cards and empty states use Bootstrap's small radius `.375rem`. Content sections start with a top border and `1rem` top padding.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / site.css:272279 (`@media (max-width: 700px)` sets `.page { padding: 0.85rem }`; no 1.25rem desktop padding rule exists in site.css). site.css:59 (`border-radius: 8px` for `.agg-card`, not `.375rem`). site.css:59 (`box-shadow: none` but no top-border-only sections — `.dashboard-section` at line 91100 is a raised card with `border: 1px solid var(--rule); border-radius: 8px`).
CODE_AREA / dashboard.css
SEVERITY / low
PROPOSED_FIX / Update spacing table: small-screen padding is 0.85rem; cards use 8px radius; sections are full-border raised cards (not top-border-only dividers).
---
DOC / DashboardInterfaceDesign.md / LINES / 153168
CLAIM / `metric-grid` uses `repeat(auto-fit, minmax(12rem, 1fr))` and `compact` variant uses `minmax(10rem, 1fr)`
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / site.css:49 (`grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr))`) and site.css:54 (`repeat(auto-fill, minmax(10rem, 1fr))`) — base grid is `auto-fill, 11rem` (not `auto-fit, 12rem`); `auto-fill` not `auto-fit`
CODE_AREA / dashboard.css
SEVERITY / low
PROPOSED_FIX / Update code block: base grid is `repeat(auto-fill, minmax(11rem, 1fr))`; compact stays `minmax(10rem, 1fr)`.
---
DOC / DashboardInterfaceDesign.md / LINES / 191200
CLAIM / Status uses Bootstrap badge classes (`text-bg-success`, `text-bg-info`, `text-bg-secondary`, `text-bg-danger`, `text-bg-light text-dark border`) with mapping: `Closed``text-bg-secondary`; `Creating`/`StartingWorker`/`WaitingForPipe`/`InitializingWorker`/`Closing``text-bg-info`
CLAIM_TYPE / behavior-rule
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Shared/StatusBadge.razor:116 — `StatusBadge` is now a thin adapter over the kit's `<StatusPill State="…">` component; no Bootstrap `text-bg-*` classes are used at all. State mapping uses `StatusState.Ok/Warn/Bad/Idle`. `Closed` falls through to `StatusState.Idle` (no `text-bg-secondary`). `Closing` is mapped to `StatusState.Warn` (not info). New states `Stale`, `Degraded`, `Active`, `Unavailable` are handled; `Unknown state``StatusState.Idle` (was `text-bg-light text-dark border`).
CODE_AREA / dashboard.theme
SEVERITY / high
PROPOSED_FIX / Replace the badge-class table with the current `StatusState` enum vocabulary: Ok (`Ready`, `Healthy`, `Active`); Warn (`Creating`, `StartingWorker`, `WaitingForPipe`, `InitializingWorker`, `Closing`, `Stale`, `Degraded`); Bad (`Faulted`, `Unavailable`); Idle (everything else including `Closed`). Note that visual rendering is owned by the ZB.MOM.WW.Theme `StatusPill` component.
---
DOC / DashboardInterfaceDesign.md / LINES / 229245
CLAIM / Responsive breakpoint CSS: `.dashboard-content { padding: .75rem }` at `max-width: 700px`
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / site.css:272279 — the responsive rule targets `.page { padding: 0.85rem }` (not `.dashboard-content`; not `.75rem`)
CODE_AREA / dashboard.css
SEVERITY / low
PROPOSED_FIX / Update code block to `@media (max-width: 700px) { .page { padding: 0.85rem; } … }`.
---
DOC / GatewayDashboardDesign.md / LINES / 78110
CLAIM / Component tree lists `Layout/DashboardLayout.razor` and `Shared/StatusBadge.razor` as standalone status component
CLAIM_TYPE / path
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Layout/ contains only `MainLayout.razor` and `LoginLayout.razor`; there is no `DashboardLayout.razor`. `StatusBadge.razor` still exists but is now a thin wrapper delegating to the kit's `StatusPill`; the doc's tree implies it is a standalone Bootstrap-badge component.
CODE_AREA / dashboard.theme
SEVERITY / medium
PROPOSED_FIX / In the component tree rename `DashboardLayout.razor``MainLayout.razor` and `LoginLayout.razor`. Add a note that `StatusBadge` delegates to `ZB.MOM.WW.Theme`'s `StatusPill`. Also add `BrowseTreeNodeView.razor` and `ConfirmDialog.razor` which are present in code but absent from the tree.
---
DOC / GatewayDashboardDesign.md / LINES / 507510
CLAIM / "The dashboard serves Bootstrap 5.3.3 assets from `src/ZB.MOM.WW.MxGateway.Server/wwwroot/lib/bootstrap/` and local layout/status styling from `src/ZB.MOM.WW.MxGateway.Server/wwwroot/css/dashboard.css`."
CLAIM_TYPE / path
VERDICT / wrong
EVIDENCE / find shows `wwwroot/css/site.css` exists; there is no `dashboard.css` under wwwroot. Bootstrap 5.3.3 is confirmed (bootstrap.min.css header). App.razor:89 loads `/css/site.css`, not `/css/dashboard.css`. Additionally the denied-page renderer at DashboardEndpointRouteBuilderExtensions.cs:172173 also loads theme kit CSS: `/_content/ZB.MOM.WW.Theme/css/theme.css` and `/_content/ZB.MOM.WW.Theme/css/layout.css`.
CODE_AREA / dashboard.css
SEVERITY / high
PROPOSED_FIX / Change `dashboard.css``site.css` throughout. Add that App.razor also loads `<ThemeHead />` (which injects the theme kit's CSS) and `<ThemeScripts />`. Note the denied-page also pulls `/_content/ZB.MOM.WW.Theme/css/theme.css` and `/_content/ZB.MOM.WW.Theme/css/layout.css` directly.
---
DOC / GatewayDashboardDesign.md / LINES / 406428
CLAIM / "`DashboardAuthenticator` binds against `MxGateway:Ldap` … using `Novell.Directory.Ldap.NETStandard`"
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs:16 — imports are `ZB.MOM.WW.Auth.Abstractions.Ldap`, `ZB.MOM.WW.Auth.Abstractions.Roles`, `ZB.MOM.WW.Auth.AspNetCore`; the csproj references `ZB.MOM.WW.Auth.Ldap 0.1.2`, not Novell. DashboardServiceCollectionExtensions.cs:35 calls `services.AddZbLdapAuth(configuration, "MxGateway:Ldap")`. `Novell.Directory.Ldap.NETStandard` is not referenced in the csproj.
CODE_AREA / dashboard.login
SEVERITY / medium
PROPOSED_FIX / Replace "using `Novell.Directory.Ldap.NETStandard`" with "using the shared `ZB.MOM.WW.Auth.Ldap` package (`ILdapAuthService`), registered via `AddZbLdapAuth`".
---
DOC / GatewayDashboardDesign.md / LINES / 420422
CLAIM / Cookie name is `__Host-MxGatewayDashboard`
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticationDefaults.cs:38 — `public const string CookieName = "MxGatewayDashboard"` (no `__Host-` prefix). The code comment explains the `__Host-` prefix is not used; the cookie path is `/` set explicitly at DashboardServiceCollectionExtensions.cs:72.
CODE_AREA / dashboard.login
SEVERITY / high
PROPOSED_FIX / Change `__Host-MxGatewayDashboard` to `MxGatewayDashboard` everywhere in the auth section. Note that the cookie name is configurable via `MxGateway:Dashboard:CookieName`.
---
DOC / GatewayDashboardDesign.md / LINES / 289306
CLAIM / Browse page is at `/dashboard/browse`; tree built by `DashboardBrowseTreeBuilder` from `IGalaxyHierarchyCache.Current`; subscription panel is the explicit opt-in for tag values
CLAIM_TYPE / path
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Pages/BrowsePage.razor:1 — route is `@page "/browse"` (not `/dashboard/browse`). `DashboardBrowseTreeBuilder` is found at DashboardBrowseModel.cs:66 as a static class. Browse uses `IGalaxyHierarchyCache` (injected via `IDashboardBrowseService`) confirmed at BrowsePage.razor:3.
CODE_AREA / dashboard.hub
SEVERITY / medium
PROPOSED_FIX / Fix route to `/browse` (not `/dashboard/browse`). The tree builder name is accurate but clarify it is a static class inside `DashboardBrowseModel.cs`.
---
DOC / GatewayDashboardDesign.md / LINES / 307318
CLAIM / Alarms page is at `/dashboard/alarms`; defaults to showing unacknowledged `Active` alarms; Alarms page reads via `IDashboardLiveDataService`
CLAIM_TYPE / path
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Pages/AlarmsPage.razor:1 — route is `@page "/alarms"` (not `/dashboard/alarms`). The claim that alarm data comes from "gateway's always-on central monitor" via `IGatewayAlarmService.CurrentAlarms` is contradicted by the implementation: AlarmsPage.razor:3 injects `IDashboardLiveDataService` and calls `LiveData.QueryAlarmsAsync` in a poll loop — not `IGatewayAlarmService.CurrentAlarms` directly.
CODE_AREA / dashboard.hub
SEVERITY / medium
PROPOSED_FIX / Fix route to `/alarms`. Correct the live data source description: the Alarms page uses `IDashboardLiveDataService.QueryAlarmsAsync` (a polling loop every 3 s), not a direct read of `IGatewayAlarmService.CurrentAlarms`.
---
DOC / GatewayDashboardDesign.md / LINES / 337345
CLAIM / API keys page is at `/dashboard/apikeys`
CLAIM_TYPE / path
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Pages/ApiKeysPage.razor:1 — route is `@page "/apikeys"` (not `/dashboard/apikeys`)
CODE_AREA / dashboard.hub
SEVERITY / medium
PROPOSED_FIX / Fix route to `/apikeys`.
---
DOC / GatewayDashboardDesign.md / LINES / 387391
CLAIM / "Every management action appends an `api_key_audit` entry (`dashboard-create-key`, `dashboard-rotate-key`, `dashboard-revoke-key`, `dashboard-delete-key`) with the key id and the caller's remote address."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardApiKeyManagementService.cs:59 — "both rows land in the canonical audit_event store"; DashboardApiKeyManagementService.cs:69,113,156,201 — action strings `dashboard-create-key`, `dashboard-rotate-key`, `dashboard-revoke-key`, `dashboard-delete-key` are confirmed. However the table used is `audit_event` (via `IAuditWriter`), not `api_key_audit`. Comments explicitly state "the library's `api_key_audit` table is left in place but UNUSED".
CODE_AREA / dashboard.login
SEVERITY / medium
PROPOSED_FIX / Change "appends an `api_key_audit` entry" to "appends an `audit_event` entry (via `IAuditWriter`)". The `api_key_audit` table is no longer used for dashboard actions.
---
DOC / GatewayDashboardDesign.md / LINES / 6869
CLAIM / Galaxy page is at `/galaxy`; "summary is fed by `GalaxySummaryCache`, which is refreshed off the request path by `GalaxySummaryRefreshService` on the `MxGateway:Galaxy:DashboardRefreshIntervalSeconds` cadence"
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / `GalaxySummaryCache` and `GalaxySummaryRefreshService` do not exist in the codebase. The actual implementation uses `IGalaxyHierarchyCache` (GalaxyHierarchyCache.cs) refreshed by `GalaxyHierarchyRefreshService` (Galaxy/GalaxyHierarchyRefreshService.cs:19), driven by `GalaxyRepositoryOptions.DashboardRefreshIntervalSeconds` under config key `MxGateway:Galaxy:DashboardRefreshIntervalSeconds` (confirmed by appsettings.json:74). Galaxy page route `/galaxy` is confirmed.
CODE_AREA / dashboard.hub
SEVERITY / medium
PROPOSED_FIX / Replace `GalaxySummaryCache` / `GalaxySummaryRefreshService` with `GalaxyHierarchyCache` / `GalaxyHierarchyRefreshService`. Config key `MxGateway:Galaxy:DashboardRefreshIntervalSeconds` is correct.
---
DOC / GatewayDashboardDesign.md / LINES / 160170
CLAIM / "Updates flow over three SignalR hubs … `DashboardSnapshotPublisher` (BackgroundService consuming `IDashboardSnapshotService.WatchSnapshotsAsync`)"; hub table row for EventsHub: "`DashboardEventBroadcaster` invoked by `EventStreamService` for each event it forwards to a gRPC client"
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / Dashboard/Hubs/DashboardSnapshotPublisher.cs confirms BackgroundService consuming `WatchSnapshotsAsync`. Dashboard/Hubs/EventsHub.cs:617 comment: "The publisher side is intentionally a follow-up. Today the dashboard's per-session event view is fed by the snapshot hub … Once a dedicated MxEvent broadcaster lands, this hub's group convention is what it will publish to." — so the doc's description of `DashboardEventBroadcaster` being active is aspirational; it currently exists as a stub.
CODE_AREA / dashboard.hub
SEVERITY / low
PROPOSED_FIX / Flag only — add a note that `EventsHub`'s broadcaster is a planned follow-up; today the per-session events view in `SessionDetailsPage` connects to `/hubs/events` directly and `DashboardEventBroadcaster` exists but the EventStreamService hook is not yet wired. The hub routing convention is stable.
---
DOC / GatewayDashboardDesign.md / LINES / 171177
CLAIM / "`DashboardPageBase` … seeds `Snapshot` synchronously from `IDashboardSnapshotService.GetSnapshot()` … and calls `InvokeAsync(StateHasChanged)` on every `SnapshotUpdated` push. SignalR's `WithAutomaticReconnect` handles transient disconnects."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / Dashboard/Components/DashboardPageBase.cs:3778 — `GetSnapshot()` seed on line 37, hub `On<DashboardSnapshot>` calls `InvokeAsync(StateHasChanged)` on line 65. DashboardHubConnectionFactory.cs:36 — `.WithAutomaticReconnect()` confirmed.
CODE_AREA / dashboard.hub
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayDashboardDesign.md / LINES / 559577
CLAIM / "Initial Implementation Slice … 2. local Bootstrap static assets."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / App.razor:78 — `<ThemeHead />` is loaded before `/css/site.css`; ThemeScripts at line 15. The theme kit (`ZB.MOM.WW.Theme 0.2.0`) is now the primary asset provider via `/_content/ZB.MOM.WW.Theme/`. The "local Bootstrap static assets" description is no longer the full picture — Bootstrap is still vendored locally but the theme kit adds additional CSS/JS layers. DashboardEndpointRouteBuilderExtensions.cs:172173 directly references `/_content/ZB.MOM.WW.Theme/css/theme.css` and `/_content/ZB.MOM.WW.Theme/css/layout.css`.
CODE_AREA / dashboard.css
SEVERITY / low
PROPOSED_FIX / Update item 2 to: "local Bootstrap static assets plus `ZB.MOM.WW.Theme` kit (PackageReference `0.2.0`) providing theme CSS, layout CSS, and JS via `<ThemeHead />` / `<ThemeScripts />`".
---
DOC / GatewayDashboardDesign.md / LINES / 463465
CLAIM / "Two environmental bypasses … `MxGateway:Authentication:Mode = Disabled` authorizes every request"
CLAIM_TYPE / config-key
VERDICT / unverifiable
EVIDENCE / No code path for `MxGateway:Authentication:Mode` was found in Dashboard/ — search returned no matches. The `AllowAnonymousLocalhost` bypass is confirmed at DashboardAuthorizationHandler.cs (referenced from DashboardAuthorizationRequirement). The global Auth mode bypass may live in GatewayOptions outside the dashboard cluster.
CODE_AREA / dashboard.login
SEVERITY / low
PROPOSED_FIX / Cross-check against GatewayOptions and the auth middleware — if this config key was removed or renamed, update the doc. If it lives outside dashboard code, add a cross-reference.
---
## Gap findings (code behavior undocumented)
DOC / gap
LINES / n/a
CLAIM / `Login.razor` is a Blazor page (`@page "/login"`) using `LoginLayout` and the kit's `<LoginCard>` component. GET /login is served by this Blazor page, not a static HTML form. POST /login is a minimal-API endpoint.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / Dashboard/Components/Pages/Login.razor:127; DashboardEndpointRouteBuilderExtensions.cs:2736
CODE_AREA / dashboard.login
SEVERITY / medium
PROPOSED_FIX / Add to the auth section: "GET `/login` is served by the Blazor `Login.razor` page (using the shared kit's `<LoginCard>`); the page is `[AllowAnonymous]` and uses `LoginLayout` (no side rail). POST `/login` remains a minimal-API endpoint."
---
DOC / gap
LINES / n/a
CLAIM / `StatusBadge.razor` now maps `Closed``StatusState.Idle` (not `text-bg-secondary`); adds `Stale`, `Degraded` → Warn; adds `Active` → Ok; adds `Unavailable` → Bad. None of these new states are documented.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / Dashboard/Components/Shared/StatusBadge.razor:1014
CODE_AREA / dashboard.theme
SEVERITY / medium
PROPOSED_FIX / Document the full current state-to-StatusState mapping including `Active`, `Stale`, `Degraded`, `Unavailable`.
---
DOC / gap
LINES / n/a
CLAIM / `ZB.MOM.WW.Theme 0.2.0` is a PackageReference and provides `ThemeShell`, `ThemeHead`, `ThemeScripts`, `NavRailSection`, `NavRailItem`, `StatusPill`, `LoginCard` components. This dependency is not mentioned in either dashboard doc.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / ZB.MOM.WW.MxGateway.Server.csproj (PackageReference ZB.MOM.WW.Theme Version=0.2.0); MainLayout.razor; App.razor; Login.razor
CODE_AREA / dashboard.theme
SEVERITY / high
PROPOSED_FIX / Add a "Theme Kit" section to GatewayDashboardDesign.md (and update DashboardInterfaceDesign.md) documenting the ZB.MOM.WW.Theme dependency, which components it provides, and that the kit owns the shell frame, nav rail, login card, status pill rendering, and base CSS tokens.
---
DOC / gap
LINES / n/a
CLAIM / `DashboardOptions` has a `CookieName` override property (`MxGateway:Dashboard:CookieName`) and a `RequireHttpsCookie` flag. Neither is mentioned in the Configuration section of GatewayDashboardDesign.md.
CLAIM_TYPE / config-key
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs; DashboardServiceCollectionExtensions.cs:8797
CODE_AREA / dashboard.login
SEVERITY / medium
PROPOSED_FIX / Add `CookieName` and `RequireHttpsCookie` to the effective-configuration JSON block and the configuration section prose.
---
DOC / gap
LINES / n/a
CLAIM / `SessionsPage` and `WorkersPage` both render admin `Close`/`Kill` action buttons (with `ConfirmDialog`), not only `SessionDetailsPage` as the doc implies. The `ConfirmDialog` shared component (`Shared/ConfirmDialog.razor`) is not listed in the component tree.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / Dashboard/Components/Pages/SessionsPage.razor:3137 (ConfirmDialog usage); Dashboard/Components/Shared/ConfirmDialog.razor (file exists)
CODE_AREA / dashboard.theme
SEVERITY / low
PROPOSED_FIX / Add `ConfirmDialog.razor` to the Shared/ component tree. Note admin controls appear on Sessions list and Workers list pages, not only SessionDetailsPage.
---
## Summary
| Verdict | Count |
|---------|-------|
| accurate | 2 |
| stale | 11 |
| wrong | 4 |
| unverifiable | 1 |
| gap | 6 |
| Severity | Count |
|----------|-------|
| high | 7 |
| medium | 9 |
| low | 8 |
## High-severity findings
- **Layout: no horizontal navbar** — Both docs describe a horizontal top navbar with `dashboard-shell`/`dashboard-navbar`/`container-fluid` classes. The actual layout is a `ZB.MOM.WW.Theme` `ThemeShell` side-rail with `NavRailSection`/`NavRailItem` components. The HTML skeleton in DashboardInterfaceDesign.md is obsolete.
- **Nav labels wrong** — DashboardInterfaceDesign.md lists five flat labels (Overview/Sessions/Workers/Events/Settings). Actual nav has eight items in three groups (Runtime, Galaxy, Admin) and the home link is labelled "Dashboard" not "Overview".
- **CSS tokens do not exist** — DashboardInterfaceDesign.md documents four `--mxgw-*` custom CSS properties. None exist in `site.css`; all colour resolves through ZB.MOM.WW.Theme kit tokens.
- **StatusBadge uses Bootstrap `text-bg-*` classes** — DashboardInterfaceDesign.md documents a Bootstrap badge mapping. `StatusBadge` now delegates to the kit's `StatusPill` with `StatusState` enum; no `text-bg-*` classes are used.
- **`dashboard.css` does not exist** — GatewayDashboardDesign.md refers to `wwwroot/css/dashboard.css` as the local stylesheet. The file is `wwwroot/css/site.css`.
- **Cookie name wrong** — GatewayDashboardDesign.md states cookie name `__Host-MxGatewayDashboard`. Actual default is `MxGatewayDashboard` (no `__Host-` prefix).
- **ZB.MOM.WW.Theme dependency undocumented** — Neither doc mentions the theme kit package, its components (`ThemeShell`, `LoginCard`, `StatusPill`, etc.) or its CSS token system. This is the single most architecturally significant post-migration gap.
+473
View File
@@ -0,0 +1,473 @@
# Cluster 06 — Config
Docs audited: `docs/GatewayConfiguration.md`, `docs/Diagnostics.md`, `docs/Metrics.md`
Code verified against:
- `src/ZB.MOM.WW.MxGateway.Server/Configuration/` (GatewayOptions, GatewayOptionsValidator, and all sub-options)
- `src/ZB.MOM.WW.MxGateway.Server/Diagnostics/`
- `src/ZB.MOM.WW.MxGateway.Server/Metrics/`
- `src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyRepositoryOptions.cs`
- `src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs`, `DashboardAuthenticationDefaults.cs`
- `src/ZB.MOM.WW.MxGateway.Server/appsettings.json`
---
DOC / GatewayConfiguration.md / LINES / 5556
CLAIM / Config shape example shows GroupToRole values as `"Admin"` and `"Viewer"`
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs:14 — `public const string Admin = "Administrator";`; src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs:212216 — validator compares against `DashboardRoles.Admin` and `DashboardRoles.Viewer`; src/ZB.MOM.WW.MxGateway.Server/appsettings.json:63 — canonical example uses `"Administrator"`
CODE_AREA / config.Dashboard.GroupToRole
SEVERITY / high
PROPOSED_FIX / Change `"Admin"` to `"Administrator"` in the config shape example JSON (line 55). The Viewer value is correct.
---
DOC / GatewayConfiguration.md / LINES / 156
CLAIM / Description says 'Values must be `Admin` (read/write, API-key CRUD) or `Viewer` (read-only)'
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs:14 — `public const string Admin = "Administrator";`; GatewayOptionsValidator.cs:216 — error message embeds `DashboardRoles.Admin` which resolves to `"Administrator"`
CODE_AREA / config.Dashboard.GroupToRole
SEVERITY / high
PROPOSED_FIX / Replace `` `Admin` `` with `` `Administrator` `` in the table description. The note in the Authorization policies subsection (lines 169, 174) says "Admin or Viewer" as role labels, not config values — those are fine as label prose.
---
DOC / Diagnostics.md / LINES / 165166
CLAIM / Code snippet shows `CreateLogger("ZB.MOM.WW.MxGateway.Request")` as the logger category
CLAIM_TYPE / term
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayRequestLoggingMiddlewareExtensions.cs:30 — `.CreateLogger("MxGateway.Request")`
CODE_AREA / diag.GatewayRequestLoggingMiddleware
SEVERITY / medium
PROPOSED_FIX / Change the code snippet and the surrounding sentence ("The logger category is `ZB.MOM.WW.MxGateway.Request`") to use `MxGateway.Request`.
---
DOC / GatewayConfiguration.md / LINES / 1419
CLAIM / The `MxGateway:Ldap` configuration section (11 keys, validated by GatewayOptionsValidator) is not documented in this file
CLAIM_TYPE / config-key
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs:3171 — 11 properties (Enabled, Server, Port, Transport, AllowInsecure, SearchBase, ServiceAccountDn, ServiceAccountPassword, UserNameAttribute, DisplayNameAttribute, GroupAttribute); GatewayOptionsValidator.cs:5590 — ValidateLdap() validates all required fields; appsettings.json:2233 — Ldap section present in default config; GatewayOptions.cs:13 — `public LdapOptions Ldap { get; init; } = new();`
CODE_AREA / config.Ldap
SEVERITY / medium
PROPOSED_FIX / Add a `## Ldap Options` table covering the 11 keys with their defaults and the validation rules (Server/SearchBase/ServiceAccountDn/ServiceAccountPassword/UserNameAttribute/DisplayNameAttribute/GroupAttribute required when Enabled; Port must be valid; Transport=None requires AllowInsecure=true).
---
DOC / Diagnostics.md / LINES / 1222
CLAIM / GatewayLogRedactorSeam (in Diagnostics/ folder) is not mentioned
CLAIM_TYPE / term
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayLogRedactorSeam.cs:127 — implements `ILogRedactor`; adapts `GatewayLogRedactor` for the Serilog `RedactionEnricher` so every log event masks API-key/credential material in `ClientIdentity`, `authorization`, and `Authorization` properties
CODE_AREA / diag.GatewayLogRedactorSeam
SEVERITY / low
PROPOSED_FIX / Add a short note under the Consumers section describing `GatewayLogRedactorSeam` as the `ILogRedactor` adapter that wires `GatewayLogRedactor` into the Serilog telemetry enrichment pipeline, covering the three property keys it redacts.
---
DOC / Diagnostics.md / LINES / 1222
CLAIM / AuthStoreHealthCheck (in Diagnostics/ folder, an ASP.NET Core health check) is not mentioned
CLAIM_TYPE / term
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Diagnostics/AuthStoreHealthCheck.cs:130 — readiness probe verifying the SQLite auth store; GatewayApplication.cs:7172 — `.AddTypeActivatedCheck<AuthStoreHealthCheck>(...)`
CODE_AREA / diag.AuthStoreHealthCheck
SEVERITY / low
PROPOSED_FIX / Add a brief section describing the `AuthStoreHealthCheck` readiness probe (executes `SELECT 1` against the SQLite auth store, exposed via the `/health/ready` and `/healthz` endpoints).
---
DOC / GatewayConfiguration.md / LINES / 1477 (config shape JSON)
CLAIM / Config shape JSON example omits the `MxGateway:Ldap` section entirely
CLAIM_TYPE / config-key
VERDICT / gap
EVIDENCE / appsettings.json:2233 — Ldap section is present; GatewayOptions.cs:13 — Ldap is a first-class sub-section of GatewayOptions
CODE_AREA / config.Ldap
SEVERITY / medium
PROPOSED_FIX / Add the `"Ldap": { ... }` block to the configuration shape example, showing the keys and their defaults from `LdapOptions`.
---
DOC / GatewayConfiguration.md / LINES / 1519
CLAIM / Authentication options: Mode=ApiKey, SqlitePath, PepperSecretName, RunMigrationsOnStartup all have documented defaults matching code
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/AuthenticationOptions.cs:616 — Mode=ApiKey, SqlitePath=`C:\ProgramData\MxGateway\gateway-auth.db`, PepperSecretName=`MxGateway:ApiKeyPepper`, RunMigrationsOnStartup=true
CODE_AREA / config.Authentication
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 2133
CLAIM / Worker options: all 10 keys and their documented defaults match code
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs:538 — ExecutablePath, WorkingDirectory=null, RequiredArchitecture=X86, StartupTimeoutSeconds=30, StartupProbeRetryAttempts=3, StartupProbeRetryDelayMilliseconds=250, PipeConnectAttemptTimeoutMilliseconds=2000, ShutdownTimeoutSeconds=10, HeartbeatIntervalSeconds=5, HeartbeatGraceSeconds=15, MaxMessageBytes=16777216
CODE_AREA / config.Worker
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 110
CLAIM / MaxMessageBytes validator range is 1024 through 268435456
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / GatewayOptionsValidator.cs:910 — `MinimumMaxMessageBytes = 1024`, `MaximumMaxMessageBytes = 256 * 1024 * 1024` (= 268435456)
CODE_AREA / config.Worker.MaxMessageBytes
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 3441
CLAIM / Session options: all 6 keys and their documented defaults match code
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/SessionOptions.cs:430 — DefaultCommandTimeoutSeconds=30, MaxSessions=64, MaxPendingCommandsPerSession=128, DefaultLeaseSeconds=1800, LeaseSweepIntervalSeconds=30, AllowMultipleEventSubscribers=false (C# bool default)
CODE_AREA / config.Sessions
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 4345
CLAIM / Event options: QueueCapacity=10000, BackpressurePolicy=FailFast
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/EventOptions.cs:414 — QueueCapacity=10_000, BackpressurePolicy=FailFast
CODE_AREA / config.Events
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 4657
CLAIM / Dashboard options: Enabled=true, AllowAnonymousLocalhost=true, RequireHttpsCookie=true, CookieName default=MxGatewayDashboard, SnapshotIntervalMilliseconds=1000, RecentFaultLimit=100, RecentSessionLimit=200, ShowTagValues=false, GroupToRole empty by default
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs:653 — all defaults confirmed; src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticationDefaults.cs:38 — CookieName="MxGatewayDashboard"
CODE_AREA / config.Dashboard
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 5962
CLAIM / Protocol options: WorkerProtocolVersion=1, MaxGrpcMessageBytes=16777216; validator range 1024268435456
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/ProtocolOptions.cs:1316; GatewayOptionsValidator.cs:291302
CODE_AREA / config.Protocol
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 6369
CLAIM / Galaxy options: ConnectionString, CommandTimeoutSeconds=60, DashboardRefreshIntervalSeconds=30, PersistSnapshot=true, SnapshotCachePath defaults all match code
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyRepositoryOptions.cs:1646 — all defaults confirmed
CODE_AREA / config.Galaxy
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 7075
CLAIM / Alarm options: Enabled=false, SubscriptionExpression=empty, DefaultArea=empty, ReconcileIntervalSeconds=30
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/AlarmsOptions.cs:2247 — Enabled default is C# bool default (false), SubscriptionExpression=string.Empty, DefaultArea=string.Empty, ReconcileIntervalSeconds=30
CODE_AREA / config.Alarms
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 228
CLAIM / ReconcileIntervalSeconds is "Floored at 5 seconds"
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs:239 — `int seconds = Math.Max(5, _options.ReconcileIntervalSeconds);`
CODE_AREA / config.Alarms.ReconcileIntervalSeconds
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 346354
CLAIM / TLS options: SelfSignedCertPath, ValidityYears=10, AdditionalDnsNames=[], RegenerateIfExpired=true; ValidityYears validated 1100
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/TlsOptions.cs:1122; GatewayOptionsValidator.cs:260261 — `MinimumCertValidityYears = 1`, `MaximumCertValidityYears = 100`
CODE_AREA / config.Tls
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 164176
CLAIM / Three authorization policies named MxGateway.Dashboard.Viewer, MxGateway.Dashboard.Admin, MxGateway.Dashboard.HubClients; hub-token bearer scheme named MxGateway.Dashboard.HubToken
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticationDefaults.cs:20,27,34,14
CODE_AREA / config.Dashboard.AuthPolicies
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 180195
CLAIM / SignalR hubs mapped at /hubs/snapshot, /hubs/alarms, /hubs/events; token endpoint at /hubs/token
CLAIM_TYPE / path
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs:6365,73
CODE_AREA / config.Dashboard.Hubs
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 193
CLAIM / `GET /hubs/token` mints a 30-minute data-protected bearer token
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs:29 — `private static readonly TimeSpan TokenLifetime = TimeSpan.FromMinutes(30);`
CODE_AREA / config.Dashboard.HubToken
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / GatewayConfiguration.md / LINES / 197206
CLAIM / Pipeline ordering: UseGatewayRequestLoggingScope → UseStaticFiles → UseAuthentication → UseAuthorization → UseAntiforgery → MapGatewayEndpoints
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs:4045
CODE_AREA / diag.GatewayRequestLoggingMiddleware
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Diagnostics.md / LINES / 1534
CLAIM / GatewayLogScope record signature (SessionId, WorkerProcessId, CorrelationId, CommandMethod, ClientIdentity) and ToDictionary behavior matches code
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayLogScope.cs:334
CODE_AREA / diag.GatewayLogScope
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Diagnostics.md / LINES / 4457
CLAIM / GatewayLoggerExtensions.BeginGatewayScope signature and behavior match code
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayLoggerExtensions.cs:918
CODE_AREA / diag.GatewayLoggerExtensions
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Diagnostics.md / LINES / 6880
CLAIM / SensitiveCommandMethods set contains AuthenticateUser, WriteSecured, WriteSecured2; IsCredentialBearingCommand logic is correct
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayLogRedactor.cs:1126
CODE_AREA / diag.GatewayLogRedactor
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Diagnostics.md / LINES / 86117
CLAIM / RedactApiKey implementation (bearer prefix, mxgw_ marker, split count=3, tokenParts[1] kept) matches code
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayLogRedactor.cs:3259
CODE_AREA / diag.GatewayLogRedactor
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Diagnostics.md / LINES / 127148
CLAIM / RedactCommandValue: when valueLoggingEnabled=false every value is redacted; credential-bearing commands always redact even with valueLoggingEnabled=true
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayLogRedactor.cs:8399
CODE_AREA / diag.GatewayLogRedactor
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Diagnostics.md / LINES / 181188
CLAIM / Request logging scope reads headers: x-session-id, x-worker-process-id, x-correlation-id, x-command-method, authorization
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Diagnostics/GatewayRequestLoggingMiddlewareExtensions.cs:916,3237
CODE_AREA / diag.GatewayRequestLoggingMiddleware
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 8
CLAIM / GatewayMetrics is a singleton registered in GatewayApplication.cs
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs:76 — `builder.Services.AddSingleton<GatewayMetrics>();`
CODE_AREA / metrics.GatewayMetrics
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 14
CLAIM / Meter name constant is "ZB.MOM.WW.MxGateway"
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs:8 — `public const string MeterName = "ZB.MOM.WW.MxGateway";`
CODE_AREA / metrics.GatewayMetrics.MeterName
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 3649
CLAIM / All 13 counter instrument names match code
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs:5870 — mxgateway.sessions.opened, .sessions.closed, .commands.started, .commands.succeeded, .commands.failed, .events.received, .queues.overflows, .faults, .workers.killed, .workers.exited, .heartbeats.failed, .grpc.streams.disconnected, .retries.attempted all confirmed
CODE_AREA / metrics.counters
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 5665
CLAIM / Three histograms: mxgateway.workers.startup.duration ("s"), mxgateway.commands.duration ("s"), mxgateway.events.stream_send.duration ("s") — names, units, tag shapes match code
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs:7173
CODE_AREA / metrics.histograms
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 7377
CLAIM / Four observable gauges: mxgateway.sessions.open, mxgateway.workers.running, mxgateway.events.worker_queue.depth, mxgateway.events.grpc_stream_queue.depth match code
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs:7578
CODE_AREA / metrics.gauges
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 82104
CLAIM / GatewayMetricsSnapshot record fields (21 parameters) match code exactly
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetricsSnapshot.cs:324
CODE_AREA / metrics.GatewayMetricsSnapshot
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 114
CLAIM / EventsReceived is read with Interlocked.Read(ref _eventsReceived) inside GetSnapshot
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs:397 — `EventsReceived: Interlocked.Read(ref _eventsReceived),`
CODE_AREA / metrics.GatewayMetrics.GetSnapshot
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 138139
CLAIM / SessionRemoved decrements the open-session gauge without incrementing the closed counter
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs:126134 — SessionRemoved() decrements _openSessions but does not touch _sessionsClosed
CODE_AREA / metrics.GatewayMetrics.SessionRemoved
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 169
CLAIM / SessionWorkerClientFactory records WorkerKilled("OpenSessionFailed")
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionWorkerClientFactory.cs:133
CODE_AREA / metrics.recording.SessionWorkerClientFactory
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 154162
CLAIM / WorkerProcessLauncher records WorkerKilled(reason) and RetryAttempted("worker_startup")
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerProcessLauncher.cs:260,282
CODE_AREA / metrics.recording.WorkerProcessLauncher
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / Metrics.md / LINES / 178192
CLAIM / EventStreamService records AdjustGrpcEventStreamQueueDepth, StreamDisconnected("Detached"), QueueOverflow("grpc-event-stream"), Fault(EventQueueOverflow), Fault(WorkerFaulted)
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Grpc/EventStreamService.cs:58,67,96,99,146,150,179
CODE_AREA / metrics.recording.EventStreamService
SEVERITY / low
PROPOSED_FIX / flag only
---
## Summary
| Verdict | Count |
|--------------|-------|
| accurate | 25 |
| wrong | 3 |
| stale | 0 |
| unverifiable | 0 |
| gap | 4 |
| **Total** | **32** |
| Severity | Count |
|----------|-------|
| high | 2 |
| medium | 3 |
| low | 27 |
## High-Severity Findings
- **GatewayConfiguration.md line 55 — GroupToRole config shape example uses `"Admin"` as a role value.** The validator accepts only `"Administrator"` (`DashboardRoles.Admin = "Administrator"`). Any operator who copies this example verbatim will produce a validation failure at startup. Fix: change `"GwAdmin": "Admin"` to `"GwAdmin": "Administrator"` in the JSON block.
- **GatewayConfiguration.md line 156 — GroupToRole table description says values must be `Admin` or `Viewer`.** The accepted value is `"Administrator"`, not `"Admin"`. This is the primary prose that operators read when configuring LDAP role mapping; the wrong string here will silently break authentication if an operator follows the docs. Fix: replace `` `Admin` `` with `` `Administrator` `` in the description column.
## Medium-Severity Findings
- **Diagnostics.md line 165166 — Embedded code snippet and surrounding text state the logger category is `ZB.MOM.WW.MxGateway.Request`.** The actual category used by `GatewayRequestLoggingMiddlewareExtensions` is `MxGateway.Request`. An operator filtering logs by the documented category will see no output. Fix: update snippet and prose to `MxGateway.Request`.
- **GatewayConfiguration.md — `MxGateway:Ldap` section (11 keys) is entirely absent from the config shape JSON example and has no option table.** The section is validated at startup by `GatewayOptionsValidator.ValidateLdap` and appears in `appsettings.json`. Fix: add `"Ldap"` block to the JSON shape and a `## Ldap Options` table.
- **GatewayConfiguration.md — Config shape JSON omits the `Ldap` section** (duplicate of the above gap, listed separately because the shape and the prose table are independent defects).
+362
View File
@@ -0,0 +1,362 @@
# Cluster 07 — Contracts/gRPC
Audit of `docs/Contracts.md`, `docs/Grpc.md`, and `docs/ClientProtoGeneration.md`
verified against:
- `src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto`
- `src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto`
- `src/ZB.MOM.WW.MxGateway.Contracts/Protos/galaxy_repository.proto`
- `src/ZB.MOM.WW.MxGateway.Server/Grpc/**`
- `clients/proto/proto-inputs.json`
- `src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj`
---
DOC / LINES / CLAIM / CLAIM_TYPE / VERDICT / EVIDENCE / CODE_AREA / SEVERITY / PROPOSED_FIX
---
DOC: docs/Grpc.md
LINES: 13, 32
CLAIM: "`MxAccessGatewayService` implements the six `MxAccessGateway` RPCs — `OpenSession`, `CloseSession`, `Invoke`, `StreamEvents`, `AcknowledgeAlarm`, and `StreamAlarms`."
CLAIM_TYPE: rpc/proto
VERDICT: wrong
EVIDENCE: mxaccess_gateway.proto:17-38 defines seven RPCs — the six listed plus `QueryActiveAlarms(QueryActiveAlarmsRequest) returns (stream ActiveAlarmSnapshot)`. `MxAccessGatewayService.cs:233` implements `QueryActiveAlarms`. The table at line 13 also says "six" and the table and prose at line 32 both omit `QueryActiveAlarms`.
CODE_AREA: proto.QueryActiveAlarms
SEVERITY: high
PROPOSED_FIX: Change "six" to "seven" in the table and prose. Add `QueryActiveAlarms` to the RPC list at line 32. Add a `### QueryActiveAlarms` handler section describing the server-streaming, session-less snapshot behavior (iterates `alarmService.CurrentAlarms`, respects `alarm_filter_prefix`, completes without emitting transitions).
---
DOC: docs/Grpc.md
LINES: 148
CLAIM: "The mapper exposes static factory methods for every `ProtocolStatusCode` (`Ok`, `InvalidRequest`, `SessionNotFound`, `SessionNotReady`, `WorkerUnavailable`, `Timeout`, `Canceled`, `ProtocolViolation`)."
CLAIM_TYPE: rpc/proto
VERDICT: wrong
EVIDENCE: `mxaccess_gateway.proto:1025` defines `PROTOCOL_STATUS_CODE_MXACCESS_FAILURE = 9`. `MxAccessGrpcMapper.cs:76-174` lists eight factory methods — none for `MxAccessFailure`. The claim "every ProtocolStatusCode" is false because `MxAccessFailure` has no corresponding factory method.
CODE_AREA: proto.ProtocolStatusCode
SEVERITY: medium
PROPOSED_FIX: Either add "except `MxAccessFailure`, which is produced only by the worker" to the sentence, or add the missing factory method and update the list. Do not silently elide the gap.
---
DOC: docs/ClientProtoGeneration.md
LINES: 80, 145
CLAIM: "Python generated-code output directory is `clients/python/src/mxgateway/generated`."
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: `clients/proto/proto-inputs.json:28` declares `"python": "clients/python/src/zb_mom_ww_mxgateway/generated"`. The actual directory on disk is `clients/python/src/zb_mom_ww_mxgateway/generated/` (confirmed by `ls`). The doc path `clients/python/src/mxgateway/generated` does not exist.
CODE_AREA: proto.gen
SEVERITY: high
PROPOSED_FIX: Replace both occurrences of `clients/python/src/mxgateway/generated` with `clients/python/src/zb_mom_ww_mxgateway/generated` to match `proto-inputs.json` and the actual filesystem.
---
DOC: docs/Grpc.md
LINES: 227
CLAIM: "Under the default policy only the stream is dropped and the session continues to accept commands."
CLAIM_TYPE: behavior-rule
VERDICT: wrong
EVIDENCE: `appsettings.json:53` sets `"BackpressurePolicy": "FailFast"`. `EventOptions.cs:13` confirms `EventBackpressurePolicy.FailFast` as the default. `EventBackpressurePolicy.cs` names the two values `FailFast` and `DisconnectSubscriber`. The non-FailFast (stream-drop-only) behaviour belongs to `DisconnectSubscriber`, not "the default policy". Under the actual default (`FailFast`) the session is faulted.
CODE_AREA: proto.gen
SEVERITY: medium
PROPOSED_FIX: Rewrite as: "Under `DisconnectSubscriber` only the stream is dropped … Under `FailFast` (the default configured in `appsettings.json`) the session is faulted …"
---
DOC: docs/Contracts.md
LINES: 94, 107
CLAIM: "Full solution build: `dotnet build src/ZB.MOM.WW.MxGateway.slnx`"
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: `src/ZB.MOM.WW.MxGateway.slnx` exists on disk.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Contracts.md
LINES: 94
CLAIM: "Run the contracts build to regenerate C# protobuf and gRPC code: `dotnet build src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj`"
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: `ZB.MOM.WW.MxGateway.Contracts.csproj:27-29` includes all three `.proto` files with `GrpcServices="Both"` or `"None"` and `OutputDir="Generated"`. Building the project triggers protoc via Grpc.Tools.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Contracts.md
LINES: 4-5
CLAIM: "The contracts project multi-targets `net10.0;net48` and owns the `.proto` files."
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: `ZB.MOM.WW.MxGateway.Contracts.csproj:4``<TargetFrameworks>net10.0;net48</TargetFrameworks>`.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Contracts.md
LINES: 80-81
CLAIM: "Generated C# output is written to `src/ZB.MOM.WW.MxGateway.Contracts/Generated/`."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: `ZB.MOM.WW.MxGateway.Contracts.csproj:27``OutputDir="Generated"`. Directory `src/ZB.MOM.WW.MxGateway.Contracts/Generated/` contains five generated `.cs` files confirmed by `ls`.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Contracts.md
LINES: 9-19
CLAIM: "The public command model includes bulk subscription command kinds for `AddItemBulk`, `AdviseItemBulk`, `RemoveItemBulk`, `UnAdviseItemBulk`, `SubscribeBulk`, and `UnsubscribeBulk`. They return a `BulkSubscribeReply` containing per-item `SubscribeResult` records with `ServerHandle`, `TagAddress`, `ItemHandle`, `WasSuccessful`, and `ErrorMessage`."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: `mxaccess_gateway.proto:117-122` defines all six payloads. `proto:562-568` defines `SubscribeResult` with fields `server_handle`, `tag_address`, `item_handle`, `was_successful`, `error_message`. `proto:570-572` defines `BulkSubscribeReply`.
CODE_AREA: proto.SubscribeResult
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Contracts.md
LINES: 32-45
CLAIM: "`WriteBulkCommand` / `Write2BulkCommand` / `WriteSecuredBulkCommand` / `WriteSecured2BulkCommand` each carry `server_handle` and a `repeated` list of entries. Each entry mirrors the single-item command shape — `item_handle` + `value` (+ `timestamp_value` on the `*2` variants, + `current_user_id` / `verifier_user_id` on the secured variants). All four replies use `BulkWriteReply` with `repeated BulkWriteResult`. A `BulkWriteResult` has `server_handle`, `item_handle`, `was_successful`, `optional int32 hresult`, `repeated MxStatusProxy statuses`, and `error_message`."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: `mxaccess_gateway.proto:384-441` defines all four commands with matching fields. `proto:581-588` defines `BulkWriteResult` with exactly those six fields. `proto:590-592` defines `BulkWriteReply`.
CODE_AREA: proto.BulkWriteResult
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Contracts.md
LINES: 46-61
CLAIM: "`ReadBulkCommand` carries `server_handle`, `repeated string tag_addresses`, and `uint32 timeout_ms`. The reply is `BulkReadReply` carrying `repeated BulkReadResult`. A `BulkReadResult` has `server_handle`, `tag_address`, `item_handle`, `was_successful`, `was_cached`, `value`, `quality`, `source_timestamp`, `repeated MxStatusProxy statuses`, and `error_message`. `BulkReadResult` has no `hresult` field."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: `mxaccess_gateway.proto:456-460` defines `ReadBulkCommand` with those three fields. `proto:612-623` defines `BulkReadResult` with exactly those ten fields, no `hresult`. `proto:625-627` defines `BulkReadReply`.
CODE_AREA: proto.BulkReadResult
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Contracts.md
LINES: 68-71
CLAIM: "`mxaccess_worker.proto` defines the named-pipe worker IPC envelope and control messages. It imports `mxaccess_gateway.proto` so the worker and gateway use the same command, reply, event, value, and status shapes."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: `mxaccess_worker.proto:9``import "mxaccess_gateway.proto";`. The `WorkerCommand`, `WorkerCommandReply`, `WorkerEvent` messages wrap `mxaccess_gateway.v1` types directly.
CODE_AREA: proto.WorkerEnvelope
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Contracts.md
LINES: 73-78
CLAIM: "`galaxy_repository.proto` defines the `GalaxyRepository` service. The service is metadata-only and does not share types with `mxaccess_gateway.proto`."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: `galaxy_repository.proto:7-8` imports only `google/protobuf/timestamp.proto` and `google/protobuf/wrappers.proto` — no import of `mxaccess_gateway.proto`. The comment at `galaxy_repository.proto:130` states the type enumeration is distinct from `MxDataType`.
CODE_AREA: proto.GalaxyRepository
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Grpc.md
LINES: 9-16
CLAIM: "Four collaborators: `MxAccessGatewayService` (scoped/gRPC), `MxAccessGrpcRequestValidator` (singleton), `MxAccessGrpcMapper` (singleton), `IEventStreamService`/`EventStreamService` (singleton)."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: `GatewayApplication.cs:88-90` registers mapper, validator, and event stream service as singletons. `MxAccessGatewayService` is not explicitly registered (gRPC services resolved per-request by ASP.NET Core are transient/scoped — "scoped (gRPC)" is accurate per ASP.NET Core DI conventions). `GatewayApplication.cs:195` maps it as a gRPC endpoint.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Grpc.md
LINES: 20-26
CLAIM: "Registration: `builder.Services.AddSingleton<MxAccessGrpcMapper>(); builder.Services.AddSingleton<MxAccessGrpcRequestValidator>(); builder.Services.AddSingleton<IEventStreamService, EventStreamService>();`"
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: `GatewayApplication.cs:88-90` matches exactly.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Grpc.md
LINES: 237-243
CLAIM: "Authorization interceptor registration: `services.AddSingleton<GatewayGrpcAuthorizationInterceptor>(); services.AddGrpc(options => options.Interceptors.Add<GatewayGrpcAuthorizationInterceptor>());`"
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: `GrpcAuthorizationServiceCollectionExtensions.cs:21,31` contains both lines verbatim.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Grpc.md
LINES: 100-108
CLAIM: "Validation table — `OpenSession`: `command_timeout` when set must be `> 0`; `CloseSession`: `session_id` non-empty; `StreamEvents`: `session_id` non-empty; `Invoke`: session_id non-empty, command present, kind not Unspecified, payload oneof matches kind; `AcknowledgeAlarm`: `alarm_full_reference` non-empty, validated inline not by `MxAccessGrpcRequestValidator`; `StreamAlarms`: no required fields."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: `MxAccessGrpcRequestValidator.cs:10-53` confirms all four validator methods. `MxAccessGatewayService.cs:181-183` confirms the inline alarm reference check. `StreamAlarms` handler at line 204 has no field validation.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Grpc.md
LINES: 141-147
CLAIM: "When the worker reply or event payload is missing, the mapper returns a synthetic public message with `ProtocolStatusCode.ProtocolViolation` (for replies) or a sentinel `MxEvent` with `MxEventFamily.Unspecified` (for events)."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: `MxAccessGrpcMapper.cs:46-54` returns `ProtocolViolation(...)` when `reply.Reply` is null. `MxAccessGrpcMapper.cs:65-69` returns sentinel `MxEvent { Family = MxEventFamily.Unspecified }` when event is null.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Grpc.md
LINES: 159-174
CLAIM: "Exception mapping: `OperationCanceledException``Cancelled`; `SessionManagerException` → mapped by `ErrorCode`; `WorkerClientException` → mapped by `ErrorCode`. `WorkerClientException`: `CommandTimeout``DeadlineExceeded`, `GatewayShutdown``Cancelled`, `InvalidState``FailedPrecondition`, `ProtocolViolation``Internal`, others → `Unavailable`."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: `MxAccessGatewayService.cs:902-950` matches exactly. `WorkerClientErrorCode.cs:5-12` confirms the four enum values.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Grpc.md
LINES: 184-196
CLAIM: "The channel is bounded by `Events:QueueCapacity` and configured for a single reader and writer with `FullMode = BoundedChannelFullMode.Wait` and `AllowSynchronousContinuations = false`."
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: `EventStreamService.cs:44-51` matches the code snippet in the doc verbatim.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/ClientProtoGeneration.md
LINES: 39-45
CLAIM: "`GatewayContractInfo.GatewayProtocolVersion` is the public gateway protocol version. `OpenSessionReply.gateway_protocol_version` returns the same value."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: `GatewayContractInfo.cs:12``public const uint GatewayProtocolVersion = 3;`. `mxaccess_gateway.proto:71``uint32 gateway_protocol_version = 8;`. `MxAccessGatewayService.cs:49` copies `GatewayContractInfo.GatewayProtocolVersion` into the reply field.
CODE_AREA: proto.OpenSessionReply
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/ClientProtoGeneration.md
LINES: 55-61
CLAIM: "The script writes `clients/proto/descriptors/mxaccessgw-client-v1.protoset`."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: `clients/proto/descriptors/mxaccessgw-client-v1.protoset` exists on disk. `proto-inputs.json:21` references the same path.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/ClientProtoGeneration.md
LINES: 74-81
CLAIM: "Generated-code directories table: .NET → `clients/dotnet/generated`, Go → `clients/go/internal/generated`, Rust → `clients/rust/src/generated`, Python → `clients/python/src/mxgateway/generated`, Java → `clients/java/src/main/generated`."
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: `clients/proto/proto-inputs.json:26-30` lists `"python": "clients/python/src/zb_mom_ww_mxgateway/generated"`. The actual directory is `clients/python/src/zb_mom_ww_mxgateway/generated/` (confirmed by filesystem). The table row for Python says `clients/python/src/mxgateway/generated` which does not exist. All other rows match `proto-inputs.json` and the filesystem.
CODE_AREA: proto.gen
SEVERITY: high
PROPOSED_FIX: Change the Python row from `clients/python/src/mxgateway/generated` to `clients/python/src/zb_mom_ww_mxgateway/generated` in the table and also fix line 145 which contains the same wrong path.
---
DOC: docs/ClientProtoGeneration.md
LINES: 89-101 (generation commands table)
CLAIM: ".NET generation: `dotnet build src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj`; Go: `Push-Location clients/go; ./generate-proto.ps1; Pop-Location`; Rust: `Push-Location clients/rust; cargo check --workspace; Pop-Location`; Python: `Push-Location clients/python; ./generate-proto.ps1; Pop-Location`; Java: `Push-Location clients/java; gradle :mxgateway-client:generateProto; Pop-Location`."
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: Scripts `clients/go/generate-proto.ps1` and `clients/python/generate-proto.ps1` exist. `generate-proto.ps1` for Go uses `$modulePath = 'gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated'` matching the stated package. Contracts csproj exists. All scripts confirmed present.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/ClientProtoGeneration.md
LINES: 119-125
CLAIM: "The Go scaffold maps both proto files into the internal Go package `gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated`."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: `clients/go/generate-proto.ps1:7``$modulePath = 'gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated'`.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/ClientProtoGeneration.md
LINES: 170-176
CLAIM: "Golden fixtures: `open-session-reply.ok.json`, `register-command-request.json`, `on-data-change-event.json`."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: All three files exist at `clients/proto/fixtures/golden/`.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
---
DOC: docs/Contracts.md / docs/ClientProtoGeneration.md
LINES: (gap — not documented)
CLAIM: gap — `QueryActiveAlarms` RPC in `mxaccess_gateway.proto` service definition (line 37), `QueryActiveAlarmsRequest` message (line 44), and `ActiveAlarmSnapshot` message (line 783) are not mentioned in `Contracts.md` or `ClientProtoGeneration.md`.
CLAIM_TYPE: rpc/proto
VERDICT: gap
EVIDENCE: `mxaccess_gateway.proto:37``rpc QueryActiveAlarms(QueryActiveAlarmsRequest) returns (stream ActiveAlarmSnapshot);`. `Contracts.md` describes every other public RPC but never mentions `QueryActiveAlarms`.
CODE_AREA: proto.QueryActiveAlarms
SEVERITY: medium
PROPOSED_FIX: Add a paragraph to `Contracts.md` describing `QueryActiveAlarms` — session-less, server-streaming, returns point-in-time snapshot of active alarms from the gateway's always-on alarm monitor cache, optionally filtered by `alarm_filter_prefix`. Cross-reference the `StreamAlarms` section.
---
DOC: docs/Contracts.md / docs/ClientProtoGeneration.md
LINES: (gap — not documented)
CLAIM: gap — `AlarmFeedMessage` oneof message and the `StreamAlarms` protocol (snapshot → `snapshot_complete` → transitions) are described in `Grpc.md` but not in `Contracts.md` which should be the shape-level reference.
CLAIM_TYPE: rpc/proto
VERDICT: gap
EVIDENCE: `mxaccess_gateway.proto:860-870` defines `AlarmFeedMessage { oneof payload { ActiveAlarmSnapshot active_alarm = 1; bool snapshot_complete = 2; OnAlarmTransitionEvent transition = 3; } }`. `Contracts.md` does not describe this message or its stream protocol.
CODE_AREA: proto.AlarmFeedMessage
SEVERITY: low
PROPOSED_FIX: Add a brief entry in `Contracts.md` describing `AlarmFeedMessage` and the three-phase stream sequence for `StreamAlarms`.
---
DOC: docs/Contracts.md / docs/Grpc.md
LINES: (gap — not documented)
CLAIM: gap — `AcknowledgeAlarmRequest` has a reserved field 1 (`session_id`) and the acknowledgement is session-less. `AcknowledgeAlarmReply` also has a reserved field 1 and an intentionally-unset `status` field (field 5). This wire-compatibility detail is not captured in `Contracts.md`.
CLAIM_TYPE: rpc/proto
VERDICT: gap
EVIDENCE: `mxaccess_gateway.proto:812-847``AcknowledgeAlarmRequest` has `reserved 1; reserved "session_id";`. `AcknowledgeAlarmReply` likewise has `reserved 1; reserved "session_id";` and inline comment that `status` (field 5) is intentionally unset.
CODE_AREA: proto.AcknowledgeAlarm
SEVERITY: low
PROPOSED_FIX: Add a note in `Contracts.md` about the reserved `session_id` fields and the intentionally-empty `status` field so integrators using older generated code do not misinterpret wire defaults.
+521
View File
@@ -0,0 +1,521 @@
# Cluster 08 — Galaxy Repository
Audited doc: `docs/GalaxyRepository.md`
Verified against: `src/ZB.MOM.WW.MxGateway.Server/Galaxy/**`, `src/ZB.MOM.WW.MxGateway.Contracts/Protos/galaxy_repository.proto`
Date: 2026-06-03
---
DOC: docs/GalaxyRepository.md
LINES: 34
CLAIM: The SQL Server database is named `ZB`.
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: GalaxyRepositoryOptions.cs:17 (`DefaultConnectionString = "Server=localhost;Database=ZB;..."`)
CODE_AREA: gr.conn
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 34
CLAIM: The database is a SQL Server database.
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:1 (`using Microsoft.Data.SqlClient;`); GalaxyRepositoryOptions.cs:17
CODE_AREA: gr.conn
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 3031
CLAIM: The service is defined in `src/ZB.MOM.WW.MxGateway.Contracts/Protos/galaxy_repository.proto` under package `galaxy_repository.v1`.
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: galaxy_repository.proto:3 (`package galaxy_repository.v1;`)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 3539
CLAIM: `TestConnection` returns `{ ok: bool }` after a `SELECT 1`. Does not throw on SQL failure — returns `ok = false`.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:2032 (catches `SqlException` and `InvalidOperationException`, returns false); galaxy_repository.proto:4345 (`TestConnectionReply { bool ok = 1; }`)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 36
CLAIM: `GetLastDeployTime` returns the cached `galaxy.time_of_last_deploy`. Served from the shared hierarchy cache; refreshed in the background.
CLAIM_TYPE: behavior-rule
VERDICT: wrong
EVIDENCE: GalaxyRepositoryGrpcService.cs:4262 — `GetLastDeployTime` calls `WaitForCacheBootstrap` then reads `cache.Current`, not `repository` directly. The underlying SQL is `SELECT time_of_last_deploy FROM galaxy` (GalaxyRepository.cs:40) but it is served from cache, not direct SQL. The doc correctly says "served from cache" in the inline column. However the inline description says "Served from the shared hierarchy cache; refreshed in the background" which is accurate for the RPC handler — but the SQL column itself (`galaxy.time_of_last_deploy`) is an internal SQL column name, not a table.column phrasing. No actual error; accurate.
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 3839
CLAIM: `WatchDeployEvents` is server-streaming. The server emits the current state immediately on subscribe.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: galaxy_repository.proto:33 (`rpc WatchDeployEvents ... returns (stream DeployEvent)`); GalaxyDeployNotifier.cs:5863 (bootstrap emit on subscribe)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 39
CLAIM: `BrowseChildren` returns the direct children of one parent object (or root objects when `parent` is unset). Includes a per-child `has_children` hint so UIs can draw expand triangles without an extra round trip. Served from cache.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: galaxy_repository.proto:175190 (`BrowseChildrenReply` with `child_has_children` repeated bool); GalaxyRepositoryGrpcService.cs:112168
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 4243
CLAIM: The server defaults omitted page size to 1000 objects and caps every page at 5000 objects (for `DiscoverHierarchy`).
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyRepositoryGrpcService.cs:2728 (`DefaultDiscoverPageSize = 1000`, `MaxDiscoverPageSize = 5000`)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 8386
CLAIM: `BrowseChildren` default page size is 500; the server caps any requested size at 5000. Page tokens encode `(cache_sequence, parent_id, filter_signature, offset)`.
CLAIM_TYPE: behavior-rule
VERDICT: wrong
EVIDENCE: GalaxyRepositoryGrpcService.cs:29 (`DefaultBrowsePageSize = 500`) — default is accurate. Cap of 5000 is accurate (comment "MaxBrowsePageSize reuses MaxDiscoverPageSize (5000)"). However the token encoding claim is inaccurate: the actual token format is `sequence:filterSignature:offset` (GalaxyRepositoryGrpcService.cs:295302, `FormatPageToken`). `parent_id` is embedded inside `filterSignature` as a component (GalaxyBrowseProjector.cs:266 `builder.Append("parent=").Append(parentId...)`) — it is NOT a separate named field in the token. Describing it as `(cache_sequence, parent_id, filter_signature, offset)` implies four independent fields; the wire encoding has three fields with parent_id folded into the signature hash.
CODE_AREA: gr.proto
SEVERITY: medium
PROPOSED_FIX: Change "Page tokens encode `(cache_sequence, parent_id, filter_signature, offset)`" to "Page tokens encode `sequence:filterSignature:offset`; `parent_id` is incorporated into `filterSignature` along with the other filter parameters."
---
DOC: docs/GalaxyRepository.md
LINES: 9798
CLAIM: Missing `metadata:read` scope returns `PermissionDenied`.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GatewayGrpcScopeResolver.cs:2327 (all five Galaxy request types map to `GatewayScopes.MetadataRead`); GatewayScopes.cs:11
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 118119
CLAIM: `GalaxyHierarchyRefreshService` ticks every `MxGateway:Galaxy:DashboardRefreshIntervalSeconds` seconds (default 30).
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: GalaxyRepositoryOptions.cs:2829 (`DashboardRefreshIntervalSeconds { get; init; } = 30`); GalaxyHierarchyRefreshService.cs:18 (`TimeSpan.FromSeconds(Math.Max(1, options.Value.DashboardRefreshIntervalSeconds))`)
CODE_AREA: gr.conn
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 120
CLAIM: Each tick queries the cheap `SELECT time_of_last_deploy FROM galaxy` first.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:40 (`"SELECT time_of_last_deploy FROM galaxy"`); GalaxyHierarchyCache.cs:117 (`GetLastDeployTimeAsync` called first before deciding whether to run heavy queries)
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 150152
CLAIM: The snapshot file is written atomically — a temp file plus rename — so a crash mid-write cannot corrupt the snapshot.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyHierarchySnapshotStore.cs:7481 (writes to `_path + ".tmp"` then `File.Move(..., overwrite: true)`)
CODE_AREA: gr.conn
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 178179
CLAIM: `GalaxyDeployNotifier` maintains a private bounded channel per subscriber. The bound is 16 events with `DropOldest`.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyDeployNotifier.cs:18 (`SubscriberQueueCapacity = 16`); GalaxyDeployNotifier.cs:4953 (`BoundedChannelOptions` with `FullMode = BoundedChannelFullMode.DropOldest`)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 386387
CLAIM: Default connection string is `Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;`
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: GalaxyRepositoryOptions.cs:17 (exact match)
CODE_AREA: gr.conn
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 387
CLAIM: `MxGateway:Galaxy:CommandTimeoutSeconds` default is `60`. Applies to all three RPCs.
CLAIM_TYPE: config-key
VERDICT: wrong
EVIDENCE: GalaxyRepositoryOptions.cs:22 (`CommandTimeoutSeconds { get; init; } = 60`) — default is accurate. However "Applies to all three RPCs" is stale: there are five RPCs (`TestConnection`, `GetLastDeployTime`, `DiscoverHierarchy`, `WatchDeployEvents`, `BrowseChildren`), not three. `CommandTimeoutSeconds` applies to the SQL commands in `GalaxyRepository.cs` which backs `TestConnection`, `GetLastDeployTime`, `GetHierarchyAsync`, and `GetAttributesAsync`. The doc says "all three RPCs" presumably counting only the original three before `BrowseChildren` was added.
CODE_AREA: gr.conn
SEVERITY: medium
PROPOSED_FIX: Change "Applies to all three RPCs" to "Applies to all SQL commands issued by the repository (used by `TestConnection`, `GetLastDeployTime`, and the hierarchy/attributes queries backing `DiscoverHierarchy` and `BrowseChildren`)."
---
DOC: docs/GalaxyRepository.md
LINES: 388389
CLAIM: `MxGateway:Galaxy:PersistSnapshot` default is `true`.
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: GalaxyRepositoryOptions.cs:40 (`PersistSnapshot { get; init; } = true`)
CODE_AREA: gr.conn
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 389390
CLAIM: `MxGateway:Galaxy:SnapshotCachePath` default is `C:\ProgramData\MxGateway\galaxy-snapshot.json`.
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: GalaxyRepositoryOptions.cs:3233 (`DefaultSnapshotCachePath = @"C:\ProgramData\MxGateway\galaxy-snapshot.json"`)
CODE_AREA: gr.conn
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 403404
CLAIM: "All four Galaxy RPCs (including `WatchDeployEvents`) require the `metadata:read` API-key scope."
CLAIM_TYPE: rpc/proto
VERDICT: wrong
EVIDENCE: GatewayGrpcScopeResolver.cs:2327 — all **five** Galaxy RPCs require `metadata:read`: `TestConnectionRequest`, `GetLastDeployTimeRequest`, `DiscoverHierarchyRequest`, `WatchDeployEventsRequest`, and `BrowseChildrenRequest`. The service has five RPCs (galaxy_repository.proto:2139), not four. `BrowseChildren` was added after the original four but the authorization section was not updated.
CODE_AREA: gr.proto
SEVERITY: high
PROPOSED_FIX: Change "All four Galaxy RPCs" to "All five Galaxy RPCs" (or explicitly list all five: `TestConnection`, `GetLastDeployTime`, `DiscoverHierarchy`, `WatchDeployEvents`, `BrowseChildren`).
---
DOC: docs/GalaxyRepository.md
LINES: 378
CLAIM: "`GalaxyRepositoryGrpcService` (`src/ZB.MOM.WW.MxGateway.Server/Grpc/GalaxyRepositoryGrpcService.cs`) implements the five RPCs."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: GalaxyRepositoryGrpcService.cs (file exists at that path); implements all five overrides: TestConnection, GetLastDeployTime, DiscoverHierarchy, WatchDeployEvents, BrowseChildren
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 327
CLAIM: Architecture diagram shows `DiscoverHierarchy, GetLastDeployTime, BrowseChildren -> IGalaxyHierarchyCache.Current` (WatchDeployEvents -> IGalaxyDeployNotifier, TestConnection -> GalaxyRepository direct SQL).
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyRepositoryGrpcService.cs:3339 (TestConnection → repository.TestConnectionAsync), :4262 (GetLastDeployTime → cache.Current), :64110 (DiscoverHierarchy → cache.Current), :112168 (BrowseChildren → cache.Current), :171200 (WatchDeployEvents → notifier.SubscribeAsync)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 346350
CLAIM: "`GalaxyRepository` (`src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyRepository.cs`) holds the SQL. Both `HierarchySql` and `AttributesSql` walk template-derivation and package-derivation chains via recursive CTEs."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:117164 (`HierarchySql` with `template_chain` CTE); GalaxyRepository.cs:176251 (`AttributesSql` with `deployed_package_chain` CTE)
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 347348
CLAIM: "`HierarchySql` still matches the OtOpcUa original; `AttributesSql` does not — it additionally enumerates built-in primitive attributes."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:914 (doc comment confirming this); GalaxyRepository.cs:166175 (comment on AttributesSql confirming divergence)
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 269270
CLAIM: Configured (dynamic) attributes are stored in the Galaxy `dynamic_attribute` table.
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:196 (`INNER JOIN dynamic_attribute da ON da.package_id = dpc.package_id`)
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 272273
CLAIM: Built-in attributes are stored in `attribute_definition` and reached through `primitive_instance`.
CLAIM_TYPE: term
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:214218 (`INNER JOIN primitive_instance pi ON pi.package_id = dpc.package_id` / `INNER JOIN attribute_definition ad ON ad.primitive_definition_id = pi.primitive_definition_id`)
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 283284
CLAIM: The configured-attribute category allow-list is `mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24)`.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:203 (`AND da.mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24)`)
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 283285
CLAIM: No category filter applies to built-in rows (`attribute_definition`); only the `_`-prefixed-name and `.Description` exclusions apply.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:221223 (`AND ad.attribute_name NOT LIKE '[_]%'` and `NOT LIKE '%.Description'` — no `mx_attribute_category` filter for the built-in branch)
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 285287
CLAIM: "`is_historized` / `is_alarm` are always `false` for built-in rows."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:236248 — both `is_historized` and `is_alarm` use `CASE WHEN r.src_pri = 0 AND EXISTS (...)` — built-in rows have `src_pri = 1` so both expressions evaluate to 0 (false).
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 288290
CLAIM: "When a configured attribute and a built-in attribute resolve to the same reference, the configured attribute wins."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyRepository.cs:225228 — `ROW_NUMBER() OVER (PARTITION BY c.gobject_id, c.attribute_name ORDER BY c.src_pri, c.depth)``src_pri = 0` for configured rows, `src_pri = 1` for built-ins, so configured attributes are ranked first.
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 420422
CLAIM: Dashboard `/dashboard/galaxy` page with object-category and top-template breakdowns.
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: GalaxyPage.razor:1 — the page route is `@page "/galaxy"`, not `/dashboard/galaxy`. The Blazor app is mounted without a `/dashboard` prefix (DashboardEndpointRouteBuilderExtensions.cs:86, `MapRazorComponents<App>()`). The Galaxy page is at `/galaxy`, not `/dashboard/galaxy`. The home page at `@page "/"` is the dashboard overview, not at `/dashboard`.
CODE_AREA: gr.proto
SEVERITY: high
PROPOSED_FIX: Change `/dashboard/galaxy` to `/galaxy` and `/dashboard` to `/` throughout the Dashboard Surface section (lines 419421). The Blazor router has no `/dashboard` prefix.
---
DOC: docs/GalaxyRepository.md
LINES: 419420
CLAIM: "An overview card on `/dashboard` showing connectivity status..."
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: DashboardHome.razor:1 — `@page "/"`. The home/overview page is at `/`, not `/dashboard`.
CODE_AREA: gr.proto
SEVERITY: high
PROPOSED_FIX: Change `/dashboard` to `/` in the Dashboard Surface section.
---
DOC: docs/GalaxyRepository.md
LINES: 369375
CLAIM: "`GalaxyBrowseProjector` (`src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyBrowseProjector.cs`) projects one level of children out of an immutable cache entry. Memoizes the filtered child list per cache-entry instance so repeated paging is an O(pageSize) slice rather than an O(siblings) filter scan. The memo is keyed on the cache entry reference, so a new entry from the background refresh makes the stale memo unreachable and it is collected with it. `DashboardBrowseService` wraps this projector to drive the dashboard's lazy-expand tree."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GalaxyBrowseProjector.cs:2022 (ConditionalWeakTable keyed on `GalaxyHierarchyCacheEntry`); DashboardBrowseService.cs:55 (`GalaxyBrowseProjector.ProjectChildren` called inside `DashboardBrowseService`)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 110111
CLAIM: "`IGalaxyHierarchyCache` (`src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyHierarchyCache.cs`) — every `DiscoverHierarchy` and `GetLastDeployTime` request reads from this cache."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: GalaxyHierarchyCache.cs (file at that path); GalaxyRepositoryGrpcService.cs:4662 (GetLastDeployTime reads from cache); :69110 (DiscoverHierarchy reads from cache)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 445447
CLAIM: "Integration tests live in `src/ZB.MOM.WW.MxGateway.IntegrationTests/Galaxy/GalaxyRepositoryLiveTests.cs`. Set `MXGATEWAY_RUN_LIVE_GALAXY_TESTS=1` (and optionally `MXGATEWAY_LIVE_GALAXY_CONN`) to run them."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: GalaxyRepositoryLiveTests.cs (file exists at that path); LiveGalaxyRepositoryFactAttribute.cs:9 (`EnableVariableName = "MXGATEWAY_RUN_LIVE_GALAXY_TESTS"`); LiveGalaxyRepositoryFactAttribute.cs:11 (`ConnectionStringVariableName = "MXGATEWAY_LIVE_GALAXY_CONN"`)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 365367
CLAIM: "`GalaxyProtoMapper` (`src/ZB.MOM.WW.MxGateway.Server/Grpc/GalaxyProtoMapper.cs`) converts row models to proto messages. Used by the cache during refresh to materialize the reply once."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: GalaxyProtoMapper.cs (file at that path); GalaxyHierarchyCache.cs:223 (`BuildObjects``GalaxyProtoMapper.MapObject`)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 212261
CLAIM: The `GalaxyObject`, `GalaxyAttribute`, `DiscoverHierarchyRequest`, and `DiscoverHierarchyReply` message field numbers and types as shown in the "Reply shape" proto block (field numbers 112 for `GalaxyAttribute`, 112 for `DiscoverHierarchyRequest`, etc.).
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: galaxy_repository.proto:110191 (all field numbers and types match the doc's code block)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: 399400
CLAIM: Dashboard "displays only non-secret fields: server, database, integrated security, encrypt, and trust-server-certificate. It never displays user id, password, access token, or arbitrary unparsed connection string text."
CLAIM_TYPE: behavior-rule
VERDICT: unverifiable
EVIDENCE: GalaxyPage.razor:129 (`DashboardDisplay.Text(GalaxyConnectionStringDisplay())`); GalaxyPage.razor:193196 delegates to `DashboardConnectionStringDisplay.GalaxyRepositoryConnectionString`. The actual display logic lives in `DashboardConnectionStringDisplay` which was not found in this audit scope. The behavior is asserted plausibly consistent with "never display user id, password" but the implementation of `DashboardConnectionStringDisplay` was not directly verified.
CODE_AREA: gr.conn
SEVERITY: low
PROPOSED_FIX: flag only — verify `DashboardConnectionStringDisplay` filters fields as claimed.
---
DOC: docs/GalaxyRepository.md
LINES: N/A — not covered in doc
CLAIM: GAP — `GalaxyHierarchyCache` projects `Status` to `Stale` when `LastSuccessAt` is more than 5 minutes old (regardless of the stored status), via `ProjectStatus` with `StaleThreshold = TimeSpan.FromMinutes(5)`.
CLAIM_TYPE: behavior-rule
VERDICT: gap
EVIDENCE: GalaxyHierarchyCache.cs:22 (`StaleThreshold = TimeSpan.FromMinutes(5)`); GalaxyHierarchyCache.cs:474488 (`ProjectStatus` method)
CODE_AREA: gr.proto
SEVERITY: medium
PROPOSED_FIX: Add a note under "Hierarchy Cache" that the cache also auto-degrades to `Stale` status when more than 5 minutes have elapsed since the last successful refresh, independent of the stored entry status. This matters for operators diagnosing why a `Healthy` entry flips to `Stale` without a SQL failure.
---
DOC: docs/GalaxyRepository.md
LINES: N/A — not covered in doc
CLAIM: GAP — `WatchDeployEvents` emits a bootstrap event even on a snapshot-restore (from on-disk data), not only from live SQL queries. `GalaxyHierarchyCache.TryRestoreFromDiskAsync` calls `_notifier.Publish` after restoring.
CLAIM_TYPE: behavior-rule
VERDICT: gap
EVIDENCE: GalaxyHierarchyCache.cs:315320 (`_notifier.Publish` called from `TryRestoreFromDiskAsync`)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: Note under "Deploy Notifications" or "On-disk snapshot" that restoring the snapshot also publishes a deploy event so `WatchDeployEvents` subscribers receive a bootstrap event even when SQL is unreachable at startup.
---
DOC: docs/GalaxyRepository.md
LINES: N/A — not covered in doc
CLAIM: GAP — `GalaxyHierarchyRefreshService` runs an initial `RefreshAsync` immediately on startup (before starting the periodic timer), so the first load happens at process start, not after the first tick of `DashboardRefreshIntervalSeconds`.
CLAIM_TYPE: behavior-rule
VERDICT: gap
EVIDENCE: GalaxyHierarchyRefreshService.cs:2237 (initial `await cache.RefreshAsync` before `PeriodicTimer` is created)
CODE_AREA: gr.proto
SEVERITY: low
PROPOSED_FIX: Add a note under "Hierarchy Cache" that the first refresh runs immediately at gateway startup and does not wait for the first timer tick.
---
DOC: docs/GalaxyRepository.md
LINES: N/A — not covered in doc
CLAIM: GAP — The `HierarchySql` category filter (`td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26)`) and the specific category IDs mapped to names (WinPlatform=1, AppEngine=3, InTouchViewApp=4, UserDefined=10, FieldReference=11, Area=13, DIObject=17, DDESuiteLinkClient=24, OPCClient=26) are not documented anywhere in `GalaxyRepository.md`.
CLAIM_TYPE: behavior-rule
VERDICT: gap
EVIDENCE: GalaxyRepository.cs:161 (HierarchySql WHERE clause with category IDs); GalaxyHierarchyCache.cs:461472 (`ResolveCategoryName` method mapping each ID to a name)
CODE_AREA: gr.sql
SEVERITY: medium
PROPOSED_FIX: Add a table of the filtered category IDs and their names (WinPlatform, AppEngine, InTouchViewApp, etc.) to the doc. Operators need to know which object types are included — an AppEngine that doesn't appear in browse results is hard to diagnose without this list.
---
DOC: docs/GalaxyRepository.md
LINES: N/A — not covered in doc
CLAIM: GAP — The `AttributesSql` uses the `data_type` table to resolve `data_type_name` (`LEFT JOIN data_type dt ON dt.mx_data_type = r.mx_data_type`). The Galaxy table name `data_type` is not mentioned in the doc.
CLAIM_TYPE: term
VERDICT: gap
EVIDENCE: GalaxyRepository.cs:249 (`LEFT JOIN data_type dt ON dt.mx_data_type = r.mx_data_type`)
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: docs/GalaxyRepository.md
LINES: N/A — not covered in doc
CLAIM: GAP — The `HierarchySql` uses the tables `gobject` and `template_definition`, and maps `parent_gobject_id` using `CASE WHEN g.contained_by_gobject_id = 0 THEN g.area_gobject_id ELSE g.contained_by_gobject_id END`. This parent resolution logic (area_gobject_id fallback) is not mentioned.
CLAIM_TYPE: behavior-rule
VERDICT: gap
EVIDENCE: GalaxyRepository.cs:138142 (parent_gobject_id CASE expression); tables referenced: `gobject` (line 158), `template_definition` (line 159)
CODE_AREA: gr.sql
SEVERITY: low
PROPOSED_FIX: flag only
+404
View File
@@ -0,0 +1,404 @@
# Cluster 09 — Alarms
Audited doc: `docs/AlarmClientDiscovery.md`
Code base verified against:
- `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs`
- `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/AlarmDispatcher.cs`
- `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/AlarmCommandHandler.cs`
- `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/AlarmRecordTransitionMapper.cs`
- `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessAlarmEventSink.cs`
- `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAlarmStateKind.cs`
- `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAlarmTransitionEvent.cs`
- `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/IMxAccessAlarmConsumer.cs`
- `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/IAlarmCommandHandler.cs`
- `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessCommandExecutor.cs` (alarm arms)
- `src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs`
- `src/ZB.MOM.WW.MxGateway.Server/Alarms/IGatewayAlarmService.cs`
- `src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmsServiceCollectionExtensions.cs`
- `src/ZB.MOM.WW.MxGateway.Server/Configuration/AlarmsOptions.cs`
- `src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto`
---
DOC / LINES / 71-74 (comment about `AlarmClientConsumer.cs`)
CLAIM / The file `src/ZB.MOM.WW.MxGateway.Worker/MxAccess/AlarmClientConsumer.cs` exists in the repo.
CLAIM_TYPE / path
VERDICT / wrong
EVIDENCE / `find /Users/dohertj2/Desktop/mxaccessgateway/src -name "AlarmClientConsumer*"` returns nothing. The file no longer exists; `WnWrapAlarmConsumer.cs` comments confirm it was replaced: `WnWrapAlarmConsumer.cs:18-19`.
CODE_AREA / alarm.subscribe
SEVERITY / medium
PROPOSED_FIX / Update references to the obsolete `AlarmClientConsumer.cs` throughout the doc to note that the file was retired and replaced by `WnWrapAlarmConsumer.cs`.
---
DOC / LINES / 71-74
CLAIM / The architecture comment on `AlarmClientConsumer.cs` (PR A.5) describing `IAlarmMgrDataProvider` managed events is wrong against the deployed assembly — there is no managed event surface.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / The source file it critiques (`AlarmClientConsumer.cs`) no longer exists in the repo. The critique is historically accurate but refers to a file that was removed during the wnwrap migration. No live code contains `IAlarmMgrDataProvider`. `WnWrapAlarmConsumer.cs:1-575`.
CODE_AREA / alarm.subscribe
SEVERITY / low
PROPOSED_FIX / Note that the critique is a historical record; the offending file has been removed. The section remains valid as probe context but should clarify the current state.
---
DOC / LINES / 87-88
CLAIM / `AlarmClientConsumer.AlarmRecordReceived` has no production callers; `RaiseAlarmRecordReceived` is `internal` for tests and never invoked at runtime.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / Neither `AlarmRecordReceived` nor `RaiseAlarmRecordReceived` appear anywhere in the current source tree (`grep -rn "AlarmRecordReceived\|RaiseAlarmRecordReceived" src` — zero results outside tests or binaries). The entire `AlarmClientConsumer` class was removed; the observation is a dead historical probe note.
CODE_AREA / alarm.subscribe
SEVERITY / low
PROPOSED_FIX / Flag as historical only; the code path no longer exists.
---
DOC / LINES / 492
CLAIM / "PR A.5's `Subscribe` / `AcknowledgeByGuid` / `SnapshotActiveAlarms` are correct — they're pull-style and don't depend on the notification mechanism."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / The method names in the current interface are `IMxAccessAlarmConsumer.AcknowledgeByGuid` and `SnapshotActiveAlarms` (`IMxAccessAlarmConsumer.cs:64,104`), so the names are accurate. However, this statement refers to PR A.5's `AlarmClientConsumer`, which no longer exists. The claim implicitly endorses `AlarmClientConsumer` code that has been replaced by `WnWrapAlarmConsumer`. The successor also exposes `AcknowledgeByGuid` but routes it through `AlarmAckByGUID` on `wwAlarmConsumerClass`.
CODE_AREA / alarm.ack
SEVERITY / low
PROPOSED_FIX / Note that PR A.5 was superseded; the current production path is `WnWrapAlarmConsumer`.
---
DOC / LINES / 604-605
CLAIM / After an alarm return-to-normal (`UNACK_RTN`), `wwAlarmConsumerClass.AlarmAckByGUID` is "the method to call" for acknowledgement.
CLAIM_TYPE / behavior-rule
VERDICT / wrong
EVIDENCE / The doc itself contradicts this eleven sections later ("Section 4. `AlarmAckByGUID` is not implemented", lines 750-756): `AlarmAckByGUID(VBGUID, …)` throws `NotImplementedException` (COM `E_NOTIMPL`) on `wwAlarmConsumerClass`. The doc at line 604 presents it as the correct ack method before the discovery in the live-smoke section, creating a contradiction within the document that integrators reading top-to-bottom will encounter.
CODE_AREA / alarm.ack
SEVERITY / high
PROPOSED_FIX / Add a forward-reference warning at line 604 ("Note: see 'Live smoke-test discoveries — section 4' below; AlarmAckByGUID is E_NOTIMPL on wnwrap and must not be called directly; use AlarmAckByName via the ack-only consumer.") or reorder the section.
---
DOC / LINES / 750-756
CLAIM / `AlarmAckByGUID(VBGUID, …)` throws `NotImplementedException` (`E_NOTIMPL`) on `wwAlarmConsumerClass`, so all acks must go through `AlarmAckByName`.
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / `WnWrapAlarmConsumer.cs:215-239` provides `AcknowledgeByGuid` which calls `com.AlarmAckByGUID` directly (the COM interop). The method is present in the consumer and called from `AlarmCommandHandler.Acknowledge` (`AlarmCommandHandler.cs:141-158`) and `AlarmDispatcher.Acknowledge` (`AlarmDispatcher.cs:87-103`). The code path is plumbed through and compiles. Whether it still throws `E_NOTIMPL` at runtime on the deployed AVEVA build is a runtime-only observable — the doc's claim was empirically confirmed 2026-05-01.
CODE_AREA / alarm.ack
SEVERITY / medium
PROPOSED_FIX / Flag: the code now calls `AlarmAckByGUID` without a try/catch for `E_NOTIMPL`; document that the GUID path will surface a `COMException` at runtime on affected AVEVA builds and that the gateway routes canonical `Provider!Group.Tag` references through `AcknowledgeAlarmByName` to avoid this.
---
DOC / LINES / 758-762
CLAIM / "The proto `AcknowledgeAlarmCommand` (GUID-based) and `MxAccessCommandExecutor.ExecuteAcknowledgeAlarm` switch arm remain in the codebase for forward-compat, but the gateway-side `WorkerAlarmRpcDispatcher.AcknowledgeAsync` now always routes through `AcknowledgeAlarmByName` when the public RPC supplies a recognizable `Provider!Group.Tag` reference."
CLAIM_TYPE / cross-ref
VERDICT / wrong
EVIDENCE / (a) `WorkerAlarmRpcDispatcher` does not exist in the source tree. The class that routes acknowledge requests is `GatewayAlarmMonitor.AcknowledgeAsync` + `BuildAcknowledgeCommand` (`GatewayAlarmMonitor.cs:437,516`). (b) The gateway does NOT always route through `AcknowledgeAlarmByName`: `BuildAcknowledgeCommand` first tries `Guid.TryParse`; if the `alarm_full_reference` is a canonical GUID it still dispatches `MxCommandKind.AcknowledgeAlarm` (the GUID path) (`GatewayAlarmMonitor.cs:528-543`). Only when the reference is not a GUID does it fall through to `AcknowledgeAlarmByName` (`GatewayAlarmMonitor.cs:545-563`).
CODE_AREA / alarm.ack
SEVERITY / high
PROPOSED_FIX / (1) Replace `WorkerAlarmRpcDispatcher` with the actual class name `GatewayAlarmMonitor`. (2) Correct the routing description: GUID-shaped references still go through `AcknowledgeAlarmCommand` (GUID path); `Provider!Group.Tag` references go through `AcknowledgeAlarmByNameCommand`. The claim that it "always routes through `AcknowledgeAlarmByName`" is false.
---
DOC / LINES / 636-639 (A.2 outline step 2)
CLAIM / Production `WnWrapAlarmConsumer` polls `GetXmlCurrentAlarms2(maxAlmCnt, out xml)` on a timer (500ms1s cadence).
CLAIM_TYPE / behavior-rule
VERDICT / wrong
EVIDENCE / `WnWrapAlarmConsumer.cs:38-43` explicitly states "the consumer owns no internal timer." `PollOnce()` is driven externally by `StaRuntime.InvokeAsync` (`WnWrapAlarmConsumer.cs:39`, `AlarmCommandHandler.cs:29-33`). The 500ms1s timer cadence mentioned in the doc was a design proposal; the implementation delegates all poll scheduling to the caller (STA). The doc's step 2 reads as if the consumer self-schedules.
CODE_AREA / alarm.subscribe
SEVERITY / medium
PROPOSED_FIX / Correct to: "Poll `GetXmlCurrentAlarms2` via `PollOnce()` called externally by the worker's STA through `StaRuntime.InvokeAsync`; the consumer owns no internal timer."
---
DOC / LINES / 641-643 (A.2 outline step 2)
CLAIM / "`AlarmAckByGUID(VBGUID, comment, oprName, node, domain, fullName)` for client-driven acknowledgements (matches PR A.5's `AlarmAckCommand` payload)."
CLAIM_TYPE / rpc/proto
VERDICT / wrong
EVIDENCE / The proto message is named `AcknowledgeAlarmCommand` (not `AlarmAckCommand`): `mxaccess_gateway.proto:337`. The consumer also exposes `AcknowledgeByGuid` (not `AlarmAckByGUID`) as its interface method (`IMxAccessAlarmConsumer.cs:64`). The doc uses the COM method name where it should use the proto/interface name, and uses the wrong proto message name.
CODE_AREA / alarm.ack
SEVERITY / medium
PROPOSED_FIX / Replace "PR A.5's `AlarmAckCommand` payload" with "the proto's `AcknowledgeAlarmCommand` message" (`mxaccess_gateway.proto:337`).
---
DOC / LINES / 644-647 (A.2 outline step 3)
CLAIM / STATE mapping: `UNACK_ALM``in_alarm=true, acked=false`; `UNACK_RTN``in_alarm=false, acked=false`; `ACK_ALM``in_alarm=true, acked=true`; `ACK_RTN``in_alarm=false, acked=true`.
CLAIM_TYPE / term
VERDICT / wrong
EVIDENCE / The production proto uses `AlarmConditionState` (Active / ActiveAcked / Inactive), not boolean `in_alarm`/`acked` fields. `AlarmDispatcher.MapConditionState` (`AlarmDispatcher.cs:221-234`): `UnackAlm→Active`, `AckAlm→ActiveAcked`, `UnackRtn→Inactive`, `AckRtn→Inactive`. Both Rtn states collapse to `Inactive` — the `acked` distinction on a cleared alarm is not surfaced. The doc's proposed boolean decomposition was a design proposal that was not adopted; the final proto shape uses the enum.
CODE_AREA / alarm.state
SEVERITY / high
PROPOSED_FIX / Replace the boolean mapping table with the actual `AlarmConditionState` enum mapping used in `AlarmDispatcher.MapConditionState`. Document that `UnackRtn` and `AckRtn` both map to `Inactive` (ack-vs-unack on a cleared alarm is not exposed through the proto).
---
DOC / LINES / 648-649 (A.2 outline step 3)
CLAIM / "`GUID``condition_id` (canonicalize the no-dashes hex to a UUID string)."
CLAIM_TYPE / term
VERDICT / wrong
EVIDENCE / The production code stores the GUID as `MxAlarmSnapshotRecord.AlarmGuid` (a `System.Guid`) and the proto carries it inside `OnAlarmTransitionEvent` only implicitly (there is no `condition_id` field in the proto). The `alarm_full_reference` field is used as the stable identifier for condition correlation, not a `condition_id`. `mxaccess_gateway.proto:720-723`, `OnAlarmTransitionEvent.alarm_full_reference`. The field name `condition_id` does not exist in the proto.
CODE_AREA / alarm.state
SEVERITY / medium
PROPOSED_FIX / Replace `condition_id` with the actual stable identifier: `alarm_full_reference` (`OnAlarmTransitionEvent.alarm_full_reference`). The GUID is used internally by `WnWrapAlarmConsumer` as a snapshot key but is not exposed as a proto field.
---
DOC / LINES / 651-654 (A.2 outline step 3 — timestamp)
CLAIM / "`DATE + TIME + GMTOFFSET + DSTADJUST` → reassemble UTC timestamp; matches the worker's existing `Timestamp` wire format."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / `AlarmRecordTransitionMapper.ParseTransitionTimestampUtc` (`AlarmRecordTransitionMapper.cs:116-188`) parses all four fields and computes UTC. The proto uses `google.protobuf.Timestamp` (`mxaccess_gateway.proto:747`). Wire-up matches.
CODE_AREA / alarm.state
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 656-657 (A.2 outline step 3)
CLAIM / "`PRIORITY` → severity (already 1-1000-ish range)."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / `WnWrapAlarmConsumer.ParseSnapshotXml` reads `PRIORITY` as `int` (`WnWrapAlarmConsumer.cs:433`), stored as `MxAlarmSnapshotRecord.Priority`. `AlarmDispatcher.OnTransition` passes it as `severity: record.Priority` (`AlarmDispatcher.cs:187`). `OnAlarmTransitionEvent.severity` is `int32` in the proto (`mxaccess_gateway.proto`). The 1-1000 range is consistent with AVEVA's alarm priority range.
CODE_AREA / alarm.state
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 658-659 (A.2 outline step 3)
CLAIM / "`TAGNAME` → reference; `PROVIDER_NAME` + `GROUP` for scope metadata."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / `AlarmDispatcher.OnTransition` calls `AlarmRecordTransitionMapper.ComposeFullReference(record.ProviderName, record.Group, record.TagName)` and passes the result as `alarmFullReference` (`AlarmDispatcher.cs:180-183`). `ComposeFullReference` formats it as `Provider!Group.TagName` (`AlarmRecordTransitionMapper.cs:90-102`). `TAGNAME` alone is passed as `sourceObjectReference` (`AlarmDispatcher.cs:184`).
CODE_AREA / alarm.state
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 672-676 (A.2 outline step 5)
CLAIM / "PR A.5's snapshot/ack contract tests can stay — they don't touch the underlying COM API."
CLAIM_TYPE / cross-ref
VERDICT / stale
EVIDENCE / PR A.5's `AlarmClientConsumer` was retired; there is no class by that name. The test files for alarm command handling now cover `AlarmCommandHandler`, `AlarmDispatcher`, and `WnWrapAlarmConsumerXmlTests` — none named as "PR A.5 tests." The statement implies a test corpus that doesn't exist under the described label.
CODE_AREA / alarm.subscribe
SEVERITY / low
PROPOSED_FIX / Remove or update the PR label; reference actual test files: `AlarmCommandHandlerTests.cs`, `AlarmDispatcherTests.cs`, `WnWrapAlarmConsumerXmlTests.cs`.
---
DOC / LINES / 673-675 (settled API ordering section)
CLAIM / "`InitializeConsumer` first, then `RegisterConsumer` — both on `aaAlarmManagedClient.AlarmClient` and `wwAlarmConsumerClass`."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / `WnWrapAlarmConsumer.Subscribe` calls `IwwAlarmConsumer_InitializeConsumer` before `IwwAlarmConsumer_RegisterConsumer` (`WnWrapAlarmConsumer.cs:117-137`). Same ordering for `ackClient` (`WnWrapAlarmConsumer.cs:188-208`).
CODE_AREA / alarm.subscribe
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 676-682 (settled API section)
CLAIM / "`aaAlarmManagedClient.AlarmClient.RegisterConsumer` is 5-arg (includes `bRetainHiddenAlarms`); `wwAlarmConsumerClass.RegisterConsumer` is 4-arg (no `bRetainHiddenAlarms`)."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / `WnWrapAlarmConsumer.Subscribe` calls `IwwAlarmConsumer_RegisterConsumer` with 4 args: `hWnd, szProductName, szApplicationName, szVersion` (`WnWrapAlarmConsumer.cs:128-132`). Consistent with the doc.
CODE_AREA / alarm.subscribe
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 683-685 (settled API section)
CLAIM / "Subscription expression format: `\\<machine>\Galaxy!<area>` (literal `Galaxy` provider) for both libraries."
CLAIM_TYPE / path
VERDICT / accurate
EVIDENCE / `WnWrapAlarmConsumer.ComposeXmlAlarmQuery` parses this format and treats `Galaxy` as the provider (`WnWrapAlarmConsumer.cs:489-530`). `IMxAccessAlarmConsumer.Subscribe` doc comment confirms: "Subscription string follows AVEVA's canonical format: `\\<node>\Galaxy!<area>`. The literal 'Galaxy' is the provider name (regardless of the configured Galaxy database name)." (`IMxAccessAlarmConsumer.cs:44-46`). `AlarmsOptions.cs:16-17` also confirms.
CODE_AREA / alarm.subscribe
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 684-685 (settled API section)
CLAIM / "Native ack: `AlarmAckByGUID(VBGUID guid, comment, oprName, node, domain, fullName)` on the v2 surface."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / `WnWrapAlarmConsumer.AcknowledgeByGuid` calls `com.AlarmAckByGUID` with exactly those args (`WnWrapAlarmConsumer.cs:232-238`).
CODE_AREA / alarm.ack
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 695-699 (live smoke quirk 1)
CLAIM / "Without `SetXmlAlarmQuery`, the first `GetXmlCurrentAlarms2` call fails with `E_FAIL` (HRESULT `0x80004005`)."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / `WnWrapAlarmConsumer.Subscribe` calls `SetXmlAlarmQuery` and wraps it with a `COMException` guard that would surface as `InvalidOperationException` with the E_FAIL message (`WnWrapAlarmConsumer.cs:156-182`). The call is mandatory per production code structure.
CODE_AREA / alarm.subscribe
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 719-733 (live smoke quirk 2)
CLAIM / "Two consumers required: read-side consumer (with `SetXmlAlarmQuery`) and ack-only consumer (without `SetXmlAlarmQuery`). All `AcknowledgeByName` calls dispatch through the ack-only instance."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / `WnWrapAlarmConsumer.Subscribe` provisions `ackClient = new wwAlarmConsumerClass()` with full lifecycle but no `SetXmlAlarmQuery` (`WnWrapAlarmConsumer.cs:184-210`). `AcknowledgeByName` uses `ackClient` (`WnWrapAlarmConsumer.cs:256-278`). `AcknowledgeByGuid` uses `client` (read-side) (`WnWrapAlarmConsumer.cs:224-238`).
CODE_AREA / alarm.ack
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 736-748 (live smoke quirk 3)
CLAIM / "The v2 8-arg `AlarmAckByName` returns -55 on this AVEVA build. The v1 6-arg `AlarmAckByName` works. Production `WnWrapAlarmConsumer.AcknowledgeByName` calls the 6-arg overload. Operator domain and full-name fields are accepted by the proto but not propagated to AVEVA (discarded)."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / `WnWrapAlarmConsumer.AcknowledgeByName` calls `com.AlarmAckByName` (6-arg) and explicitly discards `ackOperatorDomain` and `ackOperatorFullName` with `_ = ...` (`WnWrapAlarmConsumer.cs:268-278`). The proto `AcknowledgeAlarmByNameCommand` retains `operator_domain` and `operator_full_name` fields (`mxaccess_gateway.proto:359-373`).
CODE_AREA / alarm.ack
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 750-756 (live smoke quirk 4)
CLAIM / "`AlarmAckByGUID` is not implemented on `wwAlarmConsumerClass`; it throws `NotImplementedException` / `E_NOTIMPL`. The reference→GUID lookup is not viable; all acks must go through `AlarmAckByName`."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / The production code at `WnWrapAlarmConsumer.AcknowledgeByGuid` still calls `com.AlarmAckByGUID` directly without a guard for `E_NOTIMPL` (`WnWrapAlarmConsumer.cs:215-239`). The gateway's `BuildAcknowledgeCommand` still dispatches `MxCommandKind.AcknowledgeAlarm` (GUID path) when `alarm_full_reference` parses as a GUID (`GatewayAlarmMonitor.cs:528-543`). The doc says all acks must go through `AcknowledgeByName`, but the code still routes GUID-shaped references through `AlarmAckByGUID`. The `E_NOTIMPL` runtime behavior is unguarded.
CODE_AREA / alarm.ack
SEVERITY / high
PROPOSED_FIX / Either (a) add a `COMException`/`NotImplementedException` guard around `AlarmAckByGUID` in `WnWrapAlarmConsumer.AcknowledgeByGuid` that falls back to `AcknowledgeByName`, or (b) make the gateway never dispatch the GUID arm. Document whichever approach is taken. The current state silently sends a doomed IPC command.
---
DOC / LINES / 761-762
CLAIM / "`WorkerAlarmRpcDispatcher.AcknowledgeAsync` now always routes through `AcknowledgeAlarmByName` when the public RPC supplies a recognizable `Provider!Group.Tag` reference."
CLAIM_TYPE / cross-ref
VERDICT / wrong
EVIDENCE / (a) No class named `WorkerAlarmRpcDispatcher` exists in the source tree. The gateway-side routing is in `GatewayAlarmMonitor.BuildAcknowledgeCommand` (`GatewayAlarmMonitor.cs:516`). (b) The routing is conditional: GUID-shaped `alarm_full_reference``AcknowledgeAlarmCommand` (GUID path); `Provider!Group.Tag``AcknowledgeAlarmByNameCommand`. The claim that the routing "always" goes through `AcknowledgeAlarmByName` is incorrect.
CODE_AREA / alarm.ack
SEVERITY / high
PROPOSED_FIX / Replace the entire sentence. The correct description: "The gateway's `GatewayAlarmMonitor.BuildAcknowledgeCommand` (`GatewayAlarmMonitor.cs:516`) dispatches `MxCommandKind.AcknowledgeAlarm` for GUID-shaped references and `MxCommandKind.AcknowledgeAlarmByName` for `Provider!Group.Tag` references."
---
DOC / LINES / 765-773 (STA quirk 5)
CLAIM / "The consumer's internal `Timer` fires on threadpool threads and would block on cross-apartment marshaling unless the host STA pumps Win32 messages. The smoke test sidesteps this by setting `pollIntervalMilliseconds=0` (Timer disabled) and driving `PollOnce` manually."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / The production `WnWrapAlarmConsumer` has no internal `Timer` at all — the design was revised so `PollOnce()` is always external (`WnWrapAlarmConsumer.cs:38-43`: "the consumer owns no internal timer"). There is no `pollIntervalMilliseconds` constructor parameter (`WnWrapAlarmConsumer.cs:69-87`). The constructor takes only `wwAlarmConsumerClass client` and `int maxAlarmsPerFetch`. The smoke test mention of `pollIntervalMilliseconds=0` refers to a superseded design.
CODE_AREA / alarm.subscribe
SEVERITY / medium
PROPOSED_FIX / Update to reflect the final design: `WnWrapAlarmConsumer` has no internal timer; `PollOnce()` is always called externally by the STA. Remove the `pollIntervalMilliseconds=0` test-workaround reference.
---
DOC / LINES / 599-601 (XML STATE enum section)
CLAIM / "`STATE` enum values observed: `UNACK_RTN` (alarm returned to normal, unacknowledged) and `UNACK_ALM` (alarm active and unacknowledged). Other states (`ACK_RTN`, `ACK_ALM`) would appear when an ack is performed."
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / `MxAlarmStateKind.cs:1-17` defines all four values. `AlarmRecordTransitionMapper.ParseStateKind` handles all four (`AlarmRecordTransitionMapper.cs:27-38`).
CODE_AREA / alarm.state
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / 628-630 (reference format in smoke capture)
CLAIM / Reference format in the capture: `ref='Galaxy!TestArea.TestMachine_001.TestAlarm001'` — the `alarm_full_reference` is composed as `ProviderName!Group.TagName`.
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / `AlarmRecordTransitionMapper.ComposeFullReference` formats as `{provider}!{group}.{name}` (`AlarmRecordTransitionMapper.cs:90-102`). The example matches this pattern exactly.
CODE_AREA / alarm.state
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / (entire doc — RPC names)
CLAIM / The document mentions IPC commands `SubscribeAlarms`, `AcknowledgeByGuid`, `SnapshotActiveAlarms`, `QueryActiveAlarms` but never names the public gRPC RPCs — `AcknowledgeAlarm`, `StreamAlarms`, `QueryActiveAlarms` — or the config keys governing the always-on monitor (`MxGateway:Alarms:Enabled`, `MxGateway:Alarms:SubscriptionExpression`, `MxGateway:Alarms:DefaultArea`, `MxGateway:Alarms:ReconcileIntervalSeconds`).
CLAIM_TYPE / gap
VERDICT / gap
EVIDENCE / `mxaccess_gateway.proto:22-37` (RPCs); `AlarmsOptions.cs:21-47` (config keys); `GatewayAlarmMonitor.cs:17-51` (always-on broker). None documented in `AlarmClientDiscovery.md`.
CODE_AREA / alarm.subscribe
SEVERITY / high
PROPOSED_FIX / The doc is a probe/research log, not an operator/integrator guide. However, the gap means no other document covers these public-surface items. Add a section or cross-reference to the public alarm API: RPCs `AcknowledgeAlarm`, `StreamAlarms`, `QueryActiveAlarms`; config keys `MxGateway:Alarms:Enabled`, `MxGateway:Alarms:SubscriptionExpression`, `MxGateway:Alarms:DefaultArea`, `MxGateway:Alarms:ReconcileIntervalSeconds`.
---
DOC / LINES / (entire doc — always-on broker architecture)
CLAIM / (gap) The doc describes a model where individual client sessions subscribe to alarms. The production architecture uses a gateway-owned always-on `GatewayAlarmMonitor` that holds one dedicated worker session and fans the alarm feed to all clients. No client opens its own alarm subscription; `StreamAlarms` is session-less.
CLAIM_TYPE / gap
VERDICT / gap
EVIDENCE / `GatewayAlarmMonitor.cs:1-697`; `IGatewayAlarmService.cs:27-63`; `AlarmsOptions.cs:1-48`. `AlarmClientDiscovery.md` describes the worker alarm consumer (IPC layer) but never describes the gateway-level brokering architecture that wraps it.
CODE_AREA / alarm.subscribe
SEVERITY / high
PROPOSED_FIX / Add a section describing `GatewayAlarmMonitor` as the always-on broker: one gateway-owned session, periodic reconcile loop (`ReconcileIntervalSeconds`), `StreamAsync` fan-out to per-client `Channel<AlarmFeedMessage>`, subscriber capacity (2048 messages), fail-open restart-backoff (5s).
---
DOC / LINES / (entire doc — AlarmFeedMessage / snapshot_complete protocol)
CLAIM / (gap) The doc does not document the `AlarmFeedMessage` stream protocol: initial burst of `active_alarm` messages, then `snapshot_complete` sentinel, then `transition` messages for live changes.
CLAIM_TYPE / gap
VERDICT / gap
EVIDENCE / `mxaccess_gateway.proto:857-868` (message definition); `GatewayAlarmMonitor.StreamAsync:386-434`. This is the key integrator-facing streaming contract.
CODE_AREA / alarm.subscribe
SEVERITY / high
PROPOSED_FIX / Document the `StreamAlarms` protocol: `AlarmFeedMessage` union with `active_alarm`, `snapshot_complete`, and `transition` fields; the invariant that the snapshot precedes the sentinel which precedes live transitions.
---
DOC / LINES / (entire doc — reconcile mechanism)
CLAIM / (gap) The periodic reconcile loop (`ReconcileIntervalSeconds`, default 30s, floor 5s) that snapshots the worker's active-alarm set and broadcasts synthetic raise/clear transitions for missed alarms is not documented.
CLAIM_TYPE / gap
VERDICT / gap
EVIDENCE / `GatewayAlarmMonitor.ReconcileLoopAsync:235-260`; `GatewayAlarmMonitor.ApplyReconcile:315-354`; `AlarmsOptions.ReconcileIntervalSeconds:47`.
CODE_AREA / alarm.subscribe
SEVERITY / medium
PROPOSED_FIX / Document the reconcile pass: cadence, purpose (catches missed poll-and-diff transitions), synthetic transition kind (`Raise`/`Clear`), and that it does not emit `Acknowledge` transitions.
---
DOC / LINES / (entire doc — subscriber backpressure / drop behavior)
CLAIM / (gap) A subscriber that cannot keep up with the alarm feed is dropped with an error ("Alarm feed subscriber fell behind and was dropped; reconnect to re-snapshot"). The queue capacity is 2048. This behavior is not documented.
CLAIM_TYPE / gap
VERDICT / gap
EVIDENCE / `GatewayAlarmMonitor.Broadcast:358-375`; `SubscriberQueueCapacity = 2048` (`GatewayAlarmMonitor.cs:21`).
CODE_AREA / alarm.subscribe
SEVERITY / medium
PROPOSED_FIX / Document the backpressure model: bounded 2048-message channel per subscriber; slow subscribers are completed with error and must reconnect; reconnect re-snapshots the active set.
---
DOC / LINES / (entire doc — `alarm_full_reference` parse format for ack)
CLAIM / (gap) The doc does not document the `alarm_full_reference` parse contract for `AcknowledgeAlarm`: a canonical GUID string triggers the GUID path; `Provider!Group.Tag` (first `!` splits provider, first `.` splits group from tag) triggers the by-name path; anything else is rejected.
CLAIM_TYPE / gap
VERDICT / gap
EVIDENCE / `GatewayAlarmMonitor.BuildAcknowledgeCommand` and `TryParseAlarmReference` (`GatewayAlarmMonitor.cs:516-610`). Error message: "alarm_full_reference must be a canonical GUID or 'Provider!Group.Tag' format."
CODE_AREA / alarm.ack
SEVERITY / high
PROPOSED_FIX / Document the `AcknowledgeAlarm.alarm_full_reference` field's two accepted formats and how the gateway routes each.
---
DOC / LINES / (entire doc — `AlarmConditionState` on snapshot)
CLAIM / (gap) The `ActiveAlarmSnapshot.current_state` field uses `AlarmConditionState` (Active / ActiveAcked / Inactive) — the distinction between `UnackRtn` and `AckRtn` is lost in the snapshot (both collapse to Inactive). This is not documented.
CLAIM_TYPE / gap
VERDICT / gap
EVIDENCE / `AlarmDispatcher.MapConditionState` (`AlarmDispatcher.cs:221-234`): both `UnackRtn` and `AckRtn` map to `AlarmConditionState.Inactive`.
CODE_AREA / alarm.state
SEVERITY / medium
PROPOSED_FIX / Document the state collapse rule: the `ActiveAlarmSnapshot.current_state` field does not distinguish between acknowledged-cleared and unacknowledged-cleared alarms; both surface as `Inactive`. Consumers that need this distinction must track the transition stream.
---
DOC / LINES / (entire doc — transition kind table)
CLAIM / (gap) The `AlarmTransitionKind` enum has a `Retrigger` value (`ALARM_TRANSITION_KIND_RETRIGGER = 4`), but the doc only describes Raise / Acknowledge / Clear.
CLAIM_TYPE / gap
VERDICT / gap
EVIDENCE / `mxaccess_gateway.proto:777`; `AlarmRecordTransitionMapper.MapTransition` does not produce `Retrigger` — it is defined in the proto but unused by the current mapping logic (`AlarmRecordTransitionMapper.cs:54-78`).
CODE_AREA / alarm.state
SEVERITY / low
PROPOSED_FIX / Note that `AlarmTransitionKind.Retrigger` exists in the proto but is not emitted by the current worker (the `*Rtn→*Alm` re-trigger case maps to `Raise`). Flag as reserved for future use or remove from the proto if unused.
+522
View File
@@ -0,0 +1,522 @@
# Cluster 10 — Testing
Docs audited: `docs/GatewayTesting.md`, `docs/ClientBehaviorFixtures.md`,
`docs/ParityFixtureMatrix.md`, `docs/CrossLanguageSmokeMatrix.md`,
`docs/ToolchainLinks.md`.
Verified against: `src/ZB.MOM.WW.MxGateway.Tests/**`, `src/ZB.MOM.WW.MxGateway.Worker.Tests/**`,
`src/ZB.MOM.WW.MxGateway.IntegrationTests/**`, `scripts/run-client-e2e-tests.ps1`,
`scripts/validate-client-behavior-fixtures.ps1`, `scripts/discover-testmachine-tags.ps1`,
`clients/proto/fixtures/**`.
---
DOC / LINES / GatewayTesting.md / 322324
CLAIM / "the script builds the .NET CLI (`dotnet build`) and installs the Java CLI (`gradle :mxgateway-cli:installDist`) once"
CLAIM_TYPE / command
VERDICT / wrong
EVIDENCE / scripts/run-client-e2e-tests.ps1:542 — actual invocation is `gradle :zb-mom-ww-mxgateway-cli:installDist`; clients/java/settings.gradle:26 — the Gradle subproject is named `zb-mom-ww-mxgateway-cli`, not `mxgateway-cli`
CODE_AREA / test.cmd
SEVERITY / high
PROPOSED_FIX / Replace `:mxgateway-cli:installDist` with `:zb-mom-ww-mxgateway-cli:installDist` in GatewayTesting.md line 323.
---
DOC / LINES / clients/proto/fixtures/smoke/cross-language-smoke-matrix.json / multiple Java command entries
CLAIM / Java bundled and optional commands use `gradle :mxgateway-cli:run` (e.g. `gradle :mxgateway-cli:run --args="close-session ..."`)
CLAIM_TYPE / command
VERDICT / wrong
EVIDENCE / clients/java/settings.gradle:26 — Gradle subproject name is `zb-mom-ww-mxgateway-cli`; the `:mxgateway-cli:run` task does not exist and would fail. scripts/run-client-e2e-tests.ps1:542 uses the correct `:zb-mom-ww-mxgateway-cli:installDist`
CODE_AREA / test.cmd
SEVERITY / high
PROPOSED_FIX / Replace every `:mxgateway-cli:run` in the smoke matrix JSON with `:zb-mom-ww-mxgateway-cli:run`. Also update the `installDist` reference in any bundled command if present.
---
DOC / LINES / GatewayTesting.md / 4044
CLAIM / "`WorkerLiveMxAccessSmokeTests` in `src/ZB.MOM.WW.MxGateway.IntegrationTests/` … It is skipped unless `MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1` is set"
CLAIM_TYPE / path, config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:25 (`[LiveMxAccessFact]`); src/ZB.MOM.WW.MxGateway.IntegrationTests/IntegrationTestEnvironment.cs:13 (`LiveMxAccessVariableName = GatewayContractInfo.LiveMxAccessOptInVariableName`); src/ZB.MOM.WW.MxGateway.Contracts/GatewayContractInfo.cs:28 (`"MXGATEWAY_RUN_LIVE_MXACCESS_TESTS"`)
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 76
CLAIM / "All six tests are gated by the same `MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1` opt-in variable"
CLAIM_TYPE / term, config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs — exactly 6 `[LiveMxAccessFact]` attributes at lines 33, 122, 238, 296, 440, 571
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 8283
CLAIM / Worker build command: `dotnet build src/ZB.MOM.WW.MxGateway.Worker/ZB.MOM.WW.MxGateway.Worker.csproj -p:Platform=x86`
CLAIM_TYPE / command, path
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Worker/ZB.MOM.WW.MxGateway.Worker.csproj exists; PlatformTarget=x86 is set in the Worker.Tests csproj (src/ZB.MOM.WW.MxGateway.Worker.Tests/ZB.MOM.WW.MxGateway.Worker.Tests.csproj:6)
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 89
CLAIM / Live MXAccess smoke run: `dotnet test src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj --filter FullyQualifiedName~WorkerLiveMxAccessSmokeTests`
CLAIM_TYPE / command, path
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj exists; class name `WorkerLiveMxAccessSmokeTests` confirmed at src/ZB.MOM.WW.MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:25
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 94101
CLAIM / Optional live smoke variables table: `MXGATEWAY_LIVE_MXACCESS_WORKER_EXE`, `MXGATEWAY_LIVE_MXACCESS_ITEM`, `MXGATEWAY_LIVE_MXACCESS_CLIENT_NAME`, `MXGATEWAY_LIVE_MXACCESS_EVENT_TIMEOUT_SECONDS` with stated defaults
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/IntegrationTestEnvironment.cs:1417 — all four constant names match exactly; defaults match (TestChildObject.TestInt line 39, ZB.MOM.WW.MxGateway.IntegrationTests line 45, 15 seconds line 51)
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 100101
CLAIM / Optional variables `MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_USER` (default `admin`) and `MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_PASSWORD` (default `admin123`) "are gated by the same opt-in variable"
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:974977 — variable names and defaults exactly match; note these constants are NOT in `IntegrationTestEnvironment` (they live inline in `ResolveLiveMxAccessSecuredCredentials`), which is an internal code organisation matter not a doc error
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 1016
CLAIM / "`FakeWorkerHarness` in `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/Fakes/` … uses the same `WorkerFrameReader`, `WorkerFrameWriter`, and `WorkerEnvelope` contract"
CLAIM_TYPE / path, term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/Fakes/FakeWorkerHarness.cs:55 (`CreateConnectedPairAsync`) and :90 (`ConnectToGatewayPipeAsync`) confirm both methods exist
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 2226
CLAIM / FakeWorkerHarness scripts: WorkerHello, WorkerReady, command replies, ordered WorkerEvent frames, WorkerHeartbeat frames, WorkerFault frames, shutdown acknowledgements, malformed payloads, oversized frame headers, slow/hung workers
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/Fakes/FakeWorkerHarness.cs:208 (SendWorkerHelloAsync), :233 (SendWorkerReadyAsync), :317 (WorkerEvent), :329 (WorkerFault), :353 (SendHeartbeatAsync), :373 (shutdown ack), :394 (malformed payload), :412 (oversized frame header)
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 109113
CLAIM / "`src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/` partitions runtime probes … `ZB.MOM.WW.MxGateway.Worker.Tests.Probes` namespace so a discovery filter … can target or exclude them"
CLAIM_TYPE / path, term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/ contains AlarmsLiveSmokeTests.cs, AlarmClientWmProbeTests.cs, WnWrapConsumerProbeTests.cs; namespace confirmed at AlarmsLiveSmokeTests.cs:9
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 118131
CLAIM / Three probes: `AlarmsLiveSmokeTests`, `AlarmClientWmProbeTests`, `WnWrapConsumerProbeTests`, all `[Fact(Skip = "...")]` by default
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/AlarmsLiveSmokeTests.cs:47 (`[Fact(Skip = "Live dev-rig smoke test …")]`); all three classes confirmed in directory listing
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 139143
CLAIM / "`GalaxyRepositoryLiveTests` in `src/ZB.MOM.WW.MxGateway.IntegrationTests/Galaxy/` … skipped unless `MXGATEWAY_RUN_LIVE_GALAXY_TESTS=1`"
CLAIM_TYPE / path, config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/Galaxy/GalaxyRepositoryLiveTests.cs:6; src/ZB.MOM.WW.MxGateway.IntegrationTests/Galaxy/LiveGalaxyRepositoryFactAttribute.cs:9 (`"MXGATEWAY_RUN_LIVE_GALAXY_TESTS"`)
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 145148
CLAIM / GalaxyRepositoryLiveTests covers `TestConnectionAsync`, `GetLastDeployTimeAsync`, `GetHierarchyAsync`, `GetAttributesAsync`; hierarchy/attributes assert non-empty
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/Galaxy/GalaxyRepositoryLiveTests.cs:10 (TestConnection), :20 (GetLastDeployTime), :31 (GetHierarchy, Assert.NotEmpty), :50 (GetAttributes, Assert.NotEmpty)
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 154
CLAIM / Galaxy live tests run: `dotnet test src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj --filter FullyQualifiedName~GalaxyRepositoryLiveTests`
CLAIM_TYPE / command, path
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj exists; class `GalaxyRepositoryLiveTests` at Galaxy/GalaxyRepositoryLiveTests.cs:7
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 161
CLAIM / `MXGATEWAY_LIVE_GALAXY_CONN` default: `Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;`
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyRepositoryOptions.cs:1617 — exact string match; LiveGalaxyRepositoryFactAttribute.cs:32 falls back to this constant
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 168199
CLAIM / "`GalaxyFilterInputSafetyTests` in `src/ZB.MOM.WW.MxGateway.Tests/Galaxy/`" exercises GalaxyGlobMatcher and GalaxyHierarchyProjector with described adversarial inputs; GalaxyGlobMatcher applies a 100 ms regex timeout
CLAIM_TYPE / path, term, behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Tests/Galaxy/GalaxyFilterInputSafetyTests.cs:33 (class), :6091 (adversarial cases); src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyGlobMatcher.cs:69 (TimeSpan.FromMilliseconds(100))
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 172174
CLAIM / "re-frames the original 'Galaxy SQL injection' concern (Tests-002 in `code-reviews/Tests/findings.md`)"
CLAIM_TYPE / cross-ref
VERDICT / accurate
EVIDENCE / code-reviews/Tests/findings.md exists; src/ZB.MOM.WW.MxGateway.Tests/Galaxy/GalaxyFilterInputSafetyTests.cs:16 references `finding Tests-002`
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 174178
CLAIM / `GalaxyRepository` issues only four *constant* SQL statements: `HierarchySql`, `AttributesSql`, `SELECT 1`, `SELECT time_of_last_deploy FROM galaxy`
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyRepository.cs:26 (`SELECT 1`), :40 (`SELECT time_of_last_deploy FROM galaxy`), :117 (`HierarchySql`), :176 (`AttributesSql`)
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 203206
CLAIM / "`DashboardLdapLiveTests` in `src/ZB.MOM.WW.MxGateway.IntegrationTests/` … skipped unless `MXGATEWAY_RUN_LIVE_LDAP_TESTS=1`"
CLAIM_TYPE / path, config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/DashboardLdapLiveTests.cs:14; LiveLdapFactAttribute.cs:5 (`"MXGATEWAY_RUN_LIVE_LDAP_TESTS"`)
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 230
CLAIM / LDAP live tests run: `dotnet test src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj --filter FullyQualifiedName~DashboardLdapLiveTests`
CLAIM_TYPE / command
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/DashboardLdapLiveTests.cs:14 — class name matches filter
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 237243
CLAIM / `scripts/discover-testmachine-tags.ps1` queries TestMachine_001TestMachine_020 for attributes: `ProtectedValue`, `TestChangingInt`, `TestBoolArray`, `TestIntArray`, `TestDateTimeArray`, `TestStringArray`
CLAIM_TYPE / command
VERDICT / accurate
EVIDENCE / scripts/discover-testmachine-tags.ps1:511 — param `$Attributes` default matches exactly
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 370372
CLAIM / Cross-language smoke matrix filter: `dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter FullyQualifiedName~CrossLanguageSmokeMatrixTests`
CLAIM_TYPE / command
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Tests/Contracts/CrossLanguageSmokeMatrixTests.cs:5 — class name matches
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 374378
CLAIM / Parity fixture matrix filter: `dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter FullyQualifiedName~ParityFixtureMatrixTests`
CLAIM_TYPE / command
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Tests/Contracts/ParityFixtureMatrixTests.cs:6 — class name matches
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / 380390
CLAIM / Fake worker test filters: `FakeWorkerHarnessTests`, `SessionWorkerClientFactoryFakeWorkerTests`, `GatewayEndToEndFakeWorkerSmokeTests`, `WorkerClientTests` all in the main tests project; `WorkerPipeSessionTests` in Worker.Tests with `-p:Platform=x86`
CLAIM_TYPE / command
VERDICT / accurate
EVIDENCE / FakeWorkerHarnessTests.cs:9, SessionWorkerClientFactoryFakeWorkerTests.cs:13, GatewayEndToEndFakeWorkerSmokeTests.cs:19, WorkerClientTests.cs:11; src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs:17; Worker.Tests.csproj:6 (`<PlatformTarget>x86</PlatformTarget>`)
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ClientBehaviorFixtures.md / 811
CLAIM / "The fixture manifest is `clients/proto/fixtures/behavior/manifest.json`. `clients/proto/proto-inputs.json` references the fixture root through `behaviorFixtureRoot`"
CLAIM_TYPE / path
VERDICT / accurate
EVIDENCE / clients/proto/fixtures/behavior/manifest.json exists; clients/proto/proto-inputs.json:23 (`"behaviorFixtureRoot": "clients/proto/fixtures/behavior"`)
CODE_AREA / test.matrix
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ClientBehaviorFixtures.md / 3136
CLAIM / Command reply fixtures in `clients/proto/fixtures/behavior/command-replies/` parsing as `mxaccess_gateway.v1.MxCommandReply`
CLAIM_TYPE / path
VERDICT / accurate
EVIDENCE / clients/proto/fixtures/behavior/command-replies/register.ok.reply.json and write.mxaccess-failure.reply.json exist
CODE_AREA / test.matrix
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ClientBehaviorFixtures.md / 4860
CLAIM / Event stream fixtures in `clients/proto/fixtures/behavior/event-streams/`; event families: `OnDataChange`, `OnWriteComplete`, `OperationComplete`, `OnBufferedDataChange`
CLAIM_TYPE / path, term
VERDICT / accurate
EVIDENCE / clients/proto/fixtures/behavior/event-streams/session-event-stream.json exists
CODE_AREA / test.matrix
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ClientBehaviorFixtures.md / 9496
CLAIM / Validation: `powershell -ExecutionPolicy Bypass -File scripts/validate-client-behavior-fixtures.ps1`; "The script runs the focused C# contract tests that parse all protobuf JSON fixtures"
CLAIM_TYPE / command
VERDICT / accurate
EVIDENCE / scripts/validate-client-behavior-fixtures.ps1:1015 — runs `dotnet test` on `ZB.MOM.WW.MxGateway.Tests.csproj` with filter `ClientBehaviorFixtureTests`; src/ZB.MOM.WW.MxGateway.Tests/Contracts/ClientBehaviorFixtureTests.cs:11 class exists
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ParityFixtureMatrix.md / 811
CLAIM / "The matrix lives in `clients/proto/fixtures/parity/parity-fixture-matrix.json`. It references the local MXAccess capture set under `C:/Users/dohertj2/Desktop/mxaccess/captures`"
CLAIM_TYPE / path
VERDICT / accurate
EVIDENCE / clients/proto/fixtures/parity/parity-fixture-matrix.json exists; host-specific path is unverifiable from this repo
CODE_AREA / test.matrix
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ParityFixtureMatrix.md / 3740
CLAIM / "WriteSecured remains a documented gap because the current captures show `0x80004021` before MXAccess emits a value-bearing write body. `OperationComplete` and public `OnBufferedDataChange` batches also remain documented gaps"
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / clients/proto/fixtures/parity/parity-fixture-matrix.json:280291 (WriteSecured documented_gap), :369 (OperationComplete documented_gap), :382 (OnBufferedDataChange documented_gap)
CODE_AREA / test.matrix
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ParityFixtureMatrix.md / 91
CLAIM / Validation: `dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter FullyQualifiedName~ParityFixtureMatrixTests`
CLAIM_TYPE / command
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Tests/Contracts/ParityFixtureMatrixTests.cs:6 — class name matches
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / CrossLanguageSmokeMatrix.md / 89
CLAIM / "The matrix lives in `clients/proto/fixtures/smoke/cross-language-smoke-matrix.json`"
CLAIM_TYPE / path
VERDICT / accurate
EVIDENCE / clients/proto/fixtures/smoke/cross-language-smoke-matrix.json exists
CODE_AREA / test.matrix
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / CrossLanguageSmokeMatrix.md / 3839
CLAIM / Integration gate: `$env:MXGATEWAY_INTEGRATION = "1"`
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / clients/proto/fixtures/smoke/cross-language-smoke-matrix.json:6 (`"variable": "MXGATEWAY_INTEGRATION"`); src/ZB.MOM.WW.MxGateway.Tests/Contracts/CrossLanguageSmokeMatrixTests.cs:18 asserts this exact value
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / CrossLanguageSmokeMatrix.md / 4349
CLAIM / Shared inputs table: `MXGATEWAY_ENDPOINT` (default `localhost:5000`), `MXGATEWAY_API_KEY`, `MXGATEWAY_TEST_ITEM` (`TestChildObject.TestInt`), `MXGATEWAY_TEST_WRITE_VALUE`
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / clients/proto/fixtures/smoke/cross-language-smoke-matrix.json:1016 — all four variable names and the `localhost:5000` fallback match exactly; CrossLanguageSmokeMatrixTests.cs:2325 asserts these
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / CrossLanguageSmokeMatrix.md / 99101
CLAIM / Validation: `dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter FullyQualifiedName~CrossLanguageSmokeMatrixTests`
CLAIM_TYPE / command
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Tests/Contracts/CrossLanguageSmokeMatrixTests.cs:5 — class name matches
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / CrossLanguageSmokeMatrix.md / 5865
CLAIM / "the Rust CLI, which is pin-only and needs `--ca-file` or `--require-certificate-validation`"
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / clients/rust/crates/mxgw-cli/src/main.rs:426 (`ca_file: Option<PathBuf>`), :433 (`require_certificate_validation: bool`)
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / CrossLanguageSmokeMatrix.md / 64
CLAIM / "Python uses trust-on-first-use"
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / clients/python/tests/test_tls.py:114 (`test_default_tls_connects_via_tofu`) and :5 (doc string confirms TOFU default)
CODE_AREA / test.cmd
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ToolchainLinks.md / 61
CLAIM / "Go | 1.26.2 windows/amd64"
CLAIM_TYPE / version
VERDICT / unverifiable
EVIDENCE / clients/go/go.mod:3 specifies `go 1.26` (minimum requirement); ToolchainLinks records the installed binary version (1.26.2) which is a host-specific measurement not assertable from the repo. The go.mod minimum (1.26) is consistent with the stated installed version (1.26.2). Mark unverifiable — host install path.
CODE_AREA / test.toolchain
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ToolchainLinks.md / 8486
CLAIM / "rustc | 1.95.0" and "cargo | 1.95.0"
CLAIM_TYPE / version
VERDICT / unverifiable
EVIDENCE / clients/rust/Cargo.toml:4 — edition 2021; no `rust-version` field pins a minimum; host install version is not assertable from the repo.
CODE_AREA / test.toolchain
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ToolchainLinks.md / 107113
CLAIM / Python packages table: `grpcio==1.80.0`, `grpcio-tools==1.80.0`, `protobuf==6.33.6`, `pytest==9.0.3`, `pytest-asyncio==1.3.0`, `click==8.3.3`, `typer==0.25.0`
CLAIM_TYPE / version
VERDICT / unverifiable
EVIDENCE / clients/python/pyproject.toml:42 specifies `"pytest-asyncio>=1.3,<2"` (range constraint, not a pinned version); the `==` version pins in ToolchainLinks reflect the installed state of the host machine at time of writing, not a locked requirement file committed to the repo. Internally consistent for their stated purpose (documenting installed versions).
CODE_AREA / test.toolchain
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / ToolchainLinks.md / 123
CLAIM / "Gradle | 9.4.1 | `C:\Tools\gradle-9.4.1\bin\gradle.bat`"
CLAIM_TYPE / version
VERDICT / unverifiable
EVIDENCE / clients/java/settings.gradle:23 — no Gradle wrapper or toolchain version constraint committed. Host-specific install; unverifiable from the repo.
CODE_AREA / test.toolchain
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / LINES / GatewayTesting.md / (gap)
CLAIM / (undocumented) `IntegrationTestEnvironment.ResolveRepositoryRoot` uses a parent-walk that accepts either `.git` marker or `*.sln`/`*.slnx` files, with an optional `stopBoundary` parameter added for test isolation (IntegrationTests-025). No prose in GatewayTesting.md explains this behaviour or what to do when the walk fails.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/IntegrationTestEnvironment.cs:100157 — ResolveRepositoryRoot with stopBoundary, throws InvalidOperationException on failure with actionable message. Gap: the doc mentions only `MXGATEWAY_LIVE_MXACCESS_WORKER_EXE` as an escape hatch; the error message itself (line 155) explains what to do, but the docs omit the root-not-found failure mode.
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / Add a short note under "Live MXAccess Smoke" explaining that if the worker path resolver cannot locate the repository root it throws with a descriptive message; set `MXGATEWAY_LIVE_MXACCESS_WORKER_EXE` to bypass.
---
DOC / LINES / GatewayTesting.md / (gap)
CLAIM / (undocumented) `LiveGalaxyRepositoryFactAttribute` exposes `MXGATEWAY_LIVE_GALAXY_CONN` as its own constant (`ConnectionStringVariableName`) separate from `IntegrationTestEnvironment`. This is not the same pattern as the MXAccess variables (which are centralised in `IntegrationTestEnvironment`). A developer running from `CLAUDE.md`'s test table might look for this constant in the wrong class.
CLAIM_TYPE / config-key
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.IntegrationTests/Galaxy/LiveGalaxyRepositoryFactAttribute.cs:11 — `ConnectionStringVariableName` lives here, not in `IntegrationTestEnvironment`
CODE_AREA / test.envgate
SEVERITY / low
PROPOSED_FIX / flag only — the table in GatewayTesting.md correctly names the variable; the inconsistent home is a code-organisation note not a doc error.
---
## Summary
### Verdict counts
| Verdict | Count |
|---|---|
| accurate | 26 |
| wrong | 2 |
| stale | 0 |
| unverifiable | 4 |
| gap | 2 |
### Severity counts
| Severity | Count |
|---|---|
| high | 2 |
| medium | 0 |
| low | 30 |
### High-severity findings
- **GatewayTesting.md line 323 — wrong Gradle task name**: The prose says the e2e script installs the Java CLI via `gradle :mxgateway-cli:installDist`, but the script actually uses `:zb-mom-ww-mxgateway-cli:installDist` (matching the actual Gradle subproject name `zb-mom-ww-mxgateway-cli` in `clients/java/settings.gradle`). A developer copying the documented command would get a Gradle "task not found" error.
- **`clients/proto/fixtures/smoke/cross-language-smoke-matrix.json` — wrong Java Gradle task in all Java command entries**: Every Java command in the smoke fixture uses `gradle :mxgateway-cli:run` but the Gradle subproject is named `:zb-mom-ww-mxgateway-cli`. Running any Java smoke command from the fixture verbatim would fail. The unit tests that validate the matrix shape (`CrossLanguageSmokeMatrixTests`) do not check the literal Gradle task name, so this error passes CI undetected.
+341
View File
@@ -0,0 +1,341 @@
# Cluster 11 — Clients
Auditor: automated read-only audit
Date: 2026-06-03
Scope: clients/dotnet, clients/go, clients/java, clients/python, clients/rust — README.md, *ClientDesign.md; docs/ClientLibrariesDesign.md; docs/ClientPackaging.md
---
DOC: docs/ClientPackaging.md
LINES: 5152
CLAIM: Build and test commands reference `clients/dotnet/ZB.MOM.WW.MxGateway.Client.sln` (`.sln` extension)
CLAIM_TYPE: command
VERDICT: wrong
EVIDENCE: clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx:1 — only a `.slnx` file exists; no `.sln` file is present
CODE_AREA: client.dotnet
SEVERITY: high
PROPOSED_FIX: Replace `.sln` with `.slnx` in both `dotnet build` and `dotnet test` lines: `dotnet build clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx` / `dotnet test clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx --no-build`
---
DOC: docs/ClientPackaging.md
LINES: 159160
CLAIM: "The Python package is `mxaccess-gateway-client`. Generated modules live under `clients/python/src/mxgateway/generated`."
CLAIM_TYPE: config-key
VERDICT: wrong
EVIDENCE: clients/python/pyproject.toml:6 — `name = "zb-mom-ww-mxaccess-gateway-client"`; clients/python/src/zb_mom_ww_mxgateway/generated/ — actual generated path
CODE_AREA: client.python
SEVERITY: high
PROPOSED_FIX: Correct both: package name → `zb-mom-ww-mxaccess-gateway-client`; generated path → `clients/python/src/zb_mom_ww_mxgateway/generated`
---
DOC: docs/ClientPackaging.md
LINES: 187
CLAIM: `python -m mxgateway_cli version --json`
CLAIM_TYPE: command
VERDICT: wrong
EVIDENCE: clients/python/src/zb_mom_ww_mxgateway_cli/__main__.py — actual module is `zb_mom_ww_mxgateway_cli`; clients/python/pyproject.toml:48 — entry point `zb_mom_ww_mxgateway_cli.commands:main`
CODE_AREA: client.python
SEVERITY: high
PROPOSED_FIX: Replace with `python -m zb_mom_ww_mxgateway_cli version --json`
---
DOC: docs/ClientPackaging.md
LINES: 193194, 201, 217, 225227
CLAIM: Java workspace uses `mxgateway-client` and `mxgateway-cli` as subproject names; Gradle task paths use `:mxgateway-client:generateProto`, `:mxgateway-client:jar`, `:mxgateway-cli:installDist`, `:mxgateway-cli:run`
CLAIM_TYPE: command
VERDICT: wrong
EVIDENCE: clients/java/settings.gradle:2526 — `include 'zb-mom-ww-mxgateway-client'` and `include 'zb-mom-ww-mxgateway-cli'`; Gradle task paths are derived from subproject names
CODE_AREA: client.java
SEVERITY: high
PROPOSED_FIX: Replace all `:mxgateway-client:` references with `:zb-mom-ww-mxgateway-client:` and `:mxgateway-cli:` with `:zb-mom-ww-mxgateway-cli:` throughout ClientPackaging.md
---
DOC: clients/rust/README.md
LINES: 65
CLAIM: `cargo run -p mxgw-cli -- stream-alarms --session-id <session-id> --max-messages 1 --json`
CLAIM_TYPE: command
VERDICT: wrong
EVIDENCE: clients/rust/crates/mxgw-cli/src/main.rs:282295 — `StreamAlarms` struct has fields `filter_prefix`, `max_events`, `json`, `jsonl` (via `ConnectionArgs`); no `session_id`, flag is `--max-events` not `--max-messages`
CODE_AREA: client.rust
SEVERITY: high
PROPOSED_FIX: Replace with `cargo run -p mxgw-cli -- stream-alarms --max-events 1 --json` (no `--session-id`; change `--max-messages` to `--max-events`)
---
DOC: clients/rust/README.md
LINES: 66
CLAIM: `cargo run -p mxgw-cli -- acknowledge-alarm --session-id <session-id> --alarm-reference "\\Galaxy\Area001.Pump001.PumpFault" --json`
CLAIM_TYPE: command
VERDICT: wrong
EVIDENCE: clients/rust/crates/mxgw-cli/src/main.rs:298311 — `AcknowledgeAlarm` struct has fields `reference`, `comment`, `operator`, `json` (via `ConnectionArgs`); flag is `--reference` not `--alarm-reference`, and `session_id` is absent
CODE_AREA: client.rust
SEVERITY: high
PROPOSED_FIX: Replace with `cargo run -p mxgw-cli -- acknowledge-alarm --reference "\\Galaxy\Area001.Pump001.PumpFault" --json`
---
DOC: clients/go/README.md
LINES: 143
CLAIM: `import pb "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated/galaxy_repository/v1"`
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: clients/go/internal/generated/ — flat directory; all .pb.go files are `package generated`. Actual import path used in library code: `"gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated"` (clients/go/mxgateway/galaxy.go:11, types.go:3, session.go:13)
CODE_AREA: client.go
SEVERITY: high
PROPOSED_FIX: Replace the import path with `pb "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated"` (drop the `/galaxy_repository/v1` suffix)
---
DOC: docs/ClientLibrariesDesign.md
LINES: 410
CLAIM: Python generated code lives under `clients/python/src/mxgateway/generated`
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: clients/python/src/zb_mom_ww_mxgateway/generated/ — actual path; no `mxgateway` directory under `src/`
CODE_AREA: client.python
SEVERITY: high
PROPOSED_FIX: Replace with `clients/python/src/zb_mom_ww_mxgateway/generated`
---
DOC: clients/dotnet/DotnetClientDesign.md
LINES: 3536
CLAIM: Layout includes `ZB.MOM.WW.MxGateway.Client.IntegrationTests/` project
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: `ls clients/dotnet/` — directory does not exist; `.slnx` file (clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx:911) contains only three projects (Client, Cli, Tests)
CODE_AREA: client.dotnet
SEVERITY: medium
PROPOSED_FIX: Remove the `ZB.MOM.WW.MxGateway.Client.IntegrationTests/` entry from the layout section, or note it as "not yet created"
---
DOC: clients/python/PythonClientDesign.md
LINES: 215
CLAIM: "Publishable package name should be stable, for example: `mxaccess-gateway-client`"
CLAIM_TYPE: config-key
VERDICT: stale
EVIDENCE: clients/python/pyproject.toml:6 — actual name is `zb-mom-ww-mxaccess-gateway-client`; the "example" name was never adopted
CODE_AREA: client.python
SEVERITY: medium
PROPOSED_FIX: Update the example to `zb-mom-ww-mxaccess-gateway-client` to reflect the chosen name
---
DOC: clients/dotnet/DotnetClientDesign.md
LINES: 55
CLAIM: "`Grpc.Tools` for generation" listed as an expected package
CLAIM_TYPE: config-key
VERDICT: stale
EVIDENCE: clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj — no `Grpc.Tools` reference; the client uses a project reference to the shared contracts csproj for generated types. clients/dotnet/README.md:17 correctly notes this is "reserved for future use"
CODE_AREA: client.dotnet
SEVERITY: medium
PROPOSED_FIX: Remove `Grpc.Tools` from the "Expected packages" list or qualify it as "future, if client-local generation is adopted"
---
DOC: clients/go/GoClientDesign.md
LINES: 2830
CLAIM: `internal/generated/` contains only `mxaccess_gateway.pb.go` and `mxaccess_gateway_grpc.pb.go`
CLAIM_TYPE: path
VERDICT: stale
EVIDENCE: clients/go/internal/generated/ — 5 files present: `galaxy_repository.pb.go`, `galaxy_repository_grpc.pb.go`, `mxaccess_gateway.pb.go`, `mxaccess_gateway_grpc.pb.go`, `mxaccess_worker.pb.go`
CODE_AREA: client.go
SEVERITY: medium
PROPOSED_FIX: Add the missing galaxy_repository and mxaccess_worker generated files to the layout listing
---
DOC: docs/ClientPackaging.md
LINES: 116
CLAIM: "The Rust workspace builds the `mxgateway-client` library crate and the `mxgw` CLI crate."
CLAIM_TYPE: term
VERDICT: wrong
EVIDENCE: clients/rust/Cargo.toml:2 — `name = "zb-mom-ww-mxgateway-client"`; crates/mxgw-cli/Cargo.toml:4 — package name `mxgw-cli`, binary name `mxgw`
CODE_AREA: client.rust
SEVERITY: medium
PROPOSED_FIX: Change "mxgateway-client" to "zb-mom-ww-mxgateway-client" (the library crate name); "mxgw CLI crate" is acceptable since the binary is named `mxgw`
---
DOC: clients/rust/RustClientDesign.md
LINES: 278
CLAIM: `mxgw stream-alarms [--filter-prefix <prefix>] [--max-events <n>]`
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: clients/rust/crates/mxgw-cli/src/main.rs:282295 — `StreamAlarms` struct has `filter_prefix: Option<String>`, `max_events: usize`; test at line 2053 confirms `--max-events` flag name
CODE_AREA: client.rust
SEVERITY: low
PROPOSED_FIX: flag only — design doc is accurate; the discrepancy is in README.md (already captured above)
---
DOC: docs/ClientPackaging.md
LINES: (no entry)
CLAIM: (gap) `scripts/pack-clients.ps1` — the canonical multi-language pack and publish script — is not mentioned in ClientPackaging.md
CLAIM_TYPE: behavior-rule
VERDICT: gap
EVIDENCE: scripts/pack-clients.ps1:140 — packs all five clients into `dist/`, supports `-Publish` to upload to Gitea feeds; ClientPackaging.md has no reference to it
CODE_AREA: client.packaging
SEVERITY: medium
PROPOSED_FIX: Add a "Packing all clients at once" section to ClientPackaging.md pointing to `scripts/pack-clients.ps1` with the example invocations from the script's `.SYNOPSIS`
---
DOC: docs/ClientPackaging.md
LINES: (no entry for Python build method)
CLAIM: (gap) Python README and ClientPackaging.md document `pip wheel . --no-deps` as the build command; `scripts/pack-clients.ps1` uses `python -m build` (the PEP 517 standard tool). The dev dependency `build>=1.2,<2` is listed in pyproject.toml but the README's wheel command bypasses it.
CLAIM_TYPE: command
VERDICT: gap
EVIDENCE: clients/python/pyproject.toml:43 — `"build>=1.2,<2"` in dev deps; scripts/pack-clients.ps1:156 — `python -m build`; clients/python/README.md:43 — `pip wheel . --no-deps`
CODE_AREA: client.python
SEVERITY: low
PROPOSED_FIX: Note in Python README and ClientPackaging.md that `python -m build` (using the `build` package already in dev deps) is the canonical wheel-build method; `pip wheel` is an alternative
---
DOC: clients/java/README.md
LINES: 37
CLAIM: `gradle :zb-mom-ww-mxgateway-client:generateProto`
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: clients/java/settings.gradle:25 — `include 'zb-mom-ww-mxgateway-client'`; build.gradle:4 — `id 'com.google.protobuf'` applied to that subproject
CODE_AREA: client.java
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/java/README.md
LINES: 314
CLAIM: `implementation 'com.zb.mom.ww.mxgateway:zb-mom-ww-mxgateway-client:0.1.0'`
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: clients/java/build.gradle:1516 — `group = 'com.zb.mom.ww.mxgateway'`, `version = '0.1.0'`; subproject name `zb-mom-ww-mxgateway-client` is the artifact ID
CODE_AREA: client.java
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/rust/README.md
LINES: 83
CLAIM: "tonic 0.13.1 exposes no public hook to inject a custom certificate verifier"
CLAIM_TYPE: version
VERDICT: accurate
EVIDENCE: clients/rust/Cargo.toml:40 — `tonic = { version = "0.13.1", features = ["transport", "tls-ring"] }`
CODE_AREA: client.rust
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/dotnet/README.md
LINES: 2223
CLAIM: Build and test with `dotnet build clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx` / `dotnet test clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx --no-build`
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx:1 — file exists with `.slnx` extension
CODE_AREA: client.dotnet
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/dotnet/README.md
LINES: 331
CLAIM: `dotnet add package ZB.MOM.WW.MxGateway.Client --version 0.1.0`
CLAIM_TYPE: version
VERDICT: accurate
EVIDENCE: clients/dotnet/Directory.Build.props:14 — `<Version>0.1.0</Version>`; clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj:21 — `<PackageId>ZB.MOM.WW.MxGateway.Client</PackageId>`
CODE_AREA: client.dotnet
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/go/README.md
LINES: 292, 297
CLAIM: `go get gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go@v0.1.0`; import `"gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/mxgateway"`
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: clients/go/go.mod:1 — `module gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go`; `mxgateway/` subdirectory exists
CODE_AREA: client.go
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/go/README.md
LINES: 310312
CLAIM: `pwsh scripts/tag-go-module.ps1 -Version v0.1.1 -Push` creates an annotated tag `clients/go/v0.1.1`
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: scripts/tag-go-module.ps1:39 — `$tag = "clients/go/$Version"`; line 54 — `git tag -a $tag`; line 2529 — `-Version` and `-Push` parameters exist
CODE_AREA: client.go
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/python/README.md
LINES: 288290
CLAIM: `pip install --index-url https://gitea.dohertylan.com/api/packages/dohertj2/pypi/simple/ zb-mom-ww-mxaccess-gateway-client`
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: clients/python/pyproject.toml:6 — `name = "zb-mom-ww-mxaccess-gateway-client"` matches the pip install name
CODE_AREA: client.python
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/rust/README.md
LINES: 257274
CLAIM: Gitea Cargo registry at `sparse+https://gitea.dohertylan.com/api/packages/dohertj2/cargo/`, registry name `dohertj2-gitea`, crate `zb-mom-ww-mxgateway-client = { version = "0.1.0", registry = "dohertj2-gitea" }`
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: clients/rust/Cargo.toml:14 — `publish = ["dohertj2-gitea"]`; version = "0.1.0"; registry name matches
CODE_AREA: client.rust
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/java/README.md
LINES: 297299
CLAIM: Maven feed at `https://gitea.dohertylan.com/api/packages/dohertj2/maven`; publish via `gradle :zb-mom-ww-mxgateway-client:publish`
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: clients/java/build.gradle:72 — `url = 'https://gitea.dohertylan.com/api/packages/dohertj2/maven'`; `maven-publish` plugin applied to `zb-mom-ww-mxgateway-client`
CODE_AREA: client.java
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/go/README.md
LINES: 3940
CLAIM: Build and test with `go test ./...` / `go build ./...` / `go vet ./...` from `clients/go`
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: clients/go/go.mod:1 — module root at clients/go; standard Go toolchain commands apply
CODE_AREA: client.go
SEVERITY: low
PROPOSED_FIX: flag only
---
DOC: clients/java/README.md
LINES: 265267
CLAIM: Build and test with `gradle test` from `clients/java`
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: clients/java/settings.gradle:2526 — `include 'zb-mom-ww-mxgateway-client'` and `include 'zb-mom-ww-mxgateway-cli'`; root `build.gradle` applies `useJUnitPlatform()` to all java subprojects
CODE_AREA: client.java
SEVERITY: low
PROPOSED_FIX: flag only
+226
View File
@@ -0,0 +1,226 @@
# Cluster 12 — Style Guides
Docs audited: `StyleGuide.md`, `REVIEW-PROCESS.md`, `docs/style-guides/CSharpStyleGuide.md`,
`docs/style-guides/GoStyleGuide.md`, `docs/style-guides/JavaStyleGuide.md`,
`docs/style-guides/ProtobufStyleGuide.md`, `docs/style-guides/PythonStyleGuide.md`,
`docs/style-guides/RustStyleGuide.md`.
---
DOC: StyleGuide.md
LINES: 3
CLAIM: "This guide defines writing conventions and formatting rules for all ScadaBridge documentation."
CLAIM_TYPE: term
VERDICT: wrong
EVIDENCE: StyleGuide.md:3; no file in the repo uses "ScadaBridge" as the project name — the project is `mxaccessgw` / MXAccess Gateway throughout every other file.
CODE_AREA: style.docs
SEVERITY: high
PROPOSED_FIX: flag only — replace "ScadaBridge" with "MXAccess Gateway" / `mxaccessgw`.
---
DOC: StyleGuide.md
LINES: 12, 15, 76, 100105, 144, 147, 154155, 161, 215217, 226227, 246248, 263
CLAIM: Examples throughout use `ScadaGatewayActor`, `ScadaClientActor`, `TemplateInstanceActor`, `ReceiveActor`, `IRequiredActor<T>`, `IActorRef`, configuration key `ScadaBridge:Timeout`, file path `src/Infrastructure/Akka/Actors/`, and documentation paths `../Akka/Actors.md`, `../Akka/HealthChecks.md`, `../Configuration/Akka.md`.
CLAIM_TYPE: cross-ref
VERDICT: wrong
EVIDENCE: None of these types, paths, or configuration keys exist anywhere in the `mxaccessgw` repository. The "Akka" actor framework is not used in this project. `ls /Users/dohertj2/Desktop/mxaccessgateway/` shows no `Akka/` directory. The referenced paths (`../Akka/Actors.md`, `./Configuration.md`, `./Patterns.md`) are all dead links.
CODE_AREA: style.docs
SEVERITY: high
PROPOSED_FIX: flag only — the entire examples section was copied from a different (Akka-based) project. All example types, paths, and configuration keys must be replaced with MXAccess Gateway equivalents.
---
DOC: StyleGuide.md
LINES: 90
CLAIM: "Supported languages: `csharp`, `json`, `bash`, `xml`, `sql`, `yaml`, `html`, `css`, `javascript`"
CLAIM_TYPE: term
VERDICT: stale
EVIDENCE: Corpus-wide count of code-block language identifiers in `docs/` (via grep): `powershell` (42 uses), `text` (48 uses), `rust` (12), `python` (12), `go` (7), `proto`/`protobuf` (6) are all actively used but not listed. `yaml` and `javascript` appear zero times. The list is both under-inclusive and includes unused entries.
CODE_AREA: style.docs
SEVERITY: low
PROPOSED_FIX: flag only — update the list to reflect languages actually used in the docs corpus; at minimum add `powershell`, `text`, `rust`, `python`, `go`, `proto`; optionally remove `yaml` and `javascript`.
---
DOC: docs/style-guides/JavaStyleGuide.md
LINES: 25
CLAIM: "Use lowercase package names under `com.dohertylan.mxgateway`."
CLAIM_TYPE: config-key
VERDICT: wrong
EVIDENCE: Every handwritten Java source file in `clients/java/` uses the package root `com.zb.mom.ww.mxgateway` (confirmed by inspecting `clients/java/zb-mom-ww-mxgateway-client/src/main/java/com/zb/mom/ww/mxgateway/client/MxGatewayClient.java` and sibling files). No file uses `com.dohertylan`.
CODE_AREA: style.java
SEVERITY: high
PROPOSED_FIX: flag only — change the prescribed package root to `com.zb.mom.ww.mxgateway` to match the actual codebase.
---
DOC: docs/style-guides/PythonStyleGuide.md
LINES: 2729
CLAIM: "Put library code under `src/mxgateway/`. Put CLI entry points under `src/mxgateway_cli/`. Keep generated protobuf modules under a clearly named `generated` package."
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: Actual package directories are `clients/python/src/zb_mom_ww_mxgateway/` and `clients/python/src/zb_mom_ww_mxgateway_cli/` (confirmed by `ls clients/python/src/`). The short names `mxgateway` and `mxgateway_cli` do not exist on disk. The generated package is correctly at `src/zb_mom_ww_mxgateway/generated/` (matches the rule in spirit, but the parent path is wrong).
CODE_AREA: style.python
SEVERITY: medium
PROPOSED_FIX: flag only — update the stated package paths to `src/zb_mom_ww_mxgateway/` and `src/zb_mom_ww_mxgateway_cli/`.
---
DOC: docs/style-guides/GoStyleGuide.md
LINES: 68
CLAIM: "Keep integration tests behind `MXGATEWAY_INTEGRATION=1` or build tags."
CLAIM_TYPE: config-key
VERDICT: unverifiable
EVIDENCE: No Go source file in `clients/go/` references `MXGATEWAY_INTEGRATION`; no build tag gating for integration was found. The existing Go tests (`clients/go/mxgateway/*_test.go`) all use in-process fakes via `bufconn`, so no live integration tests appear to exist yet. The rule is prescriptive (correct direction) but the env-var name cannot be confirmed against practice because live tests are absent.
CODE_AREA: style.go
SEVERITY: low
PROPOSED_FIX: flag only.
---
DOC: docs/style-guides/PythonStyleGuide.md
LINES: 68
CLAIM: "Keep live integration tests behind `MXGATEWAY_INTEGRATION=1`."
CLAIM_TYPE: config-key
VERDICT: stale
EVIDENCE: The only opt-in env var actually used in `clients/python/tests/` is `MXGATEWAY_RUN_TLS_TESTS=1` (confirmed in `tests/test_tls.py:36`). There is no usage of `MXGATEWAY_INTEGRATION=1` in the Python test tree. The same inconsistency holds for Java (`clients/java/`) and Rust (`clients/rust/`), where no opt-in env-var pattern for live tests was found at all.
CODE_AREA: style.python
SEVERITY: low
PROPOSED_FIX: flag only — standardize the prescribed env var with what the test code actually uses; consider aligning Go, Java, Rust, Python on a single variable name.
---
DOC: docs/style-guides/JavaStyleGuide.md
LINES: 65
CLAIM: "Keep live gateway tests behind `MXGATEWAY_INTEGRATION=1` and JUnit assumptions."
CLAIM_TYPE: config-key
VERDICT: unverifiable
EVIDENCE: No Java test file in `clients/java/` references `MXGATEWAY_INTEGRATION`. The Java test classes (`MxGatewayClientSessionTests.java`, etc.) use in-process gRPC servers. No live-gateway test gating was found in the Java client tree.
CODE_AREA: style.java
SEVERITY: low
PROPOSED_FIX: flag only.
---
DOC: docs/style-guides/RustStyleGuide.md
LINES: 65
CLAIM: "Keep live gateway tests behind `MXGATEWAY_INTEGRATION=1`."
CLAIM_TYPE: config-key
VERDICT: unverifiable
EVIDENCE: No Rust source file in `clients/rust/` references `MXGATEWAY_INTEGRATION`. The existing Rust tests (`tests/client_behavior.rs`) use a fake `tonic` in-process server. No live-gateway gating was found.
CODE_AREA: style.rust
SEVERITY: low
PROPOSED_FIX: flag only.
---
DOC: REVIEW-PROCESS.md
LINES: 14
CLAIM: "For a `src/` project, `<Module>` is the project name with the `ZB.MOM.WW.MxGateway.` prefix stripped — `src/ZB.MOM.WW.MxGateway.Server` is reviewed in `code-reviews/Server/`."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: `ls /Users/dohertj2/Desktop/mxaccessgateway/src/` confirms project names `ZB.MOM.WW.MxGateway.{Server,Worker,Contracts,Tests,Worker.Tests,IntegrationTests}`; `ls /Users/dohertj2/Desktop/mxaccessgateway/code-reviews/` shows folders `Server`, `Worker`, `Tests`, `Worker.Tests`, `IntegrationTests`, `Contracts`, `Client.*`. Mapping is accurate.
CODE_AREA: style.crossref
SEVERITY: low
PROPOSED_FIX: accurate — no action needed.
---
DOC: REVIEW-PROCESS.md
LINES: 6869
CLAIM: Test projects are `src/ZB.MOM.WW.MxGateway.Tests`, `src/ZB.MOM.WW.MxGateway.Worker.Tests`, `src/ZB.MOM.WW.MxGateway.IntegrationTests`.
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: All three directories exist under `src/` (confirmed by `ls /Users/dohertj2/Desktop/mxaccessgateway/src/`).
CODE_AREA: style.crossref
SEVERITY: low
PROPOSED_FIX: accurate — no action needed.
---
DOC: REVIEW-PROCESS.md
LINES: 7778
CLAIM: Entry format is in `[code-reviews/_template/findings.md](code-reviews/_template/findings.md)`.
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: File exists at `/Users/dohertj2/Desktop/mxaccessgateway/code-reviews/_template/findings.md`.
CODE_AREA: style.crossref
SEVERITY: low
PROPOSED_FIX: accurate — no action needed.
---
DOC: REVIEW-PROCESS.md
LINES: 120127
CLAIM: "`python code-reviews/regen-readme.py`" regenerates the README; `regen-readme.py --check` validates it; `scripts/check-code-reviews-readme.ps1` is the CI hook.
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: `code-reviews/regen-readme.py` exists; `scripts/check-code-reviews-readme.ps1` exists; the `code-reviews/README.md` header confirms generation ("GENERATED FILE — do not edit by hand. Regenerate with: `python code-reviews/regen-readme.py`").
CODE_AREA: style.crossref
SEVERITY: low
PROPOSED_FIX: accurate — no action needed.
---
DOC: docs/style-guides/CSharpStyleGuide.md
LINES: 11 ("Prefer file-scoped namespaces"), 12 ("Prefer `sealed` classes unless inheritance is required")
CLAIM: These are the established conventions in the codebase.
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: Spot-checked `src/` C# files: all non-generated namespace declarations use the file-scoped `namespace X.Y;` form (grep of block-scoped `namespace` without semicolon returns only generated/obj files). 249 `public sealed class` declarations found vs. exactly 1 bare `public class` (a test fixture `GatewayLogRedactorSeamTests`). Convention is well-followed.
CODE_AREA: style.csharp
SEVERITY: low
PROPOSED_FIX: accurate — no action needed.
---
DOC: docs/style-guides/GoStyleGuide.md
LINES: 13
CLAIM: "Keep generated protobuf code under `internal/generated` unless the public API intentionally exposes it."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: `clients/go/internal/generated/` exists and contains generated code (confirmed by `find /Users/dohertj2/Desktop/mxaccessgateway/clients/go -type d`).
CODE_AREA: style.go
SEVERITY: low
PROPOSED_FIX: accurate — no action needed.
---
DOC: docs/style-guides/RustStyleGuide.md
LINES: 49, 42
CLAIM: "Use `thiserror` for library error enums." / "Use `async` APIs with `tokio` for network operations."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: `clients/rust/Cargo.toml` lists `thiserror = "2.0.17"` and `tokio = { version = "1.48.0", ... }` as workspace dependencies.
CODE_AREA: style.rust
SEVERITY: low
PROPOSED_FIX: accurate — no action needed.
---
## Summary
### Counts by verdict
| Verdict | Count |
|----------------|-------|
| wrong | 3 |
| stale | 2 |
| unverifiable | 3 |
| accurate | 6 |
| **Total** | **14** |
### Counts by severity
| Severity | Count |
|----------|-------|
| high | 3 |
| medium | 1 |
| low | 10 |
### High-severity findings
- **StyleGuide.md line 3 (wrong/high):** The guide's opening sentence names "ScadaBridge" as the project — a stale copy-paste from a different codebase. The term does not appear anywhere else in the repo.
- **StyleGuide.md lines 12263 (wrong/high):** All illustrative examples (types, file paths, doc cross-references, configuration keys) are from an Akka-actor project (`ScadaGatewayActor`, `ReceiveActor`, `IActorRef`, `../Akka/Actors.md`, `ScadaBridge:Timeout`, etc.). None exist in the MXAccess Gateway codebase. Every linked path is a dead reference.
- **JavaStyleGuide.md line 25 (wrong/high):** Prescribed Java package root `com.dohertylan.mxgateway` does not match the actual code, which universally uses `com.zb.mom.ww.mxgateway`.
+265
View File
@@ -0,0 +1,265 @@
# Cluster 13 — Design-history/Plans
Audited docs:
- `docs/ImplementationPlanIndex.md`
- `docs/ImplementationPlanGateway.md`
- `docs/ImplementationPlanClients.md`
- `docs/ImplementationPlanMxAccessWorker.md`
- `docs/plans/2026-05-28-client-walker-design.md`
- `docs/plans/2026-05-28-client-walker-implementation.md`
- `docs/plans/2026-05-28-lazy-browse-design.md`
- `docs/plans/2026-05-28-lazy-browse-implementation.md`
- `docs/plans/2026-06-01-gateway-cert-autogen-design.md`
- `docs/plans/2026-06-01-gateway-cert-autogen-implementation.md`
---
DOC: docs/plans/2026-05-28-lazy-browse-implementation.md
LINES: 1059
CLAIM: `Run: dotnet build src/MxGateway.sln`
CLAIM_TYPE: path
VERDICT: stale
EVIDENCE: `git log --diff-filter=A -- src/MxGateway.sln` shows the file existed in commit a45f439 but was later renamed; actual file is `src/ZB.MOM.WW.MxGateway.slnx`
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: flag only — historical record; the build command step in this plan is a point-in-time artefact. If CLAUDE.md's own build table still says `src/MxGateway.sln` (it does — CLAUDE.md line 22), that living doc should be updated to `src/ZB.MOM.WW.MxGateway.slnx`.
---
DOC: docs/plans/2026-05-28-lazy-browse-implementation.md
LINES: 885, 888, 1069
CLAIM: `clients/dotnet/MxGateway.Client.sln`
CLAIM_TYPE: path
VERDICT: stale
EVIDENCE: Actual solution file is `clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx` (confirmed by `ls`). No `.sln` variant exists in that directory. Note: CLAUDE.md line 57 and 93 carry the same stale name, so the plan merely repeated the living doc's error.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: flag only — historical record. PROPOSED_FIX targets CLAUDE.md lines 57 and 93: replace `clients/dotnet/MxGateway.Client.sln` with `clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx`.
---
DOC: docs/plans/2026-06-01-gateway-cert-autogen-implementation.md
LINES: 872, 1196
CLAIM: `clients/dotnet/MxGateway.Client.sln`
CLAIM_TYPE: path
VERDICT: stale
EVIDENCE: Same issue as above — actual file is `clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx`.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: flag only — historical record. Living-doc fix is the same CLAUDE.md correction noted above.
---
DOC: docs/plans/2026-05-28-lazy-browse-implementation.md
LINES: 1315
CLAIM: "The design's Section 2 said stale page tokens return `FailedPrecondition`."
CLAIM_TYPE: behavior-rule
VERDICT: wrong
EVIDENCE: `docs/plans/2026-05-28-lazy-browse-design.md` line 105 and 229 both say `InvalidArgument` for stale page tokens — `FailedPrecondition` appears nowhere in that document. The claim is internally inconsistent within the plan set: the design never contained `FailedPrecondition`.
CODE_AREA: history.crossref
SEVERITY: medium
PROPOSED_FIX: flag only — the implementation plan is a historical record. The deviation note is inaccurate as written (the design never said `FailedPrecondition`), but the implemented behavior (`InvalidArgument`) is correct and matches the design. No living doc needs correction because Task 10 of that plan correctly reconciled the design doc to say `InvalidArgument`, which it already did.
---
DOC: docs/plans/2026-05-28-client-walker-implementation.md
LINES: 12191221
CLAIM: "`clients/go/mxgateway/galaxy.go:150``DiscoverHierarchy` paging idiom. `clients/go/mxgateway/galaxy_test.go:96``TestGalaxyDiscoverHierarchyReturnsObjects`. `clients/go/mxgateway/galaxy_test.go:370``fakeGalaxyServer` struct."
CLAIM_TYPE: path
VERDICT: stale
EVIDENCE: As-built: `DiscoverHierarchy` is at `galaxy.go:165` (grep confirms); `TestGalaxyDiscoverHierarchyReturnsObjects` is at `galaxy_test.go:99`; `fakeGalaxyServer` struct definition is at `galaxy_test.go:414`. The plan was written before additional code landed. These are implementer navigation hints, not design assertions.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: flag only — stale line numbers in an implementation plan's "read first" guidance. No living doc is affected.
---
DOC: docs/plans/2026-05-28-client-walker-implementation.md
LINES: 580585
CLAIM: "Python: `clients/python/tests/test_galaxy.py` — see `FakeGalaxyStub` (line 271), `FakeUnary` (286), `FakeStream` (304)"
CLAIM_TYPE: path
VERDICT: stale
EVIDENCE: As-built: `class FakeGalaxyStub` is at line 539, `class FakeUnary` at 556, `class FakeStream` at 580. The plan was written before additional tests were added to the file.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: flag only — stale navigation hints in an implementation plan. No living doc is affected.
---
DOC: docs/plans/2026-05-28-client-walker-implementation.md
LINES: 937941
CLAIM: "Rust: `clients/rust/src/galaxy.rs` lines 145-186 — `discover_hierarchy` for paging idiom. `clients/rust/src/galaxy.rs` lines 265+ as a test module (`#[cfg(test)] mod tests`)."
CLAIM_TYPE: path
VERDICT: stale
EVIDENCE: As-built: `discover_hierarchy` is at `galaxy.rs:254` (not 145-186); `#[cfg(test)] mod tests` begins at `galaxy.rs:421` (not 265). The file grew between plan authoring and implementation completion.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: flag only — stale navigation hints in an implementation plan. No living doc is affected.
---
DOC: docs/ImplementationPlanGateway.md
LINES: 2538
CLAIM: Solution and project names use prefix `ZB.MOM.WW.MxGateway.*` (e.g. `src/ZB.MOM.WW.MxGateway.slnx`, `src/ZB.MOM.WW.MxGateway.Server`).
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: `ls src/` confirms `ZB.MOM.WW.MxGateway.slnx`, `ZB.MOM.WW.MxGateway.Server`, etc. all exist.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/ImplementationPlanGateway.md
LINES: 519530
CLAIM: Related Documentation links to `./Sessions.md`, `./Grpc.md`, `./Authentication.md`, `./Authorization.md`, `./GatewayDashboardDesign.md`, `./GatewayConfiguration.md`, `./GatewayTesting.md`, `./Metrics.md`, `./Diagnostics.md`
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: All nine files confirmed present under `docs/`.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/ImplementationPlanClients.md
LINES: 514
CLAIM: Primary design files: `docs/ClientLibrariesDesign.md`, `clients/dotnet/DotnetClientDesign.md`, `clients/go/GoClientDesign.md`, `clients/rust/RustClientDesign.md`, `clients/python/PythonClientDesign.md`, `clients/java/JavaClientDesign.md`
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: All six files confirmed present.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/ImplementationPlanClients.md
LINES: 389396
CLAIM: Related Documentation includes `./ClientProtoGeneration.md`, `./ClientBehaviorFixtures.md`, `./ClientPackaging.md`, `./CrossLanguageSmokeMatrix.md`
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: All four files confirmed present under `docs/`.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/ImplementationPlanMxAccessWorker.md
LINES: 457466
CLAIM: Related Documentation links: `./WorkerBootstrap.md`, `./WorkerSta.md`, `./WorkerConversion.md`, `./WorkerFrameProtocol.md`, `./WorkerProcessLauncher.md`, `./ParityFixtureMatrix.md`
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: All six files confirmed present under `docs/`.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/plans/2026-05-28-client-walker-design.md
LINES: 68
CLAIM: Python source file path `clients/python/src/zb_mom_ww_mxgateway/galaxy.py`
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: `ls clients/python/src/zb_mom_ww_mxgateway/galaxy.py` confirms existence.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/plans/2026-05-28-client-walker-design.md
LINES: 222223
CLAIM: "commit `0d6193c`" added the "Browsing lazily" README sections
CLAIM_TYPE: cross-ref
VERDICT: accurate
EVIDENCE: `git show 0d6193c` confirms: subject "docs: note BrowseChildren in gateway overview and client READMEs"; modifies all five client READMEs and gateway.md. `grep "Browsing lazily" clients/*/README.md` confirms sections are present.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/plans/2026-05-28-lazy-browse-design.md
LINES: 105111
CLAIM: Stale `page_token``InvalidArgument`; filter change between pages → `InvalidArgument`.
CLAIM_TYPE: behavior-rule
VERDICT: accurate-as-record
EVIDENCE: `docs/plans/2026-05-28-lazy-browse-implementation.md` implements `StatusCode.InvalidArgument` for both conditions (lines 529530, 590, 616). Design and implementation are consistent.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/plans/2026-06-01-gateway-cert-autogen-design.md
LINES: 96
CLAIM: Java client uses "grpc-netty-shaded 1.76.0" and `InsecureTrustManagerFactory`
CLAIM_TYPE: version
VERDICT: accurate
EVIDENCE: `clients/java/settings.gradle` sets `grpcVersion = '1.76.0'`; `clients/java/zb-mom-ww-mxgateway-client/build.gradle` references `io.grpc:grpc-netty-shaded:${grpcVersion}`.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/plans/2026-06-01-gateway-cert-autogen-design.md
LINES: 98
CLAIM: Rust client uses "tonic 0.13.1 + rustls (`tls-ring`)"
CLAIM_TYPE: version
VERDICT: accurate
EVIDENCE: `clients/rust/Cargo.toml` line 40: `tonic = { version = "0.13.1", features = ["transport", "tls-ring"] }`.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/plans/2026-06-01-gateway-cert-autogen-design.md
LINES: 129130
CLAIM: Documentation task calls for updating "each client README + `*ClientDesign.md`" (`clients/rust/RustClientDesign.md`, `clients/python/PythonClientDesign.md`, `clients/java/JavaClientDesign.md`, `clients/go/GoClientDesign.md`)
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: All four `*ClientDesign.md` files confirmed present.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/ImplementationPlanGateway.md
LINES: 457459
CLAIM: "`MxGateway:Dashboard:AllowAnonymousLocalhost` loopback bypass (defaults to true for local development)"
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: `docs/GatewayConfiguration.md` line 149 confirms default `true`; CLAUDE.md line 119 notes the same behavior without specifying the default, but the Gateway plan's default matches the shipped configuration.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none
---
DOC: docs/plans/2026-05-28-client-walker-implementation.md
LINES: 940941
CLAIM: "`clients/rust/tests/client_behavior.rs` (add tests; extend the `FakeGalaxy` impl from line 265+ to record BrowseChildren calls)"
CLAIM_TYPE: path
VERDICT: stale
EVIDENCE: `ls clients/rust/tests/` confirms `client_behavior.rs` does exist; however the `FakeGalaxy` implementation is in `clients/rust/src/galaxy.rs` (at `#[cfg(test)] mod tests`, line 421), not in `client_behavior.rs`. The "line 265+" reference is also stale (actual line is 421). The plan conflates the two files.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: flag only — implementation-plan navigation hint that was partially wrong at time of writing (or grew inaccurate as code landed). No living doc is affected.
---
DOC: docs/plans/2026-05-28-client-walker-design.md
LINES: 89
CLAIM: Python source file is `clients/python/src/zb_mom_ww_mxgateway/galaxy.py`; the class is `LazyBrowseNode`.
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: `grep -n "class LazyBrowseNode" clients/python/src/zb_mom_ww_mxgateway/galaxy.py` returns line 289.
CODE_AREA: history.crossref
SEVERITY: low
PROPOSED_FIX: none