bc7dfe4bbfcb2b4f2b9d01aead3cb59fadac5bbc
2367 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
c449379d00 |
FN-8172: persist import translations across restarts
Persist GitHub and GitLab import translation caches across application restarts. - Normalize cache ownership scope for reads, writes, pruning, and PostgreSQL RLS - Add forward migration 0016 to repair existing cache table partitioning - Cover durable cache reuse and migration behavior with PostgreSQL and service tests Files changed: .changeset/github-translation-cache-persistence.md | 7 ++ docs/settings-reference.md | 2 +- .../postgres/import-translation-cache.pg.test.ts | 105 +++++++++++++++++++++ .../src/__tests__/postgres/schema-applier.test.ts | 84 +++++++++++++++-- .../migrations/0010_import_translation_cache.sql | 6 +- .../0016_import_translation_cache_scope_fix.sql | 50 ++++++++++ packages/core/src/postgres/schema-applier.ts | 29 +++++- packages/core/src/postgres/schema/project.ts | 8 +- packages/core/src/task-store/remaining-ops-8.ts | 21 +++-- .../src/__tests__/import-translate-service.test.ts | 32 ++++++- packages/dashboard/src/import-translate-service.ts | Bin 10392 -> 11109 bytes 11 files changed, 323 insertions(+), 21 deletions(-) Fusion-Task-Id: FN-8172 Fusion-Task-Lineage: b3d18f58-dc0b-47e4-a1bf-acb8b2360869 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
07e1ceb860 |
FN-8151: add Cobalt, Clay, and Moss themes
Add three synchronized color themes across dashboard and desktop surfaces. - Register Cobalt, Clay, and Moss theme IDs, token palettes, and light/dark swatches. - Keep dashboard and desktop bootstrap validators aligned with selector metadata. - Document and test the cross-surface theme registration invariant. Files changed: .changeset/fn-8151-new-themes.md | 7 + docs/dashboard-guide.md | 3 +- packages/core/src/types/execution-and-ui.ts | 4 + .../app/__tests__/new-color-themes.test.ts | 116 ++++++++++++ .../dashboard/app/components/ThemeSelector.css | 42 +++++ packages/dashboard/app/components/themeOptions.ts | 3 + packages/dashboard/app/index.html | 2 +- packages/dashboard/app/public/theme-data.css | 202 +++++++++++++++++++++ packages/desktop/src/renderer/index.html | 3 + 9 files changed, 380 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-8151 Fusion-Task-Lineage: d97973a7-3387-4b98-b150-8ac2e838bfa9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
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> |
||
|
|
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> |
||
|
|
fd036665ae |
FN-8123: add agent clarification notifications
Add agent clarification notices with ntfy delivery and mailbox support. - Add configurable clarification ntfy topics and notification settings - Route agent clarification requests to ntfy and the planning mailbox - Update planning dialogs, translations, documentation, and regression tests Files changed: .changeset/fn-8123-agent-clarification.md | 7 + docs/dashboard-guide.md | 6 + docs/settings-reference.md | 1 + .../core/src/__tests__/global-settings.test.ts | 11 + packages/core/src/settings-schema.ts | 2 + packages/core/src/types.ts | 6 + .../app/__tests__/settings-save-split.test.ts | 13 ++ packages/dashboard/app/api/legacy.ts | 3 +- .../dashboard/app/components/PlanningModeModal.tsx | 29 ++- .../dashboard/app/components/SettingsModal.tsx | 1 + .../__tests__/PlanningModeModal.initial.test.tsx | 17 +- .../app/components/settings/save-split.ts | 1 + .../settings/sections/NotificationsSection.tsx | 11 + .../src/__tests__/routes-planning.test.ts | 7 +- packages/dashboard/src/planning.ts | 228 ++++++++++++++++----- .../src/routes/register-planning-subtask-routes.ts | 59 +++++- packages/i18n/locales/en/app.json | 4 + packages/i18n/src/resources.d.ts | 4 + 18 files changed, 346 insertions(+), 64 deletions(-) Fusion-Task-Id: FN-8123 Fusion-Task-Lineage: cf397749-01c6-44d6-8d12-8ce7f5627c29 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
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> |
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
d4914eb8b3 |
FN-8127: fix embedded PostgreSQL backups
Enable backup managers to resolve active embedded PostgreSQL runtime URLs safely. - Track embedded backend URLs with generation-aware lifecycle leases. - Keep backup resolution current through owner shutdown and joiner release. - Document PostgreSQL client-tool requirements and add regression coverage. Files changed: .changeset/fn-8127-embedded-backup.md | 7 ++ docs/settings-reference.md | 3 + packages/core/src/__tests__/backup.test.ts | 115 ++++++++++++++++++++ packages/core/src/backup.ts | 17 ++- packages/core/src/index.gate.ts | 9 ++ packages/core/src/index.ts | 9 ++ .../core/src/postgres/active-backend-registry.ts | 119 +++++++++++++++++++++ packages/core/src/postgres/embedded-lifecycle.ts | 5 + packages/core/src/postgres/index.ts | 9 ++ packages/core/src/postgres/startup-factory.ts | 118 +++++++++++++++++--- 10 files changed, 389 insertions(+), 22 deletions(-) Fusion-Task-Id: FN-8127 Fusion-Task-Lineage: 6125be5c-d1d5-4228-a6b1-290311de70d9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
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> |
||
|
|
06d03d4e1f |
FN-8103: enforce PostgreSQL-only production data access
Require production paths to use PostgreSQL-aware stores and prevent new unrestricted database access. - Add a checked allowlist that bans production getDatabase() calls by default. - Route Quality plugin persistence through an async PostgreSQL-aware store and add Drizzle ORM. - Document backend-safe plugin storage patterns and cover guarded access behavior. Files changed: docs/PLUGIN_AUTHORING.md | 20 +++ package.json | 6 +- .../src/__tests__/agent-logs-backend-mode.test.ts | 7 + packages/core/src/store.ts | 7 +- packages/core/src/task-store/remaining-ops-5.ts | 8 +- plugins/fusion-plugin-quality/package.json | 1 + .../src/__tests__/async-quality-store.pg.test.ts | 36 +++++ .../src/__tests__/cancel-and-plans.test.ts | 8 +- .../src/__tests__/experimental-gate.test.ts | 1 + .../src/routes/create-routes.ts | 50 +++---- .../src/runner/command-runner.ts | 17 ++- .../src/store/async-quality-store.ts | 34 +++++ pnpm-lock.yaml | 3 + scripts/__tests__/check-no-getdatabase.test.mjs | 90 ++++++++++++ scripts/check-no-getdatabase.mjs | 159 +++++++++++++++++++++ scripts/lib/getdatabase-allowlist.json | 18 +++ 16 files changed, 422 insertions(+), 43 deletions(-) Fusion-Task-Id: FN-8103 Fusion-Task-Lineage: ff17bcb2-5341-4c6c-a5c4-993580539676 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
291fabcce8 |
FN-8083: preserve GitLab tracking across archive restore
Preserve GitLab import tracking metadata throughout TaskStore archive and restore flows. - Store GitLab tracking data in archived task entries and restored tasks - Hydrate GitLab tracking when serializing archive entries - Cover live reads, searches, modified-task listings, and archive restoration in PostgreSQL tests - Add a patch changeset for the tracking persistence fix Files changed: .changeset/fn-8083-gitlab-tracking-hydration.md | 7 +++ .../store-gitlab-tracking-hydration.pg.test.ts | 58 +++++++++++----------- .../core/src/task-store/archive-lifecycle-2.ts | 7 +++ packages/core/src/task-store/serialization.ts | 1 + 4 files changed, 43 insertions(+), 30 deletions(-) Fusion-Task-Id: FN-8083 Fusion-Task-Lineage: 877f320f-11c9-469d-8ea5-b843e95b78d3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
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> |
||
|
|
de1638e262 |
FN-8090: use mmap shared memory for embedded PostgreSQL
Enable constrained-host embedded PostgreSQL startup without SysV shared-memory exhaustion. - Default embedded lifecycle flags to mmap-backed shared memory while preserving caller overrides - Cover normal and elevated Windows launch paths with deterministic flag propagation tests - Document the 64MB /dev/shm support floor and add a patch changeset Files changed: .changeset/fn-8090-embedded-pg-shm.md | 7 ++ docs/postgres-migration-review-2026-07-14.md | 4 + docs/storage.md | 5 ++ .../__tests__/postgres/embedded-lifecycle.test.ts | 88 ++++++++++++++++++++++ .../postgres/embedded-windows-admin.test.ts | 17 +++++ packages/core/src/postgres/embedded-lifecycle.ts | 50 +++++++++++- .../core/src/postgres/embedded-windows-admin.ts | 2 +- 7 files changed, 168 insertions(+), 5 deletions(-) Fusion-Task-Id: FN-8090 Fusion-Task-Lineage: ac175843-69ba-4c9c-9692-aff095fc351f Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
6675cdf696 |
FN-8094: persist GitLab tracking metadata
Restore shared TaskStore persistence and hydration for GitLab tracking metadata. - Register GitLab tracking JSONB for task creation, updates, and row hydration. - Reuse the shared mapper during GitLab reconciliation and cover live/deleted reads. - Migrate GitLab extension tests to the PostgreSQL harness and add a patch changeset. Files changed: .changeset/fn-8094-gitlab-tracking-mapping.md | 7 ++ .../__tests__/extension-gitlab-tracking.test.ts | 129 ++++++++++----------- packages/cli/src/__tests__/pg-extension-harness.ts | 4 - .../store-gitlab-tracking-hydration.pg.test.ts | 85 ++++++++++++++ .../store-gitlab-tracking-reconcile.test.ts | 11 +- packages/core/src/task-store/persistence.ts | 9 +- packages/core/src/task-store/remaining-ops-2.ts | 10 +- packages/core/src/task-store/serialization.ts | 1 + packages/core/src/task-store/task-creation.ts | 2 + 9 files changed, 174 insertions(+), 84 deletions(-) Fusion-Task-Id: FN-8094 Fusion-Task-Lineage: 5e876856-cf42-4628-a5ef-ab562c1bf501 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
f63818a6e9 |
FN-8072: add critical-action confirmation skip setting
Add a global operator preference that bypasses centralized critical-action confirmation dialogs. - Add a global-only skipConfirmationDialogs setting and Settings toggle - Return primary/default confirmation results without rendering dialogs when enabled - Route task reset actions through the centralized confirmation seam and add coverage - Document the setting and add a minor changeset Files changed: .changeset/fn-8072-skip-confirmation-dialogs.md | 7 +++++ docs/settings-reference.md | 1 + .../core/src/__tests__/settings-defaults.test.ts | 12 ++++++++- packages/core/src/settings-schema.ts | 5 ++++ packages/core/src/types.ts | 5 ++++ packages/dashboard/app/App.tsx | 11 ++++---- packages/dashboard/app/components/ListView.tsx | 20 +++++++++++---- packages/dashboard/app/components/TaskCard.tsx | 22 +++++++++++----- .../dashboard/app/components/TaskDetailModal.tsx | 26 +++++++++++-------- .../app/components/__tests__/ListView.test.tsx | 13 ++++++++++ .../__tests__/SettingsModal.general.test.tsx | 19 ++++++++++++++ .../app/components/__tests__/TaskCard.test.tsx | 17 ++++++++++++ .../components/__tests__/TaskDetailModal.test.tsx | 25 ++++++++++++++++++ .../app/components/settings/save-split.ts | 1 + .../sections/GlobalGeneralSection.search.ts | 10 ++++++++ .../settings/sections/GlobalGeneralSection.tsx | 10 ++++++++ .../settings-default-descriptions.test.tsx | 1 + .../app/hooks/__tests__/useConfirm.test.ts | 30 ++++++++++++++++++++++ packages/dashboard/app/hooks/useAppSettings.ts | 4 +++ packages/dashboard/app/hooks/useConfirm.ts | 21 ++++++++++++++- packages/i18n/locales/en/app.json | 4 ++- 21 files changed, 235 insertions(+), 29 deletions(-) Fusion-Task-Id: FN-8072 Fusion-Task-Lineage: bab4b8f2-5996-4161-8733-8e03bb6a7024 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
90ce57f127 |
FN-8070: preserve task title input language
Ensure auto-generated task titles use the task description's language. - Detect description language and add a confident language hint to title prompts - Cover multilingual title generation and stale-model fallback behavior - Document the language-preserving title-summary setting and add a patch changeset Files changed: .changeset/fn-8070-title-summary-input-language.md | 7 ++ docs/settings-reference.md | 2 +- packages/core/src/__tests__/ai-summarize.test.ts | 87 ++++++++++++++++++---- packages/core/src/ai-summarize.ts | 18 +++++ 4 files changed, 98 insertions(+), 16 deletions(-) Fusion-Task-Id: FN-8070 Fusion-Task-Lineage: e003e2e0-eb0c-487e-83de-8a9b37ee1c02 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
f3b68c9fff |
FN-8065: preview plans before refinement questions
Show a read-only plan preview at the Planning Mode deepening checkpoint. - Persist pending plan details on the checkpoint question for fresh and restored sessions. - Render formatted plan content and deliverables above refinement choices. - Cover preview behavior and document the checkpoint flow. Files changed: .../fn-8065-planning-checkpoint-plan-preview.md | 7 +++ docs/dashboard-guide.md | 3 +- packages/core/src/types.ts | 11 ++++ .../dashboard/app/components/PlanningModeModal.css | 60 ++++++++++++++++++++++ .../dashboard/app/components/PlanningModeModal.tsx | 44 ++++++++++++++++ .../PlanningModeModal.planning-flow.test.tsx | 24 ++++++++- .../planning-interview-formatters.test.ts | 19 +++++++ packages/dashboard/src/planning.ts | 10 ++++ 8 files changed, 175 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-8065 Fusion-Task-Lineage: 13cd52c3-af82-4723-a6b1-3366775213e1 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
e87b51bd07 |
FN-8054: add pinned chat conversations
Add durable, scoped pinning for Direct chat conversations. - Add pinned session persistence, migration coverage, and archive-safe row locking. - Enforce a three-conversation per-project pin limit through the chat API. - Add desktop and mobile pin controls, sorting, indicators, and regression tests. Files changed: .changeset/fn-8054-pin-conversations.md | 7 ++ docs/dashboard-guide.md | 2 + .../postgres/satellite-db-injected-stores.test.ts | 13 ++++ packages/core/src/async-chat-store.ts | 27 ++++++++ packages/core/src/chat-store.ts | 63 ++++++++++++++++-- packages/core/src/chat-types.ts | 9 +++ .../core/src/postgres/migrations/0000_initial.sql | 1 + .../postgres/migrations/0012_chat_session_pins.sql | 8 +++ packages/core/src/postgres/postgres-health.ts | 3 + packages/core/src/postgres/schema-applier.ts | 30 ++++++++- packages/core/src/postgres/schema/project.ts | 3 + packages/dashboard/app/api/legacy.ts | 1 + packages/dashboard/app/components/ChatView.css | 32 +++++++++- packages/dashboard/app/components/ChatView.tsx | 74 ++++++++++++++++++++-- .../dashboard/app/hooks/__tests__/useChat.test.ts | 21 ++++++ packages/dashboard/app/hooks/useChat.ts | 72 +++++++++++++++++---- .../dashboard/src/routes/register-chat-routes.ts | 25 +++++++- 17 files changed, 366 insertions(+), 25 deletions(-) Fusion-Task-Id: FN-8054 Fusion-Task-Lineage: 088cb01c-582b-4f56-a222-214da90ff356 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
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> |
||
|
|
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> |
||
|
|
375368e147 |
FN-8051: ensure PostgreSQL schemas initialize before plugin hooks
Ensure required PostgreSQL namespaces exist before plugin initialization on every boot. - Create project, central, and archive schemas under the schema advisory lock before hooks run - Cover marker-present databases with a plugin-hook schema availability regression test - Add a patch changeset for the reliability fix Files changed: .changeset/fn-8051-schema-init.md | 7 ++++ .../src/__tests__/postgres/schema-applier.test.ts | 43 ++++++++++++++++++++++ packages/core/src/postgres/schema-applier.ts | 12 ++++++ 3 files changed, 62 insertions(+) Fusion-Task-Id: FN-8051 Fusion-Task-Lineage: a3b20683-a742-4a8c-9cfc-fbf316c5649b Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
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> |
||
|
|
261901343e |
fix(core): split the domain project field from the RLS partition column (#2165)
## Problem Migration 0006 made `project_id` the RLS isolation partition on every `project`-schema table — stamped by a BEFORE INSERT trigger from the `fusion.project_id` session GUC, with every PK/unique/FK rewritten to composite `(project_id, …)`. Eleven tables **also** carried a caller-supplied domain `projectId` on their TS types and wrote that domain value into the same physical column. When the domain value differs from the session GUC, the parent row lands in the domain partition while child rows (`research_run_events`, `experiment_session_records`, `eval_task_results`, …) land in the session partition — and the composite FK fails with SQLSTATE 23503. Appending an event to a project-owned research run could not persist. ## Fix **Decision (operator): separate domain column; `project_id` stays the partition.** - **Migration `0011_owner_project_id.sql`** adds a nullable `owner_project_id` domain column to the 11 conflated tables (`research_runs`, `experiment_sessions`, `todo_lists`, `eval_runs`, `chat_sessions`, `chat_rooms`, `ai_sessions`, `chat_token_usage`, `project_insights`, `project_insight_runs`, `cli_sessions`), backfills it from `project_id` (identical in production, so exact; the `__legacy_unscoped__` sentinel backfills to NULL), and indexes it. Idempotent, `to_regclass`-guarded per the 0007 pattern. - **Stores** (`async-research-store`, `async-experiment-session-store`, `async-todo-store`, `async-chat-store`, `async-ai-session-store`, `async-eval-store`, `async-insight-store`, `cli-session-store`, …) stop writing `project_id` entirely — the trigger/GUC owns the partition — and map their domain `projectId` field to `owner_project_id` for both reads and filters. TS types unchanged. - **Applier** registers `OWNER_PROJECT_ID_SPLIT_VERSION = "0011"` and advances `SCHEMA_BASELINE_VERSION`. ## Verification (re-run independently of the implementing agent) - Core `tsc --noEmit`: exit 0 · `pnpm lint`: exit 0 · `pnpm check:changesets`: exit 0 · `pnpm test:gate`: 185/185 - Full postgres suite: **5 failed / 807 passed** vs a **7 / 804** baseline — the two conflation round-trips (`satellite-db-injected-stores` ResearchStore + ExperimentSessionStore) go green, zero new failures. The remaining 5 are pre-existing unbound-harness `__meta`/identity failures, unrelated to this change. 🤖 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** * Corrected project-scoped persistence and queries across AI sessions, chats (rooms + token usage), evaluations/experiments, insights, research, and todos by separating domain ownership from RLS partitioning. * Prevented foreign-key and row-level security violations when storing or retrieving project-scoped data, including legacy records. * **Database / New Features** * Added migration 0011 introducing `owner_project_id` and backfilling existing rows to preserve ownership while improving isolation. * **Tests** * Updated migration-parity coverage to include the new baseline step. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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 --> |
||
|
|
08c546dc75 |
test(core): bind the U14 postgres harness to a project, as production does
The usage-events round-trip failed because the harness ran unbound. Production
binds `fusion.project_id` per connection (connection.ts) and only falls back to
`fusion.project_bypass=on` when no projectId is given, so an unbound harness
wrote blank project_ids that the migration-0006 trigger rewrote to
'__legacy_unscoped__' -- and helpers scoping on `layer.projectId ?? ""` then
looked for a literal '' the database never stores.
Unbound is a shape production forbids: AgentStore.backendProjectId throws on it
("Reject unbound backend heartbeat/run access instead of silently reading or
writing the legacy empty-string partition"). The harness was wrong, not the
product -- an earlier attempt to make the product accommodate the unbound
harness was reverted in
|
||
|
|
b51de02a54 |
Revert "fix(core): resolve unbound project ids to a real partition or no filter"
This reverts commit
|
||
|
|
a048a619fc |
fix(core): resolve unbound project ids to a real partition or no filter
Six of the eight postgres-suite failures shared one root cause: writes
normalize project_id, reads did not. The fusion_assign_project_id trigger
(migration 0006) rewrites a blank project_id to the session's fusion.project_id
or '__legacy_unscoped__', but helpers reached as `layer.projectId ?? ""` then
filtered on the literal '' -- a value the database never stores. Every unbound
read missed rows it had just written.
AsyncDataLayer.projectId is optional by design (undefined = project-agnostic),
so `?? ""` is the bug: it turns "no scope" into a scope that matches nothing.
The resolution differs by what the rows are, and conflating them corrupts data:
- Data and analytics reads (usage events, agent runs, research runs) take
projectScopeFor(): a bound id filters, an unbound one reads across projects.
This matches the contract taskProjectScope already documents ("when undefined
the scope filter is a no-op").
- __meta migration guards (project-identity stamps, agent-store markers) take
projectPartitionId(): an unbound id resolves to the shared sentinel
partition. projectScopeFor would be wrong here -- dropping the predicate lets
an unbound getMetaValue return whichever project's marker it finds first, so
on the shared cluster project A's "migration complete" marker would tell
project B to skip a migration it never ran. upsertMetaValue already documented
this: "the empty binding remains the explicit project-agnostic compatibility
partition". Writing the sentinel explicitly also keeps the partition
deterministic -- a blank write from a session carrying fusion.project_id would
otherwise land in that project's stamp.
Names the sentinel (LEGACY_UNSCOPED_PROJECT_ID) instead of open-coding it, and
puts both helpers next to taskProjectScope so the convention has one home.
Fixes taskstore-remaining (24/24), project-identity (6/6), and
satellite-fusiondir-stores (16/16).
The remaining two failures are a different bug and are NOT addressed here: the
child tables research_run_events and experiment_session_records never declared
project_id in schema-as-code, though migration 0006 added the column and
rewrote their FKs to composite (project_id, parent_id). Drizzle therefore cannot
write the parent's partition, the trigger stamps '__legacy_unscoped__', and the
FK fails against a project-owned parent. That needs a schema-as-code change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
a588c38784 |
fix(core): read usage events across projects when the layer is unbound
An unbound (project-agnostic) data layer read zero usage events it had just
written. AsyncDataLayer.projectId is optional by design -- undefined means a
project-agnostic layer for single-project / global / analytics reads -- but
helpers taking `projectId: string` are called as `layer.projectId ?? ""`, which
turns "no scope" into a literal '' scope.
'' never matches: the fusion_assign_project_id BEFORE INSERT trigger (migration
0006) rewrites a written '' to the session's fusion.project_id or
'__legacy_unscoped__', so a read filtering on '' looks for a value the database
never stores. Writes normalize, reads did not. Proven by probe: the row is
present with project_id '__legacy_unscoped__', emitUsageEvent returns true, and
queryUsageEvents returns [] even with no other filters.
Treat blank as unbound and drop the scope predicate, matching the contract
taskProjectScope already documents ("when undefined the scope filter is a
no-op"). Restricting an unbound reader to '__legacy_unscoped__' rows instead
would make an unscoped analytics read silently partial.
Adds projectScopeFor() next to taskProjectScope so the convention has one home
rather than a third open-coded variant.
Note the write path is already live: remaining-ops-7.ts emits with
`layer.projectId ?? ""` under backendMode, so unscoped events are accumulating
under the sentinel today. The async reader has no production caller yet, which
is why nothing user-facing broke.
Fixes taskstore-remaining.test.ts (24/24). The remaining failures in that suite
share this root cause but not this resolution -- see the follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
d1bda3683c |
fix(core): reap the losing wrapper and stop self-joining on a startup race
Two related leaks on the embedded Postgres startup-race join. The flagged one: the catch dropped `nonAdminHandle` to null without stopping it, so a wrapper that onLaunched had already published leaked. The obvious fix -- call handle.stop() first -- is worse than the leak. stop() runs killAll(), which resolves its target by reading line 1 of the data dir's postmaster.pid. On this path that file belongs to the process that WON the race, so stop() would taskkill the instance we are joining. pg.stop() is the same trap via pg_ctl -D on the shared dir, which is why settleCancelledStart (it calls both) cannot be reused here. Added NonAdminServerHandle.stopWrapperOnly(), which kills only our wrapper pid and its children, and called it before the handle is dropped. A racing winner is another process's child, so /t cannot reach it. The one found while making that safe: the catch joined on ANY start failure. A start that took the lock and then failed later (readiness timeout, non-admin poll error) reads back its OWN postmaster.pid, so isAlreadyRunning hands back our own port and we "join" ourselves with ownsProcess=false -- nothing ever stops it, orphaning a live postmaster for the life of the host. The join now fires only on a lock-collision error, which is the one failure proving our postgres refused to start and someone else owns the dir. Every other failure returns to the existing cancellation/cleanup paths, which stop what they started. That is also what makes the wrapper-only kill provably safe: on this path our postgres never took the lock. Tests: a non-lock failure must propagate even with a postmaster.pid present (fails without the fix -- the old catch swallowed it and joined), and a lock collision must still join. Both always-on with a mocked ctor. Pre-existing and unrelated: taskstore-remaining.test.ts fails identically on a clean tree with these changes stashed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
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>
|