# Code Review — Driver.Galaxy.Contracts

<!-- Template for a per-module findings file. Copy to code-reviews/<Module>/findings.md.
     See ../../REVIEW-PROCESS.md for the full process. The base README.md is generated
     from these files by regen-readme.py — do not edit README.md by hand. -->

| Field | Value |
|---|---|
| Module | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Contracts` |
| Reviewer | Claude Code |
| Review date | 2026-06-19 |
| Commit reviewed | `7286d320` |
| Status | Reviewed |
| Open findings | 0 |

## Checklist coverage

A comprehensive review completes every category, recording "No issues found" where
a category produced nothing rather than leaving it blank.

| # | Category | Result |
|---|---|---|
| 1 | Correctness & logic bugs | Driver.Galaxy.Contracts-002 (Medium): EventPumpChannelCapacity has no Range guard; EventPump enforces ≥ 1 at runtime |
| 2 | OtOpcUa conventions | No issues found |
| 3 | Concurrency & thread safety | No issues found (pure data-contract records; no mutable shared state) |
| 4 | Error handling & resilience | No issues found |
| 5 | Security | No issues found (ApiKeySecretRef is documented as an indirection reference; no defaults store a cleartext secret; no ToString override leaks the ref) |
| 6 | Performance & resource management | No issues found (pure records; no allocations, disposables, or resource lifetimes) |
| 7 | Design-document adherence | No issues found (records match CLAUDE.md Gateway/MxAccess/Repository/Reconnect section layout) |
| 8 | Code organization & conventions | Driver.Galaxy.Contracts-003 (Low, Resolved): ResolveApiKey helper duplicated between GalaxyDriver and GalaxyBrowseSession; extracted to `GalaxySecretRef` in Contracts |
| 9 | Testing coverage | No issues found (no logic to test; pure data records with default values) |
| 10 | Documentation & comments | Driver.Galaxy.Contracts-001 (Low, Resolved): Internal code-review finding ID `(Driver.Galaxy-010)` in shipped XML doc |

## Findings

<!-- One ### entry per finding. IDs are <Module>-NNN, sequential within the module,
     never reused. Findings are never deleted — close them by changing Status and
     completing Resolution. -->

### Driver.Galaxy.Contracts-001

| Field | Value |
|---|---|
| Severity | Low |
| Category | Documentation & comments |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Contracts/GalaxyDriverOptions.cs:43` |
| Status | Resolved |

**Description:** The XML doc on `GalaxyGatewayOptions.ApiKeySecretRef` contained the
parenthetical `(Driver.Galaxy-010)` — an internal code-review finding ID — embedded
in the shipped `<summary>` tag. Once emitted via `dotnet doc` or IntelliSense, this
noise is meaningless to any consumer of the contracts assembly (AdminUI, Driver.Galaxy,
Driver.Galaxy.Browser) and will become stale if the finding ID is ever renumbered or
retired. The underlying issue that spawned Driver.Galaxy-010 (startup warning on
cleartext-literal API key) was resolved in that review; the reference in source code
is now pure noise.

**Recommendation:** Remove the parenthetical `(Driver.Galaxy-010)` from the XML doc.
The surrounding sentence already explains the behavior; the cross-reference adds no
value.

**Resolution:** Fixed 2026-06-19 in this review pass. Removed `(Driver.Galaxy-010)` from
the `ApiKeySecretRef` doc comment. Verified by build (no test project).

---

### Driver.Galaxy.Contracts-002

| Field | Value |
|---|---|
| Severity | Medium |
| Category | Correctness & logic bugs |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Contracts/GalaxyDriverOptions.cs:91` |
| Status | Resolved |

**Description:** `GalaxyMxAccessOptions.EventPumpChannelCapacity` defaults to `50_000`
but has no `[Range]` annotation. `EventPump` (the consumer) enforces `channelCapacity >= 1`
via `ArgumentOutOfRangeException` at construction:

```csharp
if (channelCapacity < 1)
    throw new ArgumentOutOfRangeException(nameof(channelCapacity),
        "channelCapacity must be >= 1; ...");
