Files
ScadaBridge/docs/plans/2026-07-23-otopcua-v3-raw-path-live-gate.md
T
Joseph Doherty d1ade5653b docs(plans): bookkeeping sync — reconcile stale trackers with merged code
A verified audit of all ~90 plan documents (2026-08-01) found ~30 .tasks.json
trackers and several plan headers still reporting 'pending'/'draft' for work
fully merged to main. Sync them so future audits don't re-litigate closed work:

- Flip ~380 stale task statuses to completed across March/May/June/July
  trackers (audit-log series, milestones M5-M10, playwright waves, stillpending,
  LocalDb, ClusterClient->gRPC DoD rows, and more), each verified against
  code/git evidence before flipping.
- Annotate obsolete-not-done rows: ClusterClient CLI transport (never built,
  HTTP shipped), TreeView Areas/Instances pages (replaced by Topology),
  template-tree drag-drop (dropped for M9 menu reorder), otopcua item C
  (premise superseded by #17).
- Flip stale headers: aggregated-live-alarm + kpi-rollups 'Draft not executed'
  -> Delivered 2026-07-10; otopcua cutover-scope SCOPING -> DECIDED;
  scadabridge-rename -> Implemented; LocalDb phase1/2 status strings ->
  merged 28ca04d7.
- Fix doc drift: T9/T10 'deferred' -> shipped as SMS (Teams dropped); waitfor
  sandbox follow-up shipped; followups #52/#53/#54/#162/#207 resolved; purge
  TODO closed by PendingDeploymentPurgeActor; live-gate pre-existing failures
  #28/#29/#31 fixed; auto-down boot-alone residual superseded by self-first
  seeds; supersession banners on keep-oldest SBR + ClusterClient-era designs;
  requirements-traceability 'Pending' clarified as frozen plan-generation
  status.

Deliberately left pending (genuinely open, tracked in the pending-work list):
opcua-tag-browser task 19 (live smoke), ipsen tasks 7-8 (vd03 verification),
selfform task 7 (vd03 overlay, user-held), live-gate observation 1
(external-system delete orphan bug), otopcua item A + maxDepth calibration.
2026-08-01 08:53:56 -04:00

5.7 KiB

OtOpcUa v3 raw-path live-gate (Gitea #14) — PASS

Date: 2026-07-23 · Scope: the raw-subtree slice of #14 (decision: "raw gate now, flag B/C"). Rig: ScadaBridge docker rig (scadabridge:latest, rebuilt with the host-rewrite fix) against the live otopcua-dev v3 cluster (otopcua-host:dev, built 2026-07-23, branch feat/mesh-phase5, past the v3 merge ec6598ce). Related: scope doc 2026-07-23-otopcua-v3-dual-namespace-cutover-scope.md, phase-2 plan 2026-07-23-otopcua-v3-nsu-hardening-and-browse-ux.md.

Headline

ScadaBridge is confirmed structurally v3-safe against a real dual-namespace server — durable nsu= references are emitted by browse, stored on bindings, and resolved live against the server's NamespaceArray to Good-quality reads. The gate also surfaced and fixed a real DCL robustness bug (couldn't dial a server advertising 0.0.0.0). Items B/C remain deferred — the dev cluster has no UNS nodes and no native alarms to gate them against.

Environment findings

  • v3 cluster publishes both namespaces: ns=2 = https://zb.com/otopcua/raw, ns=3 = https://zb.com/otopcua/uns. Raw lands at ns=2 — the exact index collision scope §4 item A predicted (v2's sole namespace and v3 raw both at ns=2).
  • Raw tree is sparsely populated: one materialized live Variable, nsu=https://zb.com/otopcua/raw;s=sa-modbus2/plc/SiteAOnly (Int16, AccessLevel=1 → read-only). Other drivers (calc1, opcua1, pymodbus, gate-modbus, Area1) expose folders but no live leaf tags.
  • UNS (ns=3) is emptyns=3;s=OtOpcUaBadNodeIdUnknown; no Equipment authored.
  • No native alarms — zero HasCondition/HasEventSource refs anywhere in the raw tree.

The fix this gate produced

RealOpcUaClient took the server-advertised EndpointDescription.EndpointUrl verbatim for the session channel. OtOpcUa advertises opc.tcp://0.0.0.0:4840/OtOpcUa (its OpcUaApplicationHostOptions.PublicHostname defaults to 0.0.0.0), and dialling 0.0.0.0 from inside the client container hits its own loopback → connect fails. Added RealOpcUaClient.RewriteEndpointHostForReachability (swaps the advertised authority for the reachable discovery host/port, preserves scheme+path, no-op when already reachable), called in both ConnectAsync and VerifyEndpointAsync. Mirrors CoreClientUtils.SelectEndpoint and OtOpcUa's own client (IEndpointDiscovery). Commit a1abbff7 on fix/opcua-discovery-host-rewrite; 9 unit tests (RealOpcUaClientEndpointRewriteTests).

Wiring (how the gate was run)

  1. Rebuilt scadabridge:latest with the fix (bash docker/deploy.sh).
  2. docker network connect otopcua-dev_default scadabridge-site-a-a (and -b) — bridged the two docker networks so site-a can resolve otopcua-dev-site-a-1-1.
  3. data-connection create --site-id 1 --name OtOpcUa-v3-raw --protocol OpcUaendpointUrl=opc.tcp://otopcua-dev-site-a-1-1:4840/OtOpcUa, securityMode none (id 3044).
  4. deploy artifacts --site-id 1 — pushed the def to site-a's LocalDb + made it live in the DCL.
  5. Template 2148 (OtOpcUaV3Probe) + attribute SiteA (Int32, --data-source nsu=https://zb.com/otopcua/raw;s=sa-modbus2/plc/SiteAOnly); instance 98 (otopcua-v3-probe-1) on site-a; set-bindings [["SiteA",3044]]; deploy instance.

Checks

# Check Result
1 Connect via ConnectAsync (through the rewrite fix) PASSdata-connection browse returned the address space
2 Connect via VerifyEndpointAsync (second rewrite call site) PASSverify-endpoint{"success":true}
3 Browse emits durable nsu= throughout PASS — root nsu=…/raw;s=OtOpcUa, drivers + …;s=sa-modbus2/plc/SiteAOnly
4 Both namespaces visible / discoverable PASS (partial) — raw browsable; uns registered but empty (nothing to browse)
5 Durable nsu= binding resolves live PASS — attribute stores nsu=…, subscription resolves it against the live NamespaceArray
6 Read round-trip, Good quality, live PASSSiteA Good, values changing 59689→59806→59822→59834 across snapshots
7 Write round-trip N/A — the only materialized tag is read-only (AccessLevel=1); no writable v3 tag to gate

Deferred (unchanged — need OtOpcUa-side authoring on the dev cluster)

  • Item ASTILL OPEN — re-author legacy ns=2 bindings to nsu= (data; the collision is confirmed real here).
  • Item BCLOSED 2026-07-23, see below.
  • Item CCLOSED 2026-07-23, see below.

To gate B/C, the OtOpcUa dev cluster needs UNS Equipment authored (populate ns=3, referencing raw tags) and at least one alarm-configured tag raising a Part-9 condition that fans to raw + equipment notifiers.

That authoring was done the same day and B/C were gated: docs/plans/2026-07-23-otopcua-v3-alarm-bc-live-gate.md — PASS, no ScadaBridge code change needed. B: one subscription per connection delivers each retained condition once, to the most-specific monitored notifier, so overlapping bindings do not double-mirror. C: the premise was obsolete — routing keys off the subscribed node reference (OpcUaAlarmMapper.BuildIdentitySourceObjectReference), not the event SourceName (Gitea #17). Item A remains the only open #14 work.

Rig artifacts created (non-default rig state)

  • ScadaBridge site-a-a / site-a-b attached to otopcua-dev_default (dropped on next docker/deploy.sh).
  • Site-a config: DataConnection 3044 OtOpcUa-v3-raw, template 2148, instance 98 (deployed). Harmless; remove with instance delete 98 / template delete 2148 / data-connection delete 3044 if a clean rig is wanted.