diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceApplier.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceApplier.cs index cdcd9f74..2161b3db 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceApplier.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceApplier.cs @@ -401,6 +401,12 @@ public sealed class AddressSpaceApplier // differences still fall through to a rebuild — FullName/DriverInstanceId re-route the node to a different // driver point, Name re-derives the NodeId, and an alarm flip turns the node into a Part 9 condition. The // override-unequal default also covers any future field. + // REACH (live-verified FB-7): the shape path only fires for drivers whose TagConfig carries a stable + // top-level "FullName" (Galaxy = tag_name.AttributeName; OpcUaClient = the node id) — there a DataType/array + // edit leaves FullName untouched ⇒ surgical. For structured-TagConfig protocol drivers (Modbus/S7/AbCip/…) + // AddressSpaceComposer.ExtractTagFullName falls back to the RAW TagConfig blob as FullName, so a DataType/ + // array edit also mutates that blob ⇒ FullName differs ⇒ this returns false ⇒ full rebuild. That is the + // correct safe default (a protocol driver's subscription needs re-spawning for a new shape anyway). private static bool TagDeltaIsSurgicalEligible(AddressSpacePlan.EquipmentTagDelta d) => d.Previous.Alarm is null && d.Current.Alarm is null && (d.Previous with