Files
lmxopcua/docs/plans/2026-07-15-v3-batch3-uns-rework-plan.md
T
Joseph Doherty cf03ca279d docs(v3): implementation plans — master orchestration + 4 batch plans
- v3 design finalized: all 7 open items resolved (live historian probe:
  255-char tagname limit; SDK multi-notifier spike: native AddNotifier,
  never duplicate ReportEvent; NamespaceKind.Simulated retired; CSV column
  dictionaries; ScadaBridge re-bind sized; rename-warning scan decided),
  reconciled with the pre-v3 universal discovery browser (its new §11
  v3 forward note re-targets the browse commit contract).
- Calculation pseudo-driver mini-design (IDependencyConsumer capability,
  mux-fed change/timer triggers, Tarjan cycle deploy gate, VT-parity
  error semantics).
- Implementation plans for Opus-agent execution, grounded against exact
  files/symbols: master plan (batch DAG, worktree-isolated parallel
  waves, contract-first fan-out, global gotcha list) + one plan per
  batch (1 schema+RawPath identity, 2 /raw UI+Calculation, 3 UNS
  reference-only+{{equip}}, 4 dual-namespace address space = v3.0),
  each with per-wave file ownership and a mandatory docker-dev live gate.
2026-07-15 16:50:30 -04:00

7.2 KiB
Raw Blame History

v3 Batch 3 — UNS reference-only Equipment + {{equip}} reference-relative resolution

Branch: v3/batch3-uns-rework (from master after Batch 2 merges) Design authority: v3 design §"UNS Equipment — reference-only", §"Scripting — paths and {{equip}} under v3", §"Error handling / edge cases" (effective-name collision, rename-warning scan). End state: Equipment holds UnsTagReference rows instead of authored tags; scripts resolve {{equip}}/<RefName> through references; every collision/unresolved case is a clear deploy error. Address space still dark for values (Batch 4), so verification is authoring + deploy-gate level.

Work packages

Wave A (3 agents, parallel)

B3-WP1 — Equipment Tags tab → reference list + raw-tag picker

  • Components/Pages/Uns/EquipmentPage.razor Tags tab: replace the Batch-1-stubbed authored-tag flow with a UnsTagReference list — columns: effective name, raw path, inherited datatype/access (read-only, from the raw tag), display-name override, remove. TagModal.razor's driver-binding path is deleted (VirtualTag/ScriptedAlarm modals untouched).
  • "+ Add reference" opens a raw-tree picker modal reusing Batch 2's RawTree component in picker mode (multi-select checkboxes on Tag leaves; Device/TagGroup select-all), scoped to the equipment's cluster (cross-cluster structurally impossible — the service query is cluster-filtered, not just the UI).
  • ImportEquipmentModal.razor + Uns/EquipmentInput.cs + UnsTreeService.ImportEquipmentAsync: drop the DriverInstanceId column (equipment no longer carries a driver).
  • Service mutations in UnsTreeService (+ IUnsTreeService): add/remove references, set override — each enforcing WP2's uniqueness check and returning readable errors.

B3-WP2 — effective-name uniqueness (authoring + deploy gate)

  • Rule: within an equipment, effective name (DisplayNameOverride else raw Name) is unique across references, VirtualTags, and ScriptedAlarms.
  • Authoring-time: service-level check in every mutation that can collide (add reference, set override, add/rename VirtualTag or ScriptedAlarm).
  • Deploy-time: DraftValidator gains the same rule over the draft snapshot — this is what catches rename-induced collisions (a raw rename the authoring check never saw); the error names both colliding sources and the equipment.
  • DraftSnapshotFactory extended to load UnsTagReference + raw-tag names into the snapshot.
  • Unit tests: authoring rejection, deploy rejection post-rename, override-vs-VT clash, case-sensitivity pinned (ordinal, matching NodeId semantics).

