Commit Graph

3324 Commits

Author SHA1 Message Date
gsxdsm
7760d783bd fix: green full-suite after getAgentLogCount and inventory drift (#2266)
## Summary
- Follow-up after #2229: full suite on main still failed on dashboard
curated inventory (21 ungated files) and mass engine failures
(`this.store.getAgentLogCount is not a function`).
- Harden executor tool-failure cursor capture for minimal/test
`TaskStore` adapters (same optional-API pattern as `project-engine`),
keep mock fixtures in lockstep, and quarantine inventory-only dashboard
files with ledger + vitest exclude.

## Changes
- **Executor**: optional `getAgentLogCount` / `getAgentLogs` /
`updateTask` at graph entry and trailing-failure detection.
- **Mocks**: `createMockStore`, soft-delete guard, post-done
continuation, cron `getGlobalSettingsDir`, executor-prompt
`bulkCompletionRefusalAt` (FN-8141).
- **i18n** (prior commit): es/fr/ko/zh-CN/zh-TW triage-duplicate keys.
- **Inventory**: 21 dashboard files → `test-quarantine.json` +
`vitest.config.ts` lockstep (VAL-REMOVAL SQLite / load flakes /
build-only dist assert).

## Test plan
- [x] `node scripts/check-test-inventory.mjs --dashboard-curated`
- [x] `pnpm test:gate`
- [x] engine: soft-delete, prompt, cron, post-done, tool-failure-retry,
and related samples
- [x] `@fusion/core` schema-applier + `@fusion/i18n` parity
- [ ] Full Suite (non-blocking) on this PR / main after merge

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

## Summary by CodeRabbit

- **New Features**
- Added localized text for triage duplicate-resolution settings and
near-duplicate task actions in Spanish, French, Korean, Simplified
Chinese, and Traditional Chinese.
- Users can now see translated options and confirmations to keep or
delete detected duplicate tasks.

- **Bug Fixes**
- Improved resilience during task execution and recovery when optional
activity-log services are unavailable, preventing avoidable failures
during error handling.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 23:44:35 -07:00
gsxdsm
13bdf63bfc fix: resolve Anthropic subscription auth through getApiKey for pi ModelRuntime.getAuth
pi >=0.80.8 moved session request auth to ModelRuntime.getAuth -> pi-ai
resolveProviderAuth, which reads credentials.read("anthropic") and refreshes an
OAuth credential via credentials.modify("anthropic"). Fusion stores the
subscription login under `anthropic-subscription` with no raw `anthropic` row,
so the refresh callback saw current===undefined, bailed, and auth resolved to
undefined -> "Provider is not configured: anthropic" (then fell back).

Resolve read("anthropic") through fusion's getApiKey (refresh + raw/legacy/
subscription/fallback precedence) and hand pi-ai a ready api_key credential;
pi-ai routes it as OAuth by the sk-ant-oat token prefix. Supersedes the prior
read-alias, which fixed lookup but not the broken OAuth refresh-via-modify path.

Verified end-to-end: ModelRuntime.getAuth(anthropic/opus) now resolves the
subscription token instead of returning undefined.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:24:45 -07:00
gsxdsm
4b150e2280 fix: resolve Anthropic subscription auth under pi-ai >=0.80 read() contract
pi-ai >=0.80 resolves provider auth via credentials.read(provider.id) and
performs OAuth refresh/derivation itself, bypassing fusion's getApiKey()
where the anthropic-subscription -> anthropic alias lived. A subscription-only
login surfaced as "Provider is not configured: anthropic" at prompt time even
though the status card showed connected.

- Alias the subscription OAuth credential into read("anthropic") at the
  credential-store layer (createFusionCredentialStore); raw/legacy rows still win.
- Match "not configured" in isRetryableModelSelectionError so an unresolved
  provider triggers the configured fallback model instead of hard-failing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:09:12 -07:00
