Repository navigation
The fallbackNodeId tombstone prescribes condition: 'fault' — the migration it hands authors does not draw a fault edge #6094
Description
Found by the triage seat while re-checking the residuals on #3863 (findings triage round, 2026-08-07). Filed unassigned. Baseline: origin/main @ 1eb13a0.
Fact
packages/spec/src/automation/flow.zod.ts:715 — the retiredKey() tombstone for flow.errorHandling.fallbackNodeId tells the author how to migrate:
flow.errorHandling.fallbackNodeIdwas removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — the engine routes unrecoverable node errors via per-node fault edges (an edge with condition'fault'), and never read this key: a fallback configured here silently did not exist. Delete the key and draw a fault edge from the failing node to the handler node instead.
The fault semantic does not live in condition. It lives in type:
flow.zod.ts:440 type: z.enum(['default', 'fault', 'conditional', 'back']).default('default')
flow.zod.ts:438 condition: ExpressionInputSchema.optional() // CEL predicate
Why this bites, and which direction it fails
An author who follows the prescription literally writes { source, target, condition: 'fault' }. That edge:
- parses clean —
typeis.default('default'), so omitting it is legal, andconditionaccepts any expression input. No error is raised at publish time; - is a normal edge, not a fault edge —
executeNoderoutes failures ontype === 'fault', so the handler is never reached on failure; - carries
'fault'as a CEL predicate — a bare identifier where a boolean was expected, so the edge is not reliably traversed on the success path either.
Net: the author deletes a key that "silently did not exist", follows the prescribed replacement, and lands a second thing that silently does not exist. The tombstone exists precisely to stop that, which is why this is worth a one-line fix rather than a note.
This is fail-open in the authoring sense (nothing rejects it), though not a data-safety hole: an un-drawn fault edge means failures keep aborting the run, which is the safe default. No stored data is at risk; no current user is known to have hit it.
The repo already knows this exact mistake by name
content/docs/automation/flows.mdx:869— "type: 'fault'is what routes — a label is not."content/docs/releases/v17.mdx:1365names the failure mode in the release notes: "label: 'error'written wheretype: 'fault'was meant".
So a documented, named author error is being prescribed by the spec's own migration message — in a different slot (condition instead of label), but the same shape. #3881 added the docs half; the tombstone was not swept with it.
Scope — deliberately one line
flow.zod.ts:715: an edge with condition 'fault' → an edge with type: 'fault'.
⛔ Do not widen this into an errorHandling prose pass, and ⛔ do not touch the fallbackNodeId retirement itself — the retirement is correct and #3896 is closed. The trailing sentence ("draw a fault edge from the failing node to the handler node") is already right and stays.
Verification notes for whoever takes it
:715is the only occurrence of the wrong spelling repo-wide —git grep "condition 'fault'" origin/mainreturns exactly this one line. Positive control that the scan surface works:type: 'fault'hitsflows.mdx:864,bpmn-mapping.ts:269,flow.test.ts:1354/:1378.- The string is not mirrored into any generated doc under
content/docstoday (git grep "an edge with condition"hits only the source line), so this is a single-file change. Whether the reference generator will pick it up later is [docs-gen] 生成的 reference 把retiredKey()墓碑渲染成any—— 嵌套两层时连[REMOVED]处方都没有,退役键读起来像自由槽 #5606's territory, not this issue's — that issue is about tombstones rendering asany, a different defect on the same machinery, and the two do not collide (it changes the renderer, this changes one string). - Acceptance is stateable in one sentence: the tombstone names the slot that actually routes.
Related: #3863 (fault-edge residuals — where this was found), #3881 (fault-edge v1 + docs), #3896 (the retirement this tombstone records), #5606 (tombstone rendering, distinct).
本单来自分诊座位 Routine(#5474 试点),不构成认领。 (Filed by the triage-seat Routine; not a claim. English per the language policy effective with #6029.)
Activity
hotlong commentedon Aug 7, 2026
Release-board audit (maintainer-directed re-audit of non-board
domain:specitems, 2026-08-07): addingtarget:v17— criterion ④: the tombstone prescribescondition: 'fault'while routing actually lives intype: 'fault'; following the published migration parses clean but silently never routes failures. Triage seat may veto.Generated by Claude Code
hotlong commentedon Aug 7, 2026
Triage:
domain:spec→domain:spec-surface(seat #6298; existing-stock migration pass, maintainer-instructed 2026-08-07). Admission test met — the tombstone's migration prose prescribescondition: 'fault'while routing lives intype: 'fault'; fixing the prescription changes no schema, so both spellings keep exactly the legality they have today. Scheduling note: the fix touchesflow.zod.ts, which in-flight #4415 is restructuring — serialize after it lands (this is also why the card is held out of sweep #6243 for now).Generated by Claude Code