Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
12 KiB
DelmiaRecipeDownload — ScadaBridge Inbound API spec (as deployed)
Captured: 2026-09-02 from the live prod management API on wonder-app-vd03 (method id 4,
API keys, the CvdReactor template's ProcessRecipeDownload script, the DelmiaReceiver template,
and the inbound audit log). QA (vq03/vq04) was NOT read — its management API requires real
LDAP credentials (prod runs with DisableLogin); re-capture QA with scadabridge api-method get --id …
once credentials are to hand. The definition is config-as-data in the central ConfigDb (not in git), so
this file is the durable record; the legacy predecessor is documented in
../former-api-specs/dnc/Delmia-Integration-API.md
(Surface B), and the client that calls it is the WWNotifier.exe drop-in
(DelmiaNotifier).
1. Endpoint
| Item | Value |
|---|---|
| Method / path | POST /api/DelmiaRecipeDownload |
| Auth | X-API-Key: sbk_<keyId>_<secret> or Authorization: Bearer sbk_<keyId>_<secret> (Authorization wins if both) |
| Content type | application/json, flat PascalCase object |
| Server timeout | 45 s (TimeoutSeconds on the method); the site-side handshake wait inside it is 25 s |
| Prod base URL | http://wonder-app-vd03.zmr.zimmer.com:8085 (single central node) |
| QA base URLs | http://10.220.158.2:8085 (vq03), http://10.220.158.3:8085 (vq04) — list both in the notifier's BaseUrls; a standby central relays to the active peer |
Unauthenticated / bad-key calls return 401 {"error":"Invalid or missing API key"} (audit kind
InboundAuthFailure). General Inbound API status semantics (413 body cap, 503 STANDBY_NODE /
ACTIVE_NODE_UNREACHABLE, unknown method) are in
Component-InboundAPI.md.
2. Request
Deployed ParameterDefinitions (JSON Schema, verbatim — note required is empty; the
script itself rejects a missing/blank MachineCode):
{"type":"object","properties":{
"MachineCode":{"type":"string"},
"DownloadPath":{"type":"string"},
"WorkOrderNumber":{"type":"string"},
"PartNumber":{"type":"string"},
"JobStepNumber":{"type":"string"},
"Username":{"type":"string"}},
"required":[]}
| Field | Notifier flag | Meaning | Where it lands on the site |
|---|---|---|---|
MachineCode |
-m |
Routing key — must equal an instance UniqueName exactly (trimmed). Prod today: Z28061, Z28062, Z28061Sim. |
Route.To(MachineCode) |
DownloadPath |
-d |
Path where the recipe/NC file was placed | DelmiaReceiver.DownloadPath |
WorkOrderNumber |
-w |
Work order | DelmiaReceiver.WorkOrderNumber |
PartNumber |
-p |
Part / item number | DelmiaReceiver.PartNumber |
JobStepNumber |
-s |
Job step / sequence op | DelmiaReceiver.JobStepNumber |
Username |
-u |
Operator (audit only) | DelmiaReceiver.Username |
Example (a real call taken from the prod audit log):
curl -s -X POST http://wonder-app-vd03.zmr.zimmer.com:8085/api/DelmiaRecipeDownload \
-H "X-API-Key: $SCADABRIDGE_API_KEY" -H "Content-Type: application/json" \
-d '{"MachineCode":"Z28061Sim","DownloadPath":"C:\\recipes\\test.nc","WorkOrderNumber":"WO-TEST-001","PartNumber":"PN-TEST","JobStepNumber":"0100","Username":"delmia-sim-test"}'
3. Response
Deployed ReturnDefinition: {"type":"object","properties":{"Result":{"type":"boolean"},"ResultText":{"type":"string"}},"required":["Result","ResultText"]}
HTTP 200 is returned for every business outcome — success is Result == true, never the status
code. ResultText values the deployed scripts can produce:
Result |
ResultText |
Produced by |
|---|---|---|
true |
echo of DelmiaReceiver.RecipeProcessResultText (often "") |
Galaxy object acknowledged the download |
false |
DelmiaReceiver.RecipeProcessResultText |
Galaxy object rejected the download (RecipeProcessResult false) |
false |
MachineCode is required |
central script, blank MachineCode |
false |
Receiver ready flag not set to true |
site script, ReadyFlag not true (or unreadable — see §7) |
false |
Timeout waiting for recipe to be processed |
site script, RecipeProcessedFlag not true within 25 s |
false |
RecipeDownload failed: <exception> |
either script — e.g. unknown instance, site unreachable, Script 'ProcessRecipeDownload' on instance 'X' failed: … |
4. Processing flow
Delmia ─► WWNotifier.exe ─► POST /api/DelmiaRecipeDownload (central, active node)
│ inbound method script (id 4): validate MachineCode
▼
Route.To(MachineCode).Call("ProcessRecipeDownload", {5 fields})
│ gRPC central→site, lands on the instance actor
▼
CvdReactor.ProcessRecipeDownload (site Call script) on the composed DelmiaReceiver child:
1. read ReadyFlag → must be true, else "Receiver ready flag not set to true"
2. write RecipeProcessedFlag = false
3. WriteBatchAndWaitAsync({DownloadPath, WorkOrderNumber, PartNumber, JobStepNumber, Username},
trigger RecipeDownloadFlag = true,
wait RecipeProcessedFlag == true, timeout 25 s)
4. read RecipeProcessResult / RecipeProcessResultText → {Result, ResultText}
Both scripts as deployed on prod (verbatim):
Central inbound method script (id 4, timeout 45 s)
try {
System.Func<string, object> P = k => Parameters.ContainsKey(k) ? Parameters[k] : null;
var code = (P("MachineCode") as string ?? "").Trim();
if (string.IsNullOrEmpty(code)) return new { Result = false, ResultText = "MachineCode is required" };
var suf = code.Substring(code.Length - 1);
return await Route.To(code).Call("ProcessRecipeDownload", new {
DownloadPath = P("DownloadPath"),
WorkOrderNumber = P("WorkOrderNumber"),
PartNumber = P("PartNumber"),
JobStepNumber = P("JobStepNumber"),
Username = P("Username"),
});
} catch (Exception ex) {
return new { Result = false, ResultText = "RecipeDownload failed: " + ex.Message };
}
Site script CvdReactor.ProcessRecipeDownload (template 1, script id 4, trigger call)
// Generic DELMIA recipe-download — performed DIRECTLY on the composed DelmiaReceiver child
// (mirrors how MesMoveIn writes the MESReceiver child) instead of delegating via CallScript.
// No side logic; the receiver's own DelmiaReceiver.Download method holds the same handshake.
var recv = Children["DelmiaRecv"];
System.Func<object,bool> IsTrue = v => { var s=v?.ToString(); return (v is bool b && b)||s=="1"||string.Equals(s,"true",System.StringComparison.OrdinalIgnoreCase); };
System.Func<string,object?> P = k => Parameters.ContainsKey(k) ? Parameters[k] : null;
if (!IsTrue(recv.Attributes["ReadyFlag"]))
return new { Result=false, ResultText="Receiver ready flag not set to true" };
try {
recv.Attributes["RecipeProcessedFlag"] = false;
var data = new System.Collections.Generic.Dictionary<string, object?> {
{ "DownloadPath", P("DownloadPath") },
{ "WorkOrderNumber", P("WorkOrderNumber") },
{ "PartNumber", P("PartNumber") },
{ "JobStepNumber", P("JobStepNumber") },
{ "Username", P("Username") },
};
if (!await recv.Attributes.WriteBatchAndWaitAsync(data, "RecipeDownloadFlag", true, "RecipeProcessedFlag", true, System.TimeSpan.FromSeconds(25)))
return new { Result=false, ResultText="Timeout waiting for recipe to be processed" };
return new {
Result = IsTrue(recv.Attributes["RecipeProcessResult"]),
ResultText = recv.Attributes["RecipeProcessResultText"]?.ToString() ?? ""
};
} catch (Exception ex) {
return new { Result=false, ResultText="RecipeDownload failed: " + ex.Message };
}
5. Galaxy-side contract — the DelmiaReceiver template (id 12; composed clone id 26)
All ten attributes are data-sourced over the site MxGateway connection, bound per instance to the
reactor-level Galaxy object (prod: DelmiaReceiver_038 on Z28061, _039 on Z28062, _037 on
Z28061Sim; Left_00x.DelmiaReceiver / Right_00x.DelmiaReceiver back the unused side compositions).
| Attribute | Type | Direction | Role in the handshake |
|---|---|---|---|
ReadyFlag |
Boolean | Galaxy → SB | Precondition; must be true or the call is refused |
RecipeDownloadFlag |
Boolean | SB → Galaxy | Trigger — set true after the five data fields are written |
RecipeProcessedFlag |
Boolean | both | SB clears to false first; Galaxy sets true when done (waited ≤ 25 s) |
RecipeProcessResult |
Boolean | Galaxy → SB | Outcome → Result |
RecipeProcessResultText |
String | Galaxy → SB | Outcome text → ResultText |
DownloadPath, WorkOrderNumber, PartNumber, JobStepNumber, Username |
String | SB → Galaxy | Payload, written as one batch before the trigger |
6. API keys scoped to this method (prod, names only — secrets are shown once at creation)
mes, mes api key, delmia-mes-receivers, TestKey, JOE_TEST_KEY — all enabled, all also
scoped to the MES methods (MesMoveIn, MesMoveOut, MesSampleData, SimpleAlarmStatusRequest,
most also IpsenMESMoveIn). delmia-mes-receivers is the one whose name says it is the Delmia
caller's key; the audit actor column shows which key served each call.
7. Observed behaviour and open issues (from the prod audit log, 2026-06-17 → 2026-06-26)
Nine calls total, all test traffic (actors delmia-mes-receivers, delmia-sim-test), none since
2026-06-26 — no production Delmia traffic has hit this method yet. Every 200 answered
Result=false: mostly Receiver ready flag not set to true, one
Script 'ProcessRecipeDownload' … The given key 'side' was not present from an earlier script
revision, and one MachineCode must end with A (Left) or B (Right) from a retired left/right variant
(the current script routes on the whole MachineCode; the leftover suf variable is unused).
- Likely broken since the 2026-06-27 rename — verify before go-live. The site script still
addresses the child as
Children["DelmiaRecv"], but theCvdReactorcomposition was renamedDelmiaRecv→DelmiaReceiverthat day (../deployment/deployment-records/2026-06-27-wonder-app-vd03.md§2b), so the flattened paths areDelmiaReceiver.*.ChildrenAccessorjust concatenates the name into a path andGetAttributeon an unknown path logs a warning and yields null, soIsTrue(null)is false and every call will answerReceiver ready flag not set to trueregardless of the real flag. No call has been made since the rename, so this is inferred from the code, not observed. Fix = change that one line toChildren["DelmiaReceiver"]on the template (scadabridge template script update) and redeploy the three CvdReactor instances; check QA's copy of the script for the same string. - Not idempotent. The design requires idempotency on
(MachineCode, DownloadPath, WorkOrderNumber)because the notifier retries on timeout/connect failure (at-least-once). The deployed script re-runs the full handshake on a duplicate, so a retry after a slow first attempt will trigger a second download on the Galaxy object. requiredis empty in the parameter schema; onlyMachineCodeis enforced, in script. Missing payload fields are written to the receiver as null/empty.- QA definitions unread (credentials) — assumed identical (same build lineage), not verified.