fix(address-space): no UNS value Variable for a reference to an alarm-bearing tag (#896)
v2-ci / unit-tests (push) Has been cancelled
v2-ci / unit-tests (push) Has been cancelled
A UnsTagReference to a raw tag that carries an "alarm" object produced a UNS
value Variable that read BadWaitingForInitialData forever (G6 live gate,
TestAlarm002Condition on testmachine-015). BuildUnsReferenceVariables now skips
those references; the equipment gets the alarm only as the raw condition wired
as an event notifier of its folder, which is unchanged (the reference still
feeds RawTagPlan.ReferencingEquipmentPaths).
Why condition-only and not "feed the mirror":
- The raw side has no value node for such a tag: AddressSpaceApplier
materialises an alarm tag as a condition OR a value variable, never both.
There is no raw value for the UNS mirror to mirror.
- DriverHostActor skips alarm tags when it builds the value fan-out and write
routing maps (`continue` before the UNS registration), and the driver leaves
them out of its value subscription ("subscribed to 5 refs" with 6 tags in
G6). Feeding the mirror would mean subscribing a value the design says the
tag does not have.
- Docs already state condition-only: ScriptedAlarms.md ("instead of a value
variable"), AlarmTracking.md ("only the condition, with no sibling value
variable"), the v3 design (native alarm = one condition at the raw tag,
surfaced at equipment via notifiers).
- The composer tests that asserted the mirror were incidental: f4f3e17e
(v3 B4-WP1) put the alarm on the fixture's Speed tag to test raw alarm
attachment, and later tests reused Speed for writable / historian / array
checks of the mirror. No commit or doc chose a value mirror for alarm tags.
- ScadaBridge does not read it: its OtOpcUa alarm path is event subscriptions
on the equipment/raw folders (v3 alarm B/C live gate), and a node that never
had a value cannot be a working binding. G6 itself authored the alarm's
Boolean as a separate value tag (TestAlarm002), which is the supported way to
read it.
Tests: the composer fixture moves the alarm onto its own HighTemp tag
(Speed is now a plain historized value tag), with a new test asserting no UNS
Variable for the alarm reference, with positive controls (the raw tag still
carries the equipment path; the value references still emit). New real-SDK
test Alarm_bearing_reference_surfaces_as_the_condition_only_against_real_SDK:
no Variable at the UNS path, the Speed mirror present, one condition, the
equipment folder wired as its notifier. Both new tests fail with the skip
removed. Docs: Uns.md, ScriptedAlarms.md, G6 evidence note.
Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
@@ -921,7 +921,8 @@ public static class AddressSpaceComposer
|
||||
/// equipment path resolve, keyed <c>(realm=Uns, s=Area/Line/Equipment/EffectiveName)</c> and carrying
|
||||
/// the backing RawPath (Organizes target + fan-out) plus the inherited DataType / writable. A
|
||||
/// reference whose backing tag is unknown, whose RawPath is unresolvable, or whose UNS path segments
|
||||
/// are invalid is SKIPPED. Sorted by NodeId ordinal.
|
||||
/// are invalid is SKIPPED. A reference to an ALARM-BEARING tag is skipped too (#896): it surfaces at the
|
||||
/// equipment as the raw condition's event notifier, not as a value Variable. Sorted by NodeId ordinal.
|
||||
/// </summary>
|
||||
private static IReadOnlyList<UnsReferenceVariable> BuildUnsReferenceVariables(
|
||||
IReadOnlyList<UnsTagReference>? unsTagReferences,
|
||||
@@ -965,6 +966,14 @@ public static class AddressSpaceComposer
|
||||
// doc-comment; 2026-08-15 surgical-deploys review). Parsed from the SAME TagConfigIntent the raw
|
||||
// tag plan uses, so the two sides can never disagree.
|
||||
var intent = TagConfigIntent.Parse(tag.TagConfig);
|
||||
// #896: an alarm-bearing backing tag gets NO UNS value Variable. Its raw side is a Part 9 condition
|
||||
// with no sibling value variable (AddressSpaceApplier.MaterialiseRawSubtree), the driver host keeps
|
||||
// it out of the value fan-out and the value subscription (DriverHostActor skips Alarm tags), so a
|
||||
// mirror Variable here could never receive a value and read BadWaitingForInitialData forever. The
|
||||
// reference still reaches the equipment: it puts the equipment's path on the raw tag's
|
||||
// ReferencingEquipmentPaths, and the applier wires the single condition as an event notifier of that
|
||||
// equipment folder. Same TagConfigIntent the raw pass reads, so the two sides cannot disagree.
|
||||
if (intent.Alarm is not null) continue;
|
||||
// Same derivation as BuildRawSubtree's ArrayWritable, off the same row, so a mirror is writable
|
||||
// exactly when its raw node is.
|
||||
var backingArrayWritable = intent.IsArray && arrayWritableFor(tag);
|
||||
|
||||
Reference in New Issue
Block a user