```

An operator who enters `0` or a negative number in the AdminUI Galaxy driver form will
pass Blazor's `DataAnnotationsValidator` silently (no annotation to check), save the
config to the central config DB, and only encounter the failure at `GalaxyDriver.InitializeAsync`
time — surfaced as a cryptic internal `ArgumentOutOfRangeException` rather than a
friendly form-validation message. The gap between the option's declared type (`int`) and
the consumer's enforced floor (`>= 1`) is not visible at the contracts layer.

**Recommendation:** Add `[Range(1, int.MaxValue)]` to `EventPumpChannelCapacity` and
update the `<param>` doc to state the minimum. The `[Range]` attribute is already used on
`ProbeTimeoutSeconds` in the same class; the pattern is established.

**Resolution:** Fixed 2026-06-19 in this review pass. Added `[Range(1, int.MaxValue)]`
to the `EventPumpChannelCapacity` positional parameter and extended the `<param>` doc to
note the minimum and that `EventPump` enforces it at construction. Verified by build
(no test project).

---

### Driver.Galaxy.Contracts-003

| Field | Value |
|---|---|
| Severity | Low |
| Category | Code organization & conventions |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser/GalaxyDriverBrowser.cs:149` (duplicate) and `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/GalaxyDriver.cs:472` (original) |
| Status | Resolved |

**Description:** `GalaxyDriver.ResolveApiKey` (the four-form `env:`/`file:`/`dev:`/literal
resolver, ~45 LOC) is duplicated verbatim as `GalaxyDriverBrowser.ResolveApiKey`. The
Browser copy acknowledges this in its XML doc ("Slim mirror of `GalaxyDriver.ResolveApiKey`
— the runtime version lives in a sibling project the Browser intentionally doesn't
reference"). The duplication is intentional to avoid a project reference from
`Driver.Galaxy.Browser` to `Driver.Galaxy`, but the `Driver.Galaxy.Contracts` project is
already referenced by both (and by `AdminUI`). If a new secret-ref prefix (e.g. `vault:`)
is added to the runtime resolver, the Browser copy will silently fall through to the
back-compat literal arm and emit a spurious startup warning — exactly the drift that
`MapSecurityClass` suffered in `Driver.Galaxy.Browser-001`.

This finding was first raised in the sibling review (Driver.Galaxy.Browser-003), which
identified `Driver.Galaxy.Contracts` as the natural home. Extracting the helper into
Contracts would be a one-file change with no public-contract break; both `Driver.Galaxy`
and `Driver.Galaxy.Browser` would lose their copies and delegate to the single
authoritative implementation. The current project has no package references beyond
`System.ComponentModel.DataAnnotations` (in-box). Adding a pure-BCL static helper
(`File.ReadAllText`, `Environment.GetEnvironmentVariable`) fits within this scope; the
`ILogger?` overload would require importing `Microsoft.Extensions.Logging.Abstractions`,
which ships in-box with .NET 10's BCL and adds no new NuGet dependency.

**Recommendation:** Move `ResolveApiKey(string, ILogger?)` (and its `ILogger`-less
overload) into a `GalaxySecretRef` static class in this project; update both call sites
to delegate to it.

**Resolution:** Resolved 2026-06-20 — extracted the four-form resolver into a new
`public static class GalaxySecretRef` (`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Contracts/GalaxySecretRef.cs`,
namespace `ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config`) as a single
`ResolveApiKey(string secretRef, ILogger? logger = null)` method (the two former
`GalaxyDriver` overloads collapsed into one optional-logger signature). The exact
resolution semantics are preserved byte-for-byte: `env:NAME` (throws when unset),
`file:PATH` (throws when missing/empty, trims), `dev:KEY` (literal, no warning), and
the back-compat literal arm (returns the literal and emits the same `Warning` when a
logger is supplied). The Contracts `.csproj` gained a single
`Microsoft.Extensions.Logging.Abstractions` PackageReference for the `ILogger`
parameter. Both call sites now delegate: `GalaxyDriver.BuildClientOptions` calls
`GalaxySecretRef.ResolveApiKey(gw.ApiKeySecretRef, _logger)` and the two private
`GalaxyDriver.ResolveApiKey` overloads are deleted; `GalaxyDriverBrowser.BuildClientOptions`
likewise delegates (passing its non-null `_logger`) and its private copy is deleted. No
migration, no public wire-contract change. Regression coverage: `GalaxyDriverApiKeyResolverTests`
was repointed at `GalaxySecretRef.ResolveApiKey` (all 10 facts green). This finding and
the sibling Driver.Galaxy.Browser-003 are closed by this one extraction.
