- workflow-graph-executor: wrap each post-merge walk() in try/catch so a malformed
post-merge IR / traversal error is logged and skipped, never flipping an already-
merged task to failed (non-blocking post-merge contract) [T9, real bug].
- Refresh stale FNXC comments now that graphNativePostMerge is default-ON and the
legacy merger post-merge path was removed (experimental-features, workflow-graph-
executor, workflow-graph-post-merge.test) [T6/T7/T8].
- Normalize FNXC timestamps to yyyy-MM-dd-hh:mm (TaskCard.test, taskProgress.test) [T2/T3].
- Changeset: category fix → feature to match the minor bump [T0].
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a focused engine regression guard for user-configured command paths.
- Add a static Vitest registry that slices protected configured-command helpers and fails on execSync usage.
- Assert each protected command path keeps bounded async safeguards such as timeout, maxBuffer, or maxLifetimeMs.
- Document the guard and its deliberate git-plumbing exclusions in the testing guide.
Files changed:
docs/testing.md | 5 +
.../user-configured-command-no-execsync.test.ts | 288 +++++++++++++++++++++
2 files changed, 293 insertions(+)
Fusion-Task-Id: FN-7056
Fusion-Task-Lineage: b92f19cf-526e-4345-b18e-869a278e0e10
Final cutover — nothing reads workflow_steps at runtime, so migration 131 drops it.
- Removed the merger post-merge execution path entirely (runPostMergeWorkflowSteps,
hasEnabledPostMergeWorkflowSteps, executePostMerge{Prompt,Script}Step, post-merge
worktree helpers + call site). Graph owns post-merge.
- Executor recovery no longer reads getWorkflowStep().gateMode; gate-ness comes from the
recorded WorkflowStepResult.status.
- Removed store CRUD (create/update/deleteWorkflowStep), materializeWorkflowSteps, and
migrateLegacyWorkflowSteps; selectTaskWorkflow now seeds default-on optional-group node
ids (consistent with create-time). KEPT the plugin step-template palette (getWorkflowStep
plugin-only resolver / listWorkflowSteps plugin-only) — never touches the table.
Removed the dashboard migrate-legacy-steps route + editor migration UI.
- SCHEMA_VERSION 130→131; migration 131 DROP TABLE IF EXISTS workflow_steps; SCHEMA_SQL
table def removed; historical migrations 77/105/109/130 guarded with tableExists().
Proof nothing stranded: the graph executes IR nodes resolved from workflowId (never
stepIds/compiled rows) — materializeWorkflowSteps writes were vestigial. Full @fusion/core
suite (6290), reliability backstop (154), boot smoke, and a seed-at-130 drop test all pass
with the table gone.
Plan U7c.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Makes graph-native post-merge the default and cuts the merger's legacy post-merge
path over so post-merge runs exactly once (via the graph), with migration data prep.
- experimentalFeatures.graphNativePostMerge → default ON; merger
hasEnabledPostMergeWorkflowSteps/runPostMergeWorkflowSteps become inert when on
(no double-run; proven by a new no-double-run test). Merger code kept (U7c removes it).
- Migration 130 (SCHEMA_VERSION 129→130) rewrites each task's enabledWorkflowSteps
entries that are legacy built-in pre-merge workflow_steps row ids → the optional-group
node id (browser-verification/code-review); dedupes; idempotent; identity-stable;
leaves node-ids/compiled/custom entries untouched. Table KEPT (U7c drops it).
- Investigation (real DBs): NO custom/plugin post-merge steps exist; the only post-merge
step is compound-engineering's 'document' graph node — so the merger no-op strands
nothing. Custom-step re-pointing was verified unnecessary and skipped.
Safe-to-drop in U7c still blocked by live readers: merger post-merge fns, store CRUD,
migrateLegacyWorkflowSteps/readConfig materialization (executor recovery reader is
already null-safe→advisory).
Plan U7b.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds graph-native post-merge step execution behind experimentalFeatures.graphNativePostMerge
(default OFF — byte-identical behavior until enabled). After a successful merge-attempt
(the merge seam awaits the merge Promise), the graph runs post-merge optional-group nodes
and records phase:"post-merge" results, non-blocking. The merge-region traversal hop is
inert when the flag is off, and empty for builtin:coding even flag-on (its merge exits only
reach merge-region nodes or end), so the parity oracle holds. Optional-group recording now
derives phase + log prefix from config.phase (defaults pre-merge). Adds postMergeOptionalGroupNode
factory for migration/custom workflows. Legacy merger post-merge path untouched (U7b cutover).
Plan U7a.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the U1-U6 code review (correctness/adversarial/reliability/maintainability):
- Delete dead code left by the runWorkflowSteps removal: parkTaskAfterWorkflowStepPause,
handleWorkflowRevisionRequest (+ createWorkflowRevisionFollowUpTask,
injectWorkflowRevisionInstructions), handleWorkflowStepFailure, the dead
partitionWorkflowRevisionFeedback export + its test, and 2 orphaned jsdocs;
reword 2 stale comments (executor.ts FN-6722, self-healing.ts jsdoc).
- Record step output/notes on graph workflow-step results: runGraphCustomNode now
emits contextPatch:{output,notes} so the Workflow tab shows real review feedback
and [pre-merge] revision logs carry detail (was always the fallback before).
- Sync the workflow-step seam contract: core (workflow-compiler SEAM_NAMES/order,
workflow-ir column map, builtin-workflow-prompts) now rejects the workflow-step
seam to match the engine's resolveSeamName, preventing a latent run-time crash on
a persisted/cloned def that core would otherwise parse.
Residual (tracked in the PR): re-introduce the FN-4343 per-step scope gate on the
graph path; the parked-failed recovery log wording; malformed-advisory->passed edge
case; recording for non-optional-group/split-branch step realizations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
U6: delete the built-in WORKFLOW_STEP_TEMPLATES catalog + its materializer
(getBuiltInWorkflowTemplate/ensureWorkflowStepForTemplate/toBuiltInWorkflowStep);
inline the browser-verification + code-review name/prompt/toolMode/gateMode into
their optional-group IR builders (node bytes unchanged); simplify
resolveEnabledWorkflowSteps to an identity-stable pass-through (no materialization,
so the optionalGroupIdSet collision guard is no longer needed). Plugin-contributed
step templates are kept as the editor palette.
U5: remove the legacy /api/workflow-steps REST surface (GET/POST/PATCH/DELETE +
/refine + /workflow-step-templates/:id/create), the dead client fns, and the
Settings management UI; GET /api/workflow-step-templates now serves plugin
templates only. The create-time optional-step toggles remain.
Scope: the workflow_steps store CRUD + table are intentionally KEPT — still consumed
by the engine (merger/recovery) and needed by U7's migration; their removal + the
table drop land in U7.
Plan U5 + U6.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes the legacy workflow-step EXECUTION path now that the graph records results
(U2): delete runWorkflowSteps(), the workflow-step seam + runWorkflowStep primitive
(runtime-primitives, workflow-node-handlers, authoritative-driver), and the legacy
execute() step blocks. Keeps task.workflowStepResults + its store write path (the
graph's sink) and executeWorkflowStep/executeScriptWorkflowStep (reused by the graph).
- Watchdog recoverCompletedTask now re-enters via maybeExecuteWorkflowGraph so the
graph re-runs pending gates, records results, and owns the in-review/back-for-fix
transition (KTD-2).
- maybeExecuteWorkflowGraph fails CLOSED (parks) when a store lacks
getTaskWorkflowSelection AND the task has enabled pre-merge steps — closing the
FN-7039 silent-skip class without changing minimal-store implementation runs (KTD-5).
KNOWN GAP (follow-up): the FN-4343 per-step workflowStepScopeEnforcement leak check
lived only in runWorkflowSteps and is NOT yet replicated on the graph path. Merge-time
File Scope enforcement (FileScopeViolationError, squash overlap) is unaffected.
Plan U4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prior WIP added a default-on code-review optional-group to builtin:coding on the
success path (execute -> browser-verification -> code-review -> review). 9 exact
visitedNodeIds expectations across 3 suites predated that node and were failing.
Updated expectations to match the real traversal (insert code-review in position).
No product code touched; no assertions weakened.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enabled optional-group nodes now upsert their outcome into the existing
task.workflowStepResults field keyed by node id, and emit [pre-merge] logs at
parity with the legacy runWorkflowSteps path. Disabled groups stay byte-inert.
Reuses the existing WorkflowStepResult type + store.updateTask path (no new
table/type/store method). Unblocks the unified progress bar for graph-run steps.
Plan U2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace real wall-clock waits and per-test rebuilds in the slowest test files
with deterministic seams. No assertions weakened, no timeouts widened, no
retries added — anti-pattern removal only.
- insights-routes.test.ts: boot the server + store ONCE in beforeAll (was a full
createServer + TaskStore.init per test x24), reset insight tables per test for
isolation, drive the sweeper via fake timers. Test-execution time ~3.7s -> ~0.8s.
- db.test.ts: convert the fixed 150ms write-lock hold to manual stdin signal-
release; keeps the real OS-lock contention under test, removes 2x150ms dead
wait. Fixed a real EPIPE on redundant release. 152 pass, non-flaky over 8 runs.
- mission-store.test.ts / agent-store.test.ts: replace real setTimeout sleeps
used only to force distinct timestamps with a controlled clock (vi.setSystemTime
/ injected renewedAt). agent-store assertions strengthened to pin exact values.
- in-process-runtime.test.ts: fake the one real 25ms sleep, drop its inflated
30s per-test timeout.
Honest note: the timestamp-sleep removals are small absolute wins (the headline
per-file durations were full-suite shard contention, not in-file dead time) but
eliminate the FN-5048 real-wait anti-pattern. workflow-routes.test.ts was
evaluated for splitting and deliberately NOT split — measured A/B showed the
split regressed wall-clock (the file is import/transform-bound, already amortized
by installInMemoryDbSnapshot), so splitting only multiplies fixed import cost.
Verified: core 612/612, dashboard 24/24, engine 78/78; typecheck + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the clean-room AI merger into smaller focused modules while preserving its public behavior.
- Extract prompt builders and review verdict parsing into merger-ai-prompts.
- Extract AI merge worktree lifecycle and cleanup helpers into merger-ai-worktree.
- Re-export the extracted APIs from merger-ai and cover prompt/verdict behavior with tests.
- Remove the merger-ai line-count baseline now that the file is under the guardrail.
Files changed:
.../engine/src/__tests__/merger-ai-prompts.test.ts | 86 ++++
packages/engine/src/merger-ai-prompts.ts | 312 ++++++++++++
packages/engine/src/merger-ai-worktree.ts | 287 +++++++++++
packages/engine/src/merger-ai.ts | 555 ++-------------------
scripts/line-count-baseline.json | 1 -
5 files changed, 723 insertions(+), 518 deletions(-)
Fusion-Task-Id: FN-7029
Fusion-Task-Lineage: 59adc31f-7386-4008-b74f-8fb9bbae078a
## Summary
Quarantine 3 test files consistently failing on the non-blocking
full-suite CI on `main`. Per the AGENTS.md deletion-ratchet policy, each
is added to `scripts/lib/test-quarantine.json` with a matching exclude
in its package's vitest config. Tests will be deleted after 14 days
unless rescued with a root-cause fix.
## Quarantined Tests
| File | Shard | Failure | CI Run |
|------|-------|---------|--------|
|
`engine/src/__tests__/self-healing-fn-5488-fast-path-regressions.test.ts`
| 1/4 | `expected +0 to be 1` + `parseFileScopeFromPrompt is not a
function` | [run
28206337202](https://github.com/Runfusion/Fusion/actions/runs/28206337202)
|
| `engine/src/__tests__/in-review-merge-stall-deadlock-recovery.test.ts`
| 2/4 | `expected 'FN-5485' to be null` | [run
28206337202](https://github.com/Runfusion/Fusion/actions/runs/28206337202)
|
| `dashboard/app/components/__tests__/DevServerView.mobile.test.tsx` |
4/4 | `expected +0 to be 1` (mobile CSS structure) | [run
28206337202](https://github.com/Runfusion/Fusion/actions/runs/28206337202)
|
## Verification
- `pnpm test:gate` passes (313 core + 58 ci-shape tests)
- `pnpm lint` clean
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1775">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated the test quarantine list to exclude several flaky or failing
tests from routine dashboard and engine test runs.
* Added records for newly quarantined tests, including the date they
were marked and the CI issue they were linked to.
* Continued using the quarantine list across relevant test projects to
keep CI runs more stable.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Quarantine three test files consistently failing on the non-blocking
full-suite CI on main, per the AGENTS.md deletion-ratchet policy:
- engine self-healing-fn-5488-fast-path-regressions.test.ts (shard 1)
- engine in-review-merge-stall-deadlock-recovery.test.ts (shard 2)
- dashboard DevServerView.mobile.test.tsx (shard 4)
Each has a matching entry in scripts/lib/test-quarantine.json with
the failing CI run link and quarantinedAt date. Tests will be deleted
after 14 days unless rescued with a root-cause fix.
Strengthen the fn_run_verification allowFullSuite parameter description, add an
AGENTS.md standing rule, and update docs/testing.md so agents default to a
file-scoped verification command and reserve allowFullSuite for genuinely full
runs with no targetable test set. allowFullSuite is the main way verification
balloons past its budget; the thin merge gate is the cross-cutting safety net.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diff-proportional verification (deriveFileScopedPnpmTestCommand) + scope-aware
verification timeout, so merge/step checks finish in seconds. Propagated to this
worktree directly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
echo ok is workspace-scoped, so the default verification budget is now 900s
(VERIFICATION_TIMEOUT_WORKSPACE_MS) rather than the retired flat 600s. Assert via
defaultVerificationTimeoutMs so the expectation tracks the scope-aware default.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The merger and executor verification gate (shared runVerificationCommand) used
a flat 10-min default (VERIFICATION_COMMAND_TIMEOUT_MS) for any configured
test/build command, while the fn_run_verification tool already derived its
default from command scope. A workspace-scoped command (a full suite, ~10+ min)
hit the flat 10-min wall and was killed as an infra timeout; a package-scoped
command got a too-generous bound.
Derive the default from command scope to match the tool: package-scoped
(pnpm --filter/-F ...) → 300s, workspace-scoped (root command like pnpm test)
→ 900s. An explicit project verificationCommandTimeoutMs still overrides, and
the 30-min hard cap still clamps the result. Covers both the merger and the
executor per-step auto-gate, which share runVerificationCommand.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A workspace land squash-merges each sub-repo via a clean room that first runs the
configured/inferred install (npm/pnpm/yarn). The install hard-fails by design so
merge verification never runs against an uninstalled checkout — but that let ONE
sub-repo with a manifest npm refuses to install (e.g. a corrupt `-@0.0.1`
lockfile entry rejected by npm 11) block landing every other sub-repo.
landWorkspaceTask now passes nonFatalDependencySync to landOneRepo: a clean-room
install failure is caught, logged + audited as a non-fatal degradation, and the
land proceeds (the git squash needs no installed deps; only dep-dependent
verification degrades for that repo). A real abort signal still propagates. The
single-repo land path keeps the documented hard-fail (flag defaults off).
Tests: new workspace-merger-deps-resilient asserts both the resilient workspace
land (all repos land despite install throwing) and the preserved single-repo
hard-fail. Also fix a pre-existing getTask mock gap in workspace-merger.test
(mergeAndReview reads getTask().comments) that broke 3 tests at the land step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- dashboard: remove the "Branch needs reattachment" banner. It fired for any
in-review task with a null singular task.branch — the NORMAL state for a
workspace task (attachment is per-sub-repo worktrees in workspaceWorktrees), so
it was a permanent false positive. Genuine lost bindings are already reattached
automatically by self-healing's reconcileInReviewBranchRebind (event-driven on
move-to-in-review + sweep), so no manual user action is needed. Delete the
now-obsolete rebind-banner test + its registry entry.
- engine/self-healing: reconcileInReviewBranchRebind now explicitly skips
workspace tasks (never rebind candidates — their fusion/<id> branches live in
the sub-repos, not the non-git browse root; null root branch is healthy).
- engine/merger-ai: pre-merge prune treats an absent ai-merge search root (ENOENT)
as "nothing to prune" instead of warning on every workspace merge.
- test: add ToggleRight to the TaskDetailModal lucide mock (pre-existing gap from
FN-6880 that broke the whole suite at import).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multiworkspace tasks could not complete due to two independent bugs:
1. task.workspaceWorktrees had no SQLite column / rowToTask mapping, so
fn_acquire_repo_worktree's updateTask write was dropped on every persist
(applyTaskPatch writes the DB-round-tripped task back to task.json). Every
later getTask returned undefined, so fn_task_done's scope verifier read {}
and blocked with "acquired no sub-repo worktrees", and isWorkspaceTask()
consumers misfired. Persist it mirroring mergeDetails (schema column + v129
migration + db-migrate + defineTaskColumn + TaskRow + rowToTask).
2. In workspace mode every task ran rooted at the shared browse-only root, and
setActiveSession registered that path keyed only by path — so a second
concurrent workspace task was rejected by the foreign-task guard
("active-session path ... is held by ..."). Give each task a task-scoped
synthetic session key (sessionRegistryPath), applied at all register and
unregister sites; the in-memory worktree Set still holds the real root.
Regression tests assert the persistence invariant across getTask/listTasks/
store-reopen and concurrent session registration across all three session
surfaces; both verified to fail without the fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>