diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs index abb6e0cd..4776b8dd 100644 --- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs +++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs @@ -275,7 +275,10 @@ public static class EndpointExtensions var scriptResult = await executor.ExecuteAsync( method, paramResult.Parameters, routeHelper, timeout, - httpContext.RequestAborted, parentExecutionId); + httpContext.RequestAborted, parentExecutionId, + // Flow Debugger (design 2026-09-05 §5.2): a method flagged TraceAllCalls traces every + // production call so Replay of a real integration call is complete. + trace: method.TraceAllCalls); if (!scriptResult.Success) { diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs index f63292fb..4359cef2 100644 --- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs @@ -330,7 +330,10 @@ public class InboundScriptExecutor // Deliberate ordering: this optional parameter trails the CancellationToken // because it was appended additively for non-breaking contract evolution. // Every call site passes it by named argument (parentExecutionId:). - Guid? parentExecutionId = null) + Guid? parentExecutionId = null, + // Flow Debugger (design 2026-09-05 §5.2): trace the routed site runs this method makes. + // Additive; the endpoint passes ApiMethod.TraceAllCalls, the Flow Debugger passes true. + bool trace = false) { // Keep the timeout source and the request-abort source // separable. A single linked CTS makes a genuine client disconnect @@ -396,7 +399,8 @@ public class InboundScriptExecutor parameters, route.WithDeadline(cts.Token) .WithRequestAborted(cancellationToken) - .WithParentExecutionId(parentExecutionId), + .WithParentExecutionId(parentExecutionId) + .WithTrace(trace), dbHelper, cts.Token); diff --git a/tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/InboundScriptExecutorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/InboundScriptExecutorTests.cs index 2334d070..f81573f1 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/InboundScriptExecutorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/InboundScriptExecutorTests.cs @@ -28,6 +28,35 @@ public class InboundScriptExecutorTests _route = new RouteHelper(locator, router); } + [Fact] + public async Task ExecuteAsync_TraceFlag_ReachesTheRoutedCallRequest() + { + // Flow Debugger (design 2026-09-05 §5.2): ExecuteAsync(trace: true) builds the script's + // Route helper WithTrace(true), so a Route.To(...).Call(...) inside the script sends a + // traced RouteToCallRequest; the default stays untraced. + var locator = Substitute.For(); + var router = Substitute.For(); + locator.GetSiteIdForInstanceAsync("inst", Arg.Any()).Returns("SiteA"); + var captured = new List(); + router.RouteToCallAsync("SiteA", Arg.Do(captured.Add), Arg.Any()) + .Returns(ci => new RouteToCallResponse(((RouteToCallRequest)ci[1]).CorrelationId, true, 1, null, DateTimeOffset.UtcNow)); + var route = new RouteHelper(locator, router); + var method = new ApiMethod("routed", "x") { Id = 9, TimeoutSeconds = 10 }; + _executor.RegisterHandler("routed", ctx => ctx.Route.To("inst").Call("work")); + + var parent = Guid.NewGuid(); + var traced = await _executor.ExecuteAsync(method, new Dictionary(), route, + TimeSpan.FromSeconds(10), parentExecutionId: parent, trace: true); + var plain = await _executor.ExecuteAsync(method, new Dictionary(), route, TimeSpan.FromSeconds(10)); + + Assert.True(traced.Success, traced.ErrorMessage); + Assert.True(plain.Success, plain.ErrorMessage); + Assert.Equal(2, captured.Count); + Assert.True(captured[0].Trace); + Assert.Equal(parent, captured[0].ParentExecutionId); + Assert.False(captured[1].Trace); + } + [Fact] public async Task RegisteredHandler_ExecutesSuccessfully() {