fix(alarms): #647 re-review — re-statement needs the same message; CNC refusal is not comms loss; worst-of health
R1: NativeAlarmProjector treats Raise-on-Active as a re-statement only when the message matches what
the condition shows; a different alarm appearing during a respawn un-acks and writes its alerts row.
FOCAS condition messages are a deterministic function of the matched set (tested across fresh driver
instances and reordered lists); Runtime E2E now covers both the same alarm and a new one during a
respawn. Residual (source message determinism) documented in AlarmTracking.md.
R2: a non-OK cnc_rdalmmsg2 return is thrown as FocasWireException{Rc} and classified as a CNC
refusal: conditions get a quality-only BadNotSupported once, one Warning, polling continues, and
driver health is untouched (no 90 s watchdog reconnect loop on a production control). Only transport
faults degrade (falsification control in the test).
R3: alarm-path health is tracked apart from the value path and combined in GetHealth: worst state,
later LastSuccessfulRead. A good value read can no longer mask a failing alarm read (no per-poll
/hosts flip) and a good alarm read cannot mask a value Degraded.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -57,9 +57,15 @@ same condition is wired as an **event notifier of every referencing equipment fo
|
||||
- **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), and so did FOCAS (`"{host}#{Type}:{Number}"`, fixed under #647 — one condition per `ALARM:` tag, see
|
||||
`drivers/FOCAS.md`). A `Raise` on a condition already projected Active is a **re-statement** (#647 review): the
|
||||
ack is kept and no second "Activated" row is published — a respawned driver re-reading a still-active alarm sends
|
||||
exactly that. Only `Retrigger` (or inactive → active) un-acks. A native source must also set `Kind`: an
|
||||
`drivers/FOCAS.md`). A `Raise` on a condition already projected Active **with the same message** is a
|
||||
**re-statement** (#647 review): the ack is kept and no second "Activated" row is published — a respawned driver
|
||||
re-reading a still-active alarm sends exactly that. A Raise with a DIFFERENT message is a new statement and un-acks:
|
||||
a respawned driver cannot tell "the same alarm" from "a different one that appeared while it was down", so the
|
||||
host compares what the condition shows. Only `Retrigger` (or inactive → active, or a changed-message Raise) un-acks.
|
||||
⚠️ **Residual:** the comparison is only as good as the source's message determinism. FOCAS and MTConnect messages
|
||||
are deterministic for an unchanged statement; a source whose message carries volatile text (a timestamp, a
|
||||
counter) would un-ack on every respawn — conservative, never hiding an alarm — and a source whose message omits
|
||||
what changed (a new alarm under identical text) would keep a stale ack across a respawn. A native source must also set `Kind`: an
|
||||
`Unspecified` kind projects no state change.
|
||||
|
||||
### Native condition state survives a full rebuild (Gitea #641)
|
||||
|
||||
+15
-3
@@ -405,16 +405,28 @@ it, then author `ALARM:T<code>`. Number ranges are series-independent.
|
||||
- **…but an unreachable source says so.** The first failed read of an outage annotates every subscribed condition
|
||||
on that device `BadNoCommunication` through the #626 quality-only path (no state change, no `/alerts` row) and
|
||||
logs ONE Warning naming `cnc_rdalmmsg2`; the next successful read lifts it to Good (before any transition it
|
||||
also finds, e.g. Good then Clear for an alarm that cleared during the outage). Failed alarm reads also reach
|
||||
also finds, e.g. Good then Clear for an alarm that cleared during the outage). Transport failures also reach
|
||||
driver health: `Degraded` with `LastSuccessfulRead` frozen (only a successful read advances it), so a driver
|
||||
whose only tags are `ALARM:` tags trips the comms-loss watchdog (#527) and the host's driver-wide #477
|
||||
annotation like any other.
|
||||
- **A CNC refusal is not comms loss.** A non-OK FOCAS return from `cnc_rdalmmsg2` (the CNC answered and declined,
|
||||
e.g. `EW_FUNC`) marks the conditions `BadNotSupported` (quality-only, once), logs one Warning, and **leaves driver
|
||||
health alone** — polling continues at the normal cadence. Treating it as comms loss would make the #527 watchdog
|
||||
reconnect a production control every 90 s forever. Only a transport fault (session down, wire fault, malformed
|
||||
reply) degrades.
|
||||
- **Health is the worst of the value path and the alarm path, computed together** (`GetHealth`). A failing alarm
|
||||
read keeps a mixed driver `Degraded` however many value reads succeed (so `/hosts` does not flip per poll), and a
|
||||
good alarm read cannot mask a value-path `Degraded`. `LastSuccessfulRead` is the later of the two paths' last
|
||||
successes, so the watchdog fires only when both have stopped.
|
||||
- **Diffed against what the server was TOLD**, recorded only while a handler is attached. The record lives on the
|
||||
driver instance: a reconnect (same-set re-subscribe) and an in-place re-initialise (Force Reconnect, #630) re-raise
|
||||
nothing. **A deploy that changes the driver's config respawns it** (the planner stops + recreates the driver, a new
|
||||
instance with an empty record), and its first poll sends `Raise` for every still-active alarm. The host treats a
|
||||
`Raise` on a condition it already shows Active as a **re-statement**: the acknowledgement is kept and no second
|
||||
"Activated" `/alerts` row is written (`NativeAlarmProjector`, #647 review). Only `Retrigger` — or inactive →
|
||||
`Raise` on a condition it already shows Active **with the same message** as a **re-statement**: the
|
||||
acknowledgement is kept and no second "Activated" `/alerts` row is written (`NativeAlarmProjector`, #647 review).
|
||||
The condition message is a deterministic function of the matched set (winner, type code, axes, `[+N more]`), so a
|
||||
true re-statement matches exactly, while an alarm set that changed during the respawn — including one that only
|
||||
LOST a member — reads as a new statement and un-acks (the conservative direction). Only `Retrigger` — or inactive →
|
||||
active — un-acknowledges, which is why a genuinely new CNC alarm in a matched set is sent as `Retrigger`.
|
||||
- **Polling:** one `cnc_rdalmmsg2` per `AlarmProjection.PollInterval` (default 2 s) for each device that owns a
|
||||
**subscribed** alarm tag; devices without one are never polled for alarms.
|
||||
|
||||
Reference in New Issue
Block a user