Address review findings on the FN-7956 hang fix:
- Per-tool outer timeouts so fn_research_run(wait_for_completion) is not clipped by a flat 60s budget
- Longer budgets for skills install, import/browse, and web_fetch
- Boot-failure cooldown + orphan-boot log when store boot times out
- Log timeout/abort/errors from the extension wrap; clearer host-extension skip reason
- Tests for budgets, research wait, and sessionPurpose forwarding
Plan Review REVISE previously fed feedback without the rejected plan body, so triage rewrote from title/description and looped. Seed the draft for surgical revision, use reviewType spec for the pre-execution gate, and tighten planner/reviewer prompts toward blocking-only REVISE with concrete edits.
Give planner/reviewer pairs more room to converge before escalating, and surface why a task is parked for plan approval—especially plan-review-replan-cap non-convergence—on cards, detail, and notifications.
AI merge review could park forever when the host fusion extension loaded
fn_task_show and booted a second TaskStore without a tool timeout (FN-7956).
- Skip host @runfusion/fusion extensions for sessionPurpose "merger"
- Forward sessionPurpose into createFnAgent for that policy
- Coalesce + 30s-bound extension TaskStore boots; ALS-propagate AbortSignal
- Wrap every extension registerTool execute with 60s timeout/abort fail-closed
- Unit tests for merger host-extension skip and tool timeout helpers
AI merge spends most of its time in reviewing and landing, not merging.
Treat the full merge pipeline as active so cards, workflow switcher, and
stall suppression show Merging… while the pump owns a task.
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
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 -->
## 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>
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.
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.
## 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
## 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>
# Migrate storage from SQLite to PostgreSQL — full dashboard cutover
Migrates Fusion's storage layer to the embedded PostgreSQL
`AsyncDataLayer` (the default backend) and **completes the
satellite-store + feature cutover** so every dashboard and Command
Center surface works in PG mode.
## Status — every surface works in embedded-PG mode
Verified live against a running embedded-Postgres dashboard (all
**200**, zero 5xx) and gate-tested (**23 files / 99 tests** on embedded
PG, plus engine-core 294 and ci-shape 63 in the blocking merge gate;
core/engine/cli/dashboard typecheck clean).
| Area | Surfaces | State |
|---|---|---|
| Satellite stores | workflows, todos, insights, research, missions,
goals, mailbox | ✅ |
| Views | artifacts, documents, evals | ✅ |
| Command Center | activity, productivity, team, tokens, tools,
**workflows**, **github**, **signals**, **plugin-activations**, **live**
(all 10) | ✅ |
| Run execution | insight generation, research run execution | ✅
(store-path; AI step needs a provider) |
| Live updates | SSE push for mission/research/insight events | ✅ |
| Workflow editing | create / update / delete / select (+ id counter) |
✅ |
| Engine | mission autopilot, incident-signal ingestion, regression
storm-guard, agent wake-on-message | ✅ |
| Core | tasks, agents, secrets, automations, memory, chat, usage, PRs,
git | ✅ |
## Approach
Each satellite store gets an `Async<Store>` wrapper exposing the sync
store's method names over the existing `async-*-store.ts` helpers;
`get<Store>Store()` returns a `Sync | Async` union; consumers `await`
(harmless on sync), and engine/CLI paths that can't convert use
`instanceof Sync` graceful fallback. Analytics aggregators branch on
`"ping" in dbOrLayer` to run schema-qualified raw SQL over `project.*`
(snake_case) in PG. Executors/orchestrators/autopilot are
await-converted to drive the union store; the async store wrappers
extend `EventEmitter` so SSE live-push fires in both backends.
Not-yet-ported capabilities degrade gracefully (never 500) and are
individually called out in commits.
## Sync with main
The branch is kept continuously merged with `main` (currently through
FN-7845, 2026-07-12); the earlier "final rebase deferred" note no longer
applies. Use **Create a merge commit** (or squash) to land it — GitHub's
rebase-merge cannot replay a merge-maintained branch.
## Residual Review Findings
Multi-agent code review of the PostgreSQL satellite-store ports (U1–U5)
applied 3 safe fixes (see `fix(review): apply autofix feedback`). The
following are **real but gated** — recorded here as follow-up work
rather than auto-applied. All are SQLite→PostgreSQL
**concurrency/atomicity regressions**: the sync stores were immune only
by SQLite's single-writer, single-threaded-handler execution; the async
ports open multi-await read-modify-write windows. **Reachability is low
today** because the execution engines that generate concurrent same-run
mutations (insight run executor, research orchestrator/dispatcher) are
`instanceof`-gated to sync mode in PG. No process-crash class survived
(all engine fallbacks correctly guard the sync store).
- **[P1] Research `appendResearchEvent` dual-write is non-atomic**
(`packages/core/src/async-research-store.ts`, corroborated: adversarial
+ reliability). The `research_run_events` insert (own transaction) and
the `run.events` jsonb update are separate writes — a crash between
them, or two concurrent appends, splits the table count from the jsonb
array. **Fix:** perform the seq-insert and the jsonb update in one
`layer.transactionImmediate`.
- **[P1] Research run terminal-reversion via stale full-row persist**
(`async-research-store.ts` `persistResearchRun`/`updateResearchStatus`).
Concurrent `PATCH /runs/:id/status` + `POST /runs/:id/events` can revert
a terminal run to `running` by overwriting the whole row, bypassing the
transition guard. **Fix:** scoped column `UPDATE`s with a `WHERE status
…` guard, or optimistic version column.
- **[P2] `updateResearchRun`/`updateInsightRun` read-then-write TOCTOU**
— concurrent PATCHes last-writer-wins on the lifecycle merge. **Fix:**
`SELECT … FOR UPDATE` / enclosing transaction.
- **[P2] `upsertRun`/`createRunOrThrowConflict` check-then-create race**
(`async-insight-store.ts`) — two callers can each create an "active"
run. **Fix:** partial unique index on `(projectId, trigger) WHERE status
IN ('pending','running')`.
- **[P3] `createResearchRetryRun` return-value divergence** — sync
returns the pre-update `queued` snapshot; async returns the reloaded
`retry_waiting` run (persisted state is identical). Pick one side for
cross-backend parity.
- **[P2/perf] Mission `getMissionWithHierarchy`/`getMissionHealth` N+1
fan-out** — O(milestones×slices) sequential round-trips hold one pool
slot per request; can starve the pool for large hierarchies. **Fix:**
batched/joined reads.
- **Testing gaps:** no PG-mode concurrency tests (interleaved
status/event mutations), no sync↔async parity assertion for the
lifecycle-error codes, and no mission status/health rollup parity test
vs the sync `MissionStore`.
~~Out of scope (deferred): AI run *execution* (insight/research) +
mission autopilot + live SSE mission events remain sync-gated/degraded
in PG mode.~~ **Since ported** — insight/research run execution, mission
autopilot, and SSE live push all run on the async layer now, which also
makes the concurrency findings above genuinely reachable; they remain
open follow-ups.
---
## Update — 2026-07-12: production-readiness hardening & live acceptance
Everything below landed on this branch since the description above was
written:
**Production blockers from review — fixed**
- `recoverStaleTransitionPending` ported to the async layer (backend
moves write + clear the crash-safe marker; startup/maintenance sweeps no
longer throw).
- Lost-update class fixed: `atomicWriteTaskJson`/`WithAudit` write
changed columns only (full-row upserts silently resurrected stale fields
across concurrent store instances — the "task stuck unplanned forever"
bug).
- First-boot **auto-migration**: booting the PG backend over a project
with a legacy `fusion.db` migrates it automatically (loud failure,
SQLite kept as backup), and the dashboard shows a one-time **"your data
was migrated" banner** with the backup paths and a Need-help Discord
link.
- `pg_dump`/`pg_restore` discovered from common install locations for
embedded-mode backups.
- The PG suite is part of the blocking merge gate (`test:pg-gate`).
**Multi-project isolation (PR #2007, merged into this branch)**
- `project_id` partition key on tasks / archived tasks / config,
`taskProjectScope` threaded through every scan/claim/count, per-project
config rows, layer bound to the project at startup.
- Review P1 follow-up: the shared cold-storage `archive.archived_tasks`
table is also partitioned and all archived-board reads/counts/searches
are scoped.
- Schema drift self-heal generalized to schema-qualified columns so
existing databases upgrade in place.
**Other changes**
- Node settings sync **removed** in PG mode (409
`settings-sync-disabled-postgres`) — nodes share state by connecting to
the same database; auth sync kept (per-machine file).
- Perf (review findings): `listTasks` pushes column filter + ORDER BY +
LIMIT/OFFSET into SQL; `getConversation` capped to the most recent 200
messages.
- Fixed a false "operator action required" pause-abort log fired on
every successfully auto-merged task.
**Live acceptance — PASSED (2026-07-12)**
A sandboxed instance (isolated HOME, embedded PG, real Opus executor)
ran a task through the complete cycle: create → triage (AI spec) →
execute → in-review → AI squash-merge landed on the project's `main` →
done. A write+read sweep of every data surface (settings, comments,
documents, attachments + artifact bridge + artifact edit, chat with real
generation, goals, missions, agent mail, secrets, workflows, memory, CC
analytics) was green on embedded PG.
**Known remaining work**
- The per-project `config` PK re-key has no upgrade path for
pre-isolation embedded-PG databases (needs a real `DROP
CONSTRAINT`/re-key migration; fresh databases are fine).
- `pg_dump`/`pg_restore` binaries are not yet bundled in release
artifacts (PATH/common-location discovery only).
- The satellite-store concurrency findings listed above.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Phil Larson <hello@phillarson.xyz>
Co-authored-by: fusion-merge <fusion-merge@local>
Add a dedicated merger model lane (project + global provider/model/thinking) so merge-agent sessions no longer share only the default model, without inheriting executor/planner/reviewer lanes.
Fixes agents silently going stale for hours even though the heartbeat repair audit process was running.
- HeartbeatTriggerScheduler now runs an independent watchdog (armTimerAuditWatchdog/checkTimerAuditLiveness) that tracks the audit loop's last-run timestamp and re-arms + immediately re-runs the 60s audit interval if it goes stale beyond a bounded multiple of the cadence, so a silently dropped audit driver self-heals instead of leaving active agents unrepaired for hours.
- Tracks consecutive non-advancing zombie-timer re-arms per agent (nonAdvancingRearmState) and escalates once the count crosses a threshold, recording consecutiveNonAdvancingRearms/nonAdvancingEscalated in agent.metadata.heartbeatTimerRepair and logging reason=heartbeat-rearm-nonadvancing-escalated instead of silently churning the same zombie-timer-rearmed repair forever.
- Clears non-advancing rearm state on unregister, non-eligible agents, paused settings, and stale-run-reap skip paths so tracking never leaks stale per-agent counters.
- Watchdog and its interval handle are armed in start() and cleared in stop() alongside the existing audit interval.
- Adds a changeset (patch) describing the fix, and updates docs/agents.md and docs/architecture.md to document the FN-7939 audit watchdog and non-advancing escalation behavior.
- Adds heartbeat-scheduler.test.ts coverage for watchdog re-arm/liveness and non-advancing escalation.
Files changed:
.changeset/fn-7939-heartbeat-audit-supervision.md | 7 +
docs/agents.md | 8 +-
docs/architecture.md | 1 +
.../src/__tests__/heartbeat-scheduler.test.ts | 209 +++++++++++++++++++++
packages/engine/src/agent-heartbeat.ts | 128 ++++++++++++-
5 files changed, 341 insertions(+), 12 deletions(-)
Fusion-Task-Id: FN-7939
Fusion-Task-Lineage: 9fa90240-4333-4588-b595-aef3811b1524
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Hardens the FN-7863 execute-node self-requeue loop guard so residual execute_loop_stall cases (#2043/#2045/#2046/#2047) can no longer reset the loop counter forever via non-terminal signature drift.
- Change buildExecuteRequeueLoopSignature to track terminal step count (done/skipped) plus total step count instead of raw currentStep + every step status, so pending/in-progress oscillation no longer produces a "new" signature each cycle.
- Add buildExecuteRequeueLoopHighWaterSignature, which derives current terminal-step progress via the shared signature parser (parseExecuteRequeueLoopProgressSignature) and only resets the streak on monotonic forward progress, keeping a high-water mark across cycles so decreases/oscillation below the high-water still count toward exhaustion.
- Update executor.ts's execute self-requeue dispatch path to use the new high-water helper when deciding whether to reset (1) or increment executeRequeueLoopCount, replacing the previous raw signature-equality check.
- Extend execute-requeue-loop-guard.test.ts with regression coverage: a drifting-signature case that oscillates step order/status with no terminal progress (still terminalizes at MAX_EXECUTE_REQUEUE_LOOP_CYCLES), a done/in-progress oscillation case bounded after the high-water stops increasing, and an updated "real progress never terminalizes" case driven by genuine monotonic done-step advancement.
- Update docs/architecture.md's FN-7863/FN-7926 self-healing notes to describe the new terminal-step high-water signature and cross-reference FN-7941.
Files changed:
docs/architecture.md | 4 +-
.../execute-requeue-loop-guard.test.ts | 83 +++++++++++++++++++++-
packages/engine/src/executor.ts | 54 ++++++++++++--
3 files changed, 130 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7941
Fusion-Task-Lineage: cbf1e536-d29b-40da-bdd8-8c34d8d6b1ca
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds validatorThinkingLevel and planningThinkingLevel task fields so the Reviewer and Planning AI lanes can override reasoning effort independently of the shared task thinkingLevel, with dashboard UI, storage, and runtime fallback wiring.
- Add validatorThinkingLevel and planningThinkingLevel to Task/TaskCreateInput types (packages/core/src/types.ts)
- Persist the new fields in the SQLite schema and store read/write/replication paths (packages/core/src/db.ts, store.ts, mesh-task-replication.ts)
- Wire executor and triage lanes to fall back per-lane thinking level -> task.thinkingLevel -> existing settings/lane fallback (packages/engine/src/executor.ts, triage.ts)
- Add per-lane thinking-level selectors to the ModelSelectorTab UI, alongside the existing thinking-level control (packages/dashboard/app/components/ModelSelectorTab.tsx)
- Expose the new fields through the legacy task API and task-workflow routes (packages/dashboard/app/api/legacy.ts, packages/dashboard/src/routes/register-task-workflow-routes.ts)
- Document the new settings in dashboard-guide.md and settings-reference.md
- Add a minor changeset and unit/integration test coverage for store persistence, routes, UI, and agent-session helpers
Files changed:
.changeset/per-lane-task-thinking.md | 7 ++
docs/dashboard-guide.md | 2 +
docs/settings-reference.md | 2 +-
.../src/__tests__/store-thinking-levels.test.ts | 43 +++++++
packages/core/src/db.ts | 15 ++-
packages/core/src/mesh-task-replication.ts | 4 +
packages/core/src/store.ts | 24 +++-
packages/core/src/types.ts | 12 ++
packages/dashboard/app/api/legacy.ts | 2 +
.../dashboard/app/components/ModelSelectorTab.tsx | 126 ++++++++++++++++++++-
.../components/__tests__/ModelSelectorTab.test.tsx | 50 +++++++-
.../src/__tests__/routes-tasks-ops.test.ts | 74 ++++++++++++
.../src/routes/register-task-workflow-routes.ts | 19 +++-
.../src/__tests__/agent-session-helpers.test.ts | 15 +++
packages/engine/src/executor.ts | 16 ++-
packages/engine/src/triage.ts | 8 +-
16 files changed, 395 insertions(+), 24 deletions(-)
Fusion-Task-Id: FN-7932
Fusion-Task-Lineage: 4202f774-aab9-41d2-86a0-f5277dd0f848
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
Fixes ALL failing shards from the latest full-suite run (29225946428)
AND adds a structural gate check to prevent the recurring mock-export
drift pattern that has caused every full-suite failure across rounds
1–9.
## What broke (run 29225946428, commit 504b0f8b0)
| Shard | Root cause | Tests fixed |
|---|---|---|
| **3 (CLI)** | `workflowValidateParams` (FN-7911) missing from
`@fusion/engine` mock | 8 files |
| **3 (CLI)** | `skill-sync.test.ts` — `fn_workflow_validate` missing
from engine-tools.md | 1 file |
| **4 (dashboard)** | 6 chat default settings keys missing from
description allowlist | 1 file |
| **1+2 (engine)** | `additionalSkillPaths` missing from
`buildSessionSkillContext` mocks (FN-1510/1511) | 10 tests |
| **1+2 (engine)** | heartbeat FN-7878 changed paused→error for generic
run failures | 1 test |
| **1+2 (engine)** | executor `updateTask` exact-match →
`objectContaining` (new fields) | 2 tests |
| **1+2 (engine)** | `connectMcpSessionTools` mock missing for pi.test
MCP forwarding | 1 test |
## Structural fix — `scripts/check-mock-completeness.mjs` (the "fix for
good")
**New gate check** added to `pnpm test:gate`. Statically validates every
hardcoded `vi.mock("@fusion/dashboard")` and `vi.mock("@fusion/engine")`
factory covers all named imports the source file uses. Runs in <0.2s, no
module evaluation.
**How it works:**
1. Extracts named exports from each barrel
(`packages/dashboard/src/index.ts`, `packages/engine/src/index.ts`)
2. For each test file with a hardcoded `vi.mock` factory (no
`importOriginal`/`importActual` spread):
- Resolves source files the test covers (static + dynamic imports,
convention mapping)
- Extracts what those source files named-import from the barrel
- Resolves spread helpers (e.g. `...workflowAuthoringEngineMock`) by
reading the helper's exported keys
- Reports any barrel exports that are named-imported by source but
absent from the mock
**Why this fixes the recurring pattern:** Every round 1–9 failure was a
new barrel export imported by source but missing from a test mock. This
check catches it at gate time, before merge — not after the full-suite
fails on main.
Also completed all 15 latent mock gaps the guard found on first run (9
dashboard + 6 engine), including expanding the centralized
`workflowAuthoringEngineMock` helper with all `extension.ts` named
imports.
## Verification
- Gate (with new check): exit 0 ✅
- CLI: 355/355 passed ✅
- Engine (6 fixed files): 250/250 passed ✅
- i18n + settings: verified ✅
- Mock completeness guard: ✅ (0 issues)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Documented a new non-destructive workflow validation tool that
performs a dry-run and returns typed validation errors.
* **Tests**
* Updated and strengthened CLI, dashboard, extension, and engine tests
with more accurate mock exports and more resilient assertions.
* Adjusted expectations for session/heartbeat and retry-related
behaviors.
* **Chores**
* Added an automated mock-completeness gate and integrated it into the
test quality gate to keep mocks aligned with available platform exports.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Stops the execute → pause-abort → re-queue-to-todo infinite loop for tasks whose implementation work is done but a dependency/blockedBy blocker is still live, by diverting them into a dedicated parked state instead of feeding the FN-7863 no-progress backstop or looping forever.
- Add TaskExecutor.parkCompletedBlockedTask(): when work is complete but getTaskCompletionBlocker() still reports a blocker, park the task in todo with pausedReason:"completed-work-blocked", status:"queued", preserved worktree/branch/steps, and a cleared execute-requeue signature.
- Replace shouldFinalizeCompletedTask's boolean with getCompletedTaskFinalizationDecision() returning "finalize" | "blocked" | "incomplete" so both the paused-after-completion and finalization call sites can react to the new "blocked" outcome without re-entering execution.
- Divert completed-but-blocked tasks before the FN-7863 execute-requeue-loop counter increments, so waiting-on-dependency states are no longer misclassified as EXECUTION_DISPATCH_LOOP_EXHAUSTED.
- Add SelfHealingManager.reconcileCompletedBlockedTasks(): a bounded sweep (wired into both startup/maintenance and periodic self-healing passes) that clears the park and advances the task to review once getTaskCompletionBlockerForStore() resolves, guarded by auto-merge eligibility, user-pause, and live-execution checks; failed advances re-park rather than strand the row.
- Add run-audit mutation types task:completed-blocked-parked and task:completed-blocked-advanced (ids/counts/outcomes-only metadata) plus AGENTS.md/docs/architecture.md entries documenting the new lifecycle.
- Extend execute-requeue-loop-guard.test.ts with coverage for the park/advance flow, including the zero-step task edge case.
Files changed:
AGENTS.md | 1 +
docs/architecture.md | 2 +
.../execute-requeue-loop-guard.test.ts | 256 ++++++++++++++++++++-
packages/engine/src/executor.ts | 85 ++++++-
packages/engine/src/run-audit.ts | 4 +
packages/engine/src/self-healing.ts | 95 ++++++++
6 files changed, 432 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7926
Fusion-Task-Lineage: e47945f4-a816-447e-9ea1-7c13105d0ba9
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Root cause of the reported incident: store init ran the retired flag-off
evacuation on every open, dumping Coding (Ideas) intake cards into triage
where they were auto-planned and executed. Init now always runs the
workflow-aware integrity pass (with a stale-selection mis-mapping guard and
per-pass IR memoization) and evacuation remains toggle-only.
Engine rebounds (Plan Review REVISE, stale-spec, fs-validation) resolve a
workflow-aware replan column instead of hardcoding triage; needs-replan now
counts as unplanned for hold-release dispatch so rejected plans cannot
re-execute; triage rediscovers needs-replan todo cards and refinement seed
prompts (shared buildRefinementSeedPrompt/isUnplannedSeedPrompt); the
fs-validation rebound sets needs-replan so unreadable-prompt tasks re-spec
instead of livelocking.
Dashboard: the All-workflows board renders column-orphaned tasks instead of
silently dropping them (hidden columns stay hidden), and the FN-7591 refetch
also fires for present-but-unrepresentable workflow mappings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a persisted Thinking Level (reasoning-effort) selector to manual insight generation, threading the selection through the dashboard API, insight run metadata, and retries.
- Add inline Thinking Level selector to the InsightsView model-config popover, persisted to localStorage (fusion-insight-thinking)
- Thread thinkingLevel through triggerInsightRun (legacy API client) and useInsights.runInsights
- Validate and store thinkingLevel in insight run inputMetadata.metadata on the POST /insights/run route; resolve it via resolvePlanningThinkingLevel for the actual generation call
- Recover and reapply the original run's thinkingLevel on retry (retryInsightRunLifecycle) so retries reuse the same reasoning-effort setting
- Export resolvePlanningThinkingLevel from @fusion/engine
- Document the new Thinking Level selector in docs/dashboard-guide.md
- Add a minor changeset for @runfusion/fusion
Files changed:
.changeset/fn-7901-insight-thinking-level.md | 7 ++
docs/dashboard-guide.md | 1 +
.../app/__tests__/insight-model-selector.test.tsx | 41 ++++++++++-
packages/dashboard/app/api/legacy.ts | 2 +
packages/dashboard/app/components/InsightsView.tsx | 24 +++++-
.../app/hooks/__tests__/useInsights.test.ts | 36 ++++++++-
packages/dashboard/app/hooks/useInsights.ts | 6 +-
.../src/__tests__/insights-routes.test.ts | 86 ++++++++++++++++++++++
packages/dashboard/src/insights-routes.ts | 36 ++++++++-
packages/engine/src/index.ts | 1 +
10 files changed, 227 insertions(+), 13 deletions(-)
Fusion-Task-Id: FN-7901
Fusion-Task-Lineage: a6249526-e97d-403e-b853-e497d16f425b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a best-effort, idempotent dashboard inbox notice announcing the upcoming embedded-Postgres storage migration, delivered once per project on the first engine start under the Fusion 0.59.x release line.
- New `deliverPostgresMigrationNoticeIfNeeded` in `@fusion/engine` (`postgres-migration-notice.ts`) builds and sends a `system` -> `user` inbox message via `MessageStore`, gated to version `0.59.x` by `isPostgresMigrationNoticeVersion`
- Idempotency via existing inbox message `metadata.kind = "postgres-migration-notice"` marker (no new settings key or table), so restarts never duplicate the notice
- Delivery is fully best-effort: any `MessageStore` failure is caught, logged as a warning, and never blocks or fails `ProjectEngine.start()`
- `ProjectEngine.start()` invokes the notice after runtime start, using an injected `cliPackageVersion` threaded from the CLI layer through `EngineManagerOptions` / `ProjectEngineOptions` so the engine never imports CLI/dashboard code directly
- `daemon.ts`, `dashboard.ts`, and `serve.ts` resolve the published `@runfusion/fusion` version via `getCliPackageVersion` / `isUnresolvedCliPackageVersion` and pass it into `ProjectEngineManager`
- Exported new symbols (`POSTGRES_MIGRATION_HELP_URL`, `POSTGRES_MIGRATION_NOTICE_KIND`, `deliverPostgresMigrationNoticeIfNeeded`, `isPostgresMigrationNoticeVersion`, related types) from `@fusion/engine`, and `isUnresolvedCliPackageVersion` from `@fusion/dashboard`
- New unit tests covering version matching and single-delivery/idempotency behavior
- Docs updated (`docs/agents.md`, `docs/dashboard-guide.md`) to describe the one-time notice and its dedup key
- Changeset added for `@runfusion/fusion` (minor, feature)
Files changed:
.changeset/fn-7879-postgres-migration-inbox-notice.md | 7 ++
docs/agents.md | 1 +
docs/dashboard-guide.md | 1 +
packages/cli/src/commands/daemon.ts | 6 +-
packages/cli/src/commands/dashboard.ts | 5 +
packages/cli/src/commands/serve.ts | 6 +-
packages/dashboard/src/index.ts | 2 +-
packages/engine/src/__tests__/postgres-migration-notice.test.ts | 140 +++++++++++++++++++++
packages/engine/src/index.ts | 9 ++
packages/engine/src/postgres-migration-notice.ts | 107 ++++++++++++++++
packages/engine/src/project-engine-manager.ts | 6 +
packages/engine/src/project-engine.ts | 12 ++
12 files changed, 299 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7879
Fusion-Task-Lineage: 201877e5-6bdc-4168-a8ac-ae0e50ec8308
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Durable agents were parking as error-unrecoverable on any non-transient-pattern failure, even generic/unknown blips that manual Retry immediately fixed; this changes the default to recoverable and reserves immediate unrecoverable parking for operator-actionable errors.
- isHeartbeatErrorRecoverable now returns true unless the error is operator-actionable (auth/model/billing/scope) or a stale worktree/module-resolution error, instead of requiring a transient-pattern match via classifyError
- Add OAuth scope-requirement and insufficient-scope patterns to the operator-actionable error detector so those still park immediately
- Update heartbeat-error-recovery, heartbeat-executor, self-healing, and transient-error-detector tests to cover the new default-recoverable behavior
- Update AGENTS.md and docs/architecture.md durable-agent error recovery notes to describe the new recoverable-by-default policy
- Add changeset documenting the fix
Files changed:
.changeset/fn-7878-recoverable-default.md | 7 ++
AGENTS.md | 2 +-
docs/architecture.md | 4 +-
.../src/__tests__/heartbeat-error-recovery.test.ts | 90 +++++++++++++++++++---
.../src/__tests__/heartbeat-executor.test.ts | 17 ++--
packages/engine/src/__tests__/self-healing.test.ts | 45 ++++++-----
.../src/__tests__/transient-error-detector.test.ts | 7 +-
packages/engine/src/agent-heartbeat.ts | 8 +-
packages/engine/src/transient-error-detector.ts | 2 +
9 files changed, 137 insertions(+), 45 deletions(-)
Fusion-Task-Id: FN-7878
Fusion-Task-Lineage: 6f929af9-ceef-404f-95c9-98f26478f020
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
- Exclude revoked/suspended/disabled/deactivated keys, inactive subscriptions,
and locked accounts from the transient-auth classifier: no retry fixes those,
so they stay operator-actionable even inside an authentication_error envelope.
- Self-healing sweep logs unrecoverable-error parks separately from
recovered-to-active agents (return value still counts actions taken).
- Document same-session retry continuation semantics at the heartbeat
withRateLimitRetry call site (side-effect replay concern).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A routine Claude Max OAuth token rotation (~8h) fails the in-flight call with
401 authentication_error "Invalid authentication credentials" even though
refreshed credentials already exist on disk. Three compounding defects turned
that into a fleet-wide operator-action park:
- The heartbeat prompt path never ran under withRateLimitRetry (executor/
triage/merger all do), so the 401 immediately failed the run. Now wrapped.
- The 401 matched the operator-actionable /credential/ pattern and defaulted
to "permanent", so FN-7859 parked agents paused/error-unrecoverable. A new
shared isTransientAuthCredentialError classifier (also used by
rate-limit-retry) classifies rotation 401s transient + not operator-
actionable; OAuth scope-grant and API-key failures still park.
- Heartbeat failure classification ran on the stack-bearing error detail;
stack frames like "at withRateLimitRetry (.../rate-limit-retry.ts)" match
the usage-limit /rate[_\s]?limit/ pattern. Classification and
agent.lastError now use the message; stderrExcerpt keeps the full detail.
Self-healing additionally un-parks agents previously paused with
error-unrecoverable whose lastError now classifies recoverable, bounded by
the shared heartbeat error-recovery budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>