Files
lmxopcua/docs/plans/2026-07-16-v3-batch3-PR.md
T
Joseph Doherty 0c65e4412e
v2-ci / build (pull_request) Successful in 3m36s
v2-ci / unit-tests (pull_request) Failing after 9m8s
docs(v3): Batch 3 PR description
Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
2026-07-16 07:38:20 -04:00

6.8 KiB
Raw Blame History

v3 Batch 3 — UNS reference-only Equipment + {{equip}}/<RefName> reference-relative resolution

Implements docs/plans/2026-07-15-v3-batch3-uns-rework-plan.md (WP1WP4), executed via the /v3-batch 3 coordinator: contracts-first fan-out, worktree-isolated Opus agents per wave, a code-reviewer pass + green build/test after each wave, then the non-negotiable 5-item live gate on docker-dev.

What landed

  • ContractsIEffectiveNameGuard (authoring-time effective-name uniqueness seam; WP2 implements, WP1 consumes).
  • Wave A (3 parallel agents):
    • WP1 — equipment Tags tab → UnsTagReference list (effective name / RawPath / inherited DataType+AccessLevel / display-name override); new AddReferenceModal reusing RawTree in opt-in PickerMode (multi-select tag leaves + device/tag-group "Select all tags below"), cluster-scoped (structural + server-enforced tag.cluster == equipment.cluster); UnsTreeService reference CRUD; consumes IEffectiveNameGuard in all colliding mutations; ImportEquipmentModal/EquipmentInput dropped DriverInstanceId; deleted the retired equipment TagModal.
    • WP2EffectiveNameGuard (injectable, ordinal) + DI registration; verified the deploy-gate UnsEffectiveNameCollision rule (already computes ref effective name as override-else-current-raw-name, so it catches rename-induced collisions; ordinal; names both sources + equipment).
    • WP3RawTreeService.BuildRenameWarningsAsync: refined historized warning (only tags without a historianTagname override) + UNS-referenced + new substring scan of every Script.SourceCode for affected tags' OLD RawPaths (recomputed pre-save via RawPathResolver, ordinal).
  • Wave B (1 integration agent):
    • WP4{{equip}}/<RefName> reference-relative resolution. EquipmentScriptPaths.DeriveEquipmentBase deleted; slash joint replaces the dot joint; new shared EquipmentReferenceMap (equipmentId → effectiveName → RawPath) built identically at both compose seams (AddressSpaceComposer + DeploymentArtifact, byte-parity). Unresolved-ref deploy error (EquipReferenceUnresolved) covering VT scripts, SA predicates, and SA message-template tokens; authoring guard (ValidateEquipTokenAsync, the Wave-A M1 fix); Monaco {{equip}}/ completion of reference effective names + unresolved diagnostic (OTSCRIPT_EQUIPREF), EquipmentId threaded request→model→JS.

Address space stays dark (values light up in Batch 4). Full solution builds 0 errors; Commons 309, Configuration 121, AdminUI 659, OpcUaServer 335/4-skip, Runtime 361/42-skip (the skips are the Batch-1 dark-address-space tests Batch 4 un-skips).

Reviewer findings fixed in-branch

  • Wave A — no HIGH. Verified: the register-AND-consume guard seam (prod DI injects the real guard; the NoOp fallback only wins under new in tests; guard invoked in all 6 colliding mutations); cluster scoping structural + server-enforced; Razor default RawTree byte-unchanged; WP3 pre-save RawPath recomputation. L1 (misleading concurrency comment) fixed.
  • Wave B — no HIGH; byte-parity + production wiring confirmed. M1 fixed: the editor⇔authoring⇔deploy invariant now holds on the ScriptedAlarm surface too (SA predicate + message-template {{equip}} refs validated at authoring, matching the deploy gate). L3 fixed: parity test hardened (unresolved-ref-intact
    • folder/tag-group RawPath ancestry). L1 already closed at the write path (empty override normalized→null).
  • Deferred (documented follow-ups): absolute-path Monaco completion still projects the raw leaf name, not the RawPath (out of {{equip}} scope; {{equip}}/ completion works) — rework ScriptTagCatalog to RawPaths in Batch 4; a broken raw-topology-chain reference is deploy-accepted but compose-dropped (low reachability); message-template {{equip}}/X is gate-validated but rendered/substituted only in Batch 4.

Live /run gate (docker-dev :9200, both central nodes rebuilt on Batch-3 code)

Substrate: an Area→Line→Equipment seeded in MAIN, plus a SITE-A raw tag so cross-cluster exclusion is demonstrable. (Hand-seeded equipment surfaced the Batch-1 EquipmentIdNotDerived invariant — the canonical EQ-<hash> id was applied before the gate.)

  1. Reference picker + list — "+ Add reference" header "The tree is scoped to the equipment's cluster"; only the Main cluster root shows (SITE-A/B absent — the seeded SiteAOnly tag unreachable); lazy expansion; a device "Select all tags below" pulled tags across a collapsed group → 4 references in one commit. Rows show effective name / RawPath (incl. deep opcua1/plc/OpcPlc/Telemetry/Basic/… ancestry) / DataType / Access / override. Set override "MainPressure" → effective name updated HR200 → MainPressure, RawPath unchanged.
  2. Collisionauthoring: setting a reference override to an existing VirtualTag's name → red banner "effective name 'GateVt' already used by VirtualTag 'VT-gatevt' in equipment '…'" (not persisted). rename-induced: renamed a raw tag so two references collide (allowed at rename) → deploy 422 [UnsEffectiveNameCollision] 2 UNS signals collide on effective name 'ImportedTag' … reference '…', reference '…'.
  3. {{equip}} (editor ⇔ deploy) — resolving {{equip}}/MainPressure deploys 202; misspelled {{equip}}/MainPresure deploys 422 [EquipReferenceUnresolved] … has no reference named 'MainPresure'. Monaco: red squiggle + hover "'{{equip}}/MainPresure' does not resolve — … no reference named 'MainPresure' … (OTSCRIPT_EQUIPREF)"; typing {{equip}}/ completes the four reference effective names (AlternatingBoolean, ImportedTag, MainPressure, RandomSignedInt32).
  4. Rename warning — renaming a driver whose beneath-it tag is historized-without-override + UNS-referenced
    • named in a script literal → "Renamed — with warnings" listing all three (historized fork, UNS-referenced by 'gateequip', "2 scripts ('cval','gatevt') reference tags … by their raw paths"); renaming an unrelated driver → no warning dialog. (Note: direct tag rename flows through UpdateTagAsync which has no warnings surface — warnings fire on container renames affecting descendants; documented follow-up if per-tag-rename warnings are wanted.)
  5. Import without driver column — the Import equipment CSV modal columns are Name, MachineCode, UnsLineId (+ optional ZTag, SAPID, Manufacturer, Model); no DriverInstanceId.

Docs

docs/Uns.md Tags section rewritten to the reference-only model; docs/ScriptEditor.md updated (WP4) to the slash/reference {{equip}} semantics; CLAUDE.md gains a v3 Batch 3 paragraph.

https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox