Commit Graph

1667 Commits

Author SHA1 Message Date
Joseph Doherty 8047de6903 fix(central-ui): Flow Debugger picker — site change clears the pick (test); wider column, shorter placeholder
Pins the site-change behaviour the picker's reference contract exists for: after
picking an instance, switching the site select empties both the page's
_selectedInstanceId and the picker's field text (the new site hands it a
different instance list), and leaves the script select on its placeholder.

Layout: the Instance column goes col-md-2 → col-md-3 so a full instance name
fits — the script-entry branch still sums to 8 of 12 (site 2 + instance 3 +
script 3), and Method never renders beside them — with the placeholder
shortened to "Search instances…" for the narrower field.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 09:49:05 -04:00
Joseph Doherty 0f97317933 feat(central-ui): Flow Debugger picks the instance through InstancePicker
The script entry's flat instance <select> is replaced by the shared
InstancePicker combobox (design 2026-09-08 §3): the site's areas load
alongside its instances in LoadInstancesAsync and are held in fields, so the
picker sees one stable list reference per site and only closes its panel on a
real site change. Picking a row raises the id, which OnInstancePickedAsync
applies before running the unchanged OnInstanceChangedAsync — the deep link,
the localStorage restore and the site-scope gate are untouched.

Tests: the five instance-select .Change("7") call sites go through a
PickInstance helper (open the panel, click the data-instance-id row), plus a
new test that areas open collapsed and only reveal the instance once both
levels are expanded, and one that ?instanceId= shows the instance name in the
picker field.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 09:45:07 -04:00
Joseph Doherty 3c3a7cbbc1 fix(central-ui): InstancePicker — field above the backdrop; site change always closes; reference contract documented; tests
Three Task 2 review findings:

1. The outside-click backdrop is a fixed full-viewport layer at z-index 1040,
   but the input-group was unpositioned — so once the panel was open a click on
   the field or the chevron hit the backdrop and closed the panel instead of
   letting the operator keep typing. The field now carries .ip-field
   (position:relative, z-index 1042), stacking backdrop < panel < field. Pinned
   in bUnit by the class (the z-index itself is scoped CSS, verified visually).

2. OnParametersSet closes the panel on ANY lists change, not only when the
   selection disappeared: an open panel is showing the previous site's tree,
   which is wrong even when the selected instance happens to exist in both.
   The reference-equality contract on Areas/Instances is now stated on both
   parameters — pass a stable reference, replace it only when the set changed.

3. Tests: the chevron closes an open panel; two pickers mint distinct panel ids
   (and matching aria-controls); an unrelated parameter change with the SAME
   Instances reference leaves a half-typed filter and the open panel alone.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 09:39:46 -04:00
Joseph Doherty 621d6bfd94 feat(central-ui): InstancePicker search filter + keyboard (Enter/Escape/ArrowDown)
Typing in the field is the filter: the forest is rebuilt to the matching
instances plus their ancestor areas and force-expanded so every match is on
screen; clearing the text restores the full forest, collapsed. Both bulk
expansions are deferred to OnAfterRenderAsync, because the TreeView only exists
once the panel has rendered.

Enter picks the first instance the operator can actually SEE — document order,
descending into a branch only when the tree reports it expanded — so with areas
collapsed it picks the first root instance, and with a filter (every branch
expanded) it degrades to plain document order. Escape closes and restores the
field text from the current selection; ArrowDown hands keyboard navigation to
the tree via a new additive TreeView.FocusFirstVisibleAsync(), which reuses the
existing roving-tabindex _focusNeedsApply path rather than adding a second
focus mechanism.

