feat(runtime): per-script execution timeout overriding the global default (#9)

Spec promised a per-script timeout but only the global ScriptExecutionTimeoutSeconds
existed. Add nullable TemplateScript.ExecutionTimeoutSeconds threaded through EF +
flattening (ResolvedScript) to ScriptExecutionActor/AlarmExecutionActor, which use
perScript ?? global for the execution CTS. Includes the EF migration for the new column.
This commit is contained in:
Joseph Doherty
2026-06-15 14:40:38 -04:00
parent 00304a26e6
commit 3edef09f51
22 changed files with 2094 additions and 17 deletions
@@ -72,6 +72,15 @@ public class AlarmActor : ReceiveActor
private readonly string? _onTriggerScriptName;
private readonly Script<object?>? _onTriggerCompiledScript;
/// <summary>
/// M2.5 (#9): the on-trigger script's per-script execution timeout in seconds,
/// or null to use the global default. Forwarded to each spawned
/// <see cref="AlarmExecutionActor"/>, which applies <c>perScript ?? global</c>
/// (treating ≤ 0 as "use global"). The value comes from the referenced
/// on-trigger script's <see cref="ResolvedScript.ExecutionTimeoutSeconds"/>.
/// </summary>
private readonly int? _onTriggerExecutionTimeoutSeconds;
// Expression trigger: compiled expression + the attribute snapshot it
// evaluates against. This field is the single home for the compiled
// expression on the hot path.
@@ -107,6 +116,9 @@ public class AlarmActor : ReceiveActor
/// <param name="serviceProvider">Optional DI service provider used to resolve the optional
/// <see cref="ISiteEventLogger"/> for M1.5 <c>alarm</c> operational events. Fire-and-forget;
/// a logging failure never affects alarm evaluation.</param>
/// <param name="onTriggerExecutionTimeoutSeconds">M2.5 (#9): the on-trigger script's per-script
/// execution timeout in seconds (from its <see cref="ResolvedScript.ExecutionTimeoutSeconds"/>),
/// or null/non-positive to use the global default.</param>
public AlarmActor(
string alarmName,
string instanceName,
@@ -119,7 +131,9 @@ public class AlarmActor : ReceiveActor
Script<object?>? compiledTriggerExpression = null,
IReadOnlyDictionary<string, object?>? initialAttributes = null,
ISiteHealthCollector? healthCollector = null,
IServiceProvider? serviceProvider = null)
IServiceProvider? serviceProvider = null,
// M2.5 (#9): per-script timeout for the on-trigger script (null = global).
int? onTriggerExecutionTimeoutSeconds = null)
{
_alarmName = alarmName;
_instanceName = instanceName;
@@ -135,6 +149,7 @@ public class AlarmActor : ReceiveActor
_priority = alarmConfig.PriorityLevel;
_onTriggerScriptName = alarmConfig.OnTriggerScriptCanonicalName;
_onTriggerCompiledScript = onTriggerCompiledScript;
_onTriggerExecutionTimeoutSeconds = onTriggerExecutionTimeoutSeconds;
_compiledTriggerExpression = compiledTriggerExpression;
// Seed the trigger-expression attribute snapshot from the instance's
@@ -574,7 +589,9 @@ public class AlarmActor : ReceiveActor
_instanceActor,
_sharedScriptLibrary,
_options,
_logger));
_logger,
// M2.5 (#9): per-script timeout from the on-trigger script (null = global).
_onTriggerExecutionTimeoutSeconds));
Context.ActorOf(props, executionId);
}