# Code Review — Driver.OpcUaClient.Contracts

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

## Checklist coverage

| # | Category | Result |
|---|---|---|
| 1 | Correctness & logic bugs | Driver.OpcUaClient.Contracts-001 |
| 2 | OtOpcUa conventions | Driver.OpcUaClient.Contracts-002 |
| 3 | Concurrency & thread safety | No issues found |
| 4 | Error handling & resilience | No issues found |
| 5 | Security | No issues found |
| 6 | Performance & resource management | No issues found |
| 7 | Design-document adherence | Driver.OpcUaClient.Contracts-002 |
| 8 | Code organization & conventions | Driver.OpcUaClient.Contracts-003 |
| 9 | Testing coverage | No test project — no issues to record |
| 10 | Documentation & comments | No issues found |

## Findings

### Driver.OpcUaClient.Contracts-001

| Field | Value |
|---|---|
| Severity | Medium |
| Category | Correctness & logic bugs |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts/NamespaceMap.cs:118-134` |
| Status | Resolved |

**Description:** `TryResolve` accepts `currentSession` as a non-nullable `ISession` parameter but contains a runtime null guard (`if (currentSession is null || …) return false`). The non-nullable annotation tells callers that null is invalid, yet the implementation silently returns false on null — masking the bug that prompted the guard. More practically: on the driver's reconnect path `_namespaceMap` is rebuilt under `_probeLock`, but `TryResolve` is called outside that lock (at the read/write call-site). A session handed to `TryResolve` concurrently with a session swap could be non-null at the call site but torn-down inside the call; the silent false return gives the caller no indication that resolution failed due to a racing session swap versus a genuinely unknown NodeId. The `out NodeId nodeId` parameter contract also does not document that callers must use `NodeId.IsNull(nodeId)` (OPC UA SDK idiom) rather than `nodeId == null` (C# null) to test the out-value — no current caller makes this mistake, but the distinction is non-obvious.

**Recommendation:** Change the parameter to `ISession? currentSession` (nullable) so the annotation matches reality and the compiler guides callers. Add XML-doc on the `out nodeId` parameter specifying `NodeId.IsNull(nodeId)` is the correct test.

**Resolution:** Resolved 2026-06-19, verified by build (no test project). Changed `ISession currentSession` to `ISession? currentSession` so the nullable annotation matches the runtime guard. Expanded XML doc on `currentSession` to explain the defensive-null contract and on `nodeId` to specify `NodeId.IsNull(nodeId)` as the correct test. Browser and runtime driver both build clean (0 errors, 0 warnings) with `TreatWarningsAsErrors=true`.

---

### Driver.OpcUaClient.Contracts-002

| Field | Value |
|---|---|
| Severity | Low |
| Category | OtOpcUa conventions / Design-document adherence |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts.csproj:9` |
| Status | Deferred |

**Description:** The Contracts project carries a direct `PackageReference` to `OPCFoundation.NetStandard.Opc.Ua.Client` solely to support `NamespaceMap`. This makes the Contracts project not a lightweight DTO/enum assembly: every consumer (AdminUI serializers, future test helpers, config-only tooling) that references Contracts for `OpcUaClientDriverOptions` and its enums also transitively receives the full OPC UA client SDK. Both `OpcUaClientDriver` and `OpcUaClientDriverBrowser` already reference the SDK directly and could host `NamespaceMap` without any new dependency. Moving `NamespaceMap` to the runtime driver (or to a dedicated shared helper project referenced by both driver and browser) would restore the Contracts project to its intended SDK-free role.

**Recommendation:** Move `NamespaceMap` out of Contracts into `ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient` (or an internal shared helper). Remove the `OPCFoundation.NetStandard.Opc.Ua.Client` reference from the Contracts `.csproj`. `NamespaceMap` is not part of the public wire contract; it is an implementation detail of the driver's namespace-stability mechanism.

**Resolution:** Deferred 2026-06-20. Investigated the relocation during the open-findings resolution pass. `NamespaceMap` is consumed by the runtime driver (`Driver.OpcUaClient`) AND the Browser (`Driver.OpcUaClient.Browser`), but the Browser references **only** `Driver.OpcUaClient.Contracts` — it has no ProjectReference to the runtime driver. So the recommended "move it into `Driver.OpcUaClient`" would break the Browser's compile. The only relocation that keeps both consumers building without the Browser taking a runtime-driver dependency is a brand-new dedicated shared-helper project referenced by both — a structural change (new `.csproj`, solution-file edit, two ProjectReference rewrites) disproportionate to a Low architectural-cleanliness finding whose only cost is the OPC UA SDK being transitively visible to Contracts consumers (AdminUI already references the SDK directly via the driver anyway). Left Deferred until an OpcUaClient project-layout refactor is undertaken for an independent reason; revisit then.

---

### Driver.OpcUaClient.Contracts-003

| Field | Value |
|---|---|
| Severity | Low |
| Category | Code organization & conventions |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Contracts/OpcUaClientDriverOptions.cs:131-138`, `:174-176` |
| Status | Resolved |

**Description:** Two inconsistency gaps in the options class:

1. `[Range(1, 60)]` is applied only to `ProbeTimeoutSeconds`. `MaxDiscoveredNodes` and `MaxBrowseDepth` have meaningful minimum bounds (zero or negative values produce silent misbehaviour: `MaxDiscoveredNodes = 0` halts all discovery, `MaxBrowseDepth = 0` skips all browsing). No `[Range]` guards exist for these knobs.

2. The `[Display(Name = …, Description = …, GroupName = "Diagnostics")]` attribute on `ProbeTimeoutSeconds` is unused dead metadata: the AdminUI page binds the field directly via `InputNumber @bind-Value` and does not read `[Display]` attributes. No UI scaffold, reflection-based form generator, or API documentation tool in the codebase consumes it. Having a `[Display]` on one property of 18 properties creates a false impression that more of the class is attributed.

**Recommendation:** Add `[Range(1, int.MaxValue)]` to `MaxDiscoveredNodes` and `MaxBrowseDepth`. Remove the `[Display]` from `ProbeTimeoutSeconds` (retaining `[Range(1, 60)]` which is enforced by the AdminUI's `DataAnnotationsValidator`).

**Resolution:** Resolved 2026-06-19, verified by build (no test project). Added `[Range(1, int.MaxValue)]` to `MaxDiscoveredNodes` and `MaxBrowseDepth`. Removed the dead `[Display(…)]` attribute from `ProbeTimeoutSeconds`; `[Range(1, 60)]` retained. All three projects (Contracts, Driver, Browser) build clean with `TreatWarningsAsErrors=true`.
