docs(delmia): document at-least-once failover semantics + DelmiaRecipeDownload idempotency requirement

Manual verification (task step 3) skipped: this worktree cannot reach a
live environment to fetch the deployed DelmiaRecipeDownload script via
`scadabridge api-method get`. Documents only the mandated contract per
PLAN-06 Task 23; verifying the deployed script against it is deferred to
whoever has live environment access.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-10 03:48:06 -04:00
parent 102f5b60ee
commit 153b82c880
3 changed files with 40 additions and 0 deletions
@@ -105,6 +105,30 @@ response came back). A node that responds at all is authoritative — its answer
> to the next node — failover is strictly for unreachable nodes. (Revisit only if operations want
> `5xx`/`503` to also fail over.)
## Delivery semantics: at-least-once
The connect-failure-only failover loop above is by design (no code change here) — but its
consequence is at-least-once delivery of the `DelmiaRecipeDownload` POST, not exactly-once. Two
cases both count as "no response" from the client's point of view, trigger failover to the next
URL, and produce a duplicate POST even though the first node may have already processed the
request:
1. **Connection reset after the server processed the POST** — the request reaches the site and is
applied, but the response never makes it back to the client (dropped connection, reset). The
client sees a connect failure (row 1 of the table above), rolls over, and re-POSTs to the next
`BaseUrls` entry.
2. **Slow response exceeding `TimeoutSeconds`** — the server is still processing (or has already
finished) when the client's `HttpClient.Timeout` fires. The client treats the timeout the same
as an unreachable node, rolls over, and re-POSTs.
**Requirement:** the deployed `DelmiaRecipeDownload` inbound method **MUST be idempotent** on the
natural key `(MachineCode, DownloadPath, WorkOrderNumber)`. A duplicate POST for the same key must
return the same `Result`/`ResultText` as the original call, not double-apply side effects (e.g. not
queue the recipe download twice). This is a contract on the inbound method's script, not on the
DelmiaNotifier client — the client cannot distinguish a "duplicate delivered" retry from a
"first delivery" attempt, so idempotency must live on the receiving side. See the timeout row in
the failover table above for the triggering condition.
## Error handling & logging
- **stdout is reserved** for the `YES`/`NO` contract. All diagnostics — per-URL attempt, status code,