fix(runtime): the native-alarm projector learns a routed client acknowledgement (#641 review)

#641 keeps the projector's prior state across a deploy instead of resetting it to the
acked default. The projector never saw a client's ack (the SDK applies it to the node and
the host only routes it), so a Clear after ack+deploy now projected Acked=false and
un-acknowledged the operator's ack - previously masked by the per-deploy reset.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 17:19:24 -04:00
parent 5c88de078c
commit 25f02e4102
3 changed files with 44 additions and 0 deletions
@@ -2018,6 +2018,12 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
return;
}
// Gitea #641 review — the node is already acknowledged (the SDK applies the client's ack); tell the
// projector too, or the next driver Clear projects (Active=false, Acked=prev=false) and un-acknowledges it.
// Most sources (MTConnect, FOCAS) send no Acknowledge transition back, and the projector now survives a
// deploy rather than resetting to its acked default.
_nativeAlarmProjector.MarkAcknowledged(msg.ConditionNodeId);
if (!_children.TryGetValue(target.DriverInstanceId, out var entry))
{
_log.Debug("DriverHost {Node}: driver {Driver} not running for alarm ack of {Cond} — dropped",
@@ -40,6 +40,17 @@ public sealed class NativeAlarmProjector
StatusCode: e.QualityStatusCode ?? OpcUaStatus.Good);
}
/// <summary>
/// Gitea #641 review — record a client acknowledgement the node manager applied to <paramref name="nodeId"/>
/// and routed toward the driver. The projector otherwise never sees it, and a later Clear would project the
/// stale unacknowledged prior. An untracked condition already defaults to acknowledged.
/// </summary>
/// <param name="nodeId">The acknowledged condition node id.</param>
public void MarkAcknowledged(string nodeId)
{
if (_prior.TryGetValue(nodeId, out var p)) _prior[nodeId] = (p.Active, true);
}
/// <summary>Clears all tracked per-node state.</summary>
public void Clear() => _prior.Clear();