Flow
Flow protocol schemas
Flow Node Types — built-in seed set (ADR-0018).
Historically this z.enum gated FlowNodeSchema.type, which made the
closed protocol reject any plugin-registered node type — defeating the open
runtime registry (registerNodeExecutor(type: string)). Per ADR-0018 the
gate is removed: FlowNodeSchema.type is now a validated string, checked
against the live action registry at registerFlow() time, not frozen here.
FlowNodeAction is retained as the canonical list of built-in type ids
(documentation + the seed descriptor set the engine registers at boot). It
no longer constrains authored flows — plugins extend the vocabulary.
Source: packages/spec/src/automation/flow.zod.ts
TypeScript Usage
import { FlowSchema, FlowEdgeSchema, FlowNodeSchema, FlowNodeAction, FlowVariableSchema, FlowVersionHistorySchema } from '@objectstack/spec/automation';
import type { Flow, FlowEdge, FlowNode, FlowNodeAction, FlowVersionHistory } from '@objectstack/spec/automation';
// Validate data
const result = FlowSchema.parse(data);Flow
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Machine name |
| label | string | ✅ | Flow label |
| description | string | optional | |
| successMessage | string | optional | Toast shown when a screen flow completes (defaults to a generic "Done"). |
| errorMessage | string | optional | Toast shown when a screen flow fails (defaults to the raw error). |
| version | integer | optional | Version number |
| status | Enum<'draft' | 'active' | 'obsolete' | 'invalid'> | optional | Deployment status |
| template | never | optional | [REMOVED] flow.template was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — no designer or engine path ever read it, so flagging a flow as a template/subflow did nothing. Delete the key. Shared logic is invoked via a subflow NODE referencing the flow by name. Run os migrate meta --from 16 to rewrite existing sources automatically. |
| type | Enum<'autolaunched' | 'record_change' | 'schedule' | 'screen' | 'api'> | ✅ | Flow type |
| variables | { name: string; type: string; isInput?: boolean; isOutput?: boolean; … }[] | optional | Flow variables |
| nodes | { id: string; type: string; label: string; config?: Record<string, any>; … }[] | ✅ | Flow nodes |
| edges | { id: string; source: string; target: string; condition?: string | object; … }[] | ✅ | Flow connections |
| active | never | optional | [REMOVED] flow.active was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — it never had an effect: the engine arms flows from status, and active: false did NOT stop a flow (worse, the default read as disabled while the engine treated unset as enabled). Delete the key. Use status: 'obsolete' (or 'invalid') to unbind and disable a flow, status: 'active' to arm it. Run os migrate meta --from 16 to rewrite existing sources automatically. |
| runAs | Enum<'system' | 'user'> | optional | Execution identity for the run: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting). A run with no trigger user has no identity to scope to, so under user its data operations are REFUSED — declare system to make the elevation explicit. This covers schedule/time-relative/api triggers AND any record-change flow fired by a write that carried no user. |
| errorHandling | { strategy?: Enum<'fail' | 'retry' | 'continue'>; maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; … } | optional | Flow-level error handling configuration |
| protection | { lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string } | optional | Package author protection block — lock policy for this flow. |
| _lock | Enum<'none' | 'no-overlay' | 'no-delete' | 'full'> | optional | Item-level lock — controls overlay & delete (ADR-0010). |
| _lockReason | string | optional | Human-readable reason shown when a write is refused by _lock. |
| _lockSource | Enum<'artifact' | 'package' | 'env-forced'> | optional | Layer that set _lock (artifact | package | env-forced). |
| _provenance | Enum<'package' | 'org' | 'env-forced'> | optional | Origin of the item (package | org | env-forced). |
| _packageId | string | optional | Owning package machine id. |
| _packageVersion | string | optional | Owning package version. |
| _lockDocsUrl | string | optional | Optional documentation link surfaced next to _lockReason. |
FlowEdge
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | ✅ | Edge unique ID |
| source | string | ✅ | Source Node ID |
| target | string | ✅ | Target Node ID |
| condition | string | { dialect: Enum<'cel' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object } | optional | Predicate (CEL) returning boolean used for branching. |
| type | Enum<'default' | 'fault' | 'conditional' | 'back'> | optional | Connection type: default (normal flow), fault (error path), conditional (expression-guarded), or back (ADR-0044 declared back-edge — traversed normally at run time, but excluded from DAG cycle validation so a revise/rework loop can re-enter an earlier node) |
| label | string | optional | Label on the connector |
| isDefault | boolean | optional | BPMN default flow: traverse this edge only when no sibling conditional edge of the same source node matched. Mutually exclusive with condition; at most one per source node. |
FlowNode
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | ✅ | Node unique ID |
| type | string | ✅ | Action type — a built-in FlowNodeAction id or a plugin-registered node type. Validated against the live action registry at registerFlow() (ADR-0018), not by a closed enum. |
| label | string | ✅ | Node label |
| config | Record<string, any> | optional | Node configuration |
| connectorConfig | { connectorId: string; actionId: string; input?: Record<string, any> } | optional | |
| position | { x: number; y: number } | optional | |
| timeoutMs | integer | optional | Maximum execution time for this node in milliseconds |
| inputSchema | Record<string, { type: Enum<'string' | 'number' | 'boolean' | 'object' | 'array'>; required?: boolean; description?: string }> | optional | Input parameter schema for this node |
| outputSchema | never | optional | [REMOVED] flow.nodes[].outputSchema was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — it was never validated: the engine does not check node outputs against it, so it documented a contract nothing enforced. Delete the key. Downstream nodes read prior outputs via expressions ({{nodeId.field}}) regardless of any declaration. Run os migrate meta --from 16 to rewrite existing sources automatically. |
| waitEventConfig | { eventType: Enum<'timer' | 'signal' | 'webhook' | 'manual' | 'condition'>; timerDuration?: string; signalName?: string } | optional | Configuration for wait node event resumption |
| boundaryConfig | { attachedToNodeId: string; eventType: Enum<'error' | 'timer' | 'signal' | 'cancel'>; interrupting?: boolean; errorCode?: string; … } | optional | Configuration for boundary events attached to host nodes |
FlowNodeAction
Allowed Values
startenddecisionassignmentloopcreate_recordupdate_recorddelete_recordget_recordhttpnotifyscriptscreenwaitsubflowmapconnector_actionparallel_gatewayjoin_gatewayboundary_event
FlowVariable
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Variable name |
| type | string | ✅ | Data type (text, number, boolean, object, list) |
| isInput | boolean | ✅ | Is input parameter |
| isOutput | boolean | ✅ | Is output parameter |
| defaultValue | any | optional | Value bound at run start when no parameter supplies one — this is what makes a declared variable always bound. An explicitly supplied param wins, including false and null; the boundary is params[name] !== undefined. |
FlowVersionHistory
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| flowName | string | ✅ | Flow machine name |
| version | integer | ✅ | Version number |
| definition | { name: string; label: string; description?: string; successMessage?: string; … } | ✅ | Complete flow definition snapshot |
| createdAt | string | ✅ | When this version was created |
| createdBy | string | optional | User who created this version |
| changeNote | string | optional | Description of what changed in this version |