Merge b640-641: native alarm state survives rebuild; projector pruned; AbCip ConditionId=RawPath (#641)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -1793,9 +1793,10 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
// #626 — a QUALITY-ONLY annotation (an MTConnect condition gone Unavailable, or back) is not an alarm
|
||||
// transition. It rides the SAME quality-only node path the #477 connectivity annotation uses
|
||||
// (AlarmQualityUpdate → WriteAlarmQuality, which touches Quality and nothing else), never a projected
|
||||
// snapshot: the projector's prior state is cleared on every apply, so a projected "keep the state"
|
||||
// after a deploy would default to inactive+acked and silently acknowledge a retained, unacked
|
||||
// condition — and would rewrite its message and severity. Ungated by role (a Secondary keeps quality
|
||||
// snapshot: the projector never sees a client's acknowledgement (that is applied to the node by the
|
||||
// SDK) and forgets a condition a deploy removed, so a projected "keep the state" can disagree with
|
||||
// the node — silently acknowledging a retained, unacked condition or rewriting its message and
|
||||
// severity. Ungated by role (a Secondary keeps quality
|
||||
// warm, as for connectivity) and no alerts row.
|
||||
if (msg.Args is { IsQualityOnly: true, QualityStatusCode: { } quality })
|
||||
{
|
||||
@@ -2017,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",
|
||||
@@ -3078,7 +3085,8 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
_alarmNodeIdByDriverRef.Clear();
|
||||
_driverRefByAlarmNodeId.Clear();
|
||||
_alarmMetaByNodeId.Clear();
|
||||
_nativeAlarmProjector.Clear();
|
||||
// NOT _nativeAlarmProjector.Clear() (Gitea #641): it is pruned to the still-deployed conditions after the
|
||||
// maps are rebuilt below, so a redeploy that keeps an alarm keeps its projected prior state.
|
||||
// The per-driver secondary indexes are rebuilt WHOLE alongside their primaries below — same pass,
|
||||
// same clear — so no incremental maintenance exists to get out of step with them.
|
||||
_valueRefsByDriver.Clear();
|
||||
@@ -3132,6 +3140,13 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
}
|
||||
}
|
||||
|
||||
// Gitea #641 — forget the projected prior state of conditions this composition no longer carries, and keep
|
||||
// the rest. Clearing it whole on every apply made the next driver Acknowledge of a still-active alarm
|
||||
// project Active=false (the default prior), and the next Clear of an unacknowledged one project
|
||||
// Acked=true — on any deploy, rebuild or not. The node manager carries the NODE's state across a rebuild,
|
||||
// so the two stay in step. Keyed by the bare condition NodeId, exactly as ForwardNativeAlarm projects.
|
||||
_nativeAlarmProjector.RetainOnly(_driverRefByAlarmNodeId.Keys.ToHashSet(StringComparer.Ordinal));
|
||||
|
||||
// Cache the per-driver payload as THE authority, in the same clear-and-repopulate pass as the routing
|
||||
// maps above so a rename/removal can never leave a stale entry behind. Keyed over the UNION of the two
|
||||
// ref maps AND the running children: a child with no authored tags must get an explicit EMPTY set (which
|
||||
|
||||
@@ -40,9 +40,36 @@ public sealed class NativeAlarmProjector
|
||||
StatusCode: e.QualityStatusCode ?? OpcUaStatus.Good);
|
||||
}
|
||||
|
||||
/// <summary>Clears all tracked per-node state (call on address-space rebuild).</summary>
|
||||
/// <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();
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #641 — drop the tracked state of every condition NOT in <paramref name="deployedNodeIds"/>, keeping
|
||||
/// the rest. Called on every apply instead of <see cref="Clear"/>: a condition that is still deployed keeps
|
||||
/// its state across the deploy (the node manager carries the node's state across a rebuild too), so the
|
||||
/// next Acknowledge does not project <c>Active = false</c> onto an active alarm and the next Clear does not
|
||||
/// project <c>Acked = true</c> onto an unacknowledged one. A removed condition is forgotten, so a later
|
||||
/// re-add starts from the default.
|
||||
/// </summary>
|
||||
/// <param name="deployedNodeIds">The condition node ids the new composition still materialises.</param>
|
||||
public void RetainOnly(IReadOnlySet<string> deployedNodeIds)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(deployedNodeIds);
|
||||
foreach (var nodeId in _prior.Keys.Where(k => !deployedNodeIds.Contains(k)).ToList())
|
||||
_prior.Remove(nodeId);
|
||||
}
|
||||
|
||||
private static ushort MapSeverity(AlarmSeverity s) => s switch
|
||||
{
|
||||
AlarmSeverity.Low => 200,
|
||||
|
||||
Reference in New Issue
Block a user