refactor(comm): retire ClusterClient naming after the gRPC cutover

Phase 4 of the ClusterClient -> gRPC migration deleted the Akka transports
but left the naming behind: `ClusterClientSiteAuditClient` was transport-
agnostic and worked unchanged, so it survived the deletion under a name
that now describes a transport the repo no longer has. Same for a scatter
of doc-comments still framing gRPC as "the new transport" beside an Akka
one that is gone.

Renames it to `SiteCommunicationAuditClient` (and its test file) and
rewrites the stale comments to describe the single transport that exists.
Also tightens CLAUDE.md: drops the self-describing directory listing and
the 27-component enumeration in favour of the non-obvious parts only.

Behaviour-neutral: names and prose only. Recorded as the Phase 4
follow-up in docs/plans/2026-07-22-clusterclient-to-grpc-plan.md.
This commit is contained in:
Joseph Doherty
2026-07-27 15:40:01 -04:00
parent b3dc17a5fe
commit 63c16d6912
57 changed files with 193 additions and 244 deletions
@@ -11,13 +11,13 @@ using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Site.Telemetry;
/// <summary>
/// Tests for <see cref="ClusterClientSiteAuditClient"/> — the production
/// Tests for <see cref="SiteCommunicationAuditClient"/> — the production
/// <see cref="ISiteStreamAuditClient"/> binding wired by the Host for site
/// roles. The client maps the proto-DTO batches produced by
/// <see cref="SiteAuditTelemetryActor"/> into the Akka
/// <see cref="IngestAuditEventsCommand"/> / <see cref="IngestCachedTelemetryCommand"/>
/// messages, Asks the site's <c>SiteCommunicationActor</c> (which forwards over
/// ClusterClient to central), and maps the reply back into an
/// messages, Asks the site's <c>SiteCommunicationActor</c> (which forwards to
/// central over its central transport), and maps the reply back into an
/// <see cref="IngestAck"/>.
/// </summary>
/// <remarks>
@@ -25,7 +25,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Site.Telemetry;
/// it lets the tests assert the exact command shape AND drive the reply (or
/// withhold one to exercise the Ask-timeout path).
/// </remarks>
public class ClusterClientSiteAuditClientTests : TestKit
public class SiteCommunicationAuditClientTests : TestKit
{
/// <summary>Short Ask timeout so the timeout test completes quickly.</summary>
private static readonly TimeSpan AskTimeout = TimeSpan.FromMilliseconds(500);
@@ -65,7 +65,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
public async Task IngestAuditEventsAsync_FullAck_MapsAllAcceptedIdsOntoAck()
{
var probe = CreateTestProbe();
var sut = new ClusterClientSiteAuditClient(probe.Ref, AskTimeout);
var sut = new SiteCommunicationAuditClient(probe.Ref, AskTimeout);
var events = Enumerable.Range(0, 3).Select(_ => NewEvent()).ToList();
var batch = BatchOf(events);
@@ -92,7 +92,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
public async Task IngestAuditEventsAsync_PartialAck_OnlyAcceptedIdsAppearOnAck()
{
var probe = CreateTestProbe();
var sut = new ClusterClientSiteAuditClient(probe.Ref, AskTimeout);
var sut = new SiteCommunicationAuditClient(probe.Ref, AskTimeout);
var events = Enumerable.Range(0, 5).Select(_ => NewEvent()).ToList();
var accepted = events.Take(3).Select(e => e.EventId).ToList();
@@ -114,7 +114,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
public async Task IngestAuditEventsAsync_AskTimeout_Throws_SoDrainLoopKeepsRowsPending()
{
var probe = CreateTestProbe();
var sut = new ClusterClientSiteAuditClient(probe.Ref, AskTimeout);
var sut = new SiteCommunicationAuditClient(probe.Ref, AskTimeout);
var batch = BatchOf(new[] { NewEvent() });
@@ -131,7 +131,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
public async Task IngestAuditEventsAsync_FaultedReply_Throws()
{
var probe = CreateTestProbe();
var sut = new ClusterClientSiteAuditClient(probe.Ref, AskTimeout);
var sut = new SiteCommunicationAuditClient(probe.Ref, AskTimeout);
var task = sut.IngestAuditEventsAsync(BatchOf(new[] { NewEvent() }), CancellationToken.None);
probe.ExpectMsg<IngestAuditEventsCommand>(TimeSpan.FromSeconds(3));
@@ -147,7 +147,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
public async Task IngestCachedTelemetryAsync_RoutesCommand_AndMapsReply()
{
var probe = CreateTestProbe();
var sut = new ClusterClientSiteAuditClient(probe.Ref, AskTimeout);
var sut = new SiteCommunicationAuditClient(probe.Ref, AskTimeout);
var events = Enumerable.Range(0, 2).Select(_ => NewEvent()).ToList();
var batch = new CachedTelemetryBatch();
@@ -182,7 +182,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
public async Task IngestCachedTelemetryAsync_AskTimeout_Throws()
{
var probe = CreateTestProbe();
var sut = new ClusterClientSiteAuditClient(probe.Ref, AskTimeout);
var sut = new SiteCommunicationAuditClient(probe.Ref, AskTimeout);
var batch = new CachedTelemetryBatch();
batch.Packets.Add(new CachedTelemetryPacket
@@ -28,7 +28,7 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
/// <b>Tolerant terminal-state assertion (validation-behavior protocol):</b> whether
/// <c>Connect</c> reaches a <b>Live</b> badge depends on the owning site returning a
/// snapshot for the instance. Reading <c>DebugView.razor::Connect</c>: it awaits
/// <c>DebugStreamService.StartStreamAsync</c> (a ClusterClient snapshot round-trip);
/// <c>DebugStreamService.StartStreamAsync</c> (a site snapshot round-trip);
/// on success it flips <c>_connected = true</c> (Live badge + success toast), and on
/// any exception it surfaces a <c>Connect failed: …</c> error <c>.toast</c>. Either
/// way <c>_connecting</c> resets, so the click always resolves — it never hangs. The
@@ -19,13 +19,13 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
/// and would never appear, so the test mints an instance via the shared
/// <see cref="DeploymentFixture"/> and drives it to <c>Enabled</c> over the CLI
/// (<c>instance deploy</c>) before selecting it. The fixture provisions onto the
/// real, running <c>site-a</c> so the ClusterClient snapshot round-trip resolves
/// fast (an unknown site has no registered ClusterClient and only times out).
/// real, running <c>site-a</c> so the snapshot round-trip resolves
/// fast (an unknown site has no reachable gRPC endpoint and only times out).
/// </para>
///
/// <para>
/// <b>Tolerant terminal-state wait, then the tabs/trees assertions</b> — Connect
/// awaits a ClusterClient snapshot round-trip; on success the page flips to the
/// awaits a site snapshot round-trip; on success the page flips to the
/// Live badge and renders the tabbed card, on failure it surfaces a
/// <c>Connect failed: …</c> <c>.toast</c> (see <c>DebugView.razor::Connect</c>).
/// The test first waits on a TERMINAL state (Live badge OR error toast) within a
@@ -11,8 +11,8 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
///
/// <para>
/// <b>Why <c>site-a</c> and not a throwaway site:</b> the deploy / enable / disable
/// actions relay to the owning site over the Akka <c>ClusterClient</c>. An unknown
/// site identifier has no registered <c>ClusterClient</c>, so the relay only
/// actions relay to the owning site over the gRPC site command plane. An unknown
/// site identifier has no reachable gRPC endpoint, so the relay only
/// resolves on a slow 10-second timeout (and never surfaces a fast failure toast).
/// To exercise the real action path, the ephemeral instances must therefore live
/// on a <em>real, running</em> site — <c>site-a</c> — rather than a fixture-created
@@ -282,8 +282,8 @@ public class SiteCallsPageTests
// be relayed to the owning site. Unlike the display-only tests above,
// this one actually relays to the owning site, so the SourceSite must
// be a *real* site identifier from the running cluster (site-a) and
// not the cosmetic "plant-a" label: an unknown site has no registered
// ClusterClient, so CentralCommunicationActor drops the envelope
// not the cosmetic "plant-a" label: an unknown site has no reachable
// gRPC endpoint, so CentralCommunicationActor drops the envelope
// without replying and the relay only resolves on the 10s inner Ask
// timeout — too slow for the toast assertion below. Relayed to a live
// site, the site finds no parked S&F message for this freshly-seeded
@@ -319,7 +319,7 @@ public class SiteCallsPageTests
// We only assert that an outcome toast appears (exactly one — the
// single-toast contract), not which one, since the live cluster
// state determines the outcome. The wait is generous (15s): the
// relay round-trips to the site over ClusterClient, and a worst-case
// relay round-trips to the site over gRPC, and a worst-case
// path can sit on the 10s inner relay timeout before the response —
// and the toast itself auto-dismisses 5s after it appears, so the
// assertion must catch it inside that window.
@@ -8,10 +8,10 @@ using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Messages;
/// <summary>
/// Contract-lock tests for the highest-traffic ClusterClient record contracts.
/// ClusterClient traffic uses Akka.NET's default Newtonsoft-JSON serializer and the
/// design supports system-wide artifact version skew across sites, so these records
/// must obey additive-only evolution:
/// Contract-lock tests for the highest-traffic cross-boundary record contracts.
/// These predate the ClusterClient→gRPC migration, when this traffic rode Akka.NET's
/// default Newtonsoft-JSON serializer; the design still supports system-wide artifact
/// version skew across sites, so these records must still obey additive-only evolution:
/// (a) an old-shape payload (missing newer trailing optional fields) still
/// deserializes, with the missing fields taking their constructor defaults;
/// (b) an unknown field from a *newer* peer is ignored (never throws);
@@ -94,7 +94,7 @@ public class ManagementCommandRegistryTests
/// <summary>
/// M9-T32c: the schema-library CRUD commands must auto-register by reflection
/// (no manual registry entry) so the HTTP / ClusterClient boundary can route them.
/// (no manual registry entry) so the HTTP management boundary can route them.
/// </summary>
[Fact]
public void Resolve_SchemaLibraryCommands_AllRegistered()
@@ -104,7 +104,7 @@ public class SiteCommunicationActorTransportTests : TestKit
// The seam preserves the reply-routing the S&F layer depends on: when the transport
// answers the captured replyTo with a Status.Failure (its transient-failure signal on a
// non-OK status), that failure reaches the original sender — here the test actor — so the
// waiting Ask faults, exactly as it did on the ClusterClient path.
// waiting Ask faults, exactly as it did on the removed ClusterClient path.
var transport = Substitute.For<ICentralTransport>();
transport
.When(t => t.SubmitNotification(Arg.Any<NotificationSubmit>(), Arg.Any<IActorRef>()))
@@ -15,10 +15,10 @@ using ZB.MOM.WW.ScadaBridge.Communication.Actors;
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests;
/// <summary>
/// Tests for the Audit Log (#23) site→central ClusterClient ingest routing on
/// <see cref="CentralCommunicationActor"/>. A site ClusterClient delivers
/// Tests for the Audit Log (#23) site→central ingest routing on
/// <see cref="CentralCommunicationActor"/>. A site delivers
/// <see cref="IngestAuditEventsCommand"/> / <see cref="IngestCachedTelemetryCommand"/>
/// to the receptionist-registered actor, which forwards to the registered
/// to the actor, which forwards to the registered
/// <c>AuditLogIngestActor</c> proxy and routes the reply back to the site.
/// Mirrors the NotificationSubmit / RegisterNotificationOutbox pattern.
/// </summary>
@@ -17,7 +17,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Tests;
/// Tests that <see cref="CentralCommunicationActor"/> routes a site→central
/// <see cref="ReconcileSiteRequest"/> through the scoped <see cref="ReconcileService"/>
/// and pipes the resulting <see cref="ReconcileSiteResponse"/> back to the original
/// sender (the site's ClusterClient path). Mirrors the audit-ingest routing tests.
/// sender (the site's transport path). Mirrors the audit-ingest routing tests.
/// </summary>
public class CentralCommunicationActorReconcileTests : TestKit
{
@@ -293,7 +293,7 @@ public class DebugStreamBridgeActorTests : TestKit
ctx.BridgeActor.Tell(new StopDebugStream());
// Should send ClusterClient unsubscribe
// Should send the site-addressed unsubscribe
var envelope = ctx.CommProbe.ExpectMsg<SiteEnvelope>();
Assert.IsType<UnsubscribeDebugViewRequest>(envelope.Message);
@@ -60,7 +60,7 @@ public class SiteCommunicationActorTests : TestKit
public void DeploymentStateQuery_ForwardedToDeploymentManager()
{
// DeploymentManager-006: the site-before-redeploy query travels over the
// ClusterClient command/control transport and is routed to the local
// gRPC command/control transport and is routed to the local
// Deployment Manager, which owns the deployed-config store.
var dmProbe = CreateTestProbe();
var siteActor = Sys.ActorOf(Props.Create(() =>
@@ -24,14 +24,14 @@ namespace ZB.MOM.WW.ScadaBridge.IntegrationTests.AuditLog;
/// <summary>
/// End-to-end integration test for the Audit Log (#23) site→central push path
/// introduced by the "real ClusterClient-based site audit push client" follow-up.
/// introduced by the "real site audit push client" follow-up.
/// </summary>
/// <remarks>
/// <para>
/// Exercises the full production chain in one actor system: the real
/// <see cref="SqliteAuditWriter"/> site SQLite hot-path, the real
/// <see cref="SiteAuditTelemetryActor"/> drain loop, the real
/// <see cref="ClusterClientSiteAuditClient"/>, the real
/// <see cref="SiteCommunicationAuditClient"/>, the real
/// <see cref="SiteCommunicationActor"/> forward, the real
/// <see cref="CentralCommunicationActor"/> routing, and the real
/// <c>AuditLogIngestActor</c> ingest — only the cross-cluster gRPC transport itself is
@@ -180,7 +180,7 @@ public class SiteAuditPushFlowTests : TestKit
new BridgeCentralTransport(centralCommActor))));
// The production site audit push client — the unit under integration.
var auditClient = new ClusterClientSiteAuditClient(
var auditClient = new SiteCommunicationAuditClient(
siteCommActor, TimeSpan.FromSeconds(5));
// Real SiteAuditTelemetryActor drains the writer's Pending queue and
@@ -47,7 +47,7 @@ public class DispatchCoverageTests : TestKit, IDisposable
{
// The two-step "ResolveRoles + command" flow is retired — the HTTP endpoint
// resolves roles itself and sends a single envelope. Leaving a handler would
// expose role-mapping data to a raw ClusterClient sender with no role gate.
// expose role-mapping data to a raw in-cluster sender with no role gate.
// See ManagementActor.DispatchCommand (the comment above the default arm, ~line 472).
typeof(ResolveRolesCommand),
@@ -1089,7 +1089,7 @@ public class ManagementActorTests : TestKit, IDisposable
//
// The two-step ResolveRoles + command flow is retired: the HTTP endpoint does
// LDAP auth and role resolution itself. The actor must no longer dispatch
// ResolveRolesCommand — a stray ClusterClient sender hitting it gets a uniform
// ResolveRolesCommand — a stray in-cluster sender hitting it gets a uniform
// ManagementError rather than an unauthenticated role-mapping enumeration.
// ========================================================================
@@ -164,7 +164,7 @@ public class SiteCallRelayTests : TestKit
public void RetrySiteCall_SiteNeverReplies_ReportsSiteUnreachable()
{
// A central comm probe that silently drops the relay — models an offline
// site / no ClusterClient route: the Ask times out.
// site / no route to the site: the Ask times out.
var central = CreateTestProbe();
var actor = CreateActor(central.Ref);