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);
+ }
}