test(mesh): step 7 — replace the boundary test with MeshGrpcBoundaryTests and sweep the comments

MeshGrpcBoundaryTests replaces the MeshClusterClientBoundaryTests deleted in step 1. Three
self-seeded single-member clusters sharing the ActorSystem name, joined ONLY by gRPC, driving
the REAL CentralCommunicationActor -> MeshGrpcCommandDialer -> Kestrel h2c ->
MeshCommandAuthInterceptor -> NodeCommandGrpcService -> NodeCommunicationActor -> EventStream:

  1. No_cross_mesh_Akka_association_forms_between_central_and_either_site — the program's own
     point, kept as a test rather than a live-gate note: each cluster stays single-member and
     none ever holds another's address as a member.
  2. One_dispatch_through_central_reaches_a_node_in_every_cluster_mesh — the fan, with each
     site holding its OWN per-cluster PSK, so a dial that selected the wrong cluster's key
     would be refused at the far side.
  3. Falsifiability_control_a_dial_carrying_the_wrong_cluster_key_delivers_nothing — without
     it, (2) cannot distinguish "the gRPC boundary carried it" from "something else in the
     process did". The same node then accepts the right key, so the silence is the auth gate
     and not a dead endpoint.

All three run fully offline — no Docker fixture — and pass.

Comment sweep across every remaining clusterclient|receptionist hit in src/ and tests/:
the program label becomes "mesh gRPC command plane", and every comment that described the
deleted transport as live now describes what actually runs (Dps = pair-local default,
Grpc = cross-mesh). mesh_transport.proto's header/service comments are reworded; no message,
field or enum is touched — the contract is locked. MeshCommActorPathTests is reshaped rather
than retired: the two paths still exist and still matter, but what a mismatch costs is now a
misleading Unavailable from the gRPC ingress rather than a silently dropped receptionist send.

The only surviving mentions of the old names are the ones that must survive: the fail-closed
rules that name "ClusterClient" as a retired value refused at boot, their tests, and this
test class's own account of what it replaces.
This commit is contained in:
Joseph Doherty
2026-08-06 15:58:36 -04:00
parent af3b0c3146
commit 4d2dfb5b50
33 changed files with 505 additions and 86 deletions
@@ -10,11 +10,12 @@ namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;
/// </summary>
/// <remarks>
/// <para>
/// <c>/user/central-communication</c> and <c>/user/node-communication</c> are the wire
/// contract of the central↔node boundary: central sends to a path string, and the node
/// registers under one. Nothing else in the codebase asserts they agree. Renaming either
/// actor would compile, pass every unit test, deploy — and deliver nothing, with no error
/// on either side, because a ClusterClient send to an unregistered path is simply dropped.
/// <c>/user/central-communication</c> and <c>/user/node-communication</c> are where each
/// side's gRPC ingress expects to find its comm actor: the services resolve them by registry
/// key, and these constants are the names the actors are spawned under. Nothing else in the
/// codebase asserts they agree. Renaming either actor would compile, pass every unit test,
/// deploy — and refuse every inbound call as <c>Unavailable</c> ("the node is still
/// starting"), which reads as a boot-window race rather than a permanent misnaming.
/// </para>
/// <para>
/// Booting the real host is the point. A test that spawned the actors itself would pin the
@@ -30,8 +31,8 @@ public class MeshCommActorPathTests
.Ask<ActorIdentity>(new Identify(path), TimeSpan.FromSeconds(10));
identity.Subject.ShouldNotBeNull(
$"{path} is the ClusterClient wire contract for the central↔node boundary; nothing else "
+ "pins it, and a mismatch delivers nothing without erroring on either side");
$"{path} is where the central↔node boundary expects its comm actor; nothing else pins "
+ "it, and a mismatch turns every inbound call into a misleading readiness rejection");
}
[Fact]
@@ -60,7 +61,7 @@ public class MeshCommActorPathTests
// is left to live-gate step 8 (stop the OLDEST central; the survivor's comm actor must still
// receive a node ack, which a singleton hosted on the stopped node cannot).
//
// NOT MeshClusterClientBoundaryTests: that class proves a send only arrives if the target is
// registered with the receptionist, which is necessary but not discriminating — with one node
// NOT MeshGrpcBoundaryTests: that class proves a command only arrives if the target is
// registered under its registry key, which is necessary but not discriminating — with one node
// per side, per-node and singleton registration look identical.
}