diff --git a/Directory.Packages.props b/Directory.Packages.props
index b81ebba2..32cfb650 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -49,6 +49,9 @@
+
+
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/ClusterClientContractLockTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/ClusterClientContractLockTests.cs
new file mode 100644
index 00000000..a24b27dc
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/ClusterClientContractLockTests.cs
@@ -0,0 +1,195 @@
+using Newtonsoft.Json;
+using ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment;
+using ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance;
+using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
+using ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification;
+using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
+
+namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Messages;
+
+///
+/// Contract-lock tests for the highest-traffic ClusterClient record contracts.
+/// ClusterClient traffic uses Akka.NET's default Newtonsoft-JSON serializer and the
+/// design supports system-wide artifact version skew across sites, so these records
+/// must obey additive-only evolution:
+/// (a) an old-shape payload (missing newer trailing optional fields) still
+/// deserializes, with the missing fields taking their constructor defaults;
+/// (b) an unknown field from a *newer* peer is ignored (never throws);
+/// (c) a round-trip preserves the values that were sent.
+/// If a change breaks one of these tests, it breaks rolling cross-cluster
+/// compatibility — do NOT "fix the test"; make the change additive (append optional
+/// trailing constructor parameters, never reorder/rename/remove/retype existing ones).
+///
+public class ClusterClientContractLockTests
+{
+ // Mirrors Akka.NET's NewtonSoftJsonSerializer tolerance: unknown members are ignored
+ // rather than treated as errors, which is what lets a newer peer add fields safely.
+ private static readonly JsonSerializerSettings AkkaLikeSettings = new()
+ {
+ MissingMemberHandling = MissingMemberHandling.Ignore,
+ };
+
+ private static T RoundTrip(T value)
+ {
+ var json = JsonConvert.SerializeObject(value, AkkaLikeSettings);
+ return JsonConvert.DeserializeObject(json, AkkaLikeSettings)!;
+ }
+
+ // ---------------------------------------------------------------------
+ // (a) Old-shape payloads: newer trailing optional fields absent → defaults.
+ // ---------------------------------------------------------------------
+
+ [Fact]
+ public void CreateDataConnectionCommand_OldShape_WithoutNewerTrailingFields_Deserializes()
+ {
+ // Shape as emitted before BackupConfiguration/FailoverRetryCount existed.
+ const string oldShape =
+ """{"SiteId":3,"Name":"conn1","Protocol":"OpcUa","PrimaryConfiguration":"{}"}""";
+
+ var cmd = JsonConvert.DeserializeObject(oldShape, AkkaLikeSettings);
+
+ Assert.NotNull(cmd);
+ Assert.Equal(3, cmd!.SiteId);
+ Assert.Equal("conn1", cmd.Name);
+ Assert.Equal("OpcUa", cmd.Protocol);
+ Assert.Equal("{}", cmd.PrimaryConfiguration);
+ // NOTE (locked reality): under Akka's Newtonsoft-JSON serializer a missing trailing
+ // field takes default(T) — NOT the C# optional-parameter default. So a reference type
+ // is null (BackupConfiguration) and a value type is 0 (FailoverRetryCount), even though
+ // the record declares `FailoverRetryCount = 3`. Version-skew callers therefore cannot
+ // rely on the C# default reaching the wire; additive fields must be safe at default(T).
+ Assert.Null(cmd.BackupConfiguration); // reference-type missing field → null
+ Assert.Equal(0, cmd.FailoverRetryCount); // value-type missing field → default(int), not 3
+ }
+
+ [Fact]
+ public void DeployInstanceCommand_OldShape_WithoutFetchFields_Deserializes()
+ {
+ // Shape as emitted before the notify-and-fetch CentralFetchBaseUrl/FetchToken pair.
+ const string oldShape =
+ """{"DeploymentId":"dep-1","InstanceUniqueName":"i","RevisionHash":"r","FlattenedConfigurationJson":"{}","DeployedBy":"me","Timestamp":"2026-07-08T00:00:00+00:00"}""";
+
+ var cmd = JsonConvert.DeserializeObject(oldShape, AkkaLikeSettings);
+
+ Assert.NotNull(cmd);
+ Assert.Equal("dep-1", cmd!.DeploymentId);
+ Assert.Equal("i", cmd.InstanceUniqueName);
+ Assert.Null(cmd.CentralFetchBaseUrl); // default applied
+ Assert.Null(cmd.FetchToken); // default applied
+ }
+
+ [Fact]
+ public void NotificationSubmit_OldShape_WithoutExecutionCorrelationFields_Deserializes()
+ {
+ // Shape as emitted before OriginExecutionId/OriginParentExecutionId/SourceNode.
+ const string oldShape =
+ """{"NotificationId":"n-1","ListName":"ops","Subject":"s","Body":"b","SourceSiteId":"site-1","SourceInstanceId":null,"SourceScript":null,"SiteEnqueuedAt":"2026-07-08T00:00:00+00:00"}""";
+
+ var msg = JsonConvert.DeserializeObject(oldShape, AkkaLikeSettings);
+
+ Assert.NotNull(msg);
+ Assert.Equal("n-1", msg!.NotificationId);
+ Assert.Equal("ops", msg.ListName);
+ Assert.Null(msg.OriginExecutionId); // default applied
+ Assert.Null(msg.OriginParentExecutionId); // default applied
+ Assert.Null(msg.SourceNode); // default applied
+ }
+
+ // ---------------------------------------------------------------------
+ // (b) Unknown fields from a newer peer are ignored (never throw).
+ // ---------------------------------------------------------------------
+
+ [Fact]
+ public void AttributeValueChanged_UnknownFieldFromNewerPeer_IsIgnored()
+ {
+ const string newerShape =
+ """{"InstanceUniqueName":"i","AttributePath":"a.b","AttributeName":"b","Value":1,"Quality":"Good","Timestamp":"2026-07-08T00:00:00+00:00","SomeFutureField":"x"}""";
+
+ var evt = JsonConvert.DeserializeObject(newerShape, AkkaLikeSettings);
+
+ Assert.NotNull(evt);
+ Assert.Equal("i", evt!.InstanceUniqueName);
+ Assert.Equal("Good", evt.Quality);
+ }
+
+ [Fact]
+ public void GetAttributeRequest_UnknownFieldFromNewerPeer_IsIgnored()
+ {
+ const string newerShape =
+ """{"CorrelationId":"c-1","InstanceUniqueName":"i","AttributeName":"a","Timestamp":"2026-07-08T00:00:00+00:00","FutureFlag":true}""";
+
+ var req = JsonConvert.DeserializeObject(newerShape, AkkaLikeSettings);
+
+ Assert.NotNull(req);
+ Assert.Equal("c-1", req!.CorrelationId);
+ Assert.Equal("a", req.AttributeName);
+ }
+
+ [Fact]
+ public void SetStaticAttributeCommand_UnknownFieldFromNewerPeer_IsIgnored()
+ {
+ const string newerShape =
+ """{"CorrelationId":"c-1","InstanceUniqueName":"i","AttributeName":"a","Value":"42","Timestamp":"2026-07-08T00:00:00+00:00","NewerAudience":"x"}""";
+
+ var cmd = JsonConvert.DeserializeObject(newerShape, AkkaLikeSettings);
+
+ Assert.NotNull(cmd);
+ Assert.Equal("c-1", cmd!.CorrelationId);
+ Assert.Equal("42", cmd.Value);
+ }
+
+ // ---------------------------------------------------------------------
+ // (c) Round-trip preserves values.
+ // ---------------------------------------------------------------------
+
+ [Fact]
+ public void ManagementEnvelope_RoundTrip_PreservesCorrelationIdAndUser()
+ {
+ var user = new AuthenticatedUser(
+ "multi-role", "Multi Role",
+ new[] { "Administrator", "Designer" }, new[] { "site-1", "site-2" });
+ var envelope = new ManagementEnvelope(
+ user, new GetDataConnectionCommand(7), "corr-1");
+
+ var round = RoundTrip(envelope);
+
+ Assert.Equal("corr-1", round.CorrelationId);
+ Assert.Equal("multi-role", round.User.Username);
+ Assert.Equal(new[] { "Administrator", "Designer" }, round.User.Roles);
+ Assert.Equal(new[] { "site-1", "site-2" }, round.User.PermittedSiteIds);
+ }
+
+ [Fact]
+ public void AttributeValueChanged_RoundTrip_PreservesValues()
+ {
+ var evt = new AttributeValueChanged(
+ "inst-1", "path.to.attr", "attr", 42, "Good",
+ new DateTimeOffset(2026, 7, 8, 12, 0, 0, TimeSpan.Zero));
+
+ var round = RoundTrip(evt);
+
+ Assert.Equal("inst-1", round.InstanceUniqueName);
+ Assert.Equal("path.to.attr", round.AttributePath);
+ Assert.Equal("attr", round.AttributeName);
+ Assert.Equal("Good", round.Quality);
+ Assert.Equal(evt.Timestamp, round.Timestamp);
+ }
+
+ [Fact]
+ public void CreateDataConnectionCommand_RoundTrip_PreservesValues()
+ {
+ var cmd = new CreateDataConnectionCommand(
+ SiteId: 5, Name: "conn-x", Protocol: "MxAccess",
+ PrimaryConfiguration: "{\"a\":1}", BackupConfiguration: "{\"b\":2}",
+ FailoverRetryCount: 9);
+
+ var round = RoundTrip(cmd);
+
+ Assert.Equal(5, round.SiteId);
+ Assert.Equal("conn-x", round.Name);
+ Assert.Equal("MxAccess", round.Protocol);
+ Assert.Equal("{\"a\":1}", round.PrimaryConfiguration);
+ Assert.Equal("{\"b\":2}", round.BackupConfiguration);
+ Assert.Equal(9, round.FailoverRetryCount);
+ }
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/ZB.MOM.WW.ScadaBridge.Commons.Tests.csproj b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/ZB.MOM.WW.ScadaBridge.Commons.Tests.csproj
index 621db8e6..bd53af9e 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/ZB.MOM.WW.ScadaBridge.Commons.Tests.csproj
+++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/ZB.MOM.WW.ScadaBridge.Commons.Tests.csproj
@@ -11,6 +11,7 @@
+