fix(flow-debugger): review follow-ups — redact runner-synthesized rows, spawner rule, budget doc, CLI ceiling note (arch-review remediation)
Code review of the merged arch-review remediation (Tasks 1-13) found four items. 1. FlowDebugRunner returned its OWN synthesized rows un-redacted. RunMethodAsync's inbound row and RunValueChangeAsync's root row were handed to WriteCentralAsync (whose writer redacts a private copy) and then put RAW into the returned Events — so a Live run's caller saw the unfiltered row while Replay of the same execution showed the redacted one, defeating F1's "Live shows what Replay reads back". Both methods now resolve IAuditRedactor once per run and place the filtered copy into the result (including the early-return path on a failed attribute write); the raw row still reaches the writer, which redacts itself. 2. FlowGraphBuilder's spawner search degenerated to "the last step of the parent". Since executions are ordered as contiguous blocks, position[p] < childPosition holds for EVERY parent step, so a run shaped ScriptRun -> Write(A) -> Wait -> Write(B) whose Write(A) fired the cascade drew the Spawn edge from Write(B). New rule, in order: (a) the last non-async, non-Response parent step occurring at or before the child's first step — parent and child of a trigger cascade share the site clock, so this picks the write that actually fired it; (b) the position rule as fallback, which is the mixed-clock case F5 was written for (a child clock behind its parent's, where no parent step satisfies (a)); (c) parentChain[0]. The class remark's stale "Replay order is unchanged" claim is replaced with what actually happens to a central Database.* call made after Route.To().Call. 3. FlowTraceCollector's byte budget counts UTF-16 chars of DetailsJson only, so "well under the 4 MB gRPC default" overstated it. Documented honestly: ~1 MB of UTF-8 for ASCII payloads, ~3 MB worst case for non-ASCII, both under 4 MB before framing — and raise it only against a real serialized-size measurement. 4. CLI --timeout-seconds help + README row now say the management endpoint caps flow-debug run-method at ManagementService:LongRunningCommandTimeout (default 5 min), so a larger client value still 504s at the server's ceiling. Deferred-work register rows 112 (site-side inline trace truncation is invisible to the operator — needs a proto field) and 113 (the F2 method-entry site scope gates routed traffic only; ApiMethods have no site affinity to scope against) record what this review deliberately did not fix. Tests: FlowGraphBuilderTests gains the same-clock Write(A)/Wait/Write(B) cascade (red before the rule change, and the clock-behind test still passes on rule (b)); FlowDebugRunnerTests extends the redactor test to the inbound row and adds two value-change cases (settled poll, failed write). Commons 793, ManagementService 615, CentralUI 1264, CLI 462 — all green. Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
@@ -113,7 +113,11 @@ public sealed class FlowDebugRunner
|
||||
}
|
||||
|
||||
var root = Guid.NewGuid();
|
||||
var collector = new FlowTraceCollector(redactor: sp.GetService<IAuditRedactor>());
|
||||
// One redactor for the whole run: the collector filters the emitted rows, and the runner
|
||||
// filters the rows it synthesizes ITSELF (review follow-up) — the central writer redacts a
|
||||
// private copy, so without this the caller's copy of the inbound row would be the raw one.
|
||||
var redactor = sp.GetService<IAuditRedactor>();
|
||||
var collector = new FlowTraceCollector(redactor: redactor);
|
||||
var startedAt = DateTimeOffset.UtcNow;
|
||||
var sw = Stopwatch.StartNew();
|
||||
var timeout = TimeSpan.FromSeconds(Math.Max(1, method.TimeoutSeconds));
|
||||
@@ -177,7 +181,7 @@ public sealed class FlowDebugRunner
|
||||
// clock and site rows the site's, so re-sorting by OccurredAtUtc lets a skewed site clock float
|
||||
// its steps ahead of the RouteCall that started them. (The value-change path below keeps its
|
||||
// OrderBy: every row it merges was stamped by the same site.)
|
||||
var ordered = new List<AuditEvent>(events.Count + 1) { inboundRow };
|
||||
var ordered = new List<AuditEvent>(events.Count + 1) { redactor?.Apply(inboundRow) ?? inboundRow };
|
||||
ordered.AddRange(events);
|
||||
|
||||
return new FlowDebugRunResult(
|
||||
@@ -210,6 +214,9 @@ public sealed class FlowDebugRunner
|
||||
var root = Guid.NewGuid();
|
||||
var startedAt = DateTimeOffset.UtcNow;
|
||||
var sw = Stopwatch.StartNew();
|
||||
// Review follow-up: the root row is synthesized here, so the copy handed back must be
|
||||
// redacted by this method — WriteCentralAsync's writer only redacts its own private copy.
|
||||
var redactor = sp.GetService<IAuditRedactor>();
|
||||
// IInstanceRouter is the same seam RouteHelper uses (central binds it to the comms layer).
|
||||
var router = sp.GetRequiredService<IInstanceRouter>();
|
||||
var write = await router.RouteToSetAttributesAsync(
|
||||
@@ -239,11 +246,12 @@ public sealed class FlowDebugRunner
|
||||
Writes = [new AttributeWriteDetail(cmd.AttributeName, cmd.Value, write.Success, write.ErrorMessage)],
|
||||
}.ToJson());
|
||||
await WriteCentralAsync(sp, rootRow, ct).ConfigureAwait(false);
|
||||
var redactedRoot = redactor?.Apply(rootRow) ?? rootRow;
|
||||
|
||||
if (!write.Success)
|
||||
{
|
||||
return new FlowDebugRunResult(root, false, write.ErrorMessage ?? "Attribute write failed", "WRITE_FAILED",
|
||||
[rootRow], false, startedAt, DateTimeOffset.UtcNow);
|
||||
[redactedRoot], false, startedAt, DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
// Poll the site for the traced run(s) the write spawned — matched by ParentExecutionId
|
||||
@@ -281,7 +289,7 @@ public sealed class FlowDebugRunner
|
||||
await _delay(PollInterval, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var ordered = new List<AuditEvent> { rootRow };
|
||||
var ordered = new List<AuditEvent> { redactedRoot };
|
||||
ordered.AddRange(known.Values.OrderBy(e => e.OccurredAtUtc).ThenBy(e => e.EventId));
|
||||
var completed = known.Values.Any(e =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user