Files
ScadaBridge/docs/deployment/deployment-records/2026-09-28-wonder-app-vd03-fe89363b.md
T
Joseph Doherty 5cc09b08ef deploy(vd03): b3021e7c — script file access live (AllowedRoots E:\ApiInstall\ScadaBridge\recipes); gate 31/31, rig 8/8
Second vd03 window of the day: no migrations, appsettings.Site.json gains ScriptFileAccess:AllowedRoots
(pushed over SMB before the swap), sample recipes in E:\ApiInstall\ScadaBridge\recipes. The Delmia
test receiver now reads + parses the downloaded file; real HTTP R100 -> 12 values loaded, R900 -> rejected
with line numbers. verify-mes-test-machine.sh: D1's cleared check reads through BTDB, so it only runs
with group C (the rig runs VERIFY_GROUPS=AD).
2026-09-28 10:45:54 -04:00

13 KiB
Raw Blame History

2026-09-28 — DEV (wonder-app-vd03) → fe89363b + in-memory MES/Delmia/alarm-API test machines

Build: main @ fe89363b (= origin/main; code tip 3a21cfc2), ProductVersion 1.0.0+fe89363b1a2dbc07c7578cd33c88b7162a1b19e7, 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 593 314 B, SHA-256 bb821df1…87167033). Delta from the box's previous build 2b9a7c3c: 62 commits — MES-reportable computed-alarm flag (IsMesReportable, Alarms.MesReportableAsync()), site artifact revision + pending-updates indicator, Gitea #36 reset-aware handshake overload, Gitea #37 Flow Debugger structured method parameters. QA (vq03/vq04) NOT touched — still 2b9a7c3c at migration 55 (register row 126).

Transport: Mac-side winrm CLI — WinRM 5985/NTLM for the two on-box scripts, winrm smb push over 445 for the zip/SQL/scripts. First deploy under the 2026-09-28 deployment-only WinRM scope (CLAUDE.md); every non-deploy read (baseline, logs, verification) went over HTTP or SMB.

Schema: 55 → 57 migrations (additive only)

Migration Effect
20260911135501_AddAlarmMesReportable TemplateAlarms.IsMesReportable bit NOT NULL DEFAULT 0; InstanceAlarmOverrides.IsMesReportableOverride bit NULL
20260914173147_AddSiteArtifactRevision Sites.ArtifactRevisionHash nvarchar(100) NULL; Sites.ArtifactsDeployedAt datetimeoffset NULL

From→to script dotnet ef migrations script 20260909203046_EnforceGlobalInstanceNamesAndProtectProtocolConfiguration 20260914173147_AddSiteArtifactRevision (sb-mig-55to57.sql, NOT idempotent — reusable as-is for the QA pair), applied with sqlcmd -C -b -I while both services were stopped. No data upgrade; the Central boot logged no "Applying migration" (the script had already applied both).

Config

No appsettings change. The only new option in the delta, ScadaBridge:DeploymentManager:PendingUpdatesCacheTtl, defaults to 60 s. The box's three appsettings*.json were copied out, restored after the swap and SHA-256-matched (A6B6CF05… / 3382DD83… / 1A4333DA…). Service registry environment untouched.