No preventDefault: Blazor's flag is all-or-nothing per element, and neither
default is harmful (Enter in a text input outside a form does nothing,
ArrowDown just moves the caret) while suppressing Tab would trap the control.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 09:37:44 -04:00
Joseph Doherty 05e4f3ca0f Merge branch 'worktree-agent-a6cee26f4fe0abdd1' into feat/instance-picker 2026-09-08 09:29:45 -04:00
Joseph Doherty ba1ac38333 feat(central-ui): InstancePicker — combobox over the site's area tree
A shared Bootstrap-only combobox that replaces the flat instance <select> on
Flow Debugger and Debug View (design docs/plans/2026-09-08-instance-picker-design.md,
plan task 2): a text field over a dropdown panel hosting TreeView across the
InstanceTreeBuilder forest, every area collapsed on open. Clicking an area row
toggles it, clicking an instance row selects it, closes the panel and raises
SelectedInstanceIdChanged. Outside clicks close through a transparent fixed
backdrop — the same pattern TreeView uses for its context menu, so no JavaScript.

A site change (new Areas/Instances references) closes the panel and clears the
field when the selection no longer resolves; a parent-driven SelectedInstanceId
change re-derives the field text while closed. ItemLabel customises row and field
text (Debug View wants a "(State)" suffix) while search still matches UniqueName.

TreeView gains one additive public ToggleExpanded(key) — the chevron's existing
private toggle, exposed so a consumer whose own NodeContent owns the label click
can toggle the branch.

Search filtering and keyboard handling land in task 3; _filter/Rebuild() are in
place for them.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 09:29:21 -04:00
Joseph Doherty 8e6b748029 fix(central-ui): InstanceTreeBuilder — orphaned instances surface at root; read-only Children; edge tests
Review follow-ups on f12ba2f9:
- An instance whose AreaId points at an area outside the supplied list was
  silently dropped; it now surfaces at the root, mirroring the orphaned-area
  rule, and stays filterable.
- InstanceTreeNode.Children is an IReadOnlyList over a private backing list,
  composed by the builder through an internal AddChild — the record no longer
  hands callers a mutable list.
