fix(comm): heartbeat IsActive uses the shared oldest-Up predicate; Host wires one delegate everywhere (plan R2-02 T4)

This commit is contained in:
Joseph Doherty
2026-07-13 09:50:44 -04:00
parent 3a5b885a44
commit 26dce8b69f
3 changed files with 16 additions and 23 deletions
@@ -67,10 +67,10 @@ public class SiteCommunicationActor : ReceiveActor, IWithTimers
/// <param name="deploymentManagerProxy">Local reference to the Deployment Manager singleton proxy.</param>
/// <param name="isActiveCheck">
/// Optional override returning <c>true</c> when this node
/// is the active member of the site cluster. <c>null</c> uses the real
/// Akka <see cref="Cluster"/> leader check (the default for production
/// wiring); tests pass a stub so they do not need to load Akka.Cluster
/// into the <c>TestKit</c> ActorSystem.
/// is the active member of the site cluster. <c>null</c> uses the shared oldest-Up
/// evaluator (production wiring passes the Host's singleton-host delegate); tests
/// pass a stub so they do not need to load Akka.Cluster into the <c>TestKit</c>
/// ActorSystem.
/// </param>
public SiteCommunicationActor(
string siteId,
@@ -506,24 +506,16 @@ public class SiteCommunicationActor : ReceiveActor, IWithTimers
}
/// <summary>
/// Default active-node check used when no override is
/// supplied. Mirrors <c>ActiveNodeGate</c> in the Host (and
/// <c>ActiveNodeHealthCheck</c>): the node is the active member of the
/// site cluster when it is the current cluster leader AND its own
/// <see cref="MemberStatus"/> is <see cref="MemberStatus.Up"/>. Any other
/// state (still joining, leaving, no leader yet) reports standby —
/// safe-by-default, matching the standby case.
/// Default active-node check used when no override is supplied: oldest-Up member
/// semantics via the shared <see cref="ClusterState.ActiveNodeEvaluator"/> — the
/// same predicate as the S&F delivery gate and the replication resync authority
/// (review 02 round 2, N1). Unscoped by role: a site cluster's members all carry
/// the site role, so role scoping is a no-op here; production wiring passes the
/// Host's role-scoped IClusterNodeProvider delegate anyway. Any other state
/// (still joining, leaving) reports standby — safe-by-default.
/// </summary>
private bool DefaultIsActiveCheck()
{
var cluster = Cluster.Get(Context.System);
var self = cluster.SelfMember;
if (self.Status != MemberStatus.Up)
return false;
var leader = cluster.State.Leader;
return leader != null && leader == self.Address;
}
private bool DefaultIsActiveCheck() =>
ClusterState.ActiveNodeEvaluator.SelfIsOldestUp(Cluster.Get(Context.System));
// ── Internal messages ──
@@ -833,7 +833,8 @@ akka {{
Props.Create(() => new SiteCommunicationActor(
_nodeOptions.SiteId!,
_communicationOptions,
dmProxy)),
dmProxy,
activeNodeCheck)),
"site-communication");
// Register local handlers with SiteCommunicationActor