feat(opcua): IOpcUaAddressSpaceSink.WriteStatuses — status-only batch write retaining the node value (03/S15)

This commit is contained in:
Joseph Doherty
2026-08-20 10:00:47 -04:00
parent 6874a562f3
commit ec5b48bfcd
7 changed files with 398 additions and 1 deletions
@@ -651,3 +651,21 @@ test could only pass once the production change landed.
- Docs: `docs/drivers/Mqtt.md` §"Quality on connection loss and re-subscribe" (links the contract in
`DriverLifecycle.md`, records the two distinct "not communicating" codes and the operator-visible
behaviour change).
### T6 — `IOpcUaAddressSpaceSink.WriteStatuses` (interface default + node manager + both sinks)
- RED (the guard, first): adding the member to the interface and running
`--filter "…DeferredSinkForwardingReflectionTests"` ⇒ **`Every_forwarding_interface_method_reaches_the_inner_sink`
FAILED**. Note the failure surfaced as `System.ArgumentNullException : Value cannot be null. (Parameter
'statuses')` rather than the guard's "did NOT forward" message — the reflection harness passes `null`
for an `IReadOnlyList<>` parameter, so the *interface default* ran on the wrapper and threw. Red for
the right reason (the member was not forwarded); green once `DeferredAddressSpaceSink.WriteStatuses`
forwards. This is the memory'd "Deferred sink doesn't forward the new member" trap, and here the
default is not merely slower — it writes a **null value**, erasing the very value the annotation exists
to keep.
- RED (node manager): `--filter "…OtOpcUaNodeManagerWriteStatusesTests"` ⇒ **build failure**, `CS1061:
'OtOpcUaNodeManager' does not contain a definition for 'WriteStatuses'` (×5).
- GREEN: 5/5 new; full `OpcUaServer.Tests` 495 passed / 1 skipped; `Commons.Tests` 348/348.
- An unmaterialised NodeId is **skipped and not counted as a failure** (Debug tally via the SDK static
trace — the manager has no `ILogger`): the caller's per-driver index racing a surgical remove is an
expected shape, not a fault.
@@ -41,7 +41,7 @@
{
"id": "T6",
"subject": "IOpcUaAddressSpaceSink.WriteStatuses (StatusWrite, default impl) + OtOpcUaNodeManager status-only batch retaining value + SdkAddressSpaceSink/DeferredAddressSpaceSink forwards (03/S15)",
"status": "pending",
"status": "done",
"blockedBy": []
},
{