Flow Debugger · design addendum · 2026-09-06

Outbound calls on a fourth lane

What the swimlane looks like once ExternalSystem, Database and Notify calls become nodes. The flow below is the rig demo the plan seeds (FlowOutboundDemo → DelmiaSim01.OutboundDemo): one synchronous REST call, one cached database write, one notification.

Inbound API methodValue-change trigger
FlowOutboundDemo
LiveReplay
Refresh outcomes Run
Completed Started05:31:02.118Z Total371 ms Steps9 Boundaries2 Writes2 Pending1 outcomes still arriving — refreshing every 5 s (1:40 left)
DbCall · External

RigDb · CachedWrite

DelmiaSim01 · OutboundDemo
Delivered
SQL + parametersResultLifecycleTiming
TimeRowStatusNodeError
05:31:02.301CachedSubmitSubmittedsite-a / node-a—
+0.9 sDbWriteCachedForwardednode-a—
+4.1 sCachedResolveDeliverednode-a—
Where this ran
Site / nodesite-a / node-a InstanceDelmiaSim01 ConnectionRigDb Operation7c2e91d4… Delivered bynode-a · +4.1 s
Open in Debug ViewAudit rows (3)

Connectors and pills

Executed edge — the run continued here. Crossed the central ↔ site gRPC boundary. New. Async branch — CachedCall / CachedWrite / Notify.Send returned at once; the run carried on without it. Inferred device echo (a write that satisfied a wait). New. Trigger spawn — this write fired a value-change script. OkSync call answered, or cached operation delivered. PendingSubmitted / forwarded, not yet resolved. The page keeps refreshing for up to two minutes after a Live run. ParkedRetries exhausted or permanent failure downstream; retry or discard from Site Calls / Notifications. FailedA sync call failed and the script received the error.

What changed in the graph

External is a lane, not a caller-side node. Like Tags, it holds actors outside ScadaBridge — a REST endpoint, a database, the mail relay — so a hop that leaves the system reads as one. Its gutter lists the targets touched.

One node per operation. A cached call's four audit rows (Submitted → Forwarded → Attempted → Delivered) fold into one node; the pill is the latest status and the Lifecycle tab is the history. A notification folds the site's NotifySend with central's NotifyDeliver rows.

Sync calls sit in the chain; async ones branch. The script blocks on ExternalSystem.Call, so the chain runs through it (write → API call → write). It does not block on the cached write or the notification, so those hang off the script run on a dashed edge and the chain skips them.

The Response now starts from the route call, not from whatever step happened last — a cascaded write on another instance is not what answered the HTTP request.

How audit rows become nodes

Script callAudit rows already writtenNodeLive inlineArrives later
ExternalSystem.CallApiOutbound / ApiCall Delivered|Failed, request + response, durationAPI CALL · in chainwhole node—
ExternalSystem.CachedCallCachedSubmit → ApiCallCached Forwarded / Attempted ×n → CachedResolveAPI CALL · async branchSubmittedForwarded, attempts, Delivered / Parked
Database.Query / ExecuteDbOutbound / DbWrite, Extra {op, rows…}DATABASE · in chainwhole node—
Database.CachedWriteCachedSubmit → DbWriteCached → CachedResolveDATABASE · async branchSubmittedthe rest
Notify.Sendsite NotifySend Submitted → central NotifyDeliver Attempted / Delivered / ParkedNOTIFY · async branchSubmittedcentral outbox rows
central method script Database.*none today → gets DbOutbound / DbWrite (closes an audit gap)DATABASE · in chain, Central-side callerwhole node—

Plan at a glance

  1. Commons types — kinds ApiCall / DbCall / Notify, lane External, outcomes Pending / Parked, FlowLifecycleRow.
  2. Projector maps the three outbound channels onto the External lane.
  3. Graph builder folds lifecycles into one node, async branch edges, spawn edges, Response from the route call.
  4. Site runtime routes outbound rows through the existing trace collector.
  5. Central audits method-script Database.* and feeds the trace sink.
  6. Replay filter widens to the three channels; site pull verified.
  7. Predictor emits the three kinds with async edges.
  8. FlowGraphView — fourth lane, pills, async/spawn connectors.
  9. Step detail — Lifecycle tab, request/response labels.
  10. Page — refresh pending outcomes after a Live run.
  11. CLI, specs, rig demo + verification step I.

Not in this slice

Retry / Discard on a parked node (Site Calls and the Notification report own those actions), deep links by operation id into those pages, and per-attempt request diffs. Each is a register row, not a blocker.

Repo plan: docs/plans/2026-09-06-flow-debugger-outbound-calls.md.