diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Phase7Composer.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Phase7Composer.cs
index 481d21ac..f6933f00 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Phase7Composer.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Phase7Composer.cs
@@ -115,8 +115,8 @@ public sealed record EquipmentTagAlarmInfo(string AlarmType, int Severity);
///
/// When true, this VirtualTag's values are historized (carried from the
/// VirtualTag.Historize entity column). Threaded through the deploy-diff equality below so a
-/// Historize-only toggle is detected as a change. Defaults to false — matching both the entity
-/// default for an unset column and the artifact-decode default when the flag is absent/non-bool —
+/// Historize-only toggle is detected as a change. Defaults to false — matching both the CLR
+/// default of the bool VirtualTag.Historize column and the artifact-decode default when the flag is absent/non-bool —
/// which keeps existing positional+named ctor call sites compiling and preserves byte-parity.
public sealed record EquipmentVirtualTagPlan(
string VirtualTagId,
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/Phase7PlannerTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/Phase7PlannerTests.cs
index cd7d1452..5bdb7bb8 100644
--- a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/Phase7PlannerTests.cs
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/Phase7PlannerTests.cs
@@ -139,6 +139,41 @@ public sealed class Phase7PlannerTests
plan.RemovedEquipmentVirtualTags.ShouldBeEmpty();
}
+ /// H5a — a VirtualTag with the same id but a toggled Historize flag (and otherwise
+ /// identical fields) must route to ChangedEquipmentVirtualTags. This pins that Historize is
+ /// part of so a Historize-only deploy is not a silent
+ /// no-op at the diff/IsEmpty gate.
+ [Fact]
+ public void Same_id_with_toggled_historize_routes_to_ChangedEquipmentVirtualTags()
+ {
+ var prev = new Phase7CompositionResult(
+ Array.Empty(), Array.Empty(), Array.Empty())
+ {
+ EquipmentVirtualTags = new[]
+ {
+ new EquipmentVirtualTagPlan("vt-1", "eq-1", FolderPath: "", Name: "Efficiency", DataType: "Float",
+ Expression: "a + b", DependencyRefs: new[] { "a", "b" }, Historize: false),
+ },
+ };
+ var next = new Phase7CompositionResult(
+ Array.Empty(), Array.Empty(), Array.Empty())
+ {
+ EquipmentVirtualTags = new[]
+ {
+ new EquipmentVirtualTagPlan("vt-1", "eq-1", FolderPath: "", Name: "Efficiency", DataType: "Float",
+ Expression: "a + b", DependencyRefs: new[] { "a", "b" }, Historize: true),
+ },
+ };
+
+ var plan = Phase7Planner.Compute(prev, next);
+
+ plan.IsEmpty.ShouldBeFalse();
+ plan.ChangedEquipmentVirtualTags.Single().Previous.Historize.ShouldBeFalse();
+ plan.ChangedEquipmentVirtualTags.Single().Current.Historize.ShouldBeTrue();
+ plan.AddedEquipmentVirtualTags.ShouldBeEmpty();
+ plan.RemovedEquipmentVirtualTags.ShouldBeEmpty();
+ }
+
/// Regression guard for structural equality on :
/// two snapshots containing the SAME VirtualTag built from SEPARATE list instances must diff to an empty plan
/// (IReadOnlyList equality is BY REFERENCE without the custom Equals override, so every VirtualTag with