Procedure (E:\psrem-stage\fe89363b\, log E:\ApiInstall\ScadaBridge\_DEPLOY-NOTES-20260928T095846.txt)

  1. Baseline over HTTP from the Mac: /health/ready 200/200; debug snapshot Z28061(1) Bad 126 / Good 144, Z28062(2) Bad 124 / Good 146, Z28061Sim(8) Bad 28 / Good 242 (270 attrs, 7 alarms each).
  2. sb-prep.ps1 (09:58:25): zip SHA-256 re-verified on the box, unpacked, staged Host/CLI ProductVersion = fe89363b; reg export of both service keys; BACKUP DATABASE [ScadaBridge] … COPY_ONLY, CHECKSUM, COMPRESSION → E:\ApiInstall\ScadaBridge-backups\ScadaBridge_pre-fe89363b_20260928T095825.bak (140 795 392 B), RESTORE VERIFYONLY … WITH CHECKSUM valid; DB at 55 migrations.
  3. sb-swap.ps1 (the 2026-09-10 script with the SHA and 55→57 step swapped in): 09:58:46 Site stopped (10 s), Central stopped (10 s), no Host.exe left; 09:59:08 migrations 55 → 57; app/cli → *.bak.20260928T095846, staged copy in, appsettings restored (SHA match); 09:59:14 Central started → ready 09:59:30; Site started → ready 09:59:39. ~53 s total downtime.
  4. deploy artifacts (last step per register row 126): Dev Site success; the site now reports artifactRevisionHash sha256:9e054ef0…, artifactsDeployedAt 2026-09-28T14:01:04Z.

Verification

Check Result
:8085 / :8084 /health/ready 200 Healthy / 200 Healthy
ProductVersion live Host + CLI fe89363b
DB 57 migrations
Snapshots after (settled) Z28061 Bad 126 / Good 144, Z28062 Bad 124 / Good 146, Z28061Sim Bad 28 / Good 242 — identical to baseline (the first read ~20 s after start showed 20 tags Uncertain while subscriptions settled)
Logs after boot (pulled over SMB) central + site: only the usual first-probe akka-cluster … member status: Removed ERR lines during startup; no other ERR/FTL

In-memory MES / Delmia / alarm-API test machines (same session)

Seeded with docker/seed-mes-test-machine.sh (idempotent; second run reused every id and the gate re-passed) and gated with docker/verify-mes-test-machine.sh — PASS 25/25. Nothing existing was modified except the SimpleAlarmStatusRequest stub (below); CvdReactor and its instances are untouched.

Object Id Notes
Template TestMESReceiver 28 26 STATIC attributes with the prod MESReceiver names; ready flags default true
Template TestDelmiaReceiver 29 10 STATIC attributes with the prod DelmiaReceiver names; ReadyFlag true
Template TestMesMachine 30 standalone base (not CvdReactor-derived); composes LeftMESReceiver/RightMESReceiver/DelmiaReceiver; Call scripts MesMoveIn, MesMoveOut, ProcessRecipeDownload, AlarmStatus; simulators SimLeft/RightMoveIn, SimLeft/RightMoveOut, SimDelmiaRecipe
Instance SBTEST01 9 Dev Site, deployed
Instance SBTEST02 10 Dev Site, deployed; alarm override SensorFault → MES-reportable
Inbound SimpleAlarmStatusRequest 9 was a stub returning [] — now real: SAPID → BTDB → AlarmStatus, flagged-only, acked included
Inbound AlarmStatusRequest 12 new: legacy /mes/alarmstatus shape — MachineFilter (SAPID → Code → ZTag → MachineID, via BTDB) + AlarmFilter
BTDB dbo.Machine 203, 204 SBTEST01 SAPID 990001, SBTEST02 SAPID 990002 (Code = Name = ZTag = instance name, Eastern Standard Time, LastUpdateBy = scadabridge-test-seed), inserted through a temporary inbound method (zzTmpBtdbSchema, id 11) that was deleted afterwards

Alarms (static source → computed alarm): VacuumPressure → HighVacuumFault (HiLo hi 5 / hihi 10, 800, MES), ArgonPressure → LowArgonPressure (lo 20 / lolo 10, 600, MES), FurnaceTemp → FurnaceOverTemp (hi 1050 / hihi 1100, 900, MES), DoorOpen → DoorOpenAlarm (true, 300, not MES), SensorStatus → SensorFault (−1, 700, not MES on the template; MES on SBTEST02 via override).

Simulator knobs (root static attributes): SimNextBatchID (5000, increments per move-in), SimMoveInReject, SimMoveOutReject, SimRecipeReject, SimNoResponse (never answer → the 25 s timeout path). Input-driven rejects: a work order containing FAIL; a Delmia path ending .bad or a blank work order; a move-out with nothing moved in on that side. Ready flags on the receivers can be set false to hit the "ready flag not set" paths.

