docs(alarms): state #478 coverage boundary + file Layer-4 comms-loss follow-up (#481)
v2-ci / build (pull_request) Successful in 5m42s
v2-ci / unit-tests (pull_request) Failing after 13m11s

Post-implementation review (HIGH finding) noted #478's mux-delivered
input-quality path does not cover a driver comms-loss: a poll driver
(Modbus/S7) whose device goes unreachable emits only ConnectivityChanged and
goes silent on the value feed, so a scripted alarm keeps the last Good value.
The code as shipped faithfully implements #478's written scope (worst of input
tags' qualities via the dependency mux). The comms-loss bridge for scripted
alarms (symmetric of native #477-L2, plus the null-value/cold-start asymmetry
and its VT-quality ripple) is tracked as #481. Docs updated in
AlarmTracking.md + the design doc.
This commit is contained in:
Joseph Doherty
2026-07-17 16:07:55 -04:00
parent 8c5e2be92e
commit 043e237dba
2 changed files with 21 additions and 0 deletions
+10
View File
@@ -158,6 +158,16 @@ condition's state?") — mirroring the native OT semantic:
guard's job) — it contributes `Good`, so scripted conditions don't flash `Bad` at every deploy. The first guard's job) — it contributes `Good`, so scripted conditions don't flash `Bad` at every deploy. The first
actually-`Bad` published value flips the bucket and annotates. actually-`Bad` published value flips the bucket and annotates.
**Coverage boundary (#478 as shipped).** Scripted quality tracks input tags whose driver **publishes a
data change carrying a Bad/Uncertain `StatusCode`** (e.g. an OpcUaClient input forwarding a server's
per-item Bad). It does **not** yet cover a driver **comms loss**: a poll driver (Modbus/S7) whose device
goes unreachable emits only `ConnectivityChanged` and goes *silent* on the value feed (see
`DriverInstanceActor.Reconnecting`), so the scripted engine keeps the last-known Good value and the
condition stays `Good`. Bridging driver connectivity into scripted inputs — the symmetric of the native
`OnDriverConnectivityChanged` path above, plus resolving the null-value/cold-start asymmetry (a runtime
`Bad` with a null value is currently indistinguishable from cold start and contributes `Good`) — is tracked
as the Layer-4 follow-up (#481).
Guards: `ScriptedAlarmEngineTests` (transition carries `Uncertain`; `Bad` input with no transition emits Guards: `ScriptedAlarmEngineTests` (transition carries `Uncertain`; `Bad` input with no transition emits
`QualityChanged(Bad)`; restore emits `QualityChanged(Good)`; unchanged bucket emits nothing), `QualityChanged(Bad)`; restore emits `QualityChanged(Good)`; unchanged bucket emits nothing),
`ScriptedAlarmSourceTests.QualityChanged_emission_raises_no_alarm_event`, `ScriptedAlarmSourceTests.QualityChanged_emission_raises_no_alarm_event`,
@@ -174,3 +174,14 @@ So even a `Bad` driver value reached the scripted engine as Good — Layer 3 has
`ScriptedAlarmSource``QualityChanged` raises no `OnAlarmEvent`. Mux — `DependencyValueChanged` carries the `ScriptedAlarmSource``QualityChanged` raises no `OnAlarmEvent`. Mux — `DependencyValueChanged` carries the
published quality. Host — `Bad` dependency → `AlarmQualityUpdate(Bad)`, no `/alerts` publish; `ToSnapshot` published quality. Host — `Bad` dependency → `AlarmQualityUpdate(Bad)`, no `/alerts` publish; `ToSnapshot`
maps the event's worst quality. maps the event's worst quality.
**Coverage boundary → Layer 4 (#481).** L3 covers inputs whose driver **publishes a Bad/Uncertain-status
data change** (the mux quality path). It does **not** cover a driver **comms loss**: a poll driver
(Modbus/S7) whose device goes unreachable emits only `ConnectivityChanged` and goes silent on the value feed
(`DriverInstanceActor.Reconnecting`), so the scripted engine keeps the last-known Good value and the
condition stays Good — the same silent-feed problem native solved in L2, but native's `OnDriverConnectivityChanged`
bridge fans only to **native** condition nodes (`_alarmNodeIdByDriverRef`), not into the mux the scripted
engine reads. Bridging connectivity into scripted inputs — plus resolving the null-value/cold-start
asymmetry (a runtime `Bad` with a null value is currently indistinguishable from cold start and contributes
`Good`) and its ripple into virtual-tag quality — is **Gitea #481 (Layer 4)**. Found by the post-implementation
code review; the code as shipped faithfully implements #478's written scope (mux-delivered input quality).