diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ClusterNode.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ClusterNode.cs
index f86fbb4..1057b1f 100644
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ClusterNode.cs
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ClusterNode.cs
@@ -1,5 +1,3 @@
-using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
-
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
/// Physical OPC UA server node within a .
@@ -10,8 +8,6 @@ public sealed class ClusterNode
public required string ClusterId { get; set; }
- public required RedundancyRole RedundancyRole { get; set; }
-
/// Machine hostname / IP.
public required string Host { get; set; }
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/RedundancyRole.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/RedundancyRole.cs
deleted file mode 100644
index e0e9ece..0000000
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/RedundancyRole.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace ZB.MOM.WW.OtOpcUa.Configuration.Enums;
-
-/// Per-node redundancy role within a cluster. Per decision #84.
-public enum RedundancyRole
-{
- Primary,
- Secondary,
- Standalone,
-}
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs
index 334cd8f..54ba0f6 100644
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs
@@ -115,7 +115,6 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions x.NodeId);
e.Property(x => x.NodeId).HasMaxLength(64);
e.Property(x => x.ClusterId).HasMaxLength(64);
- e.Property(x => x.RedundancyRole).HasConversion().HasMaxLength(16);
e.Property(x => x.Host).HasMaxLength(255);
e.Property(x => x.ApplicationUri).HasMaxLength(256);
e.Property(x => x.DriverConfigOverridesJson).HasColumnType("nvarchar(max)");
@@ -130,10 +129,10 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions x.ApplicationUri).IsUnique().HasDatabaseName("UX_ClusterNode_ApplicationUri");
e.HasIndex(x => x.ClusterId).HasDatabaseName("IX_ClusterNode_ClusterId");
- // At most one Primary per cluster
- e.HasIndex(x => x.ClusterId).IsUnique()
- .HasFilter("[RedundancyRole] = 'Primary'")
- .HasDatabaseName("UX_ClusterNode_Primary_Per_Cluster");
+ // v2: the "one Primary per cluster" filtered unique index (and the RedundancyRole
+ // column it filtered on) are gone. Akka cluster leader-of-driver-role is the
+ // authoritative primary signal (see RedundancyStateActor + ServiceLevelCalculator,
+ // Task 35).
});
}
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs
index 3be8186..8feaad5 100644
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs
@@ -228,14 +228,9 @@ public static class DraftValidator
$"Toggle the missing node(s) back on or change RedundancyMode/NodeCount to match.",
cluster.ClusterId));
- // Primary uniqueness — decision #84. Two Primary nodes is always an invariant violation
- // regardless of mode; catch it here so publish fails loud rather than the runtime
- // demoting both to ServiceLevelBand.InvalidTopology at boot.
- var primaryCount = clusterNodes.Count(n => n.Enabled && n.RedundancyRole == RedundancyRole.Primary);
- if (primaryCount > 1)
- errors.Add(new("ClusterMultiplePrimary",
- $"Cluster '{cluster.ClusterId}' has {primaryCount} Enabled Primary nodes. At most one Primary per cluster.",
- cluster.ClusterId));
+ // v2: the v1 "exactly one Primary per cluster" invariant is gone. RedundancyRole was
+ // dropped in Task 14d; in v2 the Akka cluster's role-leader-of-"driver" elects the
+ // primary at runtime, so there is no static configuration to validate here.
return errors;
}
diff --git a/tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/DraftValidatorTests.cs b/tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/DraftValidatorTests.cs
index 839c1a1..b467d89 100644
--- a/tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/DraftValidatorTests.cs
+++ b/tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/DraftValidatorTests.cs
@@ -161,7 +161,7 @@ public sealed class DraftValidatorTests
{
var cluster = BuildCluster(nodeCount: nodeCount, mode: mode);
var nodes = Enumerable.Range(0, enabledNodes)
- .Select(i => BuildNode($"n-{i}", enabled: true, role: i == 0 ? RedundancyRole.Primary : RedundancyRole.Secondary))
+ .Select(i => BuildNode($"n-{i}", enabled: true))
.ToList();
var errors = DraftValidator.ValidateClusterTopology(cluster, nodes);
@@ -175,33 +175,24 @@ public sealed class DraftValidatorTests
var cluster = BuildCluster(nodeCount: 2, mode: RedundancyMode.Hot);
var nodes = new[]
{
- BuildNode("primary", enabled: true, role: RedundancyRole.Primary),
- BuildNode("backup", enabled: false, role: RedundancyRole.Secondary),
+ BuildNode("primary", enabled: true),
+ BuildNode("backup", enabled: false),
};
var errors = DraftValidator.ValidateClusterTopology(cluster, nodes);
errors.ShouldContain(e => e.Code == "ClusterEnabledNodeCountMismatch");
}
- [Fact]
- public void ValidateClusterTopology_flags_multiple_Primary()
- {
- var cluster = BuildCluster(nodeCount: 2, mode: RedundancyMode.Hot);
- var nodes = new[]
- {
- BuildNode("a", enabled: true, role: RedundancyRole.Primary),
- BuildNode("b", enabled: true, role: RedundancyRole.Primary),
- };
-
- var errors = DraftValidator.ValidateClusterTopology(cluster, nodes);
- errors.ShouldContain(e => e.Code == "ClusterMultiplePrimary");
- }
+ // v2: the "exactly one Primary per cluster" check is gone — Akka cluster's
+ // role-leader-of-"driver" elects the primary at runtime. The corresponding
+ // ValidateClusterTopology_flags_multiple_Primary test (and the
+ // ClusterMultiplePrimary error code it asserted) were removed alongside Task 14d.
[Fact]
public void ValidateClusterTopology_returns_no_errors_on_valid_standalone()
{
var cluster = BuildCluster(nodeCount: 1, mode: RedundancyMode.None);
- var nodes = new[] { BuildNode("only", enabled: true, role: RedundancyRole.Primary) };
+ var nodes = new[] { BuildNode("only", enabled: true) };
var errors = DraftValidator.ValidateClusterTopology(cluster, nodes);
errors.ShouldBeEmpty();
@@ -219,11 +210,10 @@ public sealed class DraftValidatorTests
CreatedBy = "t",
};
- private static ClusterNode BuildNode(string id, bool enabled, RedundancyRole role) => new()
+ private static ClusterNode BuildNode(string id, bool enabled) => new()
{
NodeId = id,
ClusterId = "c-test",
- RedundancyRole = role,
Host = "localhost",
OpcUaPort = 4840,
DashboardPort = 5001,