fix(alarms): atomic InactiveUnacknowledged + flagged Clear/Acknowledge first sight for OpcUaClient (#657)
After an OpcUaClient respawn an upstream inactive+unacked condition went out as Retrigger+Clear (duplicate Activated+Cleared rows, Active flicker), and a first-sight inactive+acked one as Unspecified (an Activated row for an inactive alarm; a retained-Active host condition never cleared). - New AlarmTransitionKind.InactiveUnacknowledged: one projection, rows only for what changed (Activated+Cleared from acked, Cleared from active+unacked, none when already inactive+unacked). - OpcUaClient uses it for moves into inactive+unacked; first sight of an inactive condition is InactiveUnacknowledged or Clear+Acknowledge, flagged. - Projector: a flagged Acknowledge on an already-acked condition writes no row. - E2E tests through DriverHostActor.ForwardNativeAlarm (red-proven). Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -478,6 +478,58 @@ public class NativeAlarmProjectorTests
|
||||
legacy.ShouldBe([AlarmTransitionKind.Unspecified]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #657 — InactiveUnacknowledged projects inactive+unacked in ONE update from every prior, and writes rows
|
||||
/// only for what changed: "Activated"+"Cleared" from an acknowledged prior (an un-ack needs a new occurrence),
|
||||
/// "Cleared" from Active+unacked, nothing when the condition already shows inactive+unacked. The flag does not
|
||||
/// change it — the rows already describe only real changes.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(false, true, new[] { AlarmTransitionKind.Raise, AlarmTransitionKind.Clear })]
|
||||
[InlineData(true, true, new[] { AlarmTransitionKind.Raise, AlarmTransitionKind.Clear })]
|
||||
[InlineData(true, false, new[] { AlarmTransitionKind.Clear })]
|
||||
[InlineData(false, false, new AlarmTransitionKind[0])]
|
||||
public void InactiveUnacknowledged_projects_inactive_unacked_with_rows_only_for_what_changed(
|
||||
bool priorActive, bool priorAcked, AlarmTransitionKind[] expected)
|
||||
{
|
||||
foreach (var restated in new[] { false, true })
|
||||
{
|
||||
var sut = new NativeAlarmProjector();
|
||||
if (priorActive) sut.Project("n1", Evt(AlarmTransitionKind.Raise));
|
||||
if (priorActive && priorAcked) sut.Project("n1", Evt(AlarmTransitionKind.Acknowledge));
|
||||
if (!priorActive && !priorAcked) sut.Project("n1", Evt(AlarmTransitionKind.InactiveUnacknowledged));
|
||||
|
||||
var snap = sut.Project("n1", Evt(AlarmTransitionKind.InactiveUnacknowledged, restated: restated),
|
||||
out IReadOnlyList<AlarmTransitionKind> rows);
|
||||
|
||||
snap.Active.ShouldBeFalse();
|
||||
snap.Acknowledged.ShouldBeFalse();
|
||||
rows.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #657 — a flagged Acknowledge on an already-acknowledged condition (OpcUaClient's first-sight Clear +
|
||||
/// Acknowledge of an inactive+acked condition) writes no row; the unflagged control and a flagged Acknowledge
|
||||
/// of an UNACKED condition both write one.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void A_restated_Acknowledge_on_an_acked_condition_writes_no_row()
|
||||
{
|
||||
var sut = new NativeAlarmProjector();
|
||||
sut.Project("cold", Evt(AlarmTransitionKind.Acknowledge, restated: true), out IReadOnlyList<AlarmTransitionKind> cold);
|
||||
cold.ShouldBeEmpty();
|
||||
|
||||
sut.Project("cold", Evt(AlarmTransitionKind.Acknowledge), out IReadOnlyList<AlarmTransitionKind> unflagged);
|
||||
unflagged.ShouldBe([AlarmTransitionKind.Acknowledge]);
|
||||
|
||||
sut.Project("n2", Evt(AlarmTransitionKind.Raise));
|
||||
var snap = sut.Project("n2", Evt(AlarmTransitionKind.Acknowledge, restated: true), out IReadOnlyList<AlarmTransitionKind> real);
|
||||
real.ShouldBe([AlarmTransitionKind.Acknowledge]);
|
||||
snap.Acknowledged.ShouldBeTrue();
|
||||
snap.Active.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private static AlarmEventArgs Evt(
|
||||
AlarmTransitionKind kind,
|
||||
AlarmSeverity sev = AlarmSeverity.High,
|
||||
|
||||
Reference in New Issue
Block a user