feat(flow-debugger): outbound calls on the External lane — CLI projection, specs, rig demo + verification step I
CLI flow-debug table now projects through FlowGraphBuilder (one row per step, outbound lifecycles folded, new 'lifecycle' column; per-event fallback kept). Specs: Component-CentralUI (four lanes, node anatomy, pending refresh), Component-AuditLog (central method-script Database.* now audited; replay reads the three outbound channels), Component-InboundAPI (DbOutbound row per helper call), Component-ScriptAnalysis (REQ-SA-7 kinds + Async edges), design doc v1.1 status line, CLAUDE.md bullet, deferred-work register row 111 (Retry/ Discard + ?correlationId= deep links deferred). Rig: seed-flow-outbound-demo.sh (RigRest/RigDb/FlowDemo + OutboundDemo script + FlowOutboundDemo method/key) and verify-delmia-notifier.sh step I (live rows inline, replay until terminal). Rig verification result follows in its own commit. Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
@@ -103,6 +103,7 @@ All 7 fix-now items landed via PLAN-04/05/06/07/08 (verified in review 08 round
|
||||
| 108 | ~~**Inbound API keys are a PER-NODE SQLite store — a central failover invalidates every inbound client's key**~~ **RESOLVED 2026-09-02 — `ZB.MOM.WW.Auth` 0.3.0 (new `ZB.MOM.WW.Auth.ApiKeys.SqlServer` package) + ScadaBridge `ScadaBridge:InboundApi:ApiKeyStore:Provider=SqlServer`.** A central pair now shares one row set: both nodes' key stores, admin stores, and audit stores point at the same `zbauth` schema in the configuration database (connection string defaults to `ScadaBridge:Database:ConfigurationDb` — never a `${secret:}` reference, to avoid a circular dependency on the SQL-backed secrets store), so create/revoke/rotate/scope changes are cluster-wide immediately instead of per-node. Each node's existing SQLite file is imported once at boot (insert-where-absent, so both nodes importing is safe and the result is the union) and renamed `*.imported`. `Provider` defaults to `Sqlite` (unchanged behavior for single-node installs, MxAccessGateway, HistorianGateway); a clustered Central left on `Sqlite` now gets a boot-time `StartupValidator` Warning naming this design instead of silently keeping the gap. Both docker rigs (`docker/`, `docker-env2/`) run `Provider=SqlServer` on both centrals; `docker/sync-inbound-api-keys.sh` is deleted, superseded by the shared store. Rig proof 2026-09-03 (`docker/`, both centrals redeployed on `Provider=SqlServer`): both nodes imported their SQLite file at the same second with no PK race — central-a `1 key / 264 audit rows inserted`, central-b `0 / 26 inserted, 1 / 264 already present` (the union), files renamed `*.imported`; a key minted via `:9001` answered `200` via `:9002`, `--enabled false` via `:9001` made both nodes answer `401` and `:9002`'s own list showed `enabled=False`, re-enable via `:9002` restored `200` on `:9001`, delete via `:9002` made `:9001` answer `401`; `verify-delmia-notifier.sh` with a freshly minted key and NO file copy: A/B/E/F `YES`, C/G `NO`, active-central `docker kill` takeover **28 s**, rejoin as standby in 2 s. `docker-env2` (second pair, `ScadaBridgeConfig2`): both centrals booted, `zbauth` v1 created, empty files imported. QA (`vq03`/`vq04`) and prod (`vd03`) still run `Provider=Sqlite` — their move is a planned deployment record (`docs/deployment/deployment-records/2026-09-XX-zb-servers-inbound-key-store.md`), not yet applied. Design: `docs/plans/2026-09-02-inbound-api-key-shared-store-design.md`. Original: | `docs/api-specs/Delmia-Recipe-Download-API.md` §8; `docker/sync-inbound-api-keys.sh` header | Fix is an architecture decision, not a patch: move the store to the shared SQL Server (as Secrets 0.5.0 did for its central store) or replicate the file; either way revocation must become cluster-wide. Needs the `ZB.MOM.WW.Auth.ApiKeys` library (scadaproj), not just this repo. | Before any 2-node central goes live with inbound API clients; before the next arch-review wave. |
|
||||
| 109 | **`ZB.MOM.WW.Secrets.Replicator.SqlServer`'s store migrator retries its DDL with a flat `100 * attempt` backoff and no jitter — a latent concurrent-boot flake, open in Secrets** *(Low)*. Porting that migrator's retry loop verbatim for the Auth 0.3.0 SQL Server API-key store (register row 108) reproduced an intermittent exhaustion — about once in sixty 8-way concurrent-boot trials the five flat-backoff retries ran out before a DDL/lock conflict cleared; adding jitter to the Auth port's backoff took it to 0 failures in 180 trials. Secrets' own migrator has no concurrent-migrate test, so the same defect is presumed still open there — it just hasn't been exercised at 8-way concurrency, and a ScadaBridge central pair is only ever 2-way, so this is unlikely to bite in practice. | Found 2026-09-02 while implementing `docs/plans/2026-09-02-inbound-api-key-shared-store.md` (Auth 0.3.0 `SqlServerApiKeyStoreMigrator`) | Add jitter to `SqlServerSecretsStoreMigrator`'s backoff in `~/Desktop/scadaproj/ZB.MOM.WW.Secrets` (mirror the Auth fix) and add a `Migrate_Concurrently_FromEightTasks_AllSucceed`-shaped live test to catch a regression. Lives entirely in scadaproj, not this repo. | Next time Secrets' SQL Server migrator is touched, or if a wider-than-2-way concurrent boot of a Secrets-backed cluster is ever planned. |
|
||||
| 110 | **Flow Debugger follow-ups: the *Vertical trace* list toggle (mockup Direction B) and console capture on `ScriptRun`** *(Low)*. v1 ships the Direction A swimlane only; the mockup's Direction B — the same steps as a vertical, filterable list with the lane as a column — was kept as an alternate for long flows and screen readers, and the detail panel's *Console* tab is absent because script `Console`/log output is not captured per run (`ScriptRun` rows carry parameters, return value, error and timing, not stdout). Both are additive: a list view over the existing `FlowGraph`, and an extra `Extra.console` field on the `ScriptRun` row's `ScriptTraceExtra` (payload cap applies). | Design `docs/plans/2026-09-05-flow-debugger-design.md` §11; plan `2026-09-05-flow-debugger.md` Task 23 | Neither was asked for by an operator; the swimlane + Timing/Writes tabs cover the acceptance flow (Delmia recipe download, rig step H), and console capture needs a per-run writer seam in `ScriptRuntimeContext` plus a redaction decision. | First operator request for a list view or for script log lines in the step detail. |
|
||||
| 111 | **Flow Debugger outbound nodes: no Retry/Discard on a parked node, and no `?correlationId=` deep link from the node to the Site Calls / Notification report pages** *(Low)*. v1.1 (plan `docs/plans/2026-09-06-flow-debugger-outbound-calls.md`) draws every `ExternalSystem.*` / `Database.*` / `Notify.Send` a traced run makes as one node on the External lane and folds the cached call's / notification's later lifecycle into it (a `Parked` pill + a Lifecycle tab), but the node itself is read-only: the hint says "Retry or discard from Site Calls / Notification report" as text, without a link, and there is no per-attempt request/response diff. Both actions already exist on those pages (central→site `RetryParkedOperation` / `DiscardParkedOperation` relay; Notification Outbox Retry/Discard) and the row's `CorrelationId` (`TrackedOperationId` / `NotificationId`) is on the folded step's lifecycle rows, so a deep link is additive — the pages just don't accept a `?correlationId=` query today. | Plan 2026-09-06 "Out of scope"; `FlowStepDetail.razor` parked hint | The Site Calls page owns the retry semantics and its audit rows; duplicating the action on the debugger node would need the same Deployer gate + confirm flow. | First operator who reaches a parked node from the debugger and asks for the button. |
|
||||
| 68 | ~~**`deploy.sh` run from a git worktree silently rehomes every node's bind-mount data dir**~~ **RESOLVED 2026-08-15 (guard added in the same close-out).** The compose files' bind mounts are relative (`./site-a-node-a/data`), so a deploy from a worktree checkout mounts the worktree's copies — the rig keeps running and looks healthy, but its SQLite stores split from the main checkout's and everything written there is orphaned by the next deploy from main. Bit the rows-46–53 close-out live: the seeding agent's `rig-probe-1` deploy persisted into worktree-homed dirs and "vanished" after the orchestrator's redeploy (no tombstone, no product bug — LWW/tombstone forensics ruled out deletion; the row sat in `.claude/worktrees/…/docker/site-a-node-a/data/site-localdb.db` the whole time). Both `docker/deploy.sh` and `docker-env2/deploy.sh` now refuse to run from a `.claude/worktrees/` path (override: `SCADABRIDGE_ALLOW_WORKTREE_DEPLOY=1`). | Found during the rows-46–53 close-out | Closed 2026-08-15. | — |
|
||||
|
||||
## Resolved (verified against the code 2026-07-10)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
**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.
|
||||
**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.
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
{
|
||||
"id": 9,
|
||||
"subject": "Task 9: Flow Debugger page \u2014 pending refresh after a Live run",
|
||||
"status": "in_progress",
|
||||
"status": "completed",
|
||||
"blockedBy": [
|
||||
7,
|
||||
8
|
||||
@@ -86,7 +86,7 @@
|
||||
{
|
||||
"id": 10,
|
||||
"subject": "Task 10: CLI projection, docs, rig demo + verification",
|
||||
"status": "pending",
|
||||
"status": "in_progress",
|
||||
"blockedBy": [
|
||||
3,
|
||||
4,
|
||||
@@ -96,5 +96,5 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"lastUpdated": "2026-09-06T05:59:55.586055+00:00"
|
||||
"lastUpdated": "2026-09-06T06:03:16.400002+00:00"
|
||||
}
|
||||
@@ -57,8 +57,9 @@ boundary:
|
||||
| `POST /api/{method}` (Inbound API) | External | Inbound (invokes a script) | No (gap) |
|
||||
| `Route.To(x).Call(y)` (Inbound API → site) | Script | Central → site | Yes — `ScriptTrace.RouteCall`, **always-on** (2026-09-05) |
|
||||
| Site script run / attribute write / attribute wait under a **traced** run | Script | Site-local | Yes — `ScriptTrace.ScriptRun` / `AttributeWrite` / `AttributeWait`, **gated** by the `Trace` flag (Flow Debugger Live runs, `ApiMethod.TraceAllCalls`) |
|
||||
| Central method-script `Database.QuerySingle/Query/Execute` (`InboundDatabaseHelper`) | Script | Outbound | Yes — `DbOutbound.DbWrite` via `ICentralAuditWriter` (2026-09-06; was an unaudited gap) — `Target` = connection name, `Actor` = `api:{method}`, the request's `ExecutionId`, `Extra` = `{op, rowsAffected\|rowsReturned}` |
|
||||
|
||||
**`AuditChannel.ScriptTrace` (Flow Debugger, design `docs/plans/2026-09-05-flow-debugger-design.md` §5.1).** Trace steps ARE audit events — no parallel store. One kind is always-on (`RouteCall`, a central direct write per `Route.To().Call`, carrying the child run's id in `Extra.childExecutionId`); the three site kinds are emitted only when the run's `Trace` flag is set, ride the ordinary site `IAuditWriter` → SQLite → telemetry → central path, share the run's `ExecutionId` / `ParentExecutionId`, and are additionally returned inline on the routed reply so a Live run does not wait on the 5 s telemetry cadence. The Flow Debugger's graph (`FlowStep` / `FlowGraphBuilder`, Commons) is a central-side projection of these rows.
|
||||
**`AuditChannel.ScriptTrace` (Flow Debugger, design `docs/plans/2026-09-05-flow-debugger-design.md` §5.1).** Trace steps ARE audit events — no parallel store. One kind is always-on (`RouteCall`, a central direct write per `Route.To().Call`, carrying the child run's id in `Extra.childExecutionId`); the three site kinds are emitted only when the run's `Trace` flag is set, ride the ordinary site `IAuditWriter` → SQLite → telemetry → central path, share the run's `ExecutionId` / `ParentExecutionId`, and are additionally returned inline on the routed reply so a Live run does not wait on the 5 s telemetry cadence. The Flow Debugger's graph (`FlowStep` / `FlowGraphBuilder`, Commons) is a central-side projection of these rows — **and, since 2026-09-06, of the run's `ApiOutbound` / `DbOutbound` / `Notification` rows too** (plan `docs/plans/2026-09-06-flow-debugger-outbound-calls.md`): the outbound rows already sit in the execution tree under the run's `ExecutionId`, so the replay query (`GetTraceEventsAsync`) simply widens its channel filter to the four channels plus the inbound row, and a traced site run also passes the outbound rows it emits through the same inline trace collector as its `ScriptTrace` rows (same `AuditEvent` instance the writer receives — no duplicate construction; a collector that throws can neither fail the call nor skip the durable write). Rows written after the run returned (the S&F forwarder's `Forwarded` / `Attempted` / `CachedResolve`, the Notification Outbox's `NotifyDeliver`) are not inline — the page re-reads the tree for them.
|
||||
|
||||
Out of scope — framework traffic is not audited:
|
||||
|
||||
|
||||
@@ -216,8 +216,9 @@ Per-leaf alarm rendering (leaf nodes are individual conditions for native alarms
|
||||
- **Computed alarms render unchanged** from the prior flat-table style; the enrichment is purely additive for native rows.
|
||||
|
||||
### Flow Debugger (Deployment Role)
|
||||
- **`/deployment/flow-debugger`** (nav: Deployment → Flow Debugger; design `docs/plans/2026-09-05-flow-debugger-design.md`, plan `docs/plans/2026-09-05-flow-debugger.md`). Runs one entry point with operator-supplied inputs and renders every step the run passes through as a **three-lane swimlane** (Central / Site / Tags) — request, route call, site script run, attribute writes, attribute waits, response — each node carrying its input, output, duration and outcome; a tabbed detail panel (Input · Output · Writes · Timing, plus a *Where this ran* column) opens on the selected node with `Open in Debug View` (value-change entries, where the instance is known) and `Audit rows (n)` (→ `/audit/log?executionId=`) drill-ins.
|
||||
- **Two modes.** *Live* is a real run — the method script executes on the active central node with tracing on, or a real attribute write goes through the routed-write path and the site's triggered run is polled back — audited like any other call. *Replay* executes nothing: it rebuilds the flow from an existing execution's `ScriptTrace` audit rows (`?executionId=` drill-in from the Audit Log drawer's and the Execution Chain page's **Open in Flow Debugger** actions).
|
||||
- **`/deployment/flow-debugger`** (nav: Deployment → Flow Debugger; design `docs/plans/2026-09-05-flow-debugger-design.md`, plan `docs/plans/2026-09-05-flow-debugger.md`). Runs one entry point with operator-supplied inputs and renders every step the run passes through as a **four-lane swimlane** (Central / Site / Tags / **External**) — request, route call, site script run, attribute writes, attribute waits, **outbound API / database / notification calls**, response — each node carrying its input, output, duration and outcome; a tabbed detail panel (Input · Output · Writes *or* Lifecycle · Timing, plus a *Where this ran* column) opens on the selected node with `Open in Debug View` (value-change entries, where the instance is known) and `Audit rows (n)` (→ `/audit/log?executionId=`) drill-ins.
|
||||
- **Two modes.** *Live* is a real run — the method script executes on the active central node with tracing on, or a real attribute write goes through the routed-write path and the site's triggered run is polled back — audited like any other call. *Replay* executes nothing: it rebuilds the flow from an existing execution's `ScriptTrace` audit rows plus its `ApiOutbound` / `DbOutbound` / `Notification` rows (`?executionId=` drill-in from the Audit Log drawer's and the Execution Chain page's **Open in Flow Debugger** actions).
|
||||
- **External lane (v1.1, plan `docs/plans/2026-09-06-flow-debugger-outbound-calls.md`).** Every `ExternalSystem.Call/CachedCall`, `Database.*` and `Notify.Send` a traced run makes is a node on the fourth lane — kinds `ApiCall` / `DbCall` / `Notify` (eyebrows `API CALL` / `DATABASE` / `NOTIFY`), title = the system.method / connection / list, a status pill (`Ok` / `Pending` / `Parked` / `Failed`) plus `sync` | `cached` | `notify`. **One node per operation:** a cached call's or notification's later lifecycle rows (Forwarded → Attempted → Delivered / Parked, written minutes later by the S&F forwarder or the Notification Outbox) fold into the node (`FlowStep.Lifecycle`, a Lifecycle tab in the detail panel with *Delivered by*) instead of drawing four boxes. A sync call sits in the execution chain; a cached call or notification hangs off it on a dashed **async** connector, and a trigger cascade's spawn edge carries a bolt marker. A Live run returns only the rows the run itself emitted, so a graph left with `Pending` nodes **refreshes itself** through the Replay command every 5 s for up to 2 min (page constants), and a *Refresh outcomes* button re-reads on demand in either mode; the state chip stays *Completed* — pending outbound work is not a failed run. Retry/Discard stay on the Site Calls / Notification pages (register row 111).
|
||||
- **Two entry points.** An inbound API method (inputs form generated from the method's `ParameterDefinitions`, values coerced by declared type) or a value-change trigger on a named instance attribute (Site → Instance → Attribute pickers, site-scope filtered).
|
||||
- **Predicted graph first.** Choosing an entry point renders the statically predicted shape (`FlowShapePredictor`, Script Analysis REQ-SA-7) before anything runs — nodes whose target depends on inputs render dashed; after a run, predicted nodes the run never produced render greyed.
|
||||
- **Execution path.** `IFlowDebugService` dispatches `FlowDebugRunMethodCommand` / `FlowDebugRunValueChangeCommand` / `FlowDebugReplayCommand` to the **per-node `ManagementActor`** through `ManagementActorHolder` (the same seam the HTTP `/management` endpoint uses), so the server enforces the Deployer gate and the active-node refusal. A standby node answers the two run commands with `STANDBY_NODE`, which the page renders as an inline alert with the server message verbatim; Replay is served from any node. The same three commands back the CLI's `flow-debug run-method | run-value-change | replay`.
|
||||
|
||||
@@ -277,6 +277,8 @@ The helper API (all asynchronous — scripts `await` them; bounded by the method
|
||||
- `await Database.QueryAsync("connectionName", sql, parameters)` — all rows as case-insensitive column→value dictionaries.
|
||||
- `await Database.ExecuteAsync("connectionName", sql, parameters)` — run a write (INSERT/UPDATE/DELETE/DDL); returns rows affected. **Writes are permitted** — the move-in integration records results, not just reads them.
|
||||
|
||||
**Every helper call is audited (2026-09-06).** Each `QuerySingle` / `Query` / `Execute` writes one `AuditChannel.DbOutbound` / `AuditKind.DbWrite` row through `ICentralAuditWriter` — `Status` `Delivered` or `Failed`, `Target` = the connection name, `Actor` = `api:{methodName}`, the request's `ExecutionId`, `DurationMs`, the SQL + bound parameter names/values as `RequestSummary` (capped and redacted at write time by the shared audit redactor exactly like a site `AuditingDbCommand` row), `Extra` = `{"op":"read","rowsReturned":n}` / `{"op":"write","rowsAffected":n}`, `SourceNode` from `INodeIdentityProvider`. Best-effort: an audit fault never reaches the script, and the original exception still rethrows. Under a Flow Debugger Live run the same instance is also handed to the route's trace sink so the call appears as a `DbCall` node on the External lane (Component-CentralUI.md → Flow Debugger). This closed the outbound-DB gap Component-AuditLog.md's scope table used to list for central method scripts.
|
||||
|
||||
Containment rules (enforced, not advisory):
|
||||
|
||||
- **Named connections only.** `connectionName` selects one of the connections configured
|
||||
|
||||
@@ -222,13 +222,14 @@ The decorator changes **no resolution result**, only object identity: it returns
|
||||
|
||||
### REQ-SA-7: Flow Shape Predictor (`FlowShapePredictor`)
|
||||
|
||||
`FlowShapePredictor` (Flow Debugger design `docs/plans/2026-09-05-flow-debugger-design.md` §8) predicts, from script **syntax** alone, the swimlane shape a run would produce before anything executes: `PredictInboundMethod(methodName, methodScript, resolveRouteScript, maxDepth = 4)` and `PredictSiteScript(scriptName, scriptBody, maxDepth = 4)` return a `PredictedFlow` of `PredictedNode(Id, Kind, Lane, Title, InputDependent, Note)` + `PredictedEdge(From, To, CrossesBoundary)`.
|
||||
`FlowShapePredictor` (Flow Debugger design `docs/plans/2026-09-05-flow-debugger-design.md` §8) predicts, from script **syntax** alone, the swimlane shape a run would produce before anything executes: `PredictInboundMethod(methodName, methodScript, resolveRouteScript, maxDepth = 4)` and `PredictSiteScript(scriptName, scriptBody, maxDepth = 4)` return a `PredictedFlow` of `PredictedNode(Id, Kind, Lane, Title, InputDependent, Note)` + `PredictedEdge(From, To, CrossesBoundary, Async = false)`.
|
||||
|
||||
- **Syntax walk, not semantic.** A `CSharpSyntaxWalker` over `CSharpSyntaxTree.ParseText(code, SourceCodeKind.Script)` matches the script-API member-access chains by whitespace-stripped text, exactly as `ScriptTrustValidator`'s hardening walker does: `Route.To(x).Call(y)` / `.SetAttributes` / `.WaitForAttribute`, `Scripts.Call(y)`, `…Attributes.WriteBatchAndWaitAsync` / `WaitAsync` / `WaitForAsync` / `SetAsync`, `…Attributes[x] = v`, `ExternalSystem.Call/CachedCall`, `Database.*`, `Notify.Send`. Reads (`GetAttributes`, indexer reads) produce no node because the runtime records no trace row for them.
|
||||
- **Syntax walk, not semantic.** A `CSharpSyntaxWalker` over `CSharpSyntaxTree.ParseText(code, SourceCodeKind.Script)` matches the script-API member-access chains by whitespace-stripped text, exactly as `ScriptTrustValidator`'s hardening walker does: `Route.To(x).Call(y)` / `.SetAttributes` / `.WaitForAttribute`, `Scripts.Call(y)`, `…Attributes.WriteBatchAndWaitAsync` / `WaitAsync` / `WaitForAsync` / `SetAsync`, `…Attributes[x] = v`, `ExternalSystem.Call/CachedCall`, `Database.Execute/Query/QuerySingle/CachedWrite`, `Notify.Send(list)` / `Notify.To(list).Send(...)` (`Async`-suffixed spellings map identically). Reads (`GetAttributes`, indexer reads) produce no node because the runtime records no trace row for them; nor does `Database.Connection(...)` — the number of commands on a raw handle is not statically knowable.
|
||||
- **Outbound calls land on the External lane (2026-09-06, plan `docs/plans/2026-09-06-flow-debugger-outbound-calls.md` D8).** `ExternalSystem.*` → `ApiCall` titled `{system}.{method}`; `Database.*` → `DbCall` titled `{connection} · {member}`; `Notify` → `Notify` titled `Notify {list}`. A `CachedCall`, `CachedWrite` or `Notify` node carries `Note = "cached"` where applicable and its incoming edge is `Async`; an async edge does **not** advance the chain — the next node links from the previous non-async node — mirroring `FlowGraphBuilder.IsAsync` so a predicted async branch renders like a real one.
|
||||
- **Literals resolve; everything else is `InputDependent`** and titled with the expression's source text (`Route.To(code)`). Branching is not modelled: every recognised call is emitted once in source order, and an `if (…) return/throw` guard becomes a `Note = "early return: <condition>"` on the node that **follows** it (the condition is visited first, so a guard whose condition *is* the traced call — Delmia's `if (!await …WriteBatchAndWaitAsync(…)) return …` — attributes correctly).
|
||||
- **DB-free by contract.** Routed and nested script bodies come from the caller-supplied `resolveRouteScript(instanceCode, scriptName)` (Central UI resolves instance → template parent chain → script; called with the literal code when constant, else with the expression text so a resolver may still answer); recursion stops at `maxDepth` with a `depth limit` note.
|
||||
- **Total.** Parse errors or any walker fault degrade to the request/response pair (or the root `ScriptRun`) carrying `Note = "parse error"`; the predictor never throws into the page.
|
||||
- Predicted kinds are exactly the traced kinds (`FlowStepKind`), so `FlowGraphView` can overlay the real graph on the prediction by kind and grey the nodes a run never produced. There is deliberately **no separate central "method script" node** — the real graph records none, so the `InboundRequest` node stands for it.
|
||||
- Predicted kinds are exactly the traced kinds (`FlowStepKind`, including `ApiCall` / `DbCall` / `Notify`), so `FlowGraphView` can overlay the real graph on the prediction by kind and grey the nodes a run never produced. There is deliberately **no separate central "method script" node** — the real graph records none, so the `InboundRequest` node stands for it.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user