# Code Review — Driver.OpcUaClient.Browser

<!-- Per-module findings file. 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.OpcUaClient.Browser` |
| 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 | Finding 001 (LastUsedUtc not updated in AttributesAsync) |
| 2 | OtOpcUa conventions | No issues found |
| 3 | Concurrency & thread safety | No issues found (disposal race is accepted pattern, same as GalaxyBrowseSession; TTL reaper evicts only idle sessions) |
| 4 | Error handling & resilience | No issues found (BrowseRoot parse failure is caught and session cleaned up; continuation-point cancel-leak is cross-cutting — see 002) |
| 5 | Security | No issues found (separate PKI store correct; AutoAcceptCertificates not forwarded to browse store; no credential logging) |
| 6 | Performance & resource management | Finding 002 (continuation-point server-side leak on cancellation) |
| 7 | Design-document adherence | No issues found |
| 8 | Code organization & conventions | No issues found |
| 9 | Testing coverage | No issues found (unit tests cover pre-connect validation; live tests correctly gated on RequiresOpcPlc) |
| 10 | Documentation & comments | No issues found |

## Findings

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

### Driver.OpcUaClient.Browser-001

| Field | Value |
|---|---|
| Severity | Low |
| Category | Correctness & logic bugs |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/OpcUaClientBrowseSession.cs:66` |
| Status | Resolved |

**Description:** `AttributesAsync` returns `Task.FromResult(Array.Empty<AttributeInfo>())` without updating `LastUsedUtc`. The `IBrowseSession` interface contract (at `src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/IBrowseSession.cs:14-15`) states that `LastUsedUtc` is "Refreshed on `RootAsync`, `ExpandAsync`, and `AttributesAsync`". If the AdminUI calls `AttributesAsync` exclusively for 2+ minutes without an intermediate `RootAsync` or `ExpandAsync` (e.g. in a polymorphic picker context), the `BrowseSessionReaper` may evict an active session early.

Although the OpcUaClient browser never issues a server round-trip in `AttributesAsync` (it always returns empty), the timestamp must be refreshed to honour the contract and keep the session alive while the user is active.

**Recommendation:** Set `LastUsedUtc = DateTime.UtcNow` inside `AttributesAsync` before returning, mirroring the pattern in `BrowseOneLevelAsync`.

**Resolution:** Fixed 2026-06-19 (SHA pending commit) — added `LastUsedUtc = DateTime.UtcNow` to `AttributesAsync` body; regression test `AttributesAsync_updates_LastUsedUtc` added to the unit test project.

---

### Driver.OpcUaClient.Browser-002

| Field | Value |
|---|---|
| Severity | Low |
| Category | Performance & resource management |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/OpcUaClientBrowseSession.cs:112-125` |
| Status | Deferred |

**Description:** If the caller's `CancellationToken` fires during the `BrowseNextAsync` pagination loop, `OperationCanceledException` propagates out of the loop and any in-progress server-side continuation point is never released via `BrowseNext(releaseContinuationPoints: true)`. The server holds the cursor until the session is closed. For the transient AdminUI picker session this is low-risk (the session is short-lived and `DisposeAsync` → `CloseAsync` eventually cleans it up server-side), but it represents a server-side resource leak during cancellation.

The identical pattern exists in the runtime driver (`OpcUaClientDriver.cs`), making this a cross-cutting concern rather than a module-local defect.

**Recommendation:** Catch `OperationCanceledException` in the pagination loop, issue a fire-and-forget `BrowseNext(releaseContinuationPoints: true, continuationPoints: [cp])` before re-throwing, and apply the same fix to `Driver.OpcUaClient`.

**Resolution:** Deferred — awaiting a cross-cutting fix that also updates `Driver.OpcUaClient`. Short session lifetime of the AdminUI picker limits practical impact.

---

## Re-review 2026-06-19

Commit `7e1f34da`. Single targeted finding surfaced during code review.

### Driver.OpcUaClient.Browser-003

| Field | Value |
|---|---|
| Severity | Medium |
| Category | Correctness & logic bugs / OtOpcUa conventions |
| Location | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/OpcUaClientDriverBrowser.cs:19` |
| Status | Resolved |

**Description:** `OpcUaClientDriverBrowser`'s static `JsonOpts` (`JsonSerializerOptions`) was constructed without a `JsonStringEnumConverter`. AdminUI emits enum-valued driver-config fields (`SecurityPolicy`, `SecurityMode`, `AuthType`, `TargetNamespaceKind`) as their **string** names (e.g. `"AuthType":"Certificate"`, `"SecurityPolicy":"Basic256Sha256"`). Without the converter, `System.Text.Json` throws a `JsonException` during deserialization of any such config, surfacing a confusing parse error instead of the browser's own domain-specific validation message.

This is the same systemic enum-serialization bug fixed in the factory (`OpcUaClientDriverFactoryExtensions.JsonOptions`) and probe (`OpcUaClientDriverProbe._opts`), both of which carry the converter with an explicit comment stating the browser must parse configs the same way. The browser was the one site left unpatched.

**Recommendation:** Add `new JsonStringEnumConverter()` to the browser's `JsonSerializerOptions.Converters`, matching the factory/probe pattern exactly. `JsonStringEnumConverter` accepts both string names and numeric ordinals, so existing numeric-authored configs require no migration.

**Resolution:** Fixed 2026-06-19 (SHA `7e1f34da` working tree) — added `new JsonStringEnumConverter()` to `OpcUaClientDriverBrowser.JsonOpts.Converters` and imported `System.Text.Json.Serialization`; regression tests `OpenAsync_with_string_enum_AuthType_deserializes_correctly` and `OpenAsync_with_numeric_enum_AuthType_still_works` added to the unit test project.
