feat(scripteditor): tag-path completion for absolute RawPaths #490

Closed
opened 2026-07-21 03:36:41 -04:00 by dohertj2 · 1 comment
Owner

Deferred from v3 Batch 3, re-recorded in the Batch 4 PR doc.

Monaco tag-path completion inside ctx.GetTag("…") / ctx.SetVirtualTag("…") works for the equipment-relative {{equip}}/ form, but not for absolute RawPaths (Plant/Modbus/dev1/Speed).

Since v3 the raw tree is the authoring surface for device I/O and absolute RawPaths are a normal thing to write in a script, so the completion gap pushes authors to type them by hand — and a typo'd RawPath is only caught at deploy (Bad 0x80000000 at runtime for the literal).

Worth pairing with the existing tag-path hover so an absolute path resolves and displays like a relative one. Editor-accepts ⇔ publish-accepts is the invariant to preserve.

Deferred from v3 Batch 3, re-recorded in the Batch 4 PR doc. Monaco tag-path completion inside `ctx.GetTag("…")` / `ctx.SetVirtualTag("…")` works for the equipment-relative `{{equip}}/` form, but **not** for absolute RawPaths (`Plant/Modbus/dev1/Speed`). Since v3 the raw tree is the authoring surface for device I/O and absolute RawPaths are a normal thing to write in a script, so the completion gap pushes authors to type them by hand — and a typo'd RawPath is only caught at deploy (`Bad 0x80000000` at runtime for the literal). Worth pairing with the existing tag-path hover so an absolute path resolves and displays like a relative one. Editor-accepts ⇔ publish-accepts is the invariant to preserve.
Author
Owner

Fixed in 123ddc3f, live-gated.

It was not a missing feature — it was a stale projection, and the consequence was worse than the gap reported.

ScriptTagCatalog still emitted Tag.TagConfig.FullName, the pre-v3 driver wire address. Since v3 the resolvable key is the RawPath:

AddressSpaceComposer harvests the ctx.GetTag("…") literals into DependencyRefs -> VirtualTagActor dependency refs -> DependencyMuxActor._byRef, keyed Ordinal by AttributeValuePublished.FullReference — and a driver's wire ref is the RawPath (DriverHostActor._nodeIdByDriverRef is keyed (DriverInstanceId, RawPath)). The {{equip}}/<RefName> form already substitutes to RawPaths at both compose seams, which is why the relative form worked and the absolute one did not.

So the catalog was wrong in both directions:

before after
completion offered Motor.Speed — a path that can never resolve at runtime offers Plant/Modbus/dev1/Speed
hover on a correct absolute RawPath "not a known configured tag path" resolves, with type and owning driver

The file's own comment admitted the raw/UNS catalog was left for "Batch 2/3"; it never came.

Now projected through the shared RawPathResolver — the same byte-parity authority DraftValidator and AddressSpaceComposer use — so a suggestion is byte-identical to what the deploy gate and the runtime compute. A tag whose ancestry chain is broken resolves to null and is omitted: the runtime could not route it either, so offering it would be a lie.

The editor-accepts ⇔ publish-accepts invariant is preserved. No diagnostic keys off this catalog — OTSCRIPT_EQUIPREF only marks {{equip}} paths — so this changes completion and hover only and cannot newly reject a script.

The existing tests pinned the v2 contract, so they were re-authored (a RawPath only exists if the RawFolder -> DriverInstance -> Device -> TagGroup -> Tag chain does). Added coverage: group-nested paths, a driver at the cluster root contributing no folder segment, a broken chain omitted rather than guessed, and the pre-v3 FullName no longer resolving.

Live gate (docker-dev, rig rebuilt, both central nodes):

  • completion on an empty literal -> every RawPath, including the group-nested opcua1/plc/OpcPlc/Telemetry/Basic/AlternatingBoolean
  • completion on prefix pymod -> pymodbus/plc/HR200X, pymodbus/plc/ImportedTag
  • hover pymodbus/plc/HR200X -> "Tag · Type UInt16 · Driver MAIN-modbus" (the driver is new — it was always null before)
  • hover an unknown path -> "⚠ Not a known configured tag path"

AdminUI 742/742, script-analysis 55/55, solution builds 0 errors.

Fixed in `123ddc3f`, **live-gated**. **It was not a missing feature — it was a stale projection, and the consequence was worse than the gap reported.** `ScriptTagCatalog` still emitted `Tag.TagConfig.FullName`, the **pre-v3 driver wire address**. Since v3 the resolvable key is the **RawPath**: `AddressSpaceComposer` harvests the `ctx.GetTag("…")` literals into `DependencyRefs` -> `VirtualTagActor` dependency refs -> `DependencyMuxActor._byRef`, keyed Ordinal by `AttributeValuePublished.FullReference` — and a driver's wire ref **is** the RawPath (`DriverHostActor._nodeIdByDriverRef` is keyed `(DriverInstanceId, RawPath)`). The `{{equip}}/<RefName>` form already substitutes to RawPaths at both compose seams, which is why the relative form worked and the absolute one did not. So the catalog was wrong in **both** directions: | | before | after | |---|---|---| | completion | offered `Motor.Speed` — a path that can never resolve at runtime | offers `Plant/Modbus/dev1/Speed` | | hover on a **correct** absolute RawPath | "not a known configured tag path" | resolves, with type **and** owning driver | The file's own comment admitted the raw/UNS catalog was left for "Batch 2/3"; it never came. Now projected through the shared **`RawPathResolver`** — the same byte-parity authority `DraftValidator` and `AddressSpaceComposer` use — so a suggestion is byte-identical to what the deploy gate and the runtime compute. A tag whose ancestry chain is broken resolves to null and is **omitted**: the runtime could not route it either, so offering it would be a lie. **The editor-accepts ⇔ publish-accepts invariant is preserved.** No diagnostic keys off this catalog — `OTSCRIPT_EQUIPREF` only marks `{{equip}}` paths — so this changes completion and hover only and cannot newly reject a script. The existing tests pinned the v2 contract, so they were re-authored (a RawPath only exists if the `RawFolder -> DriverInstance -> Device -> TagGroup -> Tag` chain does). Added coverage: group-nested paths, a driver at the cluster root contributing no folder segment, a broken chain omitted rather than guessed, and the pre-v3 FullName no longer resolving. **Live gate (docker-dev, rig rebuilt, both central nodes):** - completion on an empty literal -> every RawPath, including the group-nested `opcua1/plc/OpcPlc/Telemetry/Basic/AlternatingBoolean` - completion on prefix `pymod` -> `pymodbus/plc/HR200X`, `pymodbus/plc/ImportedTag` - hover `pymodbus/plc/HR200X` -> **"Tag · Type UInt16 · Driver MAIN-modbus"** (the driver is new — it was always null before) - hover an unknown path -> "⚠ Not a known configured tag path" AdminUI 742/742, script-analysis 55/55, solution builds 0 errors.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#490