docs(ipsen-movein): correct inbound DB helper to async QuerySingleAsync in design doc

The IpsenMES MoveIn design-doc pseudocode and helper-surface sketch used the
synchronous, read-only `Database.QuerySingle<T>`/`Query`. The shipped
InboundDatabaseHelper is async and write-capable: `await QuerySingleAsync<T>`,
`QueryAsync`, `ExecuteAsync` (InboundAPI-026/027).

Three inbound methods authored from this draft (IpsenMESMoveIn, MesMoveIn,
MesMoveOut) failed Roslyn compilation in production until corrected to
`await Database.QuerySingleAsync<...>(...)` (2026-06-25). Fix the pseudocode,
the helper-surface bullet, and the inline reference, and add a dated correction
note pointing at the authoritative Component-InboundAPI.md surface.
This commit is contained in:
Joseph Doherty
2026-06-25 14:11:25 -04:00
parent 1f261263b2
commit 66bbbb7a31
@@ -74,16 +74,26 @@ receiver is ready to accept a MoveIn.
## 4. Components ## 4. Components
### A. ScadaBridge source change — inbound read-only DB helper ### A. ScadaBridge source change — inbound read-only DB helper
- New `InboundDatabaseHelper` backed by `IDatabaseGateway`, surface: - New `InboundDatabaseHelper` backed by `IDatabaseGateway`, surface (async — every call is
`T? QuerySingle<T>(string conn, string sql, object? args = null)` and awaited and bound to the method deadline):
`IReadOnlyList<IReadOnlyDictionary<string, object?>> Query(string conn, string sql, object? args = null)`. `Task<T?> QuerySingleAsync<T>(string conn, string sql, object? args = null)`,
**No write methods.** `Task<IReadOnlyList<IReadOnlyDictionary<string, object?>>> QueryAsync(string conn, string sql, object? args = null)`,
and `Task<int> ExecuteAsync(string conn, string sql, object? args = null)` (writes are supported —
see the correction below).
- Add `Database` property to `InboundScriptContext`; construct it in `InboundScriptExecutor` - Add `Database` property to `InboundScriptContext`; construct it in `InboundScriptExecutor`
from a service scope (gateway is scoped). Add the helper's assembly to the script from a service scope (gateway is scoped). Add the helper's assembly to the script
`ScriptOptions.WithReferences(...)` and confirm `ForbiddenApiChecker` still passes (the script `ScriptOptions.WithReferences(...)` and confirm `ForbiddenApiChecker` still passes (the script
references only `Database.QuerySingle`, never `System.Data`). references only `Database.QuerySingleAsync`, never `System.Data`).
- Unit tests with a fake `IDatabaseGateway`. - Unit tests with a fake `IDatabaseGateway`.
> **Correction (2026-06-25).** The shipped `InboundDatabaseHelper` is **async** —
> `QuerySingleAsync<T>` / `QueryAsync` / `ExecuteAsync`, each `await`ed — not the synchronous
> `QuerySingle`/`Query` sketched in the original draft above, and **writes are supported**
> (InboundAPI-026) rather than read-only. Scripts MUST call `await Database.QuerySingleAsync<T>(...)`.
> Three deployed methods (`IpsenMESMoveIn`, `MesMoveIn`, `MesMoveOut`) were authored from the old
> `Database.QuerySingle` spelling and failed Roslyn compilation in production until corrected
> (2026-06-25). The authoritative surface lives in `docs/requirements/Component-InboundAPI.md`.
### B. Rewrite inbound `/api/IpsenMESMoveIn` script (data, via management API) ### B. Rewrite inbound `/api/IpsenMESMoveIn` script (data, via management API)
Pseudocode (always returns the 3-field shape; never throws out): Pseudocode (always returns the 3-field shape; never throws out):
```csharp ```csharp
@@ -96,7 +106,7 @@ try {
var side = suf == "A" ? "Left" : suf == "B" ? "Right" : null; var side = suf == "A" ? "Left" : suf == "B" ? "Right" : null;
if (side == null) return new { WasSuccessful = false, ErrorText = $"Unsupported side '{suf}'", BatchID = 0 }; if (side == null) return new { WasSuccessful = false, ErrorText = $"Unsupported side '{suf}'", BatchID = 0 };
var code = Database.QuerySingle<string>("BTDB", var code = await Database.QuerySingleAsync<string>("BTDB",
"SELECT TOP 1 Code FROM dbo.Machine WHERE SAPID=@s", new { s = sap }); "SELECT TOP 1 Code FROM dbo.Machine WHERE SAPID=@s", new { s = sap });
if (string.IsNullOrEmpty(code)) return new { WasSuccessful = false, ErrorText = $"No machine for SAP {sap}", BatchID = 0 }; if (string.IsNullOrEmpty(code)) return new { WasSuccessful = false, ErrorText = $"No machine for SAP {sap}", BatchID = 0 };