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
@@ -227,6 +227,7 @@ public sealed class EntitySerializerTests
IsLocked = true,
IsInherited = true,
LockedInDerived = true,
IsMesReportable = true,
});
var aggregate = MakeEmptyAggregate() with { Templates = new[] { basic } };
@@ -242,6 +243,7 @@ public sealed class EntitySerializerTests
Assert.True(dtoSrc.IsLocked);
Assert.True(dtoSrc.IsInherited);
Assert.True(dtoSrc.LockedInDerived);
Assert.True(dtoSrc.IsMesReportable);
var rtSrc = Assert.Single(Assert.Single(sut.FromBundleContent(dto).Templates).NativeAlarmSources);
Assert.Equal("Boiler", rtSrc.Name);
@@ -252,6 +254,27 @@ public sealed class EntitySerializerTests
Assert.True(rtSrc.IsLocked);
Assert.True(rtSrc.IsInherited);
Assert.True(rtSrc.LockedInDerived);
Assert.True(rtSrc.IsMesReportable);
}
/// <summary>Gitea #59: an instance native-source override round-trips its tri-state MES flag.</summary>
[Theory]
[InlineData(true)]
[InlineData(false)]
[InlineData(null)]
public void Roundtrip_instance_NativeAlarmSourceOverride_preserves_IsMesReportableOverride(bool? flag)
{
var inst = new Instance("I1") { Id = 1, TemplateId = 1, SiteId = 1 };
inst.NativeAlarmSourceOverrides.Add(new InstanceNativeAlarmSourceOverride("Boiler")
{ InstanceId = 1, IsMesReportableOverride = flag });
var aggregate = MakeEmptyAggregate() with { Instances = new[] { inst } };
var sut = new EntitySerializer();
var dto = sut.ToBundleContent(aggregate);
Assert.Equal(flag, Assert.Single(Assert.Single(dto.Instances).NativeAlarmSourceOverrides).IsMesReportableOverride);
var rt = Assert.Single(sut.FromBundleContent(dto).Instances);
Assert.Equal(flag, Assert.Single(rt.NativeAlarmSourceOverrides).IsMesReportableOverride);
}
/// <summary>Verifies that old-form bundle JSON without the locked-in-derived field deserializes it as false.</summary>