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
@@ -50,6 +50,25 @@ public class NativeAlarmSourceNameValidationTests : TestKit
_repo.DidNotReceive().AddTemplateNativeAlarmSourceAsync(Arg.Any<TemplateNativeAlarmSource>(), Arg.Any<CancellationToken>());
}
/// <summary>Gitea #59: the update command carries IsMesReportable onto the persisted binding.</summary>
[Fact]
public void Update_CarriesIsMesReportable()
{
TemplateNativeAlarmSource? updated = null;
_repo.When(r => r.UpdateTemplateNativeAlarmSourceAsync(Arg.Any<TemplateNativeAlarmSource>(), Arg.Any<CancellationToken>()))
.Do(ci => updated = ci.Arg<TemplateNativeAlarmSource>());
_services.AddScoped<ZB.MOM.WW.ScadaBridge.TemplateEngine.TemplateService>();
CreateActor().Tell(Designer(new UpdateTemplateNativeAlarmSourceCommand(
4, "Pressure", "Opc", "ns=2;s=T01", null, null, false, IsMesReportable: true)));
// The reconcile that follows needs TemplateService collaborators this harness does not
// wire, so only the persisted binding is asserted (the Update call precedes the reconcile).
ExpectMsg<object>(TimeSpan.FromSeconds(5));
Assert.NotNull(updated);
Assert.True(updated!.IsMesReportable);
}
/// <summary>Renaming a source to an illegal actor name is rejected before any write.</summary>
[Fact]
public void Update_RenameToInvalidActorName_IsRejected()