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:
@@ -237,7 +237,7 @@ public class NativeAlarmProjectorTests
|
||||
first.ShouldBeFalse("inactive → active is a real transition");
|
||||
sut.MarkAcknowledged("n1");
|
||||
|
||||
var restated = sut.Project("n1", Evt(AlarmTransitionKind.Raise, msg: "again"), out var restatement);
|
||||
var restated = sut.Project("n1", Evt(AlarmTransitionKind.Raise), out var restatement);
|
||||
restatement.ShouldBeTrue();
|
||||
restated.Active.ShouldBeTrue();
|
||||
restated.Acknowledged.ShouldBeTrue("a re-statement must not un-acknowledge");
|
||||
@@ -247,6 +247,27 @@ public class NativeAlarmProjectorTests
|
||||
retriggered.Acknowledged.ShouldBeFalse("a Retrigger is a new statement and un-acks");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #647 re-review R1 — a respawned driver cannot tell "the same alarm" from "a different one that
|
||||
/// appeared while it was down"; its Raise is a re-statement only when its message matches what the condition
|
||||
/// shows. A different message is a new statement: it un-acks and is not flagged (so the alerts row is written).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void A_Raise_on_an_active_condition_with_a_different_message_is_a_new_statement()
|
||||
{
|
||||
var sut = new NativeAlarmProjector();
|
||||
sut.Project("n1", Evt(AlarmTransitionKind.Raise, msg: "401 SERVO (type 4 Servo, axis 1)"));
|
||||
sut.MarkAcknowledged("n1");
|
||||
|
||||
var same = sut.Project("n1", Evt(AlarmTransitionKind.Raise, msg: "401 SERVO (type 4 Servo, axis 1)"), out var r1);
|
||||
r1.ShouldBeTrue();
|
||||
same.Acknowledged.ShouldBeTrue();
|
||||
|
||||
var other = sut.Project("n1", Evt(AlarmTransitionKind.Raise, msg: "506 OVER TRAVEL (type 2 Overtravel, axis 2)"), out var r2);
|
||||
r2.ShouldBeFalse("a different alarm is not a re-statement");
|
||||
other.Acknowledged.ShouldBeFalse("the operator has not seen this alarm");
|
||||
}
|
||||
|
||||
private static AlarmEventArgs Evt(
|
||||
AlarmTransitionKind kind,
|
||||
AlarmSeverity sev = AlarmSeverity.High,
|
||||
|
||||
Reference in New Issue
Block a user