docs(alarms): native condition state across a rebuild, projector prune, exact ConditionId routing (#641)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 17:09:24 -04:00
parent 64d2514e2a
commit bd23f557f3
2 changed files with 52 additions and 2 deletions
+45
View File
@@ -54,6 +54,51 @@ same condition is wired as an **event notifier of every referencing equipment fo
- The `alerts`-topic `AlarmTransitionEvent` carries the (possibly empty) **list of referencing
equipment paths**, and the AdminUI `/alerts` page shows **one row per condition** (primary
identity RawPath + condition NodeId) with the equipment list as display metadata.
- **Routing is an exact `ConditionId == RawPath` match** (`DriverHostActor.ForwardNativeAlarm`). A driver that
decorates the id has every transition dropped as "no alarm condition" — AbCip did (`"{ref}#active"`, fixed
under #641); FOCAS still does (see `drivers/FOCAS.md`). A native source must also set `Kind`: an
`Unspecified` kind projects no state change.
### Native condition state survives a full rebuild (Gitea #641)
A full `RebuildAddressSpace` recreates every condition inactive + acknowledged. For **scripted** alarms the
engine re-asserts every non-normal condition after the reload (#487). **Native** conditions had nothing
equivalent: the host re-subscribes a driver's alarms only when its alarm-ref *set* changes, and a
transition-only source (Galaxy, OpcUaClient) has nothing to replay anyway — so a rebuild that left the alarm
set unchanged (adding one `UnsTagReference` to an alarm-bearing raw tag is enough to force one, as are
`ChangedEquipment`, a scripted-alarm edit, a raw container rename, an alarm severity/type edit, or any
surgical fallback to `SafeRebuild()`) left every active native alarm reading **normal** until its next device
transition.
The node manager now carries native state across the rebuild itself:
1. `RebuildAddressSpace` captures each **native** condition's live state off the **node** — Active, Acked,
Confirmed, Severity, Message, Quality, Time, `ActiveState.TransitionTime` — before clearing. Reading the
node, not a host-side cache, is what keeps a **client's** acknowledgement: the SDK applies it to the node
and neither the driver nor `NativeAlarmProjector` ever sees it.
2. `MaterialiseAlarmCondition(isNative: true)` for the same id in the same apply restores it. **Quality is
always restored** (it is otherwise re-annotated only on a connectivity edge, so even a normal condition
would sit at `BadWaitingForInitialData`); the rest only when the condition was off-normal (active or
unacknowledged). A normal condition keeps what the materialise built — message and authored severity
included — exactly the #487 rule. Shelving/Enabled are not carried (native conditions refuse them, #587).
3. `IOpcUaAddressSpaceSink.CompleteAddressSpaceApply`, which `OpcUaPublishActor` calls at the end of
**every** apply (in a `finally`), reports **one** condition event per restored condition — after the
notifier wiring, so the raw folder, every referencing equipment folder and `i=2253` items all get it,
stamped with the original `Time`, not the deploy time — and **discards** every capture no materialise
claimed, so a later deploy re-adding the same RawPath gets a fresh node (no resurrection). A kind swap
(native ↔ scripted on one id) is never restored. Node-only: no `/alerts` row, no historian record.
Two companions keep the host and driver in step with the carried node:
- `DriverHostActor` **prunes** `NativeAlarmProjector` to the still-deployed conditions on each apply
(`RetainOnly`) instead of clearing it. Clearing made the next driver Acknowledge of an active alarm
project `Active=false`, and the next Clear of an unacknowledged one project `Acked=true` — after *any*
deploy, rebuild or not.
- MTConnect's "last told" record is pruned to the conditions that **left** the subscribed set rather than
cleared on any set change; re-raising a surviving (carried) condition would un-acknowledge it.
Accepted residual: a condition that clears at the device while the rebuild runs is restored active until
the driver's Clear arrives — which it does, because the driver's own record still holds it active.
## Condition event identity fields (what a client reads on the wire)