## What & why
Task FN-8141 laundered a failed task into `done`: its branch had no net
changes vs `main` **only because the executor reverted its own work five
times**, and the AI empty-merge lane took the "empty means
already-landed or nothing-to-do → finalize as no-op done" path, stamping
`mergeConfirmed: true` with no reviewer or operator sign-off.
This restores the invariant: **a commit-expected task that reaches the
empty AI-merge outcome must not finalize `done` without positive
evidence the work already landed.**
`packages/engine/src/merger-ai.ts` empty-outcome lane now, for a
commit-expected task (`noCommitsExpected !== true`), requires one of:
1. Durable recorded landing on the task
(`mergeDetails.mergeConfirmed`/`commitSha`).
2. A prior AI no-op finalization proof pair in the task log
(`hasPriorAiNoOpFinalizationProof`, FN-7261 shape).
3. The branch tip is an **ancestor of the integration branch**
(fast-forwarded / zero-ahead / already-integrated).
4. The already-on-main classifier (`detectAlreadyLandedOnMain`) finds a
distinct landing commit via a **strong** strategy
(`trailer`/`ancestry`/`patch-id`).
The classifier's weak `tree-equal`/`no-diff` strategies are
**deliberately rejected**: a branch that committed work then reverted it
back to base has a tree equal to `main` (main never advanced), which is
exactly the FN-8141 shape and would false-positive. Absent proof, the
task gets `task.error` set, emits run-audit
`task:empty-merge-finalize-blocked-no-landed-proof`
(ids/counts/outcomes-only), and is moved back to `todo` with progress
preserved — mirroring the existing FN-6461 blocked lane.
`noCommitsExpected === true` tasks are untouched (hardened separately in
the sibling Task 1). The non-empty landed path, group/PR routing, and
push-after-merge behavior are unchanged.
## Surface enumeration
- **Single-repo empty-outcome finalize (primary lane)** — guarded in
`runAiMerge`.
- **Workspace/multi-repo caller** — `landWorkspaceTask`'s all-empty
finalize is a second route. Already-landed sub-repos are proven up front
by `findProvenLandedCommit` and marked `status:"landed"`; when
`landedCount === 0` the guard re-checks each empty sub-repo's branch and
blocks the FN-8141 reverted shape (tip not an ancestor / branch
vanished) identically. (Note: the genuinely-integrated all-empty
workspace case already throws `missing-merge-confirmation` on
`mergeConfirmed:false`, so it never reached `done`; that pre-existing
path is left intact.)
- **Re-promotion ping-pong** — the blocked path sets `task.error`, and
`recoverStrandedCompletedTodoTasks` excludes any task with `task.error`,
so the promoter cannot re-promote the unchanged blocked task.
Regression-tested.
## Test evidence
Scoped tests (all green):
```
vitest run merger-ai.test.ts workspace-merger.test.ts → 46 passed
vitest run self-healing.test.ts -t recoverStrandedCompletedTodoTasks → 4 passed
vitest run merger.test.ts merger-finalize-unproven.real-git → 20 passed
vitest run self-healing-workspace + workspace-merger-lease + workspace-merger-deps-resilient → 26 passed
```
New tests:
- merger-ai.test.ts: commit-expected empty (reverted) → blocked to todo
+ error + audit event, NOT done; empty + prior no-op proof → still no-op
done; empty + branch-ancestor-of-main → still no-op done;
noCommitsExpected empty → unchanged done path.
- workspace-merger.test.ts: all-empty (reverted) workspace → blocked to
todo + error, not done / not `task:merged`.
- self-healing.test.ts: a task blocked by this guard (all steps
done/skipped, `task.error` set) is NOT re-promoted by
`recoverStrandedCompletedTodoTasks`.
**`pnpm verify:fast` is red on this branch due to the pre-existing pi
SDK breakage** (`auth-storage.ts`/`pi.ts`/`provider-registration.ts` —
the FN-8142/FN-8145 `AuthStorage`/`ModelRegistry` removal that is the
root of the FN-8141 incident). Verified those identical build errors
reproduce with my changes stashed; this PR adds **zero** new type errors
(no build error is in `merger-ai.ts` or `run-audit.ts`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus <noreply@anthropic.com>
## 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>
## 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>
Show Activity Live reasoning immediately for active and review-stage tasks.
- Default thinking blocks open for in-progress and in-review tasks.
- Preserve collapsed, user-toggleable reasoning for all other task columns.
- Document behavior and cover default state and user collapse interactions.
Files changed:
.changeset/fn-8171-thinking-default-open.md | 7 ++++
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskChatTab.tsx | 20 +++++++---
.../app/components/__tests__/TaskChatTab.test.tsx | 43 ++++++++++++++++++++--
4 files changed, 63 insertions(+), 9 deletions(-)
Fusion-Task-Id: FN-8171
Fusion-Task-Lineage: 705d6ac8-0c43-47d3-84df-57e9ec55a3b5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Make the mobile More sheet participate in navigation history so Back dismisses it before changing views.
- Register the open More sheet as a navigation modal and remove it on every close path
- Cover browser, native, gesture, keyboard, action, and provider-less dismissal flows
- Document mobile More-sheet Back behavior
Files changed:
docs/dashboard-guide.md | 2 +
packages/dashboard/app/components/MobileNavBar.tsx | 48 ++++--
.../__tests__/MobileNavBar.swipe-back.test.tsx | 190 +++++++++++++++++++++
3 files changed, 228 insertions(+), 12 deletions(-)
Fusion-Task-Id: FN-8168
Fusion-Task-Lineage: 1c5652db-5f27-4671-b4b7-676753ca4cd0
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Remove the residual mobile Feed right inset so task details use balanced body padding.
- Let the task-detail body provide the mobile right inset.
- Retain first-row clearance for overlay controls.
- Cover mobile Feed padding and attached tab behavior with regression tests.
- Add a patch changeset for the dashboard fix.
Files changed:
.../fn-8166-task-detail-mobile-right-padding.md | 7 +++
.../dashboard/app/components/TaskDetailModal.css | 8 +--
.../TaskDetailModal.attachments-and-tabs.test.tsx | 2 +-
...etailModal.responsive-and-dependencies.test.tsx | 63 +++++++++++++++++++++-
4 files changed, 74 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-8166
Fusion-Task-Lineage: ae58ae8b-4659-429f-8939-1d7e1a82d34c
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
pnpm 10.33 warns on every install and build:
[WARN] The "pnpm" field in package.json is no longer read by pnpm. The
following keys were ignored: "pnpm.onlyBuiltDependencies",
"pnpm.overrides"
The wording overstates it for the pinned version: on pnpm 10.33 the
field is still honoured, so nothing is broken today. It is a
forward-looking deprecation notice, and pnpm 11 makes it real — there
the overrides silently stop applying.
Reproduced on pnpm 11 with the workspace pins declared only in
package.json:
overrides block dropped from pnpm-lock.yaml
@types/node ^25.5.2 -> ^22.0.0 (25.5.2 -> 22.20.1)
zod 4.3.6 -> 3.25.76
@types/node fragments into 5 versions across the workspace
pnpm-workspace.yaml is the supported home and is honoured by both 10.33
and 11, so moving `overrides` there is safe on the pinned version and
correct for the next major.
Verified on pnpm 10.33: `pnpm install --lockfile-only` leaves
pnpm-lock.yaml byte-identical and its `overrides` block is now sourced
from pnpm-workspace.yaml. Verified on a minimal repro that pnpm 10.33
applies yaml overrides (ms -> 2.0.0).
Scope is deliberately limited to `overrides`. `ignoredBuiltDependencies`
and `onlyBuiltDependencies` are left in place: they are also honoured on
10.33 and ignored on 11, but the workspace list is not a superset of the
package.json one (it omits `electron`, removed in c7641f9d when electron
became a runtime dependency, and three @embedded-postgres arches outside
the `supportedArchitectures` cpu list curated in 1b9dce22). Reconciling
those is a maintainer call about intended behaviour, not a mechanical
move, so it is left for a follow-up rather than guessed at here.
No changeset: workspace/build config only, behaviour-preserving on the
pinned pnpm, and it does not affect published @runfusion/fusion.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated workspace dependency version overrides for improved
consistency across packages.
* Consolidated dependency configuration in the workspace settings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
## 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>
## 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>
Let GitHub pull request previews refresh their current check status and comments.
- Add an accessible refresh action that bypasses the selected PR detail cache
- Prevent stale refresh responses from overwriting cached or visible PR details
- Cover refresh behavior across modal and embedded views, and document the control
- Add a minor changeset for the new GitHub import capability
Files changed:
.changeset/fn-8137-refresh-pr-checks.md | 7 ++
docs/dashboard-guide.md | 2 +-
.../dashboard/app/components/GitHubImportModal.css | 29 ++++++
.../dashboard/app/components/GitHubImportModal.tsx | 48 +++++++--
.../__tests__/GitHubImportModal.test.tsx | 115 +++++++++++++++++++++
5 files changed, 191 insertions(+), 10 deletions(-)
Fusion-Task-Id: FN-8137
Fusion-Task-Lineage: 1d4ffa9d-635c-4d59-b17c-63075f6d8c5e
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
Follow-up after **#2164** and main’s **FN-8103 / FN-8104**
(Postgres-only data access / SQLite retirement).
Main already routes Quality through `AsyncQualityStore` +
`getAsyncLayer()`. This PR keeps the **review hardening** that was still
missing:
- **Project binding** — reject request `projectId` mismatches vs bound
AsyncDataLayer; all SQL uses bound project
- **`createRunIfNoActive`** — advisory lock so concurrent starts cannot
double-queue
- **Cancel-safe runner** — cancel slot registered before the running
write; pre-spawn cancel skips process
- **`finalizeRun`** — never overwrites a `cancelled` terminal status
- **Detached execute** — catch only execution failures; prune fail-soft
in `finally`
- Guardrail tests + Quality v2 plan doc
## Test plan
- [ ] Task QA loads under PostgreSQL (no SQLite/backend-mode error)
- [ ] Concurrent start for same task → 409 second start
- [ ] Cancel during start does not leave a live orphan process
- [ ] Cancelled run stays cancelled after process exit
- [ ] `pnpm --filter @fusion-plugin-examples/quality test` (37 tests)