docs: Flow Debugger design reconciled with the implementation (arch-review F12, F13)

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
This commit is contained in:
Joseph Doherty
2026-09-06 14:35:10 -04:00
parent ccc08e4151
commit e15aa0e430
13 changed files with 104 additions and 42 deletions
+46 -20
View File
@@ -27,7 +27,7 @@ on the `DelmiaReceiver` child → seven attribute writes → wait for Galaxy to
| 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 page and the Run button; Replay needs `OperationalAudit`. | A separate `RequireOperator` gate for Live. |
| 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)
@@ -159,20 +159,24 @@ the echo also fires inherit the parent id exactly as static cascades do today.
`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` held by
`ScriptRuntimeContext` and attaches the list to the `ScriptCallResult` → `RouteToCallResponse`
when `Trace` is set. Capped at `MaxTraceEventsPerRun` (500); beyond it the collector keeps the
first 500, sets `Truncated`, and the audit rows remain complete. A site on an older build ignores
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 debugger instead subscribes
to the instance's debug stream (`SubscribeDebugView`, the Debug View's existing path) for the run's
duration and takes the spawned run's `ExecutionId` from the stamped `AttributeValueChanged`, then
pulls the site's trace rows for that id via a new `PullTraceEvents` RPC on `SiteStreamService`
(same PSK interceptor as `PullAuditEvents`; reads the site audit SQLite by `ExecutionId` /
`ParentExecutionId`). One additive RPC, no streaming change.
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)
@@ -257,9 +261,14 @@ surface); the walker is syntax-only and never executes anything.
## 9. Security, authorisation, audit
- **Page visibility and 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. **Replay:** `OperationalAudit` (it
- **Page visibility:** `RequireFlowDebugger` — the `OperationalAudit` roles 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:** `FlowDebugReplayCommand` accepts the same `OperationalAudit` roles (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.Actor` carries the operator's
@@ -267,21 +276,38 @@ surface); the walker is syntax-only and never executes anything.
`Extra.runNumber`; `Target` is the method as usual. API-key authentication is bypassed (there is
no key), **parameter validation is not** — `ParameterValidator` runs exactly as for HTTP so a
bad input fails the same way. The method's `TimeoutSeconds` applies. Site-scope (`SiteScopeService`)
restricts which instances a value-change entry may target.
- Trace payloads pass through the existing redaction policy (auth headers redacted, 8 KB cap,
64 KB on error rows). Console output is capped the same way.
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 to `RouteHelper`, which refuses an out-of-scope target
**before any site traffic**; the denial is rethrown as `SiteScopeViolationException` *after* the
inbound audit row is written, so the attempt is recorded under the operator's name.
Administrators and unscoped users are exempt, exactly as `EnforceSiteScope` treats them.
- Trace payloads pass through the existing redaction policy on BOTH copies: the stored row (writer)
and the inline Live copy (`FlowTraceCollector` applies `IAuditRedactor` and 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; `PullTraceEvents` sits behind `ControlPlaneAuthInterceptor`.
## 10. Volume and failure behaviour
- Untraced production is unchanged except one `RouteCall` row per routed call (inbound-driven).
Every routed call adds one central `RouteCall` insert — at inbound-driven rates that doubles the
per-call central audit write (accepted, D5).
- A traced Delmia call emits ≈ 1 `InboundRequest` + 1 `RouteCall` + 1 `ScriptRun` + 2
`AttributeWrite` + 1 `AttributeWait` + 1 value-change `ScriptRun` ≈ 7 rows. `TraceAllCalls` on a
100-call/day method is ~700 rows/day.
`AttributeWrite` + 1 `AttributeWait` + 1 value-change `ScriptRun` ≈ **7–10 rows** (a cached DB
write and a notification add 3 trace-only rows). `TraceAllCalls` on 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 run `Timed out`. The site's trace rows still land later, so Replay of the
same ExecutionId is complete.
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's
`run-method --timeout-seconds` defaults to 120 while `run-value-change` waits
`--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 `ScriptTrace` rows from a NEW site parses
+2 -2
View File
@@ -226,7 +226,7 @@ Add `using ZB.MOM.WW.Audit;` where `AuditEvent` is used (Commons already referen
**Step 1: Failing test** (follow the existing ManagementService test style for invoking `HandleUpdateApiMethod` — find a sibling test that exercises `UpdateApiMethodCommand` and copy its fixture; assert `method.TraceAllCalls` round-trips and defaults to `false` for the 5-arg constructor).
**Step 2: Implement**
- `ApiMethod`: `public bool TraceAllCalls { get; set; }` with summary "When true every production call to this method is fully traced (Flow Debugger design §5.2); intended for low-volume integration methods — ~7 audit rows per call."
- `ApiMethod`: `public bool TraceAllCalls { get; set; }` with summary "When true every production call to this method is fully traced (Flow Debugger design §5.2); intended for low-volume integration methods — 7-10 audit rows per call."
- EF: `builder.Property(m => m.TraceAllCalls).IsRequired().HasDefaultValue(false);`
- Migration: build first (CLAUDE.md gotcha), then `dotnet ef migrations add AddApiMethodTraceAllCalls --project src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase --startup-project src/ZB.MOM.WW.ScadaBridge.Host` (use the same `--startup-project` the previous migration used — check `git log -1 --stat -- src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/20260819201210_AddNotificationsListSortIndex.cs` for its commit message). Verify the generated `Up` contains exactly one `AddColumn<bool>("TraceAllCalls", "ApiMethods", nullable: false, defaultValue: false)`; delete and redo if empty.
- Commands: `CreateApiMethodCommand(..., string? ReturnDefinition, bool TraceAllCalls = false)`, `UpdateApiMethodCommand(..., string? ReturnDefinition, bool? TraceAllCalls = null)` (null = leave unchanged, so old CLIs don't reset it). Handlers copy the value.
@@ -754,7 +754,7 @@ public sealed record FlowDebugOutcome(FlowGraph? Graph, FlowDebugRunResult? Raw,
**Files:**
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/AuditEventDetail.razor` (+ `.razor.cs`): button `Open in Flow Debugger` (`data-test="open-flow-debugger"`) beside *View execution chain* → `/deployment/flow-debugger?executionId={exec}`; rendered when `ExecutionId` is non-null.
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Audit/ExecutionTreePage.razor`: same button next to *View this execution in the Audit Log*.
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/ApiMethodForm.razor`: under the Timeout field a `form-check` "Trace all calls (Flow Debugger)" bound to `_traceAllCalls`, help text "Adds ~7 audit rows per call; for low-volume integration methods", saved onto `_existing.TraceAllCalls` / the create path.
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/ApiMethodForm.razor`: under the Timeout field a `form-check` "Trace all calls (Flow Debugger)" bound to `_traceAllCalls`, help text "Adds 7–10 audit rows per call; for low-volume integration methods", saved onto `_existing.TraceAllCalls` / the create path.
- Test: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Audit/FlowDebuggerDrillinTests.cs`, extend the existing `ApiMethodForm` test file if one exists (grep `ApiMethodForm` in tests).
Commit — `git commit -am "feat(central-ui): Flow Debugger drill-ins from Audit Log + Execution Chain; TraceAllCalls checkbox on the method form"`
@@ -591,7 +591,7 @@ var m = unmatched.FirstOrDefault(p => p.Kind == s.Kind && string.Equals(p.Title,
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/ApiMethodForm.razor:50`, `src/ZB.MOM.WW.ScadaBridge.CLI/Commands/ApiMethodCommands.cs:65,101`, `src/ZB.MOM.WW.ScadaBridge.CLI/README.md:2122,2140`, `docs/requirements/Component-InboundAPI.md:73` ("~7" → "7–10 audit rows per call (3 of them trace-only)")
- Modify: `docs/requirements/Component-InboundAPI.md` (new sentences: `SchemaRefResolver` is public for the Flow Debugger runner; `RouteHelper.WithSiteScope` + `SITE_SCOPE_DENIED`; `RouteCall` honours `SkipBodyCapture`)
- Modify: `docs/requirements/Component-CentralUI.md` (Flow Debugger: page policy `RequireFlowDebugger`, Live controls Deployer-only, client wait derivation)
- Modify: `CLAUDE.md` (Flow Debugger bullet: "~7 rows/call" → "7–10", one sentence on F1/F2/F3 outcomes with the review doc path)
- Modify: `CLAUDE.md` (Flow Debugger bullet: the per-call trace-row estimate → "7–10", one sentence on F1/F2/F3 outcomes with the review doc path)
- Modify: `docs/plans/2026-09-06-flow-debugger-arch-review.md` (append `## 7. Resolution` listing each finding → commit)
Text to write (verbatim where possible):
@@ -600,7 +600,9 @@ Text to write (verbatim where possible):
- §9 redaction bullet: "Trace payloads pass through the existing redaction policy on BOTH copies: the stored row (writer) and the inline Live copy (`FlowTraceCollector` applies `IAuditRedactor` and a 1 MB byte budget beside the 500-event cap — arch-review F1)." Add: "The method entry enforces the operator's site scope at route time (`RouteHelper.WithSiteScope`, `SITE_SCOPE_DENIED` → `ManagementUnauthorized`, arch-review F2)."
- §10: "≈ 7–10 rows (a cached DB write and a notification add 3 trace-only rows)"; "Every routed call adds one central `RouteCall` insert — at inbound-driven rates that doubles the per-call central audit write (accepted, D5)"; "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`."
**Step 4:** `grep -rn "~7 audit\|~7 rows\|roughly seven" docs src CLAUDE.md` → no hits.
**Step 4:** grep `docs src CLAUDE.md` for the pre-remediation estimate wordings (the tilde-7 forms
and the spelled-out "seven audit rows" one) → no hits. The patterns are deliberately not written out
in this file so the check can return zero hits.
**Step 5: Commit** `docs: Flow Debugger design reconciled with the implementation (arch-review F12, F13)`
@@ -246,8 +246,9 @@ for the debug-stream path" or dropped from the client mapping.
- Design §5.4 (debug-stream subscription) vs the polling implementation — see F12.
- Design D6 / §9 (Replay = OperationalAudit) vs Deployer everywhere — see F3.
- Design §9 (redaction applies to trace payloads) vs F1.
- `ApiMethodForm` / CLI help say "~7 audit rows per call"; the outbound-calls addendum makes a
call with one cached DB write and one notification **10 rows** (3 trace-only). Say "7–10".
- `ApiMethodForm` / CLI help understate the row count (they were written before the outbound-calls
addendum, which makes a call with one cached DB write and one notification **10 rows**, 3 of them
trace-only). Say "7–10 audit rows per call".
- `FlowDebugRunner.RunMethodAsync` writes the inbound row *after* the run, so a Replay taken during
a long Live run shows only the `RouteCall`. Fine, but not stated anywhere.
- `SchemaRefResolver` became `public` for the runner; note it in Component-InboundAPI or move the
@@ -328,3 +329,30 @@ touches the Flow Debugger; `Deployment/FlowDebuggerTests` (Live run + localStora
Consequence worth recording alongside the login-disabled decision: the rig can no longer prove
role gating in the browser, so the Playwright suite's role-negative tests need either a second,
login-enabled central to target or a fixture that overrides the identity per test.
## 7. Resolution
All thirteen findings were remediated on branch `fix/flow-debugger-arch-review` (plan
`docs/plans/2026-09-06-flow-debugger-arch-review-remediation.md`, 2026-09-06). Nothing was
deferred to the register.
| # | Outcome | Commit |
|---|---------|--------|
| F1 | `FlowTraceCollector` moved to Commons `Types/Debugging/`, applies `IAuditRedactor` inside `Add` and caps at 500 events **and** 1 MB of summed `DetailsJson`; the site launcher resolves the redactor from the run's scope, and the central `FlowDebugRunner` collects through the same instance and reports its `Truncated`. | `e79452d2` fix(site-runtime): Flow Debugger inline trace copy is redacted and byte-capped at the collector (arch-review F1, site half) · `def6bab3` fix(management): Flow Debugger Live result is the redacted, capped trace copy (arch-review F1, central half) |
| F2 | `RouteHelper.WithSiteScope(IReadOnlySet<string>?)` refuses an out-of-scope target site before any site traffic (`RouteSiteScopeException` → executor code `SITE_SCOPE_DENIED`); the runner rethrows it as `SiteScopeViolationException` (`ManagementUnauthorized`) **after** writing the inbound audit row. Administrators and unscoped users are exempt; the HTTP endpoint is unchanged. | `20a05941` fix(inbound-api): Flow Debugger method-entry Live run enforces the operator's site scope at route time (arch-review F2) |
| F3 | New `AuthorizationPolicies.RequireFlowDebugger` (`FlowDebuggerAccessRoles` = Deployer, Administrator, Viewer) gates the page, and `FlowDebugReplayCommand` accepts the same set; the two run commands, the Live controls, Retry/Discard and the `?methodId=`/`?instanceId=` drill-ins stay Deployer-only, with the page pinning Replay mode for a non-Deployer. | `f86eb61d` fix(central-ui): Flow Debugger Replay opens to OperationalAudit roles; Live run and Retry/Discard stay Deployer-only (arch-review F3) |
| F4 | Client waits derive from the server's bound: `RunAskTimeoutFor(timeoutSeconds)` = method timeout + `RunGrace` + 15 s slack (floor 30 s), `ValueChangeAskTimeoutFor(wait)` = wait + 15 s; the CLI gains `run-method --timeout-seconds` (default 120) and waits `--wait-seconds + 25` on `run-value-change`. | `97b0a4ba` fix(central-ui,cli): Flow Debugger client waits derive from the method timeout / wait window instead of a fixed 2 minutes (arch-review F4) |
| F5 | The graph orders executions by emission sequence and uses timestamps only **within** one execution, so a site clock offset can no longer reorder the swimlane; the Live runner keeps the collector's emission order. | `1dfb9cee` fix(commons): Flow graph orders executions by emission sequence, timestamps only within one clock (arch-review F5) |
| F6 | A failed traced DCL write disarms the trace arm (`DisarmTrace` self-message), so an unrelated device update can no longer consume it. | `50bf9ac7` fix(site-runtime): trace arm is cleared when the traced DCL write fails (arch-review F6) |
| F7 | A traced run shed by `MaxConcurrentRunsPerScript` or suppressed by `MinTimeBetweenRuns` emits a Failed `ScriptRun` row (`shed: MaxConcurrentRunsPerScript …` / `suppressed: MinTimeBetweenRuns …`) instead of vanishing from the graph. | `105541b6` fix(site-runtime): shed or rate-limited traced runs emit a Failed ScriptRun row (arch-review F7) |
| F8 | All four trace/outbound fault continuations run on `TaskScheduler.Default`, and the central emitters log a Warning on a failed audit write. | `262de38c` fix(audit): trace/outbound fault continuations run on the default scheduler and central emitters log (arch-review F8) |
| F9 | `GetTraceEventsAsync` and `GetExecutionTreeAsync` share one chain walker in `AuditLogRepository`. | `297ae56b` refactor(config-db): GetTraceEventsAsync and GetExecutionTreeAsync share one chain walker (arch-review F9) |
| F10 | The prediction overlay matches a real step by `(Kind, Title)` first, falling back to kind only, so two predicted nodes of the same kind no longer collapse onto one another. | `688136de` fix(central-ui): Flow Debugger prediction overlay matches by kind and title (arch-review F10) |
| F11 | The always-on `RouteCall` row honours the method's `SkipBodyCapture` — the executor builds the helper with `RouteHelper.WithBodyCapture(false)`; the row is still written, metadata only. | `453c3dec` fix(inbound-api): RouteCall audit row honours the method's SkipBodyCapture override (arch-review F11) |
| F12 | `AttributeValueUpdate.source_execution_id` / `trace` (fields 7/8) are **kept, populated and unconsumed** — documented as reserved for a future debug-stream path in `sitestream.proto` and design §5.4, with the field numbers pinned against reuse. Not removed: dropping them would be a wire change for no gain. | this commit |
| F13 | Design D6 / §5.4 / §9 / §10, `Component-CentralUI.md`, `Component-InboundAPI.md`, `CLAUDE.md`, the proto comment and the CLI/UI help strings reconciled with the shipped code; the trace-row estimate is "7–10 audit rows per call" everywhere; `SchemaRefResolver`'s `public` visibility and the inbound-row-after-the-run ordering are now stated. | this commit — docs: Flow Debugger design reconciled with the implementation (arch-review F12, F13) |
One test defect surfaced by §3's performance commits was fixed on the same branch:
`OperationTrackingStoreTests.ReadChangedSince_BoundaryTimestamp…` raced the new single-writer
queue (a missing `FlushAsync` before the test's direct `UPDATE`) — `86451382` test(site-runtime):
flush the tracking-store queue before the boundary-timestamp test's direct UPDATE.
+1 -1
View File
@@ -6,7 +6,7 @@ SET ANSI_NULLS ON;
GO
-- Flow Debugger (design 2026-09-05 §5.2): ApiMethod.TraceAllCalls — when 1, every
-- production call to the method is fully traced (~7 audit rows per call). Production
-- production call to the method is fully traced (7-10 audit rows per call). Production
-- twin of EF migration 20260906034113_AddApiMethodTraceAllCalls.
BEGIN TRANSACTION;
IF NOT EXISTS (