- Add improvements.md with a system-wide fragility and reliability assessment - Prioritize 16 hardening recommendations by severity with estimated effort - Document high-risk hotspots across persistence, migration, runtime lifecycle, and scoping - Capture cross-cutting themes, test coverage gaps, and complexity metrics for follow-up
14 KiB
Fusion System Improvements
Generated: 2026-04-08 Task: FN-1161
Summary
Fusion is functionally rich, but fragility is concentrated in a small number of orchestration-heavy files where persistence, process control, API translation, and recovery logic are tightly coupled. The highest-risk paths are where state is written to multiple backends without a single atomic boundary, and where recovery logic is best-effort rather than explicit/stateful.
The most urgent issues are in task persistence (TaskStore dual-write), migration safety (db.ts + db-migrate.ts partial-apply semantics), and parallel step execution (StepSessionExecutor fallback behavior). These can produce split-brain state, hard-to-reconcile migration outcomes, or concurrent git writes to the same worktree.
Operationally, multi-project correctness and runtime lifecycle cleanup are the next tier of risk: several dashboard routes bypass scoped stores, child runtime restart/kill timers are not fully coordinated, and process/timer listeners can leak across repeated startups (observed during pnpm test as MaxListenersExceededWarning).
Priority 1: Critical — Risk of Data Loss or Corruption
1) Non-atomic dual persistence for task state
- File(s):
packages/core/src/store.ts:459-485(readTaskJson),packages/core/src/store.ts:488-500(atomicWriteTaskJson),packages/core/src/store.ts:1054-1139(moveTask) - Issue: Task state is written to SQLite (
upsertTask) and then mirrored totask.json. If the filesystem write/rename fails after DB write, the two sources diverge. - Impact: Recovery/fallback paths can read stale
task.json, causing inconsistent task columns/status/steps and hard-to-debug behavior after partial failures. - Suggestion: Introduce explicit single-source mode for runtime operations (SQLite-only reads/writes), and move JSON mirror writes to an async reconciliation queue with health markers. Add a consistency check endpoint/CLI command that reports DB↔file drift.
- Estimated effort: M
2) Migration steps can partially apply without rollback journal
- File(s):
packages/core/src/db.ts:375-537(migrate,applyMigration),packages/core/src/db-migrate.ts:69-121(migrateFromLegacy) - Issue:
applyMigration()explicitly avoids transaction wrapping for ALTER paths, and legacy migration catches per-step failures and continues. - Impact: A project can end in a mixed schema/data state with no checkpoint table describing which pieces are authoritative.
- Suggestion: Add migration journal tables (
schema_migrations,legacy_migration_runs, per-entity status), require explicit success/failure summary persistence, and add resumable migration commands (--resume,--verify) before backup cleanup. - Estimated effort: L
3) Parallel-step fallback can run multiple steps in the same worktree concurrently
- File(s):
packages/engine/src/step-session-executor.ts:776-795(executeParallelWave) - Issue: When per-step worktree creation fails, code currently falls back to
this.options.worktreePathand still executes the wave concurrently. - Impact: Concurrent step sessions may write to the same git worktree, risking index conflicts, bad cherry-picks, or invalid step outcomes.
- Suggestion: On any worktree creation failure, degrade the entire wave to sequential mode (or hard-fail the wave) instead of sharing primary worktree paths concurrently.
- Estimated effort: S
Priority 2: High — Risk of Operational Failure
4) Child-process runtime kill/restart lifecycle has timer races
- File(s):
packages/engine/src/runtimes/child-process-runtime.ts:347-361(killChild),packages/engine/src/runtimes/child-process-runtime.ts:436-459(handleUnhealthy) - Issue: Force-kill and restart timers are fire-and-forget and not tracked/cancelled;
this.childis nulled immediately after scheduling SIGKILL. - Impact: Delayed timer callbacks can target a new child process or run after shutdown transitions, producing restart flapping or accidental kill of replacement workers.
- Suggestion: Introduce explicit runtime lifecycle state machine + tracked timer handles. Keep per-child generation IDs so delayed callbacks only act on the intended child.
- Estimated effort: M
5) Global limit refresh timer leak and dead semaphore path
- File(s):
packages/engine/src/project-manager.ts:95-123(constructor,refreshGlobalLimit),packages/engine/src/project-manager.ts:459-470(stopAll) - Issue:
setIntervalfor limit refresh is never cleared;globalSemaphoreis recreated but never used for admission control. - Impact: Long-lived processes can accumulate timers; intended global coordination is partially implemented and misleading.
- Suggestion: Either wire
globalSemaphoreinto project run admission or remove it. Store interval handle and clear it instopAll(). - Estimated effort: S
6) Multi-project scoping bypass in dashboard mutation routes
- File(s):
packages/dashboard/src/routes.ts:1305-1311(getScopedStore), plus unscoped handlers at3525-3949(/github/issues/import,/github/issues/batch-import,/github/pulls/import) and5312-5828(subtasks/planning create flows) - Issue: Many routes correctly call
getScopedStore(req), but several import/planning/subtask endpoints still use rootstoredirectly. - Impact: Requests with
projectIdcan mutate the wrong project store and emit events on unexpected channels. - Suggestion: Add a required
resolveStore(req)middleware and ban direct outerstoreusage in route handlers via ESLint rule or route wrapper. - Estimated effort: M
7) Realtime channels are not uniformly project-scoped
- File(s):
packages/dashboard/src/server.ts:134-145(/api/events),packages/dashboard/src/server.ts:544-644(setupBadgeWebSocket),packages/dashboard/app/hooks/useTasks.ts:34-108,packages/dashboard/app/hooks/useBadgeWebSocket.ts:104,packages/dashboard/app/hooks/useAgents.ts:39-50 - Issue: SSE has scoped store support, but badge WS subscriptions are tied to the root store; client hooks also note unfiltered SSE behavior and open independent EventSources.
- Impact: Stale/cross-project updates, duplicate sockets, and reconciliation complexity under multi-project usage.
- Suggestion: Add
projectIdto badge WS subscribe protocol, normalize all event payloads with explicit project attribution, and centralize one shared app-level SSE/WS transport. - Estimated effort: M
8) CLI extension mutates global console for tool output capture
- File(s):
packages/cli/src/extension.ts:681-696(kb_task_import_github),packages/cli/src/extension.ts:1015-1034(kb_task_plan) - Issue: Tools monkey-patch
console.log/errorglobally to capture output. - Impact: Concurrent tool executions can interleave logs, lose output, or restore wrong handlers.
- Suggestion: Refactor command APIs to return structured results (
{ summary, createdTasks, logs }) and stop global console mutation. - Estimated effort: S
9) Dashboard command lifecycle leaks signal listeners across repeated startups
- File(s):
packages/cli/src/commands/dashboard.ts:791-803(process.onhandlers) - Issue: SIGINT/SIGTERM listeners are added on each run without paired teardown in test/runtime reuse scenarios.
- Impact: Observed in
pnpm testas repeatedMaxListenersExceededWarning; indicates lifecycle cleanup fragility. - Suggestion: Use a listener registrar utility and always remove listeners in shutdown paths (or
once+ scoped cleanup). - Estimated effort: S
10) AI automation timeout does not cancel underlying work
- File(s):
packages/engine/src/cron-runner.ts:371-439(executeAiPromptStep) - Issue: Timeout uses
Promise.racewithsetTimeoutbut does not abort the running AI session/executor. - Impact: Timed-out automation steps can keep consuming resources and finish out-of-band.
- Suggestion: Add AbortSignal plumbing to
AiPromptExecutor, cancel timed-out runs explicitly, and clear timeout handles deterministically. - Estimated effort: M
Priority 3: Medium — Maintainability & Technical Debt
11) Oversized orchestration files with mixed responsibilities
- File(s):
packages/dashboard/src/routes.ts(10,037 LOC),packages/core/src/store.ts(3,278 LOC),packages/engine/src/executor.ts(3,114 LOC),packages/cli/src/extension.ts(1,747 LOC) - Issue: API wiring, domain logic, persistence, git operations, and recovery are combined in single files.
- Impact: Higher regression risk and slower change velocity; difficult targeted testing.
- Suggestion: Extract service boundaries with compatibility facades (e.g.,
TaskPersistence,TaskTransitions,GitOps,PlanningRoutes,GitRoutes,ToolRegistry). - Estimated effort: L
12) CLI dispatch is a giant manual switch tree
- File(s):
packages/cli/src/bin.ts:267-900(maincommand dispatch) - Issue: 70+ case branches with repeated usage checks and
process.exitbranches. - Impact: Easy to introduce inconsistent UX/error behavior; hard to verify command coverage.
- Suggestion: Move to declarative command table with argument schemas + shared runner/error mapping.
- Estimated effort: M
13) Error handling policy is inconsistent and frequently best-effort
- File(s):
packages/dashboard/src/routes.ts(289catchoccurrences),packages/engine/src/executor.ts(73),packages/engine/src/merger.ts(36),packages/core/src/db-migrate.ts:69-121 - Issue: Frequent silent catches and warning-only branches without structured error classes.
- Impact: Operational failures are harder to classify/retry/alert; behavior differs by module.
- Suggestion: Introduce shared error taxonomy (
UserInput,RetryableInfra,InvariantViolation) and mandatory structured logging fields (area,operation,retryable,cleanupAttempted). - Estimated effort: M
14) Event payload typing is weak at SSE boundaries
- File(s):
packages/dashboard/src/sse.ts:47-109 - Issue: SSE handlers are predominantly
any-typed payloads. - Impact: Runtime-only breakage for payload shape drift; weaker refactor safety.
- Suggestion: Define shared event contracts in
@fusion/coreand type SSE/WS emitters and client handlers against those contracts. - Estimated effort: S
Priority 4: Low — Nice-to-Have Improvements
15) Add disposal for cached TaskStore instances in extension
- File(s):
packages/cli/src/extension.ts:34-42(storeCache/getStore),packages/cli/src/extension.ts:1737-1746(session_shutdown) - Issue: Session shutdown clears cache keys but does not
close()each cachedTaskStoreDB handle. - Impact: Usually minor for short-lived sessions, but can keep descriptors open longer than needed.
- Suggestion: Iterate cached stores on shutdown and call
store.close()before clearing the map. - Estimated effort: S
16) Formalize route-level validation schemas
- File(s):
packages/dashboard/src/routes.ts(many manualif (!field || typeof field !== ...)blocks) - Issue: Validation logic is repetitive and hand-rolled per endpoint.
- Impact: Subtle behavior drift across similar routes and harder API contract evolution.
- Suggestion: Adopt shared schema validation middleware (e.g., Zod/TypeBox) and generate request/response typings from one source.
- Estimated effort: M
Cross-Cutting Themes
- Split-brain state risk from hybrid persistence: runtime writes often touch SQLite + filesystem mirrors without a single transactional boundary.
- Best-effort recovery over explicit state machines: many critical cleanup/retry paths rely on ad-hoc catches/timers instead of explicit lifecycle ownership.
- Project-context propagation is incomplete: most routes are scoped correctly, but exceptions create high-severity multi-project correctness risk.
- Operational side effects are under-tested: signal handlers, timers, and child-process restart behavior are not comprehensively asserted.
- Complexity concentration in a few mega-files: defects and regressions cluster in the same hotspots (
routes.ts,store.ts,executor.ts).
Test Coverage Gaps
- No direct tests for
useAgentshook:packages/dashboard/app/hooks/useAgents.tshas no matching test file. - Missing project-scoping assertions for import/planning routes: route tests exist (
packages/dashboard/src/routes.test.tsaround lines3603+,5679+) but do not exerciseprojectIdbehavior for/github/issues/import,/github/issues/batch-import,/github/pulls/import,/planning/*,/subtasks/*. - Child runtime lifecycle edge cases are lightly tested:
packages/engine/src/runtimes/child-process-runtime.test.tscurrently validates basic status/getter behavior, but not restart timer race conditions (killChild+handleUnhealthy). - Parallel-step failure path lacks coverage:
packages/engine/src/step-session-executor.test.tsfocuses on parsing/planning utilities; it does not test worktree-creation-failure behavior insideexecuteParallelWave. - Process listener cleanup is not asserted: CLI dashboard tests surfaced
MaxListenersExceededWarning, but tests do not currently fail on listener leaks.
Appendix: Key Metrics
- Largest source files reviewed:
packages/dashboard/src/routes.ts— 10,037 linespackages/core/src/store.ts— 3,278 linespackages/engine/src/executor.ts— 3,114 linespackages/cli/src/extension.ts— 1,747 lines
- Complexity signals:
- Route handlers in
routes.ts: 210 - Tool registrations in
extension.ts: 28 execSyncusage inroutes.ts: 69catchoccurrences inroutes.ts: 289
- Route handlers in
- Large tests (complexity proxy):
packages/engine/src/executor.test.ts— 8,659 linespackages/dashboard/src/routes.test.ts— 8,277 linespackages/core/src/store.test.ts— 5,991 lines
- Full test run result:
pnpm testpassed; however, repeatedMaxListenersExceededWarningoccurred during CLI dashboard tests, indicating listener lifecycle debt.