From 4b86beef28600c2f4d43516422fc6669ce61917a Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 13 Jul 2026 10:50:50 -0400 Subject: [PATCH] refactor(core): EquipmentNodeWalker.ExtractFullName delegates to TagConfigIntent; repoint canonical-parser doc cites (R2-11) Full walker deletion remains round-1 U-1's task. --- ...tagconfig-consolidation-plan.md.tasks.json | 2 +- .../OpcUa/EquipmentNodeWalker.cs | 20 +++---------------- .../ZB.MOM.WW.OtOpcUa.Core.csproj | 2 ++ .../ScriptAnalysis/IScriptTagCatalog.cs | 6 +++--- .../TagEditors/OpcUaClientTagConfigModel.cs | 6 +++--- .../AddressSpaceApplier.cs | 2 +- 6 files changed, 13 insertions(+), 25 deletions(-) diff --git a/archreview/plans/R2-11-tagconfig-consolidation-plan.md.tasks.json b/archreview/plans/R2-11-tagconfig-consolidation-plan.md.tasks.json index 85846869..92f4e5b6 100644 --- a/archreview/plans/R2-11-tagconfig-consolidation-plan.md.tasks.json +++ b/archreview/plans/R2-11-tagconfig-consolidation-plan.md.tasks.json @@ -10,7 +10,7 @@ { "id": "T6", "subject": "Device-host re-home: composer TryExtractDeviceHost/NormalizeDeviceHost → DeviceConfigIntent; update DriverHostActor + DeploymentArtifact callers", "status": "completed", "blockedBy": ["T4", "T5"] }, { "id": "T7", "subject": "DeploymentArtifact swap: delete 4 private mirrors, parse once per tag element via TagConfigIntent", "status": "completed", "blockedBy": ["T5"] }, { "id": "T8", "subject": "DraftValidator swap: Configuration→Commons ProjectReference; ExtractTagConfigFullName → TagConfigIntent.ExplicitFullName (null-on-absent preserved)", "status": "completed", "blockedBy": ["T2", "T3"] }, - { "id": "T9", "subject": "EquipmentNodeWalker.ExtractFullName delegates to TagConfigIntent; repoint tree-wide canonical-parser doc cites", "status": "pending", "blockedBy": ["T2", "T3"] }, + { "id": "T9", "subject": "EquipmentNodeWalker.ExtractFullName delegates to TagConfigIntent; repoint tree-wide canonical-parser doc cites", "status": "completed", "blockedBy": ["T2", "T3"] }, { "id": "T10", "subject": "Retire OpcUaServer.Tests ExtractTag*Tests (authority moved to Commons.Tests); verify zero 'MUST parse identically' hits", "status": "pending", "blockedBy": ["T3", "T5"] }, { "id": "T11", "subject": "TagConfigJson strict-capable readers in Core.Abstractions (TryReadEnum Absent/Valid/Invalid, ReadEnumOrDefault, ReadWritable, DescribeInvalidEnum) (TDD)", "status": "pending", "blockedBy": [] }, { "id": "T12", "subject": "Modbus equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "pending", "blockedBy": ["T11"] }, diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Core/OpcUa/EquipmentNodeWalker.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Core/OpcUa/EquipmentNodeWalker.cs index 217cca88..96eacf2b 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Core/OpcUa/EquipmentNodeWalker.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Core/OpcUa/EquipmentNodeWalker.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using ZB.MOM.WW.OtOpcUa.Commons.Types; using ZB.MOM.WW.OtOpcUa.Configuration.Entities; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; @@ -190,22 +190,8 @@ public static class EquipmentNodeWalker /// /// The tag configuration JSON or string. /// The extracted FullName value, or the raw if it can't be extracted. - internal static string ExtractFullName(string tagConfig) - { - if (string.IsNullOrWhiteSpace(tagConfig)) return tagConfig; - try - { - using var doc = JsonDocument.Parse(tagConfig); - if (doc.RootElement.ValueKind == JsonValueKind.Object - && doc.RootElement.TryGetProperty("FullName", out var fullName) - && fullName.ValueKind == JsonValueKind.String) - { - return fullName.GetString() ?? tagConfig; - } - } - catch (JsonException) { /* fall through */ } - return tagConfig; - } + internal static string ExtractFullName(string tagConfig) => + TagConfigIntent.Parse(tagConfig).FullName; /// /// Parse (stored as the enum diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Core/ZB.MOM.WW.OtOpcUa.Core.csproj b/src/Core/ZB.MOM.WW.OtOpcUa.Core/ZB.MOM.WW.OtOpcUa.Core.csproj index 44c4f648..6cd66876 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Core/ZB.MOM.WW.OtOpcUa.Core.csproj +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Core/ZB.MOM.WW.OtOpcUa.Core.csproj @@ -14,6 +14,8 @@ + + diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/ScriptAnalysis/IScriptTagCatalog.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/ScriptAnalysis/IScriptTagCatalog.cs index fe607acc..8e8eea22 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/ScriptAnalysis/IScriptTagCatalog.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/ScriptAnalysis/IScriptTagCatalog.cs @@ -53,7 +53,7 @@ public sealed record ScriptTagInfo(string Path, string Kind, string DataType, st /// DriverInstanceActor.AttributeValuePublished.FullReference /// (src/Server/…Runtime/VirtualTags/DependencyMuxActor.cs:97) — and that /// FullReference is the FullName field extracted from Tag.TagConfig -/// (see AddressSpaceComposer.ExtractTagFullName + EquipmentNodeWalker.ExtractFullName). +/// (see Commons.Types.TagConfigIntent.Parse, the shared byte-parity FullName authority). /// The UNS-path engine (Core.VirtualTags.VirtualTagEngine, keyed by a slash-joined /// Enterprise/Site/Area/Line/Equipment/TagName) is dormant — it is NOT wired into the /// host — so UNS browse paths never resolve at runtime and are intentionally NOT suggested. @@ -179,8 +179,8 @@ public sealed class ScriptTagCatalog(IDbContextFactory d /// /// Extracts the driver-side full reference from a Tag.TagConfig JSON blob — the /// top-level FullName string every shipped driver stores. Mirrors - /// EquipmentNodeWalker.ExtractFullName / AddressSpaceComposer.ExtractTagFullName - /// (AdminUI does not reference those assemblies). Falls back to the raw blob when it is not + /// Commons.Types.TagConfigIntent.Parse(...).FullName (the shared byte-parity authority; + /// AdminUI does not reference that assembly here). Falls back to the raw blob when it is not /// a JSON object carrying a string FullName. /// private static string ExtractFullNameFromTagConfig(string tagConfig) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/OpcUaClientTagConfigModel.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/OpcUaClientTagConfigModel.cs index 82a6348b..86deb8ee 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/OpcUaClientTagConfigModel.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/OpcUaClientTagConfigModel.cs @@ -7,9 +7,9 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors; /// stable nsu=…;s=… or plain ns=2;s=… NodeId the driver reads/writes/subscribes against). /// Preserves unrecognised JSON keys across a load→save. /// -/// The FullName key is intentionally PascalCase: the deploy-time composer + node walker -/// (AddressSpaceComposer.ExtractTagFullName, EquipmentNodeWalker) read it via a -/// case-sensitive TryGetProperty("FullName", …), so the editor MUST persist that exact +/// The FullName key is intentionally PascalCase: the shared deploy-time parser +/// (Commons.Types.TagConfigIntent.Parse, consumed by the composer, artifact decoder, draft +/// gate, and node walker) reads it via a case-sensitive TryGetProperty("FullName", …), so the editor MUST persist that exact /// casing. The driver-agnostic server-side HistoryRead intent keys (isHistorized / /// historianTagname) are NOT modelled here — they are owned by the TagModal-merge seam /// () and survive a load→save of this model as preserved unknown keys. diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceApplier.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceApplier.cs index 3fba3d99..b3980942 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceApplier.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceApplier.cs @@ -647,7 +647,7 @@ public sealed class AddressSpaceApplier // REACH (live-verified): the shape path only fires for drivers whose TagConfig carries a stable // top-level "FullName" (Galaxy = tag_name.AttributeName; OpcUaClient = the node id) — there a DataType/array // edit leaves FullName untouched ⇒ surgical. For structured-TagConfig protocol drivers (Modbus/S7/AbCip/…) - // AddressSpaceComposer.ExtractTagFullName falls back to the RAW TagConfig blob as FullName, so a DataType/ + // TagConfigIntent.Parse falls back to the RAW TagConfig blob as FullName, so a DataType/ // array edit also mutates that blob ⇒ FullName differs ⇒ this returns false ⇒ full rebuild. That is the // correct safe default (a protocol driver's subscription needs re-spawning for a new shape anyway). private static bool TagDeltaIsSurgicalEligible(AddressSpacePlan.EquipmentTagDelta d) =>