fix(alarms): surface a refused native alarm ack synchronously, revert one that fails later (lmxopcua#760)
The native-ack path is fire-and-forget after the Part 9 Acknowledge returns, so a Galaxy ack refused for lack of a credential (#749), or rejected by MXAccess, left the client with Good and the condition showing Acknowledged while Galaxy was never acked. Both options from the issue, layered: 1. Synchronous refusal for what the driver can decide without I/O. New optional IAlarmAcknowledgePreflight (Core.Abstractions); GalaxyDriver refuses an anonymous session when the driver has no AlarmAck fallback account. DriverHostActor publishes a condition NodeId -> owning-driver snapshot into NativeAlarmAckPreflightView (lock-free, volatile); the node manager's new NativeAlarmAckPreflight seam, consulted after the authz and Primary gates, returns BadUserAccessDenied with the reason - nothing routed, applied or reported. A keyed session is not pre-checked (its identity lives in the Secrets store, which must not be read under the SDK lock). 2. Revert for everything else. RouteAlarmAck carries the condition NodeId; DriverInstanceActor replies AlarmAckFailed on a refusal, a backend failure, a driver without an alarm source, or an ack dropped while connecting / reconnecting. DriverHostActor re-projects the condition unacknowledged via NativeAlarmProjector.TryRevertAcknowledge, but only while the local ack is still pending - any driver transition projected since then wins. A disabled or unspawned driver is not reverted (unchanged). GatewayGalaxyAlarmAcknowledger now throws DriverOperationRefusedException on a non-zero native hresult (wrong password, no ack permission) instead of only warning, so that case reverts too; as a refusal it neither retries nor trips the breaker. Tests (each fails with its fix mutated out): projector revert/supersede, DriverHostActor refused + failed ack -> unacked AlarmStateUpdate (successful ack control), preflight view snapshot, node-manager preflight unit test, over-the-wire ConditionMethodEventTests (BadUserAccessDenied, no route, no event; positive control), Galaxy preflight + hresult refusal. Docs: Galaxy.md, AlarmTracking.md, ScriptedAlarms.md, .claude/rules/alarms.md. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -104,6 +104,10 @@ public static class ServiceCollectionExtensions
|
||||
// redundancy manager, wires no HistoryUpdate gate at all.
|
||||
services.TryAddSingleton<IHistoryUpdateRoleView>(sp => sp.GetRequiredService<RedundancyRoleView>());
|
||||
|
||||
// The condition → owning-driver native-ack pre-check snapshot (lmxopcua#760): DriverHostActor publishes
|
||||
// it, the OPC UA node manager reads it to refuse an acknowledge the driver would refuse anyway.
|
||||
services.TryAddSingleton<NativeAlarmAckPreflightView>();
|
||||
|
||||
// The node→central ack transport under MeshTransport:Mode = Grpc (mesh gRPC command plane).
|
||||
// Registered unconditionally but resolved only by the Grpc arm of
|
||||
// WithOtOpcUaRuntimeActors — the mode is not knowable here (this runs before configuration is
|
||||
@@ -654,7 +658,9 @@ public static class ServiceCollectionExtensions
|
||||
historyUpdateRoleView: resolver.GetService<IHistoryUpdateRoleView>(),
|
||||
// The node-wide Scripting section (bound + clamped in Program.cs) for the scripted-alarm
|
||||
// engine. Unbound in test graphs ⇒ null ⇒ the compiled defaults.
|
||||
scripting: resolver.GetService<ScriptingOptions>()),
|
||||
scripting: resolver.GetService<ScriptingOptions>(),
|
||||
// Where the native-ack pre-check snapshot is published (lmxopcua#760).
|
||||
ackPreflightView: resolver.GetService<NativeAlarmAckPreflightView>()),
|
||||
DriverHostActorName);
|
||||
registry.Register<DriverHostActorKey>(driverHost);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user