MAJOR-1: a deploy that changes a driver's config respawns it, and the fresh instance's empty told
record re-sent Raise for every still-active alarm, un-acking acknowledged conditions and writing a
duplicate "Activated" alerts row. NativeAlarmProjector now treats Raise on an already-active
condition as a re-statement (keeps Acked; ForwardNativeAlarm skips the alerts row); only Retrigger
(or inactive->active) un-acks. Sources whose upstream ack state is authoritative send Retrigger for
an unacked re-statement: OpcUaClient on first sight (no prior told), Galaxy for an unacked active
snapshot entry. Runtime E2E drives a FRESH FOCAS driver after a redeploy and asserts the ack survives.
MAJOR-2: a failed FOCAS alarm read annotates each subscribed condition BadNoCommunication once per
outage (#626 quality-only path) with one Warning (MINOR-6), lifted to Good on recovery; failed alarm
reads degrade driver health with LastSuccessfulRead frozen (successful ones advance it), so an
alarm-only driver trips the #527 comms-loss watchdog.
MINOR-3 CLAUDE.md #630 bullet; MINOR-4 split line; MINOR-5 a non-empty cnc_rdalmmsg2 payload that
fits neither stride throws; MINOR-7 value-tag + parsed-address tables are built and swapped, never
cleared in place under the retained poll engine. Docs corrected (FOCAS.md, AlarmTracking.md, CLAUDE.md).
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
Nothing read the mapping table beyond the startup validator; v3 UNS projection is
explicit via UnsTagReference. Legacy keys in stored DriverConfig are ignored (pinned).
Reverts the docker-dev opc-plc seed workaround from fdee129f.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
Re-review of 3de2cc3b/454775c6:
- AlarmTransitions could not reach (inactive, unacked): (active,acked)->(inactive,unacked) emitted Clear
(host stayed acked) and a first-seen cleared-unacked condition emitted nothing. The table is now total;
an exhaustive test replays every (prior, target) pair through the projector's semantics.
- The per-RawPath read-compute-emit was a lock-free TryGetValue/set across SDK publish threads; it now runs
under one lock with the projection and emission.
- Condition BRANCH events (non-null BranchId) were projected (flipping the trunk) and remembered as the ack
target (acking the branch); they are dropped. BranchId is select clause 9.
- EventId is copied, not aliased from the decoded notification.
- An unchanged state re-emitted Unspecified; it now emits nothing unless message/severity changed (an
Unspecified projects from the host projector's default after a redeploy reset it).
- Exact upstream severity rides OpcUaSeverity (#626) instead of being re-bucketed.
- ShutdownAsync no longer forgets the last-told state: a reinit re-subscribes the same set and the refresh
would otherwise re-raise every acked alarm. A CHANGED set (host rebuilt) forgets it.
ConditionRefresh: called after the alarm event item is created, after an in-place reconnect (subscriptions
found on the new session by display name, since a recreate clones them) and on RefreshRequired. Refresh
markers are never projected. BadNotSupported/BadMethodInvalid/... logs once. Wire test: a condition already
active before subscribe is reported Raise; a release+resubscribe refresh re-raises nothing (falsified by
disabling the refresh call).
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe