feat(alarms): condition Quality tracks source connectivity (#477) #479
Reference in New Issue
Block a user
Delete Branch "fix/alarm-condition-quality-477"
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?
Closes #477.
Part 9
ConditionType.Qualitywas never assigned;default(StatusCode)==Good, so every native + scripted condition reportedGoodunconditionally — a comms-lost device still showed a healthy, inactive,Goodcondition. This is a wrong-value bug, distinct from the null-value class of #473/#475. Clients (and HMIs bucketing onIsGood) could not tell "genuinely inactive" from "we have lost contact".Scope (user-chosen): Layer 1 + Layer 2, Bad-direct, annotate-only. Layer 3 (scripted worst-of-input) deferred → #478.
Layer 1 — make Quality a real, plumbed field
AlarmConditionSnapshotgainsOpcUaQuality Quality(defaultGood).MaterialiseAlarmConditionsets it (nativeBadWaitingForInitialData, scriptedGood).WriteAlarmConditionprojectssnapshot.Quality; the delta-gate gains aQualitymember so a quality-bucket change fires a Part 9 event.Layer 2 — drive native quality from driver connectivity
A comms-lost driver emits no alarm transitions, and an alarm-bearing raw tag has no value variable (
AddressSpaceApplier:tag.Alarm is not null⇒ condition, else value), so quality can't come from either existing channel.DriverInstanceActorTells its parentConnectivityChanged(id, connected)on enteringConnected/Reconnecting.DriverHostActor.OnDriverConnectivityChangedfans it to every native condition the driver owns asOpcUaPublishActor.AlarmQualityUpdate(Good on connect, Bad on disconnect).IOpcUaAddressSpaceSink.WriteAlarmQualitysets onlyQualityand fires only on a bucket change — never touches Active/Acked/Retain (an active alarm that loses comms stays active). It is a dedicated path (not a full-snapshot re-projection), so it can't clobber severity/message and works for a condition that never fired a transition. Forwarded throughDeferredAddressSpaceSink(the F10b inertness trap) — auto-verified byDeferredSinkForwardingReflectionTests. Ungated by redundancy role; publishes no/alertsrow (driver health has its own surface).Scripted conditions stay
Good(hardcoded inScriptedAlarmHostActor.ToSnapshotwith a comment pointing at #478).Tests
NodeManagerAlarmSourceFieldsTests): materialise / project / no-clobber / unknown-node no-op.NativeAlarmProjectorTests,DriverInstanceActorconnectivity emission,DriverHostActorfan-out,OpcUaPublishActorrouting.NativeAlarmEventIdentityFieldDeliveryTests.Condition_event_Quality_tracks_source_connectivity_on_the_wire): healthy → Good, comms-lost → non-Good, recovery → Good on a real client subscription. RED-verified against a simulated pre-fix always-Good server.DriverInstanceActor*Testsparent probes nowIgnoreMessagesthe newConnectivityChanged(they assert data/alarm/discovery forwards, not connectivity).Regression: Runtime.Tests 383/0, OpcUaServer.Tests 386/0, Commons.Tests 306/0; wire tests 3/3; solution builds clean.
Docs
docs/AlarmTracking.md§"Condition source-data Quality (#477)".docs/plans/2026-07-17-alarm-condition-quality-477-design.md.