Prevent concurrent orphan merge after abort, protect long merging-phase
tools from false reclaim, emit run-audit on wedged reclaim, and race PR
merge dispatch the same way as direct AI merge.
AI-merge review hangs left activeMergeTaskId/mergeRunning set while
status=reviewing and overseer logEntry noise kept updatedAt fresh, so
self-healing never reclaimed the owner and the board showed no merging
badge. Race merge work with abort, force-abort on pause/reclaim, treat
reviewing as merge-active, and recover on merger agent silence; also
forward PluginRunner into AI merge so grok-cli merger matches chat.
Admit under-budget paused/heartbeat-model-unavailable agents to the shared
heartbeatErrorRecovery budget so timer, self-healing, and startup paths
retry without a manual Retry. Keep the pause reason when the budget is
exhausted so operators still see credential guidance.
## Summary
- Extend the workspace content-hash skip cache to **all** packages (not
just plugins), with `--force` / `--full` flags
- Default local CLI packaging to a **fast mode** (bin/extension +
migrations only); full desktop/plugin/DTS staging runs on CI or `pnpm
build:full`
- Enable TypeScript `incremental` builds for warm recompiles
- Add `maxConcurrentVerifications` (default **1**) so concurrent tasks
cannot stack monorepo typecheck/build and peg CPU
Warm `pnpm build` measured ~**126s → ~0.8s** when nothing changed.
## Test plan
- [x] `node --test scripts/__tests__/build-workspace.test.mjs` (12 pass)
- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/verification-concurrency.test.ts`
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/settings-parity.test.ts`
- [x] Local: first `pnpm build` rebuilds as needed; second warm `pnpm
build` skips all packages (~0.8s)
- [x] Fast CLI packaging logs skip of desktop/plugin staging without
`FUSION_CLI_FULL_PACKAGE`
- [ ] CI: `pnpm build` still full-packages under `CI=true` (plugin
staging / release surfaces)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added a Scheduling setting to limit concurrent verification tasks from
1–8, with a default of 1.
* Verification tasks now support cancellation while waiting or running.
* Added options for forced and full workspace builds.
* **Performance**
* Local builds can skip unchanged packages and use incremental
compilation for faster rebuilds.
* Local CLI packaging is faster by default, while full packaging remains
available when needed.
* **Documentation**
* Updated the settings reference with the new verification concurrency
option.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Generated PROMPT.md (after triage/planning — not the bootstrap stub) now
keeps the operator's original task description near the top under `##
Original Description`, so executors always see the source request even
after Mission/Steps rewrites.
- **AI-planned path:** planning templates (standard/fast/concise)
require a verbatim `## Original Description` section;
`buildSpecificationPrompt` instructs the planner; `finalizeApprovedTask`
deterministically injects/rewrites it as hygiene.
- **Non-AI path:** `generateSpecifiedPrompt` uses the same pure helper
so direct creates into non-intake columns get the same contract.
- **Description edits:** real specs keep `## Original Description` in
sync when `task.description` changes.
- **Unchanged:** bootstrap stubs and `isUnplannedSeedPrompt` equality
detection.
## Surfaces
| Surface | Change |
|--------|--------|
| `original-description-policy.ts` | Shared inject/rewrite helper |
| `agent-prompts.ts` | Template + requirement text |
| `triage.ts` finalize + `buildSpecificationPrompt` | Instructions +
post-write pin |
| `generateSpecifiedPromptImpl` | Non-AI specified PROMPT.md |
| `task-update.ts` | Description sync on real specs |
## Test plan
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/original-description-policy.test.ts
src/__tests__/agent-prompts.test.ts
src/__tests__/mesh-task-replication.test.ts
src/__tests__/store-create-intake-column.test.ts --silent=passed-only
--reporter=dot`
- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/triage.test.ts -t "Original Description|injects ##
Original" --silent=passed-only --reporter=dot`
- [ ] CI gate (Lint / Typecheck / Build / Gate)
## How to verify manually
1. Create a task with a distinctive description, let triage plan it (or
finalize a mock plan).
2. Open `.fusion/tasks/<id>/PROMPT.md` and confirm `## Original
Description` appears after title/metadata with the raw description,
before Mission / Before → After.
3. Direct-create into `todo` (non-intake) and confirm the non-AI
generated prompt also has the section.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Generated `PROMPT.md` specifications now include an `## Original
Description` section near the top.
- Operator task descriptions are preserved verbatim for AI-planned and
specified prompts.
- Updated prompts remain synchronized when task descriptions change.
- **Bug Fixes**
- Replaced paraphrased original descriptions with the correct task
description.
- Preserved existing prompt content during review and retry workflows.
- **Tests**
- Added coverage for placement, formatting, replacement, and idempotent
behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Import Tasks can now offer on-demand AI translation when a selected
GitHub or GitLab issue/PR title and body appear to be in a different
language than the active dashboard locale.
- Detect foreign-language content with a conservative client heuristic
(Unicode scripts + Latin stopwords)
- Show an opt-in banner: **Translate**, then **Show original / Show
translation**, plus **Dismiss**
- Call new `POST /api/ai/translate-text` (shared AI-helper rate limit
with refine/draft)
- Translation is **display-only** in the preview; imported task text
stays the original source language
## Why
Operators working in a non-English dashboard (or reading
non-dashboard-language issues) needed a way to understand import
candidates without leaving the preview or changing what gets imported.
## Test plan
- [x] Unit tests for language detection (`detectContentLanguage`)
- [x] Unit tests for translate request validation, response parsing, and
AI agent path
- [x] GitHub import modal: French content shows translate controls;
English content does not
- [x] Dashboard typecheck clean for app + server packages
- [ ] Manual: open Import Tasks with dashboard language English, select
a French/Korean issue, translate and toggle original
- [ ] Manual: confirm Import still creates the task with original
title/body
- [ ] Manual: dismiss banner for a selection and confirm it stays
dismissed for that item
## Notes
- Comments are not translated (title + body only)
- zh-CN / zh-TW share a CJK family so Chinese content does not prompt
translation when the UI is either Chinese locale
- Secondary locale catalogs have empty placeholders for the new
`git.translate*` keys (runtime falls back to English)
## Summary
Bundled plugins now persist shared runtime state in project-scoped
PostgreSQL tables instead of maintaining independent SQLite authority.
Reports, CLI Printing Press, Compound Engineering, Roadmap, Even
Realities, and WhatsApp all follow the same ownership and startup
contract as Fusion core.
## Design decisions
- Plugin schema hooks run through the host’s PostgreSQL owner and
enforce project isolation.
- The SDK exposes the host contract needed by bundled plugins without
importing engine internals.
- Legacy Roadmap ownership fixtures use the supported empty-owner
sentinel, preserving current composite primary/foreign keys while
exercising backfill behavior.
- The lockfile travels with the Even Realities PostgreSQL dependency so
packaged installs remain reproducible.
## Validation
- All six affected plugin builds pass.
- Affected plugin suites pass: 773 tests across Printing Press, Compound
Engineering, Even Realities, Reports, Roadmap, and WhatsApp.
- `pnpm test:gate` passes all 478 gate tests.
- This PR changes 40 files.
## Stack
- Depends on #2110 → #2109 → #2108.
- The documentation/release PR completes the stack.
Related: #2105
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Breaking Changes**
* PostgreSQL is now required for runtime storage; SQLite files are used
only as one-time migration inputs.
* The legacy `FUSION_NO_EMBEDDED_PG` fallback has been removed.
* **New Features**
* Added project-isolated PostgreSQL storage for plugins, reports, tasks,
notifications, and other plugin data.
* Added agent tools for reports and CLI service drafts.
* Added PostgreSQL schema initialization support for plugin authors.
* **Bug Fixes**
* Improved migration and recovery of legacy plugin state.
* Prevented cross-project data access and strengthened transactional
schema updates.
* **Documentation**
* Updated storage, migration, deployment, plugin authoring, CLI, and
dashboard guidance for PostgreSQL.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Windows embedded Postgres verification (CI `windows-latest` and elevated
desktop) fails because PostgreSQL refuses to run under an administrative
token:
> Execution of PostgreSQL by a user with administrative permissions is
not permitted.
GitHub Actions runners execute as `runneradmin` elevated, so the
existing `test:embedded-postgres` smoke (and any elevated Local-mode
desktop launch) cannot start the server.
### Fix
- When `isWindowsElevatedAdmin()` is true, **initdb / clients stay as
the launcher**, but the **postgres server** is started as a dedicated
non-admin local user (`fusion-pg`) via PowerShell `Start-Process
-Credential`.
- Readiness waits on the postgres log line `database system is ready to
accept connections` with a lightweight poll (no per-iteration
`tasklist`).
- Real-process vitest cases use a **180s** timeout on Windows (package
default is 15s, which killed healthy boots mid-start).
- Builds on top of the packaged-desktop asar materialization work
already on main (#2106).
## Test plan
- [x] `pnpm --filter @fusion/core test:embedded-postgres` on macOS
(33/33)
- [ ] `desktop-windows.yml` on `feature/win-pg-verify`:
- [ ] Smoke embedded Postgres on Windows
- [ ] Build + package Windows EXE
- [ ] Verify app.asar assets
- [ ] Optional: download portable EXE and manual Local mode smoke on a
Windows host
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Improved embedded PostgreSQL startup on Windows when Fusion runs with
elevated administrator privileges.
- When elevated, the embedded database now boots under a dedicated
non-administrator local account, with more reliable readiness detection,
logging, and shutdown cleanup.
- Enhanced database provisioning and now prefers `127.0.0.1` for Windows
connection addressing.
- **Tests**
- Added coverage for Windows elevation detection without starting
embedded PostgreSQL.
- Increased platform-dependent timeouts for embedded real-process tests
to avoid premature failures on Windows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
CLI commands, daemon/dashboard startup, packaged desktop startup, and
live-data maintenance scripts now share the mandatory PostgreSQL
lifecycle. Operators no longer risk a command silently reading or
writing a disconnected SQLite shadow when PostgreSQL setup fails.
## Design decisions
- Every startup owner retains and awaits its PostgreSQL shutdown
callback, including partial-startup failure paths.
- CLI project context and lock-retry flows resolve through asynchronous
project stores.
- Maintenance scripts use the shared backend helper; explicit database
migration/inspection remains the only CLI surface allowed to read legacy
SQLite sources.
## Validation
- CLI and Desktop typechecks pass on the stacked branch.
- `pnpm test:gate` passes all 478 gate tests.
- This PR changes 54 files.
## Stack
- Depends on #2109, which depends on #2108.
- Bundled plugins and docs/release follow in later PRs.
Related: #2105
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* PostgreSQL is now the authoritative store for structured project and
task metadata.
* Projects can be recognized and initialized using
`.fusion/project.json`, without creating a legacy SQLite database.
* CLI commands now retry transient PostgreSQL contention errors.
* **Bug Fixes**
* Improved cleanup when commands complete, fail, or run in the
background, preventing lingering resources.
* Improved desktop, server, and session shutdown reliability.
* **Documentation**
* Updated storage and standalone binary guidance to reflect PostgreSQL
and legacy SQLite compatibility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- bind Global and Project MCP editors to their raw values from
`/api/settings/scopes`
- keep edits isolated in the owning scope instead of mutating the merged
project-effective form
- persist changed MCP scopes independently of which Settings section is
visible when Save is pressed
- add unit and SettingsModal regressions for opposite scope values and
edit → navigate → save
## Root cause
`SettingsModal` passed the merged project-effective `form.mcpServers` to
both MCP sections. A project override could therefore appear as the
Global MCP value. Saving the apparent global change could then be
dropped as a no-op when compared with the actual global-scoped value.
The first fix still tied save routing to the active section. The
follow-up carries both raw scoped MCP values through
`splitSettingsSave`, where changed-only comparisons persist each owning
scope even after navigation.
## Testing
- `pnpm exec vitest run --project dashboard-app
app/__tests__/settings-save-split.test.ts` (32 passed on PR branch)
- `pnpm exec vitest run --project dashboard-app-quality-settings
app/components/__tests__/SettingsModal.general.test.tsx` (84 passed)
- `pnpm run typecheck`
- `pnpm exec eslint app/components/SettingsModal.tsx
app/components/settings/save-split.ts
app/__tests__/settings-save-split.test.ts
app/components/__tests__/SettingsModal.general.test.tsx`
- `pnpm run build`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed MCP settings so global and project configurations remain
correctly separated.
* Prevented inherited global settings from appearing as project
overrides.
* Preserved MCP edits when navigating between Settings sections.
* Ensured unchanged settings are not unnecessarily saved.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Engine and dashboard traffic now stays on the authoritative PostgreSQL
layer across execution, recovery, project discovery, planning sessions,
analytics, and shutdown. The dashboard no longer presents a migration
notice for a cutover that is already mandatory.
## Design decisions
- Runtime composition requires an async data layer instead of
constructing a hidden SQLite fallback.
- Engine workflow, mission, claim, and self-healing reads await their
PostgreSQL-backed store contracts.
- Project-scoped dashboard stores retain and close their backend owner
exactly once.
- The dashboard test quarantine entry remains paired with its Vitest
exclusion, preserving the repository’s deletion-ratchet policy.
## Validation
- Core, Engine, Dashboard, CLI, and Desktop typechecks pass on the
stacked branch.
- `pnpm test:gate` passes all 478 gate tests.
- This PR changes 62 files.
## Stack
- Depends on #2108.
- CLI/desktop/ops, plugins, and docs/release follow in later PRs.
Related: #2105
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Project discovery now recognizes projects using the
`.fusion/project.json` marker.
* Knowledge indexing and search are more reliable across project-scoped
storage.
* **Bug Fixes**
* Improved session, audit timeline, approval, monitoring, and analytics
data consistency.
* Prevented stale planning-session updates and project-store shutdown
races.
* Ensured chat usage and CLI session status are saved before continuing.
* **UI Changes**
* Removed the storage migration notice banner now that the PostgreSQL
transition is complete.
* **Reliability**
* Improved shutdown handling, workflow execution, and worktree behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Fusion’s core runtime now treats PostgreSQL as the authoritative
metadata store without leaving current CLI, dashboard, desktop, or
engine composition roots uncompilable between stack layers. This is the
99-file foundation for the larger cutover: subsequent PRs migrate the
remaining consumers, plugins, and operator surfaces.
## Design decisions
- Runtime store construction fails closed when an asynchronous
PostgreSQL layer is unavailable; SQLite remains readable only at
explicit migration and identity-recovery boundaries.
- Project ownership is enforced across active, archived, workflow,
mission, analytics, and plugin-schema data.
- The small set of cross-package files in this layer are
compatibility-critical call sites required for a green intermediate
commit, not the complete consumer migration.
- Schema migration 0008 remains assigned to session-advisor state from
current `main`; mission lineage idempotency advances to 0009 so neither
invariant can be skipped.
## Validation
- All affected package typechecks pass: Core, Engine, Dashboard, CLI,
and Desktop.
- `pnpm test:gate` passes: 478 tests across the engine gate, PostgreSQL
core gate, and CLI workflow shape.
- The PR changes exactly 99 files.
## Stack
This is the base PR. Engine/dashboard, CLI/desktop/ops, plugins, and
docs/release follow as stacked PRs, each below 100 changed files.
Related: #2105
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* PostgreSQL is now the standard runtime backend, with embedded
PostgreSQL enabled by default.
* Added project-scoped storage for tasks, archives, chat sessions,
missions, knowledge pages, and operational data.
* Improved archived-task search, filtering, pagination, and restoration.
* Added safer plugin schema initialization with validation and project
isolation.
* Added PostgreSQL-backed workflow, mission, validator, and dashboard
capabilities.
* **Bug Fixes**
* Improved startup timeout cancellation and resource cleanup.
* Prevented cross-project data access and phantom reservation cleanup
errors.
* Ensured archived tasks remain read-only and asynchronous writes
complete reliably.
* Retired SQLite opt-out settings with clear startup errors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- Operators could see more agents running than Global Max Concurrent
(e.g. 5 running with cap 4: 4 planners + 1 executor).
- Scheduler now `tryAcquire`s a shared semaphore slot before
todo→in-progress and hands that pre-held slot to the executor/graph run.
- Triage admits planners against the live top-level running-agent claim
(planning + in-progress + active in-review), not only
`semaphore.availableCount`.
- Executor claims the pre-held slot for the full run and avoids a second
top-level acquire on step/seam re-entry (deadlock under a full cap).
## Test plan
- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/concurrency.test.ts src/__tests__/triage.test.ts`
- [x] Regression: triage leaves room when 1 in-progress agent is live
under global cap 4
- [x] Regression: pre-held executor slot register/take/drop handoff
- [ ] Manual: set Global Max Concurrent and Max triage concurrent to 4,
fill Planning + run 1 In Progress; footer should not show 5 running
under a full steady state
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Improved global concurrency enforcement so the scheduler and executor
never start more agents than the configured limit, including tighter
top-level “claimed capacity” accounting.
- Updated triage admission control to consider global top-level
utilization, factoring processing tasks and agents already running to
prevent over-admitting planners.
- Added safer pre-held concurrency-slot handoff behavior to avoid
capacity leaks and drift during graph routing, step execution, and
legacy fallback.
- Ensured reserved capacity is reliably released on early exits, failed
dispatches, and other aborted paths (with idempotent cleanup).
- Refreshed concurrency diagnostics to better explain whether throttling
is due to project or global limits, with clearer claimed/processing
visibility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Fixes `CREATE ROLE fusion_runtime` race condition in migration
`0006_project_ownership.sql` that causes 30 compound-engineering test
failures on CI.
## Root Cause
Concurrent test databases on the same PostgreSQL service container race
on `CREATE ROLE fusion_runtime`: the `IF NOT EXISTS` check is not atomic
(roles are cluster-wide, not per-database). Between the check and the
`CREATE ROLE`, another session can create the role, causing error
`23505` (unique_violation).
## Fix
Replace the non-atomic `IF NOT EXISTS` guard with a `BEGIN...EXCEPTION
WHEN duplicate_object OR unique_violation THEN NULL; END;` block that
safely handles the race.
## Verification
| Check | Result |
|---|---|
| compound-engineering (pipeline-store + orchestrator + session-routes)
| ✅ 41 passed |
| Engine shard 1/2 | ✅ 3826 passed, 0 failed |
| Merge gate | ✅ 471 passed |
| Lint | ✅ exit 0 |
## What & why
Workflow graph merge failures classified `implementation-incomplete`
(i.e. the merge node reports there is no implementation proof — no
branch / no committed work) could still be routed to the no-op merge
requester and false-complete the task as **done**. This hides genuinely
unlanded work behind a green "merge" and is the merge-side sibling of
the "(no feedback captured)" no-verdict dispatch defect.
Closes the truthfulness gap: an `implementation-incomplete` merge-graph
failure now **fails closed** when there is no executable proof to
resume, or **requeues resumable parsed steps** back to `todo` for
execution — it is never handed to a no-branch no-op merge requester.
Refs #1991 (no-op merge truthfulness). Sibling of #1946 (no-verdict "(no
feedback captured)" dispatch defect).
## Change
- New classifier `routeImplementationIncompleteMergeGraphFailure(live,
failedNode)`:
- clears paused-aborted state + active worktree,
- requeues resumable parsed steps via the existing execution-resume
router when the task still has non-terminal workflow steps,
- otherwise fails closed (`status: "failed"` with a logged, explicit
reason).
- Defense-in-depth: `isRetryableBenignMergePauseAbort` and the
merge-requester route both short-circuit (`return false`) for
`implementation-incomplete`, so this value can never reach the no-op
merge requester.
- `handleGraphFailure` routes genuine (non-global-pause,
non-completion-finalize, non-user-paused) `implementation-incomplete`
merge-graph failures through the new classifier.
- Resume-eligibility predicate treats an `implementation-incomplete`
merge failure with **no** incomplete steps as fail-closed, and keeps the
premature-merge-with-incomplete-steps requeue path.
Legitimate `noCommitsExpected` no-op merges are explicitly preserved
(regression test included).
## Tests
New regression coverage in:
-
`packages/engine/src/__tests__/reliability-interactions/merge-node-paused-abort-retryable.test.ts`
— parametrized across merge node ids: (a) no-proof
`implementation-incomplete` fails closed without requesting a no-op
merge; (b) resumable parsed steps are requeued to `todo` for execution
resume, not no-op-merged.
- `packages/engine/src/__tests__/executor-fast-mode-workflows.test.ts` —
a legitimate `noCommitsExpected` builtin:coding merge is still allowed
(guard does not over-block).
Verification (engine package):
pnpm --filter @fusion/engine exec vitest run \
src/__tests__/executor-fast-mode-workflows.test.ts \
src/__tests__/reliability-interactions/merge-node-paused-abort-retryable.test.ts
# => 2 files, 69 tests, 0 failures
pnpm check:changesets # pass
pnpm --filter @fusion/engine typecheck # 0 errors
A `patch` changeset for `@runfusion/fusion` is included.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Prevented “implementation-incomplete” workflow merge failures from
being treated as successful no-op merges.
* Ensured tasks with resumable implementation steps move back to
execution to continue where they left off.
* Ensured tasks without sufficient implementation evidence fail safely
rather than entering misleading retry/no-op paths.
* Improved paused/aborted merge-failure handling to avoid incorrect
completion states.
* **Tests**
* Added/expanded coverage for fast-mode coding merges and
implementation-incomplete pause/abort retry classification.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Fusion <noreply@runfusion.ai>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
## Summary
- Follow-up to #2083: remove the temp `fusion-omp-mcp-schemas-*.json`
file when the OMP Fusion `fn_*` MCP tool bridge is disposed.
- Prevents schema JSON from accumulating under `tmpdir()` after every
OMP ACP session.
## Context
PR #2083 was merged before this cleanup commit landed on
`feature/omp-acp`. This cherry-picks that fix onto main.
## Test plan
- [x] `pnpm --filter @fusion-plugin-examples/omp-runtime test` (includes
dispose removes schema path assertion)
## Summary
Packaged Fusion desktop Local mode failed after the SQLite→Postgres
cutover:
1. **Embedded Postgres** could not start from `app.asar` — platform
packages resolve `initdb`/`postgres` via `import.meta.url` into the asar
virtual path, and `spawn` fails with `ENOTDIR`.
2. **After Postgres was fixed**, Local mode still fell back to the mode
chooser because `@fusion-plugin-examples/omp-runtime` was never built
into `dist/` (dashboard imports it from `runtime-provider-probes.ts`).
This PR makes packaged Local mode boot embedded Postgres reliably and
keep the dashboard shell up.
### Changes
- **CJS bootstrap** (`main-bootstrap.cjs`) as Electron `main`: patches
`child_process.spawn` / `fs.promises.stat|chmod` before the ESM main
loads so asar binary paths rewrite to real files.
- **Materialize** the full native PG install (`bin` + `lib` + `share`)
under `~/.fusion/embedded-postgres/runtime-bin/<plat-arch>/`.
- **electron-builder**: full `asarUnpack` of embedded-postgres packages;
allowlist PG deps and `@fusion-plugin-examples/**/*` (+ plugin-sdk / ACP
SDK).
- **Build** `fusion-plugin-omp-runtime` with the other dashboard-static
runtime plugins; export `DASHBOARD_RUNTIME_PLUGIN_PACKAGES` for tests.
- Unit coverage for asar path rewrite, packaging allowlists, and omp
build inclusion.
## Test plan
- [x] `pnpm --filter @fusion/core test:embedded-postgres` (23/23)
- [x] Desktop packaging unit tests (`build-bundling`,
`electron-builder-config`)
- [x] Packaged macOS `Fusion.app` Local mode:
- [x] `embedded postgres: ready on port … (database "fusion")`
- [x] `desktopMode` stays `"local"` (no chooser fallback)
- [x] `GET /api/health` → `status: ok`, `database.healthy: true`,
`engine.available: true`
- [x] Linux embedded binary lifecycle smoke (Docker aarch64,
`@embedded-postgres/linux-arm64`) — initdb/start/persist/restart
- [ ] CI release desktop jobs (macOS/Linux) when this lands
- [ ] Windows packaged desktop Local + PG (separate agent / host)
## Verification notes
| Platform | Embedded Postgres | Packaged Local shell |
|----------|-------------------|----------------------|
| macOS | Working | Working after this PR |
| Linux | Native binary smoke pass | Full AppImage not built on this
host |
| Windows | Out of scope here | Separate verification |
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved embedded PostgreSQL reliability in Electron-packaged apps by
rewriting bundled `app.asar` binary paths to their unpacked/materialized
locations.
* Ensured embedded PostgreSQL runtime binaries resolve correctly across
platforms/architectures, with best-effort executable permissions and
macOS dylib link normalization.
* **Packaging**
* Updated the desktop Electron entry to use a bootstrap module for
embedded PostgreSQL binary resolution.
* Expanded Electron Builder inclusion and asar-unpack rules for
embedded-postgres and related packages, plus required runtime plugin/sdk
assets.
* **Tests**
* Updated and added checks to match the new packaging and plugin/runtime
expectations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
Reported: planning gets stuck in a cycle of retrying and regenerating
after a response was already supplied.
After the user answers a planning question, `submitResponse` pushed the
answer to history but left `session.currentQuestion` pointing at the
just-answered question for the whole next generation. The planning SSE
route's catch-up path re-emits `currentQuestion` to every fresh
connection — and each FN-7946 auto-retry (#2073) opens a fresh
connection. So after any generation error:
1. Auto-retry connects a fresh stream → the server re-emits the
**already-answered** question.
2. The client treats any question event as progress: it **resets the
3-attempt auto-retry budget** and re-shows the answered question.
3. The retry regenerates; if it errors again the cycle repeats with a
fresh budget — an unbounded retry/regenerate loop. Re-answering the
stale question also 409-collided with the in-flight generation, feeding
the same loop.
## Fix
Invariant: `currentQuestion` is only set while the session is genuinely
awaiting user input.
- `submitResponse` clears it the moment an answer is accepted (normal
turns and the deepening checkpoint), while preserving the legacy 200
respond contract on generation failure (the modal ignores the body and
lets the SSE error drive recovery).
- `retrySession` scrubs stale questions persisted by pre-fix builds
before regenerating.
- `buildSessionFromRow` only restores a question when the persisted row
is `awaiting_input`.
- `didSubmitSameAnswer` now compares against the last history entry so
the duplicate-submit 409 message survives.
- Agent onboarding gets the same fix (its SSE route also re-emits
`currentQuestion` on connect); retry now asks the next question instead
of re-asking the answered one.
Surface enumeration: mission and milestone interviews keep questions the
same way but their SSE routes never re-emit on connect, and the
auto-retry budget machinery is Planning-Mode-only — planning +
onboarding were the two affected surfaces.
## Symptom Verification
- **Original symptom:** after answering a question, Planning Mode loops
between "Retrying…" and regenerating, re-showing the already-answered
question, with the auto-retry budget never exhausting.
- **Exact reproduction:** answer a question, have the next generation
fail (stuck watchdog/provider error), let the client auto-retry open a
fresh SSE connection.
- **Assertion it is gone:** new regression suite
`planning-answered-question-reemit.test.ts` asserts `currentQuestion` is
cleared mid-generation, on generation failure, on retry, and on restore
from non-`awaiting_input` rows — so the SSE catch-up path has nothing
stale to re-emit. All 5 tests fail against pre-fix code and pass with
the fix; an onboarding regression test covers the sibling surface.
## Verification
- New regression tests: 5/5 fail on pre-fix code, pass with the fix
(plus 1 onboarding test).
- Existing suites: 137 planning server tests pass (3 failures in
`routes-planning.test.ts` fail identically without this change —
pre-existing on the branch); all 69 `PlanningModeModal.planning-flow`
client tests pass; `tsc --noEmit` clean; `pnpm check:changesets` passes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Made Planning Mode (and related planning controls) lock-free and
multi-tab—no more take-over/active-in-another-tab lock overlays.
* **Bug Fixes**
* Fixed Planning Mode retry/generation flows where already-answered
questions could reappear.
* Ensured answered questions clear immediately and aren’t re-emitted
during session recovery/SSE catch-up.
* Improved session restoration and preserved legacy recovery behavior
when generation fails after an answer.
* **Tests**
* Added regression coverage for the answered-question invariant and
updated existing tests to reflect lock-free behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---
## Follow-up: Planning Mode is now multi-tab via DB state (lock-free)
Second commit removes all cross-tab coordination from planning — the
persisted session row is the single source of truth and multiple tabs
can read and interact with the same session:
- **Server:** `/planning/*` routes no longer run `checkSessionLock` or
parse `tabId`; a stale `tabId` from an older client is ignored instead
of 409'd. Subtask/mission interview routes keep their existing lock
behavior.
- **Client:** `PlanningModeModal` drops `useSessionLock`, the
`useAiSessionSync` BroadcastChannel broadcasts,
`sessionTabId`/`lockSessionId` state, and the "Take Control" overlay.
Tabs stay current via the per-session SSE stream plus the global
`ai_session:updated` events `useBackgroundSessions` already consumes;
concurrent writes resolve via the server's generation-in-progress guard
(409).
- **API client:** planning functions lose their `tabId` params.
- **Fix uncovered by the refactor:** the 8s stuck-poll now resolves the
session id inside each tick — the removed lock state was what previously
re-armed the poll after Start Planning resolved the session id.
- Also fixes a pre-existing PG-cutover break in
`planning-generation-cancellation.test.ts` (`getSession` is async).
Verification: 144 client planning tests and 137 server planning tests
pass (the 3 remaining `routes-planning.test.ts` failures are
pre-existing on the branch and fail identically without these changes);
`tsc --noEmit` and eslint clean on changed files; `pnpm
check:changesets` passes. Lock-conflict route tests were rewritten to
assert lock-free semantics, plus a new modal test proving a session
stays fully interactive with no lock acquisition even when another tab
is active.
---
## Follow-up 2: the per-tab session lock is gone entirely
Third commit extends the multi-tab model from planning to **every** AI
interview surface (planning, subtask breakdown, mission interview,
milestone/slice interview) and deletes the lock machinery root and
branch.
**Server**
- Deleted the `/ai-sessions/:id/lock`, `/lock/force`, and `/lock/beacon`
routes.
- Dropped `checkSessionLock` from every
planning/subtask/mission/milestone route (both copies — `routes.ts` and
`mission-routes.ts`). A `tabId` from an older client is ignored, never
409'd; all `tabId` body parsing is gone.
- Dropped `acquireLock` / `releaseLock` / `forceAcquireLock` /
`getLockHolder` / `releaseStaleLocks` from `AiSessionStore`, plus the
`@fusion/core` async helpers (`acquireAiSessionLock` et al) and core's
re-exports.
- Removed `lockedByTab`/`lockedAt` from
`AiSessionRow`/`AiSessionSummary`, the upsert SQL, and all four session
producers.
**Client**
- Deleted `useSessionLock` and the now-orphaned `getSessionTabId` util.
- Removed the Take Control overlay, the "active in another tab" banners,
and `BackgroundTasksIndicator`'s active-elsewhere gate (the confirm
prompt and lock badge — sessions now just open).
- Reduced `useAiSessionSync` to what its own comments already called it
— a low-latency *status* supplement to SSE: no `activeTabMap`,
`broadcastLock/Unlock/Heartbeat`, `owningTabId`, `tab:*` messages, or
stale-heartbeat sweep.
- Dropped `tabId` from every session API client function; removed the
lock CSS.
**Deliberately kept: the two DB columns.** `ai_sessions.locked_by_tab` /
`locked_at` remain as dead, always-NULL columns with a deprecation note.
Dropping them is an irreversible migration, and released binaries still
name those columns explicitly in their upsert — an older install pointed
at the same database would fail every session write. They can be dropped
once no such binary can reach it. No code reads or writes them.
**Verification**: 397 client tests and 137 server planning tests pass
(the same 3 `routes-planning.test.ts` failures are pre-existing —
verified identical on a clean stash); `tsc --noEmit` clean for
`@fusion/core` and `@fusion/dashboard`; eslint clean on all changed
files; the 30 PG `schema-applier` tests pass (they exercise the retained
columns); `pnpm check:changesets` passes. The lock-conflict route tests
and both modal lock tests were rewritten to assert the inverse: routes
and modals stay fully interactive while another tab "holds" a lock, and
the lock API is never called.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary
- reuse the dashboard command's backend-aware per-project `TaskStore`
cache during project-scoped plugin skill discovery
- obtain plugin state through `TaskStore.getPluginStore()` instead of
constructing bare SQLite-default `PluginStore` / `TaskStore` instances
- keep cached project stores alive for the dashboard process while still
stopping request-scoped plugin loaders
- add a regression covering the real Skills adapter callback and refresh
the dashboard test fixture with `getAsyncLayer()`
## Root cause
`GET /api/skills/discovered` resolved the project correctly, then
`getProjectScopedPluginSkills()` constructed new stores without an
`AsyncDataLayer`. After `VAL-REMOVAL-005`, that enters the physically
removed synchronous SQLite runtime and returns HTTP 500 even when
PostgreSQL health, projects, tasks, and both project engines are
healthy.
The existing route tests mocked the Skills adapter callback, so they did
not exercise this CLI wiring.
## Verification
- targeted dashboard regression: 1 passed, 91 skipped
- `pnpm lint`
- `pnpm --filter @runfusion/fusion typecheck`
- `pnpm --filter @runfusion/fusion build`
- `pnpm check:changesets --strict`
- `git diff --check`
Live Atlas validation against the migrated embedded PostgreSQL runtime:
- `/api/skills/discovered?projectId=proj_84f4645c2da64288`: HTTP 200, 36
skills
- `/api/skills/discovered?projectId=proj_7538a9dd46c24c5f`: HTTP 200, 36
skills
- local dashboard and Tailscale dashboard: HTTP 200
- controlled SIGTERM: launchd restarted the dashboard and both Skills
routes remained healthy
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Fixed dashboard project-scoped plugin-skill discovery in PostgreSQL
mode with safer store reuse/teardown and request-scoped plugin-loader
lifecycle.
- Improved dashboard cleanup to avoid duplicate concurrent store closes
and ensured proper shutdown behavior per root type.
- Made `fusion_runtime` role creation race-safe during concurrent
PostgreSQL migrations.
- **New Features**
- Added `persistRuntimeState` option to control whether plugin runtime
state changes are persisted.
- **Tests**
- Expanded dashboard and core hot-reload tests to verify scoped,
non-persistent runtime behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Submit Anthropic OAuth manual codes on the first mobile tap instead of requiring keyboard dismissal first.
- Add a reusable touch action gesture hook that handles touch/pointer activation before synthetic clicks.
- Wire the OAuth manual code Submit button to invoke submission on the first touch while preventing duplicate click handling.
- Cover the mobile double-tap regression and document the UI bug pattern for future fixes.
Files changed:
.../oauth-manual-code-mobile-double-tap-submit.md | 60 +++++++++++
.../app/components/OAuthManualCodeForm.tsx | 31 +++++-
.../__tests__/OAuthManualCodeForm.test.tsx | 110 +++++++++++++++++++++
.../hooks/__tests__/useTouchActionGesture.test.ts | 110 +++++++++++++++++++++
.../dashboard/app/hooks/useTouchActionGesture.ts | 89 +++++++++++++++++
5 files changed, 399 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7953
Fusion-Task-Lineage: d387cdbd-25a7-4b7d-add6-27a1ded5cbea
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Use PostgreSQL workflow selections in the dashboard TUI, authoritative driver, and graph-runner adapter so migrated tasks cannot silently fall back to the coding workflow.
Route workflow selections, model lanes, goals, skills, and reliability reads through project-scoped async stores. Recover heartbeat agents parked against an unrelated project model and preserve workflow JSONB patches atomically.
Preserve authenticated CLI usage after migration, surface OAuth remediation, and use a single distinct model fallback before parking permanent failures. Keep transient credential errors retryable and confirm each OAuth expiry notification independently.
Fusion-Task-Id: FN-7952
Use canonical Anthropic OAuth refresh, keep CLI-backed providers out of API-key auth rows, parse Grok's omitted zero usage, and carry board workflow context into task creation.
Report source scans, per-table copy milestones, checksum phases, verification outcomes, and unambiguous failure or finalization status during first-boot and manual migrations.
## Summary
Fixes shard 4 full-suite failures: chat_sessions schema baseline gap +
two remaining PG auth bugs missed by PR #2086.
**Scope: shard 4 only.** Shards 1/2 (engine timeouts) and shard 3
(compound-engineering CI-only failure) are separate issues not addressed
here.
## Changes
### Schema baseline gap — `chat_sessions` missing columns (42703 error)
- **`0000_initial.sql`**: Added `validator_thinking_level` and
`planning_thinking_level` columns to `CREATE TABLE
project.chat_sessions`. These exist in the Drizzle schema
(`project.ts:1492-1493`) but were missing from the SQL baseline, causing
`column does not exist` on all chat_sessions inserts in fresh test
databases.
- **`postgres-health.ts`**: Added both columns to
`EXPECTED_PROJECT_COLUMNS` self-heal list so existing databases also get
them via ALTER TABLE.
**Fixes**: `chat-store-content-search-edit.pg.test.ts` (5 tests),
`satellite-db-injected-stores.test.ts` (2 tests)
### Remaining auth bugs (password auth failed for user "runner")
- **`allocator-cross-project.test.ts`**: Still had `process.env.USER` in
inline adminExec — missed by PR #2086's batch fix. Replaced with
`PG_TEST_URL_BASE` connection string.
- **`connection.test.ts`**: Used `FUSION_PG_TEST_URL` (not set on CI)
with a bare default URL lacking credentials. `postgres.js` fell back to
OS user `runner`. Changed to derive from `FUSION_PG_TEST_URL_BASE` which
includes credentials.
**Fixes**: `allocator-cross-project.test.ts` (2 tests),
`connection.test.ts` (3 tests)
## Verification
| Check | Result |
|---|---|
| Merge gate (`pnpm test:gate`) | ✅ 294 + 114 + 63 = 471 passed |
| chat-store-content-search-edit | ✅ 5 passed |
| satellite-db-injected-stores | ✅ 10 passed |
| allocator-cross-project | ✅ 2 passed |
| connection | ✅ 13 passed |
| Lint | ✅ exit 0 |
| Typecheck | ✅ clean |
## Not in scope
- **Shards 1/2**: Engine test suite timeouts with
`getAsyncLayer`/`updateSettings` mock warnings. Pre-existing.
- **Shard 3**: `compound-engineering stage-skill-loading.test.ts` — 14
tests fail on CI (`TypeError: Cannot read properties of undefined
(reading 'close')`), pass locally. Likely CI-specific teardown issue.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added separate `validator_thinking_level` and
`planning_thinking_level` fields to chat session data, including
database schema and health-check recognition.
* **Bug Fixes**
* Improved PostgreSQL test connectivity by using configured connection
URL settings instead of hardcoded local defaults.
* Made Postgres-related test teardown null-safe to avoid failures when
setup doesn’t complete.
* **Tests**
* Updated automated test quarantine/exclusions for known failing engine
and reliability-interaction cases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Preserve legacy-only tables, recover partial migration ownership, and enforce project-local keys, relationships, agents, merge queues, task IDs, archives, and monitor state with PostgreSQL RLS.
Report successful cutovers once in the dashboard and system inbox with retained SQLite paths and Discord support details.
Migrate central SQLite state once per cluster, isolate project metadata, and preserve file-local revision identities while verifying accumulated shared tables.
## Summary
- detect persisted executor sessions that cannot continue from an
assistant message
- clear the stale session pointer after the executor lock is released
- requeue the task with workflow progress preserved instead of marking
it failed
## Test plan
- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/executor-step-session.test.ts -t "clears a stale
assistant-continuation resume session and requeues without marking the
task failed" --project=engine-default --silent=passed-only
--reporter=dot`
- `pnpm --filter @fusion/engine typecheck`
- `pnpm build`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved recovery when an assistant continuation session becomes stale
by restarting a fresh session with bounded retries, preserving overall
task progress.
* Clears invalid persisted session/continuation state and defers requeue
until coordination cleanup is safe.
* When retries are exhausted, tasks are marked failed and the error
callback runs (without routing to review).
* **Tests**
* Added coverage for stale-session recovery, repeated-stale behavior,
correct (or skipped) requeue decisions, and progress/error handling
paths.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- Add `fusion-plugin-omp-runtime` so Fusion agents can run through
operator-installed **Oh My Pi (`omp`)** over the [Agent Client
Protocol](https://omp.sh/docs/acp) (`omp acp`).
- Wire staged/bundled install, Settings → Authentication card (enable +
binary path), model discovery (`omp models` → `omp-cli/*`), and MCP
eligibility for runtime id `omp`.
- Forward Fusion `systemPrompt` via ACP `session/new`
`_meta.systemPromptOverride`.
## How operators use it
1. Install/auth `omp` (credentials under `~/.omp`).
2. Enable **Oh My Pi — via omp ACP** in Settings → Authentication
(optional binary path).
3. Set agent **Runtime Source → OMP Runtime** (`runtimeHint: "omp"`), or
pick an `omp-cli/*` model when enabled.
## Known v1 gaps
- No Grok-style Fusion `fn_*` loopback tool bridge yet (operator MCP is
forwarded; in-process custom tools are not).
- Model is fixed at spawn (`omp --model … acp`); no mid-session Fusion
model switch.
## Test plan
- [x] `pnpm --filter @fusion-plugin-examples/omp-runtime test` (unit +
live ACP when `omp` is on PATH)
- [x] Auth routes: `POST /api/auth/omp-cli`, `GET
/api/providers/omp-cli/status`
- [x] Engine `runtimeSupportsMcp("omp")`
- [ ] Manual: enable card in dashboard, select OMP runtime on an agent,
run a short chat turn
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added Oh My Pi (OMP) CLI support as an ACP-backed runtime and model
provider, including model discovery and probing.
* Added dashboard auth/status controls to enable OMP, check readiness,
and configure the local binary path (with validation).
* Exposed OMP custom `fn_*` tools via an MCP loopback bridge, plus
optional filesystem capabilities and stricter tool permission gating.
* **Documentation**
* Added/expanded OMP runtime contract and integration docs (including
the ACP session/handshake flow).
* **Tests**
* Added Vitest coverage for settings wiring, provider status, model
discovery, runtime sessions, permissions, MCP bridging, and live
connectivity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Follow-up to PR #2086 addressing two Greptile review findings.
## P2 — Missing `psql` binary guard (Greptile P2)
`hasPg` in `_helpers.ts` previously checked only TCP connectivity to
PostgreSQL. But `adminExecAsync()` shells out to the `psql` CLI for DDL
(`CREATE/DROP DATABASE`). On a runner where Postgres is reachable but
`psql` isn't installed, tests would fail with `spawn psql ENOENT`
instead of skipping cleanly.
**Fix**: Added `hasPsql = spawnSync("psql", ["--version"]).status === 0`
to the `hasPg` guard, so tests skip when either Postgres is unreachable
OR `psql` is missing.
## P1 — Expired quarantine entries (Greptile P1)
The 16 dashboard test files quarantined on 2026-06-25 were past the
14-day deletion ratchet (AGENTS.md: "DELETED after 14 days unless
rescued"). Per the ratchet, the test files were deleted and all
references removed:
- **Deleted 16 test files** (CSS drift, mock drift, mobile-render
regressions)
- **Removed 16 entries** from `scripts/lib/test-quarantine.json` (only
the CLI entry remains)
- **Emptied `quarantinedDashboardTests` array** in
`packages/dashboard/vitest.config.ts`
## Verification
| Check | Result |
|---|---|
| Merge gate (`pnpm test:gate`) | ✅ 294 + 99 + 63 = 456 passed |
| Dashboard curated-gate | ✅ passes (891 files, 892 executed, 1
skip-listed, 1 quarantined) |
| Typecheck (engine) | ✅ clean |
| Lint | ✅ exit 0 |
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Tests**
* Removed multiple outdated dashboard UI, CSS/token, theme contrast, and
API/route test suites.
* Updated dashboard test configuration to stop excluding quarantined
tests and to prune the quality shard to the current set.
* Updated the Vitest split/config guard to match the new test fixture
set.
* Improved PostgreSQL test detection by requiring the `psql` CLI before
running database checks.
* Adjusted quarantine tracking by adding a new CLI extension
distribution ledger entry and removing obsolete dashboard quarantine
entries.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- add asynchronous PostgreSQL parity to research commands and engine
execution paths
- persist Roadmap, Compound Engineering sessions, and WhatsApp state in
PostgreSQL
- harden cancellation, concurrency, reconnect, replay-claim, and
detached-promise behavior
- bundle the PostgreSQL-backed integration implementations in the
published CLI
This is PR 2 of 2 and is intentionally stacked on #2088. It contains 44
changed files; merge #2088 first, then retarget this PR to `main` if
GitHub does not do so automatically.
## Verification
- `pnpm check:changesets --strict`
- `pnpm lint`
- `pnpm test:gate`: 463 tests passed
- Compound Engineering plugin: 299 tests passed
- Roadmap plugin: 144 tests passed
- WhatsApp plugin: 27 tests passed
- research CLI: 18 tests passed
- `pnpm verify:fast`: all scoped typechecks, builds, CLI build, and boot
smoke passed
## Post-Deploy Monitoring & Validation
- deploy only after #2088 and verify schema migration `0002` is present
- monitor research cancellation, automation claims, agent execution,
plugin schema initialization, and unhandled rejections
- validate Roadmap ownership, Compound Engineering session recovery, and
WhatsApp reconnect/replay deduplication
- compare per-project plugin and workflow counts after cutover
- restore the pre-deploy backup for data rollback; avoid an in-place
schema downgrade
## Summary
- make SQLite-to-PostgreSQL cutover retryable, fail-closed, versioned,
and transactionally serialized
- isolate migration sessions from runtime traffic and apply schema
upgrades through `0002`
- enforce tenant ownership across automations, analytics, activity,
usage, agent runs, evals, and todos
- replace expired SQLite-only coverage with PostgreSQL parity and
concurrency coverage
This is PR 1 of 2. The stacked follow-up restores PostgreSQL parity for
CLI, engine, dashboard, and bundled integrations.
## Verification
- `pnpm check:changesets --strict`
- `pnpm --filter @fusion/core typecheck`
- migration schema, connection, and SQLite cutover suite: 57 tests
passed
- `pnpm test:gate`: 463 tests passed
## Post-Deploy Monitoring & Validation
- take a restorable PostgreSQL backup before deploy
- confirm `fusion_schema_migrations` contains `0002`
- confirm each expected project has a complete
`fusion_sqlite_migrations` row
- verify no null or empty tenant ownership in automations, activity
logs, agent runs, and usage events
- monitor for ownership inference failures, cutover verification
failures, and migration session errors
- restore the backup for data rollback; do not downgrade the
tenant-isolation schema in place
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* PostgreSQL-backed analytics and live dashboard metrics are now
project-scoped (activity, tools, monitor, signals, and live snapshots).
* Evaluation runs and scheduled eval batches received lifecycle
improvements (ordering, updates, and execution flow).
* Todo list changes now emit events; WhatsApp persistence and
project-scoped roadmap data are supported.
* **Bug Fixes**
* SQLite-to-PostgreSQL cutovers now fail safely with stronger
verification, serialized cutover handling, and safer project ownership.
* PostgreSQL backend writes and reads are now strictly project-isolated
and fail closed when project context is missing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
In the dashboard **Planning Mode** screen, a planning session that runs
to completion **and creates multiple tasks** disappears from the "saved
sessions" history panel ("No saved sessions yet").
## Root cause
The multi-task route `POST /api/planning/create-tasks` called
`cleanupSession(planningSessionId)` → `unpersistSession` →
`_aiSessionStore.delete`, **deleting the persisted `ai_sessions` row**.
The single-task route `POST /api/planning/create-task` deliberately uses
`releaseSession` instead — it releases the in-memory runtime but
**keeps** the persisted completed row, which is what the history list
reads (`listAll` includes completed sessions). So multi-task creation
erased its own history entry.
## Fix
Switch the multi-task route to `releaseSession`, matching the
single-task path. The completed `type: "planning"` session row now
survives task creation and appears in history.
## Tests
Adds a regression test in `routes-planning.test.ts` asserting the
persisted planning row survives multi-task creation (verified it fails
against the old `cleanupSession` behavior). Merge gate green locally;
changeset included.
Made with Claude (see `Co-Authored-By` trailer).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed an issue where Planning Mode multi-task sessions could be
removed from planning history after task creation.
* Completed multi-task planning sessions are now reliably retained with
their completed status.
* **Tests**
* Added a regression test for the multi-task Planning Mode flow to
confirm all tasks are created and the planning session remains persisted
in history.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: Claude <noreply@anthropic.com>
## Problem
A task whose Plan Review step returns verdict `REVISE` can loop forever:
plan → plan-review REVISE → `needs-replan` → re-plan → near-identical
plan → REVISE → repeat. The triage **pre-execution** Plan Review gate
(`runPlanReviewBeforeExecution`) sets `status: "needs-replan"` on REVISE
with **no cap and no escape to `awaiting-approval`** — unlike the
executor graph path, which already has `PLAN_REVIEW_REPLAN_HARD_CAP`.
Under `planApprovalMode: require-all` there is also no human exit,
because the task never reaches `awaiting-approval`.
Separately, replan feedback (`triage.ts`) was derived only from
`task.log` comment actions + the latest user comment; it never consulted
the plan-review verdict stored in `task.workflowStepResults`.
## Fix
1. **Thread plan-review feedback into replan** — when re-planning with
no comment-derived feedback, seed `buildSpecificationPrompt` from the
most recent `plan-review` REVISE `output` in `workflowStepResults`
(existing user/AI-comment precedence preserved).
2. **Bounded cap** — new `planReviewReplanCount` counter (`types.ts`,
`store.ts` column + updateTask, `db.ts` migration 146,
`manual-retry-reset.ts`). After `PLAN_REVIEW_GATE_REPLAN_CAP = 3`
consecutive REVISE replans the task escalates to `awaiting-approval`
(`awaitingApprovalReason: "plan-review-replan-cap"`) instead of
replanning. Counter resets on APPROVE.
## Tests
Adds `triage-replan-feedback-from-plan-review.test.ts` and
`triage-plan-review-replan-cap.test.ts`. Merge gate green locally
(`verify:fast`, `test:gate` 337+63, `lint`); changeset included.
Made with Claude (see `Co-Authored-By` trailer).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Prevented Plan Review “REVISE” from looping indefinitely by enforcing
a bounded replan cap.
* After repeated Plan Review replans, tasks now escalate to an
approval-hold state with a dedicated reason.
* Improved replan feedback by seeding from the latest Plan Review output
when no explicit feedback is available; the counter clears when Plan
Review approves.
* Manual retries now reset the Plan Review replan cap counter.
* **Documentation**
* Added release notes describing the Plan Review replan safeguards.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
## Summary
Conflict resolution for closed
[#2074](https://github.com/Runfusion/Fusion/pull/2074) (FN-001
multi-project branch-group store scoping), rebased onto current `main`.
#2074 closed when its fork head was briefly reset to `main` during a ref
update; maintainer write access to the fork head only works while the PR
is open, so that PR could not be reopened without new fork commits.
This branch carries the same fix:
- Request-scoped `TaskStore` for branch-group
list/read/assign/promote/abandon
- Integrated reconcile/close uses the request store for cwd +
persistence
- Compatible with async branch-group store APIs and main’s
CentralProjectIdentity (`projectId` trim)
- Postgres durable FN-7438 tests + padded `projectId` regression
## Verification
- `FUSION_DASHBOARD_DEEP=1 pnpm --filter @fusion/dashboard exec vitest
run --project dashboard-api src/__tests__/routes-branch-groups.test.ts
src/__tests__/integrated-routers-group-pr-token.test.ts
src/__tests__/routes-context-project-identity.test.ts
--silent=passed-only --reporter=dot` — 3 files, 41 tests passed.
---------
Co-authored-by: Tchorizo <295840812+Tchorizo@users.noreply.github.com>
Co-authored-by: Fusion <noreply@runfusion.ai>
## Summary
- preserve target defaults when legacy SQLite rows contain `NULL` or
empty strings for `NOT NULL` jsonb columns
- derive the fallback from PostgreSQL column metadata instead of
hard-coding table or column names
- keep migration checksum conversion aligned with inserted values
- add regression coverage for legacy null JSON fields
## Test plan
- `corepack pnpm@10.33.0 --filter @fusion/core typecheck`
- `FUSION_PG_TEST_SKIP=1 corepack pnpm@10.33.0 --filter @fusion/core
exec vitest run src/__tests__/postgres/sqlite-migrator.test.ts`
- `corepack pnpm@10.33.0 --filter @fusion/core build`
The PostgreSQL-backed integration suite requires `psql`, which is
unavailable in this environment; CI should exercise the added migration
case against PostgreSQL.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved SQLite-to-PostgreSQL migration for legacy rows containing
`NULL` or empty JSON values.
* For eligible `NOT NULL` `jsonb` columns, the migrator now
preserves/apply compatible PostgreSQL column defaults instead of writing
SQL `NULL`.
* Migration verification now aligns with the final values inserted into
PostgreSQL to prevent checksum mismatches.
* **Tests**
* Added an end-to-end legacy migration case to confirm `jsonb` fields
materialize as empty defaults (e.g., `[]`) rather than staying `NULL`.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Implements the explicit-project-identity directive at the route layer: a
request's store is resolved from request projectId -> the daemon's registered
launch project id -> only for unregistered launch directories, the raw
launch-dir store (one-time warn). Resolution funnels through a single seam
(routes/context.ts resolveRequestProjectId + resolveStoreForProjectId); the
server.ts realtime resolveScopedStore delegates to the same function instead
of mirroring it. Scattered 'projectId ? getOrCreateProjectStore : store'
ternaries in todo/goals/mission/insights/research/evals routes now use the
shared seam.
Code-review fixes folded in (multi-agent ce-code-review, 10 reviewers):
- mission interview drafts list/discard resolve the same project id the
start endpoint stamps (write/read no longer split namespaces)
- chat stream-attach guard treats legacy null-projectId sessions as
launch-owned instead of 404ing; planner-chat dedup retries unscoped to
reuse legacy sessions instead of duplicating them
- getProjectIdFromRequest trims and rejects whitespace-only ids
- evals/research middleware forwards store-resolution failures to Express
(previously rethrew inside a detached promise chain and hung the request)
- one-time launch-dir fallback warning routes through runtimeLogger
- seam + delegation + whitespace + engine-fallthrough covered in
routes-context-project-identity.test.ts (10 cases)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Raise the durable archive cutoff to 0.60.0, keep only the current release in CHANGELOG.md, and rewrite labeled summary/category/dev package aggregates for 0.47–0.59 into operator-facing Highlights/New/Fixed notes.