feat(opcua): surgical RemoveAlarmConditionNode (R2-07 T9)

This commit is contained in:
Joseph Doherty
2026-07-13 12:09:55 -04:00
parent 14003ab9b8
commit 9653493905
2 changed files with 23 additions and 2 deletions
@@ -79,7 +79,7 @@
{
"id": "T9",
"subject": "Phase 2: OtOpcUaNodeManager.RemoveAlarmConditionNode (+ native-flag cleanup) (high-risk)",
"status": "pending",
"status": "completed",
"blockedBy": [
"T7"
]
@@ -1823,7 +1823,28 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
}
/// <inheritdoc cref="ISurgicalAddressSpaceSink.RemoveAlarmConditionNode"/>
public bool RemoveAlarmConditionNode(string alarmNodeId) => false;
public bool RemoveAlarmConditionNode(string alarmNodeId)
{
ArgumentException.ThrowIfNullOrEmpty(alarmNodeId);
EnsureAddressSpaceCreated();
GeneralModelChangeEventState e;
lock (Lock)
{
// Unknown id ⇒ map drift ⇒ false (caller rebuilds). Mirrors the condition teardown inside
// RebuildAddressSpace, scoped to this one condition. The equipment folder's event-notifier
// promotion is intentionally NOT demoted here — other conditions may still hang under it, and a
// lingering notifier folder is harmless (RemoveEquipmentSubtree demotes it when the whole
// equipment goes).
if (!_alarmConditions.TryRemove(alarmNodeId, out var condition)) return false;
condition.Parent?.RemoveChild(condition);
PredefinedNodes?.Remove(condition.NodeId);
_nativeAlarmNodeIds.Remove(alarmNodeId);
e = BuildNodesRemovedModelChange(alarmNodeId);
}
ReportModelChangeOutsideLock(e, alarmNodeId);
return true;
}
/// <inheritdoc cref="ISurgicalAddressSpaceSink.RemoveEquipmentSubtree"/>
public bool RemoveEquipmentSubtree(string equipmentNodeId) => false;