refactor(mesh): step 5 — delete MeshTransport:CentralContactPoints, its rules and its compose entries

The property, the four validator rules that graded it (empty / non-akka.tcp / actor-path
suffix / one-bad-among-good) and all twelve docker-compose entries (6 nodes x 2) are gone.
The compose comment blocks that explained "/system/receptionist is appended at construction
time" and the client's contact rotation go with them, and the surrounding prose now
describes the Grpc command plane it actually runs.

CentralGrpcEndpoints' doc no longer says it "replaces" the Akka contact points — there is
nothing left to replace — and ContactRefreshSeconds' failure message names the node
dispatch set rather than the ClusterClient contact points.

`rg CentralContactPoints src tests docker-dev` is now empty.
This commit is contained in:
Joseph Doherty
2026-08-06 15:45:21 -04:00
parent d67a7e5d0b
commit 85ca86e955
4 changed files with 34 additions and 234 deletions
@@ -56,21 +56,7 @@ public sealed class MeshTransportOptions
public string Mode { get; set; } = ModeDps;
/// <summary>
/// Akka remoting addresses of the central nodes, e.g.
/// <c>akka.tcp://otopcua@central-1:4053</c>. <b>Node addresses only</b> — the
/// <c>/system/receptionist</c> path is appended at construction time.
/// </summary>
/// <remarks>
/// Each entry must be a <i>central</i> node's remoting endpoint, never this node's own port.
/// The sister project shipped a site template whose second contact was the site's own
/// remoting port; it is a permanent failure in the initial-contact rotation and stays
/// invisible until a command goes missing. <see cref="MeshTransportOptionsValidator"/>
/// rejects the malformed shapes at boot instead.
/// </remarks>
public string[] CentralContactPoints { get; set; } = [];
/// <summary>
/// How often central re-reads <c>ClusterNode</c> rows to rebuild its contact points.
/// How often central re-reads <c>ClusterNode</c> rows to rebuild its dispatch set.
/// Refreshing on a timer (rather than only on admin change) means a row edited by any
/// process — migration, direct SQL, a second admin node — is picked up without a restart.
/// </summary>
@@ -83,12 +69,10 @@ public sealed class MeshTransportOptions
/// (<c>scheme://host:port</c>), not bare <c>host:port</c> — <c>http://</c> is h2c.
/// </summary>
/// <remarks>
/// Tried <b>in order, stopping at the first success</b>, which is what preserves the
/// receptionist's one-ack-per-node-per-dispatch semantic: either central node routes the ack
/// to the same <c>ConfigPublishCoordinator</c> singleton proxy, so which one answers does not
/// matter, but two answering would double-count. The gRPC counterpart of
/// <see cref="CentralContactPoints"/>, and it replaces it — a node running
/// <see cref="ModeGrpc"/> never reads the Akka contact points.
/// Tried <b>in order, stopping at the first success</b>, which is what keeps the ack
/// one-per-node-per-dispatch: either central node routes it to the same
/// <c>ConfigPublishCoordinator</c> singleton proxy, so which one answers does not matter,
/// but two answering would double-count.
/// </remarks>
public string[] CentralGrpcEndpoints { get; set; } = [];
@@ -57,48 +57,13 @@ public sealed class MeshTransportOptionsValidator : IValidateOptions<MeshTranspo
errors.Add(
$"{MeshTransportOptions.SectionName}:{nameof(MeshTransportOptions.ContactRefreshSeconds)} "
+ $"must be greater than 0 (was {options.ContactRefreshSeconds}). Central rebuilds its "
+ "ClusterClient contact points on this interval; a non-positive value would leave the "
+ "contact set frozen at whatever it held when the node booted.");
}
// Contact points are only load-bearing under ClusterClient mode. Requiring them under the
// default would make the section mandatory everywhere for a feature that is switched off —
// including on admin-only nodes, which have no reason to carry them at all.
if (isClusterClient)
{
if (options.CentralContactPoints.Length == 0)
{
errors.Add(
$"{MeshTransportOptions.SectionName}:{nameof(MeshTransportOptions.CentralContactPoints)} "
+ "is empty. Under ClusterClient mode a driver node cannot reach central without at "
+ "least one contact point, and its deployment acks would be dropped silently — the "
+ "deployment would then time out naming a node that is running perfectly.");
}
foreach (var contactPoint in options.CentralContactPoints)
{
if (!contactPoint.StartsWith("akka.tcp://", StringComparison.Ordinal))
{
errors.Add(
$"Contact point '{contactPoint}' must start with 'akka.tcp://' — it is an Akka "
+ "remoting address (akka.tcp://<system>@<host>:<port>), not a bare host:port.");
}
else if (contactPoint.Contains("/user/", StringComparison.Ordinal)
|| contactPoint.Contains("/system/", StringComparison.Ordinal))
{
errors.Add(
$"Contact point '{contactPoint}' carries an actor-path suffix. Configure the "
+ "node address only — '/system/receptionist' is appended at construction time, "
+ "and a doubled suffix produces a path that never resolves.");
}
}
+ "node dispatch set on this interval; a non-positive value would leave that set "
+ "frozen at whatever it held when the node booted.");
}
// The first Grpc block below is node-side: the PSK a driver presents (and requires), the
// central endpoints it posts its ack to, and the two call deadlines (the admin-side rule that
// follows it is a separate scope). Scoped to the driver role for the same reason the
// ClusterClient block above is scoped to its mode — an admin-only central node
// dials nodes rather than being dialled and resolves per-cluster keys at runtime through
// follows it is a separate scope). Scoped to the driver role because an admin-only central node
// MeshTransportOptions.ClusterPsks / the Secrets store, so requiring a single GrpcPsk and an
// endpoint list of it would demand configuration it never reads. Roles come from
// configuration, not IClusterRoleInfo (see the ctor remark), and through