TrinaryCompute
00cca460fc fix(engine): reclaim leaked semaphore slots when the system is busy, not only at total idle (#2265)
## fix(engine): reclaim leaked semaphore slots when the system is busy,
not only at total idle

### The bug

`recoverIdleSemaphoreLeakCandidate` only reclaims stale `AgentSemaphore`
slots when the system is **completely** idle (`persistedActive === 0 &&
inFlightCount === 0` → reconcile to 0). If even one in-progress row
persists — e.g. a zombie task whose agent session died without its
`finally` release — the valve never opens, and slots leaked by abnormal
teardown accumulate monotonically until `activeCount` pins the limit.

At that point the engine deadlocks in a distinctive way:
- every hold/release sweep logs `Hold release for <task> deferred — no
reservable slot for in-progress`
- triage/plan report `planning=0 … processing=0, semaphore
active=<limit>/<limit>, available=0`
- the merge queue grows unboundedly (merges also need a slot)
- only a process restart recovers

`reapLeakedConcurrencySlots` (FN-6782) doesn't help — it reconciles
**worktree** slots, not the shared semaphore.

### Production evidence

Observed twice on a 6-project embedded-PG deployment driving a local
model:

- After ~5 days of continuous operation: `semaphore active=24/24`,
`planning=0/24, processing=0`, 5 persisted in-progress rows (dead
sessions), merge queue at 88, **zero merges for >24h**. Restart
immediately restored merging.
- Same signature earlier at `active=40/40` with both LLM backends idle
(`kvcache≈0`).

The handful of zombie in-progress rows kept `persistedActive` nonzero
indefinitely, so the idle-only valve could never fire.

### The fix

Generalize the valve: clamp `activeCount` down to the **persisted +
in-flight bound** whenever the semaphore over-holds **continuously** for
a repair window.

- The strict-idle case (`bound === 0`) keeps its existing fast 5s window
— behavior unchanged, existing tests pass as-is.
- The non-idle case uses a deliberately conservative new window
(`STALE_SEMAPHORE_EXCESS_REPAIR_MS = 600_000`, 10 min): nested helper
agents (`runNested`) legitimately push `activeCount` above the persisted
top-level count for the duration of a nested run, so the excess must
outlive any plausible nested session before it is treated as leaked. The
candidate timestamp resets the moment the excess clears.
- `reconcileActiveCount` only ever lowers the count, so the clamp cannot
inflate capacity; a late release from a genuinely live agent after a
(worst-case, mis-timed) clamp is absorbed by the existing excess-release
guard (FN-6423).

Call-site changes are limited to the two log messages (the old
parenthetical claimed "no persisted … agent work", which is no longer
the only repair case).

### Tests

- existing idle-valve tests pass unchanged (same window, same
reconcile-to-0)
- new: stale excess above a nonzero persisted bound is repaired only
after the long window, and clamps exactly to the bound
- new: candidate resets when the excess clears (nested overshoot ending)
- new: caller in-flight sessions count into the bound (no false
candidate)

### Files

- `packages/engine/src/concurrency.ts` — generalized valve +
`STALE_SEMAPHORE_EXCESS_REPAIR_MS`
- `packages/engine/src/scheduler.ts`, `packages/engine/src/triage.ts` —
log message accuracy
- `packages/engine/src/__tests__/concurrency.test.ts` — 3 new tests


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

* **Bug Fixes**
* Improved stale “semaphore excess” recovery by using a configurable
repair window when excess persists.
* Prevented premature capacity corrections by accounting for in-flight
top-level work during reconciliation.
* Correctly handles nested helper activity so only leaked excess is
reclaimed, preserving legitimate nested runs.
* Updated reconciliation to clamp excess to the appropriate reclaim
floor instead of waiting indefinitely.
* **Improvements**
* Refreshed diagnostic warning text to clarify the over-held vs
persisted+in-flight work comparison.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: TrinaryCompute <fusion-merge@trinarycompute.dev>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:08:42 -07:00
gsxdsm
daa34fbc38 fix: refineTask/duplicateTask fail in backend (PostgreSQL) mode (#2253)
## Summary

Eliminates the remaining backend/PostgreSQL-mode sync-SQLite
(`store.db`) call sites — both the crashing ones and the
try/catch-masked ones that silently degraded features. Found via a full
audit of `store.db`/`archiveDb` residue after the PG cutover's per-site
routing missed them.

**Crashes fixed:**
1. **refineTask / duplicateTask** threw `TaskStore.db: SQLite Database
is not available in backend mode`. Both create rows through
`createTaskWithId` callbacks calling `store.atomicCreateTaskJson()`
directly, bypassing `_createTaskInternal`'s backend routing. The shared
helper now routes itself (soft-delete conflict check + non-destructive
insert in one AsyncDataLayer transaction).
2. **Merger verification cache**: `getVerificationCacheHit` ran sync
SQLite unguarded *outside* any try/catch in
`runDeterministicVerification`; `recordVerificationCachePass` was
swallowed so the cache never warmed. Both are now async with a PG
branch.

**Silent degradations fixed (features that were dead on PG):**
- Workflow run-branch + foreach step-instance persistence
(`saveWorkflowRunBranch`, `loadWorkflowRunBranches`,
`clearWorkflowRunBranches`, `saveWorkflowRunStepInstance`,
`loadWorkflowRunStepInstances`, `clearWorkflowRunStepInstances`) —
executor crash-resume checkpoints were silently never persisted.
- `getBranchProgressByTask` — returned an empty map, dropping
`branchProgress` from task payloads.
- `runPluginColumnTransitionHooks` — plugin `onEnter`/`onExit`
column-transition hooks never fired (marker bookkeeping + non-locking
task read now async).
- `getTaskColumns` — dashboard treated all agent-linked tasks as
non-terminal.
- `getWorkflowStep` / `listWorkflowSteps` — stored workflow-step rows
now read from `project.workflow_steps` (listing previously returned
plugin steps only); `getLegacyWorkflowStepSnapshot` returns `undefined`
on PG (legacy snapshot exists only in pre-migration SQLite).
- `readRawProjectSettings` / `listWorkflowPromptOverridesForProject` —
now read via the async layer.

These store methods became **async**; engine/dashboard callers await
them (the workflow persistence interfaces already accepted
`Promise`-returning impls).

**PG gotcha encoded in the fixes:** migration `0006_project_ownership`
rebuilds every project-schema PK to lead with `project_id`, so
column-list `ON CONFLICT` inference fails (42P10) — upserts target the
PK by constraint name.

## Surface Enumeration

- Creators through `atomicCreateTaskJson`: `refineTaskImpl`,
`duplicateTaskImpl` (fixed); `_createTaskInternalImpl` unaffected
(already routed).
- Verification-cache callers (all merger, all 3 sites now awaited).
- Run-branch/step-instance callers: executor persistence adapters,
parse-steps foreach probe, integration-queue flip, crash-resume
reconcile, graph-reset cleanup; triage replan cleanup; dashboard
spec-rebuild pin clears; agent-reflection rework summing — all awaited.
- Audit classified everything else as guarded or sync-mode-only (dead in
production — every entry point constructs stores via
`createTaskStoreForBackend`).

## Symptom Verification

- **Original symptoms:** refinement/duplicate creation threw; merge
verification threw; workflow checkpoints/branch progress/plugin
hooks/task-column lookups silently no-oped on PostgreSQL.
- **Exact reproduction:** `refine-duplicate-task.pg.test.ts`,
`verification-cache.pg.test.ts`, and
`sync-db-residue-backend.pg.test.ts` exercise each surface against
embedded-PostgreSQL backend-mode TaskStores.
- **Assertion it is gone:** all suites pass (14 + 5 tests), plus
`transition-pending-and-status-clear.pg.test.ts`,
`create-task-reserved-id.pg.test.ts`, dashboard `routes-github.test.ts`
(123), engine `triage.test.ts` (221) and `agent-reflection.test.ts`
(31). Core/engine/dashboard typecheck fully clean: the 13 errors from
the FN-8142 pi SDK migration are fixed by bumping
@earendil-works/pi-ai/pi-coding-agent to ^0.80.10 (FN-8142 used APIs
absent from the previously locked 0.80.6). Locally green: `pnpm
verify:fast` (scoped typecheck + build + CLI build + boot smoke), `pnpm
test:gate`, and `pnpm lint`.

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

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

* **Bug Fixes**
* Fixed refinement/duplication task creation in PostgreSQL-backed
backend mode.
* Improved backend-mode persistence for workflow checkpoints,
foreach-step instances, branch progress, and cleanup flows (including
retries/resets/transitions), so stored data reliably round-trips.
* Hardened backend-mode reads for workflow steps, task columns, project
settings, and prompt overrides.
* Made verification-cache reads/writes complete reliably, including
command-specific cache behavior.
* **Tests**
* Added PostgreSQL integration/regression coverage for
refinement/duplication, sync residue, and verification caching.
* **Chores**
* Bumped `@earendil-works/pi-ai` and `@earendil-works/pi-coding-agent`
to `^0.80.10`.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:35:01 -07:00
gsxdsm
5f70447a62 FN-8108: require resolution for triage duplicate tasks
Require explicit resolution for triage-detected duplicate tasks.

- Add configurable prompt, keep, and delete handling for explicit duplicate markers.
- Surface linked duplicate decisions in task details and scheduling settings.
- Preserve task failure provenance and strengthen lifecycle recovery coverage.

Files changed:
 .changeset/blocked-park-survives-graph-teardown.md |   7 +
 .changeset/failure-provenance-promoter-marker.md   |   7 +
 .changeset/fn-8108-triage-duplicate-resolution.md  |   7 +
 .changeset/veto-progressing-does-not-clear.md      |   7 +
 docs/settings-reference.md                         |   2 +
 .../completed-promotion-failure-provenance.test.ts |  31 +++
 .../core/src/__tests__/duplicate-intake.test.ts    |  11 ++
 .../src/completed-promotion-failure-provenance.ts  |  38 +++-
 packages/core/src/duplicate-intake.ts              |  29 +++
 packages/core/src/index.gate.ts                    |   1 +
 packages/core/src/index.ts                         |   3 +-
 packages/core/src/settings-schema.ts               |   1 +
 packages/core/src/types.ts                         |   6 +
 .../dashboard/app/components/TaskDetailModal.tsx   |  38 +++-
 .../__tests__/TaskDetailModal.rendering.test.tsx   |  26 +++
 .../settings/sections/SchedulingSection.search.ts  |   9 +
 .../settings/sections/SchedulingSection.tsx        |  15 ++
 .../__tests__/routes-tasks-near-duplicate.test.ts  |  31 +++
 .../src/routes/register-task-workflow-routes.ts    |  19 ++
 .../__tests__/executor-task-done-blocked.test.ts   | 212 ++++++++++++++++++++-
 packages/engine/src/__tests__/merger-ai.test.ts    |  56 +++++-
 .../__tests__/overseer-noop-finalize-veto.test.ts  |  64 ++++++-
 .../explicit-duplicate-marker-sweep.test.ts        |  19 +-
 packages/engine/src/__tests__/self-healing.test.ts |  53 ++++++
 .../triage-explicit-duplicate-marker.test.ts       |  22 ++-
 packages/engine/src/executor.ts                    |  30 +++
 packages/engine/src/merger-ai.ts                   |   5 +-
 packages/engine/src/overseer-noop-finalize-veto.ts | 148 ++++++++++----
 packages/engine/src/self-healing.ts                |  29 ++-
 packages/engine/src/triage.ts                      |  58 +++---
 packages/i18n/locales/en/app.json                  |  15 +-
 packages/i18n/src/resources.d.ts                   |  13 +-
 32 files changed, 897 insertions(+), 115 deletions(-)

Fusion-Task-Id: FN-8108

Fusion-Task-Lineage: 8e732bad-d418-426e-85e1-903a7f990fba

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 22:11:35 -07:00
gsxdsm
0753476c0a fix(engine): no-op finalize veto no longer cleared by mid-execution progressing — only accepted-completion evidence supersedes a failure park (#2263)
## What

FN-8141 follow-up 3. Tightens `deriveExecutorSignalMemory`
(packages/engine/src/overseer-noop-finalize-veto.ts) so a mid-execution
`progressing` overseer observation can no longer clear the executor
no-op-finalize veto.

## Why

The prior derivation took the **newest** executor `observe` entry and
cleared `incompleteWork` whenever it was anything but the canonical
failed reason. But the planner overseer emits a `progressing`
observation ("Task is actively executing in-progress work") the
**moment** a task re-enters execution — long before that execution
finishes. The defeating shape:

> task parks failed-incomplete → requeued → re-execution starts
(overseer observes `progressing`) → execution dies or reverts again
**without** a newer failed observation → newest observation is
`progressing` → `incompleteWork:false` → an empty no-op finalize is
**not** vetoed → the reverted branch launders into `done`.

`progressing` is not "completed green" — the veto's own contract says
the failure must be superseded by a green completion.

## Change

The executor stage in `planner-overseer.ts` emits only
`progressing`/`failed`/`stuck`/`blocked` — **no** green-completion
observation — so the timeline alone cannot distinguish progressing from
completed. Per the follow-up spec, the derivation now:

1. Scans the executor `observe` timeline for the newest
**failed-with-incomplete-work** observation.
2. Keeps `incompleteWork` TRUE unless a durable **clean-completion
task-log marker** is **strictly newer** than that failure park. Reuses
the shared `CLEAN_COMPLETION_MARKERS` set (now exported from
`@fusion/core`, single-sourced with
`evaluateCompletedPromotionFailureProvenance`) so it automatically
tracks sibling follow-up F2's removal of the promotion-output marker.
3. Fails safe on a malformed failure timestamp (stays vetoed).
4. `merger-ai.ts` threads `task.log` into the derivation.

All existing precedence rules are preserved: non-empty merges are never
vetoed; human-control deferral (user-paused / autoMerge:false) still
defers; a missing task fails open.

## Test evidence

- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/overseer-noop-finalize-veto.test.ts
src/__tests__/merger-ai.test.ts` → **59 passed**. New/updated cases:
failed→progressing (no completion) ⇒ still vetoed (the regression this
fixes); progressing between two failed parks (FN-8141 timeline) ⇒
vetoed; clean-completion marker newer than failure ⇒ not vetoed; older
completion marker ⇒ still vetoed; no failure park ⇒ not vetoed; bounded
tail-scan preserved. Integration: empty lane with
failed-then-progressing timeline blocks the finalize; genuinely
re-executed green task finalizes to done.
- `pnpm --filter @fusion/core exec vitest run
src/__tests__/completed-promotion-failure-provenance.test.ts` → 9
passed.
- `pnpm --filter @fusion/engine exec tsc --noEmit` → clean. `pnpm
--filter @fusion/core exec tsc --noEmit` → clean.
- `pnpm verify:fast` → PASS (3 steps green).

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

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved empty-merge finalization safeguards so an in-progress task
cannot incorrectly clear a previously detected incomplete-work failure.
* Finalization can now proceed when a newer clean-completion record
confirms successful completion.
* Added bounded task-history evaluation to ensure completion records are
interpreted safely and consistently.

* **Tests**
* Expanded coverage for progressing, failed, and clean-completion task
timelines.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus <noreply@anthropic.com>
2026-07-16 21:42:44 -07:00
gsxdsm
46866a5c5a fix(core): promoter recovery output no longer counts as clean-completion evidence in the failure-provenance guard (#2262)
## What

FN-8141 follow-up 2. Removes `"Auto-recovered: task work was complete
but stranded"` from `CLEAN_COMPLETION_MARKERS` in
`packages/core/src/completed-promotion-failure-provenance.ts`.
Clean-completion evidence is now **execution outcomes only**: `"Task
marked done by agent"` (accepted explicit fn_task_done, also covers the
PREMISE STALE skip-then-done flow) and `"All steps complete — implicit
fn_task_done"` (implicit-completion success).

## Why

That string is the PROMOTER'S OWN OUTPUT — self-healing's
`recoverCompletedTasks` (executor.ts:4594) narrating "I promoted this
task" — not evidence of a clean execution outcome. Any task whose
durable log contains a promotion written by the pre-#2257 buggy sweep
(the real FN-8141 row, or any pre-guard history) carried a permanent
"clean" marker: the tail scan hit the promotion line before the older
failure park and returned not-blocked, re-enabling the exact laundering
the guard exists to stop.

Audit confirmed no other genuine execution-outcome success markers are
missing — the PREMISE STALE accepted `fn_task_done` writes the
already-listed `"Task marked done by agent"` line (executor.ts:14939),
and the honest-blocked exit (`BLOCKED: ...`) is correctly NOT counted.
`grep` confirmed the removed string has only one other consumer: its
writer at executor.ts:4594. A task already promoted to in-review/done is
out of the promoters' todo/in-progress scan scope, so
legitimately-recovered old tasks are not wedged (verified by test rather
than assumed).

## Test evidence

- Core `completed-promotion-failure-provenance.test.ts`: **11 passed** —
added pre-fix-history shape (failure park → promoter recovery line →
blocked), promoter-line-alone → blocked, and positive coverage of each
remaining marker.
- Engine `self-healing.test.ts`: **405 passed** — added promoter
withholds on the pre-fix-history shape and emits the existing
`task:reconcile-stranded-completed-no-action` (reason
`failure-provenance`) event.
- `pnpm --filter @fusion/engine exec tsc --noEmit`: clean.
- `pnpm verify:fast`: PASS (3 steps green, no tests run).

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

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

## Summary by CodeRabbit

- **Bug Fixes**
- Prevented failed tasks with prior failure history from being
automatically promoted.
- Ensured recovery messages cannot override authoritative failure
records or be mistaken for successful completion.
- Preserved the existing no-action behavior and audit event when
promotion is blocked by failure provenance.
- **Tests**
- Added regression coverage for failed-task promotion and stranded-task
recovery scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus <noreply@anthropic.com>
2026-07-16 21:39:49 -07:00
gsxdsm
f116d05c41 fix(engine): honest BLOCKED park survives pause-abort and workflow-graph teardown (#2264)
## What

Follow-up 1 to the FN-8141 guard series (#2254–#2260). Makes the honest
`fn_task_done(outcome="blocked")` park (`status:"failed"`,
`error:"BLOCKED: <reason>"`, blockedBy → dependencies, added in #2256)
**survive the graph-teardown machinery** that bounced FN-8141's failed
park back to `todo`.

## Why

In the original FN-8141 incident, the executor's parked-failed state did
not stick: the pause-abort classifier and the workflow-graph failure
handler either rehomed the task to `todo` (clearing `status`/`error`) or
overwrote the distinctive `BLOCKED:` error with a generic "Workflow
graph terminated with failure" string. #2256 added the blocked exit but
nobody proved the park survives that bounce. Any path that
clears/overwrites the marker re-opens the laundering hole, because
self-healing (#2257/#2260) and dependency-gated scheduling key off
exactly that `BLOCKED:` error plus the recorded `blockedBy`
dependencies.

`handleGraphFailure` now detects a live blocked park (`status ===
"failed" && error.startsWith("BLOCKED:")`) **before every other
classifier** and honors it, following the existing non-graph honor-park
precedent (executor `~12163`):

- no requeue to `todo`, no engine-internal auto-continue, no `BLOCKED:`
error overwrite;
- clears the in-memory pause-abort marker so
`recoverPausedAbortFailures` has nothing to chase;
- **releases the worktree / `maxWorktrees` slot** (FN-6782 leaked-holder
precedent — the graph `finally` does not delete `activeWorktrees`);
- leaves `status`/`error`/`column`/`dependencies`/steps untouched.

Unblocking still works: the operator requeue (`moveTask`
in-progress→todo, `moves.ts ~628`) and `buildManualRetryResetPatch`
clear the `BLOCKED:` error; the guard keys off the **live** error, so a
cleared row is never re-wedged, and dependency-gated scheduling leaves
the parked row untouched while `blockedBy` deps are unmet.

## Surfaces covered

Pause-abort classifier (hard-cancel), engine-internal auto-continue, and
the plain terminal graph-failure sink — all routed through
`handleGraphFailure`, so a single top-of-method guard composes across
them.

## Test evidence

Extended `executor-task-done-blocked.test.ts` (drives
`handleGraphFailure` against a live blocked park):
- honors the park under a hard-cancel pause-abort bounce (no requeue /
clear / auto-continue);
- honors it under a plain terminal graph failure (sink never overwrites
`BLOCKED:`);
- releases the worktree/concurrency slot + clears the pause-abort
marker;
- NON-blocked failed park keeps existing behavior (guard scoped to
`BLOCKED:`);
- a cleared (unblocked) row is NOT re-honor-parked.

```
pnpm --filter @fusion/engine exec vitest run src/__tests__/executor-task-done-blocked.test.ts  → 13 passed
pnpm --filter @fusion/engine exec vitest run executor-paused-abort-todo-benign + executor-graph-requeue-gate  → 53 passed
pnpm --filter @fusion/engine exec tsc --noEmit  → clean
pnpm verify:fast  → PASS (3 steps green)
```

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

Co-authored-by: Claude Opus <noreply@anthropic.com>
2026-07-16 21:39:16 -07:00
gsxdsm
e72629c251 FN-8126: add per-task merger model controls
Enable Quick Add and task editing to select merger models and thinking levels.

- Persist merger model and thinking overrides through task APIs, storage, and PostgreSQL migrations.
- Add merger-lane selection controls to Quick Add and model settings interfaces.
- Apply task merger settings to merger and PR fallback sessions, with regression coverage.
- Document the merger lane and include a release changeset.

Files changed:
 .changeset/fn-8126-quick-add-merger-lane.md        |   7 ++
 docs/dashboard-guide.md                            |   2 +
 docs/settings-reference.md                         |   7 +-
 .../core/src/__tests__/model-resolution.test.ts    |   9 ++
 packages/core/src/index.gate.ts                    |   1 +
 packages/core/src/index.ts                         |   1 +
 packages/core/src/model-resolution.ts              |  20 ++++
 .../core/src/postgres/migrations/0000_initial.sql  |   3 +
 .../migrations/0017_task_merger_model_lane.sql     |   4 +
 packages/core/src/postgres/schema-applier.ts       |  14 ++-
 packages/core/src/postgres/schema/project.ts       |   3 +
 packages/core/src/store.ts                         |   2 +-
 .../core/src/task-store/archive-lifecycle-2.ts     |   6 ++
 packages/core/src/task-store/persistence.ts        |   6 ++
 packages/core/src/task-store/remaining-ops-2.ts    |   4 +-
 packages/core/src/task-store/remaining-ops-6.ts    |   2 +-
 packages/core/src/task-store/serialization.ts      |   6 ++
 packages/core/src/task-store/task-creation.ts      |   6 ++
 packages/core/src/task-store/task-row-mappers.ts   |   4 +-
 packages/core/src/task-store/task-update.ts        |   6 ++
 packages/core/src/types.ts                         |  18 ++++
 packages/dashboard/app/api/tasks.ts                |  13 +++
 .../dashboard/app/components/InlineCreateCard.tsx  |  33 ++++++-
 .../app/components/ModelSelectionModal.tsx         |  29 ++++++
 .../dashboard/app/components/ModelSelectorTab.tsx  | 101 +++++++++++++++++++--
 .../dashboard/app/components/QuickEntryBox.tsx     |  44 +++++++--
 .../__tests__/ModelSelectionModal.test.tsx         |  20 ++++
 .../components/__tests__/ModelSelectorTab.test.tsx |  37 +++++++-
 .../src/routes/register-task-workflow-routes.ts    |  27 +++++-
 .../src/__tests__/agent-session-helpers.test.ts    |   8 ++
 packages/engine/src/agent-session-helpers.ts       |  16 +++-
 packages/engine/src/merger-ai.ts                   |  14 +--
 packages/engine/src/merger.ts                      |  35 ++++---
 packages/engine/src/pr-response-run-ops.ts         |   7 +-
 34 files changed, 451 insertions(+), 64 deletions(-)

Fusion-Task-Id: FN-8126

Fusion-Task-Lineage: 3fc81801-6d77-4e11-9cf0-3af37313930e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 21:07:06 -07:00
gsxdsm
b747f51f8b FN-8185: use async persistence for reflection rework reads
Resolve workflow rework metrics from the persisted production run.

- Resolve workflow selections to definition-backed run IDs
- Prefer asynchronous step-instance reads with legacy-store compatibility
- Cover PostgreSQL, fallback, and degraded reflection metrics

Files changed:
 .../engine/src/__tests__/agent-reflection.test.ts  | 51 +++++++++++++++++++---
 packages/engine/src/agent-reflection.ts            | 50 ++++++++++++++-------
 2 files changed, 80 insertions(+), 21 deletions(-)

Fusion-Task-Id: FN-8185

Fusion-Task-Lineage: 2554d5f1-62b7-47a6-bad0-c00a7d649a2d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 20:50:18 -07:00
gsxdsm
a136535f15 fix(engine): taint steps skipped after a bulk-completion refusal so they cannot auto-promote (#2260)
## What & why

**FN-8141 laundered a failed task into `done` with zero net changes and
no sign-off.** After the executor's
`bulk-step-completion-without-review` refusal fired (steps had no
APPROVE verdicts), the agent used the sanctioned skip affordance
(`fn_task_update status="skipped"`) on the remaining unreviewed steps.
Because every completion check counts `skipped` as complete, the task
then satisfied the exact condition the refusal was protecting, and
downstream **automatic** promotion (implicit `fn_task_done`,
self-healing `recoverStrandedCompletedTodoTasks`) moved it to in-review
— where the AI merger found an empty diff and finalized it as a no-op
`done`.

This PR restores the invariant: **steps skipped while a
bulk-step-completion refusal marker is active on the task are "tainted"
and cannot carry the task to review through any automatic path.** The
taint clears on an honest exit — an accepted `fn_task_done` (explicit or
non-tainted implicit) or an operator manual retry — so the legitimate
`PREMISE STALE` skip-then-done flow is unaffected.

## Design

- **Persisted marker**: new nullable `Task.bulkCompletionRefusalAt` (ISO
timestamp), stamped when the `bulk-step-completion-without-review`
refusal fires (explicit `fn_task_done` handler + implicit
`handleImplicitTaskDoneRefusal`). Survives requeue so a refusal on
attempt N taints attempt N+1's promotion. Full store plumbing (types,
descriptors, serialization, SQLite/PG schema + health self-heal).
- **Pure evaluator** `evaluateSkipBypassTaint(task)` in `@fusion/core`
(next to `evaluateNoCommitsNoOpFinalize`): `blocked` iff the marker is
set AND ≥1 step is `skipped`. Single rule every AUTO-promotion check
calls.
- **Clearing**: accepted explicit `fn_task_done`, accepted
implicit/retry completion (the success-reset `updateTask`s), and
`buildManualRetryResetPatch` (operator retry). A fresh lifecycle that
genuinely re-does the work leaves zero skipped steps, so it is never
blocked even if a marker lingers.

## Surface enumeration (every consumer of "all steps done/skipped" that
gates AUTO-promotion)

- **executor.ts**: `getCompletedTaskFinalizationDecision` (gated on the
`isTaskWorkComplete` branch only, never on an accepted `taskDone`);
`recoverCompletedTask` (shared chokepoint for unpause resume,
completed-task watchdog, orphan resume);
`evaluateImplicitCompletionRefusal` (both implicit-completion loops);
`isTaskAlreadyCompleteForNonContinuableSession`; graph merge-boundary
`getWorkflowMergeImplementationProofFailure`.
- **self-healing.ts**: `recoverCompletedTasks` (stuck in-progress) and
`recoverStrandedCompletedTodoTasks` (the exact FN-8141 promoter).
- **Verified-safe, left as-is**: per-step graph node projections
(executor ~6274/6298) and progress-render checks — they don't gate
whole-task auto-promotion.

## Test evidence

Scoped runs (all green):

```
CORE:   pnpm --filter @fusion/core exec vitest run \
          src/__tests__/skip-bypass-taint-guard.test.ts \
          src/__tests__/skip-bypass-taint-persistence.test.ts \
          src/__tests__/manual-retry-reset.test.ts
        → 17 passed

ENGINE: pnpm --filter @fusion/engine exec vitest run \
          src/__tests__/executor-skip-bypass-taint.test.ts \
          src/__tests__/self-healing.test.ts
        → 401 passed
```

Coverage: pure-evaluator (skip-before-refusal counts, skip-after-refusal
doesn't, taint-clearing, empty-marker/empty-steps edges); store
round-trip of the marker (set→read→clear); executor white-box (implicit
completion refused when tainted, allowed when clean or fully re-done,
graph merge-boundary reports missing proof, and the **explicit
`fn_task_done` PREMISE-STALE honest exit stays accepted**); self-healing
(FN-8141 sequence does not promote from either recovery path; a clean
legitimately-skipped task still promotes); manual-retry clears the
marker.

## Note on `pnpm verify:fast`

`verify:fast` currently fails at the workspace-artifact bootstrap on
**pre-existing** pi-SDK type errors in
`packages/engine/src/{auth-storage,pi,provider-registration}.ts` — the
FN-8145 upstream migration breakage (pi 0.80.x removed
`AuthStorage`/`ModelRegistry.create`). **None of those files are in this
diff.** `@fusion/core` builds clean (`packages/core build: Done`), and
`@fusion/engine` `tsc` reports **no errors in the files this PR
touches** (`executor.ts`, `self-healing.ts`); the only engine build
errors are the FN-8145 files. This base failure is the same condition
FN-8141 describes and is out of scope for this task.

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

Co-authored-by: Claude Opus <noreply@anthropic.com>
2026-07-16 20:37:05 -07:00
gsxdsm
29543a0aac FN-8157: add PostgreSQL workflow step-instance persistence
Persist workflow foreach step-instance state through async PostgreSQL store APIs.

- Add async save, load, and stale-run pruning operations backed by Drizzle.
- Route executor persistence, recovery, and integration projection through async APIs.
- Cover PostgreSQL persistence and migrate foreach wiring coverage to the PG harness.
- Quarantine unrelated flaky route and triage tests per the test ledger.

Files changed:
 .../workflow-run-step-instances.pg.test.ts         | 100 +++++++++++++++++++
 packages/core/src/store.ts                         |  14 ++-
 packages/core/src/task-store/remaining-ops-6.ts    | 109 ++++++++++++++++++++-
 .../dashboard/src/__tests__/routes-github.test.ts  |  14 +--
 .../src/routes/register-task-workflow-routes.ts    |  18 ++--
 packages/engine/src/__tests__/triage.test.ts       |   6 +-
 .../src/__tests__/workflow-foreach-wiring.test.ts  |  59 +++++------
 packages/engine/src/executor.ts                    |  57 ++++++++---
 packages/engine/src/triage.ts                      |   4 +-
 packages/engine/vitest.config.ts                   |   2 +-
 scripts/lib/test-quarantine.json                   |   7 +-
 11 files changed, 315 insertions(+), 75 deletions(-)

Fusion-Task-Id: FN-8157

Fusion-Task-Lineage: c359f0d3-9191-4d27-aaed-9912419c5c27

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 20:36:45 -07:00
gsxdsm
aa1e250dd3 fix(engine): overseer vetoes no-op merge finalize when the last executor signal was failed-with-incomplete-work (#2258)
## What & why

FN-8141 (\"Update pi SDK … verify Kimi K3 end to end\") was **laundered
into `done`** despite producing zero net changes. The executor reverted
the impossible work 5 times; the planner overseer emitted
`stage=executor signal=failed` (\"Executor stage parked failed with work
incomplete\") **twice**, then an hour later — because the overseer is
**stage-scoped and memoryless** — classified the same task `stage=merger
signal=progressing` and let the AI merger's **empty no-op finalize**
promote it to `done`. No reviewer ever saw it (skipped steps request no
review; the merge-review pass reviews an empty diff).

This restores the invariant: **a task whose most-recent executor signal
is failed-with-incomplete-work, with no subsequent green completion,
must not reach `done` via a zero-diff no-op merge finalize.**

## Change

Two pure, unit-testable, never-throw functions
(`packages/engine/src/overseer-noop-finalize-veto.ts`), following the
FN-7514 `evaluateOverseerHumanControl` precedent (pure predicate +
ids/outcomes-only audit metadata):

- **`deriveExecutorSignalMemory`** — reconstructs the most-recent
executor signal from the **durable `overseer:intervention` timeline the
overseer already writes** (no new persisted column / migration; \"the
existing oversight state storage the controller uses\"). A later green
executor observation supersedes an earlier failure, which is how \"no
subsequent execution completed green\" is derived. Keys on the
newly-exported `EXECUTOR_FAILED_INCOMPLETE_REASON` constant (already
load-bearing for FN-7577 feed dedup) as the single source of truth.
- **`evaluateNoOpFinalizeExecutorVeto`** — the veto decision.

Wired into the AI **empty-merge lane** (`merger-ai.ts`), composed with
(and independent of) the FN-6461 no-commits guard: on veto it sets
`error`, writes a durable log entry, emits
`overseer:no-op-finalize-vetoed-failed-executor`, and moves the task
back to `todo` with progress preserved — mirroring the FN-6461 blocked
lane. The move-to-todo transition takes the task out of the merge lane,
so the event isn't re-emitted every poll (equivalent to the
`overseer:oversight-withheld-human-control` per-(taskId, reason) dedup).

Independent of the sibling Task 2 merger-layer lineage guard: both can
fire; **either alone stops FN-8141**.

## Surface enumeration

- **Empty (zero-diff) no-op merge** — vetoed when memory is
failed-incomplete. ✅
- **Non-empty (real squash landed) merge** — **never** vetoed
(reviewers/merge review cover real diffs). ✅
- **failed-incomplete then later green execution** — memory superseded →
no veto. ✅
- **No executor memory / store lacks the async reader** — fail open (no
veto); FN-6461 + sibling guard remain the safety net. ✅
- **user-paused / autoMerge:false / approval-blocked** — defers to
FN-7514 human-control; never fights those semantics. ✅
- **Timeline entry filtering** — only `stage=executor` +
`action=observe` entries count as signals (retry/escalate messages
ignored). ✅

## Test evidence

`pnpm --filter @fusion/engine exec vitest run
src/__tests__/overseer-noop-finalize-veto.test.ts
src/__tests__/merger-ai.test.ts --silent=passed-only --reporter=dot`

```
Test Files  2 passed (2)
     Tests  48 passed (48)
```

Covers: derivation (failed→veto, failed-then-green→no-veto,
non-executor/non-observe ignored, empty→null); evaluator (veto, green,
no-memory, non-empty never-vetoed, user-paused defer, autoMerge:false
defer, missing-task fail-open); and an engine integration test driving
an **FN-8141-shaped** empty merge through `runAiMerge` → asserts
move-to-todo + `overseer:no-op-finalize-vetoed-failed-executor` audit
event + main untouched, plus the later-green case finalizing done.

`@fusion/core` builds clean (`pnpm --filter @fusion/core build`).

**Note on `pnpm verify:fast`:** it currently fails to build
`@fusion/engine`, but **only** in `auth-storage.ts` / `pi.ts` /
`provider-registration.ts` — the pre-existing pi-SDK breakage that *is*
this incident (pi 0.80.x removed
`AuthStorage`/`ModelRuntime`/`ModelRegistry`; tracked as FN-8145).
Verified identical errors with my changes stashed; **my diff touches
none of those files and adds zero new type errors** (tsc reports all
program errors before failing — none were in my files).

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

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

## Summary by CodeRabbit

- **Bug Fixes**
- Prevented zero-change tasks from being incorrectly finalized when the
latest executor attempt failed with unfinished work.
- Preserved task progress and returned affected tasks to **Todo** for
continued processing.
- Allowed finalization to proceed after a subsequent successful executor
result.
  - Maintained existing human-control and non-empty merge behavior.
  - Added audit visibility for blocked finalization events.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus <noreply@anthropic.com>
2026-07-16 20:29:46 -07:00
gsxdsm
f079245acc fix(engine): defensive getAuth guard on session routing seam + realign pi tests to ModelRuntime seam (#2261)
## What / Why

**Reworked after FN-8179 (`fd43a57a4`) landed on `main`.** FN-8179 did
most of what the original PR #2261 did — pinned `@earendil-works/pi-ai`
/ `pi-coding-agent` to `^0.80.10` everywhere, added the
`createSessionOptions` `NonNullable` typing in `pi.ts`, and regenerated
`pnpm-lock.yaml`. This PR was rebased onto current `origin/main` and
reduced to **only the unique residual not covered by FN-8179**.

### Residual change 1 — defensive `getAuth` guard

The FN-8142 migration rewrote `attachSessionRoutingHeaders` from the
`ModelRegistry.getApiKeyAndHeaders` seam to `ModelRuntime.getAuth`, but
dropped the pre-migration defensive invariant: a missing resolution
method must **not** break session creation. On `main` the function now
calls `modelRuntime.getAuth.bind(...)` unguarded, which throws if
`getAuth` is ever absent.

This restores the guard: no-op (warn) when `getAuth` is missing, so a
future pi rename degrades to un-tagged requests instead of a hard
failure at every agent start.

### Residual change 2 — test realignment (needed against current main)

FN-8179 aligned the SDK but did **not** update the two `#1675`
routing-header test suites, which still asserted the old
`getApiKeyAndHeaders` seam. **Verified RED on current `main` before
touching them:**

- `pi-create-fn-agent.test.ts` — **60 / 104 failing** (mock had no
`ModelRuntime` export; `createAgentSession` now receives
`modelRuntime`).
- `pi-session-routing-headers.test.ts` — **4 / 5 failing**
(`attachSessionRoutingHeaders` signature is `getAuth`, not
`getApiKeyAndHeaders`).

Both are realigned to the `ModelRuntime.getAuth` seam (the mock gains
the `ModelRuntime` export) → **109 / 109 green**. Assertions were
strengthened to the new behavior, not weakened; the #1675 precedence
invariant (taskId > pi session id > no-wrap), header merge,
apiKey/provider-header passthrough, failed/undefined passthrough, and
absent-method no-op are all still asserted.

## Surface enumeration

- **Routing-header seam**: both the `createFnAgent` path and the
`attachSessionRoutingHeaders` unit (taskId / pi-session-id / no-id
precedence; header merge; apiKey + provider-header passthrough;
failed/undefined resolution; absent-method no-op).
- **Both mock forms** in `pi-create-fn-agent.test.ts`: the top-level
`vi.mock` and all three `vi.doMock` skill-selection blocks now export
`ModelRuntime`.

## Test evidence

- `pnpm --filter @fusion/engine exec tsc --noEmit` → **exit 0**.
- `pnpm --filter @fusion/engine exec vitest run` on both suites →
**109/109 pass** (was 64 failing on main).
- `pnpm verify:fast` → **PASS** (typecheck + build scoped to changed
packages + CLI build + boot smoke `GET /api/health 200`; no tests run).

Do not merge without CI. No release performed.

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

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved session creation reliability when authentication support is
unavailable.
* Preserved existing authentication details while adding session-routing
headers when supported.
* Prevented session creation from failing when authentication
information cannot be resolved.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus <noreply@anthropic.com>
2026-07-16 20:20:59 -07:00
gsxdsm
ced0e84f5d FN-8184: unify heartbeat multiplier timing
Keep scheduler repair and reports health aligned with the effective heartbeat cadence.

- Apply heartbeatMultiplier once through shared interval calculations.
- Align scheduler repair and reports-health stale thresholds with scaled cadence.
- Add multiplier regression coverage and document the effective timing rules.

Files changed:
 .../fn-8184-heartbeat-multiplier-consistency.md    |   7 ++
 docs/agents.md                                     |   8 +-
 .../src/__tests__/heartbeat-executor.test.ts       |  43 +++++++++
 .../src/__tests__/heartbeat-scheduler.test.ts      |  46 ++++++++++
 packages/engine/src/agent-heartbeat.ts             | 101 ++++++++++++++++-----
 5 files changed, 176 insertions(+), 29 deletions(-)

Fusion-Task-Id: FN-8184

Fusion-Task-Lineage: 05b79c5e-444a-42c3-9073-9c8c05df5def

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 20:16:36 -07:00
gsxdsm
26cb0cc2e1 FN-8180: add Kimi K3 catalog and pricing support
Expose Kimi K3 throughout the native pi catalog and token-cost surfaces.

- Add K3 pricing and normalized lookup coverage for Dashboard cost calculations.
- Verify native K3 registry, API model route, and repeat-stop behavior end to end.
- Add a minor release changeset for Kimi K3 model selection support.

Files changed:
 .changeset/fn-8180-pi-sdk-kimi-k3.md               |  7 +++
 packages/core/src/__tests__/model-pricing.test.ts  |  7 ++-
 packages/core/src/model-pricing.ts                 | 17 +++++-
 ...ister-model-routes-kimi-k3-supplemental.test.ts | 70 ++++++++++++++++++++++
 .../pi-prompt-session-and-check-recursion.test.ts  | 15 +++++
 .../src/__tests__/provider-registration.test.ts    | 17 ++++++
 6 files changed, 130 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-8180
Fusion-Task-Lineage: 07598d73-c866-4329-b5cf-3aebc1c0a180
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 19:48:11 -07:00
gsxdsm
136958fc60 fix(engine): stranded-completed promoters withhold tasks whose last execution ended in a failure park (#2257)
## What & why

FN-8141 laundered a failed task into `done`. The executor correctly
parked the task `failed` ("task parked failed during no-fn_task_done
retry" / "fn_task_done refusal retry budget exhausted"), the pause-abort
machinery bounced it to `todo`, and ~12 minutes later
`recoverStrandedCompletedTodoTasks` promoted it to `in-review` because
every step was done/skipped — overriding the honest failure park. From
there the AI merger found an empty diff and finalized it as a no-op
`done`, with no reviewer ever seeing it.

Existing exclusions (`task.error`,
`evaluateNoCommitsNoOpFinalize().blocked`, active statuses, refreshing
review state) all missed it because the failure provenance lived **only
in the durable task log** by the time the promoter ran — status/error
had been cleared by the pause-abort bounce.

This PR restores the invariant: **a stranded-completed promoter must not
promote a task whose most recent execution lifecycle ended in a
failure/refusal park.**

## Change

- New pure, unit-testable evaluator
`evaluateCompletedPromotionFailureProvenance(task)` in `@fusion/core`
(next to `no-commits-finalize-guard.ts`). It scans the task-log **tail**
(bounded to 250 entries) and lets the **most-recent execution-outcome
marker** decide: a failure/refusal park → `{ blocked: true, reason:
"failure-provenance" }`; a fresh clean completion (`Task marked done by
agent` / `All steps complete — implicit fn_task_done`) that appears more
recently supersedes an earlier park; zero failure markers → not blocked.
Recency is by construction, so a failure that predates a newer clean
execution is never reached.
- Both self-healing sweeps (`recoverCompletedTasks` stuck-in-progress
**and** `recoverStrandedCompletedTodoTasks` stranded-todo) fetch the
full task for candidates that already cleared the cheap slim filters
(slim listings strip `log`) and skip when blocked, emitting a
**deduped** `task:reconcile-stranded-completed-no-action` run-audit
event (ids/outcomes-only: `taskId`, `reason`, `sweep`, `marker?`).
- Defense-in-depth: the shared executor `recoverCompletedTask`
chokepoint — which the sweeps AND the executor's own
unpause/`resumeOrphaned` fast-paths all funnel through — also refuses a
provenance-blocked promotion, so no route can launder a failed park.

**Escape hatch (documented in FNXC comments):** an operator
retrying/moving the task starts a fresh execution whose clean-completion
marker supersedes the failure park, clearing the block with no code
change.

## Surface enumeration

- `recoverCompletedTasks` (stuck-in-progress sweep, self-healing.ts) —
guarded + audited.
- `recoverStrandedCompletedTodoTasks` (stranded-todo sweep,
self-healing.ts) — guarded + audited. FN-8141 shows both columns can
launder.
- `recoverCompletedTask` executor callback (the route both sweeps +
unpause + `resumeOrphaned` share) — verified it did **not** check
log-based provenance; added the guard there as the final chokepoint.

## Test evidence

Pure-evaluator unit tests (`@fusion/core`) — marker detection,
most-recent-outcome recency, supersede-by-clean-completion,
empty/missing log, tail-scan bound:
```
pnpm --filter @fusion/core exec vitest run src/__tests__/completed-promotion-failure-provenance.test.ts
  Test Files  1 passed (1)   Tests  9 passed (9)
```

Self-healing integration tests (`@fusion/engine`) — FN-8141-shaped todo
(3 done + 2 skipped + refusal-exhaust/park marker) is NOT promoted and
emits the no-action event exactly once (deduped across a second cycle);
same task after a fresh clean execution IS promoted; stuck-in-progress
variant covered:
```
pnpm --filter @fusion/engine exec vitest run src/__tests__/self-healing.test.ts -t "recoverCompletedTasks|recoverStrandedCompletedTodoTasks|FN-8141"
  Test Files  1 passed (1)   Tests  14 passed | 382 skipped (396)
```

`@fusion/core` builds clean. My engine changes add **zero** new type
errors (verified: all 13 engine build errors are the pre-existing pi-SDK
cluster in `auth-storage.ts`/`pi.ts`/`provider-registration.ts`, none in
`self-healing.ts`/`run-audit.ts`/`executor.ts`/the new file).

## Known environmental blocker

`pnpm verify:fast` cannot go green on this branch: the `@fusion/engine`
build is **already broken at baseline** (confirmed by stashing all my
changes) by the pi 0.80.x SDK migration errors
(`ModelRegistry`/`AuthStorage`/`ModelRuntime`) — the exact FN-8145
upstream breakage described in the FN-8141 incident. That is out of
scope for this task and independent of this diff. Likewise, the 22
pre-existing
`restart.integration.test.ts`/`executor-fast-mode-workflows.test.ts`
failures are identical with and without my changes.

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

Co-authored-by: Claude Opus <noreply@anthropic.com>
2026-07-16 19:36:57 -07:00
gsxdsm
1c4fdb7b59 fix(engine): require already-landed proof before finalizing an empty AI merge as done (#2259)
## What & why

Task FN-8141 laundered a failed task into `done`: its branch had no net
changes vs `main` **only because the executor reverted its own work five
times**, and the AI empty-merge lane took the "empty means
already-landed or nothing-to-do → finalize as no-op done" path, stamping
`mergeConfirmed: true` with no reviewer or operator sign-off.

This restores the invariant: **a commit-expected task that reaches the
empty AI-merge outcome must not finalize `done` without positive
evidence the work already landed.**

`packages/engine/src/merger-ai.ts` empty-outcome lane now, for a
commit-expected task (`noCommitsExpected !== true`), requires one of:
1. Durable recorded landing on the task
(`mergeDetails.mergeConfirmed`/`commitSha`).
2. A prior AI no-op finalization proof pair in the task log
(`hasPriorAiNoOpFinalizationProof`, FN-7261 shape).
3. The branch tip is an **ancestor of the integration branch**
(fast-forwarded / zero-ahead / already-integrated).
4. The already-on-main classifier (`detectAlreadyLandedOnMain`) finds a
distinct landing commit via a **strong** strategy
(`trailer`/`ancestry`/`patch-id`).

The classifier's weak `tree-equal`/`no-diff` strategies are
**deliberately rejected**: a branch that committed work then reverted it
back to base has a tree equal to `main` (main never advanced), which is
exactly the FN-8141 shape and would false-positive. Absent proof, the
task gets `task.error` set, emits run-audit
`task:empty-merge-finalize-blocked-no-landed-proof`
(ids/counts/outcomes-only), and is moved back to `todo` with progress
preserved — mirroring the existing FN-6461 blocked lane.

`noCommitsExpected === true` tasks are untouched (hardened separately in
the sibling Task 1). The non-empty landed path, group/PR routing, and
push-after-merge behavior are unchanged.

## Surface enumeration
- **Single-repo empty-outcome finalize (primary lane)** — guarded in
`runAiMerge`.
- **Workspace/multi-repo caller** — `landWorkspaceTask`'s all-empty
finalize is a second route. Already-landed sub-repos are proven up front
by `findProvenLandedCommit` and marked `status:"landed"`; when
`landedCount === 0` the guard re-checks each empty sub-repo's branch and
blocks the FN-8141 reverted shape (tip not an ancestor / branch
vanished) identically. (Note: the genuinely-integrated all-empty
workspace case already throws `missing-merge-confirmation` on
`mergeConfirmed:false`, so it never reached `done`; that pre-existing
path is left intact.)
- **Re-promotion ping-pong** — the blocked path sets `task.error`, and
`recoverStrandedCompletedTodoTasks` excludes any task with `task.error`,
so the promoter cannot re-promote the unchanged blocked task.
Regression-tested.

## Test evidence
Scoped tests (all green):
```
vitest run merger-ai.test.ts workspace-merger.test.ts  → 46 passed
vitest run self-healing.test.ts -t recoverStrandedCompletedTodoTasks → 4 passed
vitest run merger.test.ts merger-finalize-unproven.real-git → 20 passed
vitest run self-healing-workspace + workspace-merger-lease + workspace-merger-deps-resilient → 26 passed
```
New tests:
- merger-ai.test.ts: commit-expected empty (reverted) → blocked to todo
+ error + audit event, NOT done; empty + prior no-op proof → still no-op
done; empty + branch-ancestor-of-main → still no-op done;
noCommitsExpected empty → unchanged done path.
- workspace-merger.test.ts: all-empty (reverted) workspace → blocked to
todo + error, not done / not `task:merged`.
- self-healing.test.ts: a task blocked by this guard (all steps
done/skipped, `task.error` set) is NOT re-promoted by
`recoverStrandedCompletedTodoTasks`.

**`pnpm verify:fast` is red on this branch due to the pre-existing pi
SDK breakage** (`auth-storage.ts`/`pi.ts`/`provider-registration.ts` —
the FN-8142/FN-8145 `AuthStorage`/`ModelRegistry` removal that is the
root of the FN-8141 incident). Verified those identical build errors
reproduce with my changes stashed; this PR adds **zero** new type errors
(no build error is in `merger-ai.ts` or `run-audit.ts`).

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

Co-authored-by: Claude Opus <noreply@anthropic.com>
2026-07-16 19:36:41 -07:00
gsxdsm
19eb179473 fix(core): block empty-diff finalize of tasks with skipped steps — generalized FN-6461 guard (#2254)
## What & why

FN-8141 (\"Update pi SDK to latest and verify Kimi K3 end to end\") was
**laundered into `done` despite producing zero net changes**. The pi SDK
bump kept breaking `verify:fast`, the work was reverted 5×, and the
agent used the sanctioned skip affordance to mark **Testing &
Verification** and **Documentation & Delivery** `skipped`.
`isTaskComplete()` counts `skipped` as complete, so:

1. self-healing `recoverStrandedCompletedTodoTasks` promoted the todo
task to in-review (all steps done/skipped),
2. the AI merger saw an empty diff vs main → \"finalizing as no-op\" →
`done` with `mergeConfirmed:true`,
3. no reviewer ever saw it (skipped steps request no review; the
merge-review pass reviews an empty diff).

The only existing guard, `evaluateNoCommitsNoOpFinalize` (FN-6461),
missed it **twice**: it only fired when `noCommitsExpected === true`
(FN-8141 was commit-expected — the branch was empty because work was
*reverted*), and even then only blocked when `incomplete >= done`
(FN-8141 had 3 done vs 2 skipped).

## The fix

Generalize the guard (same exported name/shape — every finalize lane
keeps working) so a **zero-diff/no-op finalize is blocked whenever ANY
step is `skipped`**:

- a **verification-flavored** skipped step (name matching
`/test|verif|qa|review/i`) blocks **unconditionally**;
- any **other** skipped step blocks **unless** every non-skipped step is
`done` **AND** the task is the legacy `noCommitsExpected` ops shape;
- the legacy FN-6461 ratio rule (`noCommitsExpected` + `incomplete >=
done`) is preserved for pending/in-progress incomplete work;
- blocked evaluations return a precise `reason` naming the skipped
steps.

Legitimate shapes still pass: all-done no-skip empty diffs (left to the
lineage-proof work), zero-step tasks, and `noCommitsExpected` ops tasks
with a minor non-verification skipped tail.

## Surface enumeration

The guard is the single chokepoint used at every zero-diff finalize
lane; all already honor `.blocked`/`.reason`, so the core change fixes
each surface:

- `packages/engine/src/merger-ai.ts` ~1116 — AI empty-merge lane
- `packages/engine/src/merger.ts` ~6261 / ~7354 / ~7658 — merger
empty-own-diff + no-op lanes
- `packages/engine/src/self-healing.ts` ~2851 — stranded-todo promoter
pre-check; ~6335 — no-op review finalize

Behavior on block is unchanged (error set, durable log entry,
`task:no-commits-finalize-blocked-incomplete-steps` run-audit event,
move back to todo with progress preserved).

## Test evidence

- **Core** `pnpm --filter @fusion/core exec vitest run
src/__tests__/no-commits-finalize-guard.test.ts` → **9 passed**. Covers
FN-8141 shape (3 done + 2 skipped, not noCommitsExpected → blocked),
verification-skip blocks regardless of ratio/`noCommitsExpected`, legacy
`noCommitsExpected` shapes, all-done no-skip → not blocked, zero steps →
not blocked.
- **Engine lanes** — one test per finalize-lane family, all green:
- `merger-ai.test.ts` (AI empty lane, incl. new FN-8141
reverted-commit-expected case) → **36 passed**
  - `merger-finalize-unproven.real-git.test.ts` (merger lanes) → passing
- `self-healing.test.ts` (stranded-todo promoter + no-op review
finalize, incl. new FN-8141 promoter case) → **394 passed**

### `pnpm verify:fast` — pre-existing engine build breakage (not this
PR)

`verify:fast` fails at the workspace-dist bootstrap because
`@fusion/engine` does **not** typecheck on `main`:
`src/auth-storage.ts`, `src/pi.ts`, `src/provider-registration.ts`
reference `ModelRuntime` / `AuthInteraction` / `CredentialInfo` /
private `ModelRegistry` members removed by pi 0.80.9/0.80.10 (the
FN-8142 migration that motivated this incident; upstream fix is
FN-8145). Verified this failure reproduces with my changes **stashed**
(13 identical tsc errors at clean HEAD). This PR touches only
`@fusion/core` (builds clean, `tsc` exit 0) and engine **test** files —
no engine source — so it neither causes nor can resolve that breakage.

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

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

## Summary by CodeRabbit

* **Bug Fixes**
* Prevented empty or no-op finalization when verification, QA, review,
or other required steps are skipped.
* Ensured tasks with skipped work are not incorrectly marked complete,
merged, or promoted during recovery.
* Improved error messages to identify skipped verification steps
blocking completion.
* **Tests**
* Added regression coverage across finalization, merge, and self-healing
workflows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus <noreply@anthropic.com>
2026-07-16 19:36:02 -07:00
gsxdsm
9a37415887 fix(engine): add honest blocked exit to fn_task_done so impossible tasks park failed instead of laundering to done (#2256)
## What & why

FN-8141 ("Update pi SDK to latest and verify Kimi K3 end to end") was
impossible as specced — pi 0.80.x removed `AuthStorage`/`ModelRegistry`
APIs, so every SDK bump broke the build. The executor correctly reverted
its work and filed follow-up FN-8145 — but had **no sanctioned way to
end the task in a blocked state**. `fn_task_done` only expressed
success: the bulk-completion gate refused it, the requeue budget re-ran
the doomed task 5 times, and the only remaining affordance (mark every
step `skipped`, then complete) made `isTaskComplete()` return true.
Self-healing then promoted the "complete" todo to in-review and the AI
merger finalized the empty diff as `done`. **The honest path must be
cheaper than the laundering path.**

This adds a first-class **blocked** outcome to the executor's
`fn_task_done` tool.

## Change

- `fn_task_done` gains `outcome: "completed" | "blocked"` (default
`"completed"`), optional `blockedBy: string[]`, and `reason` (required
when blocked).
- `outcome="blocked"` runs **before** every completion gate (completion
blocker, verdict providers, worktree invariants, bulk-completion
refusal) — blocked is not a completion claim, so none of those gates
apply.
- Parks the task `failed` with `error = "BLOCKED: <reason>"`, following
the FN-7863 `EXECUTION_DISPATCH_LOOP_EXHAUSTED` park convention: **steps
keep their true statuses** (no auto-done, no auto-skip), worktree/branch
preserved. It does **not** call `onDone()`, so the executor's existing
`status === "failed"` post-loop branch honors the park instead of
handing off to review.
- `blockedBy` is recorded as real `task.dependencies` edges (unioned
with existing) so the task requeues behind the blocker.
- Emits run-audit `task:execution-blocked-parked` with ids/outcomes-only
metadata (`taskId`, `blockedBy` ids, `hasReason` boolean — **never** the
reason prose).
- Executor + core prompt guidance and the
`bulk-step-completion-without-review` refusal message now name the
blocked exit as **the** correct action when work cannot proceed,
replacing skip-and-done. `PREMISE STALE:` skip guidance is preserved for
genuinely-stale premises.

## Surface enumeration

- **fn_task_done tool schema + handler**
(`packages/engine/src/executor.ts`): blocked branch added at the top of
`execute`, before all gates.
- **Refusal/requeue machinery**: `formatTaskDoneRefusal` for
`bulk-step-completion-without-review` now points at the blocked exit;
the requeue-budget path is untouched (blocked never enters it).
- **Executor prompt text**: turn-ending rules, the "Cannot proceed"
section, the preflight/stale-premise escape hatch (now explicitly
distinguishes stale-premise skip from blocked).
- **Core prompt mirror** (`packages/core/src/agent-prompts.ts`): same
turn-ending + cannot-proceed guidance.
- **Tool reference doc**
(`packages/cli/skill/fusion/references/engine-tools.md`): `fn_task_done`
params updated. (grep for `fn_task_done` confirmed the only executable
tool schema is in executor.ts; CLI/pi surfaces re-export it, no separate
schema copy.)
- **Self-healing**: verified a blocked-parked row is NOT auto-recovered
by `recoverStrandedCompletedTodoTasks` — its steps are not all
done/skipped and `task.error` is set (both are hard filters in the
sweep).
- **Run Audit inventory** (`AGENTS.md`): documented the new event.

## Test evidence

New `packages/engine/src/__tests__/executor-task-done-blocked.test.ts`
(8 tests) asserts the invariant across surfaces:

```
pnpm --filter @fusion/engine exec vitest run \
  src/__tests__/executor-task-done-blocked.test.ts \
  src/__tests__/executor-task-done-invariant.test.ts \
  src/__tests__/gating-classifications.test.ts \
  src/__tests__/reliability-interactions/execute-requeue-loop-guard.test.ts --reporter=dot
→ Test Files 3 passed | Tests 138 passed (0 failed)
```

Coverage: blocked parks failed with `BLOCKED:` error and does **not**
trip the bulk-completion refusal or requeue to todo; `blockedBy` unioned
into `dependencies`; `task:execution-blocked-parked` emitted with
metadata that excludes the reason prose; steps left untouched; empty
`reason` rejected without parking; `completed` outcome unchanged (still
marks steps done, no blocked audit); and
`recoverStrandedCompletedTodoTasks` never promotes a blocked-parked row.

### Note on `pnpm verify:fast`

`verify:fast` currently fails at the workspace build step due to
**pre-existing** type errors in `packages/engine/src/auth-storage.ts`,
`pi.ts`, and `provider-registration.ts` — the exact FN-8142 pi SDK API
break that FN-8145 will fix. These are present on the base branch and
untouched by this PR. Verified instead that this change introduces
**zero** new type errors (`tsc` diff before/after, engine and core both
clean) and that all scoped tests are green.

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

Co-authored-by: Claude Opus <noreply@anthropic.com>
2026-07-16 19:35:37 -07:00
gsxdsm
fd43a57a41 FN-8179: align pi SDK versions with ModelRuntime API
Align workspace pi SDK dependencies with the ModelRuntime API required by the engine.

- Pin pi AI and coding-agent packages to 0.80.10 across workspace consumers.
- Keep session option typing compatible with the updated SDK contract.
- Add a patch changeset and regenerate the dependency lockfile.

Files changed:
 .changeset/fn-8179-pi-sdk-align.md                 |   7 +
 packages/cli/package.json                          |   4 +-
 packages/core/package.json                         |   2 +-
 packages/dashboard/package.json                    |   2 +-
 packages/engine/package.json                       |   4 +-
 packages/engine/src/pi.ts                          |   8 +-
 packages/pi-claude-cli/package.json                |   8 +-
 .../src/thinking-config.ts                         |   9 +-
 pnpm-lock.yaml                                     | 947 +++++++++++----------
 pnpm-workspace.yaml                                |   5 +
 10 files changed, 520 insertions(+), 476 deletions(-)

Fusion-Task-Id: FN-8179

Fusion-Task-Lineage: aef45c2e-f353-4014-93de-44be91f43293

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 19:01:03 -07:00
gsxdsm
b687cc994e FN-8174: preserve live triage planning sessions
Keep active planning sessions protected from stale recovery while reclaiming genuinely hung triage work.

- Retain stale processing entries that still have a live, non-aborted triage session.
- Continue evicting no-session and stuck-aborted tasks so recovery can proceed.
- Add triage and self-healing regression coverage, architecture guidance, and a patch changeset.

Files changed:
 .changeset/fn-8174-planning-premature-todo.md      |   7 ++
 docs/architecture.md                               |   1 +
 packages/engine/src/__tests__/self-healing.test.ts | 102 +++++++++++++++++++++
 packages/engine/src/__tests__/triage.test.ts       |  37 +++++++-
 packages/engine/src/triage.ts                      |  48 +++++-----
 5 files changed, 168 insertions(+), 27 deletions(-)

Fusion-Task-Id: FN-8174

Fusion-Task-Lineage: f6811d72-95b4-4b5f-a71f-212f50e3ecdd

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 18:54:19 -07:00
gsxdsm
5a60643c0a FN-8142: migrate auth storage and model runtime to pi SDK
Migrate Fusion's credential and model integrations to pi SDK 0.80.8+.

- Replace legacy AuthStorage initialization with a locked Fusion credential store and ModelRuntime-backed registry.
- Wire asynchronous model initialization and refresh through CLI, desktop, dashboard, executor, and provider paths.
- Update provider, routing, and registry tests for the new SDK contracts.

Files changed:
 packages/cli/src/commands/__tests__/daemon.test.ts |   2 +-
 .../cli/src/commands/__tests__/dashboard.test.ts   |   9 +-
 .../cli/src/commands/__tests__/onboard.test.ts     |   1 +
 packages/cli/src/commands/__tests__/serve.test.ts  |   2 +-
 packages/cli/src/commands/daemon.ts                |  19 +-
 packages/cli/src/commands/dashboard.ts             |  20 +-
 packages/cli/src/commands/onboard.ts               |   6 +-
 packages/cli/src/commands/serve.ts                 |  19 +-
 packages/cli/src/commands/startup-model-sync.ts    |   4 +-
 packages/core/src/__tests__/openai-models.test.ts  |  17 +-
 ...-model-routes-openai-codex-supplemental.test.ts |  17 +-
 ...register-model-routes-zai-real-registry.test.ts |  15 +-
 packages/dashboard/src/routes.ts                   |  12 +-
 .../dashboard/src/routes/register-model-routes.ts  |   2 +-
 packages/desktop/src/local-runtime.ts              |   2 +-
 packages/desktop/src/local-server.ts               |   2 +-
 .../custom-providers-openai-completions.test.ts    |  16 +-
 .../custom-providers-openai-responses.test.ts      |  16 +-
 .../engine/src/__tests__/executor-test-helpers.ts  |   2 +-
 .../src/__tests__/pi-create-fn-agent.test.ts       |   8 +-
 .../engine/src/__tests__/pi-layers-wiring.test.ts  |   2 +-
 packages/engine/src/__tests__/pi.test.ts           |  47 ++---
 .../src/__tests__/provider-registration.test.ts    |  17 +-
 packages/engine/src/auth-storage.ts                | 218 ++++++++++++++++++---
 packages/engine/src/custom-provider-registry.ts    |  14 +-
 packages/engine/src/executor.ts                    |  15 +-
 packages/engine/src/pi.ts                          |  50 +++--
 packages/engine/src/provider-auth.ts               |  58 +++---
 packages/engine/src/provider-registration.ts       |  16 +-
 29 files changed, 421 insertions(+), 207 deletions(-)

Fusion-Task-Id: FN-8142

Fusion-Task-Lineage: 8ae79064-7820-4976-9645-9431b5a3129e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 18:13:37 -07:00
gsxdsm
ca7a5a7106 FN-8144: remove workspace worktrees on archive
Archive workspace task worktrees synchronously and safely across archive entry points.

- Add store-scoped workspace disposal planning, reservations, and quarantine handling.
- Install baseline and executor disposers that remove per-repository worktrees and branches without shell interpolation.
- Cover disposal-plan deduplication and document the archive cleanup behavior.

Files changed:
 .../fn-8144-archive-removes-workspace-worktrees.md |   7 ++
 AGENTS.md                                          |   1 +
 docs/task-management.md                            |   4 +
 .../archive-removes-workspace-worktrees.test.ts    |  59 +++++++++++
 packages/core/src/archive-worktree-disposer.ts     |  52 ++++++++++
 packages/core/src/index.gate.ts                    |   8 ++
 packages/core/src/index.ts                         |   8 ++
 .../core/src/task-store/archive-lifecycle-2.ts     |  29 ++++--
 packages/core/src/task-store/archive-lifecycle.ts  | 114 ++++++++++++++++++++-
 .../src/archive-worktree-disposer-install.ts       |  27 ++++-
 packages/engine/src/executor.ts                    |  25 ++++-
 11 files changed, 319 insertions(+), 15 deletions(-)

Fusion-Task-Id: FN-8144

Fusion-Task-Lineage: 1c4b65f3-a1d2-4a5c-a4b6-c263f9e6f61d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 17:05:29 -07:00
gsxdsm
ee28f5e45e FN-8155: test triage retry title handling
Cover blank-title behavior across retryable triage failures.

- Verify deterministic validation retries retain blank task titles
- Verify transient failures retain blank task titles while retries remain

Files changed:
 packages/engine/src/__tests__/triage.test.ts | 81 ++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

Fusion-Task-Id: FN-8155

Fusion-Task-Lineage: d8b4414d-b105-4251-bc42-d02a6d84bf3a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 16:38:25 -07:00
gsxdsm
c6be0b158b FN-8129: centralize database backup settings
Move database backup policy and scheduling to shared global configuration.

- Split project memory backups from cluster-wide database backup settings.
- Migrate legacy backup values and routines safely into central global storage.
- Schedule and dispatch one shared PostgreSQL backup routine across project engines.

Files changed:
 .changeset/fn-8129-backup-settings-scope-split.md  |   7 +
 docs/dashboard-guide.md                            |   2 +
 docs/settings-reference.md                         |  10 +-
 packages/cli/src/commands/backup.ts                |   3 +-
 .../__tests__/backup-settings-migration.test.ts    |  50 ++++++
 .../src/__tests__/backup-settings-scope.test.ts    |  27 +++
 packages/core/src/backup-settings-migration.ts     | 188 +++++++++++++++++++++
 packages/core/src/backup.ts                        |  77 +++++----
 packages/core/src/global-routine-store.ts          | 104 ++++++++++++
 packages/core/src/index.gate.ts                    |   6 +-
 packages/core/src/index.ts                         |   6 +-
 .../core/src/postgres/migrations/0000_initial.sql  |  19 +++
 .../postgres/migrations/0015_global_routines.sql   |  19 +++
 packages/core/src/postgres/schema-applier.ts       |  19 ++-
 packages/core/src/postgres/schema/central.ts       |  21 ++-
 packages/core/src/postgres/startup-factory.ts      |  11 ++
 packages/core/src/settings-schema.ts               |  14 +-
 packages/core/src/types.ts                         |  31 +++-
 .../dashboard/app/components/SettingsModal.tsx     |  10 +-
 .../settings/__tests__/section-keys.test.ts        |   1 +
 .../app/components/settings/save-split.ts          |   2 +
 .../search/__tests__/settings-search-index.test.ts |   1 +
 .../settings/search/entries.ts                     |   2 +
 .../app/components/settings/section-keys.ts        |   4 -
 .../settings/sections/BackupsSection.search.ts     |  40 -----
 .../settings/sections/BackupsSection.tsx           | 112 +-----------
 .../sections/DatabaseBackupsSection.search.ts      |  51 ++++++
 .../settings/sections/DatabaseBackupsSection.tsx   | 142 ++++++++++++++++
 .../settings-default-descriptions.test.tsx         |   1 +
 packages/dashboard/src/routes.ts                   |  12 +-
 .../src/routes/register-settings-memory-routes.ts  |  41 ++---
 .../engine/src/__tests__/routine-scheduler.test.ts |  55 +++++-
 packages/engine/src/cron-runner.ts                 |   4 +-
 packages/engine/src/routine-runner.ts              |  67 +++++---
 packages/engine/src/routine-scheduler.ts           |  35 +++-
 35 files changed, 929 insertions(+), 265 deletions(-)

Fusion-Task-Id: FN-8129

Fusion-Task-Lineage: af17f39a-7f1c-40ff-8a4a-cd63895cd532

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 16:34:09 -07:00
gsxdsm
478f226a54 test: green full-suite CI after main drift (#2229)
## Summary
Restores green **Full Suite (non-blocking)** runs on `main`. Recent main
merges left i18n key parity, schema baseline bookkeeping (0011→0012),
heartbeat tool inventory (FN-8058 `fn_task_logs_read`), and merger
whitespace-classification mocks (execFile `git diff -p -w :2: :3:`) out
of date, so all four test shards failed.

## Root causes observed on main
- **Shard 4 / `@fusion/i18n`**: missing `skipConfirmationDialogs*` +
`reviewBudgetExhausted` in non-en locales; orphan
`awaitingApprovalPlanReviewReplanCap`
- **Shard 3 / `@fusion/core`**: `SCHEMA_BASELINE_VERSION` advanced to
`0012` while tests still equated it with
`OWNER_PROJECT_ID_SPLIT_VERSION` (`0011`) and omitted `0012` from
applied-migration lists
- **Shards 1–2 / `@fusion/engine`**: tool count/snapshot drift for
`fn_task_logs_read`; merger tests still mocked `git diff-tree` for
trivial classification after the execFile `:2:`/`:3:` cutover; mock
provider `updateTask` arity drift

## Changes
- Locale catalogs: add missing keys, drop orphan key
- Schema applier tests: immutable 0011 identity + baseline 0012 lists
- Heartbeat + gating snapshots: include `fn_task_logs_read`
- Merger unit mocks: recognize `git diff -p -w :2:path :3:path`
- Mock provider: accept optional third `updateTask` arg

## Test plan
- [x] `pnpm --filter @fusion/i18n exec vitest run` — 23/23
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/postgres/schema-applier.test.ts` (immutable + automation
upgrade) — pass
- [x] `pnpm --filter @fusion/core exec vitest run` project-identity +
satellite-fusiondir — pass
- [x] Engine suites from failed CI shards (file-scoped,
hermes/openclaw/paperclip/grok, reliability post-finalize/mission,
heartbeat, gating, merger recovery/prompt, mock-provider, etc.) — pass
- [ ] Full Suite workflow green on merge to main

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

- **New Features**
  - Improved project data isolation across backend operations.
- Added safer optional toast handling when UI components render outside
the full application shell.
  - Added support for reading task logs during agent heartbeat sessions.

- **Bug Fixes**
- Prevented runtime probes from hanging and avoided scanning large
binary files.
  - Improved path handling for workspaces with missing descendants.
- Corrected task retry state resets and GitHub import/issue-close
behavior.

- **Style**
  - Improved chat, terminal, and settings spacing.
  - Added clearer accessibility labeling for the auto-merge control.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 16:01:29 -07:00
gsxdsm
06a5fd813d refactor: package code organization waves 6–7 (#2166)
## Summary

Waves 6–7 of package code organization (plan:
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`),
after #2148.

### Wave 6
| New module | Parent |
|---|---|
| `merger-autostash-labels.ts` | `merger.ts` |
| `types/agent-state.ts` | `types.ts` |
| `app/api/tasks-lifecycle.ts` | `legacy.ts` |
| `task-store/task-row-mappers.ts` | `remaining-ops-3.ts` (rename) |

### Wave 7
| New module | Parent |
|---|---|
| `self-healing-optional-step-revision.ts` | `self-healing.ts` |
| `self-healing-path-utils.ts` | `self-healing.ts` |
| `merger-git-parse` (+ `quoteArg`, `getBranchChangedFiles`) |
`merger.ts` |
| `app/api/settings.ts` | `legacy.ts` |

Public import paths stay stable via re-exports.

## Test plan
- [x] engine + dashboard typecheck (incl. app)
- [x] eslint on touched modules
- [x] merger-autostash / parse-porcelain / getBranchChanged / api-tasks
- [ ] CI merge gate

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

* **New Features**
* Added a dedicated task lifecycle API client (task promotion, merge,
retry/reset/duplicate, pause/unpause, archive/unarchive, revert, plan
approve/reject) plus branch-group and planner oversight actions.
* Added a settings/config API service (effective task settings, update
check/refresh/install).
* Introduced standardized agent lifecycle states with identity/ephemeral
detection helpers.
* **Bug Fixes**
* Improved autostash label compatibility and NUL-delimited changed-file
detection for branch diffs.
* **Refactor**
* Modularized merger labeling/parsing, self-healing helpers, and
lifecycle/type wiring while keeping behavior consistent.
* **Tests**
  * Updated merger verification tests for `git diff -z` output handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 15:42:22 -07:00
gsxdsm
edc64138e0 feat(worktrees): task-pinned worktrees under worktreeNaming "task-id" (#2233)
## Summary

Adds **task-pinned worktrees** for `worktreeNaming: "task-id"`. Under
task-id naming, a task is pinned to exactly one derivable directory
`<worktreesDir>/<lowercased-task-id>` (e.g. `.worktrees/fn-7996`) for
its entire lifecycle — removing the ambiguity that let stale/foreign
`task.worktree` pointers strand a task (the FN-7996 shape).

`recycleWorktrees` stays fully functional and is **mutually exclusive**
with task-id pinning: the two can't be enabled together.

## Behavior

- **Pinned acquisition (`worktreeNaming: "task-id"`, recycling off):**
`acquireTaskWorktree` runs **derive → validate → reuse-or-recreate** at
the derived path — warm-reuse when the dir is a registered, usable
worktree on the task's own branch; otherwise reclaim-in-place
(`removeWorktree` + recreate at the SAME path, never a sibling name). A
disagreeing `task.worktree` cache self-corrects and emits a new
`worktree:pin-rederived` audit event, without consuming worktree-session
retries. The recycle pool is never consulted in pinned mode.
- **Mutual exclusivity:** enabling both `recycleWorktrees` and
`worktreeNaming: "task-id"` is rejected at the settings-write boundary —
HTTP 400 at `PUT /settings`, and an `Error` backstop in
`store.updateSettings` covering the CLI and every other writer
(`assertWorktreeNamingRecycleExclusive`). The runtime also gates pinned
mode on `!recycleWorktrees`, so a legacy on-disk config carrying both
degrades safely to recycling.
- **Settings UI:** the Settings → Worktrees panel enforces the
exclusivity bidirectionally — the *Recycle worktrees* toggle is disabled
while naming is *Task ID*, and the naming select is disabled while
recycling is on — so the conflicting state is unreachable, with help
text explaining why.
- **Byte-inert for the rest:** `random`/`task-title` naming and the
recycle pool (incl. `merger.ts` release) are unchanged;
worktrunk-managed layouts bypass pinning.

## Acceptance criteria (from the plan)

1. ✅ Pinned task dispatched N times only ever touches
`<worktreesDir>/<task-id>` on its own branch
2. ✅ No code path can hand task A's dir to task B (pool bypassed; path
derived from task id)
3. ✅ FN-7996 stale/foreign `task.worktree` self-corrects at next
dispatch (`worktree:pin-rederived`) without consuming session retries
4. ✅ Non-pinned modes with `recycleWorktrees: true|false` are
byte-identical (existing pool tests pass unchanged)
5. ✅ Stale same-name dir (crash leftover / archive→restore) reclaimed in
place, never suffixed
6. ✅ Docs updated (settings-reference, architecture, `worktreeNaming`
type doc); changeset (`minor`, `feature`); FNXC comments encode the
invariant

## Files

- `packages/engine/src/worktree-pinning.ts` — new pure helpers
(`isTaskPinnedWorktreeNaming`, `pinnedWorktreePathForTask`)
- `packages/engine/src/worktree-acquisition.ts` — pinned branch +
branch-match reclaim-in-place
- `packages/engine/src/run-audit.ts` — `worktree:pin-rederived` audit
type
- `packages/core/src/settings-validation.ts` (+ `index.ts`,
`task-store/settings-ops.ts`) — mutual-exclusion validator + wiring
- `packages/dashboard/src/routes/register-settings-memory-routes.ts` —
400 on conflict
-
`packages/dashboard/app/components/settings/sections/WorktreesSection.tsx`
(+ `packages/i18n/locales/en/app.json`) — bidirectional UI exclusivity
- `packages/core/src/types.ts`, `docs/*`, `.changeset/*`

## Verification

- New tests: engine `worktree-pinning` (5) +
`worktree-acquisition-pinned` (7); core
`worktree-naming-recycle-exclusive` (2); dashboard settings-route 400
(3) + WorktreesSection UI exclusivity (3)
- Regression sweep green: 194 engine
worktree/acquisition/pool/executor/merger-release tests, core settings
tests, dashboard i18n/settings-section tests
- `tsc --noEmit` clean for `@fusion/core` and `@fusion/engine`; changed
source files clean; eslint clean
- `pnpm verify:fast` PASS (build + scoped typecheck + boot smoke)

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

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

## Summary by CodeRabbit

* **New Features**
* Added Task ID worktree naming, providing each task with a stable,
deterministic worktree directory.
* Automatically reuses valid pinned worktrees and recreates stale or
conflicting ones at the same path.
* Added clear settings controls and validation for incompatible Task ID
naming and worktree recycling options.

* **Documentation**
* Updated worktree architecture, settings reference, and in-app guidance
to explain pinned worktrees and configuration constraints.

* **Bug Fixes**
* Improved recovery from stale or incorrect worktree assignments without
consuming session retries.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:37:42 -07:00
gsxdsm
f57dfc03b6 FN-8105: remove archived task worktrees safely
Archive task worktrees through a store-scoped, race-safe disposal lifecycle.

- Reserve pinned worktree paths during archive cleanup and successor creation.
- Reconcile quarantined removals before reusing a pinned path.
- Gate PostgreSQL archival before destructive worktree disposal and wire CLI cleanup.

Files changed:
 .changeset/fn-8105-archive-removes-worktree.md     |   7 +
 docs/task-management.md                            |   4 +
 .../extension-experiment-finalize.test.ts          |   1 +
 .../src/__tests__/extension-fn-secret-get.test.ts  |   1 +
 .../extension-gitlab-tracking.test.ts              |   1 +
 .../cli/src/__tests__/extension-web-fetch.test.ts  |   1 +
 .../task-command-github-import-tracking.test.ts    |   1 +
 packages/cli/src/commands/__tests__/task.test.ts   |   1 +
 packages/cli/src/commands/task.ts                  |   8 +-
 packages/cli/src/extension.ts                      |   4 +
 .../__tests__/worktree-path-reservation.test.ts    |  58 ++++++++
 packages/core/src/archive-worktree-disposer.ts     |  21 +++
 packages/core/src/index.gate.ts                    |  13 ++
 packages/core/src/index.ts                         |  13 ++
 .../core/src/task-store/archive-lifecycle-2.ts     |   8 ++
 packages/core/src/task-store/archive-lifecycle.ts  |  37 +++++
 packages/core/src/worktree-path-reservation.ts     | 149 +++++++++++++++++++++
 .../src/archive-worktree-disposer-install.ts       |  18 +++
 packages/engine/src/executor.ts                    |  16 +++
 packages/engine/src/index.ts                       |   2 +
 packages/engine/src/runtimes/in-process-runtime.ts |   1 +
 packages/engine/src/worktree-acquisition.ts        |  27 +++-
 22 files changed, 388 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-8105
Fusion-Task-Lineage: cabb8f52-093f-4986-bfda-2c7601a72579
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 15:27:03 -07:00
gsxdsm
4e4b6be1b4 Plan-approval mailbox notification + triage Plan Review convergence (#2237)
## Summary

Two related changes around the plan-approval flow, plus a fix for
triage's plan-review loop that was stranding tasks at the replan cap.

### 1. Post a mailbox message when a plan needs approval (`feat`)
The ntfy push on `awaiting-approval` already existed end-to-end. This
adds the missing **durable, in-dashboard record**: a `system`-typed
mailbox message linking to the task, written whenever a task enters
`awaiting-approval`.
- Fires **before** the notifications-enabled gate, so a dashboard-only
operator (no ntfy/webhook configured) still gets the record — the whole
point of the mailbox channel.
- `system` type avoids re-triggering the `message:agent-to-user` ntfy
pipeline (no double-notify); idempotent via `sendMessageOnce` (key
`plan-approval:<taskId>`).

### 2. Help triage Plan Review converge before the replan cap (`fix`)
Investigation of three tasks that burned all 8 plan-review replans
without converging (**FN-7996, FN-8105, FN-8108**) found the reviewer
surfaced a *new, deeper* issue each cycle instead of confirming its
prior ones were fixed (goalpost movement), and reviewed specs at
implementation altitude. This addresses the root causes:
- **Feed the spec reviewer its own prior REVISE feedback + the 1-based
replan attempt** so it verifies prior issues rather than moving
goalposts. Gated to `reviewType === "spec"` and `attempt > 1` — **code
review and normal plan review are byte-for-byte unaffected**
(double-verified).
- **Reviewer prompt:** converge-on-re-review rule (don't REVISE for your
own earlier miss), severity ratchet (critical-only at attempt ≥ 3), and
a **Spec Altitude** guard so exact SQL/lock/CAS protocol design is
deferred to code review.
- **Planner prompt:** front-load exhaustive surface enumeration before
writing File Scope, and a storage-architecture ground-truth note
(Postgres-only store, composite PK `(project_id, id)`, `schema-applier`
migrations) to stop the repeated stale-fact REVISE rounds.

## Testing
- `@fusion/core` + `@fusion/engine` typecheck clean.
- Added coverage: reviewer spec-convergence wiring (attempt gating +
code/plan exclusion + severity ratchet), triage prior-feedback
derivation (incl. empty-output→notes fallback), mailbox decoupling
(fires when push disabled) + rejection safety, and assertions for all
new prompt sections.
- Affected suites green: notification-service, reviewer,
triage-plan-review-replan-cap, triage-replan-feedback, agent-prompts.
- `pnpm check:changesets` passes (2 changesets: `@runfusion/fusion`
minor + patch).

## Review
Ran a 6-persona `ce-code-review` (correctness + adversarial on Opus;
maintainability, testing, project-standards, api-contract). Guards
verified unbreakable; no P0/P1 correctness or security issues. Applied
the resulting fixes: decoupled the mailbox write from the push gate
(P2), `??`→`||` in the feedback derivation (P3), de-duplicated the
`specConvergence` ternary (P3), and closed the test-coverage gap the
review flagged.

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

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

- **New Features**
- Plans transitioning to **awaiting approval** now write an idempotent,
task-linked **dashboard mailbox** message (approval reason + direct task
link), even if push/notifications are disabled.
- **Bug Fixes**
- Plan Review **replan** behavior now better **converges** on prior
REVISE feedback (including notes fallback) and stops looping at the
replan cap.
- At later attempts (attempt 3+), **REVISE** is applied to **critical**
issues while lower-severity items shift to suggestions.
- **Tests**
- Added/expanded coverage for mailbox messaging and spec-convergence
prompt wiring.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:44:07 -07:00
gsxdsm
d870878a23 FN-7998: add executor alternate model escalation
Add opt-in executor escalation after same-model tool-failure retries are exhausted.

- Persist escalation settings and one-shot task state across SQLite and PostgreSQL stores.
- Retry once on a configured alternate model or scheduler node and audit escalation outcomes.
- Expose escalation controls, documentation, translations, migration, and regression coverage.

Files changed:
 .changeset/fn-7998-executor-escalation.md          |   7 ++
 AGENTS.md                                          |   1 +
 docs/settings-reference.md                         |  13 ++-
 .../core/src/__tests__/settings-defaults.test.ts   |  23 ++++-
 packages/core/src/in-review-stall.ts               |  29 ++++++
 packages/core/src/index.gate.ts                    |   3 +-
 packages/core/src/index.ts                         |   3 +-
 packages/core/src/manual-retry-reset.ts            |   1 +
 .../0014_executor_escalation_attempt.sql           |   2 +
 packages/core/src/postgres/schema-applier.ts       |  17 ++++
 packages/core/src/postgres/schema/project.ts       |   1 +
 packages/core/src/settings-schema.ts               |   4 +
 packages/core/src/store.ts                         |   2 +-
 packages/core/src/task-store/persistence.ts        |   2 +
 packages/core/src/task-store/remaining-ops-2.ts    |   2 +-
 packages/core/src/task-store/remaining-ops-3.ts    |   2 +-
 packages/core/src/task-store/remaining-ops-6.ts    |   2 +-
 packages/core/src/task-store/serialization.ts      |   1 +
 packages/core/src/task-store/task-update.ts        |   2 +
 packages/core/src/types.ts                         |  13 +++
 .../dashboard/app/components/SettingsModal.tsx     |  12 +++
 .../app/components/settings/section-keys.ts        |   4 +
 .../settings/sections/SchedulingSection.search.ts  |  36 +++++++
 .../settings/sections/SchedulingSection.tsx        |   6 ++
 .../settings-default-descriptions.test.tsx         |   4 +
 .../__tests__/executor-tool-failure-retry.test.ts  |  91 +++++++++++++++++-
 packages/engine/src/executor.ts                    | 104 +++++++++++++++++++--
 packages/i18n/locales/en/app.json                  |   8 ++
 28 files changed, 376 insertions(+), 19 deletions(-)

Fusion-Task-Id: FN-7998

Fusion-Task-Lineage: bbce767d-c61a-4667-be62-abc0cc54d8be

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 14:31:59 -07:00
gsxdsm
2c3a777bca FN-8132: recover bare worktree branch collisions
Recover safe worktree creation when a dangling task branch already exists.

- Classify bare branch collisions and preserve foreign or mixed commit history.
- Reuse task-owned branches or recreate merged branches from the pinned start point.
- Audit recovery outcomes and cover native, fallback, and workspace acquisition paths.

Files changed:
 .../fn-8132-worktree-branch-collision-recovery.md  |   7 ++
 docs/architecture.md                               |   1 +
 .../__tests__/worktree-acquisition-backend.test.ts |  69 +++++++++++
 .../worktree-acquisition-workspace.test.ts         |  21 ++++
 .../worktree-backend-branch-collision.test.ts      | 132 +++++++++++++++++++++
 packages/engine/src/branch-conflicts.ts            | 121 +++++++++++++++++++
 packages/engine/src/run-audit.ts                   |   1 +
 packages/engine/src/worktree-acquisition.ts        |   3 +-
 packages/engine/src/worktree-backend.ts            |  94 +++++++++++++++-
 9 files changed, 447 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-8132

Fusion-Task-Lineage: f09d140f-4fcd-48a6-99b1-a351630f37bd

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 13:48:00 -07:00
gsxdsm
60b6e3e048 FN-7996: add configurable executor tool-failure retries
Add bounded, durable same-model retry handling for qualifying consecutive executor tool errors.
- Persist retry claims, cursors, and audit markers with PostgreSQL migrations.
- Expose project retry count, backoff, and failure threshold settings in the dashboard.
- Cover retry, exhaustion, reset, and stale-run safety behavior with tests.

Files changed:
 .changeset/fn-7996-executor-tool-failure-retry.md  |   7 +
 AGENTS.md                                          |   1 +
 docs/architecture.md                               |   1 +
 docs/settings-reference.md                         |  10 ++
 .../executor-tool-failure-retry-claim.test.ts      |  17 +++
 .../core/src/__tests__/manual-retry-reset.test.ts  |   3 +
 .../core/src/__tests__/settings-defaults.test.ts   |  15 +-
 packages/core/src/in-review-stall.ts               |  20 +++
 packages/core/src/index.gate.ts                    |   6 +
 packages/core/src/index.ts                         |   6 +
 packages/core/src/manual-retry-reset.ts            |   3 +
 .../0013_executor_tool_failure_retry.sql           |   4 +
 packages/core/src/postgres/schema-applier.ts       |  17 +++
 packages/core/src/postgres/schema/project.ts       |   3 +
 packages/core/src/settings-schema.ts               |   3 +
 packages/core/src/store.ts                         |  10 +-
 packages/core/src/task-store/persistence.ts        |   7 +
 packages/core/src/task-store/remaining-ops-2.ts    |   2 +-
 packages/core/src/task-store/remaining-ops-3.ts    |   2 +-
 packages/core/src/task-store/remaining-ops-6.ts    |  65 ++++++++-
 packages/core/src/task-store/serialization.ts      |   3 +
 packages/core/src/task-store/task-update.ts        |   6 +
 packages/core/src/types.ts                         |  16 +++
 .../dashboard/app/components/SettingsModal.tsx     |  15 ++
 .../app/components/settings/section-keys.ts        |   3 +
 .../settings/sections/SchedulingSection.search.ts  |  27 ++++
 .../settings/sections/SchedulingSection.tsx        |   4 +
 .../settings-default-descriptions.test.tsx         |   3 +
 .../__tests__/executor-tool-failure-retry.test.ts  | 160 +++++++++++++++++++++
 packages/engine/src/executor.ts                    |  87 ++++++++++-
 packages/i18n/locales/en/app.json                  |   6 +
 31 files changed, 523 insertions(+), 9 deletions(-)

Fusion-Task-Id: FN-7996
Fusion-Task-Lineage: d1682ef8-534c-410e-b74c-1f2cf176eac2
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 13:41:14 -07:00
gsxdsm
bd4857a44e FN-8136: update cron-runner backup error expectations
Align cron-runner backup failure assertions with the current PostgreSQL project-state error wording.

- Update legacy command backup failure expectation
- Update command-step backup failure expectation

Files changed:
 packages/engine/src/__tests__/cron-runner.test.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-8136

Fusion-Task-Lineage: 8f182491-1ded-43bb-b4ca-02485dce656a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 13:17:00 -07:00
gsxdsm
fdea8a4294 FN-8118: document continuation rescue test coverage
Document why the verified post-done continuation rescue suite remains unquarantined.

- Record the serialized in-memory reliability coverage and its exclusion rationale.
- Preserve the engine-default reliability partition exclusion.

Files changed:
 packages/engine/vitest.config.ts | 2 ++
 1 file changed, 2 insertions(+)

Fusion-Task-Id: FN-8118

Fusion-Task-Lineage: 0ac7f24a-888c-470c-b175-98b4e0411061

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 12:42:17 -07:00
gsxdsm
2142841f0c FN-8111: restore reliability test coverage
Restore PostgreSQL-compatible reliability coverage and prevent completed tasks from wedging on stale continuation recovery.

- Update reliability fixtures and audit assertions for PostgreSQL-backed stores
- Prioritize completed-task handling before stale assistant-continuation retries
- Unquarantine the restored meta-archive and continuation reliability suites

Files changed:
 .../explicit-duplicate-marker-sweep.test.ts        |  4 ++++
 .../meta-archive-guard-composition.test.ts         | 26 +++++++++++++++++-----
 .../post-done-continuation-no-wedge.test.ts        |  3 ++-
 packages/engine/src/executor.ts                    |  7 ++++++
 packages/engine/vitest.config.ts                   |  4 ++--
 scripts/lib/test-quarantine.json                   | 10 ---------
 6 files changed, 36 insertions(+), 18 deletions(-)

Fusion-Task-Id: FN-8111

Fusion-Task-Lineage: 8b30b5cb-c160-44e1-8e8c-dd58f4877edc

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 12:09:55 -07:00
gsxdsm
4a797d3804 FN-8117: restore explicit duplicate marker sweep coverage
Configure duplicate-marker PG fixtures with canonical FN task IDs so the sweep coverage exercises real deletion paths.

- Set taskPrefix to FN for duplicate-marker reliability fixtures.
- Remove the corrected test from the PG quarantine ledger and Vitest exclusions.
- Document why valid marker IDs are required for this coverage.

Files changed:
 .../explicit-duplicate-marker-sweep.test.ts          | 20 +++++++++++++-------
 packages/engine/vitest.config.ts                     |  4 +++-
 scripts/lib/test-quarantine.json                     |  5 -----
 3 files changed, 16 insertions(+), 13 deletions(-)

Fusion-Task-Id: FN-8117

Fusion-Task-Lineage: 3b09cbbe-924c-4e3c-849b-cf7643b0ac0e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 11:36:32 -07:00
gsxdsm
0b488523c2 FN-8104: retire legacy SQLite database fallbacks
Retire legacy SQLite database calls from PostgreSQL-only startup and self-healing paths.

- Route plugin schema initialization exclusively through the PostgreSQL executor.
- Delegate soft-delete column repair to the PostgreSQL reconciliation seam.
- Remove temporary getDatabase allowlist entries and add no-SQLite regression coverage.

Files changed:
 .../postgres/store-safe-defaults.pg.test.ts        | 14 +++++-
 packages/core/src/store.ts                         | 24 ++++------
 .../engine/src/__tests__/plugin-runner.test.ts     |  6 ---
 .../self-healing-fake-overlap-seam.test.ts         | 44 +++++++++++++++++++
 packages/engine/src/self-healing.ts                | 51 +++++-----------------
 scripts/lib/getdatabase-allowlist.json             | 17 +-------
 6 files changed, 77 insertions(+), 79 deletions(-)

Fusion-Task-Id: FN-8104

Fusion-Task-Lineage: 88dee027-51c3-4c68-94dc-88191fe20330

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 10:33:47 -07:00
gsxdsm
aa07a78f18 fix: recover graph-node missing-worktree failures instead of terminal-parking (FN-7996) (#2231)
## Why

FN-7996 sat in a dispatch→park loop **all day** (06:06→16:35): its
`worktree` metadata pointed at recycled pool worktrees (`coral-badger`,
`grand-ridge` — the latter actually belonged to FN-8069), Plan Review
refused to start in the missing directory, and the task terminal-parked
`failed` every cycle while the planner overseer blindly retried.

Root-cause chain:

1. **`graphFailureValue()` couldn't read optional-group results.**
`runOptionalGroup` publishes context under the group id
(`node:plan-review:value`) and the unqualified template id, but the
failed node is recorded as the materialized
`plan-review::plan-review-step` — the lookup only understood `#` foreach
ids. FN-7977's provider-failure hold *did* classify this failure, but
its hold value was invisible to routing.
2. **No graph-failure router handled the `assertValidWorktreeSession`
refusal**, so it fell to the terminal sink, which parked the task and
*overwrote* `task.error` with a generic message — erasing the signature
the missing-worktree self-healing sweep (in-review-only anyway)
classifies on.
3. **Plan Review didn't need the worktree at all** — its spec is
store-injected (FN-7561) — yet it launched its reviewer in whatever
stale `task.worktree` said.

## What

- `handleGraphFailure` routes unusable-worktree node failures (any node,
any error key, `::`/`#` materialized ids) into the existing bounded
worktree-session recovery: clear stale worktree/branch/session metadata,
requeue to todo, budgeted by `worktreeSessionRetryCount`. An exhausted
budget still falls through to the visible terminal park for human
inspection.
- `graphFailureValue` resolves `group::template` ids (group value first
— it carries post-classification routing intent — then the unqualified
template value). Foreach `#` behavior unchanged.
- Plan Review falls back to the repo root when its recorded worktree is
missing on disk; other read-only gates intentionally keep failing fast
into the new recovery (silently retargeting them to root would review
the wrong tree).
- `recoverMissingWorktreeSessionStartFailure` returns its outcome so the
graph router can distinguish requeue from escalate-exhausted; existing
truthy callers unchanged.

## Symptom Verification

- **Original symptom:** graph-node session-start refusal → `Workflow
graph terminated with failure at node 'plan-review::plan-review-step'`,
task parked failed with stale metadata intact, no recovery.
- **Reproduction:** `graph-node-missing-worktree-recovery.test.ts`
drives `handleGraphFailure` with the exact FN-7996 result shape
(optional-group materialized id + `Refusing to start coding agent in
missing worktree` node error).
- **Assertion it is gone:** the task is requeued to `todo` with
`worktree`/`branch`/`sessionFile` cleared and retry budget incremented —
and is *not* marked `failed`; budget exhaustion still parks visibly.

## Surface Enumeration

- Optional-group template nodes (Plan Review — the repro), write-capable
review gates, and any custom graph node: covered by the
`handleGraphFailure` router (scans exact/materialized/unqualified
`:error` keys).
- Execute-seam session start: already covered by the pre-existing
recovery (unchanged, still passes).
- In-review / merge-active columns: already covered by self-healing
sweeps (unchanged).
- Paused / user-paused / deleted / done tasks: explicitly left to their
owning machinery (guard tests).
- Budget exhaustion: falls through to the visible terminal park (test).

## Testing

- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/reliability-interactions/graph-node-missing-worktree-recovery.test.ts`
— 13 passed
- Adjacent suites (`worktree-incomplete-session-start`,
`executor-graph-requeue-gate`, `workflow-graph-optional-group`,
`executor-paused-abort-todo-benign`) — 78 passed
- `tsc --noEmit` on `@fusion/engine` — clean

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

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved recovery when workflow tasks encounter missing or recycled
worktrees.
* Automatically retries affected tasks with stale worktree details
cleared, up to the configured retry limit.
  * Escalates tasks after recovery attempts are exhausted.
* Improved failure routing for optional workflow groups and template
instances.
* Plan Review now falls back to the repository root when its recorded
worktree is unavailable.
* **Tests**
* Added regression coverage for recovery, routing, retry limits, and
repository-root fallback behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 10:28:11 -07:00
gsxdsm
1a337df5e9 FN-8098: add executor model fallback
Add workflow-specific executor fallback configuration and bounded recovery.

- Add executor fallback provider, model, and thinking settings across core schemas and settings UI.
- Route executor, heartbeat, child, and workflow-step sessions through the executor fallback resolver.
- Retry the primary model after fallback failure before reporting terminal exhaustion.
Files changed:

 .changeset/fn-8098-model-fallback.md               |  7 +++
 docs/settings-reference.md                         |  7 ++-
 .../core/src/__tests__/model-resolution.test.ts    | 13 ++++
 .../core/src/__tests__/settings-parity.test.ts     |  5 ++
 packages/core/src/builtin-workflow-settings.ts     | 24 +++++++
 packages/core/src/index.gate.ts                    |  1 +
 packages/core/src/index.ts                         |  1 +
 packages/core/src/model-resolution.ts              | 21 +++++++
 packages/core/src/settings-schema.ts               |  3 +
 packages/core/src/types.ts                         | 11 ++++
 .../app/components/WorkflowSettingsPanel.tsx       |  8 +++
 .../settings/sections/ProjectModelsSection.tsx     | 10 ++-
 packages/engine/src/__tests__/pi.test.ts           | 14 ++++-
 packages/engine/src/agent-session-helpers.ts       |  7 ++-
 packages/engine/src/executor.ts                    | 48 +++++++-------
 packages/engine/src/pi.ts                          | 73 ++++++++--------------
 packages/engine/src/step-session-executor.ts       |  8 ++-
 17 files changed, 180 insertions(+), 81 deletions(-)

Fusion-Task-Id: FN-8098

Fusion-Task-Lineage: 61b3103b-357b-431a-8d58-411e7806b87b

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 08:33:57 -07:00
gsxdsm
14953f39e2 FN-8075: restore PostgreSQL self-healing test coverage
Restore self-healing coverage for PostgreSQL-backed maintenance.

- Update self-healing mocks and assertions for asynchronous audit APIs and PostgreSQL WAL behavior
- Align git command expectations and transient recovery budget coverage with current implementation
- Remove the repaired self-healing suite from the quarantine ledger and gate exclusion

Files changed:
 packages/engine/src/__tests__/self-healing.test.ts | 70 ++++++++++++----------
 packages/engine/vitest.config.ts                   |  1 -
 scripts/lib/test-quarantine.json                   |  5 --
 3 files changed, 39 insertions(+), 37 deletions(-)

Fusion-Task-Id: FN-8075

Fusion-Task-Lineage: 7dfce9b0-9d10-4d70-8e92-8100f6595fce

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 08:11:24 -07:00
gsxdsm
40687cb0be FN-8050: cover cached dependency reconciliation seeding
Ensure raw dependency-reconciliation fixtures invalidate warmed task caches.

- Warm slim task-list caches before corrupt-row seeding in cycle reconciliation tests.
- Document the PostgreSQL-only fixture seam and cache invalidation rationale.

Files changed:
 .../engine/src/__tests__/reliability-interactions/_helpers.ts | 11 ++++++-----
 .../dependency-cycle-reconcile.test.ts                        |  7 +++++++
 2 files changed, 13 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-8050

Fusion-Task-Lineage: 5e708d58-faed-4271-a075-24c36eaa5878

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 07:48:12 -07:00
gsxdsm
50ed689379 FN-8044: restore dependency reconcile reliability tests
Restore dependency reconciliation coverage using a PostgreSQL raw-seeding seam.

- Add a cache-invalidating raw task-column seeding helper for corrupt fixture states.
- Update dependency-cycle and self-defeating reconciliation tests to use the PG seam.
- Remove restored suites from the reliability quarantine configuration and ledger.

Files changed:
 .../__tests__/reliability-interactions/_helpers.ts | 29 ++++++++
 .../dependency-cycle-reconcile.test.ts             | 86 +++++++++++-----------
 .../self-defeating-dep-reconcile.test.ts           |  7 +-
 packages/engine/vitest.config.ts                   |  7 +-
 scripts/lib/test-quarantine.json                   | 10 ---
 5 files changed, 82 insertions(+), 57 deletions(-)

Fusion-Task-Id: FN-8044

Fusion-Task-Lineage: 213f606c-1951-4d25-93b4-2ceb97460ace

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 07:14:01 -07:00
gsxdsm
969fce7aa1 FN-8047: migrate AgentStore multi-node tests to PostgreSQL
Migrate multi-node AgentStore coverage to shared PostgreSQL-backed fixtures.

- Make concurrent central claim insertion resolve unique-key races as checkout conflicts.
- Rework claim and owning-node handoff tests to use shared async PostgreSQL layers.
- Restore PostgreSQL-compatible tests from the quarantine ledger.

Files changed:
 packages/core/src/async-central-db.ts              |  9 ++-
 .../cross-node-claim-mutex.integration.test.ts     | 72 ++++++++++---------
 .../distributed-claim-mutex.integration.test.ts    | 27 +++----
 .../owning-node-handoff.integration.test.ts        | 41 +++++------
 .../__tests__/reliability-interactions/_helpers.ts | 83 ++++++++++++++++++++--
 .../multi-node-claim-mutex-interactions.test.ts    | 28 +++-----
 .../owning-node-unavailable-interactions.test.ts   | 36 +++++-----
 packages/engine/vitest.config.ts                   |  8 +--
 scripts/lib/test-quarantine.json                   | 25 -------
 9 files changed, 180 insertions(+), 149 deletions(-)

Fusion-Task-Id: FN-8047

Fusion-Task-Lineage: 3b7ee21e-0190-4364-a0cb-88aac5e2e1a3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 05:33:55 -07:00
gsxdsm
19ab7a9e93 FN-8068: add input-language task definitions
Add an opt-in setting that localizes planner-authored task-definition prose.

- Detect confident Spanish, French, Korean, and Chinese input before instructing triage localization.
- Expose and persist the project-level task-definition language toggle with translations and search support.
- Preserve English structural markers, code, and unsupported or uncertain input for deterministic parsing.

Files changed:
 .../fn-8068-task-definition-input-language.md      |  7 +++
 docs/settings-reference.md                         |  1 +
 packages/core/src/settings-schema.ts               |  7 +++
 packages/core/src/types.ts                         | 10 ++++
 .../app/__tests__/settings-sections.test.tsx       | 18 ++++++
 .../dashboard/app/components/SettingsModal.tsx     |  4 ++
 .../__tests__/SettingsModal.mobileClose.test.tsx   | 12 ++++
 .../__tests__/SettingsModal.models-auth.test.tsx   | 11 ++++
 .../app/components/settings/section-keys.ts        |  1 +
 .../sections/ProjectModelsSection.search.ts        | 10 ++++
 .../settings/sections/ProjectModelsSection.tsx     | 17 ++++++
 .../settings-default-descriptions.test.tsx         |  1 +
 packages/engine/src/__tests__/triage.test.ts       | 64 ++++++++++++++++++++++
 packages/engine/src/triage.ts                      | 29 +++++++++-
 packages/i18n/locales/en/app.json                  |  2 +
 packages/i18n/locales/es/app.json                  |  2 +
 packages/i18n/locales/fr/app.json                  |  2 +
 packages/i18n/locales/ko/app.json                  |  2 +
 packages/i18n/locales/zh-CN/app.json               |  2 +
 packages/i18n/locales/zh-TW/app.json               |  2 +
 packages/i18n/src/resources.d.ts                   |  2 +
 21 files changed, 205 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8068

Fusion-Task-Lineage: fe0102f5-68cb-443c-8512-2ffa5fe0e2f8

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 05:18:30 -07:00
gsxdsm
5b4ec4c08f FN-8042: add merger fallback model lane
Add configurable project fallback models for AI merger retries.

- Resolve complete project merger fallback pairs before the shared global fallback.
- Expose merger fallback model and thinking controls in Project Models with reset-aware persistence.
- Apply the fallback lane across merger, review, PR-response, and recovery sessions with tests and documentation.

Files changed:
 .changeset/fn-8042-merger-fallback-model.md        |  7 +++
 docs/settings-reference.md                         |  4 +-
 .../core/src/__tests__/model-resolution.test.ts    | 24 ++++++++++
 .../core/src/__tests__/settings-parity.test.ts     |  3 ++
 packages/core/src/index.gate.ts                    |  1 +
 packages/core/src/index.ts                         |  1 +
 packages/core/src/model-resolution.ts              | 22 +++++++++
 packages/core/src/settings-schema.ts               |  4 ++
 packages/core/src/types.ts                         | 13 ++++++
 .../app/__tests__/settings-save-split.test.ts      | 41 +++++++++++++++++
 .../app/__tests__/settings-sections.test.tsx       | 36 +++++++++++++++
 .../app/components/settings/save-split.ts          |  7 +--
 .../settings/sections/ProjectModelsSection.tsx     | 53 +++++++++++++++++++++-
 .../settings-default-descriptions.test.tsx         |  3 ++
 .../src/__tests__/agent-session-helpers.test.ts    |  1 +
 .../__tests__/mcp-pr-response-forwarding.test.ts   |  1 +
 packages/engine/src/agent-session-helpers.ts       | 10 +++-
 packages/engine/src/merger-ai.ts                   | 17 +++++--
 packages/engine/src/merger.ts                      | 43 +++++++++++++-----
 packages/engine/src/pr-response-run-ops.ts         | 11 +++--
 20 files changed, 276 insertions(+), 26 deletions(-)

Fusion-Task-Id: FN-8042

Fusion-Task-Lineage: 31762a72-461e-438c-a12d-2816580283fd

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 04:29:17 -07:00
gsxdsm
3f133e0b13 FN-8058: add task agent log reader
Expose paginated, filterable persisted agent logs to task-scoped and chat agent sessions.

- Add the read-only fn_task_logs_read tool across engine, dashboard chat/planning, heartbeat, step, and CLI extension surfaces.
- Filter agent-log entries before pagination, report matching totals, and render complete persisted rows for diagnosis.
- Document the tool, add release metadata, regression coverage, and complete affected engine mocks.

Files changed:
 .changeset/fn-8058-task-logs-read.md               |  7 ++
 docs/agents.md                                     |  4 +-
 packages/cli/skill/fusion/SKILL.md                 |  2 +-
 .../cli/skill/fusion/references/extension-tools.md | 11 +++
 .../skill/fusion/references/fusion-capabilities.md |  1 +
 .../extension-experiment-finalize.test.ts          |  2 +
 .../src/__tests__/extension-fn-secret-get.test.ts  |  2 +
 .../__tests__/extension-gitlab-tracking.test.ts    |  2 +
 .../src/__tests__/extension-integration.test.ts    |  1 +
 .../cli/src/__tests__/extension-web-fetch.test.ts  |  2 +
 packages/cli/src/__tests__/extension.test.ts       |  1 +
 packages/cli/src/extension.ts                      | 34 ++++++++
 .../src/__tests__/agent-logs-backend-mode.test.ts  | 28 +++++-
 packages/core/src/store.ts                         | 11 ++-
 packages/core/src/task-store/remaining-ops-7.ts    | 16 +++-
 packages/core/src/types.ts                         |  1 +
 packages/dashboard/src/__tests__/chat.test.ts      |  1 +
 .../planning-answered-question-reemit.test.ts      |  1 +
 .../planning-generation-cancellation.test.ts       |  1 +
 packages/dashboard/src/chat.ts                     |  5 ++
 packages/dashboard/src/planning.ts                 |  3 +
 .../__tests__/agent-task-logs-read-tools.test.ts   | 72 ++++++++++++++++
 packages/engine/src/agent-heartbeat.ts             |  4 +-
 packages/engine/src/agent-tools.ts                 | 99 +++++++++++++++++++++-
 packages/engine/src/executor.ts                    |  6 ++
 packages/engine/src/gating-classifications.ts      |  2 +
 packages/engine/src/index.ts                       |  6 ++
 packages/engine/src/step-session-executor.ts       |  6 +-
 28 files changed, 316 insertions(+), 15 deletions(-)

Fusion-Task-Id: FN-8058
Fusion-Task-Lineage: 74f198b2-f538-4b39-973f-431f22e68f29
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 04:03:43 -07:00
gsxdsm
c2475b012d FN-8064: add proactive task chat status updates
Task-detail chat now narrates engine progress and review outcomes in real time.

- Emit bounded, redacted status rows for step lifecycle and review paths.
- Present status entries with a distinct task-chat treatment.
- Cover status narration and diagnostic sanitization with engine tests.

Files changed:
 .changeset/fn-8064-proactive-chat.md               |   7 +
 docs/architecture.md                               |   1 +
 packages/dashboard/app/components/TaskChatTab.css  |  16 ++
 packages/dashboard/app/components/TaskChatTab.tsx  |   9 +-
 .../engine/src/__tests__/executor-prompt.test.ts   |  28 +++-
 .../engine/src/__tests__/proactive-status.test.ts  |  54 +++++++
 packages/engine/src/executor.ts                    | 176 ++++++++++++++++-----
 packages/engine/src/proactive-status.ts            | 117 ++++++++++++++
 8 files changed, 365 insertions(+), 43 deletions(-)

Fusion-Task-Id: FN-8064

Fusion-Task-Lineage: c6d0a9b5-0946-4bf4-8338-e982e1cbfd53

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 02:47:59 -07:00