diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Mesh/MeshPaths.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Mesh/MeshPaths.cs
index 99337b79..1bf2ec4d 100644
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Mesh/MeshPaths.cs
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Mesh/MeshPaths.cs
@@ -1,33 +1,43 @@
namespace ZB.MOM.WW.OtOpcUa.Commons.Messages.Mesh;
///
-/// Actor paths the two mesh comm actors register under. These strings are the wire
-/// contract of the central↔node boundary.
+/// The local actor names the two mesh comm actors are spawned under. Not a wire contract:
+/// nothing off-process addresses either actor by name.
///
///
///
-/// Declared once, here, rather than as literals on each side. The deployments /
-/// deployment-acks topic names are declared twice — once in
-/// ConfigPublishCoordinator and again in DriverHostActor — and agree only by
-/// convention; renaming one would compile, deploy, and deliver nothing. A mesh path
-/// has the same failure mode and no compiler to catch it, so the constant is shared.
+/// This used to be a wire contract and no longer is. While the cross-mesh transport
+/// was an Akka ClusterClient, central addressed a node by the literal path string
+/// /user/node-communication and the far side resolved it — so the two processes had to
+/// agree on a string neither could see the other spell. Both gRPC services now resolve
+/// their comm actor through ActorRegistry (CentralCommunicationActorKey /
+/// NodeCommunicationActorKey), which is type-checked, so the name below never crosses
+/// a process boundary. The full /user/... path constants that carried the old contract
+/// are deleted; what remains is a naming convention.
///
///
-/// Nothing else pins these values, which is why MeshCommActorPathTests boots a real
-/// host and Identify-probes both paths.
+/// Still declared once, here, rather than as literals at each spawn site — a shared constant
+/// costs nothing and keeps the name an operator sees in a log or an ActorSelection
+/// attached to one declaration. The registry key, not this string, is what a mis-rename
+/// would break, and that failure is a compile error rather than a silent drop.
+///
+///
+/// MeshCommActorPathTests boots a real host and Identify-probes
+/// /user/{name} on both nodes, which is what pins these values against the two
+/// registration sites.
///
///
public static class MeshPaths
{
- /// Central-side comm actor — where a driver node sends its deployment acks.
- public const string CentralCommunication = "/user/central-communication";
-
- /// Node-side comm actor — where central sends commands.
- public const string NodeCommunication = "/user/node-communication";
-
- /// Actor name of (the path's final segment).
+ ///
+ /// Actor name of the central-side comm actor — where an inbound deployment ack is handed
+ /// over, after CentralCommandService has resolved it by registry key.
+ ///
public const string CentralCommunicationName = "central-communication";
- /// Actor name of (the path's final segment).
+ ///
+ /// Actor name of the node-side comm actor — where an inbound command is handed over, after
+ /// NodeCommandService has resolved it by registry key.
+ ///
public const string NodeCommunicationName = "node-communication";
}
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Communication/NodeCommunicationActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Communication/NodeCommunicationActor.cs
index e54da301..5d6bbeea 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Communication/NodeCommunicationActor.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Communication/NodeCommunicationActor.cs
@@ -8,9 +8,11 @@ using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
namespace ZB.MOM.WW.OtOpcUa.Runtime.Communication;
///
-/// Node-side end of the central↔node command boundary (per-cluster mesh Phase 2), living at
-/// — the path this node's NodeCommandService
-/// gRPC ingress hands every decoded command to.
+/// Node-side end of the central↔node command boundary (per-cluster mesh Phase 2). This node's
+/// NodeCommandService gRPC ingress hands it every decoded command, resolving it from
+/// ActorRegistry by NodeCommunicationActorKey — the actor's name
+/// () is a local convention, not an address anything
+/// off-process uses.
///
///
///
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/MeshCommActorPathTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/MeshCommActorPathTests.cs
index 0e65d371..ac2bc21b 100644
--- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/MeshCommActorPathTests.cs
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/MeshCommActorPathTests.cs
@@ -6,45 +6,58 @@ using ZB.MOM.WW.OtOpcUa.Commons.Messages.Mesh;
namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;
///
-/// Pins the two mesh comm-actor paths against a really booted host.
+/// Pins that both mesh comm actors are actually spawned, under their declared names, on a
+/// really booted host.
///
///
///
-/// /user/central-communication and /user/node-communication 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 Unavailable ("the node is still
-/// starting"), which reads as a boot-window race rather than a permanent misnaming.
+/// What this pins is existence, not a wire contract. Since the cross-mesh transport
+/// became gRPC, both services resolve their comm actor through ActorRegistry rather
+/// than by path, so a rename is a compile error rather than a silent drop and
+/// is only a local naming convention. What is still unpinned by
+/// anything else is whether the two registration sites
+/// (WithOtOpcUaControlPlaneSingletons / WithOtOpcUaRuntimeActors) actually run
+/// and spawn these actors on a real node — and if either did not, every inbound call would be
+/// refused as Unavailable ("the node is still starting"), which reads as a boot-window
+/// race rather than as an actor that will never appear.
///
///
/// Booting the real host is the point. A test that spawned the actors itself would pin the
-/// constant against itself and prove nothing about the wiring in
-/// WithOtOpcUaControlPlaneSingletons / WithOtOpcUaRuntimeActors.
+/// constant against itself and prove nothing about that wiring.
///
///
public class MeshCommActorPathTests
{
- private static async Task AssertActorExistsAsync(ActorSystem system, string path)
+ ///
+ /// Builds the ActorSelection string for a top-level actor name. Composed here rather
+ /// than held as a constant: the full path is a local addressing detail of this test, not
+ /// something the product declares — see .
+ ///
+ /// The declared actor name.
+ /// The absolute user-guardian path.
+ private static string UserPath(string actorName) => "/user/" + actorName;
+
+ private static async Task AssertActorExistsAsync(ActorSystem system, string actorName)
{
+ var path = UserPath(actorName);
var identity = await system.ActorSelection(path)
.Ask(new Identify(path), TimeSpan.FromSeconds(10));
identity.Subject.ShouldNotBeNull(
- $"{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");
+ $"no actor exists at {path}; the registration that spawns it did not run, and every "
+ + "inbound mesh call to this node would be refused as a misleading readiness rejection");
}
[Fact]
- public async Task Both_mesh_comm_actors_exist_at_their_contract_paths()
+ public async Task Both_mesh_comm_actors_are_spawned_under_their_declared_names()
{
await using var harness = await TwoNodeClusterHarness.StartAsync();
// Harness nodes carry admin+driver, so BOTH comm actors live on each of them.
- await AssertActorExistsAsync(harness.NodeASystem, MeshPaths.CentralCommunication);
- await AssertActorExistsAsync(harness.NodeASystem, MeshPaths.NodeCommunication);
- await AssertActorExistsAsync(harness.NodeBSystem, MeshPaths.CentralCommunication);
- await AssertActorExistsAsync(harness.NodeBSystem, MeshPaths.NodeCommunication);
+ await AssertActorExistsAsync(harness.NodeASystem, MeshPaths.CentralCommunicationName);
+ await AssertActorExistsAsync(harness.NodeASystem, MeshPaths.NodeCommunicationName);
+ await AssertActorExistsAsync(harness.NodeBSystem, MeshPaths.CentralCommunicationName);
+ await AssertActorExistsAsync(harness.NodeBSystem, MeshPaths.NodeCommunicationName);
}
// NOT TESTED HERE: that the comm actors are registered per node rather than as cluster