Commit Graph

3286 Commits

Author SHA1 Message Date
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
gsxdsm
274318aebf FN-8056: enforce task token budgets
Enforce configured task token budgets whenever session usage is persisted.

- Apply soft alerts and hard pauses atomically from all executor persistence paths.
- Exclude cache-read tokens from budget usage and dispatch budget notifications once.
- Document budget semantics and add regression coverage.

Files changed: .changeset/fn-8056-token-budget-enforcement.md     |   7 ++
 docs/settings-reference.md                         |   2 +
 packages/core/src/types.ts                         |   4 +-
 .../src/__tests__/session-token-usage.test.ts      | 101 ++++++++++++++++++++-
 .../src/__tests__/token-budget-enforcer.test.ts    |  81 ++++++++++-------
 packages/engine/src/executor.ts                    |  22 ++++-
 packages/engine/src/session-token-usage.ts         |   8 +-
 packages/engine/src/token-budget-enforcer.ts       |  98 +++++++++++++++++---
 8 files changed, 262 insertions(+), 61 deletions(-)

Fusion-Task-Id: FN-8056

Fusion-Task-Lineage: 5f5ed522-f950-42ce-b4fd-e0b1d45b5815

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 01:52:10 -07:00
gsxdsm
e46ffebde1 FN-7985: surface review budget exhaustion and configure replan cap
Expose exhausted Plan Review replan budgets for operator approval and allow workflows to configure the cap.

- Add validated numeric workflow setting support and a Plan Review replan-cap setting.
- Route configured cap exhaustion with a distinct approval reason and preserve fallback behavior.
- Display the budget-exhaustion state across task cards, lists, and details.
- Add tests, localized copy, documentation, and a minor changeset.

Files changed:
 .changeset/fn-7985-review-budget-approval.md       |  7 ++++
 docs/settings-reference.md                         |  9 +++--
 docs/workflow-steps.md                             |  2 +-
 .../builtin-workflow-settings-triage.test.ts       | 27 +++++++++++++--
 packages/core/src/builtin-workflow-settings.ts     | 17 ++++++++++
 packages/core/src/index.gate.ts                    |  1 +
 packages/core/src/index.ts                         |  1 +
 packages/core/src/workflow-ir-types.ts             |  4 +++
 packages/core/src/workflow-ir.ts                   | 27 +++++++++++++++
 packages/core/src/workflow-settings-resolver.ts    |  1 +
 packages/core/src/workflow-settings.ts             |  6 ++++
 packages/dashboard/app/components/ListView.css     | 19 +++++++++++
 packages/dashboard/app/components/ListView.tsx     | 22 +++++++++---
 packages/dashboard/app/components/TaskCard.css     | 18 ++++++++++
 packages/dashboard/app/components/TaskCard.tsx     |  6 ++--
 .../dashboard/app/components/TaskDetailModal.tsx   |  4 +--
 .../app/components/__tests__/ListView.test.tsx     | 30 +++++++++++++++++
 .../app/components/__tests__/TaskCard.test.tsx     | 17 ++++++++--
 .../app/components/workflow-setting-display.ts     | 11 ++++++
 .../dashboard/app/utils/reviewBudgetApproval.ts    | 11 ++++++
 .../triage-plan-review-replan-cap.test.ts          | 39 +++++++++++++++++++---
 packages/engine/src/triage.ts                      | 21 ++++++++----
 packages/i18n/locales/en/app.json                  |  2 +-
 packages/i18n/src/resources.d.ts                   | 19 ++++++++---
 24 files changed, 288 insertions(+), 33 deletions(-)

Fusion-Task-Id: FN-7985

Fusion-Task-Lineage: 125f101c-caca-45c2-8b40-996b2a31c019

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 01:32:23 -07:00
gsxdsm
a31c370375 FN-8045: add transactional handoff failure-injection seam
Ensure PostgreSQL review handoffs roll back all dependent writes after an injected late failure.

- Add a test-only failure injector after transactional handoff writes.
- Include workflow work in same-column retry transactions.
- Restore PG-backed handoff atomicity coverage and remove its quarantine.

Files changed:
 packages/core/src/store.ts                         |  24 +++
 packages/core/src/task-store/moves.ts              |  21 ++-
 .../in-review-handoff-atomic.test.ts               | 172 +++++++++++++--------
 packages/engine/vitest.config.ts                   |   1 -
 scripts/lib/test-quarantine.json                   |   5 -
 5 files changed, 151 insertions(+), 72 deletions(-)

Fusion-Task-Id: FN-8045

