Deploy gate: leader-auto-down rollout. Rig drill PASSED (2026-11-12); next env2 → QA → DEV #116
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Status (2026-11-12): rig gate PASSED; docker-env2 gate PASSED (env2 now soaking on
leader-auto-down). All four gates passed on central and site-a, see the comments below anddocs/plans/2026-11-12-leader-auto-down-rig-drill.md. The freeze / healed-partition finding #132 is fixed and closed (952db02a). With it, a frozen node and both sides of a healed partition now converge on one active node with no operator action (rig re-run, drill doc §7).139a43fb)DRILL_ACTION=pause) and split-then-heal gate on central and site-a, plus a kill regression (2026-11-12,952db02a)docker-env2: all gates on central and site-x incl. freeze and split-then-heal, plus a kill regression (2026-11-12, imagescadabridge:env2-116froma2e9d718+e8a743de;docs/plans/2026-11-12-leader-auto-down-env2-drill.md). env2 stays onleader-auto-downas the soakComponent-ClusterInfrastructure.mdanddocker/README.md; close register row 70Deploy gate for the
leader-auto-downdowning strategy (in-repoLeaderAutoDowningProvider, ScadaBridge#106). The code and in-process tests are done; the strategy is opt-in and not the default. This issue covers the live gates that must pass before the default flips fromauto-down, and the rollout after that.Not run as part of #106: the docker failover drill. It was deliberately skipped because running it would have rebuilt the shared
scadabridge:latestimage and reused the shared rig's named volumes while another agent was working in the repo.Gates (rig first)
ScadaBridge:Cluster:SplitBrainResolverStrategy = "leader-auto-down"in bothdocker/central-node-{a,b}/appsettings.Central.json. Stop and start the pair together, then run:DRILL_MODE=active bash docker/failover-drill.sh— kills the oldest node. The survivor must take over within about 25s, and the victim must rejoin as standby.bash docker/failover-drill.sh(standby mode) — the standby is downed and removed, with no/health/activeblips.docker/site-a-node-{a,b}/appsettings.Site.json).failover-drill.shonly targets central today, so a site variant is needed:docker killthe site's oldest node, check the survivor logs[leader-auto-down] DOWNING, the site comes back Up, and the victim rejoins.auto-down-unreachable-after … keep-majority will be used insteaddeprecation warning. They should show the[leader-auto-down] started on …line, and on the drill victim's survivor[leader-auto-down] … is unreachable; it will be downed if still unreachable after 00:00:15followed byDOWNING./health/active200. After the heal, exactly one node restarts (the higher address), and the pair converges on one active node with no operator action. Rig: central +76 s and site-a +111 s after the heal. In-process:ShutOutRestartScenarioTests.HealedPartition_BothShutOut_OnlyTheHigherAddressRestarts_AndRejoins.LeaderAutoDownScenarioTests.PartitionLongerThanWindow_EachSideDownsTheOther_BothStayUpcovers the during-partition half.Rollout after the gates pass
Rig →
docker-env2→ QA pair vq03/vq04 → DEV vd03 and sites. Each pair stops and starts together; never do a rolling restart across a downing-provider change. The change is an appsettings flip only. Rollback is setting the value back toauto-down. Then switch the shipped default, update CLAUDE.md,Component-ClusterInfrastructure.mdanddocker/README.md, and close register row 70.Plan:
docs/plans/2026-11-09-autodowning-replacement-plan.md§5–6. Parent: #106, #51.Rig gate: PASS (local docker rig, 2026-11-12,
main@2d2e9f35)Evidence:
docs/plans/2026-11-12-leader-auto-down-rig-drill.mdand raw logs indocs/plans/2026-11-12-leader-auto-down-rig-drill/. Commit139a43fb. The run covered the local rig only. env2, QA and DEV were not touched.How it was run
docker/docker-compose.leader-auto-down.yml. It is an env override oncentral-a/bandsite-a-a/b, so the shipped appsettings default staysauto-down.docker/failover-drill.shgainedDRILL_PAIR=central|site-a|…(the site variant),DRILL_ACTION=kill|pauseand a strict rejoin check. The rejoin check requires the victim at ready 200 / active 503, the survivor atmemberCount 2, unreachableCount 0, and then 10 samples each with exactly one active node.auto-downbaseline was run on the same build first.DOWNING+28 s, active 29 s, Traefik 30 s, rejoin 2 sDOWNING+26 s, active 26 s, rejoin 11 sDOWNING+29 s, 0 blips, rejoin 2 sDOWNING+26 s, 0 blips, rejoin 1 s[leader-auto-down] started …, thenis unreachable … after 00:00:15, thenDOWNINGcentral-brecovered in 12 ssite-a-brecovered in 32 sDOWNINGDOWNINGExtra scenario: a freeze (
docker pauseof the active node, then unpause)auto-downdoes exactly the same, so this is parity, not a regression. The cause: Akka.Cluster 1.5.62 never acts onThisActorSystemQuarantinedEvent.leader-auto-downonly. That fix would change gate 4's contract, because both sides of a healed partition would then restart, so it needs an owner decision.LeaderAutoDowningProviderwas not changed. No defect was found in it.Rig state. Both pairs were recreated together back on the default
auto-down. All 8 nodes are Up, with one active and one standby per pair (central-a, site-a-a, site-b-a and site-c-a active).Next step (needs owner approval): roll out to
docker-env2. Apply the same strategy to its central pair and one site pair, stopping and starting each pair together, and re-run the drill there. After that comes the QA pair vq03/vq04, then DEV vd03 and its sites. I suggest deciding #132 before the default flips. This issue stays open for the rollout.Deploy gate: run the docker failover drill (central + site-a) on leader-auto-down, then roll it out rig → env2 → QA → DEVto Deploy gate: leader-auto-down rollout. Rig drill PASSED (2026-11-12); next env2 → QA → DEVFreeze gate: now PASS (#132 fixed,
main@952db02a+ docs482ac665)The extra scenario that failed in the first rig run now passes on both pairs. A frozen node, and both sides of a healed partition, now converge to one active node with no operator action. Details and design: #132. Record:
docs/plans/2026-11-12-leader-auto-down-rig-drill.md§7. Local rig only; env2, QA and DEV were not touched.DRILL_MODE=active DRILL_ACTION=pause)[leader-auto-down] shut out by … RESTARTS, exited 75, and rejoined as standby 34 s after unpause. 10/10 single-activeGate 4 contract change. The checklist above now reads: dual-active while the partition lasts, then self-healing after it heals (the higher address restarts). The issue body is updated.
The shipped default stays
auto-down. The rig is back on it with all 8 nodes healthy: one active node per pair,memberCount 2. The fix applies only underleader-auto-down. The switch isScadaBridge:Cluster:LeaderAutoDown:RestartWhenShutOut, default on for that strategy, withShutOutGraceat 60 s.auto-downkeeps the old freeze behaviour, which is one more reason to flip the default once the rollout below is done.For the rollout. The Host now exits 75 after any unplanned ActorSystem termination. Before this it exited 0. Windows service recovery with
failureflag 1restarts only on a non-zero exit, so the self-down restart on the wonder boxes very likely never fired before. This has not been verified on a box. It is worth checking on QA when this build reaches it: stop the peer's network briefly, or check the SCM event log after a drill.This issue stays open. Next step:
docker-env2, which needs owner approval. After that: QA vq03/vq04, then DEV vd03 and its sites, then the default flip.docker-env2 gate: PASS (2026-11-12, owner-approved step)
Env2 only. The primary rig, the shared infra, QA and DEV were not touched. The shipped default stays
auto-down.Build. Image
scadabridge:env2-116, built fromorigin/maina2e9d718plus the env2 tooling commit (e8a743deafter rebase, no code change).scadabridge:latest, which the primary rig runs, was not rebuilt or retagged. Record:docs/plans/2026-11-12-leader-auto-down-env2-drill.md, raw logs indocs/plans/2026-11-12-leader-auto-down-env2-drill/. Commitse8a743de,cafdc0ce,90d20030.How it was run
SCADABRIDGE_IMAGEindocker/build.shand indocker-env2/docker-compose.yml.docker-env2/docker-compose.leader-auto-down.yml, mirroring the rig overlay.DRILL_ENV=env2indocker/failover-drill.sh.--project-directory), not from the worktree.auto-downbaseline first, then both pairs switched together in one recreate.auto-downkill activeDOWNING+28 s, active 28 s, Traefik 32 s, rejoin 2 s, 10/10[leader-auto-down] started,is unreachable … after 00:00:15, thenDOWNINGDRILL_ACTION=pause)shut out … RESTARTS, exited 75, rejoined as standby 3 s after unpause, 10/10STAYand central-bRESTART; only central-b restarted; one active node, 2 members at +76 sLimits
ScadaBridgeConfig2. This run proves cluster mechanics only, and the rig's data-path and S&F check was not repeated.seed-sites.shbinds to the windev MxGateway, so it was not run./managementcalls at 04:15–04:17 UTC).Final state: env2 stays on
leader-auto-downas the soak. All 4 nodes runscadabridge:env2-116with the overlay. Each pair has one active node (central-b, site-x-b) andmemberCount 2, unreachableCount 0, and Traefik:9100answers 200. A plaindocker-env2/deploy.shwould end the soak (:latestonauto-down). The keep-soak command and the rollback are indocker-env2/README.md.Still open. Next step: QA pair vq03/vq04, which needs owner approval, so
blockedstays. On QA, also verify that the Windows service supervisor restarts the Host on exit 75. Then DEV vd03, then the default flip.env2 seeded data-path soak: done (2026-11-13 UTC 04:32–04:55). Record:
docs/plans/2026-11-12-leader-auto-down-env2-drill.md§5 (raw outputseeded-*in its directory). Commits8b6d3268,6c7e150a,644f564conmain.Seeded:
docker-env2/seed-sites.shset up Site-X, the MxGateway connection to windev (used only as a client) andMxAlarmDemo-1. The newdocker-env2/seed-data-path.shadds:env-soak-1: opc-plcSpikeData/RandomSignedInt32, plus a 2 s script that sends a tick-numberedExternalSystem.CachedCallto an env2-owned HTTP sink,scadabridge-env2-sfsink. Its 503 mode forces store-and-forward to buffer.env-galaxy-1:TestMachine_003.TestChangingInt, read only.Values are Good and updating through
:9100, and both connections are Connected in central's site health report.Finding 1 — an unreplicated site pair cannot fail over its data. The first seeded active kill passed the cluster gate (DOWNING +28 s, rejoin 1 s). But the survivor loaded no instances, had no connections and ran no scripts, and the victim's 10 buffered messages stayed on the victim. This is by design: site-x had no LocalDb peer, like the rig's site-b and site-c.
6c7e150amakes env2 site-x replicated, mirroring the rig's site-a. After that, the 10 stranded messages were delivered exactly once.Finding 2 (minor) — boot race when replication is first turned on over diverged stores. The node that boots active loads its stale store about 1 s before the snapshot arrives, and its startup reconcile then sees 0 gaps. The fix was
deploy-artifactsplus a redeploy. Steady-state failovers are not affected.Failovers with data (site-x replicated, sink failing across each hit):
No buffered message was lost or delivered twice. The 4 duplicate ticks in the freeze run come from the frozen victim's 6 s zombie window after unpause: its interval timer fired 4 catch-up runs (the in-flight cap, 1 shed) and one run overlapped the survivor's. That is the accepted dual-active trade, and #132's shut-out bounds it. The standby's S&F depth gauge reads stale values (known, 02/C3).
Final env2 state: all 4 nodes on
scadabridge:env2-116+leader-auto-down, eachmemberCount 2. Active nodes: central-a and site-x-a. Site-x is replicated and the soak load keeps running. The primary rig,scadabridge:latestand shared infra were not touched.Still open and
blocked: the next step is QA vq03/vq04, which needs owner approval.Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
QA rollout is bundled into the #107 QA window pack (
16209831):docs/deployment/deploy-prep/2026-11-13-qa-vq03-vq04/README.md, sections 4, 5.3, 5.4, gate G14 and section 8. It is not run yet and waits for the owner's go.failureflag 1).QA pack refreshed to
main@8e32f353:8ffa6394(docs/deployment/deploy-prep/2026-11-13-qa-vq03-vq04/, not executed). Per the owner's 2026-11-13 go, the window bundles theleader-auto-downflip in both appsettings files on both boxes plus the exit-code-75 service recovery (sc.exe failure … restart/10000/restart/30000/restart/60000,failureflag 1) on all four services, with no #116 drills (neither central nor site pair). G14 is struck; record "QA on leader-auto-down, not drilled" after the window. Boot-log check G6 ([leader-auto-down] startedon all 4) and the new G15 (recovery setting read back) remain.Deployed to QA vq03/vq04 (2026-11-13). Config is applied, the drills are still pending. Build
8e32f353, record8ba5d5d7(docs/deployment/deployment-records/2026-11-13-wonder-app-vq03-vq04-8e32f353.md).ScadaBridge:Cluster:SplitBrainResolverStrategyis nowleader-auto-downinappsettings.Central.jsonandappsettings.Site.jsonon both boxes. It is the only key that changed: the key-level digest diff shows nothing else, andLeaderAutoDownstays at its defaults.[leader-auto-down] started on akka.tcp://scadabridge@10.220.158.{2,3}:{8081,8082}on all four nodes, each withshut-out restart is ON. vq03gained leadershipon both pairs./health/activesampled 10× gives exactly one 200 per pair every time (vq03).FAILURE_ACTIONS_ON_NONCRASH_FAILURES: TRUE. The flag was already TRUE and was re-asserted; the existing actions were kept, as on DEV._appsettings-keep-20261113T080106on both boxes, then restart each pair together.