Commit Graph

161 Commits

Author SHA1 Message Date
gsxdsm
e24f765495 FN-8252: rescue quarantined engine tests
Restore non-mechanical engine coverage with PostgreSQL-safe test fixtures and awaited overseer audit writes.

- migrate eligible engine tests to shared PostgreSQL harnesses and restore their Vitest coverage
- harden mission and advisory reporting paths for async persistence and observable failures
- await the production planner-overseer audit callback and verify the start() wiring preserves persistence

Files changed:
 .../__tests__/mission-autopilot-end-to-end.test.ts |  27 ++--
 .../engine/src/__tests__/mission-autopilot.test.ts |   4 +-
 .../planner-overseer-intervention-wiring.test.ts   |  39 +++---
 .../engine/src/__tests__/project-engine.test.ts    | 138 ++++++++++++++++-----
 .../unlinked-missions-advisory-reporter.pg.test.ts |  51 ++++++++
 .../unlinked-missions-advisory-reporter.test.ts    |  20 ++-
 packages/engine/src/mission-execution-loop.ts      |  27 ++--
 packages/engine/src/project-engine.ts              |  41 +++---
 .../src/unlinked-missions-advisory-reporter.ts     |  23 ++--
 packages/engine/vitest.config.ts                   |   6 +-
 scripts/lib/test-quarantine.json                   |  27 +---
 11 files changed, 260 insertions(+), 143 deletions(-)

Fusion-Task-Id: FN-8252
Fusion-Task-Lineage: 4f86ce7e-11a2-4704-a5d1-00e0a8c1448e
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-17 17:28:36 -07:00
gsxdsm
62f121e0c9 FN-8247: stop session advisor with oversight
Stop now persists session-advisor disablement and reflects inherited advisor state in task details.

- Persist an explicit advisor-off override and clear its live runtime when stopping oversight.
- Resolve workflow-level advisor defaults for task-detail icons and toggle behavior.
- Cover stop cleanup and desktop/mobile oversight state transitions with regression tests.
- Document the combined stop contract and add a patch changeset.

Files changed:
 .changeset/fn-8247-session-advisor-stop-and-icon.md       |   7 ++
 docs/dashboard-guide.md                            |   6 +-
 packages/dashboard/app/components/TaskDetailModal.tsx   |  86 +++++++++++------
 packages/dashboard/app/components/__tests__/TaskDetailModal.oversight-controls.test.tsx    | 104 +++++++++++++++++++++
 packages/dashboard/app/components/__tests__/TaskDetailModal.oversight-mobile.test.tsx      |  62 ++++++++++++
 packages/engine/src/__tests__/project-engine-stop-overseer-session-advisor.test.ts |  62 ++++++++++++
 packages/engine/src/project-engine.ts              |  13 ++-
 7 files changed, 310 insertions(+), 30 deletions(-)

Fusion-Task-Id: FN-8247

Fusion-Task-Lineage: dcb67af7-9a36-4b25-bafa-84722fe158a0

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-17 16:20:10 -07:00
gsxdsm
5d2c3be6a0 FN-8221: clear inactive planner overseer state
Clear retained planner overseer state when effective oversight is disabled.

- Remove monitor, recovery, advisor, and dedup runtime for oversight-off tasks.
- Suppress stale oversight-off Eye badges in task cards.
- Cover cleanup and badge behavior with regression tests.
- Document the runtime snapshot invariant and add a patch changeset.

Files changed:
 .changeset/fn-8221-overseer-badge-oversight-off.md |   7 ++
 docs/architecture.md                               |   4 +
 packages/dashboard/app/components/TaskCard.tsx     |   9 +-
 .../app/components/__tests__/TaskCard.test.tsx     |  20 ++++
 .../__tests__/planner-overseer-off-cleanup.test.ts | 119 +++++++++++++++++++++
 packages/engine/src/project-engine.ts              |  12 +++
 6 files changed, 170 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8221

