docs(v3): Batch 3 PR description
Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
# v3 Batch 3 — UNS reference-only Equipment + `{{equip}}/<RefName>` reference-relative resolution
|
||||||
|
|
||||||
|
Implements `docs/plans/2026-07-15-v3-batch3-uns-rework-plan.md` (WP1–WP4), 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
|
||||||
|
|
||||||
|
- **Contracts** — `IEffectiveNameGuard` (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`.
|
||||||
|
- **WP2** — `EffectiveNameGuard` (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).
|
||||||
|
- **WP3** — `RawTreeService.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. **Collision** — *authoring:* 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
|
||||||
Reference in New Issue
Block a user