You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(automation,releases,plugins): back-fill the accuracy audit #4161 never got (#4219)
The drift comment on #4161 was computed by the mapper bug #4206 fixed: the
service-automation change collapsed to `packages/services`, so these four
docs were attributed to the wrong package — and a service-automation-only
diff would have reported none of them. Re-derived with the fixed mapper and
audited against the implementation.
flows.mdx carries the #4161 axis itself: a `runAs:'user'` run that resolved
no trigger user has its data operations refused. Also documents `node.type`
as an open string checked against the live registry (ADR-0018), the script
executor as naming a callable with logger-backed `email`/`slack` markers,
the `.strict()` shells, and six previously undocumented node keys.
implementation-status.mdx gains the real `/api/v1` route prefixes, the
memory driver's actual `InMemoryDriver.supports` matrix, and the ADR-0090 D3
`Role` -> `Position` rename — which ratchets the role-word baseline down by
one, so that gate-mandated update ships here too.
Gates: docs build, check:doc-authoring, check:role-word, check:nul-bytes,
check:release-notes, check:skill-examples (198 prose examples).
Claude-Session: https://claude.ai/code/session_01J2x8Tie9WT1VgWFifqMKrR
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/automation/flows.mdx
+86-36Lines changed: 86 additions & 36 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -73,11 +73,13 @@ const approvalFlow = {
73
73
|`version`|`number`| optional | Version number (defaults to `1`) |
74
74
|`status`|`enum`| optional |`'draft'`, `'active'`, `'obsolete'`, `'invalid'` (defaults to `'draft'`) |
75
75
|`type`|`FlowType`| ✅ | Flow trigger type (see below) |
76
-
|`runAs`|`enum`| optional |`'system'` or `'user'`execution context (defaults to `'user'`)|
76
+
|`runAs`|`enum`| optional |`'system'`(elevated, bypasses RLS) or `'user'`(the triggering user, RLS-respecting; the default). A `'user'` run that resolved **no** trigger user has nothing to scope to, so its data operations are refused — declare `'system'` for schedule / time-relative / API triggers and for record-change flows fired by a write that carried no user|
|`successMessage`|`string`| optional | Toast a screen-flow runner shows on terminal success (plain string — `{var}` is **not** interpolated) |
82
+
|`errorMessage`|`string`| optional | Toast a screen-flow runner shows on failure, instead of the raw error |
81
83
82
84
### Flow Types
83
85
@@ -110,7 +112,7 @@ Each node performs a specific action in the flow.
110
112
|`get_record`| Query records |
111
113
|`http`| Make an HTTP API call |
112
114
|`notify`| Send an outbound notification via the messaging service |
113
-
|`script`|Run a custom script action (dispatched by `config.actionType`) |
115
+
|`script`|Call a named callable — a registered function (`config.function`) or a built-in side-effect marker (`config.actionType`) |
114
116
|`screen`| Display a user form/screen (durable pause) |
115
117
|`wait`| Pause for a timer or named signal (durable pause; timers auto-resume) |
116
118
|`subflow`| Invoke another flow — a pause inside the child suspends both runs as a linked chain |
@@ -134,11 +136,21 @@ Each node performs a specific action in the flow.
134
136
| Property | Type | Required | Description |
135
137
| :--- | :--- | :--- | :--- |
136
138
|`id`|`string`| ✅ | Unique node identifier |
137
-
|`type`|`FlowNodeAction`| ✅ | Node type |
139
+
|`type`|`string`| ✅ | Node type — a built-in id from the table above **or** a plugin-registered one. Per ADR-0018 the spec does not gate this with a closed enum; it is checked against the live action registry at `registerFlow()`|
value at all, which counts as unanswered — without `defaultValue: false` the
248
278
user cannot express "no" by leaving it clear.
249
279
-**A broken predicate fails open** (the field stays visible) rather than hiding
250
-
an input the flow may be waiting on. Nothing validates the expression at
251
-
author time yet, so a typo shows up as a field that never hides.
280
+
an input the flow may be waiting on. `registerFlow()` does check the predicate
281
+
as bare CEL — a `{var}` template or a syntax error is a loud registration
282
+
failure that quotes the source and locates it as
283
+
`config.fields[N].visibleWhen` — but a predicate that *parses* and simply
284
+
names the wrong field still shows up as a field that never hides.
252
285
253
286
**Screen (object form):**
254
287
@@ -561,8 +594,9 @@ Edges connect nodes and define the execution path:
561
594
|`source`|`string`| ✅ | Source node ID |
562
595
|`target`|`string`| ✅ | Target node ID |
563
596
|`type`|`enum`| optional |`'default'` (success), `'fault'` (error), `'conditional'` (expression-guarded), or `'back'` (declared back-edge, ADR-0044); defaults to `'default'`|
564
-
|`condition`|`string`| optional | Boolean expression for branching |
597
+
|`condition`|`string`| optional | Boolean CEL predicate for branching (a bare string is stored as `{ dialect: 'cel', source }`)|
565
598
|`label`|`string`| optional | Label displayed on the connector — cosmetic only. It does **not** select a path except on a branching node (`decision` / `approval`), which picks its out-edge by label. |
599
+
|`isDefault`|`boolean`| optional | BPMN default-flow marker (interop). Accepted by the schema, but **the engine does not read it** — traversal selects by `condition` and by `label`. On a `decision` node, the fallback is the out-edge labelled `default`: when no `conditions[]` entry matches, the node emits `branchLabel: 'default'`|
566
600
567
601
### Fault edges — handling a failed node
568
602
@@ -582,7 +616,7 @@ edges: [
582
616
the type leaves an ordinary edge, and every unconditional out-edge is traversed
583
617
(in parallel) on **success**. The handler then runs whenever the node
584
618
*succeeds*, never when it fails, and the run still aborts on failure. Both
585
-
halves are silent, so `objectstack validate` reports this as
619
+
halves are silent, so `os validate` reports this as
586
620
`flow-error-label-not-fault`.
587
621
</Callout>
588
622
@@ -621,7 +655,7 @@ The split is deliberate. A dropped filter condition does not narrow a query, it
621
655
widens it — so if guards were routable, one `fault` edge on a `delete_record`
622
656
would turn off the protection against emptying the object while the run still
623
657
reported success. Re-running changes nothing either: the fix is to correct the
624
-
metadata, which `objectstack validate` will point at.
|`strategy`|`enum`|`'fail'` (stop) or `'retry'` (re-run the whole flow).`'continue'`parses but the engine branches only on `'retry'`, so it behaves exactly like `'fail'` — use a `fault` edge to keep going past a failed node|
677
711
|`maxRetries`|`number`| Maximum retry attempts (0-10) |
678
712
|`retryDelayMs`|`number`| Delay between retries (ms) |
A self-triggering flow caught by the loop guard reports
788
-
`reason: "reentrancy_loop_guard"` the same way. Check `skipped` before assuming
789
-
the work happened.
826
+
`reason: "reentrancy_loop_guard"` the same way. Check `data.output.skipped`
827
+
before assuming the work happened.
790
828
</Callout>
791
829
792
830
## Console Flow Viewer & Test Runner
793
831
794
-
Every flow can surface in the Console metadata browser under `/_console/`.
795
-
ObjectUI's flow viewer replaces the default JSON inspector with rich tabs when
796
-
the flow viewer plugin is installed:
832
+
Every flow surfaces in the Console metadata browser under `/_console/`, where
833
+
ObjectUI's app-shell registers `FlowPreview` as the `flow` metadata preview
834
+
(`registerMetadataPreview('flow', FlowPreview)`) in place of the default JSON
835
+
inspector. It renders the graph on a canvas — editable (add a node, patch a
836
+
selection) only in the designer's edit mode, read-only otherwise. Opening a flow
837
+
shows the **full-width canvas with no side panel**; the toolbar toggles one
838
+
collapsible panel at a time:
797
839
798
-
|Tab| Component | Purpose |
840
+
|Side panel| Component | Purpose |
799
841
|:---|:---|:---|
800
-
|**Overview**|`FlowViewer`| Renders trigger type, variables, nodes, edges, and `errorHandling` as inspector cards |
801
-
|**Run**|`FlowTestRunner`| Auto-generates a form for every `isInput: true` variable (with type-aware coercion for `number` / `boolean` / `object` / `list`), executes the flow against the per-project kernel, and shows the returned outputs + run id |
802
-
|**Runs**|`FlowRunsPanel`| Lists historical executions for the selected flow with status, duration, and a deep-link to the run record |
scoped to the active project. All three components participate in the Studio
807
-
authentication / project-scope context, so they work identically in
808
-
single-project mode and in cloud mode.
842
+
|**Variables**| — | The flow's declared variables with their types and input/output flags. Opt-in — it is the fallback panel, not one that opens on its own |
843
+
|**Problems**|`ProblemsPanel`| Validation findings; selecting one reveals the offending node or edge on the canvas |
844
+
|**Debug**|`FlowSimulatorPanel`| Steps the graph client-side, highlighting the active node, the visited nodes, and the traversed edges |
845
+
|**Runs**|`FlowRunsPanel`| Real run history from [`GET /api/v1/automation/:name/runs`](#observing-runs), each run's step log nested by loop iteration / parallel branch / try-catch handler |
846
+
847
+
Actually *running* a flow is a separate Console page — **Developer › Flow Runs**
848
+
(`developer/flow-runs`). Its test runner auto-generates a form for every
0 commit comments