Closes task #121 (partial — creation-time gate; decision #153 per-item
revocation stamp is a follow-up).
Before this commit a session could subscribe to any node via
CreateMonitoredItems, even nodes where Read was denied — the
subscription would surface BadUserAccessDenied on each data-change
read, but the client saw a successful CreateMonitoredItems response
and held the subscription open, wasting resources and leaking the
address-space shape through the item metadata.
New override on DriverNodeManager.CreateMonitoredItems:
- Pre-iterates itemsToCreate, gates each through AuthorizationGate with
OpcUaOperation.CreateMonitoredItems at the target node's scope.
- For denied slots: sets errors[i] = new ServiceResult(
StatusCodes.BadUserAccessDenied). The OPC Foundation base stack
honours pre-populated non-success errors and skips item creation for
those slots — the subscription never holds a handle to a denied
node.
- Preserves prior errors (e.g. BadNodeIdUnknown) — first diagnosis wins.
- Non-string-identifier references (stack-synthesized numeric ids)
bypass the gate.
Extracted the pure filter logic into
GateMonitoredItemCreateRequests(items, errors, identity, gate,
scopeResolver) — static internal, unit-testable without the OPC UA
server stack.
Tests — 6 new in MonitoredItemGatingTests.cs (gate-null no-op,
denied-gets-BadUserAccessDenied, allowed-passes, mixed-batch-denies-
per-item, pre-populated-error-preserved, numeric-id-bypass). Server.Tests
263 → 269.
Known follow-ups:
- Per-item (AuthGenerationId, MembershipVersion) stamp (decision #153)
for detecting revocation mid-subscription — needs subscription-layer
plumbing.
- TransferSubscriptions not yet wired (same pattern, smaller scope).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>