feat(opcua): carry Equipment-namespace tags through the deployment composition

Add EquipmentTagPlan + an init-only EquipmentTags member on Phase7CompositionResult
(mirror of GalaxyTags). Populate it compose-side (Tag.EquipmentId != null AND owning
namespace Kind == Equipment) and artifact-decode-side via BuildEquipmentTagPlans, with
FullName extracted from Tag.TagConfig. Init-only member (not a 7th positional param) so
existing convenience constructors + call sites are untouched.
This commit is contained in:
Joseph Doherty
2026-06-06 14:42:38 -04:00
parent c18943f6e1
commit febe462750
4 changed files with 262 additions and 8 deletions
@@ -117,6 +117,66 @@ public sealed class DeploymentArtifactTests
c.ScriptedAlarmPlans.Single().ScriptedAlarmId.ShouldBe("alarm-1");
}
/// <summary>
/// Verifies ParseComposition surfaces Equipment-namespace tags (non-null EquipmentId in an
/// <c>Equipment</c>-kind namespace) as <c>EquipmentTags</c>, with <c>FullName</c> extracted
/// from the tag's TagConfig blob — the equipment-signal mirror of the Galaxy-tag path. A
/// SystemPlatform (Galaxy) tag in the same blob must NOT leak into EquipmentTags and must
/// still route to GalaxyTags.
/// </summary>
[Fact]
public void ParseComposition_reads_EquipmentTags_from_equipment_namespace()
{
var blob = JsonSerializer.SerializeToUtf8Bytes(new
{
Namespaces = new[]
{
new { NamespaceId = "ns-eq", Kind = 0 }, // NamespaceKind.Equipment
new { NamespaceId = "ns-sp", Kind = 1 }, // NamespaceKind.SystemPlatform
},
DriverInstances = new[]
{
new { DriverInstanceId = "drv-modbus", DriverType = "Modbus", DriverConfig = "{}", NamespaceId = "ns-eq" },
new { DriverInstanceId = "drv-galaxy", DriverType = "Galaxy", DriverConfig = "{}", NamespaceId = "ns-sp" },
},
Tags = new object[]
{
new
{
TagId = "tag-eq",
DriverInstanceId = "drv-modbus",
EquipmentId = "eq-1",
Name = "Speed",
FolderPath = (string?)null,
DataType = "Float",
TagConfig = "{\"FullName\":\"40001\"}",
},
new
{
TagId = "tag-gx",
DriverInstanceId = "drv-galaxy",
EquipmentId = (string?)null,
Name = "Temp",
FolderPath = "area",
DataType = "Float",
TagConfig = "{\"FullName\":\"area.Temp\"}",
},
},
});
var c = DeploymentArtifact.ParseComposition(blob);
var tag = c.EquipmentTags.ShouldHaveSingleItem();
tag.EquipmentId.ShouldBe("eq-1");
tag.DriverInstanceId.ShouldBe("drv-modbus");
tag.Name.ShouldBe("Speed");
tag.DataType.ShouldBe("Float");
tag.FullName.ShouldBe("40001"); // extracted from TagConfig, not the raw blob
// The Galaxy tag still routes to GalaxyTags and does NOT leak into EquipmentTags.
c.GalaxyTags.ShouldContain(g => g.TagId == "tag-gx");
}
/// <summary>Verifies that specs missing required fields are dropped.</summary>
[Fact]
public void Spec_missing_required_fields_is_dropped()