- Extra tests: unknown-area instance (root + filterable), 3-level nesting,
  a ParentAreaId cycle that must neither loop nor throw, and a filter that
  matches instance names only (an area name match alone does not keep it).

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 09:26:14 -04:00
Joseph Doherty f12ba2f9eb feat(central-ui): InstanceTreeBuilder — area/instance forest for the instance picker
Task 1 of the Instance Picker plan (docs/plans/2026-09-08-instance-picker.md).
Pure, DI-free builder that nests a site's areas by ParentAreaId and hangs each
area's instances beneath it, mirroring Topology's ordering (child areas first,
then that level's instances; OrdinalIgnoreCase sorts) with unparented instances
at the root after the root areas. Orphaned areas become roots, a case-insensitive
filter keeps matching instances plus their ancestors and drops empty branches,
and a ParentAreaId cycle can never recurse forever.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 09:22:11 -04:00
Joseph Doherty 859922ba17 fix(central-ui): Flow Debugger spawn edges — real arrowhead + bolt badge; spawner picked by trigger attribute
The trigger-cascade (Spawn) edge used a lightning-bolt marker IN PLACE of the arrowhead. On the upward
Tags→Site hop the bolt sat on top of the riser and read as a broken arrow (rig screenshot 2026-09-08).
Every connector now ends in an ordinary arrowhead; the bolt is a badge (circle + bolt) centred on the
connector's vertical run (FlowGraphView.SpawnBadgePoint, kept in step with OrthogonalPath/AsyncPath).

Same screenshot, second defect: the cascade spawned by "RecipeDownloadFlag = false" was drawn from the
LAST write of the burst (RecipeProcessedFlag) because all four writes share a millisecond and the
spawner rule was time-only. FlowGraphBuilder now prefers the parent's last AttributeWrite of the attribute
the child's triggerAttribute names (at or before the child, else the last such write); the time/position
rules remain the fallback when the child names no attribute or the parent never wrote it.

Verified live on the rebuilt rig (DelmiaSim01 / DelmiaReceiverSimulator): both spawn edges show an
arrowhead + badge, the second one originates from the RecipeDownloadFlag write. bUnit + builder tests
updated/added; Playwright FlowDebuggerTests 4/4.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 09:04:39 -04:00
Joseph Doherty 7792b76b12 Merge branch 'worktree-agent-a33b0136b18715bde' into feat/flow-debugger-script-entry 2026-09-08 01:42:42 -04:00
Joseph Doherty ca9be68d91 fix(management): FlowDebugRunner — transport faults still write the root row; write-kind completion is script-specific; standby/unreadable-trigger tests
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:42:17 -04:00
Joseph Doherty 7197c3986f fix(central-ui): FlowDebugService — re-flatten on each instance selection; safe Ask fallback
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:41:14 -04:00
Joseph Doherty a2bfc79e3a fix(central-ui): Flow Debugger — remount ParameterValueForm per script so stale form state cannot block Run
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:32:29 -04:00
Joseph Doherty d9d8fdde05 Merge branch 'worktree-agent-aa4aca38eb138ea83' into feat/flow-debugger-script-entry 2026-09-08 01:29:38 -04:00
Joseph Doherty 0887c69500 test(rig): flow debugger script entry — Playwright direct-call run + verify-delmia-notifier step H2
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:29:10 -04:00
Joseph Doherty a4e8518e69 fix(management): FlowDebugRunner.RunScriptAsync — always write the root row after a routed call; timeout + unserializable-return tests
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:27:44 -04:00
Joseph Doherty 11d32335af Merge branch 'worktree-agent-ac243fcc404f04015' into feat/flow-debugger-script-entry 2026-09-08 01:23:36 -04:00
Joseph Doherty e1865f9ab3 fix(central-ui): FlowDebugService — reuse the runner's default script timeout, warn on flatten failure, pin one-flatten memo + parameter threading
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:23:18 -04:00
Joseph Doherty 9404ff87ca Merge branch 'worktree-agent-a6dd4922425ca12c2' into feat/flow-debugger-script-entry 2026-09-08 01:21:55 -04:00
Joseph Doherty 6fe67e147c feat(central-ui): Flow Debugger script entry — site/instance/script picker, value field or ParameterValueForm by trigger kind
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:21:30 -04:00
Joseph Doherty e5ca703714 Merge branch 'worktree-agent-a5a04e3ef680c61db' into feat/flow-debugger-script-entry 2026-09-08 01:19:07 -04:00
Joseph Doherty 3f4fde3e35 feat(management): dispatch FlowDebugRunScriptCommand (Deployer-only, long-running) on the ManagementActor
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:18:52 -04:00
Joseph Doherty b74d3f4de9 Merge branch 'worktree-agent-abbb97d8899ed576d' into feat/flow-debugger-script-entry 2026-09-08 01:17:43 -04:00
Joseph Doherty 45c6b27d99 fix(cli): flow-debug help text names the script entry; replay role note; run-script guard tests
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:17:32 -04:00
Joseph Doherty 51ce72404b Merge branch 'worktree-agent-a33b0136b18715bde' into feat/flow-debugger-script-entry 2026-09-08 01:16:34 -04:00
Joseph Doherty f1db90cb8a Merge branch 'worktree-agent-a5e1884475a5b785e' into feat/flow-debugger-script-entry 2026-09-08 01:16:11 -04:00
Joseph Doherty a5979b5cfd feat(management): FlowDebugRunner.RunScriptAsync — write-kind vs direct traced call by trigger kind (flow debugger script entry)
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:15:57 -04:00
Joseph Doherty 912e519954 fix(central-ui): ParameterValueForm — raise ValidChanged on schema set so required-field validity is never stale
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:15:40 -04:00
Joseph Doherty acf4ab3d4d Merge branch 'worktree-agent-ac243fcc404f04015' into feat/flow-debugger-script-entry 2026-09-08 01:15:04 -04:00
Joseph Doherty 5b185c93ff feat(central-ui): IFlowDebugService script entry — GetInstanceScripts / PredictScript / RunScript over the flattened configuration
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:14:40 -04:00
Joseph Doherty 067a858ce9 feat(cli): flow-debug run-script — run a site script by name through the flow debugger
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:13:15 -04:00
Joseph Doherty 001123d930 Merge branch 'worktree-agent-a5e1884475a5b785e' into feat/flow-debugger-script-entry 2026-09-08 01:11:04 -04:00
Joseph Doherty a437e0c14f Merge branch 'worktree-agent-a59908183c5526bc7' into feat/flow-debugger-script-entry 2026-09-08 01:09:34 -04:00
Joseph Doherty a36c813b89 feat(central-ui): ParameterValueForm.ValidChanged — additive validity callback
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:09:30 -04:00
Joseph Doherty 30de51c48a test(commons): FlowGraphBuilder — a RouteCall root (flow debugger direct script call) builds Central → Site
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:08:39 -04:00
Joseph Doherty 952b41f0f7 feat(commons): ScriptTriggerAttribute — monitored attribute of a ValueChange/Conditional trigger (flow debugger script entry)
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-08 01:07:38 -04:00
Joseph Doherty f17bae093a 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
2026-09-06 14:44:34 -04:00
Joseph Doherty 6c9ea39f12 test(central-ui): drill-in test pins the method form's updated '7–10 audit rows per call' help text
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-06 14:36:45 -04:00
Joseph Doherty ccc08e4151 Merge branch 'worktree-agent-aa9b7f4762bd3718d' into fix/flow-debugger-arch-review 2026-09-06 14:29:04 -04:00
Joseph Doherty 262de38cee fix(audit): trace/outbound fault continuations run on the default scheduler and central emitters log (arch-review F8)
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-06 14:28:44 -04:00
Joseph Doherty acf68bda26 Merge branch 'worktree-agent-aa79b4c585b494d98' into fix/flow-debugger-arch-review 2026-09-06 14:28:36 -04:00
Joseph Doherty 1dfb9cee20 fix(commons): Flow graph orders executions by emission sequence, timestamps only within one clock (arch-review F5)
Central rows carry central's clock, site rows the site's, so the global
OccurredAtUtc sort let a 1-2 s skew float a whole site execution ahead of the
inbound row / RouteCall that started it, and pushed the trigger-cascade spawner
search onto its parentChain[0] fallback.

FlowGraphBuilder.Build now orders executions by the input index of their
earliest-given row and orders steps by timestamp only INSIDE one execution
(one clock), breaking ties by kind rank then input index. A folded outbound
node keeps its earliest-by-time row's identity, which need not be the row given
first, so its index is the minimum over the rows folded into it. The spawner
search uses position in the ordered list rather than a raw timestamp compare.
FlowDebugRunner.RunMethodAsync keeps the collector's emission order instead of
re-sorting; the value-change path keeps its OrderBy (all rows share one site
clock). Replay input arrives timestamp-sorted from the repository, so its order
is unchanged.

The DelmiaShaped test fixture is still shuffled within each execution (the
inside-one-execution timestamp rule is unchanged) but now lists the executions
in emission order, since that is what fixes their relative order.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-06 14:28:12 -04:00
Joseph Doherty 8645138274 test(site-runtime): flush the tracking-store queue before the boundary-timestamp test's direct UPDATE
The single-writer queue (d91135f9) made RecordEnqueueAsync return on enqueue, so the
test's verifier-connection UPDATE raced the pump and touched zero rows whenever the pump
lost. Every other verifier-connection test already flushed; this one was missed.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-06 14:23:47 -04:00
Joseph Doherty b8752cc855 Merge branch 'worktree-agent-a6faa2ddb571f8394' into fix/flow-debugger-arch-review 2026-09-06 14:22:32 -04:00
Joseph Doherty 4ddabde26b Merge branch 'worktree-agent-aa92b89ae2edc1ead' into fix/flow-debugger-arch-review 2026-09-06 14:22:15 -04:00
Joseph Doherty 105541b655 fix(site-runtime): shed or rate-limited traced runs emit a Failed ScriptRun row (arch-review F7)
A traced run refused before launch — by the per-script in-flight cap
(MaxConcurrentRunsPerScript) or by MinTimeBetweenRuns — emitted no ScriptTrace row at
all, so the Flow Debugger reported NO_TRIGGER_WITHIN_WINDOW ("no value-change trigger on
this attribute, or no device echo"), which is wrong in both cases and sends the operator
to check the wrong things.

Both guards now leave a Failed ScriptTrace/ScriptRun row naming the guard that refused
the run, parented to the writing execution and carrying the trigger detail. The new
ScriptRunLauncher.EmitSuppressedRun reuses EmitScriptRun with elapsedMs 0 and no
collector (a shed run has no reply to ride on — the row reaches the debugger via
PullTraceEvents / Replay), resolving the writer and identity providers from a fresh
scope because it runs on the actor thread. Best-effort, never throws, no-op untraced.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-06 14:22:11 -04:00
Joseph Doherty 453c3dec18 fix(inbound-api): RouteCall audit row honours the method's SkipBodyCapture override (arch-review F11)
RouteHelper/RouteTarget carry a new captureBodies flag (default true) threaded
through every With* copy, same shape as the site-scope field Task 3 added.
InboundScriptExecutor resolves IOptionsMonitor<AuditLogOptions> from the
per-execution DI scope and looks up PerTargetOverrides[method.Name]?.SkipBodyCapture
exactly as AuditWriteMiddleware.cs:183-187 does for the inbound row, then calls
.WithBodyCapture(!skip) when building the script's Route helper. When set, the
always-on ScriptTrace.RouteCall row omits its request/response summary, so an
operator who suppressed a method's body from the audit log doesn't get it back
via the routed call's arguments (derived from that same body).

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-06 14:22:01 -04:00
Joseph Doherty def6bab318 fix(management): Flow Debugger Live result is the redacted, capped trace copy (arch-review F1, central half)
RunMethodAsync collected sink events into a plain ConcurrentQueue and always
reported Truncated: false, so the Live result carried RAW audit events — the
per-target SQL-parameter overrides, header scrubbing and 8 KB / 64 KB payload
caps that Replay reads back never applied to what the operator saw, and the
reply had no upper bound at all.

It now collects through the same FlowTraceCollector the site launcher uses,
constructed with the host's IAuditRedactor (AddAuditLog's singleton; a host
without one collects raw but is still capped by count and bytes), and reports
Truncated from the collector. Complete() is called as soon as the run returns,
so a late un-awaited call cannot mutate the snapshot the reply is built from.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-06 14:21:07 -04:00
Joseph Doherty 05b97b596d Merge branch 'worktree-agent-a5b63f3a0707e4e46' into fix/flow-debugger-arch-review 2026-09-06 14:15:58 -04:00
Joseph Doherty e79452d2de fix(site-runtime): Flow Debugger inline trace copy is redacted and byte-capped at the collector (arch-review F1, site half)
Every emit site hands FlowTraceCollector the same AuditEvent it hands the
writer, but redaction and payload capping lived inside the writers, which
return a NEW filtered record — so the inline copy that rides
ScriptCallResult.TraceEvents / RouteToCallResponse.TraceEvents was the RAW
event: no per-target SQL-parameter redaction, no header scrubbing, no 8 KB /
64 KB truncation. Live and Replay of the same execution could therefore show
different payloads, and the 500-event-only cap left the reply free to breach
the 4 MB gRPC receive limit — which would fail a real call, since the same
reply carries the script's return value.

The collector now applies the host IAuditRedactor itself (outside the lock;
the writers keep their own idempotent Apply) and enforces a byte budget
(DefaultMaxBytes = 1 MB over the redacted DetailsJson) alongside the event
cap, flagging Truncated when either trips. It moves to Commons
(Types/Debugging) so the central FlowDebugRunner can share it.

The redactor is a singleton, so ScriptRunLauncher resolves it from the ROOT
provider and keeps constructing the collector before the try — the traced
ScriptRun row is emitted from the catch blocks too, including the
shed-at-dequeue path that throws before the DI scope exists.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
2026-09-06 14:15:38 -04:00