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
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.fallbackNodeId was 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:
An author who follows the prescription literally writes { source, target, condition: 'fault' }. That edge:
parses clean — type is .default('default'), so omitting it is legal, and condition accepts any expression input. No error is raised at publish time;
is a normal edge, not a fault edge — executeNode routes failures on type === '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:1365 names the failure mode in the release notes: "label: 'error' written where type: '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
:715 is the only occurrence of the wrong spelling repo-wide — git grep "condition 'fault'" origin/main returns exactly this one line. Positive control that the scan surface works: type: 'fault' hits flows.mdx:864, bpmn-mapping.ts:269, flow.test.ts:1354/:1378.
The string is not mirrored into any generated doc under content/docs today (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 as any, 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.)
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— theretiredKey()tombstone forflow.errorHandling.fallbackNodeIdtells the author how to migrate:The fault semantic does not live in
condition. It lives intype:Why this bites, and which direction it fails
An author who follows the prescription literally writes
{ source, target, condition: 'fault' }. That edge:typeis.default('default'), so omitting it is legal, andconditionaccepts any expression input. No error is raised at publish time;executeNoderoutes failures ontype === 'fault', so the handler is never reached on failure;'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 (
conditioninstead oflabel), 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
errorHandlingprose pass, and ⛔ do not touch thefallbackNodeIdretirement 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.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).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.)