# 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 | 1 | ## 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 | Open | **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:** _(empty until closed — deferred: refactor affects driver and browser project references; no wire or public-contract change, but requires updating ProjectReference entries in two .csproj files and adjusting the Browser's using directive)_ --- ### 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`.