fix(opcua): exempt OnTimedUnshelve from the client AlarmAck gate (system-initiated)
The SDK fires OnTimedUnshelve with the node manager's system context (no session, no user identity) when a TimedShelve duration expires. Routing through the shared HandleAlarmCommand hit the AlarmAck gate and returned BadUserAccessDenied, leaving the alarm permanently shelved. Replace the delegated HandleAlarmCommand call with an inline lambda that bypasses the client gate, extracts the AlarmId the same way, and routes an Unshelve command so the engine clears its shelve state. The manual-client Unshelve path via OnShelve(shelving:false) remains gated. Update the AlarmCommandRouterTests OnTimedUnshelve test to use a real system context (no UserIdentity) — reproducing the actual SDK invocation path — and assert Good, AlarmId, Operation==Unshelve, User==empty. Add a doc note to AlarmCommand.Operation that Enable/Disable are in the vocabulary but not yet wired at the node-manager seam.
This commit is contained in:
@@ -361,10 +361,18 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
: ("TimedShelve", DateTime.UtcNow + TimeSpan.FromMilliseconds(shelvingTime));
|
||||
return HandleAlarmCommand(context, condition, operation, comment: null, unshelveAt);
|
||||
};
|
||||
// The auto-unshelve timer firing is an unshelve transition driven by the SDK (no client user);
|
||||
// route it as Unshelve so the engine clears its shelve state. Same AlarmAck gate applies.
|
||||
// The auto-unshelve timer callback is SDK-initiated (the TimedShelve duration expired); the SDK
|
||||
// fires it with the node manager's system context — there is NO session and NO user identity.
|
||||
// Routing through HandleAlarmCommand would hit the AlarmAck gate and return BadUserAccessDenied,
|
||||
// leaving the alarm permanently shelved. Instead, bypass the client gate, extract the AlarmId the
|
||||
// same way HandleAlarmCommand does, and route an Unshelve command so the engine clears its shelve
|
||||
// state. The manual-client Unshelve path goes through OnShelve(shelving:false) and stays gated.
|
||||
alarm.OnTimedUnshelve = (context, condition) =>
|
||||
HandleAlarmCommand(context, condition, "Unshelve", comment: null, unshelveAt: null);
|
||||
{
|
||||
var alarmId = condition.NodeId.Identifier?.ToString() ?? string.Empty;
|
||||
AlarmCommandRouter?.Invoke(new AlarmCommand(alarmId, "Unshelve", string.Empty, null, null));
|
||||
return ServiceResult.Good;
|
||||
};
|
||||
|
||||
parent.AddChild(alarm);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user