docs(alarms): OPC UA Enable/Disable wired + native-ack→AVEVA with principal + HistoryUpdate permission bit
This commit is contained in:
@@ -79,6 +79,42 @@ the dedup.
|
||||
|
||||
## Acknowledge routing — Galaxy / driver alarms
|
||||
|
||||
### Native alarm acknowledge → AVEVA
|
||||
|
||||
When an OPC UA client Acknowledges a **native** (driver-fed, e.g. Galaxy)
|
||||
`AlarmConditionState` node, the node manager's `OnAcknowledge` handler
|
||||
branches on native-ness and routes through a dedicated path — separate
|
||||
from the scripted `AlarmCommandRouter`:
|
||||
|
||||
1. **`OtOpcUaNodeManager.HandleNativeAlarmAck`** — gates on the caller's
|
||||
`AlarmAck` role (fails closed: no role → `BadUserAccessDenied`), then
|
||||
dispatches a `NativeAlarmAck(ConditionNodeId, Comment, OperatorUser)`
|
||||
to the `NativeAlarmAckRouter` seam (fire-and-forget, non-blocking under
|
||||
the node-manager Lock). `OperatorUser` carries the authenticated
|
||||
session principal's display name.
|
||||
2. **`DriverHostActor.HandleRouteNativeAlarmAck`** — receives a
|
||||
`RouteNativeAlarmAck` message (the host maps `NativeAlarmAck` at the
|
||||
wiring boundary to keep Runtime Akka-free of the OPC UA layer). Applied
|
||||
**Primary-gate first**: a Secondary or Detached node drops the message
|
||||
silently. On Primary, resolves the condition NodeId from the
|
||||
`_driverRefByAlarmNodeId` inverse map (NodeId → `(DriverInstanceId,
|
||||
FullName)`) and Tells the owning `DriverInstanceActor` a
|
||||
`RouteAlarmAck(FullName, Comment, OperatorUser)`.
|
||||
3. **Galaxy driver** — `DriverInstanceActor` calls the driver's
|
||||
`IAlarmSource.AcknowledgeAsync` with an `AlarmAcknowledgeRequest`
|
||||
carrying the authored `FullName` as the `ConditionId` and the
|
||||
authenticated `OperatorUser`. The driver forwards this to the Galaxy
|
||||
gateway → AVEVA via `GatewayGalaxyAlarmAcknowledger`.
|
||||
**Fire-and-forget** — a failed upstream ack is not surfaced back to the
|
||||
OPC UA client (mirrors the Galaxy write-outcome limitation; the local
|
||||
`AlarmConditionState` SDK update already committed at step 1).
|
||||
|
||||
Only the **Acknowledge** is routed to the driver. `Confirm` / `AddComment`
|
||||
/ `Shelve` operations on a native condition stay on the scripted
|
||||
`AlarmCommandRouter` path (Phase 3 scope is Acknowledge → AVEVA only).
|
||||
|
||||
### Legacy sub-attribute path
|
||||
|
||||
`DriverNodeManager` picks the acknowledger when registering each
|
||||
condition (PR B.3 logic):
|
||||
|
||||
|
||||
+25
-9
@@ -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
|
||||
|
||||
@@ -205,6 +205,7 @@ public enum NodePermissions : int
|
||||
AlarmConfirm = 1 << 9,
|
||||
AlarmShelve = 1 << 10,
|
||||
MethodCall = 1 << 11,
|
||||
HistoryUpdate = 1 << 12, // OPC UA annotation / insert / delete (separate from HistoryRead)
|
||||
|
||||
ReadOnly = Browse | Read | Subscribe | HistoryRead | AlarmRead,
|
||||
Operator = ReadOnly | WriteOperate | AlarmAcknowledge | AlarmConfirm,
|
||||
@@ -215,6 +216,8 @@ public enum NodePermissions : int
|
||||
|
||||
The three Write tiers map to Galaxy's v1 `SecurityClassification` — `FreeAccess`/`Operate` → `WriteOperate`, `Tune` → `WriteTune`, `Configure` → `WriteConfigure`. `SecuredWrite` / `VerifiedWrite` / `ViewOnly` classifications remain read-only from OPC UA regardless of grant.
|
||||
|
||||
`HistoryUpdate` (bit 12) is a **separate** permission from `HistoryRead` so a read-only grant cannot also authorize OPC UA HistoryUpdate (annotation / insert / delete) operations. `TriePermissionEvaluator` maps `OpcUaOperation.HistoryUpdate` to this bit, closing the read⇒update hole that existed when the only history permission was `HistoryRead`. Note: `HistoryUpdate` is **not** included in any composite bundle (`ReadOnly` / `Operator` / `Engineer` / `Admin`) because the HistoryUpdate service surface (the actual insert/replace/delete backend RPC) is not yet implemented — a client calling HistoryUpdate still receives the SDK's default reject regardless of the grant.
|
||||
|
||||
### Evaluator — `PermissionTrie`
|
||||
|
||||
`src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/`:
|
||||
|
||||
Reference in New Issue
Block a user