- 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.
7.2 KiB
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.razorTags tab: replace the Batch-1-stubbed authored-tag flow with aUnsTagReferencelist — 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
RawTreecomponent 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 theDriverInstanceIdcolumn (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 (
DisplayNameOverrideelse rawName) 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:
DraftValidatorgains 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. DraftSnapshotFactoryextended to loadUnsTagReference+ 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 ahistorianTagnameoverride (history forks), (b) UNS-referenced (names the equipment), (c) matched by a substring scan ofScriptbodies 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: deleteDeriveEquipmentBase(shared first-dot prefix of child FullNames — cannot survive reference-only equipment).SubstituteEquipmentTokenis replaced by per-reference resolution:{{equip}}/<RefName>→ look up the equipment'sUnsTagReferencerows 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 — updateExtractDependencyRefs/ExtractAlarmDependencyRefsand theEquipTokenhandling accordingly, and pin the new syntax in tests.- The same two compose seams substitute as today:
OpcUaServer/AddressSpaceComposer.cs(~line 210 branch) andRuntime/Drivers/DeploymentArtifact.cs(SubstituteEquipmentTokencall, line ~563) — both now receive the equipment's reference map instead of a derived base prefix. - Unresolved
<RefName>= deploy-time validation error (inDraftValidator, 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 ~176–182) now completes reference effective names for the equipment context; the diagnostics branch (~280–284) flags unresolved refs identically to the deploy gate (editor accepts ⇔ publish accepts — the established invariant).IScriptTagCataloggains 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).
- 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. - 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
/rawso two references collide) → authoring allowed at rename, deploy blocked byDraftValidatornaming both. {{equip}}: author a VirtualTag script usingctx.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).- 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.
- 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.