feat(site-runtime): outbound API/DB/notification audit rows ride the flow-trace collector

ScriptRuntimeContext caches a trace sink (null on an untraced run, so the
production path is byte-identical) and threads it into the ExternalSystem /
Database / Notify helpers and AuditingDbConnection→AuditingDbCommand (onAudit).
All eight outbound emit sites hand the SAME AuditEvent instance to the sink
first, then to the writer/forwarder. FlowTraceCollector gains Completed +
Complete(): ScriptRunLauncher closes the collector where it builds the reply
snapshot, so un-awaited calls finishing after the run returned never mutate a
list the reply was built from. Cap (500) unchanged. Plan 2026-09-06 D5.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-06 01:51:19 -04:00
parent 865c4be651
commit f394f58405
6 changed files with 464 additions and 19 deletions
@@ -417,7 +417,9 @@ internal static class ScriptRunLauncher
var state = await compiledScript.RunAsync(globals, cts.Token);
// Flow Debugger: the ScriptRun row goes into the collector BEFORE the reply so the
// inline list the caller receives includes the run itself.
// inline list the caller receives includes the run itself. Complete() (not Snapshot)
// closes the sink at the same instant — outbound rows finishing after this point
// (plan 2026-09-06 D5) go to the audit writer only.
EmitScriptRun(trace, collector, auditWriter, logger, scriptName, instanceName, siteId, sourceNode,
context.ExecutionId, parentExecutionId, startedUtc, runStopwatch.ElapsedMilliseconds,
parameters, state.ReturnValue, error: null, triggerExtra);
@@ -427,7 +429,7 @@ internal static class ScriptRunLauncher
{
replyTo.Tell(new ScriptCallResult(
correlationId, true, state.ReturnValue, null,
context.ExecutionId, collector?.Snapshot()));
context.ExecutionId, collector?.Complete()));
}
// Operational `script` event — execution completed successfully. Same gating as
@@ -462,7 +464,7 @@ internal static class ScriptRunLauncher
if (!replyTo.IsNobody())
{
replyTo.Tell(new ScriptCallResult(
correlationId, false, null, errorMsg, context?.ExecutionId, collector?.Snapshot()));
correlationId, false, null, errorMsg, context?.ExecutionId, collector?.Complete()));
}
completionTarget.Tell(new ScriptActor.ScriptExecutionCompleted(scriptName, false, errorMsg));
@@ -487,7 +489,7 @@ internal static class ScriptRunLauncher
if (!replyTo.IsNobody())
{
replyTo.Tell(new ScriptCallResult(
correlationId, false, null, errorMsg, context?.ExecutionId, collector?.Snapshot()));
correlationId, false, null, errorMsg, context?.ExecutionId, collector?.Complete()));
}
completionTarget.Tell(new ScriptActor.ScriptExecutionCompleted(scriptName, false, errorMsg));