feat(localdb)!: replicate site config + sf_messages via CDC, delete the bespoke replicators
Tasks 14, 15 and 16, landed as ONE commit. PLAN DEFECT: these three tasks cannot compile separately. SiteReplicationActor takes a ReplicationService and calls ReplaceAllAsync (Task 14 deletes both); DeploymentManagerActor Tells message types declared in ReplicationMessages.cs (Task 15 deletes it); AkkaHostedService constructs the actor (Task 16). Any ordering leaves a broken intermediate. Combining them also strengthens the invariant Task 14 already stated for itself — the two mechanisms never both run, and never neither. Registered 8 tables in SiteLocalDbSetup.OnReady: sf_messages plus the 7 site config tables. notification_lists and smtp_configurations are deliberately NOT registered — permanently empty by design, so registering them would open a standing replication channel whose only historical payload was plaintext SMTP passwords. Migrate stays the LAST call in OnReady, after all registrations, so migrated rows enter the oplog through live capture triggers. Deleted: SiteReplicationActor, ReplicationMessages.cs, ReplicationService, StoreAndForwardStorage.ReplaceAllAsync, and 6 test files. ReplaceAllAsync is not merely unused but unsafe to keep: a mass DELETE on a now-replicated table would be captured and shipped to the peer. Kept ActiveNodeEvaluator (delivery gate + heartbeat still need it) with its doc corrected, and activeNodeCheck in AkkaHostedService (SiteCommunicationActor). The positional-argument hazard the plan flagged was real: removing DeploymentManagerActor's optional IActorRef? replicationActor shifted 6 trailing optionals, and 4 test call sites bound the wrong arguments with no compile error at some positions. Converted them to named arguments where possible — Props.Create builds an expression tree, which rejects out-of-position named args, so the rest are padded positionally with a comment saying why. The Task 7 'not yet registered' test was INVERTED rather than deleted, and is exact in both directions: too few means a table silently stops replicating, too many means the SMTP tables leak. Added a separate security-named test for those two, and a composite-PK test (LWW keys on the full PK, so a truncated key set would collapse distinct rows). The convergence suites now get their registrations from the real OnReady — their temporary harness registration is deleted, so they prove the cutover rather than agreeing with themselves. Verified: build 0 warnings; SiteRuntime 512, StoreAndForward 130, Host 330, AuditLog 355, ExternalSystemGateway 142, HealthMonitoring 97, LocalDb integration 16 — all pass, 0 failures. Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
This commit is contained in:
+20
-14
@@ -67,12 +67,14 @@ public class DeploymentManagerActorTests : TestKit, IDisposable
|
||||
null, // no stream manager in tests
|
||||
options,
|
||||
NullLogger<DeploymentManagerActor>.Instance,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
serviceProvider,
|
||||
null,
|
||||
configFetcher)));
|
||||
// Named from here on. These trailing parameters are all optional and several
|
||||
// share a type, so a positional list silently binds the wrong argument when the
|
||||
// signature changes — which is exactly what removing replicationActor did.
|
||||
dclManager: null,
|
||||
healthCollector: null,
|
||||
serviceProvider: serviceProvider,
|
||||
loggerFactory: null,
|
||||
configFetcher: configFetcher)));
|
||||
}
|
||||
|
||||
private static string MakeConfigJson(string instanceName)
|
||||
@@ -301,8 +303,11 @@ public class DeploymentManagerActorTests : TestKit, IDisposable
|
||||
var dm = ActorOf(Props.Create(() => new DeploymentManagerActor(
|
||||
_storage, _compilationService, _sharedScriptLibrary, null,
|
||||
new SiteRuntimeOptions(), NullLogger<DeploymentManagerActor>.Instance,
|
||||
null, null, null, null, null, null,
|
||||
TimeSpan.FromMilliseconds(200), loader)));
|
||||
// dclManager, healthCollector, serviceProvider, loggerFactory, configFetcher.
|
||||
// Props.Create builds an expression tree, which rejects named arguments that
|
||||
// are out of position, so the optional tail has to be padded positionally.
|
||||
null, null, null, null, null,
|
||||
startupLoadRetryInterval: TimeSpan.FromMilliseconds(200), configLoader: loader)));
|
||||
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
@@ -892,13 +897,14 @@ public class DeploymentManagerActorTests : TestKit, IDisposable
|
||||
// Security cleanup. notification_lists and smtp_configurations can hold plaintext
|
||||
// SMTP passwords written by a pre-2026-07-10 build, and the ACTIVE node's artifact
|
||||
// apply is what clears them (DeploymentManagerActor.HandleDeployArtifacts). The
|
||||
// standby's copy of this call lives in SiteReplicationActor and dies with it at
|
||||
// Task 15, which makes this call site the ONLY remaining one.
|
||||
// standby used to hold a second copy of this call in SiteReplicationActor; LocalDb
|
||||
// Phase 2 deleted that actor, so this is now the ONLY call site that keeps the
|
||||
// tables empty.
|
||||
//
|
||||
// Nothing pins it today: ArtifactStorageTests covers the storage method, not the
|
||||
// actor's call to it, so Task 16's edits to this actor could drop the call and every
|
||||
// suite would stay green. That is precisely the kind of silent security regression
|
||||
// this test exists to prevent — verified red-first by commenting out the call.
|
||||
// ArtifactStorageTests covers the storage method, not the actor's call to it, so
|
||||
// without this test the call could be dropped and every suite would stay green.
|
||||
// That is precisely the kind of silent security regression it exists to prevent —
|
||||
// verified red-first by commenting out the call.
|
||||
await SeedCentralOnlyRowsAsync();
|
||||
Assert.Equal(1, await RowCountAsync("notification_lists"));
|
||||
Assert.Equal(1, await RowCountAsync("smtp_configurations"));
|
||||
|
||||
Reference in New Issue
Block a user