The Flow Debugger design doc still described the pre-remediation shape: a
debug-stream subscription for the value-change entry, Deployer everywhere, an
unredacted inline trace copy and "~7 audit rows per call". Reconciled with what
shipped on this branch (Tasks 1-12):
- design D6 / §5.4 / §9 / §10: RequireFlowDebugger page policy with Deployer-only
execution (F3), the PullTraceEvents polling path with AttributeValueUpdate
fields 7/8 recorded as RESERVED and unconsumed (F12), the redacting + byte-
capped shared FlowTraceCollector (F1), route-time site scope (F2), the derived
client waits (F4), the 7-10 row estimate, the doubled central audit write per
routed call, and the inbound-row-after-the-run ordering.
- sitestream.proto: fields 7/8 marked reserved-but-populated, numbers not to be
reused (comment only; no regeneration needed).
- Component-InboundAPI: SkipBodyCapture on the RouteCall row (F11),
SchemaRefResolver's public visibility, WithSiteScope / SITE_SCOPE_DENIED (F2),
Replay's wider role set (F3).
- Component-CentralUI: page policy vs Deployer-only controls (F3), route-time
site scope (F2), client wait derivation (F4).
- "7-10 audit rows per call (3 of them trace-only)" everywhere the estimate is
quoted: the method form, both CLI --trace-all-calls help strings, the CLI
README, the ApiMethod entity doc, the manual migration script and the two
Flow Debugger plans.
- arch-review doc: new §7 Resolution listing F1-F13 with the fixing commit, plus
the tracking-store test race fixed alongside them (86451382).
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
28 KiB
Flow Debugger — design
Status: implemented 2026-09-06 (branch feat/flow-debugger); plan 2026-09-05-flow-debugger.md (24 tasks, all complete). Residual follow-ups: deferred-work register row 110. v1.1 outbound calls (a fourth External lane for API / DB / notification nodes, lifecycle folding, async/spawn edges, pending refresh) — see the addendum plan 2026-09-06-flow-debugger-outbound-calls.md; §4's "three lanes fixed" is amended by its D2. v1.2 follow-ups (node Retry/Discard, ?correlationId= deep links to the report pages, the prediction circuit-deadlock fix, Playwright coverage) — plan 2026-09-06-flow-debugger-followups.md; register row 111 closed.
Mockup: Claude Design canvas Flow Debugger Mockup — https://claude.ai/code/artifact/3f3e81e5-8e5e-4333-bdf6-59256108494d (Direction A swimlane boards + two low-fi alternates + decision notes).
Owner components: Central UI (#page), Inbound API, Site Runtime, Audit Log, Communication, Script Analysis, Configuration Database.
1. What it is
A Central UI page, Deployment → Flow Debugger, that runs one entry point with operator-supplied inputs and renders every step the run passes through as a swimlane flowchart: lanes are where code ran (Central, the Site the route landed on, the tag source), nodes are steps (request, script run, route call, attribute write, wait, response) each carrying its input, output, duration and status. Clicking a node opens a tabbed detail panel (Input · Output · Attribute writes · Console · Timing).
Worked example, and the acceptance flow for the whole feature: the Delmia recipe download
(docs/api-specs/Delmia-Recipe-Download-API.md §4) — inbound POST /api/DelmiaRecipeDownload →
central method script → Route.To(MachineCode).Call("ProcessRecipeDownload") → site Call script
on the DelmiaReceiver child → seven attribute writes → wait for Galaxy to raise
RecipeProcessedFlag → read result → response.
Decisions taken (2026-09-05)
| # | Decision | Rejected |
|---|---|---|
| D1 | Two modes: Live and Replay. Live is a real run with real values, real writes, audited like any inbound call. Replay executes nothing — it rebuilds the flow from an existing ExecutionId's trace rows. | Dry run (execute scripts but capture writes/ES/DB/Notify instead of sending). Needs an interception seam in the site runtime that does not exist and would fork the semantics of every helper. |
| D2 | Two entry points in v1: an Inbound API method, and a value-change-triggered site script on a named instance. Both may land on a site script on instance X. | Alarm/timer trigger simulation, direct "call script X" — see §11. |
| D3 | Value-change on a data-sourced attribute does a REAL tag write through the instance actor's normal write path; the device echo fires the trigger. Correlation is by a short-lived trace arm on the instance actor (§5.3). | A debug-only "inject value" command that synthesises the change without touching the device — leaves the instance's cached value diverged from the device until the next real update. |
| D4 | Live gets its trace inline; Replay reads audit rows. Site audit telemetry forwards on a 5 s busy / 30 s idle cadence (AuditLog:SiteTelemetry), too slow to drive an interactive run, so the routed reply carries the site steps back and the same steps are also written as audit rows for Replay. |
Live waiting on the audit pipeline. |
| D5 | Tracing is gated, not always-on. Sites run scripts at high rates from timers and value changes; a trace flag rides beside ParentExecutionId and only flagged runs emit trace rows. One exception is always-on: a central RouteCall row per Route.To().Call (inbound-driven volume, joins the two halves of a Replay). |
Tracing every run (volume), or a purely time-window join. |
| D6 | Live run is a Deployer act. RequireDeployment gates the Run controls and the two run commands; the page and Replay are open to OperationalAudit roles + Deployer (RequireFlowDebugger, arch-review F3). |
A separate RequireOperator gate for Live. |
2. What exists today (reused, not rebuilt)
- Correlation ids already thread the chain.
AuditWriteMiddlewaremints anExecutionIdper inbound request;RouteHelper.Callstamps it asRouteToCallRequest.ParentExecutionId;InstanceActorforwards it onScriptCallRequest.ParentExecutionId; the site run mints its own id (ScriptRuntimeContext._executionId) and records the parent; a static write carries the writer's id (SetStaticAttributeCommand.SourceExecutionId→AttributeValueChanged.SourceExecutionId) into the value-change / alarm cascade.IAuditLogRepository.GetExecutionTreeAsyncwalks it. - Audit captures the ends.
AuditKind.InboundRequeststores the full request and response (up toInboundMaxBytes);ApiCall/DbWrite/NotifySendrows are per-execution. - Central already drives live instances.
SiteCommandServicerouting group:RouteToCall,RouteToGetAttributes,RouteToSetAttributes,RouteToWaitForAttribute; the Test Run sandbox'sSandboxInstanceGatewayruns a central script against a deployed instance over exactly this transport. - Roslyn compiles every script centrally (
ScriptAnalysisService,ScriptTrustValidator's syntax walk), so the predicted graph is a sibling walker, not new infrastructure. - Attribute writes already have one path.
WriteAttributeBatchRequest→InstanceActor.HandleWriteAttributeBatch→WriteTagBatchRequestto the DCL for data-sourced attributes;SetStaticAttributeCommandfor static ones.
3. What is missing (the build)
| Gap | Why it blocks | Closed by |
|---|---|---|
G1 No record that a site script ran — no AuditKind for a run; per-run site events are opt-in, aggregated, id-less. |
Replay would show the inbound row and any outbound leaf rows with the site script itself absent. | §5.1 ScriptRun trace row. |
G2 Attribute writes and waits are invisible — WriteAttributeBatchRequest/WaitForAttributeRequest carry no execution id; nothing records outcomes. |
The Delmia flow's steps 5–7 cannot be drawn. | §5.1 AttributeWrite/AttributeWait rows + §6 additive fields. |
| G3 Route calls are not audited centrally and the routed reply does not return the child run's id. | Central and site halves of a Replay cannot be joined without guessing. | §5.1 RouteCall row (always-on) + RouteToCallResponse.ChildExecutionId. |
| G4 No trace gate. | Always-on would flood AuditLog from timer/value-change scripts. |
§5.2 trace flag + per-method TraceAllCalls. |
| G5 A value-change entry needs a way to fire the trigger and correlate the echo. | Data-sourced echoes arrive as roots. | §5.3 trace arm. |
| G6 No page, no graph builder, no predictor. | — | §7, §8. |
| G7 Live runs have no API key, so no actor on the inbound row. | Audit would show an anonymous inbound call. | §9. |
4. User experience
Reference the mockup's two full-fidelity boards; this section only records what the mockup fixes.
Toolbar. Entry point (Inbound API method | Value-change trigger) + a picker for the method or
the instance + script; Mode (Live | Replay); Edit inputs, Load from audit…, Run.
Status strip. State chip (Not run / Running / Completed / Failed / Timed out), run
number, wall-clock and node, total duration, counts (steps, boundaries crossed, attribute writes),
root ExecutionId linking to the existing Execution Chain page.
Flow panel. Three lanes fixed in v1: Central, Site ({siteId} · {instance}), Tags
({connection} → {source}). Before a run the graph is predicted (§8) — nodes whose target
depends on an input value or on live site state render dashed. After a run every node is real;
predicted nodes that did not execute (an early-return branch not taken) render greyed. Node =
kind eyebrow + duration, title, one-line in/out summary; the selected node gets the accent border.
Connectors: solid = executed edge, accent = crossed a gRPC boundary, dashed = external actor
(the tag source responding). Zoom −/+/Fit only; no pan-by-drag in v1.
Detail panel (below the flow, tabbed): Input, Output, Attribute writes (attribute · before ·
written · quality · ack ms), Console, Timing; a right column Where this ran (site/node,
instance, composed child, connection, attribute kind, source line) with Open in Debug View and
Audit rows (n) drill-ins.
Inputs form (the Step 1 detail before a run): generated from the method's
ParameterDefinitions (type, required) or, for a value-change entry, one field for the trigger
attribute's new value plus read-only current value/quality. Fill from last real call pre-fills
from the most recent InboundRequest row for that method.
Replay. Load from audit… takes an ExecutionId (or arrives via a new Open in Flow Debugger
action on the Audit Log drawer and the Execution Chain page). The graph is built from trace rows
(§7.3); a run that was not traced degrades honestly — inbound row + RouteCall row + outbound
leaf rows, with the site interior rendered as one collapsed not traced node.
5. Trace model
5.1 Trace rows are audit rows
A new AuditChannel.ScriptTrace with kinds:
| Kind | Emitted by | Payload (DetailsJson) |
|---|---|---|
RouteCall |
Central RouteHelper.Call (always-on, not gated) |
target instance, script, args (redacted per existing policy), return value, success/error, duration ms, ChildExecutionId |
ScriptRun |
Site ScriptRunLauncher on run completion (gated) |
script, instance, trigger {kind, attribute, value, source} (a value-change run's trigger rides here — there is no separate TriggerFired kind), parameters, return value, success/error, duration ms, source node |
AttributeWrite |
Site ScriptRuntimeContext.SetAttribute / WriteBatchAndWait (gated) |
one row per write or batch: writes[{attribute, written, success, error}], trigger attribute |
AttributeWait |
Site ScriptRuntimeContext.WaitInternal (gated) |
attribute, predicate/target, timeout, outcome (satisfied/timeout/cancelled/error), elapsed ms, observed value |
Who emits AttributeWrite/AttributeWait (planning correction 2026-09-05): every accessor path
funnels into three ScriptRuntimeContext methods — SetAttribute, WriteBatchAndWait and
WaitInternal — and the context already holds IAuditWriter, _executionId and
_parentExecutionId, so the InstanceActor needs no new ids and no new request fields. Batch-write
echoes are not armed in v1 — only SetStaticAttributeCommand writes arm (§5.3).
Every row carries the run's ExecutionId and ParentExecutionId exactly as the existing channels
do, so the Execution Chain walk, IX_AuditLog_ParentExecution, telemetry forwarding, reconciliation
pulls, site 7-day retention and the central purge all apply unchanged. RouteCall is a
central-direct-write row (ICentralAuditWriter, SourceNode = central-x); the site kinds go
through the site IAuditWriter (SQLite hot path → telemetry → central).
Retention: ScriptTrace gets a per-channel override in AuditLog:PerChannelRetentionDays
(rig default 30 days; must stay under the 365-day global window per the existing rule).
5.2 The trace gate
A boolean Trace rides beside ParentExecutionId everywhere the parent id already travels:
RouteToCallRequest, RouteToCallResponse (echoed), ScriptCallRequest,
SetStaticAttributeCommand, and the new SourceExecutionId carriers in §6. Site-side the flag
lands in ScriptRuntimeContext as IsTraced and every helper consults it before emitting a
ScriptTrace row. Nested runs inherit it (Scripts.Call → ScriptCallRequest.Trace;
static-write cascades → SetStaticAttributeCommand.Trace → AttributeValueChanged.Trace →
the spawned run), so a traced root traces its whole tree and nothing else.
Two ways a run becomes traced:
- The debugger's Live run sets it on the root.
ApiMethod.TraceAllCalls(newbit NOT NULL DEFAULT 0column, EF migration, CLIapi-method update --trace-all-calls, Central UI checkbox on the method form): every real production call to that method is traced, so Replay of a real Delmia download is complete. Meant for low-volume integration methods; the method form shows the warning that it adds ~5 audit rows per call.
5.3 Value-change entry and the trace arm
The debugger writes the chosen attribute through the instance actor's normal path:
- Static attribute:
RouteToSetAttributes→SetStaticAttributeCommandwithSourceExecutionId = <debugger root id>,Trace = true. The existing cascade already stamps the resultingAttributeValueChangedand the spawned run'sParentExecutionId. Nothing new. - Data-sourced attribute: the same
RouteToSetAttributes→SetStaticAttributeCommandpath —InstanceActoralready dispatches data-sourced attributes to the DCL (HandleSetDataAttribute→WriteTagRequest); noWriteAttributeBatchRequestchange is needed. The device echo comes back through the DCL as an ordinaryAttributeValueChangedwith no source. Trace arm: whenHandleSetDataAttributeseescommand.Trace && command.SourceExecutionId != nullit records(attribute → sourceExecutionId, expiresAt = now + TraceArmWindow)(default 30 s,SiteRuntimeOptions, one entry per attribute, newest wins). The next DCL value change for that attribute — whatever the value, so a device that clamps or rejects the write still shows what it actually did — is published withSourceExecutionIdandTraceset (consumed inHandleTagValueUpdate), and the arm is consumed. An arm that expires unconsumed emits nothing; the debugger renders the trigger node asno echo within 30 s.
The arm exists only for traced writes, so production behaviour is unchanged. Alarm cascades that the echo also fires inherit the parent id exactly as static cascades do today.
5.4 Live-run inline transport
A trace step IS an AuditEvent in channel ScriptTrace — there is no separate wire record.
RouteToCallResponse gains additive ChildExecutionId (Guid?) and TraceEvents
(IReadOnlyList<AuditEvent>?, the very events that became the site's audit rows, in emission
order); on the wire it is repeated AuditEventDto trace_events (the DTO sitestream.proto
already defines). The site collects events in a per-run FlowTraceCollector (Commons
Types/Debugging/, shared with the central FlowDebugRunner) held by ScriptRuntimeContext and
attaches the list to the ScriptCallResult → RouteToCallResponse when Trace is set. The
collector applies IAuditRedactor inside Add, so the inline record is the same filtered event
the writer persists, and it caps at MaxTraceEventsPerRun (500) or 1 MB of summed
DetailsJson (DefaultMaxBytes), whichever comes first; beyond either bound it keeps the earlier
events, sets Truncated, and the audit rows remain complete (arch-review F1). A site on an older build ignores
Trace, returns no TraceEvents, and the UI labels its lane site not traced (older build).
FlowStep is a central-side projection (Commons/Types/Debugging/FlowStepProjector)
computed from AuditEvent + AuditDetails; nothing serializes it.
For the value-change entry there is no routed reply to ride on. The runner polls the site's
PullTraceEvents RPC by ParentExecutionId (1 s cadence, 2 s settle window, WaitSeconds bound) —
simpler than the debug-stream subscription originally planned. AttributeValueUpdate.source_execution_id/trace
(fields 7/8) are populated by the site and consumed by nothing; they are reserved for a future
debug-stream path and must not be reused. The RPC sits behind the same PSK interceptor as
PullAuditEvents and reads the site audit SQLite by ExecutionId / ParentExecutionId — one
additive RPC, no streaming change.
6. Message and wire changes (all additive)
| Contract | Change |
|---|---|
RouteToCallRequest |
+ bool Trace = false |
RouteToCallResponse |
+ Guid? ChildExecutionId, + IReadOnlyList<AuditEvent>? TraceEvents |
ScriptCallRequest / ScriptCallResult |
+ bool Trace; + Guid? ExecutionId, + IReadOnlyList<AuditEvent>? TraceEvents on the result |
RouteToSetAttributesRequest |
+ Guid? ParentExecutionId, + bool Trace |
SetStaticAttributeCommand, AttributeValueChanged |
+ Guid? SourceExecutionId (where missing), + bool Trace |
site_command.proto |
trace on RouteToCallRequestDto/RouteToSetAttributesRequestDto; child_execution_id + repeated AuditEventDto trace_events on RouteToCallResponseDto (no FlowStepDto); regenerate with docker/regen-proto.sh sitecommand |
sitestream.proto |
+ PullTraceEvents RPC (PullTraceEventsRequest { repeated execution_ids, since_utc, max_events } → PullTraceEventsResponse { repeated AuditEventDto events }); AttributeValueUpdate + string source_execution_id = 7, + bool trace = 8 so the debug stream can hand the debugger the spawned id |
ApiMethod |
+ TraceAllCalls bit NOT NULL DEFAULT 0 (EF migration + manual SQL script) |
AuditChannel/AuditKind |
ScriptTrace; RouteCall, ScriptRun, AttributeWrite, AttributeWait |
FlowStep (Commons Types/Debugging/FlowStep.cs, projection only — never on the wire):
EventId, ExecutionId, ParentExecutionId, Kind, Lane (Central/Site/Tags), Title,
Subtitle, OccurredAtUtc, DurationMs, Outcome, InputJson, OutputJson, ErrorMessage,
SiteId/Node/Instance/Target, and a kind-specific ScriptTraceExtra parsed from
AuditDetails.Extra.
Field numbers never reused; proto3 defaults are the negotiation, as for stream batching.
7. Central components
7.1 FlowDebugService (Central UI, scoped)
PredictAsync(entry)→ §8.RunMethodAsync/RunValueChangeAsync/ReplayAsync→ Live runs and Replay areFlowDebugRunMethodCommand/FlowDebugRunValueChangeCommand/FlowDebugReplayCommandon theManagementActor(Deployer role, long-running timeout). TheManagementActoris a per-node actor (AkkaHostedService,/user/management), not a singleton. The run-method handler consultsIActiveNodeGateand refuses on a standby with error codeSTANDBY_NODEnaming the design rule (Component-InboundAPI: method scripts andRoute.Tonever run on a standby); Traefik routes UI traffic to the active node so this is rare. The Central UI calls it throughManagementActorHolder(theDataConnectionMoveServicepattern) and the CLI through/management— one path, one audit story. The inbound row for a Live run is written by the handler viaICentralAuditWriter(kindInboundRequest, actor = operator username,Extra.source = "flow-debugger"). Inbound entry:InboundScriptExecutor.ExecuteAsync(method, inputs, route, timeout, parentExecutionId: root, trace: true)with aRouteHelperwhoseTraceflag is set and whose inline trace sink collects theRouteCallrow plus the site'sTraceEvents. Value-change entry: §5.3 + §5.4.ReplayAsync(executionId)→IAuditLogRepository.GetExecutionTreeAsync+ a newGetTraceEventsAsync(rootExecutionId)(allScriptTracerows in the tree, plus theInboundRequestrow) →FlowGraphBuilder.
7.2 FlowGraphBuilder (pure, Commons or Central UI)
Steps → FlowGraph { Lanes, Nodes, Edges }. Lane assignment by Where (central node → Central;
site node → Site; AttributeWrite → Tags). Edges follow emission order within an
execution and parent→child across executions; an edge whose endpoints sit on different lanes is
marked CrossesBoundary. A predicted graph and a real graph share the model; the page overlays
them (predicted-but-not-executed → greyed).
7.3 Page and renderer
Components/Pages/Deployment/FlowDebugger.razor (+ .razor.cs), nav item under Deployment after
Debug View. Renderer is a custom SVG component FlowGraphView (no third-party charting or diagram
libraries, matching KpiTrendChart): fixed 110 px lanes, nodes placed by topological column,
connectors as orthogonal paths with arrow markers; zoom via a viewBox scale. Detail panel is a
plain Bootstrap tab strip. Persisted per-browser: last entry point and inputs (same
localStorage pattern Debug View uses for its selection).
8. Predicted graph (static analysis)
FlowShapePredictor in Script Analysis (#25), a CSharpSyntaxWalker beside
ScriptTrustValidator.HardeningWalker, over the entry script and every script it can reach:
- Recognised sites:
Route.To(x).Call(name, …)/.SetAttributes/.WaitForAttribute/.GetAttributes;Scripts.Call(name)/CallShared;Attributes["a"] = v,Children["c"].Attributes["a"] = v;WriteBatchAndWaitAsync(…),WaitAsync(…),WaitForAsync(…);ExternalSystem.Call/CachedCall;Database.*;Notify.Send;return. - Constant string arguments resolve; anything else yields a dashed node (
target = input value).Route.To(constant)resolves the instance → template → the named script's body, which is then walked too (one hop; depth cap 4, the same as the runtime call-depth cap). - Branches: an
if … returnbefore a recognised site becomes anearly returnannotation on the edge, as the mockup shows; the predictor does not attempt path-sensitivity beyond that. - Output is a
FlowGraphwithPredicted = true; the page renders it before the run and overlays the real graph after.
Compile is the existing cached compile (CachingScriptMetadataResolver attached like every other
surface); the walker is syntax-only and never executes anything.
9. Security, authorisation, audit
- Page visibility:
RequireFlowDebugger— theOperationalAuditroles plus Deployer (AuthorizationPolicies.FlowDebuggerAccessRoles= Deployer, Administrator, Viewer), so an auditor can open the page and Replay a past execution (arch-review F3). Live run:RequireDeployment(same as Debug View) — decided 2026-09-05: a Live run writes real tags, but the Deployer already deploys and redeploys the scripts that do exactly that, so no separate Operator gate. The two run commands, the Live controls, the parked-node Retry/Discard actions and the?methodId=/?instanceId=drill-ins stay Deployer-only, and the page pins Replay mode for a non-Deployer. Replay:FlowDebugReplayCommandaccepts the sameOperationalAuditroles (it reads audit rows). No Verifier step — the debugger is a diagnostic, not a production write control, and the audit row is the safeguard. - Inbound audit row for a Live run: the shared
AuditEvent.Actorcarries the operator's username (today it carries the API-key name);Extra.source = "flow-debugger",Extra.runNumber;Targetis the method as usual. API-key authentication is bypassed (there is no key), parameter validation is not —ParameterValidatorruns exactly as for HTTP so a bad input fails the same way. The method'sTimeoutSecondsapplies. Site-scope (SiteScopeService) restricts which instances a value-change entry may target. The method entry enforces the operator's site scope at route time (RouteHelper.WithSiteScope,SITE_SCOPE_DENIED→ManagementUnauthorized, arch-review F2): the runner resolves the operator's permitted numeric site ids to site identifiers and hands them toRouteHelper, which refuses an out-of-scope target before any site traffic; the denial is rethrown asSiteScopeViolationExceptionafter the inbound audit row is written, so the attempt is recorded under the operator's name. Administrators and unscoped users are exempt, exactly asEnforceSiteScopetreats them. - Trace payloads pass through the existing redaction policy on BOTH copies: the stored row (writer)
and the inline Live copy (
FlowTraceCollectorappliesIAuditRedactorand a 1 MB byte budget beside the 500-event cap — arch-review F1). The row-level caps are unchanged (auth headers redacted, 8 KB cap, 64 KB on error rows). Console output is capped the same way. - Nothing here touches the PSK boundary;
PullTraceEventssits behindControlPlaneAuthInterceptor.
10. Volume and failure behaviour
- Untraced production is unchanged except one
RouteCallrow per routed call (inbound-driven). Every routed call adds one centralRouteCallinsert — at inbound-driven rates that doubles the per-call central audit write (accepted, D5). - A traced Delmia call emits ≈ 1
InboundRequest+ 1RouteCall+ 1ScriptRun+ 2AttributeWrite+ 1AttributeWait+ 1 value-changeScriptRun≈ 7–10 rows (a cached DB write and a notification add 3 trace-only rows).TraceAllCallson a 100-call/day method is ~700–1 000 rows/day. - Trace emission is best-effort like all audit (a failed trace write never fails the run).
- Live run timeout = method timeout +
FlowDebugRunGrace(10 s); a timeout renders whatever steps arrived and marks the runTimed out. The site's trace rows still land later, so Replay of the same ExecutionId is complete. The client waits longer than the server on purpose: the UI derives its Ask wait from the same bound (RunAskTimeoutFor= method timeout + grace + 15 s slack, floored at 30 s;ValueChangeAskTimeoutFor= the wait window + 15 s) and the CLI'srun-method --timeout-secondsdefaults to 120 whilerun-value-changewaits--wait-seconds + 25(arch-review F4). - The Live run's inbound row is written AFTER the run completes, so a Replay taken during a
long Live run shows only the
RouteCall. - Older site build: no
TraceEvents, lane labelled; older central build: ignores new fields; proto3 defaults negotiate as for stream batching. - Mixed-version caveat: an OLD central ingesting
ScriptTracerows from a NEW site parses unknown channel/kind names to theAuditRowProjectionfallbacks (ApiInbound/InboundRequest); upgrade central first.
11. Out of scope (recorded so they are not rediscovered)
- Dry run (D1). Alarm-trigger and timer-trigger entry points. Step-into for
CallSharedinline shared scripts (they execute inline; onlyScripts.Callspawns a traceable child). Live step-by-step / breakpoints. Editing a script from the debugger. Cross-run comparison. AVertical tracelist toggle (mockup Direction B) — cheap follow-up on the sameFlowGraph. Line-level pinning (Direction C) — needs per-statement instrumentation. Console output capture onScriptRunrows (no script console exists today).
12. Testing
- Pure:
FlowGraphBuilderTests(lane/edge/boundary rules, truncation),FlowShapePredictorTests(each recognised site, constant vs input-dependent target, one-hop route resolution, depth cap, early-return annotation),FlowTraceCollectorTests(cap +Truncated). - Site runtime:
ScriptRuntimeContexttraced write emitsAttributeWrite;InstanceActorarms; echo consumes the arm and stampsSourceExecutionId/Trace; expiry emits nothing; untraced write emits nothing and arms nothing (the production-unchanged pin).ScriptRunLauncheremitsScriptRunonly when traced.ScriptRuntimeContext.WaitInternalemitsAttributeWaitfor satisfied/timeout. - Inbound API:
RouteHelper.Callalways writesRouteCall;ChildExecutionIdround-trips;Tracepropagates toRouteToCallRequest. - Communication: DTO mapper round-trips for every new field; an old-shape response (no
TraceEvents) maps to null. - Audit: new kinds accepted by ingest; per-channel retention deletes
ScriptTracerows early;GetTraceEventsAsyncreturns the tree's rows in order. - Central UI (bUnit): page renders predicted graph, overlays real graph, Run button hidden
without Deployer, Replay degrades to the collapsed
not tracednode. - Rig: extend
docker/verify-delmia-notifier.shwith step H — a Live run ofDelmiaRecipeDownloadagainstDelmiaSim01from the page's service, asserting 9 steps, and a Replay of the same ExecutionId producing the same graph.
13. Documents to update with the implementation
Component-CentralUI.md (new page), Component-InboundAPI.md (RouteCall row, TraceAllCalls,
Live-run actor), Component-SiteRuntime.md (trace collector, trace arm, gated emission),
Component-AuditLog.md (ScriptTrace channel, retention override, PullTraceEvents),
Component-Communication.md (proto changes), Component-ScriptAnalysis.md (predictor),
Component-ConfigurationDatabase.md (migration), README.md component table if a component is
added (none planned — the debugger is a Central UI page plus seams in existing components),
CLAUDE.md key-decisions bullet, CLI README (--trace-all-calls), deferred-work register row for
the Direction B list toggle.