# DelmiaNotifier (`WWNotifier.exe`) A compact, self-contained Windows console app that **DELMIA Apriso shells out to** on each recipe / NC-program download to notify the plant that a recipe was placed at a path for a machine. It is the modern, drop-in replacement for the legacy `WWNotifier` (see [`../../docs/former-api-specs/dnc/Delmia-Integration-API.md`](../../docs/former-api-specs/dnc/Delmia-Integration-API.md), *Surface B*), repointed from the old Wonderware `/notify` receiver to the **ScadaBridge Inbound API** method `DelmiaRecipeDownload` (as-deployed spec: [`docs/api-specs/Delmia-Recipe-Download-API.md`](../../docs/api-specs/Delmia-Recipe-Download-API.md)). The command-line flags, the `YES`/`NO` stdout contract, and the exit-code semantics are **unchanged**, so Delmia's call site and output parsing need no changes — only the executable on disk and its `appsettings.json` are swapped. > The published assembly name is **`WWNotifier`** precisely so it is a literal file-for-file drop-in > for Delmia's existing call path. ## What it does Per invocation it POSTs the recipe-download notification to the configured ScadaBridge node(s) and maps the result back to the legacy contract: ``` DELMIA → WWNotifier.exe → POST {baseUrl}/api/DelmiaRecipeDownload (X-API-Key: ) ← { "Result": true/false, "ResultText": "..." } ← stdout: YES | NO\n · exit: 0 | -1 ``` ## Command-line flags | Short | Long | Required | → payload field | |---|---|---|---| | `-d` | `--downloadpath` | yes | `DownloadPath` | | `-m` | `--machine` | yes | `MachineCode` | | `-w` | `--workorder` | yes | `WorkOrderNumber` | | `-p` | `--partnumber` | yes | `PartNumber` | | `-s` | `--seqop` | no | `JobStepNumber` | | `-u` | `--username` | no | `Username` | Each flag takes one value (`-m Z28061`). A missing required flag → `NO` + reason, exit `-1`, **no** HTTP attempt. ``` WWNotifier.exe -m Z28061 -d "C:\recipes\job.nc" -w WO12345 -p PN-7788 -s 0100 -u operator1 ``` ## Output contract (drop-in parity) - **stdout** is reserved for the contract Delmia parses: - success → `YES` (exit `0`) - failure → `NO` on the first line, then a human-readable reason on the next line (exit `-1`) - **exit code**: `0` on success, `-1` on failure. (On POSIX shells `-1` surfaces as `255`; on Windows `%ERRORLEVEL%` is `-1`.) - All diagnostics (per-URL attempt, status code, which URL answered, exceptions) go to **stderr** and, if `LogPath` is set, an appended log file — never to stdout. ## Delivery semantics The notifier retries only when a request is known not to have executed: connection refusal, name-resolution failure, or the server's explicit standby rejection. A connection reset after sending or a response timeout returns `OUTCOME_UNKNOWN` without replay. Operators must verify an uncertain result before manually retrying. Idempotent method implementations remain useful because callers can deliberately retry, but the notifier does not blindly repeat ambiguous sends. ## Configuration — `appsettings.json` Placed next to the exe (copied to the output on build/publish): ```json { "ScadaBridge": { "BaseUrls": "http://host-a:8085,http://host-b:8085", "TimeoutSeconds": 30, "LogPath": "logs/delmia-notifier.log", "ApiKey": "" } } ``` - **`BaseUrls`** — comma-separated failover list of **base URLs only**; the app appends `/api/DelmiaRecipeDownload`. URLs are tried in order after proven DNS/connection refusal or explicit standby rejection. Other HTTP responses are final; ambiguous transport errors return `NO` + `OUTCOME_UNKNOWN` without trying another URL. - **`TimeoutSeconds`** — per-attempt HTTP timeout (default `30`). - **`LogPath`** — optional diagnostic log file, relative to the exe. Omit to log to stderr only. ## Secret — `SCADABRIDGE_API_KEY` (or `ScadaBridge:ApiKey`) The inbound API key is sent as `X-API-Key`. It is resolved from **`SCADABRIDGE_API_KEY`** first (recommended for production — the key never touches disk), falling back to **`ScadaBridge:ApiKey`** in `appsettings.json` when the variable is unset or blank (a blank file value counts as unset too; both values are trimmed). The diagnostic log records which source supplied the key — never the key itself — so a stale environment variable silently shadowing an edited file is visible. If neither is set → `NO` + `API key not configured (SCADABRIDGE_API_KEY or ScadaBridge:ApiKey)`, exit `-1`, no HTTP attempt. Set the variable on the machine/account that DELMIA runs under (e.g. a system/user environment variable, or the service account's environment). If you use the file instead, restrict its ACL to the account DELMIA runs under — the key is stored in plain text there. Fill `ApiKey` in **only on the deployed copy**: the repo's `appsettings.json` is tracked and copied to the build output, so a key pasted there to test locally is one `git add` away from being committed. It must stay `""` in source control. ## Building & publishing (Native AOT, `win-x64`) The runtime artifact is a self-contained, single-file native exe. **Native AOT does not cross-compile from macOS/Linux** — publish on Windows with the MSVC build tools (Desktop C++ workload) installed: ```powershell # On Windows: dotnet publish src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier -c Release -r win-x64 # → bin/Release/net10.0/win-x64/publish/WWNotifier.exe (+ appsettings.json) ``` Copy `WWNotifier.exe` **and** `appsettings.json` to the Delmia host, set `SCADABRIDGE_API_KEY` (or fill in `ScadaBridge:ApiKey` on that copy), and point `BaseUrls` at the central node(s). > Managed `dotnet build` / `dotnet test` run cross-platform and are the development/CI gate; only the > final native publish requires Windows. ## Manual smoke test After deploying (or against a reachable test cluster such as `wonder-app-vd03`): ```powershell $env:SCADABRIDGE_API_KEY = "sbk_..." .\WWNotifier.exe -m Z28061Sim -d "C:\recipes\test.nc" -w WO-TEST -p PN-TEST -s 0100 -u smoketest # expect: YES (exit 0) — or NO + reason (exit -1) with details on stderr / in the log file ``` This mirrors the earlier `curl` verification of `POST /api/DelmiaRecipeDownload` with the `X-API-Key` header. ### Docker rig recipe (no Galaxy needed) The local rig (`docker/`) carries an in-memory stand-in for a vd03-style machine, so the whole path — notifier → inbound method → site `Call` script → simulated `DelmiaReceiver` handshake — runs on a laptop, including a 2-node central failover. Verified 2026-09-02 (`docs/api-specs/Delmia-Recipe-Download-API.md` §8): ```bash bash docker/seed-delmia-sim.sh # SimMachine/DelmiaReceiverSim templates + DelmiaSim01 on site-a, deployed bash docker/seed-delmia-api.sh # DelmiaRecipeDownload method + scoped key; prints DELMIA_API_KEY=sbk_... ONCE export DELMIA_API_KEY=sbk_... bash docker/sync-inbound-api-keys.sh # keys are a per-node store: mirror central-a -> central-b (register row 108) bash docker/verify-delmia-notifier.sh # runs the managed build from a scratch copy; the key goes in THAT copy's appsettings.json ``` | Case | Setup | Expected | |------|-------|----------| | A | active node first in `BaseUrls` | `YES` | | B | standby first (standby relays to the active) | `YES` | | C | bad key | `NO` + `HTTP 401` (an answer is final — no failover) | | D | active `docker kill`ed, attempt inside the takeover window | historical baseline: `NO` + `HTTP 503`; the updated client retries explicit standby refusals and still fails if no node is active | | E | survivor `/health/active` = 200 (~27 s) | `YES` (first URL refused → second URL) | | F | victim restarted and rejoined as standby | `YES` (victim relays) | | G | `DownloadPath` ending in `.bad` | `NO` + `Simulated reject: bad path or missing work order` | ### Safe failover The notifier advances to the next configured URL after a proven connection refusal/name-resolution failure, or HTTP 503 carrying the server's exact `X-ScadaBridge-Standby: 1` pre-execution marker. A generic 503 or business rejection is final. Both centrals may therefore be listed even when their internal standby-forwarding peer lists are absent. A timeout or other response loss is reported as `OUTCOME_UNKNOWN` with a failed result and no automatic replay: the first node may already have applied the download. Verify the operation before manually retrying. Configure trusted central endpoints; the standby marker is a server protocol contract, not permission to retry arbitrary HTTP errors.