feat(native-alarms): per-binding IsMesReportable on native alarm source bindings (ScadaBridge#59)

TemplateNativeAlarmSource.IsMesReportable + tri-state InstanceNativeAlarmSourceOverride
.IsMesReportableOverride flatten onto ResolvedNativeAlarmSource; NativeAlarmActor stamps every
mirrored condition and InstanceActor the quiet-binding placeholder, so ScriptAlarm,
Alarms.MesReportableAsync() and the #60 wire fields carry it. Additive to the 900-999 severity band.
Migration AddNativeAlarmSourceMesReportable. Management commands, CLI --mes-reportable, Central UI
(template dialog checkbox, instance override select), Transport DTOs/importer/diff and the revision
hash (true-only, so existing hashes are unchanged) carry it.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 15:09:54 -05:00
parent 69a2017000
commit f98369a786
38 changed files with 2579 additions and 43 deletions
@@ -51,7 +51,7 @@ public class InstanceActorNativeAlarmTests : TestKit, IDisposable
NullLogger<InstanceActor>.Instance,
dclManager)));
private static FlattenedConfiguration ConfigWithNativeSource(string instanceName) => new()
private static FlattenedConfiguration ConfigWithNativeSource(string instanceName, bool mesReportable = false) => new()
{
InstanceUniqueName = instanceName,
NativeAlarmSources =
@@ -60,7 +60,8 @@ public class InstanceActorNativeAlarmTests : TestKit, IDisposable
{
CanonicalName = "Pressure",
ConnectionName = "Opc",
SourceReference = "ns=2;s=T01"
SourceReference = "ns=2;s=T01",
IsMesReportable = mesReportable
}
]
};
@@ -100,6 +101,22 @@ public class InstanceActorNativeAlarmTests : TestKit, IDisposable
a.SourceReference == "T01.Hi" && a.Kind == AlarmKind.NativeOpcUa && a.Condition.Severity == 800);
}
/// <summary>Gitea #59: the quiet-binding placeholder row carries the binding's MES flag.</summary>
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Snapshot_QuietNativeBinding_PlaceholderCarriesBindingMesFlag(bool flag)
{
var dcl = CreateTestProbe();
var actor = CreateInstanceActorWithDcl("inst", ConfigWithNativeSource("inst", flag), dcl.Ref);
dcl.ExpectMsg<SubscribeAlarmsRequest>();
actor.Tell(new SubscribeDebugViewRequest("inst", "c"));
var snap = ExpectMsg<DebugViewSnapshot>();
Assert.Equal(flag, Assert.Single(snap.AlarmStates, a => a.IsConfiguredPlaceholder).IsMesReportable);
}
/// <summary>Verifies that a quiet native binding emits a placeholder in the snapshot.</summary>
[Fact]
public void Snapshot_QuietNativeBinding_EmitsPlaceholder()