From 6c7e150a327b80f8c40bd73930b518e099fe7da3 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 12 Nov 2026 23:42:26 -0500 Subject: [PATCH] chore(docker-env2): replicate site-x's LocalDb, mirroring docker/ site-a (#116) Unreplicated, the standby holds no deployed configs, data connections or S&F buffer: a seeded active kill showed the survivor take over the cluster in 28 s but load no instances and run no scripts. Dev-only sync key. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi --- .../site-x-node-a/appsettings.Site.json | 18 +++++++++++++++++- .../site-x-node-b/appsettings.Site.json | 11 ++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/docker-env2/site-x-node-a/appsettings.Site.json b/docker-env2/site-x-node-a/appsettings.Site.json index 49f554e7..a1db0fb5 100644 --- a/docker-env2/site-x-node-a/appsettings.Site.json +++ b/docker-env2/site-x-node-a/appsettings.Site.json @@ -77,7 +77,23 @@ // outside the volume and were lost on every recreate. // Replication is opt-in and configured separately; absent = local-only. "LocalDb": { - "Path": "/app/data/site-localdb.db" + "Path": "/app/data/site-localdb.db", + // site-x is env2's REPLICATED pair (since 2026-11-13, ScadaBridge#116 seeded soak), + // mirroring docker/ site-a. Without it the standby holds no deployed configs, data + // connections or S&F buffer, so a failover brings up an EMPTY active node: the + // cluster recovers but data collection does not. + // + // This node is the initiator (dials the peer); the stream is bidirectional. Port + // 8083 is the existing site gRPC listener. The ApiKey must be IDENTICAL on both + // nodes (LocalDbSyncAuthInterceptor is fail-closed). Dev-only key, not a secret. + "Replication": { + "PeerAddress": "http://scadabridge-env2-site-x-b:8083", + "ApiKey": "dev-env2-site-x-localdb-sync-key", + // Same sizing as docker/site-a (see the rationale there). + "MaxBatchSize": 64, + "MaxOplogRows": 250000, + "MaxOplogAge": "2.00:00:00" + } }, // arch-review remediation WP1.2: the site hot-path audit writer's SQLite file has no // default path (mirrors LocalDb:Path above) and StartupValidator now requires it for diff --git a/docker-env2/site-x-node-b/appsettings.Site.json b/docker-env2/site-x-node-b/appsettings.Site.json index 28d2fc1d..ff31dc68 100644 --- a/docker-env2/site-x-node-b/appsettings.Site.json +++ b/docker-env2/site-x-node-b/appsettings.Site.json @@ -77,7 +77,16 @@ // outside the volume and were lost on every recreate. // Replication is opt-in and configured separately; absent = local-only. "LocalDb": { - "Path": "/app/data/site-localdb.db" + "Path": "/app/data/site-localdb.db", + // The PASSIVE half of env2 site-x's replicated pair (see node-a): no PeerAddress, + // node-a dials in, and this node's writes still reach node-a over the same stream. + // The key MUST match node-a's exactly. Dev-only key, not a secret. + "Replication": { + "ApiKey": "dev-env2-site-x-localdb-sync-key", + "MaxBatchSize": 64, + "MaxOplogRows": 250000, + "MaxOplogAge": "2.00:00:00" + } }, // arch-review remediation WP1.2: the site hot-path audit writer's SQLite file has no // default path (mirrors LocalDb:Path above) and StartupValidator now requires it for