ObjectStackObjectStack

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

PropertyTypeRequiredDescription
namestringMachine name
labelstringFlow label
descriptionstringoptional
successMessagestringoptionalToast shown when a screen flow completes (defaults to a generic "Done").
errorMessagestringoptionalToast shown when a screen flow fails (defaults to the raw error).
versionintegeroptionalVersion number
statusEnum<'draft' | 'active' | 'obsolete' | 'invalid'>optionalDeployment status
templateneveroptional[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.
typeEnum<'autolaunched' | 'record_change' | 'schedule' | 'screen' | 'api'>Flow type
variables{ name: string; type: string; isInput?: boolean; isOutput?: boolean; … }[]optionalFlow 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
activeneveroptional[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.
runAsEnum<'system' | 'user'>optionalExecution 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; … }optionalFlow-level error handling configuration
protection{ lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string }optionalPackage author protection block — lock policy for this flow.
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

FlowEdge

Properties

PropertyTypeRequiredDescription
idstringEdge unique ID
sourcestringSource Node ID
targetstringTarget Node ID
conditionstring | { dialect: Enum<'cel' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object }optionalPredicate (CEL) returning boolean used for branching.
typeEnum<'default' | 'fault' | 'conditional' | 'back'>optionalConnection 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)
labelstringoptionalLabel on the connector
isDefaultbooleanoptionalBPMN 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

PropertyTypeRequiredDescription
idstringNode unique ID
typestringAction 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.
labelstringNode label
configRecord<string, any>optionalNode configuration
connectorConfig{ connectorId: string; actionId: string; input?: Record<string, any> }optional
position{ x: number; y: number }optional
timeoutMsintegeroptionalMaximum execution time for this node in milliseconds
inputSchemaRecord<string, { type: Enum<'string' | 'number' | 'boolean' | 'object' | 'array'>; required?: boolean; description?: string }>optionalInput parameter schema for this node
outputSchemaneveroptional[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 }optionalConfiguration for wait node event resumption
boundaryConfig{ attachedToNodeId: string; eventType: Enum<'error' | 'timer' | 'signal' | 'cancel'>; interrupting?: boolean; errorCode?: string; … }optionalConfiguration for boundary events attached to host nodes

FlowNodeAction

Allowed Values

  • start
  • end
  • decision
  • assignment
  • loop
  • create_record
  • update_record
  • delete_record
  • get_record
  • http
  • notify
  • script
  • screen
  • wait
  • subflow
  • map
  • connector_action
  • parallel_gateway
  • join_gateway
  • boundary_event

FlowVariable

Properties

PropertyTypeRequiredDescription
namestringVariable name
typestringData type (text, number, boolean, object, list)
isInputbooleanIs input parameter
isOutputbooleanIs output parameter
defaultValueanyoptionalValue 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

PropertyTypeRequiredDescription
flowNamestringFlow machine name
versionintegerVersion number
definition{ name: string; label: string; description?: string; successMessage?: string; … }Complete flow definition snapshot
createdAtstringWhen this version was created
createdBystringoptionalUser who created this version
changeNotestringoptionalDescription of what changed in this version

On this page