diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptions.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptions.cs index 7fa62de4..33593bdc 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptions.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptions.cs @@ -6,9 +6,11 @@ namespace ZB.MOM.WW.OtOpcUa.Cluster; /// /// /// -/// is a deliberate dark switch: the transports live in the tree -/// side by side so the docker-dev rig can run the same deployment over each and compare, and -/// so a bad rollout rolls back with a config change rather than a revert. +/// selects between a pair-local transport and a cross-mesh +/// one. is the default and cannot cross an application-Cluster +/// boundary; is the only mode that can, and +/// requires it on any node carrying a +/// cluster-{ClusterId} role. /// /// /// The DistributedPubSub branch is retained, not scheduled for deletion. An earlier @@ -16,15 +18,15 @@ namespace ZB.MOM.WW.OtOpcUa.Cluster; /// deliberately reversed — see docs/Configuration.md § "The compiled transport-mode /// defaults were deliberately NOT flipped". DPS remains the correct and default transport for /// a single-mesh topology, which every integration test and every profile that leaves this -/// key unset still runs. What is being replaced is , by -/// . +/// key unset still runs. /// /// /// Discovery is deliberately asymmetric, matching the sister project. Central /// discovers nodes from the database — enabled, non-maintenance ClusterNode /// rows, rebuilt every . Nodes discover central from -/// this section: static, restart to change. The asymmetry follows the rate of change — -/// operators add and retire nodes, but central's own address is part of the deployment. +/// this section (): static, restart to change. The +/// asymmetry follows the rate of change — operators add and retire nodes, but central's own +/// address is part of the deployment. /// /// public sealed class MeshTransportOptions @@ -32,26 +34,26 @@ public sealed class MeshTransportOptions /// Configuration section name. public const string SectionName = "MeshTransport"; - /// Every command stays on DistributedPubSub — the pre-Phase-2 behaviour. + /// + /// Every command stays on DistributedPubSub — pair-local, and the default. Cannot cross an + /// application-Cluster boundary. + /// public const string ModeDps = "Dps"; - /// Commands cross the ClusterClient receptionist boundary. - public const string ModeClusterClient = "ClusterClient"; - /// /// Commands cross the mesh boundary as unary gRPC calls on the mesh_transport.v1 /// contract — central dials each node's NodeCommandService, a node posts its - /// deployment ack to central's CentralCommandService. The replacement for - /// ; unlike it, the command plane is authenticated - /// (). + /// deployment ack to central's CentralCommandService. The command plane is + /// authenticated per application Cluster (). /// public const string ModeGrpc = "Grpc"; /// - /// (default), or - /// . Any other value fails the host at startup rather than silently - /// falling back — a transport that is none of these delivers nothing, and the symptom would - /// be commands that vanish. + /// (default) or . Any other value fails the host + /// at startup rather than silently falling back — a transport that is neither delivers + /// nothing, and the symptom would be commands that vanish. That includes the retired + /// ClusterClient value: a node still carrying it is refused at boot rather than + /// quietly demoted to DPS, which on a split-topology node would deliver nothing at all. /// public string Mode { get; set; } = ModeDps; diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptionsValidator.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptionsValidator.cs index 0bd78471..65223102 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptionsValidator.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptionsValidator.cs @@ -40,16 +40,18 @@ public sealed class MeshTransportOptionsValidator : IValidateOptions(); var isDps = string.Equals(options.Mode, MeshTransportOptions.ModeDps, StringComparison.OrdinalIgnoreCase); - var isClusterClient = string.Equals( - options.Mode, MeshTransportOptions.ModeClusterClient, StringComparison.OrdinalIgnoreCase); var isGrpc = string.Equals(options.Mode, MeshTransportOptions.ModeGrpc, StringComparison.OrdinalIgnoreCase); - if (!isDps && !isClusterClient && !isGrpc) + // An unrecognised mode is refused, never defaulted. The retired 'ClusterClient' value lands + // here: a node left carrying it fails to start with the two remaining values named, rather + // than silently falling back to Dps — which on a split-topology node cannot cross the mesh + // boundary and would deliver nothing at all. + if (!isDps && !isGrpc) { errors.Add( $"{MeshTransportOptions.SectionName}:{nameof(MeshTransportOptions.Mode)} is " - + $"'{options.Mode}'. Expected '{MeshTransportOptions.ModeDps}', " - + $"'{MeshTransportOptions.ModeClusterClient}' or '{MeshTransportOptions.ModeGrpc}'."); + + $"'{options.Mode}'. Expected '{MeshTransportOptions.ModeDps}' or " + + $"'{MeshTransportOptions.ModeGrpc}'."); } if (options.ContactRefreshSeconds <= 0) @@ -64,6 +66,7 @@ public sealed class MeshTransportOptionsValidator : IValidateOptions