feat(site-runtime): traced runs emit ScriptRun rows, return ExecutionId + trace events; traced value-change runs inherit the parent

Flow Debugger (design 2026-09-05 §5.1/§5.4): ScriptRunLauncher emits one ScriptRun row per
traced run on every exit path (OccurredAtUtc = run start), replies with the run's
ExecutionId (always) and the collector snapshot (traced only). ScriptActor forwards
ScriptCallRequest.Trace and, for a value-change trigger fired by a traced stamped change,
parents the run to the writer with trigger detail in Extra; every other trigger stays a root.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
This commit is contained in:
Joseph Doherty
2026-09-05 23:43:33 -04:00
parent 93489cd7a2
commit eca1434160
3 changed files with 319 additions and 17 deletions
@@ -4,6 +4,8 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.ScriptExecution;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening;
using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
using ZB.MOM.WW.ScadaBridge.SiteEventLogging;
@@ -272,7 +274,9 @@ public class ScriptActor : ReceiveActor, IWithTimers
// (tag-change / timer) runs and nested Script.Call invocations.
SpawnExecution(
request.Parameters, request.CurrentCallDepth, Sender, request.CorrelationId,
request.ParentExecutionId);
request.ParentExecutionId,
// Flow Debugger (design §5.2): the trace flag rides beside the parent id.
request.Trace);
}
/// <summary>
@@ -289,7 +293,23 @@ public class ScriptActor : ReceiveActor, IWithTimers
{
if (valueTrigger.AttributeName == changed.AttributeName)
{
TrySpawnExecution(null);
// Flow Debugger (design §5.2/§5.3): a change stamped by a traced write parents
// the spawned run to the writing execution and carries the trace flag; every
// other value-change run stays a root by design (CLAUDE.md, alarm-leg note).
if (changed.Trace && changed.SourceExecutionId is { } sourceExecutionId)
{
TrySpawnExecution(null, sourceExecutionId, trace: true, new ScriptTraceExtra
{
TriggerKind = "valuechange",
TriggerAttribute = changed.AttributeName,
TriggerValue = AttributeValueCodec.Encode(changed.Value),
TriggerSource = "value-change",
});
}
else
{
TrySpawnExecution(null);
}
}
}
else if (_triggerConfig is ConditionalTriggerConfig conditional)
@@ -496,7 +516,11 @@ public class ScriptActor : ReceiveActor, IWithTimers
/// <summary>
/// Attempts to spawn a script execution, respecting MinTimeBetweenRuns.
/// </summary>
private void TrySpawnExecution(IReadOnlyDictionary<string, object?>? parameters)
private void TrySpawnExecution(
IReadOnlyDictionary<string, object?>? parameters,
Guid? parentExecutionId = null,
bool trace = false,
ScriptTraceExtra? triggerExtra = null)
{
if (_compiledScript == null) return;
@@ -513,7 +537,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
}
_lastExecutionTime = DateTimeOffset.UtcNow;
SpawnExecution(parameters, 0, ActorRefs.NoSender!, Guid.NewGuid().ToString());
SpawnExecution(parameters, 0, ActorRefs.NoSender!, Guid.NewGuid().ToString(), parentExecutionId, trace, triggerExtra);
}
/// <summary>
@@ -544,7 +568,9 @@ public class ScriptActor : ReceiveActor, IWithTimers
int callDepth,
IActorRef replyTo,
string correlationId,
Guid? parentExecutionId = null)
Guid? parentExecutionId = null,
bool trace = false,
ScriptTraceExtra? triggerExtra = null)
{
if (callDepth == 0 && _runsInFlight >= _options.MaxConcurrentRunsPerScript)
{
@@ -590,7 +616,10 @@ public class ScriptActor : ReceiveActor, IWithTimers
_executionTimeoutSeconds,
// Scheduler seam (#18): thread this actor's scheduler override down so
// launched script bodies share the same pool (null = process-wide shared).
_scheduler);
_scheduler,
// Flow Debugger (design §5.2): trace flag + value-change trigger detail.
trace,
triggerExtra);
}
catch (Exception ex)
{