# 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`) 1. 10:43 — `reg export` of 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 CHECKSUM` ok. Zip pushed over SMB, SHA-256 verified on the box, unpacked, staged `Host.exe`/`scadabridge.exe` ProductVersion checked = `2b9a7c3c`. 2. Baseline: `/health/ready` 200/200; `POST /management ListInstances` no-auth → 200; bogus `X-API-Key` → 401; `debug snapshot` Z28061Sim(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`). 3. **10:45:29** Site stopped (10 s), Central stopped (10 s), no `Host.exe` left. 4. 10:45:50 migration script: 53 → 55 rows in `__EFMigrationsHistory`. 5. `app`/`cli` → `app.bak.20260910T104529` / `cli.bak.20260910T104529`; staged copy in; the three appsettings restored (SHA match). 6. 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.CollectWarnings` counts `Cluster:SeedNodes`, and vd03 carries the phantom second seed (8091/8092) that the same validator requires (`>= 2` seed 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` (or `SCADABRIDGE_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.