Commit Graph

2 Commits

Author SHA1 Message Date
Joseph Doherty
8be82e02c2 Path-based NodeIds — decouple client contract from driver address
The pre-refactor design minted OPC UA NodeIds directly from the driver's
FullReference (the native-address string). That had three long-term
problems:

1. OPC UA Part 3 §5.2.2 requires NodeIds to be immutable across a node's
   lifetime. A rename of the underlying device address — Galaxy attribute,
   S7 tag, Modbus register alias — changed the NodeId and broke every
   client that had pinned the previous identifier.
2. Two drivers with coincidentally-matching native addresses (e.g. `temp`
   in Modbus and `temp` in S7 under different Equipment rows) collided on
   the NodeId identifier.
3. TagConfig was being placed verbatim on the wire; for drivers whose
   TagConfig is JSON (every driver shipped today, per the
   CK_Tag_TagConfig_IsJson check constraint), clients saw the raw JSON
   blob as the NodeId string.

Refactor:

* DriverNodeManager.Variable now mints a stable path-based NodeId
  `{driverId}/{folder-path}/{browseName}` and records the driver-side
  FullReference in a new _fullRefByNodeId map. OnReadValue / OnWriteValue
  / ResolveFullRef look the FullReference up via that map instead of
  casting NodeId.Identifier. The old cast path is preserved as a
  fallback so any test fixture that still registers variables with
  FullRef-shaped NodeIds keeps working.

* EquipmentNodeWalker.AddTagVariable now extracts the cross-driver
  `FullName` field from Tag.TagConfig before handing the address to
  DriverAttributeInfo. Every shipped driver stores the wire reference in
  TagConfig[FullName]; falling back to the raw string covers any future
  driver that wants an opaque non-JSON address. ExtractFullName is
  exposed internal for unit coverage.

* scripts/e2e/test-galaxy.ps1 defaults updated to the new path-based
  NodeIds. Verified live against p7-smoke-galaxy on the dev box:
  `ns=2;s=p7-smoke-galaxy/lab-floor/galaxy-line/reactor-1/Source` reads
  return Status=0x00000000 with a real Galaxy byte-array value.

Test suite: 195/195 Core.Tests + 283/283 Server.Tests green. Five new
ExtractFullName / FullName-passthrough tests added.

Task #112 GA-3 — golden-path read verified end-to-end; remaining E2E
script stages still blocked on pre-existing issues (ScriptedAlarm
predicate NRE on empty upstream cache, PowerShell $changeLines.Count
guard), tracked separately.
Task #134 — complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:57:20 -04:00
Joseph Doherty
5fc596a9a1 E2E test script — Galaxy (MXAccess) driver: read / write / subscribe / alarms / history
Seven-stage e2e script covering every Galaxy-specific capability surface:
IReadable + IWritable + ISubscribable + IAlarmSource + IHistoryProvider.
Unlike the other drivers there is no per-protocol CLI — Galaxy's proxy
lives in-process with the server + talks to OtOpcUaGalaxyHost over a
named pipe (MXAccess COM is 32-bit-only), so every stage runs through
`otopcua-cli` against the published OPC UA address space.

## Stages

1. Probe                   — otopcua-cli read on the source NodeId
2. Source read             — capture value for downstream comparison
3. Virtual-tag bridge      — Phase 7 VirtualTag (source × 2) through
                             CachedTagUpstreamSource
4. Subscribe-sees-change   — data-change events propagate
5. Reverse bridge          — opc-ua write → Galaxy; soft-passes if the
                             attribute's Galaxy-side ACL forbids writes
                             (`BadUserAccessDenied` / `BadNotWritable`)
6. Alarm fires             — scripted-alarm Condition fires with Active
                             state when source crosses threshold
7. History read            — historyread returns samples from the Aveva
                             Historian → IHistoryProvider path

## Two new helpers in _common.ps1

- `Test-AlarmFiresOnThreshold` — start `otopcua-cli alarms --refresh`
  in the background on a Condition NodeId, drive the source change,
  assert captured stdout contains `ALARM` + `Active`. Uses the same
  Start-Process + temp-file pattern as `Test-SubscribeSeesChange` since
  the alarms command runs until Ctrl+C (no built-in --duration).
- `Test-HistoryHasSamples` — call `otopcua-cli historyread` over a
  configurable lookback window, parse `N values returned.` marker, fail
  if below MinSamples. Works for driver-sourced, virtual, or scripted-
  alarm historized nodes.

## Wiring

- `test-all.ps1` picks up the optional `galaxy` sidecar section and
  runs the script with the configured NodeIds + wait windows.
- `e2e-config.sample.json` adds a `galaxy` section seeded with the
  Phase 7 defaults (`p7-smoke-tag-source` / `-vt-derived` /
  `-al-overtemp`) — matches `scripts/smoke/seed-phase-7-smoke.sql`.
- `scripts/e2e/README.md` expected-matrix gains a Galaxy row.

## Prereqs

- OtOpcUaGalaxyHost running (NSSM-wrapped) with the Galaxy + MXAccess
  runtime available
- `seed-phase-7-smoke.sql` applied with a live Galaxy attribute
  substituted into `dbo.Tag.TagConfig`
- OtOpcUa server running against the `p7-smoke` cluster
- Non-elevated shell (Galaxy.Host pipe ACL denies Admins)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 12:59:06 -04:00