How to test (real HTTP)

H=http://wonder-app-vd03.zmr.zimmer.com:8085/api; K='X-API-Key: <key with the method in scope>'
curl -s -X POST $H/MesMoveIn  -H "$K" -H 'Content-Type: application/json' -d '{"SAPID":"990001_A","ContainerNumber":"CN-1","JobSequenceNumber":"10","WorkOrders":[{"WorkOrderNumber":"WO-1","PartNumber":"PN-1"}],"OperatorName":"me"}'
curl -s -X POST $H/MesMoveOut -H "$K" -H 'Content-Type: application/json' -d '{"SAPID":"990001_A","ContainerNumber":"CN-1","NumberWorkOrders":1,"WorkOrders":[{"WorkOrderNumber":"WO-1","PartNumber":"PN-1"}],"OperatorName":"me"}'
curl -s -X POST $H/DelmiaRecipeDownload -H "$K" -H 'Content-Type: application/json' -d '{"MachineCode":"SBTEST01","DownloadPath":"\\\\share\\r1.xml","WorkOrderNumber":"WO-1","PartNumber":"PN-1","JobStepNumber":"20","Username":"me"}'
curl -s -X POST $H/SimpleAlarmStatusRequest -H "$K" -H 'Content-Type: application/json' -d '{"SAPID":"990001"}'
curl -s -X POST $H/AlarmStatusRequest -H "$K" -H 'Content-Type: application/json' -d '{"MachineFilter":{"Code":"SBTEST01"},"AlarmFilter":{"MinSeverity":500}}'

_A/_B picks the Left/Right receiver, _LT makes it a leak-test move (MoveOut then returns the six LT* rows instead of the 16 cycle rows). Trip an alarm without a key via the Flow Debugger (CLI flow-debug run-value-change --instance-id 9 --attribute VacuumPressure --value 12, clear with 0.5), or from the Central UI Debug View. The gate needs no key — it drives the methods through flow-debug run-method.

