feat(flow-debugger): carry site-side trace truncation on the route reply (ScadaBridge#54)

ScriptCallResult and RouteToCallResponse gain trailing Truncated/LogsTruncated,
filled from the site FlowTraceCollector after Complete(); RouteToCallResponseDto
fields 8/9 carry them (sitecommand regen). FlowDebugRunner ORs them into
FlowDebugRunResult on the script entry, and on the method entry through a new
optional truncation sink on RouteHelper.WithTraceSink (carried through every
With* clone). Previously a site run whose collector dropped rows came back as a
silently short list. Older sites send false.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 12:37:57 -05:00
parent 5310fd0e26
commit 6d5bb6189f
15 changed files with 484 additions and 155 deletions
@@ -307,6 +307,38 @@ public class FlowDebugRunnerTests
Assert.Contains(_central, e => e.AsRow().Kind == AuditKind.RouteCall);
}
/// <summary>
/// Gitea #54: on the method entry the routed reply reaches the runner through RouteHelper's
/// sink, so the site collector's truncation flags must ride that path too.
/// </summary>
/// <param name="truncated">The site's step-row truncation flag.</param>
/// <param name="logsTruncated">The site's log-row truncation flag.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task RunMethod_OrsTheRoutedSitesTruncationFlagsIntoTheResult(bool truncated, bool logsTruncated)
{
var method = new ApiMethod("M", "x") { Id = 7, TimeoutSeconds = 5 };
_apiRepo.GetApiMethodByIdAsync(7, Arg.Any<CancellationToken>()).Returns(method);
_locator.GetSiteIdForInstanceAsync("inst", Arg.Any<CancellationToken>()).Returns("site-a");
var child = Guid.NewGuid();
var siteRun = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
executionId: child, target: "work");
_router.RouteToCallAsync("site-a", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
.Returns(ci => new RouteToCallResponse(((RouteToCallRequest)ci[1]).CorrelationId, true, 7, null,
DateTimeOffset.UtcNow, child, [siteRun], truncated, logsTruncated));
_executor.RegisterHandler("M", ctx => ctx.Route.To("inst").Call("work"));
var result = await Runner().RunMethodAsync(_services.BuildServiceProvider(),
new FlowDebugRunMethodCommand(7, null), Deployer, CancellationToken.None);
Assert.True(result.Completed, result.Error);
Assert.Equal(truncated, result.Truncated);
Assert.Equal(logsTruncated, result.LogsTruncated);
}
/// <summary>Verifies that a site clock behind central still returns the sink events in emission order.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
@@ -908,6 +940,35 @@ public class FlowDebugRunnerTests
Assert.Equal("OtherScript", result.Events[1].Target);
}
/// <summary>
/// Gitea #54: a call-kind script run whose SITE collector dropped rows reports truncation on
/// the result, even though central's own collector saw only the (short) list the site sent.
/// </summary>
/// <param name="truncated">The site's step-row truncation flag.</param>
/// <param name="logsTruncated">The site's log-row truncation flag.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task RunScript_CallKind_OrsTheSitesTruncationFlagsIntoTheResult(bool truncated, bool logsTruncated)
{
WireScript(CallScript("Manual", """{"type":"object","properties":{"n":{"type":"integer"}},"required":["n"]}""", timeout: 5));
var child = Guid.NewGuid();
var siteRun = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
executionId: child, target: "Manual");
_router.RouteToCallAsync("site-a", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
.Returns(ci => new RouteToCallResponse(((RouteToCallRequest)ci[1]).CorrelationId, true, 42, null,
DateTimeOffset.UtcNow, child, [siteRun], truncated, logsTruncated));
var result = await Runner().RunScriptAsync(_services.BuildServiceProvider(),
new FlowDebugRunScriptCommand(5, "Manual", Json("{\"n\":3}"), null), Deployer, CancellationToken.None);
Assert.True(result.Completed, result.Error);
Assert.Equal(truncated, result.Truncated);
Assert.Equal(logsTruncated, result.LogsTruncated);
}
/// <summary>Verifies that a call-kind script run routes a traced call and returns the root route-call row followed by the site rows.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]