Fusion-Task-Lineage: 517e3000-9b88-4b0d-9b25-1a585eb8f322

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 00:44:35 -07:00
gsxdsm
9a34862586 refactor: package code organization waves 3–5 (#2148)
## Summary

Waves 3–5 of package code organization (plan:
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`).
Behavior-preserving peels after #2139 and #2143.

### Wave 3 — Merger + heartbeat recovery
- **`merger-errors.ts`** — verification/abort error classes
- **`merger-owned-landed.ts`** — ownership classification +
`Fusion-Task-Id` trailer
- **`merger-conflict-resolution.ts`** — conflict classify/auto-resolve
- **`agent-heartbeat-error-recovery.ts`** — durable error-recovery
budget helpers

### Wave 4 — Self-healing + dashboard API
- **`self-healing-constants.ts`** — public timing/budget constants
- **`self-healing-branch.ts`** — `isBranchAheadOfBase`
- **`app/api/client.ts`** — `api` / `ApiRequestError` / `buildApiUrl` /
`proxyApi`
- **`app/api/health.ts`** — health, engine status, updates +
`withProjectId`

### Wave 5 — Types tracking + merger parse + task CRUD
- **`types/task-tracking.ts`** — PR/issue/GitHub/GitLab tracking
contracts
- **`merger-git-parse.ts`** — `parseFailingFilesFromOutput`,
`parsePorcelainZ`, `parseShortstatSummary`
- **`app/api/tasks.ts`** — task list/detail/create/update/move client
surface
- Line-count baselines ratcheted down for `merger.ts`, `types.ts`,
`legacy.ts`

Public import paths stay on parent modules / `legacy.ts` / package
barrels via re-exports.

## Test plan
- [x] core/engine/dashboard typecheck (including `tsconfig.app.json`)
- [x] eslint on touched modules
- [x] `parse-porcelain-z` + merger parseFailing/getBranchChanged tests
- [x] dashboard `api-tasks` + legacy-prinfo/pr-types (69)
- [ ] CI merge gate

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

- **New Features**
- Added dashboard API support for task listing/detail, archiving,
creation, review updates, duplicate detection, bulk model updates,
moving tasks, and overlap repair.
- Added health/engine status and refresh/start controls, plus update
checking.

- **Bug Fixes**
- Improved dashboard API handling for non-JSON/HTML responses with
clearer errors, better URL routing for remote nodes, and project-scoped
queries.
- Strengthened automated recovery for heartbeat error/model-unavailable
scenarios and safer merge-conflict classification/auto-resolution.

- **Tests**
- Updated merge-conflict resolution and lifecycle test mocks to match
the updated git command behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 00:01:17 -07:00
gsxdsm
1d6a0449ce fix(engine): keep replan cards plannable when they carry prior steps
Tasks piled up in the Planning column and never moved. hasAdvancedPastPlanning
counted steps.length > 0 as proof a card had advanced past planning, but a
replan card legitimately retains the steps its previous planning pass
materialized. The still-in-planning guard therefore failed for every card
Plan Review sent back, so triage's specifyTask claim silently skipped its
status:"planning" write and re-claimed the same cards every poll — never
planning them, and starving healthy cards out of the maxTriageConcurrent
slots they held.

Steps are no longer advancement evidence while a card sits in a planner lane:
the "triage" column, and the merged "todo" planner lane used by plan-in-place
workflows when the card carries a planning status. Worktrees and
execution/terminal columns remain durable advancement evidence, preserving
FN-7977's protection against a recovery write clobbering a card that raced
ahead into execution.

The primary claim path now warns instead of returning silently; recovery-write
skips stay silent by design. The silence is why this stalled the planner for
hours undiagnosed.

Regression coverage asserts the invariant across both planner surfaces rather
than the reported repro alone: triage cards with and without an explicit
needs-replan status, plan-in-place todo replans, every parked-for-planning
status, and the advancement signals that must still fire.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 22:53:49 -07:00
gsxdsm
7b31d54b98 fix(FN-8004): make AI merge rejections actionable and stranded merges retryable (#2160)
Two follow-ups to FN-8004. Both were found by watching FN-8004's *own*
merge livelock for 40 minutes — it turned out to be blocked by the very
class of bug it was filed to fix.

## 1. AI merge rejections lost their reasons

The reviewer prompt said **both** of these:

> "**End with a single decision line**: `REVIEW_VERDICT:
approve|reject`"
> "**Then list each concrete reason as a bullet.**"

Those are impossible to satisfy at once. Reviewers obeyed "End with" and
wrote their reasoning *above* the verdict — but `extractRejectReasons`
only scanned lines *after* it. So every such rejection collapsed to the
placeholder `reviewer rejected the merge without a stated reason`, and
that placeholder was then handed to the corrective re-merge pass **as
its instruction**. The pass got no actionable feedback and just
re-rolled the merge.

The evidence, from FN-8004's own merge — the pattern repeated across
*both* attempts:

| | Attempt A | Attempt B |
|---|---|---|
| review pass 1 | rejected, no reason (03:46) | rejected, no reason
(03:57) |
| corrective pass | 1/3 | 1/3 |
| review pass 2 | **approved** `a3a3cc6a8` (03:49) | approved |

A reviewer that rejects and then approves identical content isn't
objecting — the reason was being thrown away. Each wasted cycle cost ~7
minutes, stretching the merge past main's ~8-minute churn window so
every attempt lost to a concurrent advance and rebuilt. **The livelock
was caused by the lost-reason bug.**

Fix: the parser recovers reasons from either side of the verdict (inline
→ after → before, nearest-first so the closing argument leads, capped at
8 so a long transcript can't flood the corrective prompt), skipping
severity/verdict/markdown scaffolding. The prompt ordering is now
unambiguous — reasons first, verdict last, nothing after it.

## 2. An orphaned merge-active stamp was un-retryable by hand

The Retry gate refused **every** merge-active status (`Task is not in a
retryable state (current status: landing)`), while self-healing cleared
stale stamps automatically minutes later. So a merger killed mid-flight
— crash, engine restart, operator SIGTERM — blocked the operator's own
escape hatch at exactly the moment they'd reach for it. FN-8004 hit
this: a killed merge left `landing` stamped and Retry 400'd for the full
sweep delay.

`isStaleMergeActiveStatus` now lives in the leaf
`merge-active-status.ts`, shared by `recoverStaleMergingStatus` and the
Retry gate — so **the manual path can never be stricter than the
automatic one**. This is the same one-concept-two-definitions bug as
FN-8004's transient classifier, which is why it's worth fixing
structurally rather than adding another special case.

A live merge stays protected by two independent signals: it holds the
in-process lease **and** refreshes `updatedAt` each phase. Staleness
fails closed on an unparseable timestamp.

One subtlety worth reviewing: the bypass feeds `isInReviewRetry` rather
than only the gate. A bare gate bypass would fall through to the generic
branch and move fully-executed work to `todo`, **re-running finished
work** — a bug this fix could easily have introduced.

## Verification

- Gate green (294 + 122 + 63) · lint clean · engine + dashboard
typecheck clean · `verify:fast` PASS
- 70 merger-suite tests green; all 7 pre-existing verdict-parser tests
still pass (backward compatible — none of them covered the verdict-last
layout, which is exactly why this shipped)
- **The route regression test was confirmed non-vacuous**: neutralizing
the fix fails the two "now retryable" cases while the three
live-merge-protection cases still pass, proving they guard real behavior
rather than the new code.
- Regression tests assert the invariant across every surface per *Fix
the Invariant, Not the Repro*: all five `ACTIVE_MERGE_STATUSES` (a
merger can die in any phase, not just the reported `landing`), both
live-merge signals, boundary conditions, fail-closed paths, and that
pre-existing retry paths are unchanged. Test files carry the required
`## Symptom Verification` and `## Surface Enumeration` sections.

🤖 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**
* AI merge rejections now reliably include concrete, correctly ordered
reasons, even when provided before the verdict line.
* Manual retry can recover tasks stuck in stale merge-processing states.
* Retry is still blocked for tasks tied to active merge activity or
recently updated/advancing merges.
  * Existing failed-merge retry behavior remains unchanged.

* **Reliability**
* Improved shared handling of “orphaned” merge-active detection across
the engine and dashboard.

* **Tests**
* Added/expanded coverage for merge-active staleness, retry eligibility,
and verdict/reason parsing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 22:43:24 -07:00
gsxdsm
959a7877c8 FN-8020: harden agent heartbeat health classification
Validate the four-interval heartbeat grace window and classify invalid persisted heartbeats safely.

- Cover reported field heartbeat ages in dashboard and engine health checks.
- Mark unparseable heartbeat timestamps as unresponsive and clamp future timestamps to fresh.
- Align dashboard health documentation with the existing four-interval grace window.

Files changed:
 .../app/utils/__tests__/agentHealth.test.tsx       | 48 +++++++++++++++++++++-
 packages/dashboard/app/utils/agentHealth.tsx       | 26 ++++++++++--
 .../src/__tests__/heartbeat-executor.test.ts       | 31 ++++++++++++++
 3 files changed, 100 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-8020

Fusion-Task-Lineage: 2bc0df78-d68c-489b-8bfb-9b09da10cdfa

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 22:11:26 -07:00
gsxdsm
9d791b3bf1 FN-8009: preserve approved plans through prompt hygiene
Keep manually approved plans idempotent when deterministic prompt hygiene is applied.

- Document normalized fingerprint comparison at the approval gate
- Cover approval reuse after Original Description and Frontend UX injection

Files changed:
 packages/engine/src/__tests__/triage.test.ts | 37 ++++++++++++++++++++++++++++
 packages/engine/src/triage.ts                |  8 ++++++
 2 files changed, 45 insertions(+)

Fusion-Task-Id: FN-8009

Fusion-Task-Lineage: 8474bc13-61e0-421a-8e99-080f99382285

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 21:52:06 -07:00
gsxdsm
3dcb62f40f FN-8008: normalize plan approval fingerprints
Keep approval recovery idempotent when deterministic prompt hygiene is injected.

- Normalize plan approval fingerprints around Original Description and Frontend UX sections.
- Preserve re-approval for operator-authored plan changes and cover recovery behavior.
- Document the normalization contract and add a patch changeset.

Files changed:
 .changeset/fn-8008-plan-approval-fingerprint.md   |  7 +++
 docs/workflow-steps.md                            |  2 +-
 packages/core/src/__tests__/plan-approval.test.ts | 53 +++++++++++++++-
 packages/core/src/plan-approval.ts                | 73 ++++++++++++++++++++++-
 packages/engine/src/__tests__/triage.test.ts      | 45 ++++++--------
 packages/engine/src/triage.ts                     | 40 ++-----------
 6 files changed, 153 insertions(+), 67 deletions(-)

Fusion-Task-Id: FN-8008

Fusion-Task-Lineage: 9c0f415d-662a-455a-a4bd-b873307e53bc

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 21:33:11 -07:00
gsxdsm
402b3a91fa fix(FN-8004): treat heartbeat soft-delete races as benign instead of stranding agents
A task soft-deleted concurrently with a heartbeat-driven moveTask raised
TaskDeletedError from the engine's own board path, leaving the agent in `error`
with a non-empty lastError and requiring a stop/start cycle to recover.

The race is benign by construction: the task is gone, so the move is a no-op.
The heartbeat now classifies it via isConcurrentSoftDeleteRaceError (matching the
canonical message and serialized/typed forms), keeps the agent active, clears
stale error/recovery state, and emits agent:heartbeat-move-skipped-soft-delete
with ids/counts-only metadata. Concurrent operator pauses are preserved.

Squash-merged by hand from fusion/fn-8004. The engine's AI merge approved this
content twice (squash a3a3cc6a8) but could not land it: main advances every ~8
minutes and each merge cycle took ~10, so every attempt lost to a concurrent
advance and rebuilt. Each cycle also burned a corrective pass on a first-pass
review rejection with no stated reason — the issue #1946 class of bug that this
task's own report cites as a sibling.

Reconciled against #2157, which refactored transient-error-detector.ts: the new
classifier coexists with the extracted transient-error-patterns.ts leaf. Verified
on the merged tree — 123 tests green across FN-8004's suites and #2157's,
engine typecheck clean.

Fusion-Task-Id: FN-8004

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:00:45 -07:00
gsxdsm
08a10bf486 fix(FN-8006): back off and pause Plan Review on provider rate limits
A rate-limited Plan Review re-ran every 30s for hours (~1,900 requests
per 5h window, reviewerFallbackRetryCount observed past 100), which is
the request volume that trips a provider's low-interactivity throttle —
so the retry storm prolonged the very outage it was retrying.

Root cause: runPlanReviewBeforeExecution catches every reviewStep throw
inline to keep triage alive, which converts them all to an UNAVAILABLE
verdict. That laundering had two consequences the earlier fixes missed:
FN-8006 terminalized RetryStormError and the reviewer started throwing
ReviewerProviderError for 429s, but a ReviewerProviderError still landed
in the UNAVAILABLE park — a FIXED 30s nextRecoveryAt with no attempt
counter and no cap. The reviewer's own escalation contract ("escalate so
UsageLimitPauser pauses every lane") held only on the executor path,
because the inline catch hid the error from triage's usage-limit handler
in specifyTask.

- triage: fire usageLimitPauser.onUsageLimitHit for usage-limit reviewer
  failures, so a 429 pauses every lane instead of re-parking one task.
- triage: re-park via computeRecoveryDecision (60s/120s/240s, ±10%
  jitter) and terminalize at MAX_RECOVERY_RETRIES. A reviewer that never
  yields a verdict is a real failure and must surface, not spin.
- triage: clear the borrowed recoveryRetryCount budget on any real
  verdict, so surviving an outage cannot shorten the executor's later
  transient budget.
- core: RetryStormError takes an optional cause, surfaced as
  underlyingError in serializeRetryStormError and folded into the
  message, so a cap no longer masks the real error. recordRetry threads
  it from the reviewer's error path.

Surface enumeration: the park is driven by a thrown provider error, a
thrown generic error, and a plain UNAVAILABLE verdict with no throw.
All three are covered — a repro pinned only to the reported 429 would
leave the other two spinning on the old fixed timer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:53:05 -07:00
gsxdsm
71dd191c7c FN-8006: terminalize Plan Review retry storms
Plan Review now fails tasks when reviewer fallback retry limits are exceeded.

- Detect RetryStormError from Plan Review workflow execution
- Serialize the terminal retry error, clear recovery scheduling, and preserve workflow results
- Add retry-storm regression coverage, architecture guidance, and a patch changeset

Files changed:
 .changeset/fn-8006-plan-review-retry-storm.md      |  7 ++++
 docs/architecture.md                               |  2 +-
 packages/engine/src/__tests__/triage-plan-review-unavailable-retry.test.ts | 47 +++++++++++++++++++++-
 packages/engine/src/triage.ts                      | 33 +++++++++++++++
 4 files changed, 87 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-8006

Fusion-Task-Lineage: 932e7930-2069-4b0c-9cd1-9db39c2de5a3

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

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

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

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

## Three defects fixed

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

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

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

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

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

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

## Loosened budgets

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

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

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

## Verification

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

## Note

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

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:14:25 -07:00
gsxdsm
0b332816d5 FN-7986: raise plan review replan cap to 8
Allow more automatic Plan Review revisions before escalating tasks for human approval.

- Raise the consecutive REVISE replan cap from 3 to 8.
- Cover the seven- and eight-revision boundaries in triage tests.
- Add a patch changeset describing the revised default.

Files changed:
 .changeset/fn-7986-plan-review-cap.md              |  7 +++++++
 .../triage-plan-review-replan-cap.test.ts          | 23 +++++++++++++---------
 packages/engine/src/triage.ts                      |  8 ++++----
 3 files changed, 25 insertions(+), 13 deletions(-)

Fusion-Task-Id: FN-7986

Fusion-Task-Lineage: 3b61f333-be9a-414a-bd27-aabdbb45caa0

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 19:57:34 -07:00
gsxdsm
753b1bb710 fix(engine): honor graph cancellation at the merge node
The merge node could not observe a graph abort. WorkflowPrimitiveContext
carried no signal, so requestMerge raced the merge only against its own
30-minute GRAPH_MERGE_TIMEOUT_MS using a controller it owned. A hard-cancel
(user cancel, engine restart, pause/resume) aborted the graph controller and
the walk kept sitting inside the merge node for the full timeout. When the
timeout finally fired it aborted the still-running AI merge -- surfacing as
"Manual-merge failed: Request was aborted" -- and the walk reported
value=merge-timeout for a cancellation it had missed half an hour earlier.
An abort landing between merger-ai's `worktree: null` write and
mergeConfirmed then stranded the card as no-worktree-no-merge-confirmed.

Thread the graph AbortSignal from WorkflowNodeExecutionContext (where it
already existed) through primitiveNodeContext/primitiveContextForNode into
the primitives, and honor it on both merge surfaces:

- requestMerge fails fast when the walk is already cancelled, before
  ensureWorkflowMergeBoundaryTask mutates the row or the requester enqueues
  a merge, and links the graph signal into its timeout controller via
  AbortSignal.any -- raced separately so the walk returns on the abort
  rather than waiting on a requester that may never settle.
- The legacy merge seam had the identical unguarded race and gets the same
  treatment.

The timeout stays: it bounds a wedged merge queue, which is a different
failure from cancellation. Both signals must stay live -- dropping either
silently restores the stall with no type error.

Cancellation returns a distinct `merge-cancelled` rather than reusing
merge-timeout. Returning `data.status: "failed"` would let classifyMergeFailure
read the unknown reason as merge-failed and route the cancellation into
bounded auto-merge retry, re-requesting the merge the operator just cancelled.

Regression test covers both merge surfaces, both cancel timings (pre-flight
and mid-flight), the no-signal back-compat path, the signal plumbing itself,
and the classification boundary. Verified by removing the fix: 7 of 9 cases
fail, with the mid-flight cases hanging until timeout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 19:43:08 -07:00
gsxdsm
40ae6ddb3a fix(FN-8024): stop logging skipped stale triage recovery writes
Skipping a stale planning-state write is the expected outcome of a normal
scheduler advancement, not an anomaly, so the warn was pure log noise.
Behavior is unchanged; only the two planLog.warn emissions are removed.

Fusion-Task-Id: FN-8024

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 17:35:32 -07:00
gsxdsm
0e84731d8a fix(FN-7965): let the overseer see executor-stage failures
`deriveSignalAndSources`'s executor branch never read `task.status`, so a row
parked `status: "failed"` — e.g. the terminal fn_task_done refusal/invariant
park — reported `signal: "progressing"` with the reason "Task is actively
executing in-progress work". The overseer observed a dead task as healthy and
took no action. `failed` was only ever derived for the merger/pull-request
stages, so the sole backstop was the FN-7743 2h stall proxy firing hours later.
This is exactly what FN-7965's audit trail shows: every intervention on a
terminally-parked task was action="observe", reason="Task is actively executing
in-progress work".

Report `failed` so recovery engages on the next poll. This adds no new policy:
a failed executor observation already routes to `retry_step` (executor sources
are `agent-log`, never an ERROR_SOURCE_KIND), bounded by
PLANNER_RECOVERY_MAX_ATTEMPTS and escalated on exhaustion.

Precedence and dedup preserved: `paused` still wins, so an operator/user-paused
row stays `blocked` and is never routed into autonomous recovery; and the reason
is a constant (never interpolating task.error/status) so the FN-7577
`stage|signal|reason` feed dedup still suppresses repeat observations.

Verified: the repro test fails with the branch disabled; paused-precedence,
healthy-card (FN-7577) and dedup guards added; overseer/recovery surfaces
93 passed + core planner-recovery 20/20; engine + dashboard typecheck clean;
`pnpm test:gate` green (294+122+63).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 16:32:17 -07:00
gsxdsm
214af98591 FN-7977: hold Plan Review provider failures without replan regression
Prevent provider, model, transport, and abort failures from bouncing tasks back to planning after they enter execution.

- Classify non-plan-defect Plan Review failures and skip needs-replan handoff
- Terminate graph traversal with plan-review-provider-failure-hold and retry in place
- Guard triage recovery so advanced column/worktree/step state is never overwritten
- Document planning-recovery no-regression invariant and add regression tests
- Add patch changeset for the operator-facing fix

Files changed:
 .changeset/fn-7977-planning-failure-no-regression.md |   7 ++
 docs/architecture.md                               |   1 +
 docs/workflow-steps.md                             |   2 +-
 packages/engine/src/__tests__/replan-target.test.ts     |  17 +++-
 packages/engine/src/__tests__/transient-error-detector.test.ts |  32 +++++-
 packages/engine/src/__tests__/triage.test.ts       | 110 +++++++++++++++++++++
 packages/engine/src/__tests__/workflow-graph-optional-group.test.ts          |  46 ++++++++-
 packages/engine/src/__tests__/workflow-graph-optional-step-fix.test.ts       |  36 +++++++
 packages/engine/src/executor.ts                    |  62 +++++++++++-
 packages/engine/src/replan-target.ts               |  22 +++++
 packages/engine/src/transient-error-detector.ts    |  37 +++++++
 packages/engine/src/triage.ts                      |  73 +++++++++++---
 packages/engine/src/workflow-graph-executor.ts     |  45 ++++++++-
 13 files changed, 466 insertions(+), 24 deletions(-)

Fusion-Task-Id: FN-7977

Fusion-Task-Lineage: 6d62d3ca-c6f3-4d02-a377-d7fd59f0c0f9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:29:10 -07:00
gsxdsm
f1b528f4c5 fix(FN-7965): honor terminal fn_task_done park instead of resurrecting the session
The in-session `fn_task_done` handler parks a task terminally (status=failed,
worktree/branch/sessionFile cleared) once its refusal/invariant retry budget is
exhausted. That write happens inside the live agent session, so the executor's
no-fn_task_done retry loop never observed it and spawned a fresh session anyway.
The retry completed, marked the task done, and dragged a worktree-less row into
the pre-merge graph, where the first write-capable node failed on
`no-worktree-for-write-node` — surfacing as a misleading "Workflow graph
terminated with failure at node 'code-review-remediation'" instead of the real
refusal. Observed on FN-7965 and again live on FN-7981.

Re-read state at the top of the retry loop and honor the park. The status probe
covers all three park sites (invariant-check, explicit refusal, implicit
refusal) rather than the single reported repro.

Deliberately not routed through the FN-4806 reclaim branch: its silent todo
requeue would clear the park and, with the budget already spent, re-park on the
next pickup in a todo->execute->park loop.

The pre-existing reclaim probes could not catch this — they test
`worktree === null`, but the store maps a cleared column to `undefined`
(`task-store/serialization.ts`: `row.worktree || undefined`), so the existing
test only passed because its mock returned a value production never emits.
Tightening that probe regressed 7 fixtures and is left as separate work.

Verified: new tests fail with the guard disabled; engine reliability surfaces
show zero regressions vs baseline (17 pre-existing failures unchanged, 495->499
passing); engine-core gate suite 294/294.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 16:21:03 -07:00
gsxdsm
363916926d FN-7995: always persist tool_error detail for Activity feed diagnosis
Always persist bounded tool_error detail so the task Activity feed can surface underlying failure messages even when verbose tool-output persistence is off.

- Keep tool args and successful tool_result detail opt-in via persistAgentToolOutput
- Always include bounded tool_error detail in agent-log JSONL rows
- Document diagnostic retention in types, agent-logger, and storage docs
- Cover Activity reveal behavior and logger persistence with unit tests
- Add patch changeset for operator-facing Activity error detail fix

Files changed:
 .changeset/fn-7995-tool-error-detail.md            |  7 ++++
 docs/storage.md                                    |  1 +
 packages/core/src/agent-log-constants.ts           |  4 +++
 packages/core/src/types.ts                         | 10 ++++--
 .../app/components/__tests__/TaskChatTab.test.tsx  | 42 ++++++++++++++++++++++
 packages/engine/src/__tests__/agent-logger.test.ts | 41 ++++++++++++++++++---
 packages/engine/src/agent-logger.ts                |  9 ++---
 7 files changed, 104 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-7995

Fusion-Task-Lineage: 0fa063df-58b1-4991-a0d9-e8a77181d32a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:17:37 -07:00
gsxdsm
dc7bb40948 FN-7990: share worktree classifier so Code Review acquires a worktree
Unify write-capability classification so graph preparation acquires a worktree for inline-fix Code Review before runtime runs, eliminating the immediate no-worktree-for-write-node failure.

- Add shared workflowNodeRequiresWorktree helper for preparation and runtime
- Plumb optional-group context and reviewerInlineFixes into graph preparation
- Acquire/reuse/reacquire worktrees for write-capable inline review nodes
- Keep Plan Review and disabled inline fixes read-only
- Add regression tests and a patch changeset

Files changed:
 .changeset/fn-7990-code-review-worktree.md         |  7 ++
 .../__tests__/ce-workflow-step-executor.test.ts    | 97 ++++++++++++++++++++++
 .../workflow-node-execution-needs.test.ts          | 47 +++++++++++
 packages/engine/src/executor.ts                    | 32 +++----
 packages/engine/src/workflow-graph-executor.ts     | 52 ++++++++----
 .../engine/src/workflow-node-execution-needs.ts    | 46 ++++++++++
 6 files changed, 243 insertions(+), 38 deletions(-)

Fusion-Task-Id: FN-7990

Fusion-Task-Lineage: f5d19181-0b98-4827-8adb-069f7dc05c03

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:34:43 -07:00
gsxdsm
667f4c8a55 FN-7987: expose shared fusion toolset to chat agents and Grok CLI
Give dashboard chat and room responders the same safe coordination/productivity tools as other agent lanes, including via the Grok MCP bridge.

- Export chat coordination tool factories from @fusion/engine for public use
- Assemble createChatFusionToolset with board, delegation, web, goal, memory, and research tools
- Wire the shared toolset into model-loop chat and room-responder sessions
- Exclude destructive agent-lifecycle tools and fn_memory_append from chat
- Cover chat fusion parity and Grok bridge tool preservation with tests
- Document chat Grok tool parity and add a minor changeset

Files changed:
 .changeset/fn-7987-chat-fusion-toolset.md          |  7 ++
 docs/agents.md                                     |  1 +
 docs/grok-cli-contract.md                          |  2 +-
 packages/dashboard/src/__tests__/chat-manager.test.ts | 52 +++++++++++-
 packages/dashboard/src/chat.ts                     | 95 +++++++++++++++++++++-
 packages/engine/src/__tests__/agent-session-helpers.test.ts | 15 ++++
 packages/engine/src/index.ts                       | 26 ++++++
 plugins/fusion-plugin-grok-runtime/src/__tests__/tool-bridge.test.ts | 36 ++++++++
 8 files changed, 230 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-7987

Fusion-Task-Lineage: 4d8d3dbc-10b8-4b56-9b63-79fe85a13755

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:25:39 -07:00
gsxdsm
8c3758584b refactor(engine): peel merger glob, test-command, and file-scope satellites (#2143)
## Summary

Wave 2 of package code organization (follows merged #2139): peel
pure/satellite clusters out of `packages/engine/src/merger.ts` while
keeping `merger.js` import paths stable for tests and production.

- **`merger-glob.ts`** — `ConflictType`, `LOCKFILE_PATTERNS`,
`GENERATED_PATTERNS`, `matchGlob`
- **`merger-workspace-test-commands.ts`** — pnpm workspace package
mapping + `inferDefaultTestCommand` / scoped + file-scoped test command
derivation
- **`merger-file-scope.ts`** — `parseDiffStat`, `extractFileScope` /
`matchesScope`, squash file-scope invariant (`FileScopeViolationError`,
assert/enforce with main’s `status` audit semantics and merge-trait
modes)
- Re-exports from `merger.ts`; baseline ratchet for `merger.ts` (~12.5k
→ ~11.8k)

## Test plan

- [x] `pnpm --filter @fusion/engine exec tsc --noEmit`
- [x] eslint on merger + satellites
- [x] `merger-file-scope-invariant.test.ts` (18 tests)
- [x] focused conflict-resolution pattern tests
- [ ] CI merge gate

## Plan

Continues
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`
(U6 merger satellites).

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

## Summary by CodeRabbit

* **New Features**
* Added file-scope validation for merge operations, with strict and
warning-only enforcement options.
* Added automatic classification of lockfile and generated-file
conflicts.
* Added workspace-aware test selection based on changed packages and
files.
* Added support for inferring test commands across pnpm, Yarn, Bun, and
npm projects.

* **Refactor**
* Organized merge, conflict-resolution, file-scope, and test-inference
functionality into dedicated modules without changing existing public
access paths.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:44:35 -07:00
gsxdsm
f9c19f9f3a FN-7967: accept custom triage workflow IDs and honor project default
Allow triageDefaultWorkflowId and triageDecisionOnlyWorkflowId to accept custom workflow IDs so project default workflows are honored at triage time.

- Change triage workflow settings from enum to string; empty triageDefaultWorkflowId inherits config.settings.defaultWorkflowId
- Render triage prompt default from project settings unless an explicit stored override exists
- Only pass stored triageDefaultWorkflowId into triage policy settings so declaration defaults do not clobber project defaults
- Document settings behavior and add core/engine regression coverage
- Add patch changeset for @runfusion/fusion

Files changed:
 .changeset/fn-7967-triage-default-workflow.md                        |  7 +++++++
 docs/settings-reference.md                                           |  4 ++--
 packages/core/src/__tests__/builtin-workflow-settings-triage.test.ts | 43 +++++++++++++++++++++++++++++++++++++++++--
 packages/core/src/builtin-workflow-settings.ts                       | 35 ++++++++++++++++++++---------------
 packages/engine/src/__tests__/triage.test.ts                         | 41 +++++++++++++++++++++++++++++++++++++++++
 packages/engine/src/triage.ts                                        | 28 ++++++++++++++++++++++++----
 6 files changed, 135 insertions(+), 23 deletions(-)

Fusion-Task-Id: FN-7967

Fusion-Task-Lineage: e42ea061-889c-4bdd-8a9d-f56f34fc0c89

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:43:55 -07:00
gsxdsm
05151a25db feat: faster dashboard and serve startup (#2132)
## Summary

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

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

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

## Test plan

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

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

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

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

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

* **Tests**
* Expanded coverage for deferred startup, shutdown, project isolation,
and startup timing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:01:08 -07:00
gsxdsm
599a509d22 refactor: package code organization (god-file peels, wave 1) (#2139)
## Summary

First wave of package-internal code organization: split oversized
modules into domain-named files/folders while preserving public import
paths via re-exports, and refresh the line-count ratchet scoreboard.

- **Plan:**
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`
(multi-wave program; this PR lands U1–U4 + first U3/U6 slices)
- **Core types:** peel `types.ts` into
`types/{board,merge-queue,execution-and-ui,merge-policy,workflow-steps}.ts`
with browser-safe Vite alias preserved
- **Core TaskStore:** rename `remaining-ops-9` →
`task-commit-associations` (domain-named, not ordinal dump)
- **Engine executor:** peel pure helpers into
`executor/{browser-probe,requeue-loop,pseudo-pause,workflow-step-failures}.ts`
- **Engine heartbeat:** peel system prompts/procedures into
`agent-heartbeat-prompts.ts`
- **Ratchet:** one-time baseline truth-up + ratchet-down for touched
files

### Deferred to follow-up PRs (plan U5, U7–U9 + remaining waves)
- Self-healing folder split
- Further remaining-ops domain peels
- Dashboard `legacy.ts` / routes / UI monofiles
- CLI extension + TUI peels

## Test plan

- [x] `pnpm --filter @fusion/core exec tsc --noEmit`
- [x] `pnpm --filter @fusion/engine exec tsc --noEmit`
- [x] Focused vitest: `detect-pseudo-pause`,
`executor-browser-verification`, `clear-terminal-workflow-step-failures`
- [x] `node scripts/check-file-line-count.mjs` clean against updated
baseline
- [ ] CI merge gate (lint/typecheck/build/gate)
- [ ] Browser smoke: N/A for this PR (no dashboard UI route changes)

## Residual Review Findings

None. Review autofix applied dual-home wiring for
`clearTerminalWorkflowStepFailures` only.

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

* **New Features**
* Added configurable heartbeat procedures for task and no-task scenarios
(including patrol-aware rendering).
* Improved agent-browser availability verification with clearer
availability/status reporting.
  * Added detection for pseudo-pauses and review-handoff requests.
* Expanded core configuration/contract options for
execution/UI/localization, merges, merge queues, and workflow steps.
* **Bug Fixes**
* Improved handling of transient execute-requeue and workflow-step
retry/cleanup behavior, including better Windows path support.
  * Preserved existing public interfaces during internal restructuring.
* **Documentation**
  * Added a multi-phase roadmap for future package reorganization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 13:34:30 -07:00
gsxdsm
85f8b1f909 feat: shared Postgres multi-node — retire mesh data-plane replication (#2130)
## Summary

- Treat **shared PostgreSQL** (`DATABASE_URL`) as the multi-node durable
data plane; mesh HTTP is membership + optional auth, not task/settings
replication.
- **Peer exchange**: under Postgres backend mode, write queue is
**topology/auth-only**; non-topology pending rows fail rather than
replaying multi-leader task/settings payloads.
- **Mesh routes**: task-ID reserve/commit/abort always hit local shared
allocator rows (ignore remote `coordinatorNodeId`); mesh sync ignores
settings and only exchanges `authMaterial`.
- **Docs**: rewrite multi-project runbook, shared cluster protocol, and
architecture mesh sections for shared-Postgres + claims/leases.

## Context

Follows the SQLite→Postgres cutover. Multiple Fusion nodes can share one
external Postgres while keeping **per-node execution** (worktrees,
processes, claims via `central.task_claims`). Explicit non-goals remain:
scheduler failover and live process migration.

Plan:
`docs/plans/2026-07-15-001-refactor-mesh-shared-postgres-multinode-plan.md`

## Test plan

- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/peer-exchange-service.test.ts`
- [x] `pnpm --filter @fusion/dashboard exec vitest run
src/__tests__/mesh-routes.test.ts`
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/shared-mesh-state.test.ts`
- [ ] CI gate (lint/typecheck/build/gate)
- [ ] Manual (optional): two processes, same `DATABASE_URL`, create task
on A visible on B; settings change without mesh settings sync; claim
exclusivity

## Operator note

Multi-node shared board requires **external** `DATABASE_URL` on every
node. Default embedded Postgres is still single-host.

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

* **New Features**
* Improved multi-node deployments using shared PostgreSQL as the durable
source of execution state.
* Task ID reservation/commit/abort now run locally (no remote
coordinator forwarding).
* Mesh syncing now prioritizes topology visibility and authentication
material; settings replication is disabled in shared-Postgres mode.
* **Bug Fixes**
* Prevented task/settings replication over mesh HTTP in shared-Postgres
deployments.
* Refined lease ownership, recovery, and reconciliation to converge via
shared-database primitives.
* **Documentation**
* Updated architecture and shared-mesh protocol guidance, including
multi-node setup and lease/task-ID allocation behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 13:32:33 -07:00
gsxdsm
f6e43d7232 fix: reclaim merger-ai autostashes and stop dropping untracked work
merger-ai's local-checkout sync stashed under fusion-ai-merge-sync-<taskId>,
a label none of merger.ts's reclamation machinery matches — every path keys
off the fusion-merger-autostash: prefix. Those entries were never classified,
never subsumed-dropped, never age-swept, and never surfaced as orphans holding
work, so they accumulated indefinitely: six entries dating back a month were
found on one working tree, and their age made real lost work indistinguishable
from litter. merger-ai now labels through buildAutostashLabel, and the legacy
prefix stays recognized so already-leaked entries are reclaimed rather than
stranded in developers' stash lists.

Routing them into that machinery first required fixing what it does with
untracked files. A stash created with --include-untracked keeps them in a
third parent (<sha>^3) that git stash show omits, so an untracked-only stash
read as empty — and all three copies of the liveness check treated empty as
"subsumed, safe to drop". Every leaked ai-sync stash carried untracked files,
so the fix would otherwise have destroyed the work it was meant to reclaim.
Liveness now resolves through one authority, classifyStashContent, which reads
both sides, diffs untracked paths against <sha>^3 rather than the stash commit
(whose tree never contained them), and treats unreadable state as unknown and
therefore undroppable.

Age-based sweeping is left alone: it drops by timestamp without consulting
content, which is deliberate bounded retention and the backstop against this
same accumulation, not a safety gap.

Regression test uses real git — the defect lives in git's stash object model,
so a mocked git can neither express nor catch it — and asserts the invariant
across tracked-only, untracked-only, and mixed stashes in both live and
subsumed states. The mixed shape (tracked subsumed, untracked live) is the one
that silently lost work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:22:22 -07:00
gsxdsm
5e5fa9a2be fix: auto-approve plans whose approval predates the prompt-hygiene injection
An operator was re-asked to approve a plan they had already approved and
that had not changed.

POST /tasks/:id/approve-plan fingerprints the on-disk PROMPT.md, so a plan
approved before the `## Original Description` hygiene injection
(applyOriginalDescription) shipped carries a hash over PRE-injection
content. On the task's next pass the injection rewrites PROMPT.md, the
fingerprint moves, and FN-7569's idempotency short-circuit misses — so the
manual gate re-parks an unchanged, already-approved plan.

finalizeApprovedTask now also compares the recorded fingerprint against the
as-read (pre-injection) content. This does not weaken the gate: `written`
diverges from `writtenInput` only via that injection, so both arms hash
bytes the operator actually approved — only the representation differs. A
genuinely changed plan matches neither arm and still parks.

On a legacy match the stored fingerprint is migrated forward, so the
reconciliation is one-time per task rather than a comparison carried
forever. The migration is a direct updateTask — the taskUpdates batch is
flushed well before this gate runs.

Covers both finalizeApprovedTask callers (direct + recoverApprovedTask),
asserts the changed-plan safety edge still parks, and asserts no redundant
fingerprint write when the approval is already post-hygiene.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:17:24 -07:00
gsxdsm
3b938887c9 test: fix FN-7569 plan-approval fixtures to model approved on-disk PROMPT.md
The recoverApprovedTask idempotency test failed deterministically, and its
siblings passed for the wrong reason. Both traced to the same stale fixture,
not a product defect.

finalizeApprovedTask injects `## Original Description` into PROMPT.md
(applyOriginalDescription) BEFORE computing the approval fingerprint, and
POST /tasks/:id/approve-plan fingerprints the on-disk file — so the
fingerprint an approval records is always over post-injection content. The
fixtures wrote RAW planner text and fingerprinted that, modelling a state
approve-plan can never produce: the injection then rewrote the content, the
fingerprint moved, and the short-circuit looked broken.

Verified the product is correct: the injection is idempotent, so the real
approve -> recover round-trip fingerprint matches (checked end to end).

- recoverApprovedTask test: write and fingerprint the approved on-disk
  content. It now exercises the real short-circuit — the run logs "plan
  unchanged since prior approval" then "recovered and moved to todo",
  where before it logged "awaiting manual approval".
- same-plan test: it only passed because the injection's rewrite ENOENT'd
  (no task dir), the failure was swallowed, and `written` stayed raw — so
  the fingerprint matched by accident. Feed it the approved content so the
  injection is a genuine no-op and the assertion means something.

Fixtures derive from applyOriginalDescription rather than hard-coding
post-injection text, so they keep meaning "the content the operator
approved" if the hygiene injection changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:10:17 -07:00
gsxdsm
2b8df56cb8 fix: escalate reviewer provider errors instead of looping on them
A rate-limited reviewer filled a task's Chat tab with 14 identical
"Reviewer using model: ..." markers and no review text, hammering an
already-limited provider.

Root cause: the reviewer was the only AI lane that never classified
provider errors, so a 429 became an UNAVAILABLE verdict. With no
validator fallback configured the fallback ladder re-ran the SAME model
instantly, and fn_review_step answered with "code review remains
blocking; retry once" — bounding the loop with prompt text rather than
code. The tool's catch-all also swallowed the error into tool output, so
withRateLimitRetry, UsageLimitPauser and RetryStormError never fired.

- reviewer: throw ReviewerProviderError for usage-limit/transient errors
  instead of laundering them into UNAVAILABLE, and never spend the
  fallback budget (which bounds bad reviews) on an outage.
- reviewer: absorb flaky-network blips in-lane via withRetry with
  jittered backoff; rate limits still escalate immediately.
- executor: re-raise the fatal after the prompt via
  throwDeferredReviewerFatal — pi-agent-core converts tool throws into
  tool_error results, so a tool cannot throw out of session.prompt().
- executor: give code review a real MAX_CODE_REVIEW_UNAVAILABLE_RETRIES
  counter, mirroring the plan/spec limiter.
- reviewer: dedupe the model marker on text, so same-model retries stay
  silent while a genuine model switch still emits.

Also fixes the run-on rendering: AgentLogType gains `status` for complete
engine messages. `text` means "streamed delta" and is re-glued with
join(""), which is why N standalone markers rendered as one string. The
split is at the type, not a separator — a separator would reintroduce the
FN-5787/5789/5803 streamed-spacing regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:54:16 -07:00
gsxdsm
3e978e1540 fix: quiet per-poll scheduler hold-release and routing log spam
Both lines fired on every scheduler poll while nothing changed: a held
card re-attempts release each sweep, and every dispatch candidate logged
its resolved node. On a busy board that filled the operator log pane with
"Hold release for FN-XXXX deferred" and "routed to node=local" within
seconds, burying real scheduler events.

Add a Logger.debug() level, off by default and opted into per subsystem
via FUSION_DEBUG, and demote both lines to it. Routing to a remote node
stays at info since it explains where work actually went; only the local
default is demoted. Lines reporting a real transition (capacity
rejection, racing sweep, release failure) are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:49:43 -07:00
gsxdsm
836e53c6c0 FN-7975: exclude engine-paused wall-clock from task active timing
Reconcile active task segment anchors on full Global/Engine unpause so stopped-engine wall-clock does not inflate execution time, reusing the FN-7011 downtime path with a transition-captured heartbeat.

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

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

Fusion-Task-Id: FN-7975

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

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 12:41:05 -07:00
gsxdsm
de25e32eac FN-7963: add plannerHeartbeatPatrolEnabled to gate idle heartbeat task creation
Add a workflow setting that disables idle/no-task heartbeat proactive task creation without turning off planner overseer stuck-task recovery.

- Declare plannerHeartbeatPatrolEnabled (default true) in BUILTIN_OVERSIGHT_SETTINGS
- Resolve the flag via resolveEffectivePlannerHeartbeatPatrolEnabled and wire it into agent-heartbeat/triage prompts
- Render patrol-off instruction when disabled; keep FN-7962 outage backoff lines when patrol stays enabled
- Cover setting defaults, prompt builders, and heartbeat executor paths with tests
- Document the setting in settings-reference and add a changeset

Files changed:
 .changeset/fn-7963-planner-heartbeat-patrol.md     |   7 ++
 docs/settings-reference.md                         |  11 +-
 packages/core/src/__tests__/agent-prompts.test.ts  |  29 +++++
 .../builtin-workflow-settings-triage.test.ts       |  21 ++++
 .../plannerHeartbeatPatrolEnabled-default.test.ts  |  64 ++++++++++
 packages/core/src/agent-prompts.ts                 |  55 +++++++--
 packages/core/src/builtin-workflow-settings.ts     |  14 +++
 packages/core/src/index.gate.ts                    |   5 +
 packages/core/src/index.ts                         |   5 +
 packages/core/src/workflow-settings-resolver.ts    |  15 ++-
 .../src/__tests__/heartbeat-executor.test.ts       |  59 ++++++++-
 packages/engine/src/agent-heartbeat.ts             | 135 +++++++++++++++++++--
 packages/engine/src/triage.ts                      |   8 +-
 13 files changed, 402 insertions(+), 26 deletions(-)

Fusion-Task-Id: FN-7963

Fusion-Task-Lineage: c5e7a382-52c1-4cc1-8b21-aba7dc7d2b97

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 12:27:15 -07:00
gsxdsm
93baf482f9 fix: tighten extension tool budgets after hang-fix review
Address review findings on the FN-7956 hang fix:
- Per-tool outer timeouts so fn_research_run(wait_for_completion) is not clipped by a flat 60s budget
- Longer budgets for skills install, import/browse, and web_fetch
- Boot-failure cooldown + orphan-boot log when store boot times out
- Log timeout/abort/errors from the extension wrap; clearer host-extension skip reason
- Tests for budgets, research wait, and sessionPurpose forwarding
2026-07-15 11:23:29 -07:00
gsxdsm
779954afee fix: seed rejected PROMPT.md on replan so Plan Review can converge
Plan Review REVISE previously fed feedback without the rejected plan body, so triage rewrote from title/description and looped. Seed the draft for surgical revision, use reviewType spec for the pre-execution gate, and tighten planner/reviewer prompts toward blocking-only REVISE with concrete edits.
2026-07-15 11:18:55 -07:00
gsxdsm
335b6a4dc2 fix: raise Plan Review replan cap to 8 and explain approval holds
Give planner/reviewer pairs more room to converge before escalating, and surface why a task is parked for plan approval—especially plan-review-replan-cap non-convergence—on cards, detail, and notifications.
2026-07-15 11:14:17 -07:00
gsxdsm
508453ad03 fix: stop merger/extension tools from wedging on hung fn_task_show
AI merge review could park forever when the host fusion extension loaded
fn_task_show and booted a second TaskStore without a tool timeout (FN-7956).

- Skip host @runfusion/fusion extensions for sessionPurpose "merger"
- Forward sessionPurpose into createFnAgent for that policy
- Coalesce + 30s-bound extension TaskStore boots; ALS-propagate AbortSignal
- Wrap every extension registerTool execute with 60s timeout/abort fail-closed
- Unit tests for merger host-extension skip and tool timeout helpers
2026-07-15 11:13:56 -07:00