B3-WP3 — rename-warning script scan

  • Extend Batch 2's rename-warning seam in RawTreeService: on rename of a tag or any ancestor, compute affected RawPaths (prefix scan) and warn when a tag is (a) historized without a historianTagname override (history forks), (b) UNS-referenced (names the equipment), (c) matched by a substring scan of Script bodies for the old RawPath (service-level, tolerates false positives — decided in the design; no new bookkeeping). {{equip}}-relative refs need no scan (they resolve through references; the deploy gate catches breakage).
  • UI: warning list in the rename confirm dialog; rename proceeds on confirm (Kepware-equivalent, deliberate).

Wave B (1 agent — single integration point)

B3-WP4 — {{equip}} reference-relative resolution

  • src/Core/ZB.MOM.WW.OtOpcUa.Commons/Types/EquipmentScriptPaths.cs: delete DeriveEquipmentBase (shared first-dot prefix of child FullNames — cannot survive reference-only equipment). SubstituteEquipmentToken is replaced by per-reference resolution: {{equip}}/<RefName> → look up the equipment's UnsTagReference rows by effective name → substitute the backing RawPath. Token syntax note: today's derivation used a dot joint ({{equip}}.X); v3's is slash-shaped ({{equip}}/<RefName>) per the design — update ExtractDependencyRefs / ExtractAlarmDependencyRefs and the EquipToken handling accordingly, and pin the new syntax in tests.
  • The same two compose seams substitute as today: OpcUaServer/AddressSpaceComposer.cs (~line 210 branch) and Runtime/Drivers/DeploymentArtifact.cs (SubstituteEquipmentToken call, line ~563) — both now receive the equipment's reference map instead of a derived base prefix.
  • Unresolved <RefName> = deploy-time validation error (in DraftValidator, where the reference map + script sources are both in the snapshot), naming the script, the equipment, and the missing ref name — replacing today's silent null-base no-substitution. Alarm {TagPath} message tokens follow the same rule.
  • Monaco/editor parity: AdminUI/ScriptAnalysis/ScriptAnalysisService.cs — the {{equip}} completion branch (lines ~176182) now completes reference effective names for the equipment context; the diagnostics branch (~280284) flags unresolved refs identically to the deploy gate (editor accepts ⇔ publish accepts — the established invariant). IScriptTagCatalog gains the per-equipment reference list; absolute-path completions list RawPaths (already moved in Batch 1's catalog rework — verify, don't assume).
  • Tests: substitution unit tests (resolved, unresolved→error, override-named ref, alarm-token), composer/artifact parity tests, ScriptAnalysis completion/diagnostic tests.

Wave schedule

Wave Packages Agents Parallel-safe because
A WP1 ∥ WP2 ∥ WP3 3 WP1 owns Razor + service mutation methods; WP2 owns validator/snapshot + service check helpers (shared file UnsTreeService.cs — WP1 owns it, WP2 delivers its check as a separate injectable EffectiveNameGuard consumed by WP1); WP3 owns RawTreeService warning code
B WP4 1 Touches Commons + composer + artifact + validator + ScriptAnalysis — single integration agent, rebased on Wave A

Batch verification gate (live /run on docker-dev)

Rebuild both centrals; fixtures up; Batch 2's raw tags present (re-author or re-seed).

  1. Equipment page: "+ Add reference" picker is cluster-scoped (raw tags of another cluster absent), multi-select pulls 3+ tags at once; rows show raw path + inherited type/access; set a DisplayNameOverride → effective name updates.
  2. Collision: add a reference whose effective name equals an existing VirtualTag → authoring rejected with both sources named. Then force a rename-induced clash (rename the raw tag in /raw so two references collide) → authoring allowed at rename, deploy blocked by DraftValidator naming both.
  3. {{equip}}: author a VirtualTag script using ctx.GetTag("{{equip}}/<RefName>") — Monaco completes the reference names and shows no diagnostic; deploy succeeds. Misspell the ref → Monaco diagnostic AND deploy error agree (same message family).
  4. Rename warning: rename a raw tag that is historized + referenced + named in a script literal → confirm dialog lists all three warnings; renaming an unrelated tag warns nothing.
  5. ImportEquipmentModal: import runs without the driver column.

Deploys via POST /api/deployments (X-Api-Key). Values still dark — assert nothing about live data. Record evidence in the PR.