feat(commons): Flow Debugger outbound kinds, External lane, Pending/Parked outcomes, FlowLifecycleRow
FlowLane.External (3); FlowStepKind.External replaced by ApiCall/DbCall/Notify; FlowStepOutcome gains Pending/Parked; new FlowLifecycleRow + additive FlowStep.Lifecycle; FlowEdge gains Async/Spawn (default false). Predictor and graph-view references renamed compile-only (full work in Tasks 6/7). Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
@@ -185,7 +185,9 @@ public partial class FlowGraphView : ComponentBase
|
||||
FlowStepKind.AttributeWrite => "WRITE",
|
||||
FlowStepKind.AttributeWait => "WAIT",
|
||||
FlowStepKind.Response => "RESPONSE",
|
||||
FlowStepKind.External => "EXTERNAL",
|
||||
FlowStepKind.ApiCall => "API CALL",
|
||||
FlowStepKind.DbCall => "DATABASE",
|
||||
FlowStepKind.Notify => "NOTIFY",
|
||||
_ => "STEP",
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
|
||||
/// <param name="ToEventId">Target step.</param>
|
||||
/// <param name="CrossesBoundary">True when the edge crosses the central ↔ site boundary (drawn in the accent colour).</param>
|
||||
/// <param name="External">True for an inferred relationship the system did not observe directly — a device echo satisfying a wait after a write (drawn dashed).</param>
|
||||
public sealed record FlowEdge(Guid FromEventId, Guid ToEventId, bool CrossesBoundary, bool External);
|
||||
/// <param name="Async">True when the target is a fire-and-forget branch the source did not wait for — a cached call or a notification (drawn dashed, plan D4).</param>
|
||||
/// <param name="Spawn">True when the target is the first step of a child execution spawned by something other than a route call — a trigger cascade (bolt marker, plan D9).</param>
|
||||
public sealed record FlowEdge(Guid FromEventId, Guid ToEventId, bool CrossesBoundary, bool External, bool Async = false, bool Spawn = false);
|
||||
|
||||
/// <summary>The projected flow of one execution tree (Flow Debugger design 2026-09-05 §7.2).</summary>
|
||||
/// <param name="Steps">Steps in display order (time, then kind rank, then event id).</param>
|
||||
|
||||
@@ -129,7 +129,7 @@ public static class FlowGraphBuilder
|
||||
FlowStepKind.ScriptRun => 2,
|
||||
FlowStepKind.AttributeWrite => 3,
|
||||
FlowStepKind.AttributeWait => 4,
|
||||
FlowStepKind.External => 5,
|
||||
FlowStepKind.ApiCall or FlowStepKind.DbCall or FlowStepKind.Notify => 5,
|
||||
FlowStepKind.Unknown => 6,
|
||||
FlowStepKind.Response => 9,
|
||||
_ => 7,
|
||||
|
||||
@@ -9,6 +9,8 @@ public enum FlowLane
|
||||
Site,
|
||||
/// <summary>Attribute / tag layer: attribute writes and their device echoes.</summary>
|
||||
Tags,
|
||||
/// <summary>Actors outside ScadaBridge: outbound API calls, database calls and notifications (v1.1, 2026-09-06 plan D2).</summary>
|
||||
External,
|
||||
}
|
||||
|
||||
/// <summary>What a flow step represents — the projection of an audit channel/kind pair.</summary>
|
||||
@@ -26,8 +28,12 @@ public enum FlowStepKind
|
||||
AttributeWait,
|
||||
/// <summary>Synthesized final node — the inbound response (from the inbound row's response summary).</summary>
|
||||
Response,
|
||||
/// <summary>An outbound API / DB / notification row emitted by a traced run (shown, not chained across the boundary).</summary>
|
||||
External,
|
||||
/// <summary>An <c>ApiOutbound</c> row (<c>ExternalSystem.Call</c> / <c>CachedCall</c>) — one node per operation, lifecycle folded in.</summary>
|
||||
ApiCall,
|
||||
/// <summary>A <c>DbOutbound</c> row (<c>Database.Query</c> / <c>Execute</c> / <c>CachedWrite</c>).</summary>
|
||||
DbCall,
|
||||
/// <summary>A <c>Notification</c> row (<c>Notify.Send</c> and its central delivery).</summary>
|
||||
Notify,
|
||||
/// <summary>A channel/kind this build does not recognise — rendered, never dropped.</summary>
|
||||
Unknown,
|
||||
}
|
||||
@@ -45,8 +51,29 @@ public enum FlowStepOutcome
|
||||
Predicted,
|
||||
/// <summary>No trace event exists for this position (e.g. site on an older build).</summary>
|
||||
NotTraced,
|
||||
/// <summary>An outbound operation whose latest lifecycle row is non-terminal (Submitted / Forwarded / Attempted).</summary>
|
||||
Pending,
|
||||
/// <summary>An outbound operation that ended Parked or Discarded — retry/discard lives on the Site Calls / Notification pages.</summary>
|
||||
Parked,
|
||||
}
|
||||
|
||||
/// <summary>One audit row of a folded outbound operation's lifecycle (<see cref="FlowStep.Lifecycle"/>).</summary>
|
||||
/// <param name="EventId">The audit row's id.</param>
|
||||
/// <param name="Kind">Audit kind name (<c>CachedSubmit</c>, <c>ApiCallCached</c>, <c>CachedResolve</c>, <c>NotifyDeliver</c>, …).</param>
|
||||
/// <param name="Status">Audit status name (<c>Submitted</c>, <c>Forwarded</c>, <c>Attempted</c>, <c>Delivered</c>, <c>Parked</c>, …).</param>
|
||||
/// <param name="OccurredAtUtc">When the row was written.</param>
|
||||
/// <param name="Node">Node that wrote it.</param>
|
||||
/// <param name="ErrorMessage">Error summary on a failed attempt.</param>
|
||||
/// <param name="DurationMs">The row's own duration, when it has one.</param>
|
||||
public sealed record FlowLifecycleRow(
|
||||
Guid EventId,
|
||||
string Kind,
|
||||
string Status,
|
||||
DateTimeOffset OccurredAtUtc,
|
||||
string? Node,
|
||||
string? ErrorMessage,
|
||||
int? DurationMs);
|
||||
|
||||
/// <summary>
|
||||
/// One node of the flow graph — a central-side projection of a <c>ScriptTrace</c> /
|
||||
/// <c>ApiInbound.InboundRequest</c> <see cref="ZB.MOM.WW.Audit.AuditEvent"/>. Never serialized
|
||||
@@ -70,4 +97,5 @@ public sealed record FlowStep(
|
||||
string? Node,
|
||||
string? Instance,
|
||||
string? Target,
|
||||
ScriptTraceExtra? Extra);
|
||||
ScriptTraceExtra? Extra,
|
||||
IReadOnlyList<FlowLifecycleRow>? Lifecycle = null);
|
||||
|
||||
@@ -63,10 +63,12 @@ public static class FlowStepProjector
|
||||
&& string.Equals(kind, nameof(AuditKind.InboundRequest), StringComparison.Ordinal))
|
||||
return FlowStepKind.InboundRequest;
|
||||
|
||||
if (string.Equals(channel, nameof(AuditChannel.ApiOutbound), StringComparison.Ordinal)
|
||||
|| string.Equals(channel, nameof(AuditChannel.DbOutbound), StringComparison.Ordinal)
|
||||
|| string.Equals(channel, nameof(AuditChannel.Notification), StringComparison.Ordinal))
|
||||
return FlowStepKind.External;
|
||||
if (string.Equals(channel, nameof(AuditChannel.ApiOutbound), StringComparison.Ordinal))
|
||||
return FlowStepKind.ApiCall;
|
||||
if (string.Equals(channel, nameof(AuditChannel.DbOutbound), StringComparison.Ordinal))
|
||||
return FlowStepKind.DbCall;
|
||||
if (string.Equals(channel, nameof(AuditChannel.Notification), StringComparison.Ordinal))
|
||||
return FlowStepKind.Notify;
|
||||
|
||||
return FlowStepKind.Unknown;
|
||||
}
|
||||
|
||||
@@ -385,20 +385,20 @@ public static class FlowShapePredictor
|
||||
if (receiverText == "ExternalSystem" && member is "Call" or "CachedCall")
|
||||
{
|
||||
var (system, dependent) = LiteralOrExpr(args.FirstOrDefault()?.Expression);
|
||||
_b.AddNode(FlowStepKind.External, _lane, $"ExternalSystem.{member}({system})", dependent, null);
|
||||
_b.AddNode(FlowStepKind.ApiCall, _lane, $"ExternalSystem.{member}({system})", dependent, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (receiverText == "Database")
|
||||
{
|
||||
var (conn, dependent) = LiteralOrExpr(args.FirstOrDefault()?.Expression);
|
||||
_b.AddNode(FlowStepKind.External, _lane, $"Database.{member}({conn})", dependent, null);
|
||||
_b.AddNode(FlowStepKind.DbCall, _lane, $"Database.{member}({conn})", dependent, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (receiverText == "Notify" && member == "Send")
|
||||
{
|
||||
_b.AddNode(FlowStepKind.External, _lane, "Notify.Send", false, null);
|
||||
_b.AddNode(FlowStepKind.Notify, _lane, "Notify.Send", false, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user