docs(alarms): OPC UA Enable/Disable wired + native-ack→AVEVA with principal + HistoryUpdate permission bit

This commit is contained in:
Joseph Doherty
2026-06-15 14:59:10 -04:00
parent 30315185a3
commit db22c2b19a
3 changed files with 64 additions and 9 deletions
+25 -9
View File
@@ -109,7 +109,7 @@ Every mutation the state machine produces is immediately persisted inside the en
Two mapping notes specific to this adapter:
- `SubscribeAlarmsAsync` accepts a list of source-node-id filters, interpreted as Equipment-path prefixes. Empty list matches every alarm. Each emission is matched against every live subscription — the adapter keeps no per-subscription cursor.
- `IAlarmSource.AcknowledgeAsync` does not carry a user identity. The adapter defaults the audit user to `"opcua-client"` so callers using the base interface still produce an audit entry. The server's Part 9 method handlers call the engine's richer `AcknowledgeAsync` / `ConfirmAsync` / `OneShotShelveAsync` / `TimedShelveAsync` / `UnshelveAsync` / `AddCommentAsync` directly with the authenticated principal instead.
- `IAlarmSource.AcknowledgeAsync` accepts an `AlarmAcknowledgeRequest` list; the `OperatorUser` field carries the authenticated principal when available. The adapter passes `OperatorUser` through to the engine's `AcknowledgeAsync`; when `OperatorUser` is null (non-OPC-UA callers using the raw interface) it falls back to `"opcua-client"` so callers still produce an audit entry. The server's Part 9 method handlers call the engine's richer `AcknowledgeAsync` / `ConfirmAsync` / `OneShotShelveAsync` / `TimedShelveAsync` / `UnshelveAsync` / `AddCommentAsync` directly with the authenticated principal instead of going through this adapter.
## Native driver alarms (equipment-tag path)
@@ -193,15 +193,27 @@ The alarm is authored on the `Tags` tab of the equipment page (`/uns/equipment/{
by editing the tag's raw `TagConfig` JSON to include the `"alarm"` object. No
other configuration is required.
### Deferred follow-ups
### Native-alarm OPC UA operator operations
Two items are explicitly out of scope for Phase B:
An OPC UA client can **Acknowledge** a native (e.g. Galaxy) condition and the ack
now propagates to the device. The `OnAcknowledge` handler on a native condition
routes through a separate `NativeAlarmAckRouter` seam (instead of the scripted
`AlarmCommandRouter`) → `DriverHostActor` (a condition NodeId → `(DriverInstanceId,
FullName)` inverse map, Primary-gated) → the owning driver's
`IAlarmSource.AcknowledgeAsync` → Galaxy gateway → AVEVA. The call carries the
authenticated operator's display name via the `AlarmAcknowledgeRequest.OperatorUser`
field. This is fire-and-forget — a failed upstream ack is not surfaced back to the
OPC UA client (mirrors the Galaxy write-outcome limitation). See [AlarmTracking.md
§Native alarm acknowledge → AVEVA](AlarmTracking.md#native-alarm-acknowledge--aveva)
for the full routing diagram.
1. **Inbound device-ack**: an OPC UA client `Acknowledge` currently updates the
local `AlarmConditionState` but does **not** yet propagate back to the device
via `IAlarmSource.AcknowledgeAsync` (→ AVEVA). Device-ack round-trip is a
deferred follow-up.
2. **AdminUI Galaxy address-picker pre-fill**: the `alarm` object must be authored
**Enable/Disable** on a native condition returns `BadNotSupported` — the driver
backing a native alarm has no enable/disable surface distinct from OPC UA; the
Part 9 enable/disable concept maps to the scripted-alarm engine only.
One item remains explicitly out of scope:
1. **AdminUI Galaxy address-picker pre-fill**: the `alarm` object must be authored
by editing the tag's raw `TagConfig` JSON today; a future picker enhancement
could pre-fill `alarmType` / `severity` from driver discovery
(`DriverAttributeInfo.IsAlarm`).
@@ -212,10 +224,14 @@ Operators interact with active scripted alarms through two surfaces — both con
### AlarmAck gate (OPC UA method path)
`OtOpcUaNodeManager` wires the OPC UA Part 9 condition methods (Acknowledge / Confirm / AddComment / OneShotShelve / TimedShelve / Unshelve) on each `AlarmConditionState` node. Every method call is gated on the `AlarmAck` LDAP role — fail-closed: a session with no resolved roles or no `AlarmAck` group membership receives `BadUserAccessDenied` immediately without reaching the engine. The role is carried on the session by `RoleCarryingUserIdentity` (a `UserIdentity` subclass that preserves the LDAP-resolved role set past `OpcUaApplicationHost`).
`OtOpcUaNodeManager` wires the OPC UA Part 9 condition methods (Acknowledge / Confirm / AddComment / OneShotShelve / TimedShelve / Unshelve / Enable / Disable) on each `AlarmConditionState` node. Every method call is gated on the `AlarmAck` LDAP role — fail-closed: a session with no resolved roles or no `AlarmAck` group membership receives `BadUserAccessDenied` immediately without reaching the engine. The role is carried on the session by `RoleCarryingUserIdentity` (a `UserIdentity` subclass that preserves the LDAP-resolved role set past `OpcUaApplicationHost`).
On allow, the handler publishes a `Commons.OpcUa.AlarmCommand` (containing command kind, condition id, comment, and operator principal) onto the `alarm-commands` DPS topic. The node manager itself stays Akka-free: the dispatch action is a settable `Action<AlarmCommand>` injected at boot by the hosted service.
**Scripted vs native conditions — Enable/Disable and Acknowledge:**
- **Scripted conditions** — all eight Part 9 operations (including Enable/Disable) route through `AlarmCommandRouter` onto the `alarm-commands` topic, which `ScriptedAlarmHostActor` dispatches to the engine (`EnableAsync` / `DisableAsync` / `AcknowledgeAsync` / …). On enable, `ActiveState` is re-derived from the next predicate evaluation.
- **Native (driver-fed) conditions** — `OnAcknowledge` branches to `NativeAlarmAckRouter` and routes the ack to the owning driver rather than the scripted engine (see §[Native-alarm OPC UA operator operations](#native-alarm-opc-ua-operator-operations) above). `OnEnableDisable` returns `BadNotSupported` immediately — native conditions have no engine-side enable/disable surface.
`OnTimedUnshelve` (the SDK's internal auto-unshelve timer) bypasses the client gate — it is system-initiated and not subject to operator role checks.
### Delta-gate de-duplication