Known semantics (not bugs)

  • Computed alarms are always Acknowledged → StatusCode = "Triggered.Acked", AckDT = null, and AlarmFilter.IncludeAcked = false returns nothing for this machine (it only bites on native mirrors).
  • Description falls back to the alarm name (computed alarms carry no message/type text).
  • The alarm methods accept an exact BTDB SAPID first, then its leading digits (so 990001_A works), and strip a trailing A/B from the BTDB Code to find the instance (MesMoveOut's convention); HierarchicalName keeps the BTDB Code, as the legacy service did.
  • A machine without an AlarmStatus script (e.g. CvdReactor's Z28061) answers Alarm status is not supported on machine '<code>'.

Rollback

Upgrade: stop Site, stop Central; RESTORE DATABASE [ScadaBridge] FROM DISK = 'E:\ApiInstall\ScadaBridge-backups\ScadaBridge_pre-fe89363b_20260928T095825.bak' WITH REPLACE; rename app → app.failed, app.bak.20260928T095846 → app, same for cli; start Central, then Site. (The migrations are additive, so 2b9a7c3c would also run against the 57-migration DB — the restore just makes it exact.) Test machines: instance delete 9/10, template delete 30/29/28, restore the stub body of method 9 (return new { WasSuccessful = true, Alarms = Array.Empty<Object>() };) and delete method 12; DELETE FROM dbo.Machine WHERE MachineID IN (203, 204) AND LastUpdateBy = N'scadabridge-test-seed'.

Staging left on the box under E:\psrem-stage\fe89363b\ (zip, unpacked tree, migration SQL, prep + swap scripts, .reg exports). The _appsettings-keep-20260928T095846 copy and *.bak.20260928T095846 directories can go once the build has soaked.


Second window, same day — fe89363b → b3021e7c (script file access + recipe-parsing receiver)

Build: main @ b3021e7c (ProductVersion 1.0.0+b3021e7c5ff68ba20abc510f644045c7dbbccd86; zip 125 585 217 B, SHA-256 5b7d7c68…2d66df). Delta: the read-only, allow-listed Files script global (design docs/plans/2026-09-28-script-file-access-design.md) and the TestMesMachine Delmia simulator now reading + parsing the downloaded recipe file. No migrations (57 before and after — asserted, nothing applied).

Config (the only change): appsettings.Site.json gained ScadaBridge:SiteRuntime:ScriptFileAccess:AllowedRoots = ["E:\\ApiInstall\\ScadaBridge\\recipes"] (+ a _comment_SiteRuntime), edited on the Mac and pushed over SMB before the window so the swap script's keep/restore carried it through (new SHA-256 3E7AD9A4…); original saved as E:\psrem-stage\b3021e7c\appsettings.Site.json.pre-b3021e7c. The three sample recipes (docker/recipes/SBTEST-R100.txt, SBTEST-R200.txt, SBTEST-R900-invalid.txt) were pushed to E:\ApiInstall\ScadaBridge\recipes\. Central's appsettings unchanged.

Window: prep 10:41:47 (COPY_ONLY backup ScadaBridge_pre-b3021e7c_20260928T104147.bak, 140 064 768 B, VERIFYONLY valid); swap 10:42:08 → site ready 10:42:57 (~50 s); the swap script asserted the recipes root (3 files) and logged site AllowedRoots = E:\ApiInstall\ScadaBridge\recipes; boot validation accepted it. Backups: app.bak.20260928T104207 / cli.bak.20260928T104207.

Re-seed + gate: seed-mes-test-machine.sh added the 14 recipe attributes to TestDelmiaReceiver and updated all nine scripts; verify-mes-test-machine.sh (with RECIPE_ROOT='E:\ApiInstall\ScadaBridge\recipes', RECIPE_OUTSIDE='E:\ApiInstall\ScadaBridge\app\appsettings.Site.json') PASS 31/31 — group A now proves accept (12 typed values loaded), re-download, invalid-line rejection with line numbers and nothing loaded, NotFound, and a file outside the root refused (OutsideAllowedRoots). Real HTTP with a key:

DelmiaRecipeDownload R100  -> {"Result":true,"ResultText":"Loaded 12 recipe values from SBTEST-R100.txt for WO-1001"}
DelmiaRecipeDownload R900  -> {"Result":false,"ResultText":"Recipe rejected: line 4: expected RecipeValueName,RecipeValue;
                                line 5: unknown recipe value 'MysterySetting'; line 6: PotTempSP expects Int32, got '980.5'"}

Recipe file format (RecipeValueName,RecipeValue per line; value = int / double (invariant .) / bool / text; # comments and blank lines ignored; everything after the first comma is the value; surrounding double quotes unwrapped). Loaded all-or-nothing into TestDelmiaReceiver's typed recipe tags (RecipeName, RecipeRevision, CoatingType, ReactorTempSP, PotTempSP, ArgonFlowSP, HydrogenFlowSP, ChlorineFlowSP, VacuumSP, TargetRuntime, PurgeEnabled, OperatorNote) plus RecipeValueCount / RecipeLoadedFile. To test your own file: drop it in E:\ApiInstall\ScadaBridge\recipes\ (SMB) and call DelmiaRecipeDownload with that full path.

Rollback: stop Site + Central, rename app → app.failed, app.bak.20260928T104207 → app (same for cli), restore appsettings.Site.json.pre-b3021e7c if the key must go (the old build ignores it), start Central then Site. No DB restore needed (no schema change).

Rig: docker/deploy.sh from b3021e7c (site-a both nodes mount docker/recipes read-only at /app/recipes, AllowedRoots ["/app/recipes"]); seeded on site-a; VERIFY_GROUPS=AD PASS 8/8.