feat(streaming): carry IsMesReportable on AlarmStateUpdate (25) and DebugAlarmStateDto (20) (ScadaBridge#60)

Central's Debug View live stream and Alarm Summary cache always saw false
because neither wire message had the field. Additive proto fields, vendored
C# regenerated with docker/regen-proto.sh (sitestream + sitecommand), mapped
both ways in StreamRelayActor/SiteStreamGrpcClient and SiteCommandDtoMapper.
The live stream publishes Alarm Actor events, which never carried the flag,
so InstanceActor.HandleAlarmStateChanged now stamps it before retaining and
publishing. A pre-#60 site sends the proto3 default. No UI badge yet.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 12:31:37 -05:00
parent 6bd1fd3072
commit 5310fd0e26
14 changed files with 489 additions and 294 deletions
@@ -1625,6 +1625,17 @@ public class InstanceActor : ReceiveActor
/// </summary>
private void HandleAlarmStateChanged(AlarmStateChanged changed)
{
// Gitea #60: stamp the deployed MES-reportable flag before the event is retained
// AND published, so the live stream (Debug View, central's Alarm Summary cache)
// carries the same value the snapshot does. Alarm Actors never set it themselves.
// A native condition has no _alarmMesReportable entry and passes through as-is.
if (!changed.IsMesReportable
&& _alarmMesReportable.TryGetValue(changed.AlarmName, out var mesReportable)
&& mesReportable)
{
changed = changed with { IsMesReportable = true };
}
_alarmStates[changed.AlarmName] = changed.State;
_alarmTimestamps[changed.AlarmName] = changed.Timestamp;
// Retain the full enriched event (Kind, Condition, native metadata) so the
@@ -1713,11 +1724,12 @@ public class InstanceActor : ReceiveActor
/// </summary>
private List<AlarmStateChanged> BuildAlarmStatesSnapshot()
{
// The MES-reportable flag is stamped HERE and nowhere else (not at the Alarm
// Actor's emit sites), so the retained-event rows below and the fallback rows
// that follow always agree on it. A native condition has no _alarmMesReportable
// entry and is returned as-is (no record copy) — the default false is the intended
// result, and native rows dominate this list on a busy binding.
// The MES-reportable flag is stamped by the Instance Actor, never at the Alarm
// Actor's emit sites: HandleAlarmStateChanged stamps retained/published events
// (Gitea #60) and this projection re-applies it so the retained-event rows and
// the fallback rows below always agree on it. A native condition has no
// _alarmMesReportable entry and is returned as-is (no record copy) — the default
// false is the intended result, and native rows dominate this list on a busy binding.
var states = _latestAlarmEvents.Values
.Select(e => _alarmMesReportable.TryGetValue(e.AlarmName, out var mes) && mes
? e with { IsMesReportable = true }