feat(composer): admit GalaxyMxGateway-backed equipment alias tags (+byte-parity)

This commit is contained in:
Joseph Doherty
2026-06-11 21:10:21 -04:00
parent 4b4738a891
commit bc9e83ed9f
4 changed files with 211 additions and 3 deletions
@@ -362,11 +362,16 @@ public static class Phase7Composer
// Equipment tags = the inverse filter: a Tag bound to an Equipment (non-null EquipmentId)
// whose driver's namespace is Equipment-kind. FullName is the driver-side wire reference
// pulled from TagConfig — it becomes the variable's NodeId + read/write routing key.
// A Galaxy alias is the one exception to the Equipment-kind namespace rule: a GalaxyMxGateway
// driver lives in a SystemPlatform-kind namespace, yet an equipment-scoped alias Tag bound to
// it must still surface as an equipment tag — so admit those by DriverType. The galaxyTags
// producer keeps its `t.EquipmentId is null` guard, so an alias (EquipmentId set) never
// double-counts there.
var equipmentTags = tags
.Where(t => t.EquipmentId is not null)
.Where(t => driversById.TryGetValue(t.DriverInstanceId, out var di)
&& namespacesById.TryGetValue(di.NamespaceId, out var ns)
&& ns.Kind == NamespaceKind.Equipment)
&& (ns.Kind == NamespaceKind.Equipment || di.DriverType == "GalaxyMxGateway"))
.OrderBy(t => t.EquipmentId, StringComparer.Ordinal)
.ThenBy(t => t.FolderPath ?? string.Empty, StringComparer.Ordinal) // coalesce so the sort matches the artifact-decode side exactly
.ThenBy(t => t.Name, StringComparer.Ordinal)
@@ -471,8 +471,11 @@ public static class DeploymentArtifact
if (isEquipment) equipmentNamespaces.Add(id!);
}
// driverInstanceId → namespaceId
// driverInstanceId → namespaceId, and driverInstanceId → DriverType. The DriverType map admits
// a Galaxy alias (a GalaxyMxGateway-backed equipment-scoped tag) that lives in a SystemPlatform
// namespace — byte-parity with the composer's `di.DriverType == "GalaxyMxGateway"` clause.
var driverToNamespace = new Dictionary<string, string>(StringComparer.Ordinal);
var driverToType = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var el in diArr.EnumerateArray())
{
if (el.ValueKind != JsonValueKind.Object) continue;
@@ -480,6 +483,9 @@ public static class DeploymentArtifact
var ns = el.TryGetProperty("NamespaceId", out var nsEl) ? nsEl.GetString() : null;
if (!string.IsNullOrWhiteSpace(id) && !string.IsNullOrWhiteSpace(ns))
driverToNamespace[id!] = ns!;
var dtype = el.TryGetProperty("DriverType", out var dtEl) ? dtEl.GetString() : null;
if (!string.IsNullOrWhiteSpace(id) && !string.IsNullOrWhiteSpace(dtype))
driverToType[id!] = dtype!;
}
var result = new List<EquipmentTagPlan>(tagsArr.GetArrayLength());
@@ -502,7 +508,10 @@ public static class DeploymentArtifact
if (string.IsNullOrWhiteSpace(tagId) || string.IsNullOrWhiteSpace(di) || string.IsNullOrWhiteSpace(name)) continue;
if (!driverToNamespace.TryGetValue(di!, out var nsId)) continue;
if (!equipmentNamespaces.Contains(nsId)) continue;
// A GalaxyMxGateway-backed alias qualifies even though its namespace is SystemPlatform-kind
// (not Equipment) — byte-parity with the composer's broadened equipment-tag filter.
var isGalaxyAlias = driverToType.TryGetValue(di!, out var dtype2) && dtype2 == "GalaxyMxGateway";
if (!equipmentNamespaces.Contains(nsId) && !isGalaxyAlias) continue;
result.Add(new EquipmentTagPlan(
TagId: tagId!,