fix(code-review): resolve Batch 3 wave A (OpcUaServer history/guard, ControlPlane topology gate)
- OpcUaServer-002: HistoryRead-Events NumValuesPerNode==0 now maps to unbounded (int.MaxValue) instead of the backend default-cap sentinel; no Core.Abstractions contract change (+EventMaxEvents helper tests) - OpcUaServer-004: EnsureAddressSpaceCreated guard on public mutators -> clear InvalidOperationException instead of bare NRE if called pre-start (+tests) - OpcUaServer-003: Deferred (endUtc inclusive/exclusive needs live Wonderware boundary confirmation) - Configuration-013: wire DraftValidator.ValidateClusterTopology into AdminOperationsActor deploy gate (read-only, no migration) (+2 tests)
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
| Review date | 2026-06-19 |
|
||||
| Commit reviewed | `7286d320` |
|
||||
| Status | Reviewed |
|
||||
| Open findings | 4 |
|
||||
| Open findings | 1 |
|
||||
|
||||
## Checklist coverage
|
||||
|
||||
@@ -72,7 +72,7 @@ which is outside this module's edit boundary.
|
||||
| Severity | Medium |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Location | `OtOpcUaNodeManager.cs:1748` (`HistoryReadEvents`), `OtOpcUaNodeManager.cs:1814` (`ClampToInt`) |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
|
||||
**Description:** For HistoryRead-Events, `HistoryReadEvents` passes
|
||||
`ClampToInt(details.NumValuesPerNode)` to `IHistorianDataSource.ReadEventsAsync(maxEvents)` and
|
||||
@@ -93,7 +93,21 @@ backend truncation and surface a continuation point / `GoodMoreData` for events.
|
||||
event backends (cross-module, Core.Abstractions contract); option (b) requires the backend to report
|
||||
truncation. Both cross this module's boundary.
|
||||
|
||||
**Resolution:** _(Open — deferred: rooted in the cross-module `IHistoryProvider.ReadEventsAsync` `maxEvents <= 0` sentinel contract (Core.Abstractions-006) and the Wonderware/OpcUaClient event backends; cannot be fixed safely inside OpcUaServer alone.)_
|
||||
**Resolution:** Resolved — 2026-06-20 (SHA pending): fixed locally inside OpcUaServer without touching
|
||||
the cross-module `IHistoryProvider.ReadEventsAsync` `maxEvents <= 0` sentinel. Added a small testable
|
||||
`internal static int EventMaxEvents(uint numValuesPerNode)` helper next to `ClampToInt` that translates
|
||||
the OPC UA Part 4/11 "no limit" request (`NumValuesPerNode == 0`) to UNBOUNDED (`int.MaxValue`, a very
|
||||
large positive cap) rather than the backend's `<= 0` "use the default cap" sentinel; a positive value
|
||||
still passes through `ClampToInt` unchanged. `HistoryReadEvents` now calls `EventMaxEvents(details.NumValuesPerNode)`
|
||||
instead of `ClampToInt(details.NumValuesPerNode)`, so a "give me the whole window" events read is no
|
||||
longer silently truncated at the backend default. The sentinel contract + the Wonderware/OpcUaClient
|
||||
backends are untouched (a positive `int.MaxValue` is never the `<= 0` sentinel). Tests:
|
||||
`NodeManagerEventMaxEventsTests` (helper purity — `0u→int.MaxValue`, normal passthrough,
|
||||
`>int.MaxValue→int.MaxValue` clamp, exact-`int.MaxValue` boundary) plus
|
||||
`NodeManagerHistoryReadEventsTests.Events_unbounded_request_passes_int_max_to_backend` (the recording fake
|
||||
`IHistorianDataSource` receives `int.MaxValue` when `NumValuesPerNode == 0`). Note: option (b) — surfacing
|
||||
a continuation point / `GoodMoreData` on backend truncation — remains a cross-module/backend change and is
|
||||
out of scope; option (a) here removes the silent-truncation defect for the common "all events" request.
|
||||
|
||||
### OpcUaServer-003
|
||||
|
||||
@@ -102,7 +116,7 @@ truncation. Both cross this module's boundary.
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Location | `OtOpcUaNodeManager.cs:1978` (`ServeRawPaged`), `HistoryPaging.cs` (whole), `HistoryPaging.cs:213` (`SliceTieCluster` `next <= endUtc`) |
|
||||
| Status | Open |
|
||||
| Status | Deferred |
|
||||
|
||||
**Description:** The Raw paging chain treats `endUtc` as an **inclusive** upper bound throughout —
|
||||
the `HistoryContinuationState`/`HistoryPaging` XML docs all say "the original (inclusive) end of
|
||||
@@ -126,7 +140,14 @@ the inclusive/exclusive question requires confirming the Wonderware backend's ac
|
||||
semantics (cross-module / infra), and changing a comparison without that confirmation risks the
|
||||
opposite off-by-one.
|
||||
|
||||
**Resolution:** _(Open — deferred: needs the backend's authoritative endUtc boundary semantics confirmed before the comparison/doc is changed; flipping it blindly risks an off-by-one in the other direction.)_
|
||||
**Resolution:** Deferred — 2026-06-20: infra-gated. Resolving the `endUtc` inclusive-vs-exclusive
|
||||
disagreement requires confirming the actual Wonderware historian backend's boundary semantics, which is
|
||||
hardware/infra-gated and not reachable from this macOS dev host. The impact is benign and bounded — because
|
||||
the backend is the authority on which samples exist (a sample at exactly `endUtc` never appears in an
|
||||
exclusive-end read), the disagreement only ever yields ONE extra empty resume page (`[endUtc, endUtc)` →
|
||||
GoodNoData, no continuation point) rather than any duplicated or dropped data. Changing the
|
||||
`SliceTieCluster` comparison / paging XML docs without confirming the live backend boundary risks
|
||||
introducing the opposite off-by-one, so no code is changed here pending that live confirmation.
|
||||
|
||||
### OpcUaServer-004
|
||||
|
||||
@@ -135,7 +156,7 @@ opposite off-by-one.
|
||||
| Severity | Low |
|
||||
| Category | Error handling & resilience |
|
||||
| Location | `OtOpcUaNodeManager.cs:1597` (`ResolveParentFolder`), and every public sink mutator that calls it (`EnsureFolder` 1278, `EnsureVariable` 1335, `MaterialiseAlarmCondition` 597, plus `WriteValue`/`WriteAlarmCondition` `CreateVariable`) |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
|
||||
**Description:** `ResolveParentFolder` dereferences `_root!` with the null-forgiving operator, and
|
||||
`CreateVariable` uses `_root` (`AddChild`). `_root` is only assigned in `CreateAddressSpace`, which
|
||||
@@ -153,7 +174,19 @@ mutators, so a too-early call fails legibly instead of with a bare NRE. Low prio
|
||||
hardening, not a live defect. Left Open to avoid an unscoped change to the mutator entry points on
|
||||
this critical class without a regression scenario that reproduces the early-call ordering.
|
||||
|
||||
**Resolution:** _(Open — defensive-only; latent given current boot ordering. Deferred to avoid an unscoped guard-add across five mutators without a reproducing pre-start ordering scenario.)_
|
||||
**Resolution:** Resolved — 2026-06-20 (SHA pending): added a private `EnsureAddressSpaceCreated()` helper
|
||||
that throws `InvalidOperationException("OPC UA address space has not been created yet (server not started.)")`
|
||||
when `_root` is null, and call it at the top of `ResolveParentFolder` and at every public address-space
|
||||
mutator entry point (`WriteValue`, `WriteAlarmCondition`, `EnsureFolder`, `EnsureVariable`,
|
||||
`MaterialiseAlarmCondition`) — right after argument validation, before any `_root` dereference. A too-early
|
||||
call (a sink wired or a publish replayed before `StartAsync` drives `CreateAddressSpace`) now fails legibly
|
||||
instead of with a bare NRE out of `ResolveParentFolder` / `CreateVariable`. Happy-path behaviour is
|
||||
unchanged. The guard was test-feasible after all: `NodeManagerPreStartGuardTests` boots a real host,
|
||||
borrows the live node manager's real `IServerInternal`, constructs a SECOND, never-started
|
||||
`OtOpcUaNodeManager` from it (so `_root` is null), and asserts each of the four lock-taking mutators
|
||||
(`EnsureFolder`/`EnsureVariable`/`WriteValue`/`MaterialiseAlarmCondition`) throws `InvalidOperationException`
|
||||
(not NRE), with the folder case asserting the message text. (`WriteAlarmCondition`'s guard is identical and
|
||||
sits on the same path; it is build-verified.) Full `OpcUaServer.Tests` suite green (284/284).
|
||||
|
||||
### OpcUaServer-005
|
||||
|
||||
|
||||
Reference in New Issue
Block a user