# MES and Delmia-DNC integrations — API & MXAccess write specification Documents two existing Wonderware integrations hosted on the `ZimmerBiomet` Gitea org (`http://wonder-app-vd03.zmr.zimmer.com:3000`): | Integration | Repo | What it does | Who does the MXAccess write | |---|---|---|---| | **MES** | [`ZimmerBiomet/MESAPI`](http://wonder-app-vd03.zmr.zimmer.com:3000/ZimmerBiomet/MESAPI) (solution `WWSupport`) | REST API the Camstar MES calls to move-in / move-out work orders against a machine, and to read machine alarm status | **The service itself** (`MesNotifier`, in-repo) | | **Delmia DNC** | [`ZimmerBiomet/DelmiaIntegration`](http://wonder-app-vd03.zmr.zimmer.com:3000/ZimmerBiomet/DelmiaIntegration) | Pull an NC/recipe document from the DELMIA/Apriso (Intercim) DNC server and push the resulting recipe-download notification into Wonderware | **An external receiver service** at `wonder-app-vd01:9001/notify`; the actual flag handshake is implemented in the Galaxy `$DelmiaReceiver` object (`ProcessRecipe`/`Reset` scripts) — *not* in this repo | Both integrations talk to AVEVA System Platform ("Galaxy") through **MXAccess COM** (`ArchestrA.MxAccess.LMXProxyServerClass`) and use the same general pattern: > **Handshake pattern** — read a *ready* flag → write the data tags → set a *trigger* flag > → wait (bounded by a timeout) for a *complete* flag → read a *success* flag + *error text* > → return the result and unsubscribe. All facts below are taken verbatim from source at the repo `master` branch (cloned 2026-06-17) unless explicitly marked **(inferred)**. --- ## 1. MES integration — `MESAPI` / `WWSupport` ### 1.1 Topology & hosting ``` Camstar MES ──HTTPS/JSON──▶ WWSupport API (ServiceStack self-host) │ ├─ SQL Server "BT" (machine lookup by SAPID, alarm catalog) │ └─ MXAccess COM (LMXProxyServerClass, client "MesNotifier") ▼ Galaxy object {MachineCode}.MesReceiver.* (move-in / move-out tags) Galaxy object {MachineCode}.{AlarmName}.* (alarm attributes) ``` - **Framework:** ServiceStack, self-hosted via `AppSelfHostBase` (`AppHost : base("APIServer", typeof(MesServices).Assembly)`), .NET Framework, run as a Windows service. - **Listen URL (per environment, `App.config` → `HttpListener`):** - DEV `http://*:9501/` · QA `http://*:9500/` · PROD `http://*:9500/` - **Database (`App.config` → connection `BatchDB`, DB `BT`):** - DEV `wonder-sql-vd01.zmr.zimmer.com` · QA `wondersqlqa.zmr.zimmer.com` · PROD (same form). User `wonderapp`. - **Auth:** every operation is decorated `[Authenticate]` + `[RequiredRole("MESAPI")]` (`MesServices.cs`). `AppHost` registers an `AuthFeature` with two providers: `ApiKeyAuthProvider` and `LdapAuthProvider`. - Unauthenticated → **401**; authenticated without the `MESAPI` role → **403**. - **Serialization:** `JsConfig.IncludeNullValues = true` (null fields ARE emitted in JSON). `PostmanFeature` + `OpenApiFeature` (Swagger) are enabled. - **MES counterpart object:** the live Galaxy attribute listing for the receiver object is in [`mesrec.md`](mesrec.md) (`$MESReceiver` template). Note the API binds tags under the contained name `MesReceiver` (i.e. `{MachineCode}.MesReceiver.`). ### 1.2 Endpoints (inputs / outputs) Routes come from `[Route(...)]` on the request DTOs; all are **POST**, JSON in / JSON out, handled by `MesServices.Any(...)` which resolves a per-request `MesNotifier`. #### `POST /mes/movein` → `MoveInResponse` Request `MoveInRequest`: | Field | Type | Notes | |---|---|---| | `SAPID` | string | machine key; looked up in `BT.Machine` to get `Machine.Code` | | `OperatorName` | string | | | `JobSequenceNumber` | string | | | `WorkOrders` | `List` | each = `{ WorkOrderNumber: string, PartNumber: string }` | Response `MoveInResponse`: `WasSuccessful` (bool), `ErrorText` (string), `BatchID` (int?, only set if machine returns non-zero). #### `POST /mes/moveout` → `MoveOutResponse` Request `MoveOutRequest`: `SAPID` (string), `OperatorName` (string), `WorkOrders` (`List`). *(Move-out has no `JobSequenceNumber`.)* Response `MoveOutResponse`: identical shape to `MoveInResponse` (`WasSuccessful`, `ErrorText`, `BatchID`). #### `POST /mes/alarmstatus` → `AlarmStatusResponse` Request `AlarmStatusRequest`: - `MachineFilter` = `{ MachineID: int?, SAPID: string, ZTag: string, Code: string }` (any one identifies the machine) - `AlarmFilter` = `{ NameFilter: string, MinSeverity: int?, MaxSeverity: int?, IncludeTriggered: bool=true, IncludeAcked: bool=true, FlaggedOnly: bool=false }` #### `POST /mes/simplealarmstatus` → `AlarmStatusResponse` Request `SimpleAlarmStatusRequest`: `SAPID` (string). Internally loads only alarms with `FlaggedForMES == true` for that machine. Response `AlarmStatusResponse` (both alarm endpoints): `WasSuccessful` (bool), `ErrorText` (string), `Alarms` (`List`). `AlarmInfo`: `Name` (string), `HierarchicalName` (string, `{Code}.{AlarmName}`), `Description` (string), `IsFlaggedForMES` (bool), `Severity` (int), `StatusCode` (string — `"Triggered"` or `"Triggered.Acked"`), `TriggeredDT` (DateTime), `AckDT` (DateTime?), `AckComment` (string). ### 1.3 MXAccess connection model `MesNotifier` owns one MXAccess proxy for the request: ```csharp _lmxProxy = new ArchestrA.MxAccess.LMXProxyServerClass(); _lmxHandle = _lmxProxy.Register("MesNotifier"); _lmxProxy.OnDataChange += ...; // value updates resolve pending read/OnValue tasks _lmxProxy.OnWriteComplete += ...; // write acks resolve pending write tasks ``` Tags are added with `AddItem` + `AdviseSupervisory` (subscribe), updated via `OnDataChange`, and removed with `UnAdvise` + `RemoveItem` on cleanup. Reads/writes are wrapped as `Task` that complete on the corresponding callback or **fail (`false`) on cancellation/timeout**. A read is considered valid only if MXAccess **quality == 192** ("good"). **Target selection:** request `SAPID` → `db.Single(x => x.SAPID == SAPID)` → `Machine.Code` becomes the tag prefix. Move tags live under `{Code}.MesReceiver.*`; alarm tags under `{Code}.{MachineAlarm.Name}.*` (alarm catalog from `db.Select(...)`). ### 1.4 Tag mappings **Move-in** (`MesMoveInTagset`, all `{Code}.MesReceiver.`): | Tag | Type | Dir | Role | Source field | |---|---|---|---|---| | `MoveInReadyFlag` | bool | read | gate — must be `true` before writing | — | | `MoveInFlag` | bool | **write** | **trigger** — set `true` last | — | | `MoveInCompleteFlag` | bool | read | completion — handshake waits on this | — | | `MoveInSuccessfulFlag` | bool | read | result | → `response.WasSuccessful` | | `MoveInErrorText` | string | read | result | → `response.ErrorText` | | `MoveInBatchID` | int | read | result | → `response.BatchID` (if ≠ 0) | | `MoveInOperatorName` | string | write | data | `request.OperatorName` | | `MoveInJobSequenceNumber` | string | write | data | `request.JobSequenceNumber` | | `MoveInNumberWorkOrders` | int | write | data | `request.WorkOrders.Count` | | `MoveInWorkOrderNumbers[]` | string[] | write | data (fixed length 50) | `WorkOrders.Select(w => w.WorkOrderNumber)` | | `MoveInPartNumbers[]` | string[] | write | data (fixed length 50) | `WorkOrders.Select(w => w.PartNumber)` | **Move-out** (`MesMoveOutTagset`): identical set with `MoveOut` prefix, **minus** `JobSequenceNumber` (`MoveOutReadyFlag`, `MoveOutFlag`, `MoveOutCompleteFlag`, `MoveOutSuccessfulFlag`, `MoveOutErrorText`, `MoveOutBatchID`, `MoveOutOperatorName`, `MoveOutNumberWorkOrders`, `MoveOutWorkOrderNumbers[]`, `MoveOutPartNumbers[]`). **Alarms** (`AlarmTagset`, all `{Code}.{AlarmName}.`): `Quality` (int), `InAlarm` (bool), `TimeAlarmOn` (DateTime), `DescAttrName` (string), `Acked` (bool), `TimeAlarmAcked` (DateTime?), `AckMsg` (string). ### 1.5 The handshake — `MesNotifier.MoveIn` (move-out is identical with `MoveOut*` tags) Whole operation is bounded by **`new CancellationTokenSource(30000)` = 30 s**. 1. **Look up machine** by `SAPID`. Not found → `WasSuccessful=false`, `ErrorText="Failed to find machine with SAPID '{SAPID}'"`, return. 2. **Subscribe** to every move-in tag (`Advise(t, cts)`), `await Task.WhenAll(...)`. Any subscription that fails / quality ≠ 192 → `ErrorText="Failed to connect to machine"`. 3. **Check ready flag:** `if (!MoveInReadyFlag.Value)` → `ErrorText="Machine move in ready flag not set to true"`, stop. 4. **Arm completion watch:** `Task flagTask = MoveInCompleteFlag.OnValue(true, cts);` (completes when the flag goes `true`, or `false` on the 30 s timeout). 5. **Write data + trigger (in parallel, trigger last):** `MoveInOperatorName`, `MoveInJobSequenceNumber`, `MoveInNumberWorkOrders`, `MoveInPartNumbers[]` (padded to 50), `MoveInWorkOrderNumbers[]` (padded to 50), then `MoveInFlag = true`. `await Task.WhenAll(writeTasks)`; any write `!= true` → `ErrorText="Failed to write move in information to machine"`. 6. **Wait for completion:** `await Task.WhenAll(flagTask)`. - `flagTask.Result == true` → read results: `WasSuccessful = MoveInSuccessfulFlag.Value`, `ErrorText = MoveInErrorText.Value`, `BatchID = MoveInBatchID.Value` (if ≠ 0). - `flagTask.Result == false` (timed out) → `WasSuccessful=false`, `ErrorText="Timeout waiting for move in information to be processed"`. 7. **Cleanup:** `Tags.ForEach(Unadvise)` and return. ```csharp using (var cts = new CancellationTokenSource(30000)) { // 30 s budget ... if (!moveInTagset.MoveInReadyFlag.Value) { /* not-ready error */ } var flagTask = moveInTagset.MoveInCompleteFlag.OnValue(true, cts); // arm completion watch var writeTasks = new List> { Write(moveInTagset.MoveInOperatorName.Handle, request.OperatorName, cts), Write(moveInTagset.MoveInJobSequenceNumber.Handle, request.JobSequenceNumber, cts), Write(moveInTagset.MoveInNumberWorkOrders.Handle, request.WorkOrders.Count, cts), Write(moveInTagset.MoveInPartNumbers.Handle, request.WorkOrders.Select(wo => wo.PartNumber).ToFixedLength(50), cts), Write(moveInTagset.MoveInWorkOrderNumbers.Handle, request.WorkOrders.Select(wo => wo.WorkOrderNumber).ToFixedLength(50), cts), Write(moveInTagset.MoveInFlag.Handle, true, cts) // TRIGGER — set last }; await Task.WhenAll(writeTasks); await Task.WhenAll(flagTask); if (flagTask.Result) { response.WasSuccessful = moveInTagset.MoveInSuccessfulFlag.Value; response.ErrorText = moveInTagset.MoveInErrorText.Value; if (moveInTagset.MoveInBatchID.Value != 0) response.BatchID = moveInTagset.MoveInBatchID.Value; } else { response.WasSuccessful = false; response.ErrorText = "Timeout waiting for move in information to be processed"; } moveInTagset.Tags.ForEach(Unadvise); } ``` > There is **no busy-poll loop**: completion is event-driven via the MXAccess `OnDataChange` > callback; the 30 s `CancellationTokenSource` is the only timeout. ### 1.6 Alarm-status path 1. Resolve machine from `MachineFilter` (`SAPID` / `Code` / `ZTag` / `MachineID`) — wrong/missing → error. 2. Load `MachineAlarm` rows for the machine; apply filters (`FlaggedOnly`, `MinSeverity`, `MaxSeverity`, case-insensitive `NameFilter.Contains`). *(`IncludeTriggered` is read but not used in the filter.)* 3. Subscribe + read each alarm's `Quality` and `InAlarm` (30 s budget). Bad quality / read failure → `ErrorText="Failed to read machine alarm status"`. 4. For alarms where `InAlarm == true`, additionally read `TimeAlarmOn`, `DescAttrName`, `Acked`, `TimeAlarmAcked`, `AckMsg`. 5. Build `AlarmInfo` per triggered alarm; `StatusCode = "Triggered.Acked"` if acked else `"Triggered"`. If `AlarmFilter.IncludeAcked == false`, acked alarms are skipped. 6. Unsubscribe; on failure `Alarms` is cleared. ### 1.7 Outputs / error handling (MES) - **Transport status is always 200** for handled responses — success/failure is carried by the body's `WasSuccessful` flag + `ErrorText`. (401/403 only from the auth layer.) - Success: `{ "WasSuccessful": true, "ErrorText": null, "BatchID": }`. - Failure/timeout: `{ "WasSuccessful": false, "ErrorText": "", "BatchID": null }`. - Distinct `ErrorText` values: machine-not-found, "Failed to connect to machine", "…ready flag not set to true", "Failed to write … to machine", "Timeout waiting for … to be processed", "Failed to read machine alarm status". --- ## 2. Delmia-DNC integration — `DelmiaIntegration` (+ Galaxy `$DelmiaReceiver`) ### 2.1 Topology — three hops ``` Operator (DelmiaIntegration.exe WinForms) │ ① DelmiaClient ──HTTP POST (form-url-encoded)──▶ DELMIA/Apriso DNC "Downloader.asmx" │ (e.g. http://dnc-app-vd01.zmr.zimmer.com/IntercimService/Downloader.asmx) │ ◀── XML (SearchResults / DownloadResult, ns http://intercim.com/ruleset) ── │ ② recipe file written to disk; WWNotifier.exe launched with CLI args ▼ WWNotifier.exe ──HTTP POST (JSON RecipeDownload)──▶ WW receiver service (http://wonder-app-vd01:9001/notify) ◀── JSON RecipeDownloadResult ── │ ③ MXAccess COM write ▼ Galaxy object {machine}.$DelmiaReceiver.* (recipe tags + flags) + ArchestrA scripts ProcessRecipe / Reset ``` **Assemblies in the repo:** `DelmiaContracts` (XML DTO library), `DelmiaIntegration` (`DelmiaClient` + WinForms UI), `WWNotifier.exe` (console notifier), plus test harnesses (`AdminTestUtil`, `DownloadTestUtil`, `TestUI`). > **Scope note.** Hops ① and ② are fully in this repo. Hop ③ — the service at `:9001/notify` > that actually performs the MXAccess write — is **not** in the `ZimmerBiomet` Gitea org; only the > JSON contract (below) and the Galaxy-side `$DelmiaReceiver` object (scripts + attributes, exported > under `AA_EXPORT/`) are available. `WWNotificationSystem` *also* uses MXAccess but is an unrelated > tag→email alerting service (port `:9876`, client name `WWNotifierMonitor`) — **not** the recipe receiver. ### 2.2 DNC server interface (`DelmiaClient`, hop ①) - **Transport:** `HttpClient.PostAsync` with `FormUrlEncodedContent`; response is XML deserialized with `XmlSerializer`. Base URL is `DelmiaClient.URL`; per-call `Timeout` default **30 s**. Action is appended to the base URL (`URL.TrimEnd('/') + "/"`). - **Base URL (from `AdminTestUtil` `DefaultURL`):** `http://dnc-app-vd01.zmr.zimmer.com/IntercimService/Downloader.asmx`. - On any exception the client returns a result object with the error in `ErrorMessage` / `TransferSuccessful=false` (it does not throw to the caller). | Method (sync + `…Async`) | POST action | Form fields | Returns | |---|---|---|---| | `Search` | `/Search` | `username, machineID, partNumber, operationNumber` | `SearchResults` | | `RequestProvenDocument` | `/RequestProvenDocument` | `username, machineID, partNumber, operationNumber, workOrderNumber` | `DownloadResult` | | `RequestDocument` | `/RequestDocument` | `username, machineID, partNumber, operationNumber, workOrderNumber, documentKey` | `DownloadResult` | DTO field lists (`DelmiaContracts`, XML namespace `http://intercim.com/ruleset`): - **`SearchResults`**: `Results` (`List`), `ErrorMessage` (string). - **`SearchResult`**: `ShopOrderKey` (int), `ShopOrderID` (string), `ShopOrderStatus` (string), `ShopOrderOperKey` (int), `ShopOrderOperID` (string), `ShopOrderOperStatus` (string), `DocumentKey` (int), `DocumentObjectID` (int, with `…Specified` flag), `DocumentName` (string), `DocumentRev` (string), `DocumentStatus` (string), `DocumentURL` (string), `PartID` (string), `PartRev` (string). - **`DownloadResult`**: `UserKey` (int), `UserName` (string), `UserSite` (string), `MachineKey` (int), `MachineID` (string), `MachineSite` (string), `WorkOrderNumber` (string), `ShopOrderKey` (int), `ShopOrderID` (string), `ShopOrderStatus` (string), `ShopOrderOperKey` (int), `ShopOrderOperID` (string), `ShopOrderOperStatus` (string), `DocumentKey` (int), `DocumentName` (string), `DocumentRev` (string), `DocumentStatus` (string), `PartID` (string), `PartRev` (string), **`TransferSuccessful` (bool)**, **`ErrorMessage` (string)**. - **`MachineInfo`** (contract present; not called by current code): `MachineKey` (int), `MachineID` (string), `MachineName` (string), `DownloadPath` (string), `MachineDescription` (string), `MachineSite` (string), `MachineStatus` (string). - **`UserInfo`**: `UserKey` (int), `UserName` (string), `UserSite` (string), `IsActive` (bool). **Recipe file:** the downloaded document is a key/value recipe file parsed by `DelmiaIntegration/Models/RecipeSet.cs` (`KEY,VALUE` lines; typed accessors `GetString/GetInt/GetBool/GetFloat/GetDouble/...`). It is written to disk; its path is what gets handed to `WWNotifier` (`--downloadpath`). ### 2.3 WWNotifier (hop ②) — invocation & handoff contract `WWNotifier.exe` (uses `CommandLineParser`); CLI options (`CommandLineOptions`): | Short | Long | Required | Field | |---|---|---|---| | `-d` | `--downloadpath` | yes | `DownloadPath` (recipe file path) | | `-m` | `--machine` | yes | `MachineCode` | | `-w` | `--workorder` | yes | `WorkOrderNumber` | | `-p` | `--partnumber` | yes | `PartNumber` | | `-s` | `--seqop` | no | `JobStepNumber` | | `-u` | `--username` | no | `Username` | Config (`WWNotifier/App.config`): `NotifyURL = http://wonder-app-vd01.zmr.zimmer.com:9001/notify` (comma-separated list allowed — tried in order until one succeeds), `NotifyTimeout = 30` (seconds, applied as the global Flurl HTTP timeout). Handoff (Flurl): `url.PostJsonAsync(recipeDownload).ReceiveJson()`. - **Request body `RecipeDownload`** (JSON): `MachineCode`, `DownloadPath`, `WorkOrderNumber`, `PartNumber`, `JobStepNumber`, `Username` (all string). - **Response body `RecipeDownloadResult`** (JSON): `Result` (bool), `ResultText` (string). - **Outputs:** prints `YES` and exit code `0` on success; prints `NO` + a message and sets exit code `-1` on failure (parse error, missing `NotifyURL`/`NotifyTimeout`, `Result==false`, or HTTP exception). *(Caveat: on a caught exception it logs `error.InnerException.Message`, which throws a NRE when there is no inner exception — so bare transport errors surface only as a generic failure.)* ### 2.4 MXAccess write — Galaxy `$DelmiaReceiver` object (hop ③) The receiver service maps `RecipeDownload` fields onto the `$DelmiaReceiver` object instance selected by `MachineCode`. Object attributes (from `AA_EXPORT/.../$DelmiaReceiver` export) and their roles: | Attribute | Type | Role | Maps from | |---|---|---|---| | `ReadyFlag` | Boolean | gate — receiver expects `true` before writing | — | | `DownloadPath` | String | data | `RecipeDownload.DownloadPath` | | `WorkOrderNumber` | String | data | `RecipeDownload.WorkOrderNumber` | | `PartNumber` | String | data | `RecipeDownload.PartNumber` | | `JobStepNumber` | String | data | `RecipeDownload.JobStepNumber` | | `Username` | String | data | `RecipeDownload.Username` | | `RecipeDownloadFlag` | Boolean | **trigger** — set `true` to start processing | — | | `RecipeProcessedFlag` | Boolean | completion — handshake waits on this | — | | `RecipeProcessResult` | Boolean | result | → `RecipeDownloadResult.Result` | | `RecipeProcessResultText` | String | result | → `RecipeDownloadResult.ResultText` | *(`MachineCode` selects which receiver instance; it is not itself a written attribute.)* **Galaxy-side handshake (authoritative — ArchestrA scripts on `$DelmiaReceiver`):** `ProcessRecipe` (runs when `RecipeDownloadFlag` is set): ``` Me.RecipeDownloadFlag = false; ' clear trigger Me.ReadyFlag = false; ' clear ready try Me.RecipeProcessResult = true; Me.RecipeProcessResultText = "Success"; catch Me.RecipeProcessResult = false; Me.RecipeProcessResultText = "Failed to read recipe file"; endtry; Me.RecipeProcessedFlag = true; ' signal completion ``` `Reset` (clears the slot for the next download): ``` Me.RecipeDownloadFlag = false; Me.RecipeProcessedFlag = false; Me.RecipeProcessResult = false; Me.RecipeProcessResultText = ""; Me.DownloadPath = ""; Me.WorkOrderNumber = ""; Me.PartNumber = ""; Me.JobStepNumber = ""; Me.Username = ""; ``` **Receiver-side sequence (inferred** — mirrors the MES handshake and is driven by the flags above; the C# source is not in the repo**):** 1. resolve the `$DelmiaReceiver` instance from `MachineCode`; 2. (optionally) verify `ReadyFlag == true`; 3. write `DownloadPath`, `WorkOrderNumber`, `PartNumber`, `JobStepNumber`, `Username`; 4. set `RecipeDownloadFlag = true` (trigger) → Galaxy `ProcessRecipe` fires; 5. wait for `RecipeProcessedFlag == true`, bounded by the request timeout; 6. read `RecipeProcessResult` → `Result`, `RecipeProcessResultText` → `ResultText`; return the `RecipeDownloadResult` JSON; 7. `Reset` the object. ### 2.5 Outputs / error handling (Delmia) - **DNC server call:** failures are swallowed into the returned DTO — `SearchResults.ErrorMessage`, or `DownloadResult.TransferSuccessful=false` + `ErrorMessage="Failed to call Delmia web service at ''."`. - **Notify handoff:** `RecipeDownloadResult.Result` (bool) + `ResultText` (string); `WWNotifier` exit code `0` (`YES`) / `-1` (`NO`). - **Galaxy script:** `RecipeProcessResultText` is `"Success"` or `"Failed to read recipe file"`. --- ## 3. Side-by-side summary | | MES (`MESAPI`) | Delmia DNC (`DelmiaIntegration`) | |---|---|---| | Caller | Camstar MES (HTTP/JSON) | Operator UI → DELMIA DNC server, then WWNotifier | | API style | ServiceStack REST, `POST /mes/*` | DNC = form-url-encoded → XML; notify = JSON POST | | Who writes MXAccess | the service (`MesNotifier`, in-repo) | external `:9001/notify` receiver (source not in repo) | | MXAccess client | `LMXProxyServerClass`, register `"MesNotifier"` | `LMXProxyServerClass` (receiver), Galaxy `$DelmiaReceiver` scripts | | Target object | `{MachineCode}.MesReceiver.*` | `{MachineCode}.$DelmiaReceiver` instance | | Ready / trigger / complete | `MoveInReadyFlag` / `MoveInFlag` / `MoveInCompleteFlag` | `ReadyFlag` / `RecipeDownloadFlag` / `RecipeProcessedFlag` | | Result / error | `MoveInSuccessfulFlag` / `MoveInErrorText` (+`MoveInBatchID`) | `RecipeProcessResult` / `RecipeProcessResultText` | | Timeout | 30 s (`CancellationTokenSource(30000)`), event-driven | 30 s HTTP (`NotifyTimeout`); Galaxy wait at receiver | --- ## 4. Sources & open gaps **Repos (Gitea `ZimmerBiomet`, `master`):** - MES: `MESAPI` — `APIServer.ServiceInterface/MesServices.cs`, `MesNotifier.cs`, `Mes{MoveIn,MoveOut}Tagset.cs`, `AlarmTagset.cs`, `Tag.cs`/`OnValueTask.cs`; `APIServer.ServiceModel/Types/*`; `APIServer/AppHost.cs`, `App.config`. - Delmia: `DelmiaIntegration` — `DelmiaIntegration/DelmiaClient.cs`, `Models/RecipeSet.cs`; `WWNotifier/Program.cs`, `CommandLineOptions.cs`, `Models/RecipeDownload(Result).cs`, `App.config`; `DelmiaContracts/*`. **Galaxy export (`~/Desktop/AA_EXPORT/EXTRACTED/$DelmiaReceiver`):** `scripts/ProcessRecipe.txt`, `scripts/Reset.txt`, `$DelmiaReceiver.top_level_attributes.csv`. MES receiver object attributes: [`mesrec.md`](mesrec.md). **Open gaps / to verify against the box:** 1. The Delmia recipe **`/notify` receiver service** source (the actual MXAccess writer at `wonder-app-vd01:9001`) was not found in the Gitea org — §2.4 receiver steps are inferred from the contract + Galaxy scripts. 2. MES tag prefix in code is `{Code}.MesReceiver.*`, while the live probe in `mesrec.md` shows a top-level `MESReceiver_002` instance — confirm the exact contained-name/instance convention on the live Galaxy. 3. PROD `HttpListener`/DB host values should be read from the deployed `App.config`, not assumed.