31 KiB
title, type, status, date, depth, origin
| title | type | status | date | depth | origin |
|---|---|---|---|---|---|
| feat: Executable custom workflows + visual node editor | feat | completed | 2026-06-03 | deep | none (solo planning bootstrap) |
feat: Executable custom workflows + visual node editor
Summary
Today the only executable custom-workflow surface is the WorkflowStep engine — a flat, per-task list of prompt/script/gate quality-gate steps that run at the pre-merge and post-merge boundaries around the fixed execute → review → merge pipeline. The richer WorkflowIr graph primitive (start | prompt | script | gate | end nodes + conditional edges) exists but is inert: it is parsed only in tests, WorkflowGraphExecutor is a flag-off no-op, and the parity observer is diagnostic-only.
This plan makes custom workflows work and gives them a visual home, in a way that respects the FN-4359 reliability freeze:
- Persist named workflows as
WorkflowIrgraphs (+ editor layout) in a new core table. - Compile IR → executable
WorkflowStepset (a pure function), so authored graphs run on the already-executable engine path with zero changes to the scheduler/executor/merger reliability core. - Select a workflow per task, with a per-project default; selection resolves to the task's existing
enabledWorkflowStepsseam. - Build a graph node editor on
@xyflow/react(React Flow) as a lazy-loaded dashboard modal that creates/edits the sameWorkflowIrand round-trips throughparseWorkflowIrfor validation. - Document — but do not build — the full interpreter cutover (promoting
WorkflowGraphExecutorto authoritative) as an explicit deferred track, so the long-term direction is recorded and the MVP IR feeds it without rework.
The MVP delivers a working, visual, executable custom-workflow feature now. The same IR artifact is forward-compatible with a future graph interpreter.
Problem Frame
- Inert graph primitive.
WorkflowIr/WorkflowGraphExecutor/workflow-parityare a flag-off, observe-only scaffold with no live call sites. There is no way for a user to author a graph, and nothing executes one. - No authoring surface. The dashboard has list/form UIs for individual
WorkflowSteps (WorkflowStepManager) but no graph/canvas editor and no graph library installed. - No workflow selection. Tasks carry
enabledWorkflowSteps?: string[](a set of step IDs) but there is no concept of a named workflow a task or project can select. - Reliability freeze. FN-4359 blocks changes to scheduler/executor/self-healing/merger reliability behavior without an explicit carve-out. A naive "make the graph authoritative" approach trips this gate immediately.
The opportunity: the IR's prompt / script / gate node kinds are exactly the WorkflowStep primitives. A graph that reduces to an ordered set of pre-merge/post-merge steps around the fixed execute → review → merge seams can be compiled onto the existing executable engine — delivering real execution without touching frozen code.
Scope Boundaries
In scope (MVP)
- New persisted
workflowsentity:WorkflowIrbody + editor layout + metadata, with CRUD. - A pure
compileWorkflowToSteps(ir)function mapping IR nodes →WorkflowSteprecords with phase/gate/order, plus a linearity validator that flags graphs the WorkflowStep engine cannot honor. - Per-task workflow selection (
Task.selectedWorkflowId) and a per-project default workflow; selection resolves toenabledWorkflowStepsat selection time (not in the executor). - A
@xyflow/react-based node editor modal for creating/editing workflows, round-tripped throughparseWorkflowIr. - Dashboard API endpoints + client functions for workflow CRUD, compile-preview, and selection.
- Surfacing the selected workflow + its compiled steps in the task detail view.
Deferred to Follow-Up Work
- Full IR interpreter cutover (
### Deferred Track: Interpreter Cutoverbelow) — promotingWorkflowGraphExecutorto authoritative. Multi-milestone, FN-4359-gated. - Non-linear graph execution (true branching beyond the linear success/failure chain the WorkflowStep engine supports). The MVP compiler rejects such graphs with a clear "requires interpreter (deferred)" message rather than mis-executing them.
- Workflow versioning/history, import/export, sharing across projects.
- Plugin-contributed workflows (plugin-contributed steps already exist and are reused).
Outside this product's identity
- Replacing the kanban board columns or the fixed
triage → todo → in-progress → in-review → doneprogression. Custom workflows configure what runs at the boundaries, not the board model.
Key Technical Decisions
KTD-1 — Compile IR to WorkflowSteps for MVP execution (freeze-safe)
The node editor authors a WorkflowIr; a pure compiler maps prompt/script/gate nodes onto the existing executable WorkflowStep engine, anchored around the fixed execute → review → merge seams. Rationale: the executor already reads enabledWorkflowSteps and runs runWorkflowSteps at pre/post-merge; routing through that seam means no scheduler/executor/merger reliability change → no FN-4359 carve-out. The same IR is the artifact a future interpreter would run, so there is no rework. (User decision: "Both: compile now, cutover later".)
KTD-2 — @xyflow/react (React Flow) for the editor
Mature node-editor library (drag nodes, edge handles, pan/zoom, minimap, controlled state). Fastest path to a polished editor; its controlled-state model maps cleanly to WorkflowIr nodes/edges. Cost: a new dependency in the bundled @runfusion/fusion CLI (~50kb gzipped) — requires a changeset and a Vite manual-chunk entry. Rationale over hand-rolled SVG: drag/connect/zoom/layout is exactly the high-effort, high-bug-surface work React Flow already solves.
KTD-3 — Selection resolves to enabledWorkflowSteps at selection time
Selecting a workflow for a task compiles it and writes the resulting step set into Task.enabledWorkflowSteps; a per-project default is inherited by new tasks. Rationale: keeps the executor's read path unchanged (it already consumes enabledWorkflowSteps), so selection is a store/API-layer concern, not an engine change. (User decision: "Per-task + per-project default".)
KTD-4 — Do not bump the IR schema
Per FN-5769, start | prompt | script | gate | end + edge.condition is the canonical v1 contract. The editor emits exactly this and validates via parseWorkflowIr / serializeWorkflowIr. Editor layout (node x/y) is persisted separately from the IR (IR v1 deliberately excludes layout).
KTD-5 — Linearity validation is part of compilation, not an afterthought
The WorkflowStep engine runs an ordered list at pre/post-merge; it cannot honor arbitrary branching. The compiler validates that the authored graph reduces to a linear pre-merge chain → seams → post-merge chain (with only the canonical success/failure edges around seams). Non-conforming graphs are rejected at save/compile with an actionable message. Rationale: honest semantics — never silently drop branches.
High-Level Technical Design
Architecture: authoring → persistence → compilation → execution
flowchart TD
subgraph Dashboard[Dashboard - React]
NE[WorkflowNodeEditor - @xyflow/react]
SEL[Workflow selector - task + project default]
end
subgraph API[Dashboard server - src/routes]
R1[/workflows CRUD/]
R2[/workflows/:id/compile preview/]
R3[/tasks/:id/workflow selection/]
end
subgraph Core[@fusion/core]
ST[(workflows table)]
IR[parseWorkflowIr / serializeWorkflowIr]
CMP[compileWorkflowToSteps + validateLinearity]
WS[(workflow_steps table)]
TASK[Task.selectedWorkflowId / enabledWorkflowSteps]
end
subgraph Engine[@fusion/engine - UNCHANGED reliability core]
EX[TaskExecutor.runWorkflowSteps]
end
NE -->|WorkflowIr + layout| R1 --> ST
NE -->|validate| IR
SEL --> R3
R3 --> CMP
CMP --> WS
R3 -->|resolve| TASK
R2 --> CMP
TASK -.read unchanged.-> EX
EX -->|runs| WS
The dotted edge is the key safety property: the engine's read of enabledWorkflowSteps is untouched. Everything new lives upstream of it.
Compilation: IR node → WorkflowStep mapping (directional)
WorkflowIr graph compiled WorkflowStep set
───────────────── ─────────────────────────
start ─▶ [gate: lint] ┐
[prompt: spec-check]│ pre-merge ─▶ WS{mode, phase:pre-merge,
[script: build] ┘ gateMode, order:n}
─▶ <execute> (fixed seam, not emitted as a step)
─▶ <review> (fixed seam)
─▶ <merge> (fixed seam)
[prompt: changelog] ┐ post-merge ─▶ WS{phase:post-merge,
[script: notify] ┘ readonly-aware, order:n}
─▶ end
validateLinearity(ir):
- exactly one start, one end (parseWorkflowIr already enforces)
- user nodes partition cleanly into pre-/post-merge relative to seams
- no branch fan-out except canonical success/failure around seams
-> else: WorkflowCompileError("graph requires interpreter (deferred)")
Directional guidance, not implementation specification.
Deferred Track: Interpreter Cutover (documented, not built)
Recorded so the MVP IR is forward-compatible and the long-term path is legible. Follows the FN-5719 4-phase revertable rollout, gated by an explicit FN-4359 carve-out:
flowchart LR
P1[Phase 1: shadow<br/>IR parsed, no exec] --> P2[Phase 2: dual-observe<br/>parity drift -> 100% agree]
P2 --> P3[Phase 3: cutover<br/>flag flips, interpreter owns<br/>execute/review/merge, legacy fallback]
P3 --> P4[Phase 4: remove legacy<br/>once parity proven]
Gating invariants (must not regress): FileScopeViolationError / file-scope guard, squash/merge contract, autoMerge:false terminal-until-merged (FN-5147), moveTask(in-progress→todo) hard-cancel, self-healing/resume-limbo non-oscillation (FN-5704). Parity is machine-checked via the existing compareWorkflowRunObservations / compareWorkflowRunAudits.
Output Structure
packages/core/src/
workflow-definition-types.ts # NEW: WorkflowDefinition, layout, input types
workflow-compiler.ts # NEW: compileWorkflowToSteps + validateLinearity
__tests__/workflow-compiler.test.ts # NEW
__tests__/workflow-definition-store.test.ts # NEW
store.ts # MODIFY: workflow CRUD + selection resolution
db.ts # MODIFY: workflows table + migration 103
types.ts # MODIFY: Task.selectedWorkflowId, project default
index.ts # MODIFY: re-exports
packages/dashboard/src/
routes/register-workflow-routes.ts # NEW: workflow CRUD + compile + selection
__tests__/workflow-routes.test.ts # NEW
packages/dashboard/app/
components/WorkflowNodeEditor.tsx # NEW: React Flow modal
components/WorkflowNodeEditor.css # NEW: token-only
components/nodes/WorkflowNodeTypes.tsx # NEW: custom node renderers
components/WorkflowSelector.tsx # NEW: task + project default selector
components/WorkflowSelector.css # NEW
api/legacy.ts # MODIFY: workflow client fns
components/AppModals.tsx # MODIFY: mount lazy editor
App.tsx # MODIFY: prefetchLazyViews entry
components/TaskDetailModal.tsx # MODIFY: surface selected workflow
__tests__/workflow-node-editor.test.tsx # NEW
Requirements
- R1 — A user can create, rename, edit, and delete named workflows that persist per project.
- R2 — A workflow is authored as a graph (nodes + edges) in a visual editor and validated against the v1 IR contract before saving.
- R3 — A saved workflow compiles to an ordered, executable
WorkflowStepset; non-linear graphs are rejected with an actionable message. - R4 — A task can select a workflow; the selection drives what runs at the pre/post-merge boundaries via the existing engine path (no reliability-core change).
- R5 — A project can set a default workflow that new tasks inherit, overridable per task.
- R6 — The task detail view shows the selected workflow and its compiled steps/results.
- R7 — The MVP introduces no scheduler/executor/merger reliability changes (smallest correct blast radius); the interpreter cutover, where such changes live, is documented as a deferred track. (The FN-4359 freeze is waived for this work, so this is a scoping choice, not a hard gate.)
Implementation Units
U1. Persist named workflow definitions
Goal: Store named workflows (IR + editor layout + metadata) per project. Requirements: R1, R2. Dependencies: none. Files:
packages/core/src/workflow-definition-types.ts(new) —WorkflowDefinition(id,name,description,ir: WorkflowIr,layout: Record<nodeId,{x,y}>, timestamps),WorkflowDefinitionInput.packages/core/src/db.ts(modify) —CREATE TABLE IF NOT EXISTS workflows(snake_case, TEXT idsWF-001via counter, IR + layout as TEXT JSON, ISO timestamps); bumpSCHEMA_VERSION102 → 103; addif (version < 103) applyMigration(103, …)block.packages/core/src/store.ts(modify) —createWorkflowDefinition,listWorkflowDefinitions,getWorkflowDefinition,updateWorkflowDefinition,deleteWorkflowDefinition; validateirviaparseWorkflowIron write.packages/core/src/index.ts(modify) — re-export new types.packages/core/src/__tests__/workflow-definition-store.test.ts(new). Approach: Mirror theworkflow_stepstable + CRUD convention exactly (DDL inline indb.ts, ID counter inconfig, store methods alongside the existing*WorkflowStepmethods). Layout persisted separately from IR per KTD-4. Additive, forward-only migration. Patterns to follow:workflow_stepstable (db.ts~line 371) andcreateWorkflowStep/listWorkflowSteps(store.ts~10479+); migration runnermigrate()(db.ts~1997). Test scenarios:- Create → list returns the workflow with parsed IR and layout intact (round-trip).
- Create with malformed IR (missing start/end) → rejected with
WorkflowIrError. - Update name/IR/layout persists;
updatedAtadvances. - Delete removes it; subsequent
getWorkflowDefinitionreturns null/throws per store convention. - ID sequence increments (
WF-001,WF-002); counter survives reopen. - Migration applies on a
version < 103DB and is idempotent on re-run (IF NOT EXISTS).
U2. Workflow compiler (IR → WorkflowStep set) + linearity validation
Goal: Turn a WorkflowIr into an ordered, executable WorkflowStep set, rejecting graphs the engine cannot honor.
Requirements: R3, R7.
Dependencies: U1.
Files:
packages/core/src/workflow-compiler.ts(new) —compileWorkflowToSteps(ir): WorkflowStep[],validateLinearity(ir): WorkflowCompileError | null,WorkflowCompileError.packages/core/src/index.ts(modify) — re-export.packages/core/src/__tests__/workflow-compiler.test.ts(new). Approach: Pure function, no I/O. Walk the IR fromstart; partition userprompt/script/gatenodes into pre-merge vs post-merge by their position relative to the fixedexecute/review/mergeseam nodes (seams are not emitted as steps). Map each node →WorkflowStepInput-shaped record:modefrom kind,phasefrom partition,gateModefromgatevs advisory,orderfrom topological position, prompt/script fromnode.config. Reject (viaWorkflowCompileError) graphs with branch fan-out beyond canonicalsuccess/failureseam edges, or user nodes that cannot be cleanly partitioned. KeepparseWorkflowIras the structural pre-check. Technical design: see "Compilation" sketch in HTD — directional only. Patterns to follow:BUILTIN_CODING_WORKFLOW_IRseam encoding (builtin-coding-workflow-ir.ts);WorkflowStepInputshape (types.ts~573). Test scenarios:- Linear pre-merge gate + prompt → two pre-merge steps in authored order with correct
gateMode. - Post-merge node after
mergeseam → one post-merge step. - Mixed pre/post around seams → correct partition and ordering.
gatenode →gateMode: "gate"; non-gate → advisory.- Branching graph (fan-out other than success/failure) →
WorkflowCompileError("requires interpreter (deferred)"). - IR missing a required seam → compile error, not silent drop.
- Round-trip stability:
compileWorkflowToSteps(parseWorkflowIr(serializeWorkflowIr(ir)))is deterministic. - Empty user-node graph (just start→seams→end) → empty step set, no error.
U3. Workflow selection + project default (resolve to enabledWorkflowSteps)
Goal: Let a task select a workflow and a project set a default; resolve selection into the existing executable seam without touching the engine. Requirements: R4, R5, R7. Dependencies: U2. Files:
packages/core/src/types.ts(modify) — addTask.selectedWorkflowId?: string; add project-default workflow field to the project/settings shape.packages/core/src/db.ts(modify) —addColumnIfMissingforselected_workflow_idon tasks (additive, self-healing); project default storage.packages/core/src/store.ts(modify) —selectTaskWorkflow(taskId, workflowId)that compiles (U2), upserts the resultingWorkflowSteprecords, and setstask.enabledWorkflowSteps+selectedWorkflowId; project default getter/setter; new-task inheritance of the project default.packages/core/src/__tests__/workflow-definition-store.test.ts(extend). Approach: Selection is a store-layer operation. Compiling on selection produces step records (reuse theworkflow_stepstable) and writes their IDs intoenabledWorkflowSteps. The executor's read path (runWorkflowStepsreadingenabledWorkflowSteps) is unchanged — explicitly no edits toexecutor.ts/scheduler.ts/merger.ts. New tasks read the project default at creation and apply the same resolution. Execution note: Add a guard test asserting no engine reliability files are imported/modified by this unit (surface-enumeration discipline). Patterns to follow:Task.enabledWorkflowStepsconsumption (executor.ts~7756);addColumnIfMissing(db.ts~1927); project/settings persistence instore.ts. Test scenarios:- Selecting a workflow sets
selectedWorkflowIdand populatesenabledWorkflowStepswith the compiled step IDs. - Re-selecting a different workflow replaces the prior compiled steps (no orphan accumulation).
- Clearing selection empties
enabledWorkflowSteps(back to no custom steps). - New task in a project with a default workflow inherits it; per-task override wins.
- Selecting a non-linear workflow surfaces the
WorkflowCompileError(no partial write). - Guard: this unit does not modify
executor.ts/scheduler.ts/merger.ts(assert via import/static check).
U4. Dashboard API: workflow CRUD + compile-preview + selection
Goal: HTTP surface for managing workflows and applying selection. Requirements: R1, R3, R4, R5. Dependencies: U1, U2, U3. Files:
packages/dashboard/src/routes/register-workflow-routes.ts(new) —GET/POST /workflows,GET/PATCH/DELETE /workflows/:id,POST /workflows/:id/compile(preview → steps or 422 with compile error),PUT /tasks/:taskId/workflow(select),GET/PUT /project/default-workflow.packages/dashboard/src/routes.ts(modify) — register the new route module.packages/dashboard/src/__tests__/workflow-routes.test.ts(new). Approach: Mirror the WorkflowStep endpoints exactly: resolvegetProjectContext(req)→ scoped store, validate body, call core store methods,rethrowAsApiError. Compile-preview returns the step set or422with theWorkflowCompileErrormessage. Illegal selection (unknown workflow) →409/404per existing conflict semantics. Patterns to follow:register-task-workflow-routes.ts; the/workflow-stepshandlers inroutes.ts(~2772+);409-on-illegal-transition convention from the DAG Milestone-C plan. Test scenarios:POST /workflowswith valid IR → 201 + body; with malformed IR → 400.GET /workflowsreturns project-scoped list only.POST /workflows/:id/compileon linear graph → 200 + steps; on branching graph → 422 + message.PUT /tasks/:taskId/workflow→ task reflectsselectedWorkflowId+ populatedenabledWorkflowSteps.PUT /project/default-workflowthen create task → task inherits default.- Unknown workflow id on select → 404; cross-project id → not found (scoping enforced).
U5. API client functions
Goal: Typed client wrappers for the new endpoints. Requirements: R1, R4, R5. Dependencies: U4. Files:
packages/dashboard/app/api/legacy.ts(modify) —fetchWorkflows,createWorkflow,updateWorkflow,deleteWorkflow,compileWorkflow,selectTaskWorkflow,fetchProjectDefaultWorkflow,setProjectDefaultWorkflow;WorkflowDefinitionclient types. Approach: Follow thefetchWorkflowSteps/createWorkflowSteptemplate (api<T>,withProjectId,dedupefor GETs). Patterns to follow: WorkflowStep client fns (app/api/legacy.ts~4862+). Test scenarios:Test expectation: none — thin pass-through wrappers; covered indirectly by U4 route tests and U7 editor tests.(Add one happy-path fetch/parse assertion if the file has existing client-fn unit tests to extend.)
U6. Add React Flow + node editor scaffold
Goal: Lazy-loaded editor modal that renders a WorkflowIr as a React Flow graph and saves changes back.
Requirements: R2.
Dependencies: U5.
Files:
packages/dashboard/package.json(modify) — add@xyflow/react.packages/dashboard/vite.config.ts(modify) — add avendor-reactflowmanual chunk..changeset/*.md(new) — feature changeset (published CLI bundles the dashboard).packages/dashboard/app/components/WorkflowNodeEditor.tsx(new) — modal shell (isOpen/onClose/addToast/projectId),useOverlayDismiss,useModalResizePersist, React Flow canvas; IR ↔ React Flow node/edge mapping (apply persistedlayout).packages/dashboard/app/components/WorkflowNodeEditor.css(new) — token-only (semantic graph tokens, e.g.--workflow-node-bg,--workflow-edge); no raw px/hex.packages/dashboard/app/components/AppModals.tsx(modify) — mount viaReact.lazy+<Suspense fallback={null}>.packages/dashboard/app/App.tsx(modify) — add toprefetchLazyViews(); update the lazy-view inventory doc/test. Approach: Controlled React Flow state derived from the loadedWorkflowDefinition. On save, project nodes/edges back toWorkflowIr, validate via acompileWorkflowpreview call, persist viaupdateWorkflow(IR + layout). Respect Buttons Frozen — no.btnmobile-reflow changes. Patterns to follow:WorkflowStepManager.tsxmodal conventions; lazy-load +prefetchLazyViews(per AGENTS.md inventory +app/__tests__/lazy-loaded-views-docs.test.ts); CSS token rules +app/test/cssFixture.ts. Test scenarios:- Loading a workflow renders one node per IR node at its persisted layout position.
- Editing a node label and saving round-trips through serialize→parse without contract drift.
- Saving a branching graph surfaces the compile
422as a toast and blocks save. - Modal open/close persists size (resize-persist) and dismisses correctly (overlay-dismiss).
- CSS uses only design tokens (cssFixture assertion).
- Lazy-view inventory test stays green (editor registered).
U7. Node editor authoring interactions
Goal: Full create/edit UX — node palette, per-kind config panels, edge conditions, validation. Requirements: R2, R3. Dependencies: U6. Files:
packages/dashboard/app/components/WorkflowNodeEditor.tsx(modify) — palette to addprompt/script/gatenodes; connect edges withsuccess/failureconditions; inline validation banner from compile-preview.packages/dashboard/app/components/nodes/WorkflowNodeTypes.tsx(new) — custom node renderers per kind (icon, gate badge, model/script selector) reusingCustomModelDropdown/script pickers fromWorkflowStepManager.packages/dashboard/app/components/WorkflowNodeEditor.css(modify).packages/dashboard/app/__tests__/workflow-node-editor.test.tsx(new). Approach: Each node kind opens a config panel mirroring theWorkflowStepform fields (prompt text + model forprompt; script picker forscript; gate/advisory toggle forgate). Start/end are fixed, non-deletable. Live-validate by debounced compile-preview; show the seam anchors (execute/review/merge) as fixed, read-only nodes so users author around them. Patterns to follow:WorkflowStepManager.tsxform fields,CustomModelDropdown, script fetching;ConfirmDialogfor node deletion. Test scenarios:- Add a
gatenode, connect into pre-merge chain, save → compiles to a gate step. - Attempt to delete
start/end→ blocked. - Add a node with no prompt/script → validation banner, save disabled.
- Connect two outgoing
successedges from one node → "requires interpreter (deferred)" banner. - Prompt node model selection persists into the compiled step.
- Covers R3 — branching rejection surfaced in-editor before save.
U8. Surface workflow selection in task + project UI
Goal: Let users pick a workflow on a task and set a project default, and see compiled steps/results. Requirements: R4, R5, R6. Dependencies: U5, U7. Files:
packages/dashboard/app/components/WorkflowSelector.tsx(new) +.css(new) — dropdown of workflows + "edit in node editor" affordance; used in task detail and project settings.packages/dashboard/app/components/TaskDetailModal.tsx(modify) — show selected workflow + compiled steps in the existing workflow tab (alongsideWorkflowResultsTab).packages/dashboard/app/components/SettingsModal.tsx(modify) — project default workflow selector.packages/dashboard/app/__tests__/workflow-node-editor.test.tsx(extend) or a new selector test. Approach: Reuse the existing workflow tab inTaskDetailModal(whereWorkflowResultsTabmounts) to show the selected workflow name + its compiled, ordered steps; selecting callsselectTaskWorkflow. Project default lives in settings. Patterns to follow:WorkflowResultsTabmount inTaskDetailModal(~line 2738); settings field patterns inSettingsModal.tsx. Test scenarios:- Selecting a workflow on a task shows its compiled steps and persists selection.
- Clearing selection returns the task to no custom steps.
- Setting a project default and creating a task → task shows the inherited workflow.
- Per-task override of the project default is reflected and persisted.
- Task with a selected workflow shows results in the workflow tab after a run (integration with existing
WorkflowResultsTab).
Risks & Dependencies
- R-RISK-1 — Engine reliability regressions. The FN-4359 freeze is explicitly waived for this work, so engine changes are permitted if needed. The MVP still deliberately routes through the existing
enabledWorkflowStepsread path because it's the simplest correct seam — not because it's forced to. Mitigation: keep the MVP's blast radius small (no executor edits in U1–U8); reserve engine changes for the deferred interpreter-cutover track, where the parity invariants below still matter for correctness even though the freeze no longer gates them. - R-RISK-2 — Honest graph semantics. Users may author branches the WorkflowStep engine can't run. Mitigation: compiler-level linearity validation (U2) + in-editor banner (U7); reject, never mis-execute. The deferred interpreter track is where true branching lands.
- R-RISK-3 — Bundle size / dependency in published CLI. React Flow adds ~50kb to the bundled CLI. Mitigation: manual Vite chunk + lazy load; add a changeset; confirm
pnpm buildchunk sizes. - R-RISK-4 — CSS consistency. Buttons-Frozen is waived for this work, so button styling may be touched if the editor genuinely needs it. Mitigation: still prefer semantic design tokens (verified via
cssFixture) for visual consistency with the rest of the dashboard — a convention worth keeping even when not enforced. - R-RISK-5 — Schema migration safety.
fusion.dbcorruption has recurred historically. Mitigation: additive, forward-only migration 103 +addColumnIfMissing; idempotentIF NOT EXISTS; non-blocking at startup; never touch the central DB. - R-RISK-6 — Orphaned compiled steps. Re-selecting workflows could accumulate dead
WorkflowSteprows. Mitigation: U3 replace-on-reselect semantics + a test asserting no orphan accumulation.
External dependency: @xyflow/react (new). Internal: reuses WorkflowStep engine, parseWorkflowIr, CustomModelDropdown, script fetching, modal/lazy infrastructure.
System-Wide Impact
- Engine: none to reliability behavior (by design). The interpreter cutover remains flag-off and deferred.
- Core schema: +1 table (
workflows), +1 task column (selected_workflow_id), +1 project default field;SCHEMA_VERSION102 → 103. - Published CLI: new dependency + behavior reaches
@runfusion/fusion→ changeset required. - Affected parties: end users gain a visual workflow authoring surface; existing flat
WorkflowStepusers are unaffected (the manager remains; workflows are an additive layer over the same primitives).
Sources & Research
packages/core/src/workflow-ir-types.ts,workflow-ir.ts,builtin-coding-workflow-ir.ts— v1 IR contract (frozen per FN-5769).packages/core/src/workflow-parity.ts,packages/engine/src/workflow-graph-executor.ts,workflow-parity-observer.ts— inert, flag-off interpreter + parity scaffold.packages/engine/src/executor.ts(runWorkflowSteps~7747, readsenabledWorkflowSteps~7756),merger.ts,scheduler.ts— the executable WorkflowStep path and the frozen reliability core.packages/core/src/types.ts(WorkflowStep~449,WorkflowStepInput~573),store.ts(*WorkflowStep~10479+),db.ts(workflow_steps~371,migrate()~1997,SCHEMA_VERSION = 102line 152).packages/dashboard/src/routes.ts(/workflow-steps~2772+),app/api/legacy.ts(~4862+),WorkflowStepManager.tsx,WorkflowResultsTab.tsx,AppModals.tsx,vite.config.ts.docs/workflow-steps.md,docs/rfcs/FN-5719-decouple-executor-merger.md,docs/dag/adr-0001-dag-orchestration.md,docs/dag/milestone-b-*.md,docs/dag/milestone-c-dashboard-plan.md,docs/custom-workflows-mvp-spec.md,docs/incidents/2026-05-23-lost-work-tasks.md— prior art, parity/cutover patterns, reliability invariants, Buttons-Frozen + token-only CSS conventions.AGENTS.md— test layout (__tests__/siblings), fast-test rule (FN-5048), Surface Enumeration (FN-5893), FN-4359 reliability freeze, Buttons Frozen, changeset policy.STRATEGY.md— aligns with the "evolving workflows" item of the Ecosystem & adaptability track.