4d2dfb5b50
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.
41 lines
2.3 KiB
C#
41 lines
2.3 KiB
C#
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Runtime.Communication;
|
|
|
|
/// <summary>
|
|
/// Posts this node's deployment acknowledgment to central over the <c>mesh_transport.v1</c>
|
|
/// command plane (mesh gRPC command plane, Phase 1B). The seam
|
|
/// <see cref="NodeCommunicationActor"/> uses under <c>MeshTransport:Mode = Grpc</c>, in place of
|
|
/// the node-local DistributedPubSub publish it uses under <c>Dps</c>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b>One ack per node per dispatch.</b> Whatever the implementation does about redundancy, it
|
|
/// posts to <i>one</i> central endpoint at a time rather than fanning out — the semantic of the
|
|
/// one-ack-per-node-per-dispatch semantic the deploy coordinator counts on.
|
|
/// This is <b>defence in depth, not a correctness dependency</b>: the coordinator counts acks
|
|
/// in a <c>NodeId</c>-keyed dictionary behind an expected-set membership guard
|
|
/// (<c>ConfigPublishCoordinator.HandleAck</c>), so a duplicate from one node overwrites its own
|
|
/// entry and cannot inflate the count or seal a deployment early. What fan-out would actually
|
|
/// cost is duplicated work at central, doubled deploy-ack rows written, and a transport whose
|
|
/// delivery count nobody can reason about — none of which is worth having.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Fire-and-forget, and it never throws.</b> The caller is an actor that discards the
|
|
/// returned task, so an exception escaping here would become an unobserved task fault — the
|
|
/// silent drop this migration exists to remove. An implementation reports failure by logging
|
|
/// and returning, never by faulting the task.
|
|
/// </para>
|
|
/// </remarks>
|
|
public interface IApplyAckSender
|
|
{
|
|
/// <summary>Posts <paramref name="ack"/> to central, or logs why it could not be.</summary>
|
|
/// <param name="ack">The per-node apply acknowledgment.</param>
|
|
/// <param name="cancellationToken">Cancels the in-flight call.</param>
|
|
/// <returns>
|
|
/// A task that completes when the attempt finishes, successfully or not. It never faults —
|
|
/// see the remarks.
|
|
/// </returns>
|
|
Task SendAsync(ApplyAck ack, CancellationToken cancellationToken = default);
|
|
}
|