Galaxy event history: notifier folders never name a Galaxy object, so AVEVA A&E history is unreachable in production (+ AVEVA severity/condition mapping) #886
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 #882.
Gap. A notifier folder's historian event source is its NodeId identifier (
EnsureFolderIsEventNotifier:folder.NodeId.IdentifierAsString). That is a RawPath (<Folder>/<Driver>/<Device>/<TagGroup>) for a Raw folder, or an EquipmentId for a UNS equipment folder. It is never a Galaxy object'stag_name. The gateway filters a per-sourceReadEventson AVEVA'sSource_Object, which is the Galaxytag_name(for exampleDevWinPlatform). So in production, AVEVA's own A&E history (object events likeSystem.Stop/System.Start, and attribute alarms reported as<Object>.<Attribute>) reaches a folder only when an EquipmentId happens to equal the Galaxy tag_name. The #801/#882 live gate passes because its harness names the folder after the object.Live delivery does not have this gap. A native Galaxy condition (ConditionId = RawPath, dialled from the attributeRef
tag_name.Attr) fires on its Raw parent folder and on every referencing UNS equipment folder (WireAlarmNotifiers).Options.
source_conditionvariable/Source_Object= the part before the first.). The folder then reads each object with the #882 boundary match. This is the closest fit to live delivery. It needs the dial ref plumbed fromRawTagPlaninto the sink and multiple sources per notifier (several gateway reads, merged in(EventTime, EventId)order for paging).source_conditionvariablenames one of its wired conditions' dial refs. This is narrower and drops object-level system events.Related mapping gaps on AVEVA rows (EventMapper, same root: AVEVA property keys are lower-case):
Severity/Priority. AVEVA sendspriority(500), so every AVEVA event projects Severity 1.AlarmId/AlarmName. AVEVA sendsalarm_id(a GUID-like id) andsource_conditionvariable, so ConditionName is null and SourceNode can never resolve. Resolving SourceNode needs the dial-ref → condition map from option A/B.The per-folder ACL (#826) must keep governing whatever a folder serves (#882 holds this: attribute events ride the folder's own HistoryRead verdict).
Fixed on master
3a312ffd, OtOpcUa only. No HistorianGateway wire change. Design:docs/Historian.md§ "Galaxy native event history on real folders (Gitea #886)".Design. Option A, with live delivery as the rule. A folder's event history now reads its own source, as before, plus the Galaxy object of every native condition that fires on it live. Those are the conditions parented under the folder (the Raw device folder) and the ones
WireAlarmNotifierswired to it (each referencing UNS equipment folder), so live and history use the same membership.attributeRef(tag_name.AlarmAttribute, the address the driver dials), taken up to the first..attributeRefwith the driver's own parser (GalaxyAttributeRef, now shared withGalaxyDriver). It does this only for aGalaxyMxGatewaytag that carries an alarm, and stores the result inRawTagPlan.NativeAlarmReference, which is part of plan equality.BindNativeAlarmHistory, which the deferred sink forwards.<Object>.<Attribute>rows, never a prefix sibling.ReadEventsfilter is a single predicate, so there is no multi-source request: the server makes one request per source, sequentially within the folder, each with the page cap.EventSourceFanOutmerges the results into one source:(time, source position, arrival), and a reverse read is the forward merge backwards. Ties are never re-sorted by EventId, which keeps the boundary skip stable on the Gateway.AuthorizeHistoryRead(#826). A denied folder reads nothing:BadUserAccessDenied, and no backend call is made (fail closed).alarmname,alarmid,severity,message), as seen on vd03.prioritymaps throughGalaxyAlarmPriority, the table live Galaxy alarms use (MxAccessSeverityMappernow delegates to it), so history matches live:< 250→ 250,< 500→ 500,< 750→ 750, otherwise 1000. A row withoutpriority(OtOpcUa's own) takesseverity, clamped. AVEVA's ownseverity(1–4, or 0 for system events) is ignored. ⚠️ AVEVA ranks priority 1 as most urgent and the live table ranks higher numbers as more severe; I filed that as #887 rather than change live severity here.source_conditionvariablenames a native condition that fires on this folder (case-insensitive, as the driver matches) takes that condition's SourceNode and live ConditionName. Otherwise ConditionName isalarmname, or the condition variable's attribute (CPULoadHiAlarm). A condition outside the folder is never named.alarm_idandalarmidmap to AlarmId.alarm_ididentifies one alarm occurrence (Set and Clear share it), not a condition, so it is not used as the ConditionName.Tests.
GalaxyEquipmentEventHistoryWireTests(4). These run over a real SDK session against the production Gateway backend and an AVEVA-shaped fake gateway. EquipmentEqAis backed by objectsOven_007andOvenDoor_07; neither name matches the folder. Coverage:Oven_0070excluded, although it reached OtOpcUa;Other tests:
EventSourceFanOutTests(8), EventMapper AVEVA cases (11),GalaxyAttributeRefTests(16), a composer test and an applier ordering test.Mutation checks: 17 mutants, all killed by at least one test:
.boundary; priority ignored; case-sensitive keys;One redundant guard survives as belt-and-braces: the forget-on-rematerialise, which the rebuild clear already covers.
Suites (pre-push rebase): OpcUaServer.Tests 1275/0 failed, OpcUaServer.IntegrationTests 316/0 failed (live gate skipped without env), Runtime.Tests 1029/0, Historian.Gateway.Tests 550/0, Galaxy.Tests 580/0, Core.Abstractions.Tests 745/0, Commons.Tests 363/0. After the final rebase onto
1e1fad76(#879/#888, unrelated files) the solution builds and the event-history wire tests pass.Live re-run: read-only, network-only, no WinRM. It ran against vd03 HistorianGateway
ea9f21c. The API key was read over SMB from vd03's OtOpcUa env backup, never printed, and the local copy was deleted.events886.*: a realistic equipment folder,EQ-886-ZipsenLeft, with conditionsDevWinPlatform.CPULoadHiAlarmandLeft_001.FRR_Warningwired to it.DevWinPlatform2 +DevWinPlatform.CPULoadAvg70 + 20Left_001/Left_001.*. That is exactly the unfiltered read's rows for both objects, in time order.CPULoadHiAlarm, 3 →FRR_Warning.diff_runs.pyvs the #882 run: 221 of 226 cases are identical. The 5 that differ are expected: the 4DevWinPlatformevent cases (only Severity changed, from 1 to 750/1000) and the #881 capability flags.docs/plans/evidence/2026-11-10-opcua2/aveva-history-801.md§ "#886 re-run", reportaveva-gate-sdk2-886.json.gz.Consumer impact. ScadaBridge: none (it does no HistoryRead). Generic OPC UA history clients reading a Galaxy-backed folder now get AVEVA's A&E history (more rows), a real Severity and ConditionName, and SourceNode on rows for the folder's own conditions. Not deployed.