Alarm Severity mixes two directions: MxGateway and computed rows carry priority (lower = more urgent) while OPC UA rows carry Part 9 severity (higher = worse) #129
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while fixing lmxopcua#887 (Galaxy alarm Severity direction). Not a regression from that fix; it makes the OPC UA path (first row below) correct and so exposes the disagreement.
What.
AlarmConditionState.Severityis documented as "the unified 0–1000 severity", but the three sources fill it with two opposite conventions:SeverityOpcUaAlarmMapper.NormalizeSeverity)MxGatewayAlarmMapper.NormalizeSeverity)severity, which mxaccessgw fills from the AVEVA alarm priority (AlarmDispatcher:Severity = record.Priority), clamped 0–1000AlarmConditionStateFactory.ForComputed(state, PriorityLevel))TemplateAlarm.PriorityLevel("lower values indicate higher priority")The consumers all read it as higher = worse:
NativeAlarmActor.ErrorSeverityThreshold = 700: an active native alarm withSeverity >= 700logs asError, elseWarning. On the MxGateway path an AVEVA priority-1 (Critical) alarm logs as Warning, and priority 700–999 (AVEVA Medium/Low) as Error.AlarmSummaryService"Worst Severity" = the maxSeverity;AlarmSummary.razorseverity threshold filter isSeverity >= minand sorts bySeverity. For MxGateway and computed rows that picks the least urgent alarm.The MES 900–999 band (native alarms). Per
docs/plans/2026-06-30-mes-alarm-status-api.md§6.1 the band is configured in the Galaxy alarm priority and tested onSeverity. So:What lmxopcua#887 changed on the OPC UA path (Galaxy via OtOpcUa). AVEVA priority 1–250 → 1000, 251–500 → 750, 501–750 → 500, 751–999 → 250 (was 250/500/750/1000 in the other order, and live used 200/500/700/900). ScadaBridge code has nothing that depends on the old inverted values. No deployed ScadaBridge reads Galaxy alarms through OtOpcUa today: vd03 ScadaBridge has no OPC UA link to OtOpcUa, and vd03 OtOpcUa has no Galaxy driver.
Owner decision needed.
AlarmConditionState.Severity. Recommended: OPC UA's, higher = more severe, since the consumers and the OPC UA path already use it. Map MxGateway priority with the same AVEVA bands OtOpcUa uses (GalaxyAlarmPriority) or a linear1001 - p. Map computedPriorityLevelthe same way, or carry it separately.Severity(keep the 900–999 band on the priority), or move native sources to the binding-levelIsMesReportable(#59) and retire the band.ErrorSeverityThreshold, Alarm Summary worst/filter/sort, and the MESMinSeverity/MaxSeverityfilter semantics to match.Consumer impact: alarm Severity now runs one direction (higher = more urgent)
This is a visible ordering flip for anyone who reads
Severityon an MxAccess Gateway native alarm or a computed alarm. OPC UA rows do not change.What a reader sees after this build
SeveritybeforeSeveritynowPriority(= Severity; OPC UA has no separate priority)Priority(clamped 0–1000, i.e. the old Severity)PriorityLevel)PriorityLevelverbatimPriority(PriorityLevelverbatim)The table is
AlarmPriorityBands. It matches OtOpcUaGalaxyAlarmPriority(lmxopcua#887,b61f3e05) value for value, edge rows and clamps included, so a Galaxy alarm now has the same Severity whether it arrives through OtOpcUa or straight from mxaccessgw. OtOpcUa's history-only "not an alarm" row (AVEVA severity 0 → 1) has no counterpart here. The live gateway feed carries only alarms and only their priority, and a computed alarm is always an alarm. Priority 0 therefore clamps to Critical, as OtOpcUa'sMap(0)does.Who sees the flip
>=) and the severity sort (descending by default) now put AVEVA priority 1 at the top. Before, they put priority 999 at the top. The numbers in the Sev column change for MxAccess and computed rows.sev N). These show the banded number.alarmcategory.NativeAlarmActor): the threshold is unchanged (Severity >= 700→Error). An MxAccess priority 1–500 raise now logsErrorand 501–999 logsWarning. Before it was the other way round.AlarmActor): the raise now compares the banded severity with the same 700 threshold. Priority 1–500 →Error, 501+ →Warning. Before, rawPriorityLevel >= 700→Error. A computed alarm configured at priority 700–1000 used to log as Error and now logs as Warning. One at priority 1–500 used to log as Warning and now logs as Error.ScriptAlarm.Severity(Alarms.CurrentAsync()/MesReportableAsync()). They get the banded value.ScriptAlarm.Priority(new, trailing) carries exactly whatSeveritycarried before.MES 900–999 band: unchanged selection, but deployed scripts must move to
PriorityThe band keys off the raw priority.
ScriptAlarm.Priorityequals the pre-#129Severityfor every source and every input value;Priority_is_the_pre_129_Severity_so_the_MES_band_is_unchangedpins this. A script that testsa.Priorityin 900–999 therefore selects exactly the alarms it selected before. A script that still testsa.Severitywould select nothing, because banded values are only 250/500/750/1000 for MxAccess and computed rows. The MES API'sSeverity,MinSeverityandMaxSeverityare the raw 0–999 priority as well. The repo rig scripts (docker/seed-mes-alarm-demo.sh,docker/seed-mes-test-machine.sh) now reada.Priority, so their MES output is byte-for-byte what it was.docker/verify-*.shneeded no change.Owner action before deploying this build anywhere. Any deployed site script that reads
ScriptAlarm.Severityfor MES must switch toScriptAlarm.Priorityin the same deploy. Known scripts:CvdReactor.SimpleAlarmStatus/CvdReactor.AlarmStatus(the band predicate,IsFlaggedForMES,Severity = a.Severity)AlarmStatussite script (re-rundocker/seed-mes-test-machine.sh, or edita.Severity→a.Priorityin its three places)Nothing is deployed yet: vd03 and QA run builds older than this one. Tracked as deploy gate #131.
Wire, persistence, mixed versions
AlarmStateUpdate.prioritykeeps its pre-#129 meaning (the raw source number). Central derivesSeverityfrom it by kind (AlarmConditionStateFactory.SeverityFromPriority: banded for MxAccess and computed, verbatim for OPC UA). That is also correct for a site still on an older build. An old central reading a new site gets the old behaviour on the live stream.Severityexplicitly inAlarmConditionStateDto, so its MxAccess rows show the raw number until the site is upgraded. Central and sites normally deploy together.native_alarm_staterows written by an older build. These have noPriority. On rehydrate,NativeAlarmActormoves the old Severity intoPriorityand re-derivesSeverity(banded for MxAccess, kept for OPC UA). No migration is needed.Deliberately unchanged
debug snapshot"Priority" column (it prints the rawpriority, correctly labelled).AlarmContext.Priorityfor on-trigger scripts (raw).PriorityLevelauthoring (UI, CLI, Transport).AlarmStateBadges(it only displaysSeverity).Fixed in
dd4f1c2a(main, rebased one36cd605). Owner decision, approved 2026-11-12: take the recommendation.What changed
AlarmConditionState.Severityis higher = more urgent for every kind. NewCommons.Types.Alarms.AlarmPriorityBands.ToSeverityis OtOpcUa'sGalaxyAlarmPriority.Map(lmxopcua#887,b61f3e05) value for value: 1–250 → 1000, 251–500 → 750, 501–750 → 500, 751–999 → 250, with ≤ 0 → 1000 and > 999 → 250. It is used byMxGatewayAlarmMapper(the gatewayseverityis the AVEVA priority) and byAlarmConditionStateFactory.ForComputed(PriorityLevel). The OPC UA path is unchanged. OtOpcUa's history-only "not an alarm" row (AVEVA severity 0 → 1) has no live counterpart, so it is not ported.AlarmConditionState.Priority(new, trailingint?),AlarmStateChanged.PriorityandScriptAlarm.Priority(new, trailing) all carry exactly the pre-#129 Severity: the AVEVA priority clamped to 0–1000,PriorityLevel, or Part 9 Severity for OPC UA. There is no proto change.AlarmStateUpdate.prioritykeeps its meaning, and central derives Severity by kind (SeverityFromPriority), so the decode is right for un-upgraded sites too. The Debug View snapshot decode restoresCondition.Priority.NativeAlarmActorupgrades pre-#129native_alarm_staterows on rehydrate.Priority, neverSeverity. A test pinsPriority== pre-#129Severityfor every input fromint.MinValuetoint.MaxValue, so the band selects identical alarms. The rig scriptsdocker/seed-mes-alarm-demo.shandseed-mes-test-machine.shnow usea.Priorityfor the filters and the MESSeverityfield, so their output is unchanged. Deployed scripts must make the same switch: deploy gate #131.AlarmActorraise event-log severity is now banded severity ≥ 700 (was raw priority ≥ 700);SiteStreamGrpcClient.ConvertToDomainEvent;SiteCommandDtoMapper.FromProto;ScriptRuntimeContext.ToScriptAlarm;NativeAlarmActor(Emit/rehydrate).NativeAlarmActor.ErrorSeverityThreshold,AlarmSummaryServiceworst = max,AlarmSummary.razor>=filter and descending sort,AlarmStateBadges.debug snapshotPriority column (raw, labelled),AlarmContext.Priority(raw),PriorityLevelauthoring in UI/CLI/Transport. Notifications, Inbound API host code and Flow Debugger have no severity sort or filter.docs/plans/2026-06-30-mes-alarm-status-api.md, CLAUDE.md, CLI README.Tests (Release, rebased tree): Commons 977/977, DataConnectionLayer 549/549, Communication 834/834, SiteRuntime 1055/1055, CentralUI (bUnit) 1545/1545, IntegrationTests
SiteAlarmStream2/2. One unrelated DCL flake (TagResolutionCounts_ConnectionLevelFailureThenRecovery…) failed once and passed on its own and in every later full run.Priority, legacy-row rehydrate, the computed and native event-log thresholds, Alarm Summary worst/sort.ToSeverityto the old clamp turned 62 tests red across all five projects, bUnit included. KeyingPriorityoff the new Severity turned the MES-band test (plus 3 others) red. Both were restored.Consumer impact: see the previous comment. No deploys were made.