docs(mesh): sweep the remaining doubled-quorum comment sites — the coordinator absorbs duplicates idempotently

This commit is contained in:
Joseph Doherty
2026-08-06 06:22:44 -04:00
parent 667639e6ab
commit 07bc1878fc
3 changed files with 12 additions and 8 deletions
@@ -128,8 +128,9 @@ public sealed class NodeCommunicationActor : ReceiveActor
}
// Send, not SendToAll: the deploy coordinator is a single singleton, and the central comm
// actor forwards to its proxy. SendToAll would deliver one ack per central node, and the
// coordinator would count the same node twice.
// actor forwards to its proxy. Its NodeId-keyed upsert absorbs a duplicate idempotently,
// so single-target is defence in depth and a bound on wasted sends — mirroring the
// corrected Grpc-branch rationale above.
_centralClient.Tell(new ClusterClient.Send(MeshPaths.CentralCommunication, ack));
}
}
@@ -22,8 +22,9 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Communication;
/// <b>The load-bearing fact is that ONE endpoint is dialled on success.</b> This replaces an
/// Akka <c>ClusterClient.Send</c>, which is single-target at-most-once — not a
/// <c>SendToAll</c>. Both central nodes route an ack to the same
/// <c>ConfigPublishCoordinator</c> singleton proxy, so posting to both would have the
/// coordinator count this node twice and could seal a deployment on a doubled quorum.
/// <c>ConfigPublishCoordinator</c> singleton proxy, whose NodeId-keyed upsert absorbs a
/// duplicate idempotently — so the single-endpoint rule is defence in depth and a bound
/// on wasted dials, not what protects the seal.
/// </para>
/// <para>
/// The failure side matters just as much: the receptionist dropped an undeliverable ack in
@@ -94,8 +94,9 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
public void ApplyAck_goes_to_central_as_a_Send_not_a_SendToAll()
{
// Send, not SendToAll: the deploy coordinator is one singleton behind the central comm
// actor's proxy. SendToAll would deliver one ack per central node and the coordinator would
// count this node twice.
// actor's proxy. The coordinator's NodeId-keyed upsert absorbs a duplicate idempotently,
// so the single-target rule is defence in depth and a bound on wasted work, not a
// correctness requirement.
var client = CreateTestProbe();
var actor = Sys.ActorOf(NodeCommunicationActor.Props(client.Ref));
@@ -161,8 +162,9 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
public void A_sender_wins_over_a_cluster_client_and_the_ack_is_not_sent_twice()
{
// Defence in depth. The registration arm builds exactly one transport — under Grpc no
// ClusterClient is created at all — but if both were ever present, posting over both would
// deliver two acks for one apply and the coordinator would count this node twice.
// ClusterClient is created at all — and if both were ever present, posting over both would
// deliver two acks for one apply. The coordinator would absorb the duplicate (NodeId-keyed
// upsert), so this pins tidiness and bounded work, not seal correctness.
var client = CreateTestProbe();
var sender = new RecordingAckSender();
var actor = Sys.ActorOf(NodeCommunicationActor.Props(client.Ref, sender));