Both driven from the Mac over the winrm CLI (WinRM 5985 + SMB 445, no helper on the boxes). Migration 53->55; the first Central boot encrypts DataConnections.PrimaryConfiguration (dp:protocol:v1:), so rollback is the COPY_ONLY DB restore, not a binary swap. QA: all four nodes stopped together, one shared-DB migration, 2 min 35 s window, preflight against a restored clone. Claude-Session: https://claude.ai/code/session_01RgthVLVU5CJ8SUuEwruTkt
7.3 KiB
2026-09-10 — DEV (wonder-app-vd03) → 2b9a7c3c (arch-review remediation + CLI parity + Flow Debugger v1.4)
Build: main @ 2b9a7c3c (= origin/main), ProductVersion 1.0.0+2b9a7c3ca05fde8fe58f647d8f91e5ab9452807b,
published from a detached worktree at that SHA (dotnet publish Host + CLI -c Release -r win-x64 --self-contained true -p:PublishSingleFile=false; 1029 app files, 223 cli files, zip 125 543 056 B,
SHA-256 371b3271…).
Delta from the box's previous build afb27e77: 253 commits — Flow Debugger instance picker + v1.4
step logs, the 2026-09-09 whole-system arch review remediation (13 High + 33 Medium + 46 Low), and
management API / CLI parity (docs/plans/2026-09-10-management-api-cli-parity-review.md). The CLI
gains the shared-schema and flow-debug groups.
QA (vq03/vq04) NOT touched — this record covers vd03 only.
Transport: first ScadaBridge deploy driven entirely by the Mac-side winrm CLI (WinRM 5985/NTLM
for commands, winrm smb push over 445 for the zip) — no psbridge, no servecli.
Schema: 53 → 55 migrations, one of them a coordinated data upgrade
| Migration | Effect | Pre-check |
|---|---|---|
20260906034113_AddApiMethodTraceAllCalls |
ApiMethods.TraceAllCalls bit NOT NULL DEFAULT 0 |
— |
20260909203046_EnforceGlobalInstanceNamesAndProtectProtocolConfiguration |
THROW 51002 if Instances.UniqueName is not globally unique; drops IX_Instances_SiteId_UniqueName, adds IX_Instances_SiteId + unique IX_Instances_UniqueName; widens DataConnections.Primary/BackupConfiguration to nvarchar(max) |
0 duplicate names (4 instances, all site 2); 1 DataConnection (MxGateway, 210 chars, unprotected) |
The migration is schema-only. The first Central boot on the new binary ran
DataConnectionSecretUpgrade and rewrote DataConnections.PrimaryConfiguration as
dp:protocol:v1:… (LEN 210 → 426) using the Data Protection key ring, which on this install lives
in the same database (DataProtectionKeys, 2 keys, unchanged). So the single
BACKUP DATABASE … COPY_ONLY, CHECKSUM taken beforehand is the coordinated database/key-ring
backup the component doc asks for. Old binaries cannot read the protected row, and the migration's
Down refuses while protected rows exist — rollback is the DB restore, not dotnet ef.
From→to script generated with dotnet ef migrations script 20260819201210_AddNotificationsListSortIndex 20260909203046_EnforceGlobalInstanceNamesAndProtectProtocolConfiguration (NOT idempotent — the
full idempotent script does not compile against this DB, see the 2026-06-16 record), applied with
sqlcmd -C -b -I while both services were stopped.
Config
No appsettings change. Every new option in the delta has a passing default
(InboundApi:StandbyForwardPeerEndpoints/StandbyForwardTimeout/MaxLogLinesPerRun,
SiteRuntime:TagSubscribeRetryMaxIntervalMs/TagSubscribeInFlightCeilingMs/TraceArmWindowSeconds/
MaxLogLinesPerRun, Communication:ConnectedHeaderTimeout, MxGateway:ClientNamePrefix,
AuditLogPurge:MaxPurgedRowsPerChannelPerTick), verified by reading the validators and then by the
clean boot. ScadaBridge:InboundApi:ApiKeyStore:Provider left unset (= Sqlite) — the SqlServer
cutover is the separate planned record 2026-09-XX-zb-servers-inbound-key-store.md. The box's three
appsettings*.json were copied out, restored after the swap and SHA-256-matched. Service registry
environment untouched.
Procedure (E:\psrem-stage\2b9a7c3c\sb-swap.ps1, log E:\ApiInstall\ScadaBridge\_DEPLOY-NOTES-20260910T104529.txt)
- 10:43 —
reg exportof both service keys (_envbak-ScadaBridge-{Central,Site}-20260910T104349.reg);BACKUP DATABASE [ScadaBridge] … COPY_ONLY, CHECKSUM, COMPRESSION→E:\ApiInstall\ScadaBridge-backups\ScadaBridge_pre-2b9a7c3c_20260910T104349.bak(120 MB),RESTORE VERIFYONLY … WITH CHECKSUMok. Zip pushed over SMB, SHA-256 verified on the box, unpacked, stagedHost.exe/scadabridge.exeProductVersion checked =2b9a7c3c. - Baseline:
/health/ready200/200;POST /management ListInstancesno-auth → 200; bogusX-API-Key→ 401;debug snapshotZ28061Sim(8) attrs=270 Bad=28 Good=242, Z28061(1) attrs=270 Bad=126 Good=144 (via the on-box CLI with--username multi-role --password password). - 10:45:29 Site stopped (10 s), Central stopped (10 s), no
Host.exeleft. - 10:45:50 migration script: 53 → 55 rows in
__EFMigrationsHistory. app/cli→app.bak.20260910T104529/cli.bak.20260910T104529; staged copy in; the three appsettings restored (SHA match).- 10:45:57 Central started → ready 10:46:11 (14 s, includes the protocol-row encryption upgrade); Site started → ready 10:46:19. ~50 s total downtime.
Verification
| Check | Result |
|---|---|
:8085 /health/ready |
200 Healthy — database, akka-cluster Up (members=1), required-singletons 5/5 |
:8084 /health/ready |
200 Healthy — akka-cluster, localdb |
| ProductVersion | Host + CLI + 25 ZB.MOM.WW.ScadaBridge.* DLLs at 2b9a7c3c |
| DB | 55 migrations; new indexes present; DataConnections id 1 PrimaryConfiguration protected (dp:protocol:v1:), backup null; DataProtectionKeys = 2 |
| Auth posture | POST /management no-auth 200 (DisableLogin preserved); bogus X-API-Key 401 (key store loaded); GET / 200 |
| Site runtime | Loaded 3 deployed configs; reconcile 0 fetched / 0 failed / 0 orphans; Z28061, Z28061Sim, Z28062 each subscribing to 270 tags on connection MxGateway |
| Snapshots after | Z28061Sim attrs=270 alarms=1 Bad=28 Good=242; Z28061 attrs=270 Bad=126 Good=144 — identical to baseline |
| Logs after boot | central 0 ERR/FTL, site 0 ERR/FTL (only the usual first-probe member status: Removed during startup) |
| New CLI | --version = 2b9a7c3c; instance list → 4 rows; groups vs old: +shared-schema +flow-debug |
Observed, not a regression
- Boot
[WRN] Startup configuration warning: Inbound API keys use the per-node SQLite store on a 2-node central cluster— spurious on this box.StartupValidator.CollectWarningscountsCluster:SeedNodes, and vd03 carries the phantom second seed (8091/8092) that the same validator requires (>= 2seed nodes, unconditionally). The node is single-member; nothing to act on until the SqlServer key-store cutover. - The CLI refuses to run without
--username/--password(orSCADABRIDGE_USERNAME/_PASSWORD) even though the server ignores them under DisableLogin — pass any value. - The site's known
SiteLocalDbHealthCheck replicationConnected:false(unreplicated single node) is unchanged and cosmetic.
Rollback
Stop Site, stop Central. RESTORE DATABASE [ScadaBridge] FROM DISK = 'E:\ApiInstall\ScadaBridge-backups\ScadaBridge_pre-2b9a7c3c_20260910T104349.bak' WITH REPLACE
(required — the protected protocol row is unreadable by afb27e77 and the migration Down
throws over it). Rename app → app.failed, app.bak.20260910T104529 → app, same for cli.
Start Central, then Site. Service env untouched; .reg exports are there if a service is ever
re-created.
Staging left on the box under E:\psrem-stage\2b9a7c3c\ (zip, unpacked tree, migration SQL, swap
script, before/after snapshot JSON). The _appsettings-keep-20260910T104529 copy and the
*.bak.20260910T104529 directories can go once the build has soaked.