feat(v3-batch4-wp3): raw-only binding + UNS fan-out + write routing
Wave B of Batch 4 — the runtime binding seam for the dual namespace. Applier (both realms, explicit realm at every sink call site): - MaterialiseRawSubtree: Raw containers as folders + Raw tags as variables keyed by RawPath (native-alarm tag → single Part 9 condition at the RawPath), all in AddressSpaceRealm.Raw; historian tagname = override else RawPath. - MaterialiseUnsReferences: each UNS reference Variable under its equipment folder (Uns realm) + an Organizes UNS->Raw edge; inherits writable/array/ historian tagname from the backing raw tag (both NodeIds -> one tagname). - FeedHistorizedRefs / ProvisionHistorizedTags now source RAW tags (mux ref stays single, keyed by RawPath); ApplyPureRemove tears down raw tags + UNS refs in place (raw-container removal falls back to rebuild). DriverHostActor (dual-NodeId, single-source fan-out): - _nodeIdByDriverRef value gains a realm (NodeRealmRef); rebuilt from RawTags UNION UnsReferenceVariables so one (DriverInstanceId, RawPath) fans to the raw NodeId AND every referencing UNS NodeId with identical value/quality/timestamp. Write inverse map keyed by the bare id; the ns-qualified NodeId the write hook passes is normalised (BareNodeId) so a write to either NodeId resolves the same driver ref (-> RawPath write). - Native raw alarm condition routing is realm-tagged (Raw); AttributeValueUpdate + AlarmStateUpdate carry the realm through to the sink. Retire EquipmentNodeIds -> V3NodeIds.Uns (applier/VirtualTagHostActor) and RawPaths.Combine (DiscoveredNodeMapper, discovered nodes are Raw now). DeploymentArtifact.ParseComposition emits the Raw + UNS subtrees byte-parity with the composer (reconstruct entities -> AddressSpaceComposer.Compose). Sink surface: removed the transitional `= AddressSpaceRealm.Uns` defaults from IOpcUaAddressSpaceSink / ISurgicalAddressSpaceSink / SdkAddressSpaceSink / DeferredAddressSpaceSink / NullOpcUaAddressSpaceSink — every call site is now explicit (realm reordered before the trailing optionals on EnsureVariable + MaterialiseAlarmCondition). Node-manager convenience methods keep their defaults (they are not the interface impl; Sdk delegates explicitly). Tests: rewrote DriverHostActorLiveValueTests (fan-out drift, 1:N) + DriverHostActorWriteRoutingTests (dual-NodeId raw/uns write routing) to the v3 raw+uns model; new AddressSpaceApplierRawUnsTests; migrated the EquipmentTags provisioning/feed tests to RawTags; swept EquipmentNodeIds test callers. Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
This commit is contained in:
+15
-10
@@ -292,8 +292,8 @@ public sealed class AddressSpaceApplierProvisioningTests
|
||||
RemovedAlarms: Array.Empty<ScriptedAlarmPlan>(),
|
||||
ChangedAlarms: Array.Empty<AddressSpacePlan.AlarmDelta>())
|
||||
{
|
||||
RemovedEquipmentTags = new[] { removedTag },
|
||||
ChangedEquipmentTags = new[] { new AddressSpacePlan.EquipmentTagDelta(prev, cur) },
|
||||
RemovedRawTags = new[] { removedTag },
|
||||
ChangedRawTags = new[] { new AddressSpacePlan.RawTagDelta(prev, cur) },
|
||||
};
|
||||
|
||||
applier.Apply(plan);
|
||||
@@ -324,15 +324,20 @@ public sealed class AddressSpaceApplierProvisioningTests
|
||||
outcome.RebuildCalled.ShouldBeFalse(); // PureAdd — no rebuild; Apply still completed + provisioned
|
||||
}
|
||||
|
||||
private static EquipmentTagPlan HistorizedTag(string displayName, string? historianName, string dataType, string fullName = "ref")
|
||||
=> new("tag-" + displayName, "eq-1", "drv", FolderPath: "", Name: displayName, DataType: dataType, FullName: fullName,
|
||||
Writable: false, Alarm: null, IsHistorized: true, HistorianTagname: historianName);
|
||||
// v3 Batch 4: historized value tags are RAW tags — the mux ref + historian default are the RawPath
|
||||
// (== NodeId). The `fullName` param plays the role of the RawPath here (kept as a param name so the
|
||||
// existing assertions — HistorizedTagRef("ref"/"40001", …) — read unchanged).
|
||||
private static RawTagPlan HistorizedTag(string displayName, string? historianName, string dataType, string fullName = "ref")
|
||||
=> new("tag-" + displayName, NodeId: fullName, ParentNodeId: null, DriverInstanceId: "drv", Name: displayName,
|
||||
DataType: dataType, Writable: false, Alarm: null, ReferencingEquipmentPaths: Array.Empty<string>(),
|
||||
IsHistorized: true, HistorianTagname: historianName);
|
||||
|
||||
private static EquipmentTagPlan NonHistorizedTag(string displayName, string dataType)
|
||||
=> new("tag-" + displayName, "eq-1", "drv", FolderPath: "", Name: displayName, DataType: dataType, FullName: "ref",
|
||||
Writable: false, Alarm: null, IsHistorized: false, HistorianTagname: null);
|
||||
private static RawTagPlan NonHistorizedTag(string displayName, string dataType)
|
||||
=> new("tag-" + displayName, NodeId: "ref", ParentNodeId: null, DriverInstanceId: "drv", Name: displayName,
|
||||
DataType: dataType, Writable: false, Alarm: null, ReferencingEquipmentPaths: Array.Empty<string>(),
|
||||
IsHistorized: false, HistorianTagname: null);
|
||||
|
||||
private static AddressSpacePlan PlanWithAddedTags(params EquipmentTagPlan[] tags) => new(
|
||||
private static AddressSpacePlan PlanWithAddedTags(params RawTagPlan[] tags) => new(
|
||||
AddedEquipment: Array.Empty<EquipmentNode>(),
|
||||
RemovedEquipment: Array.Empty<EquipmentNode>(),
|
||||
ChangedEquipment: Array.Empty<AddressSpacePlan.EquipmentDelta>(),
|
||||
@@ -343,6 +348,6 @@ public sealed class AddressSpaceApplierProvisioningTests
|
||||
RemovedAlarms: Array.Empty<ScriptedAlarmPlan>(),
|
||||
ChangedAlarms: Array.Empty<AddressSpacePlan.AlarmDelta>())
|
||||
{
|
||||
AddedEquipmentTags = tags,
|
||||
AddedRawTags = tags,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user