feat(inbound-api): audit central method-script Database.* calls (DbOutbound) and feed the flow-trace sink

InboundDatabaseHelper gains trailing optional auditWriter / nodeIdentity /
executionId / parentExecutionId / methodName / traceSink parameters; every
QuerySingle/Query/Execute writes one DbOutbound/DbWrite row (Delivered/Failed,
Target = connection name, Actor = 'api:{method}', DurationMs, SQL + parameters
as RequestSummary, Extra in the site's {op, rowsAffected|rowsReturned}
vocabulary, SourceNode) via ICentralAuditWriter — best-effort, never into the
script; the same instance goes to the RouteHelper trace sink first. Closes the
audit-scope gap Component-AuditLog.md already claimed (plan D7). Redaction and
the 8 KB cap apply at write time in the shared redactor, as for site rows.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-06 01:52:50 -04:00
parent 3b9def5a09
commit 69feb40e65
4 changed files with 514 additions and 25 deletions
@@ -381,7 +381,23 @@ public class InboundScriptExecutor
// Pass the method timeout so the helper derives a
// CommandTimeout backstop and forwards cts.Token to every async DB call —
// a slow query is then bounded by the method deadline.
var dbHelper = new InboundDatabaseHelper(gateway, cts.Token, timeout);
//
// Flow Debugger outbound calls (plan 2026-09-06, D7): the helper audits every
// Database.* call as a DbOutbound/DbWrite row. The inbound request's own
// execution id (the parentExecutionId argument — see its doc above) is the
// row's ExecutionId, exactly as the RouteCall row stamps it; an inbound request
// is a root, so the row's ParentExecutionId is null. The writer + node identity
// come from the same scope as the gateway (both null-tolerant: an unregistered
// writer means no row, never a failed script), and the trace sink is the one the
// caller attached to the RouteHelper so a Live run collects both row kinds.
var dbHelper = new InboundDatabaseHelper(
gateway, cts.Token, timeout,
auditWriter: scope?.ServiceProvider.GetService<ICentralAuditWriter>(),
nodeIdentity: scope?.ServiceProvider.GetService<INodeIdentityProvider>(),
executionId: parentExecutionId,
parentExecutionId: null,
methodName: method.Name,
traceSink: route.TraceSink);
// Bind the route helper to the method deadline so a
// routed Route.To(...).Call(...) inherits the method-level timeout