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 32a9a4cc..85846869 100644 --- a/archreview/plans/R2-11-tagconfig-consolidation-plan.md.tasks.json +++ b/archreview/plans/R2-11-tagconfig-consolidation-plan.md.tasks.json @@ -9,7 +9,7 @@ { "id": "T5", "subject": "AddressSpaceComposer swap: single TagConfigIntent.Parse per tag, delete 4 Extract* statics (P-1 parse-once)", "status": "completed", "blockedBy": ["T1", "T3"] }, { "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": "pending", "blockedBy": ["T2", "T3"] }, + { "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": "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": [] }, diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs index b5ba029d..e1c18e8e 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using ZB.MOM.WW.OtOpcUa.Commons.Types; using ZB.MOM.WW.OtOpcUa.Configuration.Entities; using ZB.MOM.WW.OtOpcUa.Configuration.Enums; @@ -47,29 +48,16 @@ public static class DraftValidator if (t.EquipmentId is null) continue; if (!typeByDriver.TryGetValue(t.DriverInstanceId, out var dtype) || dtype != "GalaxyMxGateway") continue; - if (string.IsNullOrWhiteSpace(ExtractTagConfigFullName(t.TagConfig))) + // The Galaxy rule wants the EXPLICIT reference, not the raw-blob fallback — so it reads + // TagConfigIntent.ExplicitFullName (the "FullName" property only when present-and-string, + // else null), preserving the historical null-on-absent semantics by construction (R2-11). + if (string.IsNullOrWhiteSpace(TagConfigIntent.Parse(t.TagConfig).ExplicitFullName)) errors.Add(new("GalaxyTagMissingReference", $"Galaxy tag '{t.TagId}' on equipment '{t.EquipmentId}' is missing a Galaxy reference (TagConfig.FullName)", t.TagId)); } } - // Minimal reader for the top-level "FullName" string in a tag's schemaless TagConfig JSON - // (mirrors AddressSpaceComposer.ExtractTagFullName — a small local copy, consistent with this codebase - // where the composer keeps its own). - private static string? ExtractTagConfigFullName(string? tagConfig) - { - if (string.IsNullOrWhiteSpace(tagConfig)) return null; - try - { - using var doc = System.Text.Json.JsonDocument.Parse(tagConfig); - return doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Object - && doc.RootElement.TryGetProperty("FullName", out var fn) - && fn.ValueKind == System.Text.Json.JsonValueKind.String ? fn.GetString() : null; - } - catch (System.Text.Json.JsonException) { return null; } - } - private static void ValidateNoEquipmentSignalNameCollision(DraftSnapshot draft, List errors) { // Materialiser NodeId key: "{EquipmentId}[/{FolderPath}]/{Name}". Tag (EquipmentId != null) and diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/ZB.MOM.WW.OtOpcUa.Configuration.csproj b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/ZB.MOM.WW.OtOpcUa.Configuration.csproj index aaf2d613..4b014f63 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/ZB.MOM.WW.OtOpcUa.Configuration.csproj +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/ZB.MOM.WW.OtOpcUa.Configuration.csproj @@ -27,6 +27,9 @@ + +