fix(site-runtime): shed or rate-limited traced runs emit a Failed ScriptRun row (arch-review F7)
A traced run refused before launch — by the per-script in-flight cap
(MaxConcurrentRunsPerScript) or by MinTimeBetweenRuns — emitted no ScriptTrace row at
all, so the Flow Debugger reported NO_TRIGGER_WITHIN_WINDOW ("no value-change trigger on
this attribute, or no device echo"), which is wrong in both cases and sends the operator
to check the wrong things.
Both guards now leave a Failed ScriptTrace/ScriptRun row naming the guard that refused
the run, parented to the writing execution and carrying the trigger detail. The new
ScriptRunLauncher.EmitSuppressedRun reuses EmitScriptRun with elapsedMs 0 and no
collector (a shed run has no reply to ride on — the row reaches the debugger via
PullTraceEvents / Replay), resolving the writer and identity providers from a fresh
scope because it runs on the actor thread. Best-effort, never throws, no-op untraced.
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
@@ -515,6 +515,50 @@ internal static class ScriptRunLauncher
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arch-review F7: a traced run that never launched (shed at the in-flight cap, or
|
||||
/// suppressed by <c>MinTimeBetweenRuns</c>) still leaves a <c>Failed</c>
|
||||
/// <c>ScriptTrace.ScriptRun</c> row, so the Flow Debugger explains the gap with the guard
|
||||
/// that refused the run instead of reporting <c>NO_TRIGGER_WITHIN_WINDOW</c> — which reads
|
||||
/// as "no value-change trigger on this attribute, or no device echo" and sends the operator
|
||||
/// to check the wrong things.
|
||||
///
|
||||
/// <para>There is no collector: a shed run has no reply to ride on (an Ask caller gets the
|
||||
/// shed <c>ScriptCallResult</c> from <c>ScriptActor.ShedRun</c>, not a trace list), so the
|
||||
/// row goes to the audit writer only and reaches the debugger via <c>PullTraceEvents</c> /
|
||||
/// Replay. The writer and identity providers come from a fresh scope because this runs on
|
||||
/// the ACTOR thread, outside any launch scope. <c>elapsedMs</c> is 0 — the run never ran.
|
||||
/// Best-effort and never throws: a no-op when there is no provider or no writer.</para>
|
||||
/// </summary>
|
||||
internal static void EmitSuppressedRun(
|
||||
IServiceProvider? serviceProvider,
|
||||
ILogger logger,
|
||||
string scriptName,
|
||||
string instanceName,
|
||||
Guid? parentExecutionId,
|
||||
string reason,
|
||||
ScriptTraceExtra? triggerExtra)
|
||||
{
|
||||
if (serviceProvider is null) return;
|
||||
try
|
||||
{
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var writer = scope.ServiceProvider.GetService<IAuditWriter>();
|
||||
if (writer is null) return;
|
||||
var siteId = scope.ServiceProvider.GetService<ISiteIdentityProvider>()?.SiteId ?? string.Empty;
|
||||
var sourceNode = scope.ServiceProvider.GetService<INodeIdentityProvider>()?.NodeName;
|
||||
|
||||
EmitScriptRun(
|
||||
trace: true, collector: null, writer, logger, scriptName, instanceName, siteId, sourceNode,
|
||||
executionId: Guid.NewGuid(), parentExecutionId, DateTime.UtcNow, elapsedMs: 0,
|
||||
parameters: null, returnValue: null, error: reason, triggerExtra);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Flow trace suppressed-run emission failed for {Script}", scriptName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.1): emits the <c>ScriptTrace.ScriptRun</c> row for a
|
||||
/// traced run — collector first (so the inline reply carries it), then the best-effort
|
||||
|
||||
Reference in New Issue
Block a user