Resolve all 46 low-severity architecture review findings

This commit is contained in:
Joseph Doherty
2026-09-09 22:56:36 -04:00
parent fef3e7675f
commit 9ee402bbcd
139 changed files with 2621 additions and 1082 deletions
@@ -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>