bc55396334
IntegrationTests-001: documented the live Galaxy Repository test suite and its MXGATEWAY_RUN_LIVE_GALAXY_TESTS / MXGATEWAY_LIVE_GALAXY_CONN gating in docs/GatewayTesting.md. IntegrationTests-002: documented the live LDAP test suite in docs/GatewayTesting.md and added a concrete "Provisioning the GwAdmin group" step to glauth.md so DashboardLdapLiveTests' GwAdmin-membership assumption is reproducible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Reviews
Cross-module code review index for the mxaccessgw codebase. The review process is defined in ../REVIEW-PROCESS.md.
Each module's findings.md is the source of truth; this file is generated from them by regen-readme.py and must not be edited by hand.
Module status
| Module | Reviewer | Date | Commit | Status | Open | Total |
|---|---|---|---|---|---|---|
| Client.Dotnet | Claude Code | 2026-05-18 | 3cc53a8 |
Reviewed | 8 | 8 |
| Client.Go | Claude Code | 2026-05-18 | 3cc53a8 |
Reviewed | 10 | 10 |
| Client.Java | Claude Code | 2026-05-18 | 3cc53a8 |
Reviewed | 12 | 12 |
| Client.Python | Claude Code | 2026-05-18 | 3cc53a8 |
Reviewed | 12 | 12 |
| Client.Rust | Claude Code | 2026-05-18 | 3cc53a8 |
Reviewed | 0 | 12 |
| Contracts | Claude Code | 2026-05-18 | 6c64030 |
Reviewed | 8 | 8 |
| IntegrationTests | Claude Code | 2026-05-18 | 6c64030 |
Reviewed | 10 | 10 |
| Server | Claude Code | 2026-05-18 | 6c64030 |
Reviewed | 12 | 14 |
| Tests | Claude Code | 2026-05-18 | 6c64030 |
Reviewed | 12 | 12 |
| Worker | Claude Code | 2026-05-18 | 6c64030 |
Reviewed | 15 | 15 |
| Worker.Tests | Claude Code | 2026-05-18 | 6c64030 |
Reviewed | 15 | 15 |
Pending findings
Findings with status Open or In Progress, ordered by severity.
| ID | Severity | Category | Location | Description |
|---|---|---|---|---|
| Client.Go-001 | High | Correctness & logic bugs | clients/go/mxgateway/errors.go:88-93, clients/go/mxgateway/errors.go:117-128 |
MxAccessError.Unwrap returns e.Command directly. EnsureMxAccessSuccess constructs &MxAccessError{Reply: reply} with Command left nil (the HRESULT / failing-MxStatusProxy path). When Command is a nil *CommandError, `Unwrap()… |
| IntegrationTests-001 | High | Design-document adherence | src/MxGateway.IntegrationTests/Galaxy/LiveGalaxyRepositoryFactAttribute.cs:7, src/MxGateway.IntegrationTests/Galaxy/GalaxyRepositoryLiveTests.cs |
The Galaxy Repository live test suite and its gating env var MXGATEWAY_RUN_LIVE_GALAXY_TESTS (plus connection-string override MXGATEWAY_LIVE_GALAXY_CONN) are completely absent from docs/GatewayTesting.md. CLAUDE.md mandates updating… |
| IntegrationTests-002 | High | Design-document adherence | src/MxGateway.IntegrationTests/DashboardLdapLiveTests.cs:13, src/MxGateway.Server/Configuration/LdapOptions.cs:27 |
DashboardLdapLiveTests builds the authenticator with new GatewayOptions(), so it relies on LdapOptions.RequiredGroup defaulting to GwAdmin and asserts the admin user is a member of a GwAdmin LDAP group. glauth.md does not lis… |
| Tests-001 | High | Testing coverage | src/MxGateway.Tests/Gateway/Grpc/MxAccessGatewayServiceTests.cs:483-489 |
FakeSessionManager.TryGetSession unconditionally returns true and synthesizes a session for any id. As a result, Invoke_WhenSessionMissing_ThrowsNotFound (line 52) only passes because InvokeException is pre-seeded — it does not ver… |
| Tests-002 | High | Security | src/MxGateway.Tests/Gateway/Grpc/GalaxyRepositoryGrpcServiceTests.cs:198-210 |
The Galaxy Repository RPCs browse a SQL Server database (ZB). Every test injects a StubGalaxyHierarchyCache, so actual SQL query construction, parameterization, and filter/glob translation are never exercised. No test demonstrates that… |
| Worker-001 | High | Concurrency & thread safety | src/MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs:204-207 |
When constructed with pollIntervalMilliseconds > 0, Subscribe starts a System.Threading.Timer whose OnPoll callback runs PollOnce() — which calls wwAlarmConsumerClass.GetXmlCurrentAlarms2 — on a thread-pool thread. The wnwrap C… |
| Worker-002 | High | Correctness & logic bugs | src/MxGateway.Worker/Ipc/WorkerPipeSession.cs:545-549 |
RunHeartbeatLoopAsync calls await Task.Delay(_sessionOptions.HeartbeatInterval, ...) before sending the first heartbeat. The gateway therefore receives no heartbeat for the first full interval (default 5s) after the worker reaches `Rea… |
| Worker-003 | High | Correctness & logic bugs | src/MxGateway.Worker/Ipc/WorkerPipeSession.cs:399-403, :416-419 |
ProcessCommandAsync checks _state after DispatchAsync completes and silently returns without writing a WorkerCommandReply (or fault) when _state is not Ready/ExecutingCommand. _state is a plain field mutated from multiple… |
| Worker.Tests-001 | High | Testing coverage | src/MxGateway.Worker.Tests/Sta/ (no StaMessagePumpTests.cs) |
StaMessagePump — whose entire reason for existing is pumping Windows messages so MXAccess COM event sink calls deliver onto the STA — has no direct unit test. WaitForWorkOrMessages (timeout conversion, the MsgWaitForMultipleObjectsEx… |
| Worker.Tests-002 | High | Testing coverage | src/MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs, src/MxGateway.Worker.Tests/MxAccess/MxAccessEventMapperTests.cs |
No test verifies that a COM event raised on the STA thread is converted to protobuf and lands in the MxAccessEventQueue. MxAccessEventMapperTests exercises the mapper directly with hand-built fakes, and AlarmDispatcherTests covers th… |
| Client.Dotnet-001 | Medium | Error handling & resilience | clients/dotnet/MxGateway.Client/GrpcMxGatewayClientTransport.cs:190-199, clients/dotnet/MxGateway.Client/GrpcGalaxyRepositoryClientTransport.cs:131-140 |
MapRpcException only produces typed exceptions for Unauthenticated and PermissionDenied. Every other gRPC status — NotFound, InvalidArgument, ResourceExhausted, FailedPrecondition, Unavailable, Internal — collapses into t… |
| Client.Dotnet-002 | Medium | Testing coverage | clients/dotnet/MxGateway.Client.Tests/FakeGatewayTransport.cs:145-148, clients/dotnet/MxGateway.Client.Tests/MxGatewayClientSessionTests.cs:236-256 |
The retry predicate MxGatewayClientRetryPolicy.IsTransientGrpcFailure handles two shapes: a raw RpcException and an MxGatewayException { InnerException: RpcException }. In production the transport always maps RpcException → `MxGate… |
| Client.Dotnet-003 | Medium | Concurrency & thread safety | clients/dotnet/MxGateway.Client/MxGatewaySession.cs:659-663, clients/dotnet/MxGateway.Client/MxGatewayClient.cs:230-240 |
DisposeAsync calls CloseAsync() (no token) then unconditionally _closeLock.Dispose(). If another thread is concurrently awaiting CloseAsync(token) — legal, since the type exposes public async methods and no single-threaded contract… |
| Client.Go-002 | Medium | Error handling & resilience | clients/go/mxgateway/session.go:440-516 |
For the Events/EventsAfter compatibility API (cancelWhenResultBufferFull == true), when the 16-slot results channel is full sendEventResult cancels and returns false; the goroutine returns and close(results) runs — the consum… |
| Client.Go-003 | Medium | Correctness & logic bugs | clients/go/cmd/mxgw-go/main.go:517-532 |
parseInt32List calls panic(err) when an item-handles token fails to parse as an int32. The CLI is a documented user-facing tool; a typo like -item-handles 1,foo crashes the process with an unrecovered panic and stack trace instead… |
| Client.Java-001 | Medium | Security | clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/MxGatewaySecrets.java:30-32 |
redactApiKey preserves the leading and trailing four characters of the key. A gateway API key has the form mxgw_<key-id>_<secret>; the last four characters belong to the secret portion, so the "redacted" form leaks 4 characters of the… |
| Client.Java-002 | Medium | Concurrency & thread safety | clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/MxEventStream.java:31,66-92 |
The next field is a plain (non-volatile) instance field, and MxEventStream exposes no thread-confinement guarantee. More concretely, a queue-overflow offer() and a close() offer(END) can interleave so the overflow exception is en… |
| Client.Java-003 | Medium | mxaccessgw conventions | clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/MxGatewayClient.java:119-140 |
OpenSessionReply carries gateway_protocol_version (proto field 8), and MxGatewayClientVersion.GATEWAY_PROTOCOL_VERSION exists so the client can reject incompatible generated-code inputs. The client never reads `reply.getGatewayProtoc… |
| Client.Java-004 | Medium | Correctness & logic bugs | clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/MxGatewaySession.java:114-120,157-163,191-197 |
register, addItem, and addItem2 check reply.hasRegister()/hasAddItem() and otherwise fall back to reply.getReturnValue().getInt32Value(). If the gateway returns a reply with neither the typed payload nor a return_value set, t… |
| Client.Java-005 | Medium | Error handling & resilience | clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/MxGatewaySession.java:92-105 |
close() delegates to closeRaw(), which performs a network RPC. When MxGatewaySession is used in try-with-resources and the body throws, a failure inside closeSession (e.g. WORKER_UNAVAILABLE) throws from close() and replaces th… |
| Client.Python-003 | Medium | Error handling & resilience | clients/python/src/mxgateway/client.py:125-137,155-173 |
stream_events_raw and query_active_alarms call the stub directly with a timeout kwarg when stream_timeout is set, with no TypeError fallback. galaxy.py:watch_deploy_events and _unary do have a fallback that strips timeout… |
| Client.Python-005 | Medium | Performance & resource management | clients/python/src/mxgateway/galaxy.py:117-140 |
discover_hierarchy pages through the entire Galaxy object hierarchy and accumulates every GalaxyObject (each carrying its full attribute list) into a single in-memory list before returning. For a large Galaxy this is a very large all… |
| Client.Python-009 | Medium | Testing coverage | clients/python/tests/ |
Several non-trivial public paths are untested: Session.write2/add_item2 request construction; the bulk-size limit _ensure_bulk_size/MAX_BULK_ITEMS guard; the None-argument TypeError guards in bulk methods; the TLS ca_file rea… |
| Contracts-002 | Medium | Error handling & resilience | src/MxGateway.Contracts/Protos/mxaccess_gateway.proto:384-385, :95 |
MxCommandKind includes MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME = 29 and MxCommand.payload carries AcknowledgeAlarmByNameCommand acknowledge_alarm_by_name_command = 38, but MxCommandReply.payload has only `acknowledge_alarm = 34… |
| IntegrationTests-003 | Medium | Correctness & logic bugs | src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:89-97 |
The test asserts only on the first MxEvent recorded by RecordingServerStreamWriter. A live MXAccess provider can deliver an initial state/quality event whose family or handles differ from the expected OnDataChange (e.g. a registratio… |
| IntegrationTests-004 | Medium | Error handling & resilience | src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:108-111 |
In the finally block, after CloseSessionAsync, the test does await streamTask.WaitAsync(StreamShutdownTimeout). If closing the session does not promptly complete the stream (or StreamEvents itself faults), this throws `TimeoutExcep… |
| IntegrationTests-005 | Medium | Testing coverage | src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs |
The only live MXAccess test covers the Register→AddItem→Advise→one-OnDataChange→Close happy path. CLAUDE.md stresses that MXAccess parity is the contract and calls out non-obvious behaviors (WriteSecured ordering, OperationComplete sem… |
| IntegrationTests-006 | Medium | Testing coverage | src/MxGateway.IntegrationTests/DashboardLdapLiveTests.cs |
LDAP live coverage is two cases: admin succeeds, readonly is denied for missing group. There is no coverage of a wrong password for a valid user, an unknown username, or the LDAP-server-unreachable path — all of which `DashboardAuthenticat… |
| Server-002 | Medium | Design-document adherence | src/MxGateway.Server/Program.cs:24, src/MxGateway.Server/GatewayApplication.cs |
gateway.md:583 and CLAUDE.md state the first version "terminates orphaned workers on startup." No code in MxGateway.Server enumerates or kills leftover MxGateway.Worker.exe processes at startup — a grep for orphan/reattach/`termina… |
| Server-004 | Medium | Code organization & conventions | src/MxGateway.Server/Security/Authentication/ApiKeyAdminCommandLineParser.cs:227-233, src/MxGateway.Server/Security/Authentication/ApiKeyAdminCliRunner.cs:53-77, src/MxGateway.Server/Dashboard/DashboardApiKeyManagementService.cs:21-67 |
ParseScopes accepts any comma-separated strings and CreateKeyAsync persists them verbatim; neither the CLI nor the dashboard create path validates scopes against GatewayScopes. A typo or non-canonical name (e.g. CLAUDE.md's example `… |
| Server-005 | Medium | Error handling & resilience | src/MxGateway.Server/Galaxy/GalaxyHierarchyRefreshService.cs:22-28, src/MxGateway.Server/Galaxy/GalaxyHierarchyCache.cs:184 |
GalaxyHierarchyCache.RefreshCoreAsync only catches SqlException and InvalidOperationException. The initial cache.RefreshAsync call in GalaxyHierarchyRefreshService.ExecuteAsync is wrapped only for OperationCanceledException. A… |
| Server-006 | Medium | Correctness & logic bugs | src/MxGateway.Server/Sessions/SessionManager.cs:84-114 |
In OpenSessionAsync, _metrics.SessionOpened() (line 89) increments the _openSessions gauge before TryAutoSubscribeAlarmsAsync runs. If auto-subscribe throws (which it does when Alarms.RequireSubscribeOnOpen is true and the worker… |
| Tests-003 | Medium | Performance & resource management | src/MxGateway.Tests/Security/Authentication/SqliteAuthStoreTests.cs:170-176, src/MxGateway.Tests/Security/Authentication/ApiKeyAdminCliRunnerTests.cs:252-258 |
CreateTempDatabasePath creates a fresh directory under %TEMP%\mxgateway-auth-tests\<guid> (and ...-cli-tests) for every test but nothing ever deletes it. WorkerProcessLauncherTests.TestDirectory correctly implements IDisposable a… |
| Tests-004 | Medium | Testing coverage | src/MxGateway.Tests/Security/Authorization/GatewayGrpcAuthorizationInterceptorTests.cs |
The authorization interceptor and MxAccessGatewayService are each tested in isolation, but no test composes the interceptor in front of the real service to confirm scope enforcement gates real RPCs end-to-end. A wiring mistake — intercep… |
| Tests-005 | Medium | Testing coverage | src/MxGateway.Tests/Gateway/Grpc/EventStreamServiceTests.cs:239-261, src/MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs |
Worker-crash handling is only tested as a clean terminal exception from ReadEventsAsync or a pre-set ShutdownException. There is no test for a worker that faults mid-command — an InvokeAsync in flight when the pipe/worker dies — whic… |
| Tests-006 | Medium | Concurrency & thread safety | src/MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs:76, src/MxGateway.Tests/Gateway/Workers/FakeWorkerHarnessTests.cs:122 |
Several tests rely on fixed Task.Delay values: WorkerClientTests.InvokeAsync_WithLateReply… waits a hard-coded 50 ms after writing a late reply before issuing the second command, and the heartbeat tests use a 20 ms delay to make timest… |
| Worker-004 | Medium | Correctness & logic bugs | src/MxGateway.Worker/Ipc/WorkerPipeSession.cs:565-588 |
After ReportWatchdogFaultIfNeededAsync sends an StaHung fault, the heartbeat loop continues sending normal heartbeats with State derived from _state, which the watchdog path never sets to Faulted. The heartbeat then keeps reporti… |
| Worker-005 | Medium | Error handling & resilience | src/MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs:297-313 |
OnPoll catches every exception from PollOnce() and discards it (_ = ex;). The production poll path (MxAccessStaSession.RunAlarmPollLoopAsync → AlarmCommandHandler.PollOnce → AlarmDispatcher.PollOnce → consumer.PollOnce()) has… |
| Worker-006 | Medium | Correctness & logic bugs | src/MxGateway.Worker/Ipc/WorkerPipeSession.cs:117-124, src/MxGateway.Worker/MxAccess/MxAccessStaSession.cs:386-491 |
RunAsync's finally calls _runtimeSession?.Dispose() unless _shutdownTimedOut. On the normal path ShutdownGracefullyAsync already disposed the STA runtime, so re-entering Dispose() is a harmless no-op only because `ShutdownGrace… |
| Worker-007 | Medium | mxaccessgw conventions | src/MxGateway.Worker/MxAccess/MxAccessComServer.cs:130-150 |
Invoke uses late-bound Type.InvokeMember reflection as a fallback when the COM object does not cast to ILMXProxyServer*. In production the object is always LMXProxyServerClass, so the reflection path exists only for test doubles —… |
| Worker-008 | Medium | Concurrency & thread safety | src/MxGateway.Worker/MxAccess/MxAccessStaSession.cs:205-249, :429-447 |
RunAlarmPollLoopAsync correctly marshals handler.PollOnce() onto the STA via staRuntime.InvokeAsync, and the cancel/await/dispose ordering in ShutdownGracefullyAsync is sound. However, nothing enforces that the consumerFactory an… |
| Worker.Tests-003 | Medium | Concurrency & thread safety | src/MxGateway.Worker.Tests/Sta/StaRuntimeTests.cs:46-48 |
InvokeAsync_WakesIdlePumpForQueuedCommand asserts stopwatch.Elapsed < TimeSpan.FromSeconds(2) — a wall-clock assertion that on a loaded CI agent can exceed 2s, producing a false failure. The test also does not actually prove the wake e… |
| Worker.Tests-004 | Medium | Concurrency & thread safety | src/MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs:281-329 |
StartAsync_WithAlarmCommandHandlerFactory_PollOnceCalledViaSta and Dispose_StopsAlarmPollLoop use poll-until loops, and Dispose_StopsAlarmPollLoop additionally does await Task.Delay(1000) then asserts PollCount is unchanged. The… |
| Worker.Tests-005 | Medium | Performance & resource management | src/MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs:20-31,103-105, src/MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs:28-31 |
MemoryStream instances are created and never disposed across the frame-protocol and pipe-session tests (MemoryStream stream = new(); with no using). Disposal is cheap so impact is low, but it is inconsistent with the rest of the suit… |
| Worker.Tests-006 | Medium | Performance & resource management | src/MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs:282,305,315,323 |
Dispose_StopsAlarmPollLoop constructs MxAccessStaSession session without using (unlike every sibling test) and relies on an explicit session.Dispose(). If an assertion between StartAsync and Dispose() throws, the session — its… |
| Worker.Tests-007 | Medium | Design-document adherence | docs/WorkerFrameProtocol.md:38-49 |
docs/WorkerFrameProtocol.md instructs running dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj --filter WorkerFrameProtocolTests and states the frame protocol "is part of MxGateway.Server". The frame protocol actually lives in… |
| Client.Dotnet-004 | Low | Error handling & resilience | clients/dotnet/MxGateway.Client/MxGatewayClient.cs:283-294, clients/dotnet/MxGateway.Client/GalaxyRepositoryClient.cs:392-403 |
ExecuteSafeUnaryAsync wraps the whole Polly retry pipeline in a single linked CTS cancelled after Options.DefaultCallTimeout, while CreateCallOptions also stamps each individual call with a DefaultCallTimeout gRPC deadline. The ret… |
| Client.Dotnet-005 | Low | Correctness & logic bugs | clients/dotnet/MxGateway.Client/MxGatewaySession.cs:82,124,175 |
RegisterAsync/AddItemAsync/AddItem2Async return reply.<Typed>?.ServerHandle ?? reply.ReturnValue.Int32Value. After EnsureMxAccessSuccess() passes, a missing typed payload silently falls back to ReturnValue.Int32Value, which for… |
| Client.Dotnet-006 | Low | Code organization & conventions | clients/dotnet/MxGateway.Client/MxGatewayClientOptions.cs:50, clients/dotnet/MxGateway.Client/MxGatewayClientContractInfo.cs:10-14 |
MxGatewayClientOptions.MaxGrpcMessageBytes and the two consts in MxGatewayClientContractInfo are public members with no XML doc comments, inconsistent with every other public member in the assembly and with the repo's documented C# s… |
| Client.Dotnet-007 | Low | Documentation & comments | clients/dotnet/MxGateway.Client/MxGatewayClient.cs:185-192 |
The AcknowledgeAlarmAsync XML comment states the gateway authenticates against an invoke:alarm-ack scope, but CLAUDE.md documents the scope set without any invoke:alarm-ack sub-scope. The comment may describe an intended finer-grai… |
| Client.Dotnet-008 | Low | Correctness & logic bugs | clients/dotnet/MxGateway.Client.Cli/MxGatewayCliSecretRedactor.cs:9-17 |
The CLI redactor only removes the API key string when it was supplied via --api-key; RunCoreAsync passes arguments.GetOptional("api-key") to Redact. When the key comes from an environment variable (--api-key-env, the documented d… |
| Client.Go-004 | Low | mxaccessgw conventions | clients/go/mxgateway/alarms_test.go:153-154, clients/go/mxgateway/galaxy_test.go:58-59 |
gofmt -l flags alarms_test.go and galaxy_test.go for misaligned struct-literal field padding. The Go client README lists gofmt as part of the workflow and the repo enforces style; unformatted committed code breaks gofmt-gated che… |
| Client.Go-005 | Low | Design-document adherence | clients/go/mxgateway/client.go:64,68, clients/go/mxgateway/galaxy.go:83,87 |
The client uses grpc.DialContext with grpc.WithBlock(). In current grpc-go both are deprecated in favour of grpc.NewClient (lazy connection). WithBlock also changes failure semantics: a transient gateway-unavailable at dial time be… |
| Client.Go-006 | Low | Error handling & resilience | clients/go/mxgateway/errors.go:9-130 |
docs/ClientLibrariesDesign.md recommends a high-level error taxonomy (TransportError, AuthenticationError, TimeoutError, etc.). The Go client collapses all transport/gRPC failures into a single GatewayError with no way to classif… |
| Client.Go-007 | Low | Correctness & logic bugs | clients/go/mxgateway/session.go:526-532 |
newCorrelationID returns an empty string when crypto/rand.Read fails, silently producing an MxCommandRequest with no correlation id. rand.Read failure is rare, but the failure mode (untraceable command, no error surfaced) is worse… |
| Client.Go-008 | Low | Testing coverage | clients/go/mxgateway/ (test files) |
Several critical paths are untested: TLS credential resolution in resolveTransportCredentials (only the Plaintext path is exercised); the callContext deadline-shortening logic (client.go:198-204) including the negative-timeout disa… |
| Client.Go-009 | Low | Code organization & conventions | clients/go/mxgateway/galaxy.go:60-93,241-256, clients/go/mxgateway/client.go:41-74,190-205 |
DialGalaxy/Dial and GalaxyClient.callContext/Client.callContext are near-identical duplicates (dial-context setup, credential resolution, dial-option assembly, deadline arithmetic). A fix to one (e.g. the Client.Go-005 dial migrati… |
| Client.Go-010 | Low | Documentation & comments | clients/go/mxgateway/client.go:39-40 |
The Dial doc comment states it configures "blocking dial cancellation from ctx." This describes the deprecated WithBlock behaviour; once Client.Go-005 is addressed the comment is misleading about how connection establishment and cancel… |
| Client.Java-006 | Low | Performance & resource management | clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/MxGatewayClient.java:323-328, clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/GalaxyRepositoryClient.java:279-284 |
close() (the AutoCloseable method invoked by try-with-resources) calls only ownedChannel.shutdown() and returns immediately without awaiting termination. In-flight calls and Netty event-loop threads may still be running when the call… |
| Client.Java-007 | Low | Testing coverage | clients/java/mxgateway-client/src/test/java/com/dohertylan/mxgateway/client/ |
The alarm surface — acknowledgeAlarm/acknowledgeAlarmAsync/queryActiveAlarms and MxGatewayActiveAlarmsSubscription — has zero test coverage. TLS channel construction, the async streamEventsAsync path, MxGatewayEventSubscription… |
| Client.Java-008 | Low | Error handling & resilience | clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/MxGatewayClient.java:298-304 |
acknowledgeAlarmAsync and openSessionAsync apply ensureProtocolSuccess inside thenApply. If that validator throws a non-MxGatewayException RuntimeException it is wrapped by CompletionException with no fromGrpc normalisation… |
| Client.Java-009 | Low | Code organization & conventions | clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/GalaxyRepositoryClient.java:310-391, clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/MxGatewayClient.java:346-413 |
createChannel, withDeadline, withStreamDeadline, and toCompletable are duplicated nearly verbatim across MxGatewayClient and GalaxyRepositoryClient (~80 lines). A fix to one will not propagate to the other. |
| Client.Java-010 | Low | Documentation & comments | clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/MxGatewayClient.java:269-272, clients/java/README.md:76 |
The acknowledgeAlarm Javadoc states the gateway authenticates against an invoke:alarm-ack scope, and the README states the Galaxy Repository requires a metadata:read scope. CLAUDE.md's documented scope set names neither — the Javadoc… |
| Client.Java-011 | Low | Performance & resource management | clients/java/mxgateway-client/src/main/java/com/dohertylan/mxgateway/client/MxEventStream.java:37-63 |
The event stream relies on default gRPC auto-inbound flow control: the async stub auto-requests messages, so the server can push faster than the 16-element bounded queue drains. A momentarily slow consumer triggers queue overflow and an im… |
| Client.Java-012 | Low | Correctness & logic bugs | clients/java/mxgateway-cli/src/main/java/com/dohertylan/mxgateway/cli/MxGatewayCli.java:667-674 |
CommonOptions.resolved() mutates this (resolvedApiKey, resolvedTimeout) and returns this, but toClientOptions() and redactedJsonMap() read those mutated fields. If redactedJsonMap() is ever called before resolved(), it si… |
| Client.Python-001 | Low | Documentation & comments | clients/python/pyproject.toml:8,25, clients/python/src/mxgateway_cli/commands.py:25 |
The package description in pyproject.toml still says "Async Python client scaffold" even though the client is fully implemented. Stale "scaffold" wording misrepresents maturity to anyone reading PyPI metadata. (The mxgw-py console-… |
| Client.Python-002 | Low | Code organization & conventions | clients/python/src/mxgateway/__init__.py:27 |
MxGatewayCommandError is imported into __init__.py and is a documented public exception, but it is missing from __all__. It is the parent of MxAccessError and a meaningful catch target, so omitting it from the public surface is inc… |
| Client.Python-004 | Low | Correctness & logic bugs | clients/python/src/mxgateway_cli/commands.py:386,402-404 |
In _smoke, the local variable closed is set to False and never reassigned; the finally block's if not closed: is therefore always true. This is dead/misleading code suggesting a removed early-close path. |
| Client.Python-006 | Low | Concurrency & thread safety | clients/python/src/mxgateway/client.py:74-82, clients/python/src/mxgateway/galaxy.py:85-93, clients/python/src/mxgateway/session.py:38-55 |
close() on the clients and Session.close() use a plain self._closed check-then-set with an await between, with no lock. If two coroutines call close() concurrently both can pass the guard before either sets it, causing a double `… |
| Client.Python-007 | Low | Error handling & resilience | clients/python/src/mxgateway/client.py:204-213 |
_canceling_iterator (gateway event stream) does not catch asyncio.CancelledError to invoke call.cancel() explicitly — it relies on the finally block. galaxy.py:_canceling_iterator does explicitly catch CancelledError, cancel,… |
| Client.Python-008 | Low | Correctness & logic bugs | clients/python/src/mxgateway/values.py:62-67,83-88 |
to_mx_value maps any Python float to VT_R8/MX_DATA_TYPE_DOUBLE with no handling for nan/inf, which are serialised and forwarded to MXAccess which may reject or mis-handle them. bytes is mapped to VT_RECORD/`MX_DATA_TYPE_UNK… |
| Client.Python-010 | Low | Code organization & conventions | clients/python/src/mxgateway/session.py:404, clients/python/src/mxgateway_cli/commands.py:422-425 |
session.py ends with a module-level late import from .client import GatewayClient # noqa: E402 purely to satisfy a string type hint, and commands.py:_session does a function-local import. Both work around a circular dependency that `… |
| Client.Python-011 | Low | Error handling & resilience | clients/python/src/mxgateway/errors.py:122-148 |
ensure_mxaccess_success raises MxAccessError if any mx_status.success == 0. This treats success == 0 as the failure sentinel, but 0 is also the proto3 scalar default for an unset MxStatusProxy. If the gateway ever returns a rep… |
| Client.Python-012 | Low | mxaccessgw conventions | clients/python/src/mxgateway/client.py:84-108, clients/python/src/mxgateway/session.py:57-77 |
Session.invoke_raw does not run ensure_mxaccess_success while Session.invoke does, so a caller using invoke_raw for parity tests gets a reply where an MXAccess HRESULT failure is silently embedded with no exception. This is by desi… |
| Contracts-001 | Low | Design-document adherence | docs/Grpc.md:13 (and :3, :32, :39) |
mxaccess_gateway.proto now declares six RPCs on MxAccessGateway (OpenSession, CloseSession, Invoke, StreamEvents, AcknowledgeAlarm, QueryActiveAlarms). docs/Grpc.md still describes "the four MxAccessGateway RPCs" in its… |
| Contracts-003 | Low | Code organization & conventions | src/MxGateway.Contracts/MxGateway.Contracts.csproj:10 |
The <Protobuf> item for mxaccess_worker.proto omits ProtoRoot="Protos", while the items for mxaccess_gateway.proto (line 9) and galaxy_repository.proto (line 11) both set it. mxaccess_worker.proto does `import "mxaccess_gateway… |
| Contracts-004 | Low | Documentation & comments | src/MxGateway.Contracts/GatewayContractInfo.cs:3-6 |
The XML summary says the class exposes version metadata "before generated protobuf contracts are introduced." Generated protobuf contracts have long been introduced and are consumed across the solution. The comment is stale; the class now… |
| Contracts-005 | Low | mxaccessgw conventions | src/MxGateway.Contracts/Protos/mxaccess_gateway.proto, src/MxGateway.Contracts/Protos/mxaccess_worker.proto |
The ProtobufStyleGuide mandates reserving removed field numbers / enum values. Evolution to date has been purely additive, so this is not a current violation — but none of the .proto files contain any reserved declarations, leaving no… |
| Contracts-006 | Low | Correctness & logic bugs | src/MxGateway.Contracts/Protos/mxaccess_gateway.proto:647 |
MxStatusProxy.success is declared int32 success = 1 with no comment. The name reads like a boolean flag but the type is a 32-bit integer (mirroring MXAccess MXSTATUS_PROXY, which stores a numeric success/HResult-like value). Without… |
| Contracts-007 | Low | Testing coverage | src/MxGateway.Tests/Contracts/ProtobufContractRoundTripTests.cs |
ProtobufContractRoundTripTests covers gateway command/reply/event, alarm transition, alarm ack request/reply, active-alarm snapshot, and the worker envelope. It has no coverage for: (a) any galaxy_repository.proto message (`DiscoverHie… |
| Contracts-008 | Low | Design-document adherence | src/MxGateway.Contracts/Protos/mxaccess_gateway.proto:451-459, :627-636 |
The worker-side AcknowledgeAlarmReplyPayload carries the alarm-ack outcome as int32 native_status, while the public AcknowledgeAlarmReply carries it as MxStatusProxy status plus optional int32 hresult. The comment explains the wo… |
| IntegrationTests-007 | Low | Concurrency & thread safety | src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:20, src/MxGateway.IntegrationTests/Galaxy/GalaxyRepositoryLiveTests.cs:5, src/MxGateway.IntegrationTests/DashboardLdapLiveTests.cs:9 |
The live test classes contend for genuinely shared singletons — one MXAccess COM provider, one ZB SQL database, one GLAuth instance with a 3-fail/10-minute per-IP lockout. No [Collection] annotation or DisableTestParallelization is dec… |
| IntegrationTests-008 | Low | Code organization & conventions | src/MxGateway.IntegrationTests/LiveLdapFactAttribute.cs, src/MxGateway.IntegrationTests/Galaxy/LiveGalaxyRepositoryFactAttribute.cs, src/MxGateway.IntegrationTests/LiveMxAccessFactAttribute.cs |
Three near-identical fact attributes each re-implement the same "compare env var to 1 with StringComparison.Ordinal, set Skip otherwise" pattern. LiveMxAccessFactAttribute delegates to IntegrationTestEnvironment while the other t… |
| IntegrationTests-009 | Low | Documentation & comments | src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:372-375 |
TestServerCallContext is XML-documented as a "Mock server call context," but it is a hand-written stub/fake with no mocking framework and no verification behavior. Per the style guides (accurate naming; explain why not what), calling it… |
| IntegrationTests-010 | Low | Correctness & logic bugs | src/MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs:366-369 |
WaitForFirstMessageAsync accepts only a timeout and never observes a CancellationToken. There is no per-test cancellation propagation, so if the gateway/worker hangs without writing an event the test relies solely on the 15s `WaitAsy… |
| Server-007 | Low | Performance & resource management | src/MxGateway.Server/Galaxy/GalaxyHierarchyProjector.cs:55-70 |
Project always iterates the full entry.Index.ObjectViews collection and re-applies all filters to skip offset matched items before collecting a page. Paging through a large Galaxy hierarchy is therefore O(total) per page and O(total²… |
| Server-008 | Low | Performance & resource management | src/MxGateway.Server/Grpc/GalaxyRepositoryGrpcService.cs:111-134,160-189 |
WatchDeployEvents calls ResolveBrowseSubtrees() on every streamed event, and MapDeployEvent re-runs GalaxyHierarchyProjector.Project over the entire cached hierarchy (and Sums attribute counts) for every event of every constraine… |
| Server-009 | Low | Error handling & resilience | src/MxGateway.Server/Security/Authentication/AuthSqliteConnectionFactory.cs:15-32 |
Each auth-store operation opens a fresh SqliteConnection with no busy timeout, no WAL journal mode, and default journaling. MarkKeyUsedAsync runs on every authenticated request and SqliteApiKeyAuditStore appends on every denial; unde… |
| Server-010 | Low | Security | src/MxGateway.Server/Security/Authentication/SqliteApiKeyAdminStore.cs:91-114, src/MxGateway.Server/Dashboard/Components/Pages/ApiKeysPage.razor:168-172 |
RotateAsync sets revoked_utc = NULL, so rotating a previously revoked key silently reactivates it. This is documented intentional behavior in docs/Authentication.md:167, but the dashboard renders the "Rotate" button unconditionally —… |
| Server-011 | Low | Code organization & conventions | src/MxGateway.Server/Sessions/WorkerAlarmRpcDispatcher.cs:1-46 |
WorkerAlarmRpcDispatcher deviates from the module's conventions: it fully-qualifies System.Guid, System.ArgumentNullException, and System.Threading types inline instead of relying on using directives, and uses an explicit constru… |
| Server-012 | Low | Documentation & comments | CLAUDE.md (Authentication section and apikey create example) |
CLAUDE.md describes scopes as session, invoke, event, metadata, admin and shows apikey create --scopes session,invoke,event,metadata,admin. The actual canonical scope strings (used by GatewayScopes, GatewayGrpcScopeResolver… |
| Server-013 | Low | Testing coverage | src/MxGateway.Tests/Gateway/Dashboard/DashboardAuthorizationHandlerTests.cs, src/MxGateway.Tests/Gateway/GatewayApplicationTests.cs |
DashboardAuthorizationHandler is unit-tested in isolation, but no test exercises the dashboard routes end-to-end to confirm the policy is actually enforced — which is why Server-001 (policy registered but never wired) went uncaught. Ther… |
| Server-014 | Low | Documentation & comments | src/MxGateway.Server/Grpc/MxAccessGatewayService.cs:162-171,191-198,206-214,229-237 |
The XML <remarks> and inline comments on AcknowledgeAlarm and QueryActiveAlarms describe the alarm path as not yet wired and say NotWiredAlarmRpcDispatcher is the default ("Clients calling this method today receive an OK reply with… |
| Tests-007 | Low | Code organization & conventions | src/MxGateway.Tests/Gateway/Grpc/MxAccessGatewayServiceTests.cs:682, src/MxGateway.Tests/Gateway/Grpc/GalaxyRepositoryGrpcServiceTests.cs:324, src/MxGateway.Tests/Gateway/GatewayEndToEndFakeWorkerSmokeTests.cs:460, src/MxGateway.Tests/Security/Authorization/GatewayGrpcAuthorizationInterceptorTests.cs:233 |
A near-identical TestServerCallContext implementation is copy-pasted into at least four test files (and AllowAllConstraintEnforcer / TestServerStreamWriter / RecordingStreamWriter into several). Duplication risks the copies driftin… |
| Tests-008 | Low | mxaccessgw conventions | src/MxGateway.Tests/Gateway/Sessions/WorkerAlarmRpcDispatcherTests.cs:1-9, src/MxGateway.Tests/Gateway/Sessions/NotWiredAlarmRpcDispatcherTests.cs:1-3, src/MxGateway.Tests/Gateway/Sessions/SessionManagerAlarmAutoSubscribeTests.cs:1 |
The alarm test files diverge from the project's C# style and the rest of the suite: snake_case test method names instead of the PascalCase Method_Condition_Result pattern; redundant explicit using System;/System.Threading; imports de… |
| Tests-009 | Low | Documentation & comments | src/MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs:36-37,99,365 |
Several XML <summary> comments are copy-paste mismatches: the comment above OpenSessionAsync_SetsInitialDefaultLease describes correlation-ID generation; the comment above GatewaySessionSubscribeBulkAsync_ForwardsOneBulkCommand… desc… |
| Tests-010 | Low | Security | src/MxGateway.Tests/Gateway/Dashboard/DashboardAuthorizationHandlerTests.cs:26-36 |
The anonymous-localhost bypass is tested only for the success case (allowAnonymousLocalhost: true + loopback succeeds) and the remote-unauthenticated denial. There is no test for the security-critical negatives: anonymous + loopback when… |
| Tests-011 | Low | Correctness & logic bugs | src/MxGateway.Tests/Gateway/GatewayEndToEndFakeWorkerSmokeTests.cs:233-301 |
GatewayEndToEndFakeWorkerSmokeTests correctly stores and awaits launcher.WorkerTask, but SessionWorkerClientFactoryFakeWorkerTests uses _ = RunWorkerAsync(...) with no stored task (lines 152, 184, 220). An unhandled exception in th… |
| Tests-012 | Low | Concurrency & thread safety | src/MxGateway.Tests/Gateway/Workers/Fakes/FakeWorkerHarness.cs:62, src/MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs:472 |
Pipe names are uniquified per test with a GUID (good), but xUnit runs test classes in parallel by default and there is no xunit.runner.json or collection configuration. Tests that build a full WebApplication bind ephemeral ports (`--ur… |
| Worker-009 | Low | Performance & resource management | src/MxGateway.Worker/Ipc/WorkerFrameReader.cs:31,49, src/MxGateway.Worker/Ipc/WorkerFrameWriter.cs:57-58 |
Every frame read allocates a fresh 4-byte length buffer and a payload byte[]; every write allocates ToByteArray() plus a 4-byte prefix. On the hot event-drain path (batches of up to 128 WorkerEvent frames every 25 ms) this produces s… |
| Worker-010 | Low | Correctness & logic bugs | src/MxGateway.Worker/Conversion/VariantConverter.cs:204-226 |
ConvertInt64Scalar is reached for TypeCode.UInt32 and TypeCode.Int64. For a uint with expectedDataType == MxDataType.Time, the value is treated as a Windows FILETIME via DateTime.FromFileTimeUtc(longValue); a 32-bit FILETIME… |
| Worker-011 | Low | Correctness & logic bugs | src/MxGateway.Worker/Ipc/WorkerPipeClient.cs:169-171 |
retryAttempts is computed as (connectTimeout / min(connectTimeout, attemptTimeout)) - 1. With defaults (30000 / 2000) this yields 14 retries, but each retry also incurs Polly exponential backoff. The overall connectDeadline (`CancelA… |
| Worker-012 | Low | Documentation & comments | src/MxGateway.Worker/MxAccess/MxAccessAlarmEventSink.cs:44-55, src/MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs:38-43, src/MxGateway.Worker/MxAccess/MxAccessEventMapper.cs:106-112 |
Multiple comments describe the alarm path as not-yet-wired future work ("PR A.2 — COM-side subscription scaffold … the worker advertises no alarm subscription", "the worker bootstrap will gain a thin 'run-on-STA' wrapper as part of A.3").… |
| Worker-013 | Low | Testing coverage | src/MxGateway.Worker/Sta/StaMessagePump.cs |
StaMessagePump — the heart of COM event delivery (MsgWaitForMultipleObjectsEx + PeekMessage/DispatchMessage) — has no direct unit tests. StaRuntimeTests exercises it indirectly for command wake-up but never verifies that a posted… |
| Worker-014 | Low | Code organization & conventions | src/MxGateway.Worker/MxAccess/AlarmCommandHandler.cs:33, :202 |
The file declares two public types — the AlarmCommandHandler class and the IAlarmCommandHandler interface. The C# style guide and the rest of the module follow one-public-type-per-file (e.g. interfaces in their own I*.cs files like `… |
| Worker-015 | Low | Correctness & logic bugs | src/MxGateway.Worker/MxAccess/MxAccessEventQueue.cs:115-145 |
On overflow, Enqueue records the overflow fault and throws MxAccessEventQueueOverflowException; MxAccessBaseEventSink.EnqueueEvent catches it and calls RecordFault again. RecordFault is a no-op when a fault already exists, so the… |
| Worker.Tests-008 | Low | Documentation & comments | src/MxGateway.Worker.Tests/Conversion/VariantConverterTests.cs:175-182 |
Redactor_WithCredentialBearingValueFields_RedactsBeforeLogging lives in VariantConverterTests but asserts on WorkerLogRedactor.RedactValue, which has nothing to do with VariantConverter. It is also a near-duplicate of coverage in `… |
| Worker.Tests-009 | Low | Code organization & conventions | src/MxGateway.Worker.Tests/MxAccess/AlarmCommandHandlerTests.cs, AlarmDispatcherTests.cs, AlarmCommandExecutorTests.cs, AlarmRecordTransitionMapperTests.cs, WnWrapAlarmConsumerXmlTests.cs |
The alarm-related test files use snake_case method names while the rest of the project uses the Method_State_Result PascalCase convention. docs/style-guides/CSharpStyleGuide.md and the surrounding code establish PascalCase as the pro… |
| Worker.Tests-010 | Low | Correctness & logic bugs | src/MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs:230-258 |
StartAsync_WithoutAlarmCommandHandlerFactory_SubscribeAlarmsReturnsInvalidRequest asserts Assert.Contains("alarm", reply.DiagnosticMessage, StringComparison.OrdinalIgnoreCase). The XML doc claims it verifies the diagnostic says "alarm… |
| Worker.Tests-011 | Low | Documentation & comments | src/MxGateway.Worker.Tests/Sta/StaCommandDispatcherTests.cs:92-112 |
DispatchAsync_WhenCanceledAfterExecutionStarts_StillReturnsLateReply is named and documented as if it proves cancellation arrived after execution began. The test does Started.Wait(...) then cancellation.Cancel(), which proves executi… |
| Worker.Tests-012 | Low | Testing coverage | src/MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs |
docs/WorkerFrameProtocol.md states the reader "rejects zero-length payloads and payloads larger than the configured maximum (default 16 MiB) before allocating the payload buffer." WorkerFrameProtocolTests covers malformed-length, wrong… |
| Worker.Tests-013 | Low | Concurrency & thread safety | src/MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs:539-546 |
ThrowIfCompletedAsync does an unconditional await Task.Delay(TimeSpan.FromMilliseconds(100)) then checks task.IsCompleted. This adds a fixed 100 ms to the test and only catches a RunAsync that fails within that arbitrary window; a… |
| Worker.Tests-014 | Low | Code organization & conventions | src/MxGateway.Worker.Tests/Ipc/WorkerPipeClientTests.cs:194, WorkerPipeSessionTests.cs:622, Sta/StaCommandDispatcherTests.cs:348, MxAccess/MxAccessStaSessionTests.cs:334, MxAccess/MxAccessCommandExecutorTests.cs:1124 |
FakeRuntimeSession, NoopComApartmentInitializer, NoopEventSink/NullEventSink, and the CreateFrame/WriteUInt32LittleEndian helpers are re-implemented independently in multiple test files. The two FakeRuntimeSession implementat… |
| Worker.Tests-015 | Low | Testing coverage | src/MxGateway.Worker.Tests/MxAccess/MxAccessEventQueueTests.cs |
MxAccessEventQueueTests covers monotonic sequencing, drain, capacity overflow, and first-fault-wins, but does not cover Drain with maxEvents: 0 (drain-all) — a branch FakeRuntimeSession.DrainEvents even special-cases — nor draining… |
Closed findings
Findings with status Resolved, Won't Fix, or Deferred.
| ID | Severity | Status | Category | Location |
|---|---|---|---|---|
| Server-001 | Critical | Resolved | Security | src/MxGateway.Server/GatewayApplication.cs:147-149, src/MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs:55-58, src/MxGateway.Server/Dashboard/Components/Routes.razor:1-15 |
| Client.Rust-001 | High | Resolved | mxaccessgw conventions | clients/rust/src/options.rs:98,143 |
| Client.Rust-002 | High | Resolved | mxaccessgw conventions | clients/rust/src/session.rs:522 |
| Client.Rust-003 | High | Resolved | Correctness & logic bugs | clients/rust/crates/mxgw-cli/src/main.rs:1051 |
| Client.Rust-012 | High | Resolved | mxaccessgw conventions | clients/rust/src/galaxy.rs:282 |
| Server-003 | High | Resolved | Security | src/MxGateway.Server/Dashboard/DashboardAuthorizationHandler.cs:39,54-59, src/MxGateway.Server/Dashboard/DashboardAuthenticator.cs:236-258 |
| Client.Rust-005 | Medium | Resolved | Correctness & logic bugs | clients/rust/src/session.rs:489-520 |
| Client.Rust-006 | Medium | Resolved | Error handling & resilience | clients/rust/src/session.rs:531-555 |
| Client.Rust-004 | Low | Resolved | Documentation & comments | clients/rust/src/version.rs:7 |
| Client.Rust-007 | Low | Resolved | Design-document adherence | clients/rust/RustClientDesign.md:14-55 |
| Client.Rust-008 | Low | Resolved | Performance & resource management | clients/rust/src/value.rs:161-261 |
| Client.Rust-009 | Low | Resolved | Testing coverage | clients/rust/tests/client_behavior.rs, clients/rust/src/galaxy.rs |
| Client.Rust-010 | Low | Resolved | Error handling & resilience | clients/rust/src/client.rs:255-268, clients/rust/src/galaxy.rs:204-216 |
| Client.Rust-011 | Low | Resolved | mxaccessgw conventions | clients/rust/src/session.rs:469 |