fix(inbound): authorize+secure Database helper, async/deadline-bound DB, wait-timeout-bound WaitForAttribute

Resolves InboundAPI-026/027/028/029 (+ newly-surfaced -030).

- 026: authorize the scoped Database helper in the design doc; SQL-injection
  protection is parameter binding (values never concatenated); allow writes via
  ExecuteAsync; drop the false 'read-only' claim. Named connections only.
- 027: async ADO.NET end-to-end (no .GetAwaiter().GetResult()); honour the method
  deadline token on ExecuteScalarAsync/ExecuteReaderAsync/ExecuteNonQueryAsync +
  a CommandTimeout backstop derived from the method timeout.
- 028: negative-path tests (null-gateway, deadline cancellation, parameterization)
  + e2e Database + WaitForAttribute cases through the real endpoint.
- 029: WaitForAttribute is bounded by its WAIT timeout (per-wait CTS + client-abort
  + explicit token), NOT the method deadline (spec §6) — a long wait may outlive the
  method timeout; WithRequestAborted threads the raw client-abort token separately.
- 030: Central UI compile-surface mirrors (InboundScriptHost / SandboxInboundScriptHost)
  gained the Database member (drifted since the runtime helper was added) so the
  authorized async API type-checks at the design-time gate.
This commit is contained in:
Joseph Doherty
2026-06-23 22:00:17 -04:00
parent d39089f4ed
commit b3c9014379
11 changed files with 540 additions and 68 deletions
@@ -660,4 +660,21 @@ public class ScriptAnalysisServiceTests
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("CS"));
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("SCADA"));
}
[Fact]
public void InboundScript_Database_DiagnosesClean()
{
// InboundAPI-026/030: the scoped Database helper is a shipped inbound-script
// capability. The editor must not flag its async read/write API as an error —
// the InboundScriptHost mirror must expose QuerySingleAsync/QueryAsync/ExecuteAsync
// with signatures matching the runtime helper so Roslyn resolves them.
var resp = _svc.Diagnose(new DiagnoseRequest(
Code: "var code = await Database.QuerySingleAsync<string>(\"BTDB\", \"SELECT Code FROM Machine WHERE SAPID=@s\", new { s = (string)Parameters[\"sap\"] });\n"
+ "var rows = await Database.QueryAsync(\"BTDB\", \"SELECT Code FROM Machine\");\n"
+ "var n = await Database.ExecuteAsync(\"BTDB\", \"UPDATE Machine SET Code=@c WHERE SAPID=@s\", new { c = code, s = \"131453\" });",
Kind: ScriptKind.InboundApi));
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("CS"));
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("SCADA"));
}
}