diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugTableProjection.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugTableProjection.cs index 5f514fde..5d99c6df 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugTableProjection.cs +++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugTableProjection.cs @@ -18,7 +18,7 @@ namespace ZB.MOM.WW.ScadaBridge.CLI.Commands; /// to the generic renderer unprojected, the object takes the Property/Value branch and the /// whole event array lands in one cell as raw JSON. This projects the events through the same /// the Flow Debugger page uses and prints one row per STEP — -/// occurred | kind | lane | target | durationMs | status | lifecycle | error — in the +/// occurred | kind | lane | target | durationMs | status | lifecycle | correlation | error — in the /// order the page draws them, so a terminal user can read the swimlane top-to-bottom. An /// outbound operation's rows (a cached call's Submit → Forwarded → Attempted → Resolve, a /// notification's Send → Deliver) fold into ONE row whose lifecycle column counts them @@ -146,6 +146,7 @@ internal static class FlowDebugTableProjection ["durationMs"] = step.DurationMs, ["status"] = status, ["lifecycle"] = lifecycle is { Count: > 0 } ? $"{lifecycle.Count} {(lifecycle.Count == 1 ? "row" : "rows")}" : null, + ["correlation"] = step.CorrelationId?.ToString("D"), ["error"] = step.ErrorMessage, }); } diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/README.md b/src/ZB.MOM.WW.ScadaBridge.CLI/README.md index 37511025..a254cec0 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CLI/README.md +++ b/src/ZB.MOM.WW.ScadaBridge.CLI/README.md @@ -2289,7 +2289,7 @@ Trace steps are ordinary audit rows: a run leaves them in the central `AuditLog` chain after the fact. Table output prints one row per step in chronological order — -`occurred | kind | lane | target | durationMs | status | lifecycle | error` — the same +`occurred | kind | lane | target | durationMs | status | lifecycle | correlation | error` — the same projection the Flow Debugger page draws (`FlowGraphBuilder`): an outbound `ExternalSystem.*`, `Database.*` or `Notify` call is ONE row on the `External` lane (kinds `ApiCall` / `DbCall` / `Notify`), and a cached call's or notification's later rows (Forwarded → Attempted → Delivered / diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStep.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStep.cs index 2e0a0363..247c2cf4 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStep.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStep.cs @@ -81,6 +81,11 @@ public sealed record FlowLifecycleRow( /// (; outbound-calls plan 2026-09-06 D3). Never serialized to the wire; /// the wire carries the audit events themselves (design §5.4). /// +/// +/// CorrelationId is the audit row's own: the TrackedOperationId of a cached call or the +/// NotificationId of a notification — the key the Site Calls / Notification report pages accept as +/// ?correlationId= (follow-ups plan 2026-09-06 D1/D2). Null for sync one-shots and trace rows. +/// public sealed record FlowStep( Guid EventId, Guid? ExecutionId, @@ -100,4 +105,5 @@ public sealed record FlowStep( string? Instance, string? Target, ScriptTraceExtra? Extra, - IReadOnlyList? Lifecycle = null); + IReadOnlyList? Lifecycle = null, + Guid? CorrelationId = null); diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStepProjector.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStepProjector.cs index 92649d6d..b9598d29 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStepProjector.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStepProjector.cs @@ -42,7 +42,8 @@ public static class FlowStepProjector Node: evt.SourceNode ?? extra?.Node, Instance: d.SourceInstanceId, Target: evt.Target, - Extra: extra); + Extra: extra, + CorrelationId: evt.CorrelationId); } /// Projects one outbound audit row onto its lifecycle entry (). diff --git a/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/FlowDebugCommandsTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/FlowDebugCommandsTests.cs index 8e6b42ff..2e0f33f8 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/FlowDebugCommandsTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/FlowDebugCommandsTests.cs @@ -229,11 +229,13 @@ public class FlowDebugCommandsTests Assert.Equal("External", rows[3].GetProperty("lane").GetString()); Assert.Equal("RigDb", rows[3].GetProperty("target").GetString()); Assert.Equal("3 rows", rows[3].GetProperty("lifecycle").GetString()); + Assert.Equal(corr.ToString("D"), rows[3].GetProperty("correlation").GetString()); Assert.Equal("Delivered", rows[3].GetProperty("status").GetString()); Assert.Equal(50, rows[3].GetProperty("durationMs").GetInt32()); Assert.Equal("09:00:00.070", rows[3].GetProperty("occurred").GetString()); Assert.Equal("DelmiaSim01.OutboundDemo", rows[1].GetProperty("target").GetString()); Assert.Equal(JsonValueKind.Null, rows[1].GetProperty("lifecycle").ValueKind); + Assert.Equal(JsonValueKind.Null, rows[1].GetProperty("correlation").ValueKind); } } diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Debugging/FlowGraphBuilderTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Debugging/FlowGraphBuilderTests.cs index 14c82c44..1bee9430 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Debugging/FlowGraphBuilderTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Debugging/FlowGraphBuilderTests.cs @@ -232,6 +232,7 @@ public class FlowGraphBuilderTests Assert.Equal(T0.AddMilliseconds(35), api.OccurredAtUtc.UtcDateTime); Assert.Equal(4, api.Lifecycle!.Count); Assert.Equal(new[] { "CachedSubmit", "ApiCallCached", "ApiCallCached", "CachedResolve" }, api.Lifecycle.Select(r => r.Kind)); + Assert.Equal(corr, api.CorrelationId); Assert.Equal("503", api.Lifecycle[2].ErrorMessage); Assert.Equal(FlowStepOutcome.Ok, api.Outcome); Assert.Equal(4100, api.DurationMs); diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Debugging/FlowStepProjectorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Debugging/FlowStepProjectorTests.cs index 9de0afa8..aebc334b 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Debugging/FlowStepProjectorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Debugging/FlowStepProjectorTests.cs @@ -1,3 +1,4 @@ +using ZB.MOM.WW.Audit; using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit; using ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; @@ -161,4 +162,23 @@ public class FlowStepProjectorTests occurredAtUtc: T0.UtcDateTime, target: "MesDb", extra: extra); Assert.Equal(expected, FlowStepProjector.Project(evt).Subtitle); } + + [Fact] + public void Project_CarriesTheAuditRowsCorrelationId() + { + var corr = Guid.NewGuid(); + var evt = ScadaBridgeAuditEventFactory.Create(AuditChannel.ApiOutbound, AuditKind.CachedSubmit, AuditStatus.Submitted, + occurredAtUtc: T0.UtcDateTime, target: "Delmia.Ack", executionId: Guid.NewGuid(), correlationId: corr, sourceSiteId: "site-a"); + + Assert.Equal(corr, FlowStepProjector.Project(evt).CorrelationId); + } + + [Fact] + public void Project_NullCorrelation_LeavesCorrelationIdNull() + { + var evt = ScadaBridgeAuditEventFactory.Create(AuditChannel.ApiOutbound, AuditKind.ApiCall, AuditStatus.Delivered, + occurredAtUtc: T0.UtcDateTime, target: "Delmia.Ack", executionId: Guid.NewGuid(), sourceSiteId: "site-a"); + + Assert.Null(FlowStepProjector.Project(evt).CorrelationId); + } }