feat(historian): OPC UA HistoryUpdate in the node manager + HistoryUpdate authorization (T6)

- OtOpcUaNodeManager overrides HistoryUpdateData / HistoryDeleteRawModified / HistoryDeleteAtTime,
  block-bridging to the optional IHistorianDataUpdater under the shared HistoryRead batch limiter +
  deadline. Insert/Replace/Update map to HistoryUpdateMode; Remove, IsDeleteModified, events and
  structure data are BadHistoryOperationUnsupported. Per-item codes stitched back in request order
  (MinValue SourceTimestamp => BadInvalidTimestamp, untypeable value => BadTypeMismatch); success
  clears the base's pre-seeded Unsupported error.
- HistoryWrite AccessLevel bit OR-ed in for historized nodes when an updater is wired (raw + UNS);
  it is dispatch-load-bearing in the SDK base. OtOpcUaSdkServer.SetHistorianDataUpdater; the hosted
  service assigns it from an optional HistorianUpdaterHolder before SetSink, clears it on stop.
- AuthorizeHistoryUpdate: ACL on => scoped OpcUaOperation.HistoryUpdate decision (replaces the role
  check); ACL off => #563 key re-check + new OpcUaDataPlaneRoles.HistoryUpdate role, conferred by
  ApiKeyInterimRoleMapper only from a Cluster-scoped HistoryUpdate grant (WriteOperate does not).
- AdminUI ACL editor offers NodePermissions.HistoryUpdate.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 12:55:42 -04:00
parent ff7c6c531a
commit 2ef8ee3ade
10 changed files with 1065 additions and 11 deletions
@@ -30,8 +30,8 @@ public enum NodePermissions : int
MethodCall = 1 << 11,
// OPC UA HistoryUpdate (annotation / insert / delete) — separate from HistoryRead so a
// read-only grant cannot authorize historian writes. Not included in any composite bundle
// until the HistoryUpdate service surface is implemented.
// read-only grant cannot authorize historian writes. Deliberately not included in any
// composite bundle — it must be granted explicitly.
HistoryUpdate = 1 << 12,
// Bundles (one-click grants in Admin UI)