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
@@ -61,6 +61,34 @@ public class TemplateEngineRepositoryTests : IDisposable
Assert.Equal("Slot1", loaded.Compositions.First().InstanceName);
}
[Fact]
public async Task TemplateScript_ExecutionTimeoutSeconds_RoundTripsThroughEf()
{
// M2.5 (#9): the nullable per-script execution timeout must persist and
// reload through EF — both an explicit value and a null (use-global).
var template = new Template("TimeoutTemplate");
template.Scripts.Add(new TemplateScript("WithTimeout", "return 1;")
{
ExecutionTimeoutSeconds = 45
});
template.Scripts.Add(new TemplateScript("NoTimeout", "return 2;")); // null
_context.Templates.Add(template);
await _context.SaveChangesAsync();
// Detach so the reload comes from the store, not the change tracker.
_context.ChangeTracker.Clear();
var loaded = await _context.Templates
.Include(t => t.Scripts)
.SingleAsync(t => t.Name == "TimeoutTemplate");
var withTimeout = loaded.Scripts.Single(s => s.Name == "WithTimeout");
Assert.Equal(45, withTimeout.ExecutionTimeoutSeconds);
var noTimeout = loaded.Scripts.Single(s => s.Name == "NoTimeout");
Assert.Null(noTimeout.ExecutionTimeoutSeconds);
}
[Fact]
public async Task GetTemplateWithChildrenAsync_ReturnsNull_WhenTemplateDoesNotExist()
{