Fusion-Task-Lineage: 01c9d838-fbe4-4d34-8eb5-d735cf35e581

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-17 11:27:44 -07:00
gsxdsm
cae7847085 fix(FN-8004): retry ACP provider blips in auto-merge instead of parking failed (#2157)
## What happened

FN-8004's implementation work finished and passed review. The auto-merge
then failed with `Grok ACP turn failed: Internal error` — a ~20 second
provider blip — and the task was parked `status: "failed"` with 8 files
of complete, reviewed work stranded on its branch.

The park is the interesting part: `status: "failed"` is precisely what
tells recovery to stop. So a misclassification here isn't a missed
retry, it's **terminal**. Both recovery paths were disabled by the same
wrong verdict:

- `maybeRetryTransientMerge` (inline, 3 retries w/ backoff) — never
fired once (`mergeTransientRetryCount: 0`).
- `recoverTransientMergeFailures` (self-healing sweep, exists exactly to
rescue parked in-review tasks) — skipped it, gated on the same
classifier.

## Three defects fixed

**1. No AI-provider failure class existed.** The AI merge drives a real
LLM turn, but `classifyTransientMergeError` only modeled git/lease/spawn
faults. Adds `ai-provider-turn-failure`.

**2. ACP dropped the error detail.** `promptAcpSession` rethrew the SDK
error unchanged, discarding the JSON-RPC `code`/`data` — the only
evidence the fault was provider-side. ("Internal error" is just the
standard text for `-32603`.) It now preserves them, keeping the original
as `cause`:

```
Internal error (acp rpc code -32603, retryable)
```

Classification anchors on that envelope, **not** on the bare `"Internal
error"` — matching that unanchored would disguise genuine application
defects as retryable blips. Only provider-fault codes (`-32603`,
`-32000`..`-32003`) are retryable; caller-fault codes
(`-32600`..`-32602`) stay permanent, since retrying just repeats the
failing call.

**3. Sweep/inline asymmetry** (found while tracing; latent and
unreported). The inline gate accepted `isTransientError(msg) ||
classify(msg)`, but the sweep consulted **only** the classifier. So
`ECONNRESET` / `socket hang up` during a merge earned inline retries and
then went **invisible to the sweep** once parked — stranded forever. The
classifier now delegates to `isTransientError`, so both gates agree by
construction.

To keep that delegation from importing the detector's
`usage-limit-detector → logger` chain (the chain FN-5627 split the
classifier out to avoid, which would break
`notification-service.test.ts`'s partial `vi.mock`), the pure predicates
moved to the import-free leaf `transient-error-patterns.ts`, re-exported
from `transient-error-detector.ts`. All 13 exports preserved, verified
programmatically.

## Loosened budgets

Per request, so more self-heals. Both apply **only** to errors already
proven transient; the ceiling and
`merger:transient-failure-budget-exhausted` audit path remain.

| Budget | Before | After |
|---|---|---|
| `MAX_AUTO_MERGE_TRANSIENT_RETRIES` | 3 | 5 (backoff
5s/10s/20s/40s/80s) |
| `MAX_TRANSIENT_MERGE_RECOVERIES` | 2 | 5 |

The bump broke two suites that had hardcoded the old `3`. Rather than
swap in another magic number, both now derive the cap from the constant
so future tuning doesn't re-break them.

## Verification

- `pnpm test:gate` green · `pnpm lint` clean · engine + ACP typecheck
clean · `pnpm verify:fast` PASS (5/5)
- ACP plugin 230 tests green · Grok plugin 64 green · engine
transient/merge suites 136 green
- Regression tests assert the **invariant across every surface** (per
*Fix the Invariant, Not the Repro*), not just the reported Grok string:
both ACP runtime prefixes, all retryable/non-retryable rpc codes, both
SDK error shapes, network delegation, class-ordering, and negative cases
proving bare `"Internal error"` and real defects stay permanent.
- A test caught a genuine bug in my own code mid-review (nested-shape
message shadowing), now fixed.
- `notifier.test.ts > "awaiting approval"` fails — **confirmed
pre-existing on clean main**, unrelated.

## Note

FN-8004's own branch (`fusion/fn-8004`) is still unmerged and its work
looks complete. Once this lands, its merge should be retried separately.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:14:25 -07:00
gsxdsm
05151a25db feat: faster dashboard and serve startup (#2132)
## Summary

Speeds up **time-to-HTTP-ready** for `fn dashboard` and `fn serve` after
the PostgreSQL cutover without reintroducing the historical 3s
cwd-engine race that degraded webhooks.

- **Dashboard store share (serve parity):** inject the factory-booted
`TaskStore` as `externalTaskStore` so cwd `ensureEngine` does not open a
second pool; share only when store root matches project working
directory (multi-project safe).
- **Serve multi-project:** stop awaiting `startAll()` before listen;
await only the primary engine; background the rest + reconciliation.
- **Defer non-route-critical engine work:** ordered OAuth (refresh →
monitor), automation schedule syncs, and auto-merge **enqueue** after
the engine handle is returnable.
- **Critical-path merge status clear:** still clear stale
`merging`/`merging-pr` before ready so manual merge is not blocked after
crash.
- **Serve `--paused`:** apply `enginePaused` before
`ensureEngine`/`startAll` (dashboard ordering).
- **Stop safety:** generation counter so deferred tails cannot resume
after `stop()` clears `shuttingDown`.
- **Phase timing:** shared `phaseTime` helper, factory substep logs,
serve time-to-listen.

Plan: `docs/plans/2026-07-14-001-feat-faster-startup-plan.md`

## Test plan

- [x] `packages/engine` — `project-engine-manager.test.ts` (path-matched
external store)
- [x] `packages/engine` — `project-engine-deferred-startup.test.ts`
(status clear, OAuth order, stop generation)
- [x] `packages/cli` — `startup-phase.test.ts`
- [x] `packages/cli` — `serve.test.ts` (60 tests, including `--paused`)
- [ ] Local: warm `fn dashboard` / `fn serve` and compare `startup phase
*` / `time-to-listen` logs
- [ ] `pnpm smoke:boot` (real serve `/api/health` on ephemeral port)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Performance**
* Improved dashboard and serve startup times, including faster
time-to-listen and time-to-ready.
* Moved non-essential background initialization off the critical startup
path.
  * Parallelized dashboard service initialization where possible.

* **Reliability**
  * Improved multi-project startup handling and project selection.
  * Prevented cross-project task-store sharing.
  * Added safer shutdown behavior for partially completed startup.

* **Diagnostics**
* Added startup phase timing logs to help identify performance
bottlenecks.

* **Tests**
* Expanded coverage for deferred startup, shutdown, project isolation,
and startup timing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:01:08 -07:00
gsxdsm
836e53c6c0 FN-7975: exclude engine-paused wall-clock from task active timing
Reconcile active task segment anchors on full Global/Engine unpause so stopped-engine wall-clock does not inflate execution time, reusing the FN-7011 downtime path with a transition-captured heartbeat.

- Pass optional engineLastActiveAtOverride into reconcileActiveTimingForEngineDowntime so unpause callers freeze the stopped-window proof against racing scheduler heartbeats
- Await downtime reconciliation in resumeAfterUnpauseAndSweepInReview before resuming agentic work or sweeping in-review tasks
- Fold Global/Engine unpause into the unified pause-lifecycle listener (single reconcile when both clear together; no-op while either pause remains)
- Soft-fail reconcile errors so unpause resume still proceeds
- Add store and project-engine coverage for override, await-before-resume, dual-source clear, and fail-soft paths; document FN-7975 in AGENTS.md run-audit notes
- Add patch changeset for the operator-facing timing fix

Files changed:
 .changeset/fn-7975-engine-pause-active-timing.md   |   7 ++
 AGENTS.md                                          |   2 +-
 .../core/src/__tests__/store-active-timing.test.ts |  86 +++++++++++++
 packages/core/src/store.ts                         |  23 ++--
 .../project-engine-unpause-active-timing.test.ts   |  94 ++++++++++++++
 .../engine/src/__tests__/project-engine.test.ts    | 139 +++++++++++++++++++++
 packages/engine/src/project-engine.ts              |  64 +++++-----
 packages/engine/src/self-healing.ts                |   6 +-
 8 files changed, 378 insertions(+), 43 deletions(-)

Fusion-Task-Id: FN-7975

Fusion-Task-Lineage: 84a46e6f-92bf-452a-ab67-c25ba85cbffb

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 12:41:05 -07:00
gsxdsm
a67c2763af fix(merge-queue): serialize reclaim and status-aware silence policy
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.
2026-07-15 10:06:32 -07:00
gsxdsm
0eb46f2a89 fix(engine): reclaim wedged single-flight merge pump automatically
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.
2026-07-15 09:52:41 -07:00
gsxdsm
e9f14bf024 perf: speed up local pnpm build and cap stacked verifications (#2134)
## 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 -->
2026-07-15 08:44:11 -07:00
gsxdsm
ba1e82381e fix(FN-7952): cut runtime services over to PostgreSQL (#2109)
## 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 -->
2026-07-14 22:16:16 -07:00
gsxdsm
4f037679ad feat: planner overseer session advisor (OMP advisor parity) (#2082)
## Summary

Adds a **session advisor** to the planner overseer so Fusion can review
live executor transcripts the way [oh-my-pi’s
advisor](https://github.com/can1357/oh-my-pi/tree/main/packages/coding-agent/src/advisor)
does — without replacing the existing lifecycle supervisor (stage watch,
retry, merge confirmation, human-control withhold).

### What ships

- **Emission guard** (`OverseerEmissionGuard`) — content-free phrase
filter, session dedupe with severity-rank escalation, one accept per
advisor update
- **Session delta runtime** — queues agent-log deltas, drains through an
advisor agent, drops backlog after 3 failures
- **Session advisor service** — model gate, level matrix (`observe` /
`steer` / `autonomous`), human-control re-check at inject,
`[session-advisor]` steering comments
- **OVERSEER.md / WATCHDOG.md** discovery for project review priorities
- **AgentLogger `onEntriesFlushed`** + poll-backed agent-log cursor for
durable deltas
- Workflow settings: `plannerOverseerAdvisorProvider` +
`plannerOverseerAdvisorModelId` (both required; empty = soft-disabled
for cost safety)
- Docs + changeset

### What does not ship (deferred)

- Multi-advisor YAML roster, mutating advisor tools, reviewer/merger
shadowing, true tool-abort interrupt

### Plan

`docs/plans/2026-07-13-001-feat-overseer-advisor-parity-plan.md`

## Enablement

1. Set workflow **Session advisor model provider** + **Session advisor
model id**
2. Oversight level `observe` (log only), `steer`, or `autonomous`
(inject)
3. Optional: add `OVERSEER.md` or `WATCHDOG.md` in the project

## Test plan

- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/overseer-emission-guard.test.ts`
- [x] `pnpm --filter @fusion/engine exec vitest run` overseer-* unit
tests (21 tests)
- [x] Related planner-overseer / intervention regression tests
- [x] `@fusion/engine` + `@fusion/core` typecheck
- [ ] Manual: configure advisor model, run an executor task, confirm
`[session-advisor]` inject + timeline metadata when concern is raised

## Residual Review Findings

None from autofix pass (log-cursor ordering fix already committed).


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added an off-by-default “session advisor” that can review live
execution activity and provide severity-based guidance.
* Added project and per-task controls to enable it, including a default
enable switch and Quick Add / Task Detail toggles.
* Enhanced advisor prompting by discovering and incorporating
`OVERSEER.md`/`WATCHDOG.md` review files.
* **Documentation**
* Added architecture and settings documentation for the new
session-advisor parity behavior.
* **Bug Fixes**
* Improved fail-soft handling so advisor behavior won’t disrupt
execution.
  * Fixed concurrent PostgreSQL migration startup failures.
* **Tests**
* Added coverage for advice parsing, emission guarding, runtime
behavior, and watchdog discovery.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 20:27:35 -07:00
gsxdsm
945d629e3b fix(core): make SQLite cutover lossless and project-local
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.
2026-07-14 12:41:10 -07:00
gsxdsm
c15c78feeb feat: migrate storage from SQLite to PostgreSQL (#1793)
# 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>
2026-07-13 19:07:58 -07:00
gsxdsm
c745990aa2 FN-7879: deliver one-time Postgres-migration inbox notice on first 0.59 startup
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>
2026-07-12 17:02:33 -07:00
gsxdsm
eb377ba831 FN-7750: gate shared-branch-group auto-merge exemption on live groups
Fixes autoMerge=false being bypassed for engine-created branch-group member tasks whose branch group had already dissolved/finalized.

- Add isLiveSharedBranchGroupMemberIntegration(task, group) in @fusion/core, requiring the branch group's status be "open" before the shared-branch-member exemption bypasses the global/task autoMerge:false hold.
- Export the new helper from packages/core/src/index.ts and index.gate.ts.
- Thread the live-group check through packages/engine/src/project-engine.ts (allowInReviewMergeProcessing, enqueueEligibleInReviewTasks, merge-confirmed fast-path branch routing, and merge handoff paths).
- Add TaskExecutor.isLiveSharedBranchGroupMember helper in packages/engine/src/executor.ts and use it in retryable pre-merge remediation, no-op finalize, benign pause-abort classification, and merge-processing gates.
- Keep self-healing.ts's solo no-op finalize predicate on the pure branchContext-shape check (isSharedBranchGroupMemberIntegration) intentionally, so stale shared-group members stay excluded from solo finalize regardless of group liveness.
- Add regression tests covering the executor and project-engine auto-merge-hold behavior for stale/dissolved branch groups.
- Add a patch changeset documenting the fix.

Files changed:
 .../fn-7750-automerge-hold-stale-branch-group.md   |  7 ++
 packages/core/src/__tests__/task-merge.test.ts     | 42 +++++++++--
 packages/core/src/index.gate.ts                    |  1 +
 packages/core/src/index.ts                         |  1 +
 packages/core/src/task-merge.ts                    | 13 +++-
 ...cutor-live-branch-group-auto-merge-hold.test.ts | 85 ++++++++++++++++++++++
 .../engine/src/__tests__/project-engine.test.ts    | 37 +++++++++-
 packages/engine/src/executor.ts                    | 22 ++++--
 packages/engine/src/project-engine.ts              | 32 +++++---
 packages/engine/src/self-healing.ts                |  1 +
 10 files changed, 214 insertions(+), 27 deletions(-)

Fusion-Task-Id: FN-7750

Fusion-Task-Lineage: d61f8847-0b09-49b5-b66a-00018c8738bb

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-09 19:58:08 -07:00
gsxdsm
1fa4a69dde FN-7747: derive dashboard authStorage fallback from engine.getAuthStorage()
Fixes desktop provider API keys not persisting when a host wires an engine into createServer() but forgets to pass its own authStorage, which previously caused register-auth-routes.ts to throw "Authentication is not configured".

- Add ProjectEngine.getAuthStorage() exposing the OAuth subsystem's raw createFusionAuthStorage() instance
- In createServer(), derive options.authStorage from engine.getAuthStorage() when not explicitly provided (mirrors existing engine-derivation pattern for onMerge/automationStore/etc.); explicit authStorage still overrides
- Add regression tests covering the fallback-derivation and explicit-override behavior
- Add changeset (patch) documenting the fix for @runfusion/fusion

Files changed:
 .changeset/fn-7747-derive-authstorage-from-engine.md |   7 ++
 packages/dashboard/src/__tests__/server.test.ts      | 119 +++++++++++++++++++++
 packages/dashboard/src/server.ts                     |  28 ++++-
 packages/engine/src/project-engine.ts                |  25 +++++
 4 files changed, 178 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7747

Fusion-Task-Lineage: f8e72b15-d084-4e8d-89db-47453d57b41b

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-09 19:58:07 -07:00
gsxdsm
2ff8e2e13e FN-7743: detect and recover stalled in-progress executor tasks in overseer
Fix the planner overseer's executor-stage stall detection so hung in-progress tasks get unstuck instead of being reported as progressing forever.

- Add configurable stuck-detection: the executor-stage overseer observation now emits signal: "stuck" once an in-progress task has been inactive past a new plannerOverseerExecutorStuckAfterMs threshold, feeding the existing decidePlannerRecovery -> bounded inject_guidance recovery path.
- Register the new plannerOverseerExecutorStuckAfterMs setting in builtin-workflow-settings.ts and export it via core index.ts/index.gate.ts.
- Preserve human-control withholds (user-paused / approval-blocked / autoMerge-off) taking precedence over stuck detection.
- Add/extend tests covering planner-overseer, planner-recovery-controller, planner-recovery, and builtin-workflow-settings-triage.
- Document the new setting in docs/architecture.md and docs/settings-reference.md.
- Add changeset fn-7743-overseer-executor-stall.md (patch).

Files changed:
 .changeset/fn-7743-overseer-executor-stall.md      |   7 +
 docs/architecture.md                               |  31 +++++
 docs/settings-reference.md                         |   3 +-
 .../builtin-workflow-settings-triage.test.ts       |  22 ++++
 .../core/src/__tests__/planner-recovery.test.ts    |  12 ++
 packages/core/src/builtin-workflow-settings.ts     |  26 ++++
 packages/core/src/index.gate.ts                    |   1 +
 packages/core/src/index.ts                         |   1 +
 .../engine/src/__tests__/planner-overseer.test.ts  | 146 +++++++++++++++++++++
 .../__tests__/planner-recovery-controller.test.ts  |  52 +++++++-
 packages/engine/src/planner-overseer.ts            |  88 ++++++++++++-
 packages/engine/src/project-engine.ts              |  11 +-
 12 files changed, 391 insertions(+), 9 deletions(-)

Fusion-Task-Id: FN-7743

Fusion-Task-Lineage: 791852a2-5b77-48de-955a-1b3916616dfa

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-09 19:58:07 -07:00
gsxdsm
67cc02750c FN-7692: fix misleading merger confirmation copy under active auto-merge
Correct the planner-oversight confirmation messaging so it no longer claims a hard block when the active auto-merge policy will actually advance the merge/pull-request stage unattended.

- decidePlannerRecovery accepts an additive, messaging-only `autoMergeWillProceed` flag and picks accurate reason wording (advisory vs. genuine human-approval block vs. neutral/unknown) for merger/pull-request await_confirmation decisions
- PlannerRecoveryController.tick threads `allowsAutoMergeProcessing(task, settings)` into decidePlannerRecovery as `autoMergeWillProceed`
- project-engine's requestConfirmation steering comment prefix changed from "confirmation required" to neutral "merge checkpoint" so it doesn't contradict the now-accurate reason text
- added regression tests in planner-recovery.test.ts and planner-overseer-intervention-wiring.test.ts
- added changeset and doc note

Files changed:
 .changeset/fn-7692-merger-confirmation-copy.md     |  7 +++
 docs/architecture.md                               | 10 +++-
 packages/core/src/__tests__/planner-recovery.test.ts    | 66 ++++++++++++++++++++++
 packages/core/src/planner-recovery.ts              | 36 +++++++++++-
 packages/engine/src/__tests__/planner-overseer-intervention-wiring.test.ts | 37 ++++++++++++
 packages/engine/src/planner-recovery-controller.ts | 14 ++++-
 packages/engine/src/project-engine.ts               | 11 +++-
 7 files changed, 176 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-7692

Fusion-Task-Lineage: 187684b8-1d24-425d-85d4-627587469908

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 15:09:26 -07:00
gsxdsm
a8c018f7d4 fix: prevent false "OAuth token expired" push on startup
Start OAuthRefreshScheduler before the refresh-blind OAuthExpiryMonitor so a
stale-but-refreshable access token is renewed before the monitor's first
awaited check() reads `expires`. Previously the monitor fired a false
"OAuth token expired" ntfy push on startup, moments before the refresher
silently renewed the token. Ordering locked by an invocationCallOrder
assertion in project-engine.test.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 09:14:14 -07:00
gsxdsm
60081fb1f4 FN-7610: route workspace-mode tasks around PR-merge auto-merge strategy
Fixes workspace-mode (workspaceWorktrees) tasks failing auto-merge under mergeStrategy=pull-request, where processPullRequestMergeTask threw "could not determine repository" because the workspace root is a container of independent git sub-repos, not itself a git repo.

- Hoist an isWorkspaceTask check in ProjectEngine's merge dispatch (project-engine.ts) before the mergeStrategy branch, so workspace tasks always fall through to the existing direct/landWorkspaceTask path regardless of configured mergeStrategy.
- Add processPullRequestMergeTask and syncGroupPrCallback defense-in-depth guards (task-lifecycle.ts) that throw the new named WorkspaceTaskMergeError if a workspace task ever reaches the PR-merge path.
- Add engine tests covering multi-repo, single-repo, and zero-commit no-op workspace tasks under mergeStrategy=pull-request, plus a non-regression test for the legacy single-worktree PR path.
- Add CLI tests asserting the new guards throw WorkspaceTaskMergeError.
- Add a patch changeset describing the fix.

Files changed:
 .changeset/fn-7610-workspace-pr-merge-routing.md   |   7 ++
 .../src/commands/__tests__/task-lifecycle.test.ts  |  56 +++++++++
 packages/cli/src/commands/task-lifecycle.ts        |  33 ++++-
 .../engine/src/__tests__/project-engine.test.ts    | 140 +++++++++++++++++++++
 packages/engine/src/project-engine.ts              |  18 ++-
 5 files changed, 252 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7610
Fusion-Task-Lineage: 31768b77-d9a9-4a79-a055-bbc6b228a1c4
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-06 19:03:06 -07:00
Phil Larson
8d6c92ac2e fix: preserve Hermes runtime chat session state 2026-07-05 17:50:13 -07:00
gsxdsm
ce9df297eb FN-7574: fix OAuth token expiry detection and add proactive auto-refresh
Unifies OAuth expiry detection so expired Claude subscription logins correctly show as disconnected with a re-login prompt, and adds a proactive engine-side scheduler that refreshes tokens before they expire.

- Share expiry-detection logic between OAuthExpiryMonitor and the /api/auth/status route so both agree on when a token is expired.
- Add engine-side oauth-refresh-scheduler that proactively refreshes OAuth tokens ahead of expiry, wired into project-engine (guarded by skipNotifier).
- Extend auth-storage with the helpers needed for expiry checks/refresh.
- Add tests covering routes-auth status detection, auth-storage expiry helpers, and the new refresh scheduler.
- Document the new behavior in dashboard-guide.md and settings-reference.md.
- Add changeset for the user-facing fix.

Files changed:
 .../fn-7574-oauth-expiry-detection-refresh.md      |   7 +
 docs/dashboard-guide.md                            |   4 +
 docs/settings-reference.md                         |   4 +
 .../dashboard/src/__tests__/routes-auth.test.ts    |  76 +++++++++++
 .../dashboard/src/routes/register-auth-routes.ts   |  25 +++-
 packages/engine/src/__tests__/auth-storage.test.ts |  60 +++++++++
 packages/engine/src/auth-storage.ts                |  14 +-
 .../__tests__/oauth-refresh-scheduler.test.ts      | 141 ++++++++++++++++++++
 packages/engine/src/notification/index.ts          |   3 +
 .../src/notification/oauth-refresh-scheduler.ts    | 143 +++++++++++++++++++++
 packages/engine/src/project-engine.ts              |  14 +-
 11 files changed, 488 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-7574

Fusion-Task-Lineage: 59996eac-c070-4992-9727-d066c6934b69

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-05 11:31:46 -07:00
gsxdsm
3d58260e1a FN-7551: wire overseer decision points to engine emitOverseer* façade
Wires PlannerOverseerMonitor/PlannerRecoveryController decision points (human-control withholds, confirmation requests/resolutions, and related overseer stages) to the FN-7520 emitOverseer* façade using the real TaskStore, so the planner-oversight intervention timeline now populates from real engine activity instead of staying empty.

- Add onConfirmationResolved handler to PlannerRecoveryController, invoked (best-effort, audit-only) from resolveConfirmation for both approved and denied outcomes.
- Wire project-engine.ts to call emitOverseerObservation/emitOverseerEscalation/emitOverseerConfirmation at the real engine decision points, deduped per (task, stage[, signal]).
- Add planner-overseer-intervention-wiring.test.ts covering the new wiring end-to-end.
- Update docs/architecture.md to reflect the wiring.
- Add changeset fn-7551-overseer-timeline-wiring.md (patch).

Files changed:
 .changeset/fn-7551-overseer-timeline-wiring.md     |   7 +
 docs/architecture.md                               |   2 +-
 .../planner-overseer-intervention-wiring.test.ts   | 319 +++++++++++++++++++++
 packages/engine/src/planner-recovery-controller.ts |  36 +++
 packages/engine/src/project-engine.ts              | 248 +++++++++++++++-
 5 files changed, 607 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-7551

Fusion-Task-Lineage: 8bcd103e-8797-4ef5-9b68-bd2daec8d26b

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-04 21:28:18 -07:00
gsxdsm
6498d028f2 FN-7517: add task detail oversight quick-controls (level change, manual nudge, stop, explain-current-action)
Adds task detail modal controls that let an operator quickly change a task's
oversight level, nudge the planner with a manual instruction, stop oversight
entirely, and request an explanation of the overseer's current action,
backed by new dashboard API routes and engine/core plumbing.

- Add oversight quick-controls UI (level change, manual nudge, stop
  oversight, explain-current-action) to TaskDetailModal with supporting
  styles in TaskDetailModal.css and TaskCard.css
- Add dashboard legacy API + task-workflow routes to handle the new
  oversight actions (register-task-workflow-routes.ts, api/legacy.ts)
- Extend planner-overseer-state and planner-overseer-runtime-snapshot to
  track/report manual nudge and stop-oversight state
- Extend PlannerRecoveryController and project-engine to apply manual
  oversight actions (level change, nudge, stop, explain) end-to-end
- Add tests: TaskDetailModal.oversight-controls.test.tsx,
  tasks-overseer-controls.test.ts,
  planner-recovery-controller-manual-action.test.ts, plus updates to
  planner-overseer-runtime-snapshot.test.ts and test-helpers
- Update docs/dashboard-guide.md and docs/settings-reference.md

Files changed:
 docs/dashboard-guide.md                            |   2 +
 docs/settings-reference.md                         |   2 +-
 packages/core/src/planner-overseer-state.ts        |  18 +
 packages/dashboard/app/api/legacy.ts               |  33 ++
 packages/dashboard/app/components/TaskCard.css     |  13 +
 packages/dashboard/app/components/TaskDetailModal.css   | 122 +++++++
 packages/dashboard/app/components/TaskDetailModal.tsx   | 374 ++++++++++++++++++++-
 packages/dashboard/app/components/__tests__/TaskDetailModal.oversight-controls.test.tsx | 290 ++++++++++++++++
 packages/dashboard/app/components/__tests__/TaskDetailModal.test-helpers.ts      |  11 +
 packages/dashboard/src/routes/__tests__/tasks-overseer-controls.test.ts      | 191 +++++++++++
 packages/dashboard/src/routes/register-task-workflow-routes.ts    |  68 ++++
 packages/engine/src/__tests__/planner-overseer-runtime-snapshot.test.ts      |  24 +-
 packages/engine/src/__tests__/planner-recovery-controller-manual-action.test.ts |  84 +++++
 packages/engine/src/planner-overseer-runtime-snapshot.ts       |  11 +
 packages/engine/src/planner-recovery-controller.ts |  40 +++
 packages/engine/src/project-engine.ts              | 102 ++++++
 16 files changed, 1380 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-7517

Fusion-Task-Lineage: eded7ff5-d126-429d-acbb-9f4bfff5ae2a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-04 21:28:16 -07:00
gsxdsm
726cbf89cc FN-7531: expose planner overseer state to task cards
Expose transient planner overseer runtime snapshots to board task payloads and cards.

- Add core planner overseer state types and deterministic state derivation.
- Assemble read-only engine runtime snapshots from overseer observations and recovery registries.
- Enrich GET /api/tasks with best-effort planner overseer state and render non-idle TaskCard badges.
- Cover state derivation, API enrichment, runtime snapshot assembly, and card rendering with tests.

Files changed:
 .../fn-7531-planner-overseer-state-exposure.md     |   7 ++
 docs/architecture.md                               |  36 +++++++
 .../src/__tests__/planner-overseer-state.test.ts   |  85 +++++++++++++++
 packages/core/src/index.ts                         |   7 ++
 packages/core/src/planner-overseer-state.ts        |  78 ++++++++++++++
 packages/core/src/types.ts                         |  12 +++
 packages/dashboard/app/components/TaskCard.tsx     |  27 ++++-
 .../app/components/__tests__/TaskCard.test.tsx     |  29 ++++++
 .../__tests__/tasks-planner-overseer-state.test.ts | 114 +++++++++++++++++++++
 .../src/routes/register-task-workflow-routes.ts    |  23 ++++-
 .../planner-overseer-runtime-snapshot.test.ts      | 104 +++++++++++++++++++
 packages/engine/src/index.ts                       |   8 ++
 .../src/planner-overseer-runtime-snapshot.ts       |  67 ++++++++++++
 packages/engine/src/project-engine.ts              |  17 +++
 14 files changed, 612 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7531

Fusion-Task-Lineage: b7659ed2-bf33-4312-a5ab-818ad37049b9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-04 21:28:15 -07:00
gsxdsm
79ab367547 FN-7514: withhold overseer actions under human control
Add a human-control guard so planner overseer recovery stays inert for paused or human-review tasks.

- Add a pure overseer human-control policy that treats explicit user pauses and autoMerge:false / human-review tasks as full withhold states.
- Thread settings through planner recovery ticks, skip action classification and pending confirmations when withheld, and emit deduped no-action run-audit events.
- Wire ProjectEngine audit recording and document the new guard, run-audit event, exports, and release note.

Files changed:
 .changeset/fn-7514-overseer-human-control-guard.md |   7 +
 AGENTS.md                                          |   1 +
 docs/architecture.md                               |  39 +++++
 .../overseer-human-control-policy.test.ts          |  86 +++++++++++
 ...anner-recovery-controller-human-control.test.ts | 170 +++++++++++++++++++++
 packages/engine/src/index.ts                       |   7 +
 .../engine/src/overseer-human-control-policy.ts    |  88 +++++++++++
 packages/engine/src/planner-recovery-controller.ts | 107 ++++++++++++-
 packages/engine/src/project-engine.ts              |  44 +++++-
 packages/engine/src/run-audit.ts                   |  15 +-
 10 files changed, 558 insertions(+), 6 deletions(-)

Fusion-Task-Id: FN-7514

Fusion-Task-Lineage: d4d3bd04-3f8e-4a05-9636-f177e050390b

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-04 14:11:25 -07:00
gsxdsm
2cc84b5177 FN-7513: require planner confirmation for risky side effects
Require explicit approval before planner recovery runs merge, PR, destructive, or external-service actions.

- Add pure planner side-effect classification and confirmation request modeling in core.
- Route merge/PR recovery decisions to await confirmation instead of autonomous dispatch.
- Persist pending confirmation requests and only execute approved controller actions.
- Cover confirmation gating with core and engine regression tests and document the policy.

Files changed:
 .changeset/fn-7513-planner-confirmation-gate.md    |   7 +
 docs/architecture.md                               |  85 ++++++++-
 docs/settings-reference.md                         |   2 +-
 .../src/__tests__/planner-confirmation.test.ts     | 125 +++++++++++++
 .../core/src/__tests__/planner-recovery.test.ts    |  14 +-
 packages/core/src/index.ts                         |   7 +
 packages/core/src/planner-confirmation.ts          | 141 ++++++++++++++
 packages/core/src/planner-recovery.ts              | 103 ++++++++---
 ...lanner-recovery-controller-confirmation.test.ts | 205 +++++++++++++++++++++
 packages/engine/src/index.ts                       |   5 +
 packages/engine/src/planner-recovery-controller.ts | 204 +++++++++++++++++++-
 packages/engine/src/project-engine.ts              |  44 +++++
 12 files changed, 913 insertions(+), 29 deletions(-)

Fusion-Task-Id: FN-7513

Fusion-Task-Lineage: 1e3c6640-8a4f-41f6-89dd-41eb9b675b2b

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-04 13:51:09 -07:00
gsxdsm
81f2053921 FN-7512: add bounded planner recovery
Adds bounded autonomous planner recovery decisions and dispatch so overseer observations can safely nudge stuck planning stages.

- Add pure core recovery policy with per-stage attempt limits and no-op fallbacks for disallowed or exhausted cases.
- Add engine controller wiring to inject guidance, retry steps, request targeted fixes, and emit recovery audit events.
- Register the planner recovery controller in project engine lifecycle and document the autonomous recovery behavior.
- Cover core decisions and controller dispatch with targeted tests, plus a patch changeset for the published CLI package.

Files changed:
 .changeset/fn-7512-planner-bounded-recovery.md     |   7 +
 docs/architecture.md                               |  62 ++++++
 .../core/src/__tests__/planner-recovery.test.ts    | 116 +++++++++++
 packages/core/src/index.ts                         |  12 ++
 packages/core/src/planner-recovery.ts              | 222 +++++++++++++++++++++
 .../__tests__/planner-recovery-controller.test.ts  | 163 +++++++++++++++
 packages/engine/src/index.ts                       |  20 ++
 packages/engine/src/planner-recovery-controller.ts | 195 ++++++++++++++++++
 packages/engine/src/project-engine.ts              |  66 ++++++
 9 files changed, 863 insertions(+)

Fusion-Task-Id: FN-7512

Fusion-Task-Lineage: aad3849d-090e-497d-ae5c-34ec7ca96c3d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-04 13:19:02 -07:00
gsxdsm
12a6d1bc6a FN-7511: add planner overseer stage monitoring
Add records-only planner overseer monitoring across in-flight task lifecycle stages.

- Add a PlannerOverseerMonitor with normalized observations and deterministic watched-stage resolution.
- Wire ProjectEngine to poll in-progress and in-review tasks, gated by effective planner oversight level.
- Document the monitoring seam and add focused coverage plus a release changeset.

Files changed:
 .changeset/fn-7511-planner-overseer-monitoring.md  |   7 +
 docs/architecture.md                               |  26 ++
 docs/workflow-steps.md                             |   2 +
 .../engine/src/__tests__/planner-overseer.test.ts  | 294 ++++++++++++++++++
 packages/engine/src/index.ts                       |  12 +
 packages/engine/src/planner-overseer.ts            | 338 +++++++++++++++++++++
 packages/engine/src/project-engine.ts              |  93 +++++-
 7 files changed, 771 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7511

Fusion-Task-Lineage: 81b616cf-47e9-4769-b02d-fc7ebd3fcb2f

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-04 12:55:25 -07:00
gsxdsm
123639c89f fix(FN-7231): require current workflow merge proof 2026-06-29 09:28:02 -07:00
gsxdsm
1f365163a4 fix(FN-7225): clear stale pause state on workflow retry 2026-06-29 01:05:44 -07:00
gsxdsm
d7a02c4d5b FN-7151: free scoped project concurrency slots
Free stopped or paused project engines' leaked slots without disturbing other projects.

- Add scoped semaphore accounting around the shared global agent pool.
- Return residual per-project slots after runtime stop drains aborted agents.
- Keep idle leak reconciliation scoped to the owning project so active projects retain their slots.
- Cover scoped release, reconciliation, project pause, and in-process runtime behavior with tests.

Files changed:
 .../fn-7151-free-concurrency-slots-on-stop.md      |   7 ++
 docs/architecture.md                               |   2 +-
 packages/engine/src/__tests__/concurrency.test.ts  | 126 +++++++++++++++++++++
 .../src/__tests__/project-engine-manager.test.ts   |  60 ++++++++++
 packages/engine/src/concurrency.ts                 | 123 +++++++++++++++++++-
 packages/engine/src/project-engine.ts              |   2 +-
 .../runtimes/__tests__/in-process-runtime.test.ts  |  51 +++++++++
 packages/engine/src/runtimes/in-process-runtime.ts |  22 +++-
 8 files changed, 385 insertions(+), 8 deletions(-)

Fusion-Task-Id: FN-7151

Fusion-Task-Lineage: e59e5e0c-67e8-4f80-a6b9-1ea4febaeb97

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-06-27 20:55:59 -07:00
gsxdsm
ad3149093a FN-7077: inject configured MCP servers across agent surfaces
Configured MCP servers now follow every agent-work lane that launches model-backed tools.

- Thread MCP server forwarding through chat, task execution, tools, PR response, cron, memory, research, and planning surfaces.
- Add regression coverage for dashboard and engine lanes that previously omitted MCP server configuration.
- Document all covered MCP surfaces and add a published package changeset.

Files changed:
 .changeset/fn-7077-mcp-all-surfaces.md             |   7 +
 docs/mcp.md                                        |   3 +
 docs/settings-reference.md                         |   2 +-
 packages/cli/src/commands/__tests__/serve.test.ts  |   3 +-
 packages/core/src/memory-compaction.ts             |   4 +
 .../src/__tests__/mcp-lane-forwarding.test.ts      |  62 +++++++++
 packages/dashboard/src/agent-generation.ts         |   4 +
 packages/dashboard/src/ai-refine.ts                |   8 ++
 .../dashboard/src/milestone-slice-interview.ts     |  11 +-
 packages/dashboard/src/mission-interview.ts        |   9 +-
 packages/dashboard/src/pr-conflict-resolver.ts     |  12 +-
 packages/dashboard/src/routes.ts                   |  10 ++
 packages/dashboard/src/subtask-breakdown.ts        |   8 ++
 packages/engine/src/__tests__/cron-runner.test.ts  |  61 +++++++++
 .../src/__tests__/mcp-lane-forwarding.test.ts      |   3 +-
 .../__tests__/mcp-pr-response-forwarding.test.ts   | 110 ++++++++++++++++
 .../src/__tests__/mcp-surface-coverage.test.ts     | 141 +++++++++++++++++++++
 packages/engine/src/agent-heartbeat.ts             |  18 +++
 packages/engine/src/cli-agent-ask.ts               |   4 +
 packages/engine/src/cron-runner.ts                 |   9 +-
 packages/engine/src/pr-nodes.ts                    |   2 +-
 packages/engine/src/pr-response-run-ops.ts         |  10 +-
 packages/engine/src/project-engine.ts              |   2 +-
 .../research/providers/llm-synthesis-provider.ts   |   4 +
 .../src/research/providers/web-search-provider.ts  |   4 +
 25 files changed, 497 insertions(+), 14 deletions(-)

Fusion-Task-Id: FN-7077
Fusion-Task-Lineage: 00d36357-c3d8-4954-b647-d3aea24a967b
2026-06-26 16:14:44 -07:00
gsxdsm
59d5e81a95 Merge remote-tracking branch 'origin/main' into latest4-1718
# Conflicts:
#	packages/engine/src/__tests__/merge-error-recovery.test.ts
#	packages/engine/src/merger-ai.ts
2026-06-23 20:58:31 -07:00
gsxdsm
9822fbd6a8 Merge remote-tracking branch 'origin/main' into conflict-resolution-1718
# Conflicts:
#	packages/engine/src/__tests__/executor-recovery.test.ts
#	packages/engine/src/agent-tools.ts
#	packages/engine/src/executor.ts
#	packages/engine/src/merger-ai.ts
#	packages/engine/src/project-engine.ts
#	packages/engine/src/run-audit.ts
#	packages/engine/src/self-healing.ts
#	packages/engine/src/worktree-acquisition.ts
2026-06-23 16:23:28 -07:00
gsxdsm
0dea25061d Merge remote-tracking branch 'origin/main' into conflict-resolution-1717
# Conflicts:
#	packages/engine/src/__tests__/executor-recovery.test.ts
#	packages/engine/src/agent-tools.ts
#	packages/engine/src/executor.ts
#	packages/engine/src/merger-ai.ts
#	packages/engine/src/project-engine.ts
#	packages/engine/src/worktree-acquisition.ts
2026-06-23 16:12:10 -07:00
gsxdsm
9218613552 FN-6949: fix auto-merge lifecycle finalization
Make auto-merge completion idempotent when landed tasks are left outside done.

- Add shared auto-merge finalization logic that refreshes task rows, clears stale queue/blocker state, and recovery-rehomes proven landed tasks to done.
- Route direct AI merge, merge-confirmed fast paths, and self-healing recovery through the shared finalizer with run-audit events for reconciled or blocked column mismatches.
- Extend lifecycle and recovery tests to cover todo/in-review mismatches, finalization failures, stale blockers, and cleanup idempotency.
- Add the required patch changeset for the published Fusion package.

Files changed:
 .changeset/fn-6949-auto-merge-lifecycle.md         |   5 +
 .../src/__tests__/merge-error-recovery.test.ts     |  50 ++++-
 packages/engine/src/__tests__/merger-ai.test.ts    |   8 +-
 .../src/__tests__/merger-merge-lifecycle.test.ts   | 155 +++++++++++++++
 .../ai-merge-cleanup-enoent-idempotent.test.ts     |   2 +-
 packages/engine/src/__tests__/self-healing.test.ts | 194 +++++++++++--------
 packages/engine/src/auto-merge-finalization.ts     | 208 +++++++++++++++++++++
 packages/engine/src/merger-ai.ts                   |  47 +++--
 packages/engine/src/project-engine.ts              |  87 +++++----
 packages/engine/src/run-audit.ts                   |   4 +
 packages/engine/src/self-healing.ts                |  92 ++++-----
 11 files changed, 663 insertions(+), 189 deletions(-)

Fusion-Task-Id: FN-6949

Fusion-Task-Lineage: db784ec0-68e9-4a37-9728-daa6f133e294
2026-06-22 23:18:28 -07:00
gsxdsm
3a71237624 fix(review): address PR #1717 Phase C merge-loop review feedback
- merger-ai: resolve+persist concrete landedSha when a sub-repo is recognized
  already-landed via the Fusion-Task-Id trailer fallback, so finalize no longer
  drops it and mis-finalizes a fully-landed workspace task as a no-op
- project-engine: manual-merge land-lease busy errors reject the resolver without
  burning mergeRetries; clear stale busy-reenqueue counter on real partial land;
  persist retry count before arming the backoff timer (fail closed on write error)
- cli/dashboard + task: use shared isWorkspaceTask predicate instead of inlining
- base-commit-capture: POSIX single-quote shell escaping for integration ref
- git-repository: validate workspace.json repos elements are strings
- merger-ai: drop dead store param from landOneRepo
- tests: assert the 60s backoff cap across cycles; exercise the real runAiMerge
  merge door; fix non-git-root assertion; re-export real workspace error classes
  in the merger-ai mock (fixes 24 pre-existing instanceof-undefined failures);
  remove generic fake-timer smoke test now covered by the live engine assertion

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 03:04:21 -07:00
gsxdsm
8e70d69601 fix(review): Phase D self-healing hardening — finalize-site audit, lease/TOCTOU safety
4-persona review of the Phase-D workspace self-healing. The headline: the P0
single-commit-finalize guard had to be applied across ALL surfaces, not just the
one reconciler U1 patched (FN-5893).

Finalize-site audit (A): gated every site where a workspace task could be
single-commit-finalized on one repo's commit — recoverStuckMergeDeadlocks (the
twin of the U1-patched reconciler, reachable via blocked-dependents),
recoverOrphanOnlyScopeViolations, recoverAlreadyMergedReviewTasks,
recoverBranchMisboundInReviewTasks (workspace tasks carry task.branch so the
Boolean(branch) filter didn't exclude them), plus a defensive filter on
finalizeNoOpReviewTasks. recoverMergedReviewTasks confirmed safe (mergeConfirmed
gate). Each is an isWorkspaceTask early-skip; single-repo behavior unchanged.

Reliability/concurrency:
- The partial-land reconciler now captures enqueueMerge's boolean and bounds
  re-enqueues (mergeStarvationDrops → fail after N) instead of looping silently
  forever on a full queue.
- The phantom-lease reclaim only acts on a terminal owner (null/done/failed) — it
  no longer reclaims the lease of an in-progress executing task that registered it
  early (shared isWorkspaceOwnerLive predicate).
- A new isMergePending(taskId) = mergeActive ∪ mergeQueue seam (exposed from
  ProjectEngine, wired through the runtime) guards both reconcilers against the
  merge-queue dispatch window — a task dequeued-but-not-yet-merging is no longer
  re-enqueued (which, since a same-task land lease isn't contention, could have
  caused a concurrent double-squash).
- FORK-A: a repo whose branch is gone and which isn't landed is parked, not
  re-enqueued forever. Orphan-worktree removal failures log.warn + bound.
  recoverDoneTaskMergeMetadata skips workspace tasks.

Maintainability: dissolved the self-healing↔merger-ai import cycle by moving
isRepoLanded into a dependency-free workspace-land-predicate.ts; removed a
redundant cast.

Gate green: build, typecheck, lint, test:gate (649+58); self-healing + e2e +
project-engine + merger 724.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 02:19:17 -07:00
gsxdsm
627bdcfb0a fix(review): Phase C merge-loop hardening — double-land, lease clobber, retry storm
5-persona review of the Phase-C per-repo merge loop. No P0; the no-push invariant
and retry/park accounting verified clean. Fixed:

Land mechanics (merger-ai.ts / active-session-registry.ts):
- persistRepoLandedSha no longer swallows the DB write: a failed landedSha write
  after the ref advanced now escalates to WorkspacePartialLandError so the engine
  parks/retries instead of silently re-landing (duplicate squash). isRepoLanded
  gains a landedSha-independent fallback — it scans the integration ref for this
  task's Fusion-Task-Id trailer (a squash commit is NOT a branch descendant, so a
  branch-ancestor check is provably wrong), so an actually-landed repo is skipped
  on retry.
- The land lease is now taskId-aware across kinds: any foreign-task holder on a
  sub-repo path is contention (a merging task can't run over an executing task's
  acquire lease), and registerPath throws ActiveSessionPathHeldByForeignTaskError
  instead of silently clobbering a different task's entry.
- The per-repo loop is wrapped in try/finally(setStatus(null)) so the busy/partial
  throws can't leave the task stuck 'merging'. WorkspacePartialLandError is a real
  exported class (not a .name-mutated Error). finalizeWorkspaceTask re-reads fresh
  and no longer swallows the mergeDetails write (TOCTOU). isRepoLanded exported for
  Phase D.

Dispatch + doors (project-engine.ts / dashboard.ts / task.ts / @fusion/core):
- getTask-null in the partial-land catch fails closed (park) instead of defaulting
  retries to 0 and scheduling an indefinite retry storm.
- The merge-confirmed reachability fast-path skips workspace tasks (its
  representative commitSha is a sub-repo squash sha, unreachable in the root cwd —
  it was demoting fully-merged tasks); they're verified by per-repo landedSha.
- The CLI/dashboard merge doors now return merged:true on full land (were hardcoded
  merged:false). WorkspaceRepoLandBusyError re-enqueues with backoff WITHOUT burning
  the mergeRetries quota (bounded busy counter) so contention can't park a healthy
  task. Backoff capped at 60s. shouldRetryWorkspacePartialLand folded into
  shouldRetryAutoMergeConflict. Catch switched to instanceof. New canonical
  isWorkspaceTask predicate in @fusion/core.

Gate green: build, typecheck, lint, test:gate (649+58); workspace-merger + oracle
+ project-engine 174.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:11:52 -07:00
gsxdsm
64e87f9a12 feat(workspace): Phase C U3 — per-repo land lease (serialize same-sub-repo lands)
landWorkspaceTask now holds a per-repo land lease around each landOneRepo call:
a new activeSessionRegistry kind "workspace-repo-land" keyed on the sub-repo
absolute path, registered synchronously before the per-repo try and released in
a finally (on success and failure, only yanking our own taskId+ownerKey entry —
never a foreign/different-kind entry). Two workspace tasks landing the same
sub-repo serialize; the loser throws the retryable WorkspaceRepoLandBusyError,
which reuses the U2 partial-land retry/park machinery (consume a mergeRetry,
backoff re-enqueue up to MAX skipping landed repos, then operator-park). Disjoint
sub-repos never falsely serialize.

The lease is for serialization / clean-room-collision avoidance, not ref
correctness — advanceIntegrationBranchRef's CAS already makes interleaved
update-ref safe. Distinct from the execution-phase "workspace-repo-acquire" lease
(different kind, different lifecycle phase, each ignores the other's entry).

3 new tests (serialize, independence, release-on-failure); oracle (56) + U1/U2
(idempotency) stay green. Gate: build, typecheck, lint, test:gate (649+58).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 23:56:41 -07:00
gsxdsm
7544346320 feat(workspace): Phase C U2 — per-repo landed predicate, finalize-once, auto-retry-then-park
landWorkspaceTask now tracks per-repo landing and finalizes the task exactly once.
After a repo lands, its advanced integration tip is persisted as
workspaceWorktrees[repo].landedSha (fresh-read merge, siblings untouched). Before
landing, isRepoLanded skips a repo iff its landedSha is present AND an ancestor of
(or equal to) its local integration ref — so a retry after a partial land never
re-advances an already-landed ref. finalizeWorkspaceTask runs only when every
acquired repo is landed: it builds an aggregate MergeResult (representative
commitSha + a workspaceLandedShas map in MergeDetails) and calls the existing
task-global finalizeTask once, satisfying the task:merged consumer. No premature
done on the first repo.

Partial lands surface as WorkspacePartialLandError; the engine consumes a
mergeRetry and re-enqueues landWorkspaceTask (skipping landed repos) with the
existing conflict-retry backoff up to MAX, then operator-parks (status:failed) —
mirroring shouldRetryAutoMergeConflict (new exported shouldRetryWorkspacePartialLand
seam). The defense-in-depth WorkspaceTaskMergeError still hard-fails without
burning retries; manual merges fall through to rejectMergeResolvers.

types: workspaceWorktrees entry gains landedSha?; MergeDetails gains
workspaceLandedShas?. 6 new idempotency/predicate/finalize-once/retry-park tests;
oracle (52) + U1 (5) stay green. Gate: build, typecheck, lint, test:gate (649+58).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 23:46:44 -07:00
gsxdsm
744ed098a5 feat(workspace): Phase C U1 — per-repo merge loop (landOneRepo + landWorkspaceTask)
Extracts the per-repo land mechanics out of runAiMerge's inline clean-room
closure into an exported landOneRepo(store, repoRootDir, branch, integrationBranch,
ctx): pre-merge prune (rooted at the sub-repo), the clean-room temp worktree,
mergeAndReview, landSquash, and the CAS concurrent-advance retry that advances ONE
local integration ref — no remote push. runAiMerge is rewired as the single-repo
caller (its task-global finalization unchanged); the merger-ai suite (56 tests)
stays green as the byte-for-byte oracle.

landWorkspaceTask loops a workspace task's acquired sub-repos (sorted keys),
re-resolving each repo's integration branch with the shared override stripped
({...settings, integrationBranch: undefined, baseBranch: undefined}) so each
sub-repo lands on its own origin/HEAD, calls landOneRepo per repo, and aggregates
repo-tagged results — land-as-you-go on each repo's LOCAL ref (D2/D5). It does NOT
finalize/move the task (finalize-once + landed-tracking + idempotent retry are U2).

Door routing (KTD2): the engine dispatch and the user-facing CLI `fn task merge`
+ dashboard merge doors route workspace tasks to landWorkspaceTask so manual merge
works; store.mergeTask, aiMergeTask, and the runAiMerge chokepoint guard keep
throwing WorkspaceTaskMergeError as defense-in-depth.

New two-repo fixture tests: both repos land + no-push assertion, per-repo
override-stripped resolution onto distinct branches, repo-B conflict partial land
(task not moved), defense-in-depth throws. Gate green: typecheck, lint, build,
test:gate (649+58).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 23:31:43 -07:00
gsxdsm
316d2659b8 fix(review): workspace-merge park must use status:'failed' to avoid re-enqueue loop (U0)
Post-fix verification review (correctness + adversarial + reliability, unanimous
P0) found that the earlier retry-burn fix introduced an infinite loop: parking a
WorkspaceTaskMergeError task with status:null + mergeRetries:0 passes every
auto-merge eligibility gate (canMergeTask short-circuits only on status==='failed'),
so the cooldown sweep re-enqueues it every tick → guard re-throws → re-park, forever.

- Park with status:'failed' (keep mergeRetries:0). canMergeTask now blocks the
  auto-sweep; a human's manual merge still works because it flows through the
  manual-resolver branch (rejectMergeResolvers), which bypasses canMergeTask — so
  'failed' does not block manual retry (the original comment's worry was wrong).
- Detect the error via `err instanceof Error && err.name === "WorkspaceTaskMergeError"`,
  matching the VerificationError/MergeAbortedError convention and bundle-safe across
  the @fusion/core→@fusion/engine boundary (drops the now-unused class import).
- Document that the dispatch door guard is a fast-fail only; the unconditional
  chokepoint guard inside runAiMerge is the authoritative enforcement.
- Add a regression test asserting the auto-merge park sets status:'failed' (not null).

Gate green: lint, typecheck, build, test:gate (649+58), project-engine (81).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 20:29:46 -07:00
gsxdsm
7240b77c67 fix(review): harden R7 workspace merge guard + deprecation warning (U0)
Applies ce-code-review (autofix) feedback — 5 reviewers, P1s corroborated.

F1 [P1, ×4 reviewers] Guard the merge chokepoint, not just the 4 doors. The
per-caller `getTask().catch(()=>null); if(t) assert` pattern failed open on a
transient read, and runAiMerge re-read the task unguarded — so a workspace
task could reach git work against the non-git root. Added a named
WorkspaceTaskMergeError and call assertNotWorkspaceTaskMerge inside runAiMerge
(the sole merge path) and the deprecated aiMergeTask body; door guards remain
as fast-fail defense-in-depth.

F2 [P1] The dispatch catch treated the guard throw as a merge failure and set
mergeRetries=MAX, permanently blocking manual retry. It now recognizes
WorkspaceTaskMergeError and parks without burning retries.

F3 [P2] Deprecation-warning test asserted toBeLessThanOrEqual(1) — vacuously
true on zero emissions. Now resets the per-project flag and asserts the
warning fires exactly once and not again on a second deterministic merge.

F6 [P2] The once-per-process warning flag suppressed the notice for all other
projects in a multi-project host; now keyed per project (Set by cwd).

F5/F7/F8 [P3] @deprecated propagated to the aiMergeTask barrel re-export; CLI
runTaskMerge guard moved inside the formatted try/catch; FNXC placeholder
timestamps corrected; test .at(-1) -> length index.

Documented as residual (deferred to master-plan U8, not bugs in U0's window):
self-healing auto-finalize + store.mergeTask are additional merge-completing
paths not hardened here — workspace tasks are not end-to-end runnable until
master-plan Phase A, and U8 makes self-healing workspace-aware.

Gate green: typecheck (29 projects), lint, build, test:gate (649+58),
affected tests (206+4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:19:55 -07:00
gsxdsm
a6252e518d refactor(merger): unify on runAiMerge as the sole merge path (U0)
Make runAiMerge (the FN-5633 clean-room AI merge path, already the default)
the sole merge path; soft-deprecate the legacy aiMergeTask pipeline. Phase 0
of the workspace-mode master plan — a standalone merge-consolidation refactor
that lands first so downstream workspace work targets one merge function.

U1 — collapse the dispatch: project-engine.ts now calls runAiMerge
unconditionally; the two direct callers that bypassed the dispatch
(dashboard.ts onMergeImpl in --no-engine mode, task.ts runTaskMerge /
`fn task merge`) now route to runAiMerge too. Export runAiMerge from
@fusion/engine.

U2 — soft-deprecate: aiMergeTask is @deprecated (body retained for a later
deletion pass; shared helpers runAiMerge imports, e.g.
captureSingleCommitLandedMetadata, left intact). merger.mode "deterministic"
is annotated deprecated and made inert (type + field kept — published
@runfusion/fusion surface); the dispatch logs a one-time deprecation warning
and routes to runAiMerge. Changeset added (minor).

U3 — R7 workspace merge-boundary guard: shared @fusion/core predicate
assertNotWorkspaceTaskMerge(task) rejects tasks with populated
workspaceWorktrees at all four merge entry points (dispatch, store.mergeTask,
onMergeImpl, runTaskMerge) with an error naming master-plan U6. Covers the
window until per-repo merge support lands; U6 removes it.

U4 — deterministic-mode blast-radius audit: no production project, CI config,
or seeded setting pins merger.mode "deterministic"; only four engine tests
used it to drive the dispatch to aiMergeTask as a mockable seam — migrated to
mock runAiMerge instead. Other module-level aiMergeTask mocks were dead under
the default "ai" mode or test aiMergeTask directly (body retained), so they
are unaffected.

Also removes an unused acquireWorkspaceRepoWorktree import inherited from the
foundation branch (executor.ts) that was failing lint; master-plan U1 re-adds
it with its per-repo usage.

Merge gate green: lint, typecheck (29 projects), build, test:gate
(649 + 58), plus the migrated (114) and new predicate (4) tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:58:06 -07:00
gsxdsm
e2a3a37b26 FN-6569: make auto-merge retry cap configurable
Adds a project setting that controls how many auto-merge conflict retries run before recovery paths give up.

- Add maxAutoMergeRetries to project settings defaults, schema, types, and dashboard controls.
- Resolve the retry cap in engine merge handling, self-healing recovery, stall detection, and blocker fanout logic.
- Cover custom retry caps with core, dashboard, and engine regression tests.

Files changed:
 .changeset/fn-6569-max-auto-merge-retries.md       |  5 ++
 docs/architecture.md                               |  2 +-
 docs/settings-reference.md                         |  1 +
 .../core/src/__tests__/settings-defaults.test.ts   | 12 ++++
 packages/core/src/in-review-stall.ts               | 16 ++++-
 packages/core/src/index.ts                         |  1 +
 packages/core/src/settings-schema.ts               |  6 ++
 packages/core/src/task-priority.ts                 |  4 +-
 packages/core/src/types.ts                         |  8 +++
 .../dashboard/app/components/SettingsModal.tsx     |  8 +++
 .../components/settings/sections/MergeSection.tsx  | 26 +++++++
 .../app/hooks/__tests__/useBlockerFanout.test.ts   |  5 +-
 packages/dashboard/app/hooks/useBlockerFanout.ts   |  3 +-
 .../auto-merge-retry-cap-settings.test.ts          | 79 ++++++++++++++++++++++
 packages/engine/src/project-engine.ts              | 77 +++++++++++++--------
 packages/engine/src/self-healing.ts                | 30 +++++---
 16 files changed, 236 insertions(+), 47 deletions(-)

Fusion-Task-Id: FN-6569
Fusion-Task-Lineage: 42242d6a-68bc-41f1-b2d9-af2e6f168eed
2026-06-17 04:51:31 -07:00
gsxdsm
bffae81a98 FN-6277: track and reconcile legacy auto-merge stamps
Track legacy auto-merge stamp provenance and add safe operator cleanup.

- Add autoMergeProvenance storage and migration support so user overrides can be distinguished from legacy review-entry stamps.
- Mark existing ambiguous in-review autoMerge=true tasks as legacy stamps without changing behavior, and expose a dry-run/apply reconciliation API to clear them safely.
- Emit a non-mutating advisory when global auto-merge is disabled while legacy stamped review tasks remain.
- Cover migration, persistence, reconciliation, movement, merge resolution, and advisory behavior with regression tests and docs.

Files changed:
 .../fn-6277-legacy-automerge-stamp-cleanup.md      |   5 +
 docs/architecture.md                               |   2 +-
 docs/settings-reference.md                         |   2 +-
 packages/core/src/__tests__/db-migrate.test.ts     |  30 ++--
 packages/core/src/__tests__/db.test.ts             |  44 +++---
 packages/core/src/__tests__/goals-schema.test.ts   |   2 +-
 packages/core/src/__tests__/insight-store.test.ts  |  10 +-
 .../legacy-automerge-stamp-reconcile.test.ts       | 144 +++++++++++++++++++
 .../src/__tests__/merge-request-record.test.ts     |   2 +-
 packages/core/src/__tests__/mission-store.test.ts  |   2 +-
 packages/core/src/__tests__/run-audit.test.ts      |   4 +-
 .../core/src/__tests__/store-merge-queue.test.ts   |   2 +-
 packages/core/src/__tests__/store-movement.test.ts |  14 ++
 packages/core/src/__tests__/task-documents.test.ts |   2 +-
 packages/core/src/__tests__/task-merge.test.ts     |  16 ++-
 packages/core/src/db.ts                            |  10 +-
 packages/core/src/index.ts                         |   1 +
 packages/core/src/store.ts                         | 157 ++++++++++++++++++++-
 packages/core/src/task-merge.ts                    |   8 +-
 packages/core/src/types.ts                         |  10 +-
 .../automerge-toggle-legacy-advisory.test.ts       | 128 +++++++++++++++++
 packages/engine/src/project-engine.ts              |  68 ++++++++-
 22 files changed, 593 insertions(+), 70 deletions(-)

Fusion-Task-Id: FN-6277

Fusion-Task-Lineage: 22d36519-f2ba-4ca7-8a09-12fc803c9a5b
2026-06-13 05:06:05 -07:00
Phil Larson
8541db3185 fix(engine): normalize dashboard research providers 2026-06-12 15:57:12 -07:00
gsxdsm
b6800e8622 fix(FN-6035): address workflow primitive review feedback 2026-06-08 21:16:32 -07:00