Resolve all 46 low-severity architecture review findings
This commit is contained in:
@@ -66,16 +66,17 @@ public static class FlowGraphBuilder
|
||||
// D2: a ScriptLog row is a line, never a node. Partition BEFORE the input-order index below, so
|
||||
// `seq` indexes only real step rows and a line can never shift an execution's display position.
|
||||
var lines = new List<FlowLogLine>();
|
||||
var stepEvents = new List<AuditEvent>(events.Count);
|
||||
var stepEvents = new List<(AuditEvent Event, AuditDetails Details)>(events.Count);
|
||||
foreach (var e in events)
|
||||
{
|
||||
if (FlowStepProjector.IsLogLine(e))
|
||||
lines.Add(FlowStepProjector.ToLogLine(e));
|
||||
var details = AuditDetailsCodec.Deserialize(e.DetailsJson);
|
||||
if (FlowStepProjector.IsLogLine(details))
|
||||
lines.Add(FlowStepProjector.ToLogLine(e, details));
|
||||
else
|
||||
stepEvents.Add(e);
|
||||
stepEvents.Add((e, details));
|
||||
}
|
||||
|
||||
events = stepEvents;
|
||||
events = stepEvents.Select(e => e.Event).ToList();
|
||||
|
||||
if (events.Count == 0)
|
||||
return new FlowGraph(Array.Empty<FlowStep>(), Array.Empty<FlowEdge>(), Predicted: false, Truncated: truncated,
|
||||
@@ -105,7 +106,7 @@ public static class FlowGraphBuilder
|
||||
return index;
|
||||
}
|
||||
|
||||
var folded = Fold(events).ToList();
|
||||
var folded = Fold(stepEvents).ToList();
|
||||
var firstSeqByExecution = folded
|
||||
.GroupBy(s => s.ExecutionId ?? Guid.Empty)
|
||||
.ToDictionary(g => g.Key, g => g.Min(InputIndex));
|
||||
@@ -205,7 +206,7 @@ public static class FlowGraphBuilder
|
||||
|
||||
var firstChained = chain.FirstOrDefault(s => !IsAsync(s));
|
||||
if (firstChained is not null && spawner.EventId != firstChained.EventId)
|
||||
edges.Add(new FlowEdge(spawner.EventId, firstChained.EventId, CrossesBoundary(spawner, firstChained), External: false, Spawn: spawn));
|
||||
edges.Add(new FlowEdge(spawner.EventId, firstChained.EventId, CrossesBoundary(spawner, firstChained), External: spawn && first.Extra?.TriggerSource == "inferred-device-update", Spawn: spawn));
|
||||
}
|
||||
|
||||
// 3. Satisfied wait ⇐ preceding write in the same execution (inferred device echo).
|
||||
@@ -219,6 +220,7 @@ public static class FlowGraphBuilder
|
||||
for (var j = i - 1; j >= 0; j--)
|
||||
{
|
||||
if (chain[j].Kind != FlowStepKind.AttributeWrite) continue;
|
||||
edges.RemoveAll(e => e.FromEventId == chain[j].EventId && e.ToEventId == wait.EventId);
|
||||
edges.Add(new FlowEdge(chain[j].EventId, wait.EventId, CrossesBoundary: false, External: true));
|
||||
break;
|
||||
}
|
||||
@@ -376,14 +378,14 @@ public static class FlowGraphBuilder
|
||||
/// Projects every event and folds the outbound rows of one operation (same kind + <c>CorrelationId</c>) into one step
|
||||
/// carrying the whole lifecycle. Non-outbound rows and null-correlation outbound rows pass through one-to-one.
|
||||
/// </summary>
|
||||
private static IEnumerable<FlowStep> Fold(IReadOnlyList<AuditEvent> events)
|
||||
private static IEnumerable<FlowStep> Fold(IReadOnlyList<(AuditEvent Event, AuditDetails Details)> events)
|
||||
{
|
||||
var groups = new Dictionary<(FlowStepKind Kind, Guid Correlation), List<(FlowStep Step, AuditEvent Evt)>>();
|
||||
var groups = new Dictionary<(FlowStepKind Kind, Guid Correlation), List<(FlowStep Step, FlowLifecycleRow Row)>>();
|
||||
var order = new List<object>(events.Count); // FlowStep, or the group key — preserves first-seen order
|
||||
|
||||
foreach (var evt in events)
|
||||
foreach (var (evt, details) in events)
|
||||
{
|
||||
var step = FlowStepProjector.Project(evt);
|
||||
var step = FlowStepProjector.Project(evt, details);
|
||||
if (!FlowStepProjector.IsOutbound(step.Kind))
|
||||
{
|
||||
order.Add(step);
|
||||
@@ -392,7 +394,7 @@ public static class FlowGraphBuilder
|
||||
|
||||
if (evt.CorrelationId is not { } correlation)
|
||||
{
|
||||
order.Add(step with { Lifecycle = [FlowStepProjector.ToLifecycleRow(evt)] });
|
||||
order.Add(step with { Lifecycle = [FlowStepProjector.ToLifecycleRow(evt, details)] });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -403,7 +405,7 @@ public static class FlowGraphBuilder
|
||||
groups[key] = group;
|
||||
order.Add(key);
|
||||
}
|
||||
group.Add((step, evt));
|
||||
group.Add((step, FlowStepProjector.ToLifecycleRow(evt, details)));
|
||||
}
|
||||
|
||||
foreach (var item in order)
|
||||
@@ -416,7 +418,6 @@ public static class FlowGraphBuilder
|
||||
|
||||
var key = ((FlowStepKind, Guid))item;
|
||||
var rows = groups[key]
|
||||
.Select(r => (r.Step, Row: FlowStepProjector.ToLifecycleRow(r.Evt)))
|
||||
.OrderBy(r => r.Row.OccurredAtUtc)
|
||||
.ThenBy(r => LifecycleRank(r.Row.Kind, r.Row.Status))
|
||||
.ThenBy(r => r.Row.EventId)
|
||||
@@ -435,6 +436,7 @@ public static class FlowGraphBuilder
|
||||
Subtitle = latest.Subtitle,
|
||||
OutputJson = latest.OutputJson,
|
||||
ErrorMessage = latest.ErrorMessage,
|
||||
ErrorDetail = latest.ErrorDetail,
|
||||
DurationMs = rows.Count == 1 && !terminal ? null : duration,
|
||||
Lifecycle = lifecycle,
|
||||
};
|
||||
|
||||
@@ -65,6 +65,7 @@ public enum FlowStepOutcome
|
||||
/// <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>
|
||||
/// <param name="ErrorDetail">Exception detail from this lifecycle row.</param>
|
||||
public sealed record FlowLifecycleRow(
|
||||
Guid EventId,
|
||||
string Kind,
|
||||
@@ -72,7 +73,8 @@ public sealed record FlowLifecycleRow(
|
||||
DateTimeOffset OccurredAtUtc,
|
||||
string? Node,
|
||||
string? ErrorMessage,
|
||||
int? DurationMs);
|
||||
int? DurationMs,
|
||||
string? ErrorDetail = null);
|
||||
|
||||
/// <summary>
|
||||
/// One log line folded onto a step (<see cref="FlowStep.Logs"/>; design 2026-09-09 D1/D8) — the
|
||||
|
||||
@@ -17,7 +17,11 @@ public static class FlowStepProjector
|
||||
public static FlowStep Project(AuditEvent evt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evt);
|
||||
var d = AuditDetailsCodec.Deserialize(evt.DetailsJson);
|
||||
return Project(evt, AuditDetailsCodec.Deserialize(evt.DetailsJson));
|
||||
}
|
||||
|
||||
internal static FlowStep Project(AuditEvent evt, AuditDetails d)
|
||||
{
|
||||
var extra = ScriptTraceExtra.TryParse(d.Extra);
|
||||
var kind = ResolveKind(d.Channel, d.Kind);
|
||||
var lane = ResolveLane(kind, d.SourceSiteId);
|
||||
@@ -52,6 +56,11 @@ public static class FlowStepProjector
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evt);
|
||||
var d = AuditDetailsCodec.Deserialize(evt.DetailsJson);
|
||||
return IsLogLine(d);
|
||||
}
|
||||
|
||||
internal static bool IsLogLine(AuditDetails d)
|
||||
{
|
||||
return string.Equals(d.Channel, nameof(AuditChannel.ScriptTrace), StringComparison.Ordinal)
|
||||
&& string.Equals(d.Kind, nameof(AuditKind.ScriptLog), StringComparison.Ordinal);
|
||||
}
|
||||
@@ -60,7 +69,11 @@ public static class FlowStepProjector
|
||||
public static FlowLogLine ToLogLine(AuditEvent evt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evt);
|
||||
var d = AuditDetailsCodec.Deserialize(evt.DetailsJson);
|
||||
return ToLogLine(evt, AuditDetailsCodec.Deserialize(evt.DetailsJson));
|
||||
}
|
||||
|
||||
internal static FlowLogLine ToLogLine(AuditEvent evt, AuditDetails d)
|
||||
{
|
||||
var x = ScriptTraceExtra.TryParse(d.Extra);
|
||||
return new FlowLogLine(
|
||||
EventId: evt.EventId,
|
||||
@@ -79,7 +92,11 @@ public static class FlowStepProjector
|
||||
public static FlowLifecycleRow ToLifecycleRow(AuditEvent evt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evt);
|
||||
var d = AuditDetailsCodec.Deserialize(evt.DetailsJson);
|
||||
return ToLifecycleRow(evt, AuditDetailsCodec.Deserialize(evt.DetailsJson));
|
||||
}
|
||||
|
||||
internal static FlowLifecycleRow ToLifecycleRow(AuditEvent evt, AuditDetails d)
|
||||
{
|
||||
return new FlowLifecycleRow(
|
||||
EventId: evt.EventId,
|
||||
Kind: d.Kind ?? string.Empty,
|
||||
@@ -87,7 +104,8 @@ public static class FlowStepProjector
|
||||
OccurredAtUtc: evt.OccurredAtUtc,
|
||||
Node: evt.SourceNode,
|
||||
ErrorMessage: d.ErrorMessage,
|
||||
DurationMs: d.DurationMs);
|
||||
DurationMs: d.DurationMs,
|
||||
ErrorDetail: d.ErrorDetail);
|
||||
}
|
||||
|
||||
/// <summary>Maps a channel/kind string pair onto a step kind.</summary>
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed record ScriptTraceExtra
|
||||
/// <summary>Encoded value that fired a value-change run.</summary>
|
||||
public string? TriggerValue { get; init; }
|
||||
|
||||
/// <summary>Where the triggering change came from: <c>value-change</c> (the actor cannot tell a static write from a device echo).</summary>
|
||||
/// <summary>Where the triggering change came from: <c>value-change</c> for an observed write, or <c>inferred-device-update</c> for the first device publish in a trace-arm window (not a confirmed echo).</summary>
|
||||
public string? TriggerSource { get; init; }
|
||||
|
||||
/// <summary>On a <c>RouteCall</c> row: the routed site run's <c>ExecutionId</c>.</summary>
|
||||
|
||||
Reference in New Issue
Block a user