fix(host): run monitored-item revalidation on a low-frequency timer (archreview 03/S19)

This commit is contained in:
Joseph Doherty
2026-08-20 09:55:01 -04:00
parent b09f78396f
commit a4f12bccef
5 changed files with 228 additions and 1 deletions
@@ -687,3 +687,26 @@ for the orchestrator.
- Two stale remarks corrected, not one: `OnAuthorityChanged`'s "this is the only trigger" **and**
`IsSubscriptionStillPermitted`'s "a subscription is re-checked at a deployment, never on a timer".
- OpcUaServer.Tests: 493 passed / 1 skipped.
### T8 — wire the timer into `OtOpcUaServerHostedService`
- **RED observed:** `error CS1061: 'OtOpcUaServerHostedService' does not contain a definition for
'StartRevalidationTimer'` and `error CS1729: … does not contain a constructor that takes 13 arguments`
(the two new ctor parameters).
- **DEVIATION (test seam, and the reason for it):** the plan says "extend the hosted-service tests". **There
are none — `grep -rln OtOpcUaServerHostedService tests/` matched nothing**, because `StartAsync` boots
the real OPC UA SDK (PKI store, listening socket). A new suite,
`OtOpcUaServerHostedServiceRevalidationTests`, drives the timer's lifecycle through an **`internal`**
`StartRevalidationTimer()` (new `InternalsVisibleTo` on the Host csproj, the convention six driver
projects already use). Everything the timer's lifecycle touches — the options, the clock, the disposal —
is exercised exactly as production reaches it; only the SDK boot is skipped.
- **DEVIATION (assertion not made):** the plan asks the test to assert disposal happens **before**
`SetDataPlaneAuthorizer(null)`. That is **not observable**: both detaches go through `_server?.…`, and
`OtOpcUaSdkServer.NodeManager` is created only by the SDK's own `CreateMasterNodeManager` during a real
boot, so with no server both calls are no-ops. The ordering is instead guaranteed structurally — the
dispose is the **first statement** of `StopAsync`, with a comment saying why — and what IS asserted is
that `StopAsync` (and `DisposeAsync`) dispose the timer, with a non-vacuity check that it was live
beforehand. **Reviewer: this is the one plan assertion downgraded rather than met.**
- The node manager is resolved **per tick** (`() => _server?.NodeManager?.Revalidate…`) rather than
captured, so a restart cannot leave the tick walking a dead manager's item map.
- Host.Tests: 132 green.
@@ -59,7 +59,7 @@
{
"id": "T8",
"subject": "Wire the revalidation timer into OtOpcUaServerHostedService (create after StartAsync, dispose FIRST in StopAsync) + ordering test",
"status": "pending",
"status": "done",
"blockedBy": [
"T6",
"T7"