fix(r2-04): scripted-alarm alerts emit uses PrimaryGatePolicy (consistency with 03/S4)

This commit is contained in:
Joseph Doherty
2026-07-13 11:10:43 -04:00
parent c6f975b963
commit 1647942414
3 changed files with 104 additions and 11 deletions
@@ -519,6 +519,58 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
evt.TransitionKind.ShouldBe("Activated");
}
/// <summary>Boot-window multi-driver suppression (archreview 03/S4): role unknown + a real driver peer
/// (member count 2) ⇒ the alerts publish is DENIED (default-deny closes the dual-primary window that would
/// historize duplicate AVEVA rows), while the OPC UA node write stays ungated.</summary>
[Fact]
public void Unknown_role_multi_driver_suppresses_alerts_but_still_writes_opcua()
{
var publish = CreateTestProbe();
var mux = CreateTestProbe();
var alerts = CreateTestProbe();
SubscribeToAlerts(alerts);
var (host, _) = SpawnWithMemberCount(publish, mux, LocalNode, driverMemberCount: 2);
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout);
// No snapshot sent ⇒ role unknown; count 2 ⇒ default-DENY.
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
}
/// <summary>Boot-window single-driver emit (archreview 03/S4): role unknown + no driver peer (member
/// count 1) ⇒ the alerts publish proceeds (single-node posture preserved).</summary>
[Fact]
public void Unknown_role_single_driver_publishes_alerts()
{
var publish = CreateTestProbe();
var mux = CreateTestProbe();
var alerts = CreateTestProbe();
SubscribeToAlerts(alerts);
var (host, _) = SpawnWithMemberCount(publish, mux, LocalNode, driverMemberCount: 1);
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout);
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
alerts.ExpectMsg<AlarmTransitionEvent>(Timeout).AlarmId.ShouldBe("alm-1");
}
private (IActorRef Host, DependencyMuxTagUpstreamSource Upstream) SpawnWithMemberCount(
TestProbe publish, TestProbe mux, NodeId localNode, int driverMemberCount)
{
var upstream = new DependencyMuxTagUpstreamSource();
var engine = BuildEngine(upstream);
var host = Sys.ActorOf(ScriptedAlarmHostActor.Props(
publish.Ref, mux.Ref, upstream, engine, localNode, driverMemberCountProvider: () => driverMemberCount));
return (host, upstream);
}
/// <summary>Inbound command ungated by role (T1): the alerts-publish gate must NOT affect inbound
/// command processing. Under a Secondary role, an AlarmCommand("Acknowledge") for an owned, active
/// alarm still drives the engine — observed via the resulting AlarmStateUpdate(Acknowledged=true)