fix(central-ui): FlowDebugService — reuse the runner's default script timeout, warn on flatten failure, pin one-flatten memo + parameter threading

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-08 01:23:18 -04:00
parent e5ca703714
commit e1865f9ab3
2 changed files with 38 additions and 15 deletions
@@ -371,6 +371,8 @@ public sealed class FlowDebugServiceTests : IDisposable
Assert.Equal(2, _received.Count);
var vcCmd = Assert.IsType<FlowDebugRunScriptCommand>(_received[0].Command);
Assert.Equal((7, "OnSpeed", "42"), (vcCmd.InstanceId, vcCmd.ScriptName, vcCmd.Value));
// A write kind fires through the attribute, so it carries no parameters.
Assert.Null(vcCmd.Parameters);
var rpCmd = Assert.IsType<FlowDebugReplayCommand>(_received[1].Command);
Assert.Equal(exec, rpCmd.ExecutionId);
}
@@ -607,13 +609,36 @@ public sealed class FlowDebugServiceTests : IDisposable
WireFlattened(9, "Sim01", new ResolvedScript { CanonicalName = "Manual", Code = "x", TriggerType = "Call", ExecutionTimeoutSeconds = 40 });
var raw = new FlowDebugRunResult(Guid.NewGuid(), true, null, null, [], false, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow);
var svc = CreateService(reply: new ManagementSuccess("c", JsonSerializer.Serialize(raw, ServerJson)));
using var doc = JsonDocument.Parse("""{"n":3}""");
var outcome = await svc.RunScriptAsync(9, "Manual", null, null);
var outcome = await svc.RunScriptAsync(9, "Manual", doc.RootElement.Clone(), null);
Assert.True(outcome.Success, outcome.Error);
var env = Assert.Single(_received);
var cmd = Assert.IsType<FlowDebugRunScriptCommand>(env.Command);
Assert.Equal((9, "Manual"), (cmd.InstanceId, cmd.ScriptName));
// A call kind carries its parameters verbatim and no value.
Assert.Equal(3, cmd.Parameters!.Value.GetProperty("n").GetInt32());
Assert.Null(cmd.Value);
}
/// <summary>
/// The picker, the prediction and the run of one instance share ONE flatten for the life of
/// the scoped service (design 2026-09-08 §5) — a regression here means every keystroke on the
/// page re-walks the template chain.
/// </summary>
[Fact]
public async Task GetInstanceScripts_ThenPredict_ThenRun_FlattenOnce()
{
WireFlattened(9, "Sim01", new ResolvedScript { CanonicalName = "Manual", Code = "x", TriggerType = "Call" });
var raw = new FlowDebugRunResult(Guid.NewGuid(), true, null, null, [], false, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow);
var svc = CreateService(reply: new ManagementSuccess("c", JsonSerializer.Serialize(raw, ServerJson)));
await svc.GetInstanceScriptsAsync(9);
await svc.PredictScriptAsync(9, "Manual");
await svc.RunScriptAsync(9, "Manual", null, null);
await _pipeline.Received(1).FlattenAndValidateAsync(9, Arg.Any<CancellationToken>(), false, null);
}
private sealed class StubAuth : AuthenticationStateProvider