fix(runtime): #869 review round 2 — a carry-through transition no longer settles a pending local ack
NativeAlarmProjector dropped the pending local ack on EVERY driver transition, so an operator ack followed by the alarm returning to normal (a Clear carrying the optimistic ack) inside the confirmation window made a later "not confirmed" report a no-op — the condition stayed inactive+acked while Galaxy showed UNACK_RTN. Only a transition that states the ack (Acknowledge, ActiveAcknowledged, or an un-acking Raise/Retrigger/InactiveUnacknowledged) now settles it; a Clear / same-message Raise / Unspecified update leaves it revertible, and the revert projects inactive+unacked. Tests: projector + host end-to-end (ack, Clear, confirmation fails -> inactive + unacked); tightened the confirmed-ack positive controls; pinned that the confirmation receives the same request instance as AcknowledgeAsync. Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
@@ -78,9 +78,6 @@ public sealed class NativeAlarmProjector
|
||||
/// <returns>The full Part 9 condition snapshot to write to the node.</returns>
|
||||
public AlarmConditionSnapshot Project(string nodeId, AlarmEventArgs e, out IReadOnlyList<AlarmTransitionKind> historyKinds)
|
||||
{
|
||||
// Any driver transition supersedes a pending local acknowledge: from here the projected state is the
|
||||
// driver's word, so a late ack failure must not rewrite it.
|
||||
_pendingLocalAck.Remove(nodeId);
|
||||
var prev = _prior.TryGetValue(nodeId, out var p)
|
||||
? p : (Active: false, Acked: true, Message: (string?)null, Severity: (ushort)0, StatusCode: OpcUaStatus.Good, Class: (AlarmConditionClass?)null);
|
||||
// A source with a finer scale than the four buckets supplies the exact Part 9 severity.
|
||||
@@ -153,6 +150,15 @@ public sealed class NativeAlarmProjector
|
||||
break;
|
||||
}
|
||||
|
||||
// A driver transition that STATES the ack (Acknowledge / ActiveAcknowledged, or an un-acking Raise /
|
||||
// Retrigger / InactiveUnacknowledged) supersedes a pending local acknowledge: from here the ack state is the
|
||||
// driver's word, so a late ack failure must not rewrite it. One that merely CARRIES the prior ack through
|
||||
// (a Clear, a same-message Raise, a message/severity update) says nothing about it — the optimistic local
|
||||
// ack rode along, so it stays pending and a later failure still reverts it (lmxopcua#869: an operator acks,
|
||||
// the value returns to normal while the unconfirmed ack is still being confirmed, Galaxy shows UNACK_RTN).
|
||||
if (acked != prev.Acked || e.Kind is AlarmTransitionKind.Acknowledge or AlarmTransitionKind.ActiveAcknowledged)
|
||||
_pendingLocalAck.Remove(nodeId);
|
||||
|
||||
var statusCode = e.QualityStatusCode ?? OpcUaStatus.Good;
|
||||
// A source that classifies its conditions (#839) states the class on its transitions; one that does not (or a
|
||||
// quality-only annotation) keeps whatever the condition last carried.
|
||||
@@ -185,8 +191,9 @@ public sealed class NativeAlarmProjector
|
||||
/// <summary>
|
||||
/// Undo a client acknowledgement the driver refused or failed to deliver upstream (lmxopcua#760), returning
|
||||
/// the unacknowledged snapshot to re-project onto the node. Only an acknowledge still pending since
|
||||
/// <see cref="MarkAcknowledged"/> is reverted: once any driver transition for the condition has been
|
||||
/// projected, that transition is authoritative and a late failure changes nothing.
|
||||
/// <see cref="MarkAcknowledged"/> is reverted: once a driver transition that STATES the condition's ack state
|
||||
/// has been projected, that transition is authoritative and a late failure changes nothing. A transition that
|
||||
/// only carries the prior ack through (e.g. a Clear) leaves it pending (lmxopcua#869).
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The condition node id whose acknowledge failed.</param>
|
||||
/// <param name="snapshot">The unacknowledged snapshot to project; null when nothing was reverted.</param>
|
||||
|
||||
Reference in New Issue
Block a user