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:
+11
-3
@@ -107,9 +107,17 @@ Galaxy password, so `GalaxyAckCredentialResolver` picks the account per acknowle
|
||||
`file:PATH` or `dev:VALUE`; a bare literal is refused so a password never sits in DriverConfig.
|
||||
3. **Neither** → the driver throws `DriverOperationRefusedException`; **nothing is sent to the
|
||||
gateway**. The refusal is excluded from the resilience breaker (it says nothing about gateway
|
||||
health). ⚠️ The native ack path is fire-and-forget after the Part 9 method returns, so the OPC UA
|
||||
call itself still returns `Good` and the refusal surfaces as a `DriverInstance` warning naming the
|
||||
fix — a synchronous status / revert is #760.
|
||||
health). How the client learns of it (#760):
|
||||
- **Synchronously** when the driver can tell without I/O — an *anonymous* session on a driver with
|
||||
no fallback account: the Part 9 `Acknowledge` call itself fails `BadUserAccessDenied` with the
|
||||
reason, and nothing is applied (`IAlarmAcknowledgePreflight`, published per condition by
|
||||
`DriverHostActor` into `NativeAlarmAckPreflightView`).
|
||||
- **By revert** otherwise — a keyed session whose key has no identity (that lives in the Secrets
|
||||
store, which is not read under the SDK lock), or MXAccess rejecting the account (a non-zero
|
||||
native `hresult`, e.g. a wrong password, now thrown as a refusal): the call returned `Good`, then
|
||||
`DriverHostActor` re-projects the condition **unacknowledged** (a fresh condition event,
|
||||
`Retain=true`) and logs the reason. A driver transition projected after the ack wins over a late
|
||||
failure.
|
||||
|
||||
The comment written to `AckMsg` is suffixed with the OPC UA identity —
|
||||
`"<comment> [OPC UA: <principal>, key <keyId>]"` or `"[OPC UA: anonymous]"` — so the client is not
|
||||
|
||||
Reference in New Issue
Block a user