feat(mesh): MeshCommand envelope + generation-suffixed ClusterClient factory
Phase 2 Task 2. Two small pieces the comm actors are built on. MeshCommand carries a command plus the DPS topic it would have been published on, so the admin singletons stop knowing which transport is in force. Without it the dark switch would have to be threaded through five publish sites across two actors, each free to drift. Central-side only -- it never crosses the wire, so it carries no serialization contract. DefaultMeshClusterClientFactory appends a generation counter to the actor name. Context.Stop is asynchronous and the name stays reserved until termination completes, so recreating the same name inside one message handler throws InvalidActorNameException -- which is exactly what a contact-set change does (stop the old client, create the replacement, one handler). Ported from the sister project, where it was a shipped bug fix rather than foresight. Sabotage-verified: freezing the name to a constant reddens both name tests with InvalidActorNameException. Dropped a third test I had written -- it asserted ClusterClientSettings directly and never touched the factory, so it was coverage theatre. Replaced with a comment saying where contact propagation IS covered (the Task 8 boundary test, the only place it can actually fail). Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Commons.Messages.Mesh;
|
||||
|
||||
/// <summary>
|
||||
/// A central→node command, plus the DistributedPubSub topic it would have been published on.
|
||||
/// </summary>
|
||||
/// <param name="Topic">
|
||||
/// The legacy DPS topic. Carried so the router can publish under
|
||||
/// <c>MeshTransport:Mode = Dps</c> without a second dispatch table; ignored entirely under
|
||||
/// <c>ClusterClient</c> mode, where the destination is an actor path rather than a topic.
|
||||
/// </param>
|
||||
/// <param name="Message">
|
||||
/// The command itself, never wrapped. Node-side handlers see exactly the type they see today,
|
||||
/// which is what lets the dark switch touch publishers only.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This envelope exists so the admin singletons stop knowing which transport is in force.
|
||||
/// Before per-cluster mesh Phase 2 each publisher held its own
|
||||
/// <c>DistributedPubSub.Get(Context.System).Mediator</c> call, so the switch would have had
|
||||
/// to be threaded through five call sites across two actors, and each would have been free
|
||||
/// to drift.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Central-side only — it never crosses the wire, so it carries no serialization contract
|
||||
/// and needs no additive-evolution discipline.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed record MeshCommand(string Topic, object Message);
|
||||
Reference in New Issue
Block a user