From bdcb84bd7d8a9f874dcfc3f27d281ea2edc370d6 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 16 Jul 2026 07:37:36 -0400 Subject: [PATCH] docs(v3-batch3): UNS reference-only Equipment + {{equip}}/RefName resolution Uns.md Tags section rewritten to the reference-only model (UnsTagReference list, cluster-scoped picker, effective-name uniqueness, {{equip}}/RefName); CLAUDE.md gains a v3 Batch 3 paragraph. ScriptEditor.md was updated by WP4. Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox --- CLAUDE.md | 2 ++ docs/Uns.md | 51 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index acc8fe69..3e7d1313 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -217,6 +217,8 @@ The AdminUI's global **UNS** page (`/uns`) is the single surface for managing th **v3 (Batch 2): device I/O is authored in the `/raw` project tree** (`GlobalRaw.razor` → `RawTree.razor`, `IRawTreeService`) — a cluster-rooted `Folder → Driver → Device → TagGroup → Tag` hierarchy with per-node context menus (the reusable `ContextMenu`), lazy loading, driver/device config modals (endpoint moved to `Device.DeviceConfig`; Test-connect probes the `DriverDeviceConfigMerger`-merged config), manual-entry + CSV import/export (RFC-4180, staged review grid, all-or-nothing), and browse-commit of discovered leaves into raw tags. The routed `/clusters/{id}/drivers` flow (`DriverTypePicker`, `DriverEditRouter`, the 8 `*DriverPage` shells, the Drivers list) is **retired** — its form bodies live on inside the `/raw` modals. The `DriverType` dispatch maps are keyed off the `DriverTypeNames` constants (fixing the `TwinCat`/`Focas` drift). A `Calculation` pseudo-driver (`DriverTypeNames.Calculation`, `IDependencyConsumer`, deploy-time `scriptId`-existence + Tarjan cycle gates) computes signal-level tags from other tags' RawPaths. See `docs/Raw.md`. +**v3 (Batch 3): UNS Equipment is reference-only.** The equipment **Tags** tab no longer authors or binds tags — it holds `UnsTagReference` rows pointing at raw tags authored in `/raw`. "+ Add reference" opens the `RawTree` in picker mode, **cluster-scoped** (structurally + server-enforced `tag.cluster == equipment.cluster`); rows show effective name / RawPath / inherited DataType+AccessLevel / display-name override. **Effective-name uniqueness** (references + VirtualTags + ScriptedAlarms share the `{EquipmentId}/{EffectiveName}` NodeId space) is enforced at authoring (`IEffectiveNameGuard`) and at the deploy gate (`DraftValidator.UnsEffectiveNameCollision` — catches rename-induced collisions). Scripts use **`ctx.GetTag("{{equip}}/")`** — resolved per-equipment through `UnsTagReference` effective names to the backing RawPath at both compose seams (`AddressSpaceComposer` + `DeploymentArtifact`, via the shared `EquipmentReferenceMap`, byte-parity); an unresolved `` is a deploy error (`EquipReferenceUnresolved`) AND a live Monaco diagnostic (`OTSCRIPT_EQUIPREF`) — editor accepts ⇔ publish accepts. `EquipmentScriptPaths.DeriveEquipmentBase` is deleted (the dot-joint `{{equip}}.X` became the slash-joint `{{equip}}/`). Raw rename warns when a beneath-it tag is historized-without-override / UNS-referenced / named by a script literal (`RawTreeService` substring scan). `ImportEquipmentModal` dropped the `DriverInstanceId` column. See `docs/Uns.md` + `docs/ScriptEditor.md`. + The `/uns` **TagModal** uses **driver-typed tag-config editors**: it dispatches by the bound driver's `DriverType` to a per-driver editor (Modbus/S7/AbCip/AbLegacy/TwinCAT/Focas/OpcUaClient) via `TagConfigEditorMap`, with client-side validation via `TagConfigValidator`; unmapped drivers (only Galaxy) fall back to the generic raw-`TagConfig`-JSON textarea. Each editor is a thin razor shell over a pure `TagConfigModel` (`FromJson`/`ToJson`/`Validate`, preserves unknown keys). To add a driver's editor, copy the Modbus template under `Components/Shared/Uns/TagEditors/` + `Uns/TagEditors/`, reusing the driver's enums + camelCase JSON property names, and register it in `TagConfigEditorMap` + `TagConfigValidator`. See `docs/plans/2026-06-09-driver-typed-tag-editors-design.md`. ## Scripting / Script Editor diff --git a/docs/Uns.md b/docs/Uns.md index cb0dfa3b..912958b5 100644 --- a/docs/Uns.md +++ b/docs/Uns.md @@ -74,26 +74,53 @@ changed by editing the area's cluster in the Area modal, which moves the whole branch. There is no separate "served-by" concept and no migration — it is simply `UnsArea.ClusterId`. -### Tags +### Tags — reference-only (v3) -Tags created on the equipment page are **equipment-bound** and require a driver -instance. The driver list on the Tags tab is scoped to the equipment's cluster -and to drivers on an **Equipment-kind** namespace, so a driver-less equipment -shows no eligible drivers until you bind one (edit the equipment on the Details -tab and pick a driver). +> **v3 (Batch 3):** UNS equipment no longer authors or binds tags. Device I/O is +> authored once in the **Raw project tree** (`/raw` — see [`Raw.md`](Raw.md)); an +> equipment's **Tags** tab holds **references** to those raw tags. The old +> driver-bound Tag modal on this tab is retired. -**Galaxy / AVEVA System Platform points are ordinary equipment tags** bound to -a `GalaxyMxGateway` driver instance. Author them on the Tags tab using the -standard Tag modal; the Galaxy address picker browses the live Galaxy hierarchy -so you can select the attribute and set `TagConfig.FullName`. There is no -separate alias concept or `SystemPlatform`-kind namespace. +The **Tags** tab is a list of `UnsTagReference` rows. Each row shows the +**effective name**, the backing tag's **RawPath**, its inherited **DataType** and +**AccessLevel** (read-only — they come from the raw tag), and an editable +**display-name override**. The effective name is the override else the raw tag's +`Name`. + +**"+ Add reference"** opens a raw-tree picker (the `/raw` tree in picker mode) +**scoped to the equipment's cluster** — cross-cluster tags are structurally +unreachable, and the service also enforces `tag.cluster == equipment.cluster` on +commit. Tick individual tag leaves, or use a device / tag-group's *"Select all +tags below"* menu to pull many at once. + +**Effective-name uniqueness:** within an equipment the effective name must be +unique across references, VirtualTags, and ScriptedAlarms (they share the +equipment's UNS NodeId space, `{EquipmentId}/{EffectiveName}`). This is enforced +both at authoring (a readable rejection naming the colliding source) and at the +deploy gate (`DraftValidator`, `UnsEffectiveNameCollision`) — the deploy gate is +what catches **rename-induced** collisions a raw rename produced after the +reference was authored, naming both colliding sources and the equipment. + +Removing a raw tag in `/raw` is blocked while a `UnsTagReference` points at it +(the error names the referencing equipment); renaming a raw tag or any ancestor +warns when a beneath-it tag is historized (no `historianTagname` override), +UNS-referenced, or named by a script literal — see [`Raw.md`](Raw.md). + +**Galaxy / AVEVA System Platform points** are now ordinary raw tags on a +`GalaxyMxGateway` driver in `/raw`, referenced into equipment like any other raw +tag. There is no separate alias concept or `SystemPlatform`-kind namespace. ### Virtual tags A virtual tag is bound to an equipment and driven by a **script** (no driver). Add and edit virtual tags on the equipment page's **Virtual Tags** tab; the data type is chosen from the standard OPC UA type list and the Monaco script -editor is available inline. +editor is available inline. Scripts may read the equipment's references +relative-to-equipment with **`ctx.GetTag("{{equip}}/")`** — the token +resolves through the equipment's `UnsTagReference` rows (by effective name) to +the backing raw tag's RawPath at deploy; an unresolved `` is a deploy +error and a live Monaco diagnostic (editor accepts ⇔ publish accepts). See +[`ScriptEditor.md`](ScriptEditor.md). ### Galaxy tags