From 24e595d6d96a06935e6adb6772e523d8e0b83455 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 5 Sep 2026 23:41:50 -0400 Subject: [PATCH] feat(commons): additive Trace flag + ChildExecutionId/TraceEvents on route/call/write/change records MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flow Debugger (design 2026-09-05 §5.2/§5.4): Trace rides beside ParentExecutionId on RouteToCallRequest/RouteToSetAttributesRequest/ScriptCallRequest/SetStaticAttributeCommand/ AttributeValueChanged; RouteToCallResponse and ScriptCallResult carry the site run's ExecutionId and its ScriptTrace AuditEvents inline. All trailing optional — zero call-site edits. Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP --- .../InboundApi/RouteToInstanceRequest.cs | 16 ++++-- .../Instance/SetStaticAttributeCommand.cs | 4 +- .../ScriptExecution/ScriptCallRequest.cs | 4 +- .../ScriptExecution/ScriptCallResult.cs | 8 ++- .../Streaming/AttributeValueChanged.cs | 4 +- .../Messages/TraceFlagDefaultsTests.cs | 53 +++++++++++++++++++ 6 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/TraceFlagDefaultsTests.cs diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/InboundApi/RouteToInstanceRequest.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/InboundApi/RouteToInstanceRequest.cs index 9a880561..bea0483b 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/InboundApi/RouteToInstanceRequest.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/InboundApi/RouteToInstanceRequest.cs @@ -1,3 +1,5 @@ +using ZB.MOM.WW.Audit; + namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi; /// @@ -12,23 +14,29 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi; /// Additive trailing member — null for requests built before the field existed /// or for routed calls with no spawning execution (e.g. the Central UI sandbox). /// +/// Flow Debugger trace flag — see design 2026-09-05 §5.2. Additive; defaults to untraced. public record RouteToCallRequest( string CorrelationId, string InstanceUniqueName, string ScriptName, IReadOnlyDictionary? Parameters, DateTimeOffset Timestamp, - Guid? ParentExecutionId = null); + Guid? ParentExecutionId = null, + bool Trace = false); /// /// Response from a Route.To() call. /// +/// Flow Debugger — the routed site run's ExecutionId (design 2026-09-05 §5.4); null from a site that predates the field. +/// Flow Debugger — the site's ScriptTrace audit events for a traced run, in emission order (design 2026-09-05 §5.4); null when untraced or from an older site. public record RouteToCallResponse( string CorrelationId, bool Success, object? ReturnValue, string? ErrorMessage, - DateTimeOffset Timestamp); + DateTimeOffset Timestamp, + Guid? ChildExecutionId = null, + IReadOnlyList? TraceEvents = null); /// /// Request to read attribute(s) from a remote instance. @@ -68,12 +76,14 @@ public record RouteToGetAttributesResponse( /// write path. Additive trailing member — null for the Central UI sandbox path or for /// callers built before the field existed. /// +/// Flow Debugger trace flag — see design 2026-09-05 §5.2. Additive; defaults to untraced. public record RouteToSetAttributesRequest( string CorrelationId, string InstanceUniqueName, IReadOnlyDictionary AttributeValues, DateTimeOffset Timestamp, - Guid? ParentExecutionId = null); + Guid? ParentExecutionId = null, + bool Trace = false); /// /// Response confirming attribute writes on a remote instance. diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Instance/SetStaticAttributeCommand.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Instance/SetStaticAttributeCommand.cs index bda4d24d..35bccb69 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Instance/SetStaticAttributeCommand.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Instance/SetStaticAttributeCommand.cs @@ -19,13 +19,15 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance; /// AttributeValueChanged so an alarm the write trips can record it as the /// on-trigger script run's ParentExecutionId. /// +/// Flow Debugger trace flag — see design 2026-09-05 §5.2. Additive; defaults to untraced. public record SetStaticAttributeCommand( string CorrelationId, string InstanceUniqueName, string AttributeName, string Value, DateTimeOffset Timestamp, - Guid? SourceExecutionId = null); + Guid? SourceExecutionId = null, + bool Trace = false); /// /// Response confirming that a static attribute was set. diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/ScriptExecution/ScriptCallRequest.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/ScriptExecution/ScriptCallRequest.cs index dc6ee99a..217f1dc3 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/ScriptExecution/ScriptCallRequest.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/ScriptExecution/ScriptCallRequest.cs @@ -9,9 +9,11 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.ScriptExecution; /// null for normal (tag-change / timer-triggered) runs, nested Script.Call /// invocations, and any request built before the field existed. /// +/// Flow Debugger trace flag — see design 2026-09-05 §5.2. Additive; defaults to untraced. public record ScriptCallRequest( string ScriptName, IReadOnlyDictionary? Parameters, int CurrentCallDepth, string CorrelationId, - Guid? ParentExecutionId = null); + Guid? ParentExecutionId = null, + bool Trace = false); diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/ScriptExecution/ScriptCallResult.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/ScriptExecution/ScriptCallResult.cs index 4467c4de..d506c357 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/ScriptExecution/ScriptCallResult.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/ScriptExecution/ScriptCallResult.cs @@ -1,7 +1,13 @@ +using ZB.MOM.WW.Audit; + namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.ScriptExecution; +/// Flow Debugger — the run's own ExecutionId (design 2026-09-05 §5.4). Additive; null from callers that predate the field. +/// Flow Debugger — the run's ScriptTrace audit events in emission order when traced (design 2026-09-05 §5.4); null otherwise. public record ScriptCallResult( string CorrelationId, bool Success, object? ReturnValue, - string? ErrorMessage); + string? ErrorMessage, + Guid? ExecutionId = null, + IReadOnlyList? TraceEvents = null); diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AttributeValueChanged.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AttributeValueChanged.cs index 6bd73496..a11632c2 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AttributeValueChanged.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AttributeValueChanged.cs @@ -33,6 +33,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming; /// Actor, and central reads the linkage from the audit rows instead. /// /// +/// Flow Debugger trace flag — see design 2026-09-05 §5.2. Additive; defaults to untraced. public record AttributeValueChanged( string InstanceUniqueName, string AttributePath, @@ -40,4 +41,5 @@ public record AttributeValueChanged( object? Value, string Quality, DateTimeOffset Timestamp, - Guid? SourceExecutionId = null) : ISiteStreamEvent; + Guid? SourceExecutionId = null, + bool Trace = false) : ISiteStreamEvent; diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/TraceFlagDefaultsTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/TraceFlagDefaultsTests.cs new file mode 100644 index 00000000..d7596963 --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/TraceFlagDefaultsTests.cs @@ -0,0 +1,53 @@ +using ZB.MOM.WW.Audit; +using ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi; +using ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance; +using ZB.MOM.WW.ScadaBridge.Commons.Messages.ScriptExecution; +using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming; + +namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Messages; + +/// +/// Flow Debugger (design 2026-09-05 §5.2/§5.4): the trace flag and trace payload are additive +/// trailing optional members — every pre-existing positional construction stays untraced. +/// +public class TraceFlagDefaultsTests +{ + [Fact] + public void ExistingPositionalConstructions_DefaultToUntraced() + { + var now = DateTimeOffset.UtcNow; + Assert.False(new RouteToCallRequest("c", "i", "s", null, now).Trace); + Assert.False(new RouteToSetAttributesRequest("c", "i", new Dictionary(), now).Trace); + Assert.False(new ScriptCallRequest("s", null, 0, "c").Trace); + Assert.False(new SetStaticAttributeCommand("c", "i", "a", "v", now).Trace); + Assert.False(new AttributeValueChanged("i", "p", "a", 1, "Good", now).Trace); + + var reply = new RouteToCallResponse("c", true, null, null, now); + Assert.Null(reply.ChildExecutionId); + Assert.Null(reply.TraceEvents); + + var result = new ScriptCallResult("c", true, null, null); + Assert.Null(result.ExecutionId); + Assert.Null(result.TraceEvents); + } + + [Fact] + public void TraceEvents_AreAuditEvents() + { + var evt = new AuditEvent + { + EventId = Guid.NewGuid(), + OccurredAtUtc = DateTimeOffset.UtcNow, + Actor = "a", + Action = "ScriptTrace.ScriptRun", + Outcome = AuditOutcome.Success + }; + var result = new ScriptCallResult("c", true, null, null, Guid.NewGuid(), [evt]); + Assert.Single(result.TraceEvents!); + Assert.NotNull(result.ExecutionId); + + var reply = new RouteToCallResponse("c", true, null, null, DateTimeOffset.UtcNow, result.ExecutionId, result.TraceEvents); + Assert.Equal(result.ExecutionId, reply.ChildExecutionId); + Assert.Same(evt, reply.TraceEvents![0]); + } +}