feat(validation): require TagConfig.FullName on Galaxy alias tags; reframe Tag doc

This commit is contained in:
Joseph Doherty
2026-06-11 21:21:32 -04:00
parent 53116bdd83
commit 9f13101896
3 changed files with 99 additions and 8 deletions
@@ -33,10 +33,43 @@ public static class DraftValidator
ValidateEquipmentIdDerivation(draft, errors);
ValidateDriverNamespaceCompatibility(draft, errors);
ValidateNoEquipmentSignalNameCollision(draft, errors);
ValidateAliasTagFullName(draft, errors);
return errors;
}
private static void ValidateAliasTagFullName(DraftSnapshot draft, List<ValidationError> errors)
{
var typeByDriver = draft.DriverInstances
.ToDictionary(d => d.DriverInstanceId, d => d.DriverType, StringComparer.Ordinal);
foreach (var t in draft.Tags)
{
if (t.EquipmentId is null) continue;
if (!typeByDriver.TryGetValue(t.DriverInstanceId, out var dtype) || dtype != "GalaxyMxGateway")
continue;
if (string.IsNullOrWhiteSpace(ExtractTagConfigFullName(t.TagConfig)))
errors.Add(new("AliasTagMissingReference",
$"Alias 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 Phase7Composer.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<ValidationError> errors)
{
// Materialiser NodeId key: "{EquipmentId}[/{FolderPath}]/{Name}". Tag (EquipmentId != null) and