# Known issue — inbound API method compile errors are not client-visible; no on-demand validation (2026-06-25 session) **Status:** OPEN · **Found:** 2026-06-25 · **Context:** live ops session on `wonder-app-vd03` — three deployed inbound methods (`IpsenMESMoveIn`, `MesMoveIn`, `MesMoveOut`) returned `Script compilation failed for this method` after being authored from a design doc that used the wrong DB-helper name (`Database.QuerySingle` instead of the shipped async `Database.QuerySingleAsync`). Diagnosing the *actual* Roslyn error required an SSH dive into the central log; nothing in the CLI or the Management/data-plane API surfaces it. **Components:** Inbound API (#14), CLI (#19), Management Service (#18) Issues are listed worst-first. Severities are author estimates. Neither item caused data loss — once the scripts were corrected via `UpdateApiMethod` they compiled and ran (verified with a live `MesMoveIn` test against the `Z28061Sim` instance: `{"WasSuccessful":true,"ErrorText":"","BatchID":0}`). Related: the runtime mechanics behind both items are captured in the recall notes `inbound-known-bad-method-cache` and `scadabridge-inbound-db-helper-querysingleasync`. The root-cause doc fix shipped in `66bbbb7a` / `33da8c79`. --- ## 1. The real inbound-script compile error is server-log-only; there is no `api-method validate` **Severity:** Medium · **Components:** Inbound API (#14), CLI (#19), Management Service (#18) **Symptom:** When an inbound method's script fails to compile, every caller of `POST /api/{method}` gets the same generic body — `Script compilation failed for this method` — with no diagnostic. The actual Roslyn error (e.g. `'InboundDatabaseHelper' does not contain a definition for 'QuerySingle'`) is written **only** to the central server log. There is no CLI command and no API verb to (a) retrieve the last compile error for a method, or (b) compile/validate a method's script on demand the way templates can be validated. **Reproduction (this session):** ```bash curl -s -X POST http://wonder-app-vd03.zmr.zimmer.com:8085/api/IpsenMESMoveIn \ -H "X-API-Key: " -H "Content-Type: application/json" -d '{ ...MoveIn... }' # -> {"WasSuccessful":false,"ErrorText":null,"...":"Script compilation failed for this method"} (no detail) ``` The only way to get the real cause was: ``` ssh -tt -p 2222 -i ~/.ssh/servecli_wonder dohertj2@wonder-app-vd03.zmr.zimmer.com # grep E:\ApiInstall\ScadaBridge\central\logs\scadabridge-central-.log for "script compilation failed" ``` **Root cause:** `InboundScriptExecutor` deliberately returns a non-leaky generic message to the data plane (`InboundScriptExecutor.cs:299` and `:311` — `"Script compilation failed for this method"`), while the genuine diagnostic only ever reaches the logger: - `InboundScriptExecutor.cs:182-183` — `LogWarning("API method {Method} script compilation failed: {Errors}", …)` - `InboundScriptExecutor.cs:197` — `LogError(ex, "Failed to compile API method {Method} script", …)` Returning the raw Roslyn text to an *external, API-key* caller is the right default (it can leak code/internal type names), but it means an **operator/admin** has no first-class channel to that text either. Contrast `template validate` (CLI `template validate --id`, README §Template) which runs a real compile and returns the diagnostics — there is no `api-method validate` equivalent (`grep` for `ValidateApiMethod`/`CompileApiMethod`/`RecompileApiMethod` across `src/` returns nothing; `ApiMethodCommands.cs` registers only `list`/`get`/`create`/`update`/`delete`). **Impact:** turns a one-line fix into a host-access investigation. Authoring/repairing an inbound script becomes "update → fire a request → if it fails, SSH into the host and read the log → repeat," instead of "validate → read the error → fix." **Suggested fix (pick one or both):** 1. **`api-method validate --id `** (CLI #19) backed by a management `ValidateApiMethodCommand` (#18) that runs `CompileAndRegister`'s compile path and returns the structured diagnostics to the *authenticated, role-gated* management caller (never the data plane). Mirror `template validate`. 2. Surface the **last compile state** on `api-method get` — e.g. `LastCompileError` (string, null when clean) + `IsKnownBad` (bool) — so an operator can see why a method is failing without re-firing it. Keep the data-plane `/api/{method}` body generic as-is. --- ## 2. The `_knownBadMethods` cache is neither observable per-method nor resettable without a full-replace update **Severity:** Low-Medium · **Components:** Inbound API (#14) **Symptom:** Once a method's script fails to compile, its name is recorded in an in-memory bad-methods set and every later request **short-circuits** to the generic message *without recompiling*. Consequences observed/known: - Fixing the stored script **directly in the config DB does not take effect** — the running process keeps serving the compile-failed message until a management `UpdateApiMethod` (which calls `CompileAndRegister`) or a service restart. (This is exactly why one of the three methods this session, `MesMoveIn`, had to be re-saved via the management API even though its stored script was already fine.) - There is no way to *see* whether a given method is currently in the bad set (only an aggregate `internal int KnownBadMethodCount`, not exposed to any client), and no lightweight way to force a recompile short of a full-entity replace. **Root cause:** `InboundScriptExecutor` (`InboundScriptExecutor.cs`): - declares the cache at `:39` (`ConcurrentDictionary _knownBadMethods`), - short-circuits on it at `:58` / `:298`, adds to it at `:62`, - and clears an entry in **exactly one place** — `CompileAndRegister` (`:103`, removal at `:118`). A direct DB row edit never runs `CompileAndRegister`, so the stale entry persists. The cache itself is correct and desirable (it stops every request re-running a doomed Roslyn compile). The gap is purely **observability + a targeted reset**. **Suggested fix:** - Expose per-method state via the item-1 fix (`IsKnownBad` / `LastCompileError` on `api-method get`). - Add a lightweight **`api-method recompile --id`** (management `RecompileApiMethodCommand`) that re-runs `CompileAndRegister` for one method without requiring the caller to round-trip the whole entity (script + timeout + parameterDefinitions + returnDefinition) — today `UpdateApiMethod` is full-replace, so an operator must re-send every field just to bust the cache. This is the smaller, lower-risk sibling of item 1's validate verb.