From 97afa84fcd3914d4e9df93f1b91b77058eb8ac5f Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 23 Jul 2026 15:17:13 -0400 Subject: [PATCH] docs(dcl): sweep ns= examples to durable nsu= form (v3 cutover #14) --- docs/requirements/Component-DataConnectionLayer.md | 1 + .../Interfaces/Protocol/IBrowsableDataConnection.cs | 2 +- .../Adapters/OpcUaDataConnection.cs | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/requirements/Component-DataConnectionLayer.md b/docs/requirements/Component-DataConnectionLayer.md index edbfdbed..8207fd45 100644 --- a/docs/requirements/Component-DataConnectionLayer.md +++ b/docs/requirements/Component-DataConnectionLayer.md @@ -203,6 +203,7 @@ DCL is a clean data pipe on the hot path. Browse is an **opt-in capability** for - `Resolve(reference, session.NamespaceUris)` accepts **both** `ns=;s=` (existing bindings, unchanged) and the durable `nsu=;s=`, mapping the URI to the live index at use time. It is used at every parse site — subscribe, read, write, alarm-subscribe, browse. A URI absent from the server's `NamespaceArray` throws with a message naming the URI, rather than binding to whatever sits at that index; a `svr=` reference to another server is rejected outright. - `ToDurable(expandedNodeId, session.NamespaceUris)` is what the browser emits, so **what the picker shows is what gets stored** and newly-authored bindings are index-proof from the start. Namespace 0 is exempt (spec-fixed) and keeps its short form. It also closes a round-trip gap: the browse previously emitted `ExpandedNodeId.ToString()`, which for a URI- or server-index-carrying reference produced a string `NodeId.Parse` could not read back. - Bindings stored before this change keep their `ns=` form and keep working; they are only as durable as the server's namespace order. Re-authoring against a picker (which now emits `nsu=`) is what makes them durable — see `docs/plans/` and Gitea #14 for the OtOpcUa v3.0 cutover, where v2's sole custom namespace and v3's `raw` namespace both sit at index 2, so v2-era bindings resolve against v3 without error while meaning something else. + - **OtOpcUa v3.0 dual-namespace cutover**: v3.0 splits OtOpcUa's address space into two namespaces, `raw` (`https://zb.com/otopcua/raw`) and `uns` (`https://zb.com/otopcua/uns`), where v2 exposed one. ScadaBridge is already namespace-URI-durable against this split: `OpcUaNodeReference` resolves a stored `nsu=;s=...` binding against the live session `NamespaceArray` at use time, so it is index-proof regardless of which namespace a given tag lands in or how many namespaces the server publishes. The node-browser picker already nudges operators toward the durable form, since `ToDurable` is what it emits for newly-authored bindings. See `docs/plans/2026-07-23-otopcua-v3-dual-namespace-cutover-scope.md` for the full cutover scope. - Browse runs against the live session; no caching at DCL. - **Frame-size guard**: the reply crosses the site→central Akka frame (default 128 KB) on a temp Ask actor; an oversized reply is silently discarded by remoting, hanging the picker. The child handler caps each `BrowseNodeResult` to a byte budget (~100 KB) before replying, OR-ing the adapter's own truncation signal into `Truncated`. This is protocol-agnostic (every adapter's reply funnels through it). Per-protocol upstream caps narrow the window first: OPC UA requests at most 500 references per node (continuation point → `Truncated`); MxGateway relies on the gateway's `BrowseChildren` page cap. - **`BrowseNext` paging** (OPC UA): a `Truncated` level no longer forces manual node-id entry. When the OPC UA browse is truncated, the adapter returns the session's continuation point as the reply's opaque `ContinuationToken`; a follow-up `BrowseNodeCommand` carrying that token calls `Session.BrowseNext` to fetch the next page (the picker exposes this as **"Load more"**). Continuation points are session-bound and can expire — `BadContinuationPointInvalid` is caught and the browse restarts from a fresh first page rather than failing. Manual node-id entry remains as a fallback when the site or its session is offline. diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/IBrowsableDataConnection.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/IBrowsableDataConnection.cs index 0b9d4208..2b7de4fd 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/IBrowsableDataConnection.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/IBrowsableDataConnection.cs @@ -33,7 +33,7 @@ public record BrowseChildrenResult( bool Truncated, string? ContinuationToken = null); -/// Server-issued node identifier (e.g. "ns=2;s=Devices.Pump1.Speed"). +/// Server-issued node identifier (e.g. "nsu=https://server/ns;s=Devices.Pump1.Speed"). /// Human-readable display name from the server's DisplayName attribute. /// Classifies the node for UI purposes (Variable rows are selectable; Object rows are navigable). /// Hint so the UI can render an expand chevron without a second roundtrip. diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/OpcUaDataConnection.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/OpcUaDataConnection.cs index 10344eed..7176492c 100644 --- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/OpcUaDataConnection.cs +++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/OpcUaDataConnection.cs @@ -12,7 +12,8 @@ namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters; /// Maps IDataConnection methods to OPC UA concepts via IOpcUaClient abstraction. /// /// OPC UA mapping: -/// - TagPath → NodeId (e.g., "ns=2;s=MyDevice.Temperature") +/// - TagPath → NodeId (durable form, e.g. "nsu=https://server/ns;s=MyDevice.Temperature"; +/// the legacy "ns=2;s=..." index form is still accepted — see OpcUaNodeReference) /// - Subscribe → MonitoredItem with DataChangeNotification /// - Read/Write → Read/Write service calls /// - Quality → OPC UA StatusCode mapping