## Summary
Adds the **Stash memory backend** (`memory.backendType=stash`) that
connects Fusion's agent memory to the
[Stash](https://github.com/Fergana-Labs/stash) product — *knowledge
bases for the agent era* ([product site:
joinstash.ai](https://joinstash.ai)). Fusion becomes a first-class Stash
client: complete chat sessions and finished tasks are captured into
Stash, memory is recalled during chat, and Stash sessions are kept in
sync with the dashboard (including deletes and archival).
**Product:** <https://github.com/Fergana-Labs/stash> ·
[joinstash.ai](https://joinstash.ai)
## What's included
### 1. Stash memory backend (RUFU-068 / RUFU-121)
- New `StashMemoryBackend` (`memory.backendType=stash`) with `stashUrl`
/ `stashApiKey` settings (global secrets-store `stash-api-key` +
per-project override).
- **Complete-chat-session capture** keyed by ChatSession id.
- Sessions are classified into **per-project folders** (get-or-create,
`external_key fusion-<projectId>`, 1h per-process cache) and
**soft-deleted with their chat** via `DELETE /api/chat/sessions/:id`.
- Per-conversation **memory-focus** read-time scoping (new
`0066_chat_session_memory_focus.sql` migration — sequence renumbered
0059→0060→0061→0065→0066 as origin/main claimed the lower numbers);
event metadata enriched with `project` / `project_name` / `chat_title`.
- Recall queries normalized to single-keyword / explicit-OR ASCII (≤100
chars); shared normalizer export reused by per-turn recall.
### 2. Per-task executor transcript capture (RUFU-122)
Finished or failed tasks upload their executor transcript
(`agent-log.jsonl`) to Stash as a task session.
### 3. Bulk archive Stash sync (RUFU-125)
Archived task-planner chats soft-delete their Stash sessions on bulk
archival (paged). The snapshot of doomed session ids is taken *before*
the local bulk delete, and the Stash sync runs fire-and-forget so a
Stash stall can never delay local archival.
### 4. Per-chat "Preserve to Stash" backfill (RUFU-136)
A per-chat action that backfills a chat's full history into Stash, with
client-side idempotency and a pre-check that skips already-uploaded
content (fail-closed, no duplicate upload on transport failure).
- **Session-folder naming fix:** the first project folder is now named
"Fusion — <project name>" instead of the bare "Fusion" fallback
(the backfill now resolves the central-registry project name,
best-effort, never blocking the upload).
### 5. Opt-in semantic (vector) recall (RUFU-126)
`stashVectorSearch` setting (default `false` — **zero behavior change
until enabled**). For multi-word queries the backend tries Stash's
semantic-search endpoint first, then falls back byte-identically to the
keyword path. Definitive 404/405/501/503 responses are negatively cached
per process. Requires a patched Stash server (new endpoint +
`sentence-transformers` + embedding backfill); unpatched servers are
transparently bypassed after the first 404.
## Safety
- **Opt-in / inert by default:** the default backend remains `qmd`; the
Stash backend is inert until `memoryBackendType=stash` + `stashUrl` are
set.
- All Stash I/O is **best-effort, fail-closed, and non-blocking** — a
Stash outage never blocks chat, task completion, or archival. No
run-audit content is emitted.
## Testing
- Backfill + delete-sync suites (20/20), Stash backend suite (68/68),
executor memory / session capture suites, `memory-focus-recalling`,
description-guard — all green.
- `tsc` clean across core / engine / dashboard.
- Live verification: bulk backfill of 21/24 chats completed; the
"Preserve to Stash" action is idempotent on re-run.
## Changesets
- `@runfusion/fusion` **minor** — Stash memory backend + capture
(RUFU-068/121), per-task transcript (RUFU-122), bulk archive sync
(RUFU-125), per-chat backfill (RUFU-136), opt-in vector search
(RUFU-126)
- `@runfusion/fusion` **patch** — backfill session-folder naming fix
## Rebase Note (2026-08-23)
Rebased onto `origin/main` `3f448f7292` (v0.77.0-beta.7). Conflicts
resolved additively:
- `packages/core/src/postgres/schema-applier.ts` + test — upstream's
0062-0065 migrations (task/subtask splitting removal, AI merge review
reconciliation, task repository scope, FN-149 review convergence)
unioned with this PR's `chat_sessions.memory_focus` migration, which is
**renumbered 0065 → 0066** (upstream's FN-149 shipped 0065 canonically
on origin/main); `SCHEMA_BASELINE_VERSION` advances to `0066`.
- `packages/dashboard/app/components/ChatView.tsx` — upstream's docked
chat sidebar resize handlers unioned with the RUFU-136 "Preserve to
Stash" backfill handler.
- New commit: `settings.memory.*` stash-backend i18n keys added to all 6
secondary locales (RUFU-121/122 parity fix; `pnpm i18n:status` no longer
reports any violation introduced by this PR).
**Deploy note (operator environments that already ran a pre-rebase build
of this PR):** the memory-focus SQL may already be in the schema under
ledger row `0065`. Remap that row to `0066` (`UPDATE
fusion_schema_migrations SET version = '0066' WHERE version = '0065';`)
*before* first boot of a 0066-ceiling binary — otherwise the fresh
upstream `0065_fn_149_review_convergence_stage.sql` would be skipped as
"already applied". Clean databases (no prior memory-focus row) need no
action.
**CI note — Lint (lifecycle-column census) is red on the merge base:**
`pnpm check:lifecycle-columns --strict` fails identically on pure
`origin/main` `3f448f7292` with
`packages/core/src/db/legacy-adoption.ts: 0 -> 3` (3 column guards in
the U9b legacy-adoption table without a baseline entry or
`DELIBERATE-LITERAL` marker). Verified by running the census on a clean
origin/main checkout — inherited from the base, not introduced by this
PR. Fix belongs upstream; tracked separately.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added Stash memory integration with project configuration and optional
semantic search.
* Added per-chat memory focus controls and a `/focus` command.
* Added “Preserve to Stash” for uploading complete chat history.
* Added automatic chat, task transcript, and completion-event capture.
* Added project-specific Stash session folders and archive/delete
synchronization.
* **Bug Fixes**
* Improved Stash folder naming and handling of missing branches during
no-commit tasks.
* **Documentation**
* Added setup, configuration, integration, vector-search, and
performance guidance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Fusion <noreply@runfusion.ai>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
## Summary
- detect an open conversation by its composer instead of the mobile-only
back button
- assert the persistent desktop sidebar and mobile back-navigation
contracts separately
- keep the active-header New Chat expectation aligned with shipped
behavior
## Test plan
- `FUSION_DASHBOARD_DEEP=1 pnpm --filter @fusion/dashboard exec vitest
run --silent=passed-only --reporter=dot
app/components/__tests__/ChatView.ios-keyboard.test.tsx
app/components/__tests__/ChatView.mobile.test.tsx
app/components/__tests__/ChatView.rooms.test.tsx
app/components/__tests__/ChatView.title-switcher.test.tsx
app/components/__tests__/ChatView.docked-sidebar.test.tsx
app/components/__tests__/ChatView.sessions-rooms.test.tsx`
- `pnpm --filter @fusion/dashboard typecheck`
- `pnpm exec eslint
packages/dashboard/app/components/__tests__/ChatView.rooms.test.tsx`
- `node scripts/check-changeset-format.mjs --strict`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Tests**
- Expanded coverage for navigating between chat lists and conversation
details.
- Verified selecting a different room updates the active room and
conversation header.
- Added responsive checks for desktop and mobile layouts.
- Verified support for both room chats and direct conversations.
- Confirmed mobile users see **New Chat** in the active header.
- Confirmed desktop navigation presents list and detail views without an
unnecessary back button.
- Improved viewport isolation and cleanup between responsive tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- restore the 21 JIRA settings keys in all six secondary app catalogs
- return the workspace i18n parity gate to green
- add patch release metadata
## Test Plan
- `pnpm i18n:status`
- `pnpm check:changesets`
- `pnpm --filter @fusion/dashboard build`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Localization**
* Added Jira settings translation coverage for Spanish, French, Korean,
Brazilian Portuguese, Simplified Chinese, and Traditional Chinese.
* Standardized availability of Jira configuration labels across
supported dashboard locales, including URLs, credentials, scopes, and
issue templates.
* **Documentation**
* Added release metadata for the localization update.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
## Summary
- restore explicit executor guidance for assigning workflows to tasks
the agent creates
- keep the existing prohibition on rerouting the task currently being
executed
- restore parity between both built-in executor prompt variants and
their regression test
## Test plan
- `pnpm --filter @fusion/core exec vitest run --silent=passed-only
--reporter=dot src/__tests__/agent-prompts.test.ts`
- `pnpm --filter @fusion/core typecheck`
- `pnpm check:changesets`
- `pnpm exec eslint packages/core/src/agents/agent-prompts.ts`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Improvements**
* Executor workflow guidance now appears only when task creation or
delegation capabilities are available.
* Built-in executor prompts provide clearer task-assignment instructions
based on available capabilities.
* Custom executor prompts remain unchanged.
* Removed outdated workflow-setting guidance when task-management
capabilities are unavailable.
* **Tests**
* Added coverage for task creation, delegation, and capability-specific
workflow guidance scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- Re-record the lane-wiring baseline after #3514 removed the final
unwired merge-readiness call site.
- Normalize the duplicate `self-healing.ts` key while regenerating the
canonical JSON baseline.
## Test Plan
- `node scripts/check-lane-wiring.mjs`
- `git diff --check`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated internal baseline tracking to remove an obsolete merge-task
entry.
* No user-facing functionality or behavior changed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Core 32 failures -> 0 (5,981 passing); CLI 46 -> 0 (2,021 passing). Three
agents per package, root-cause fixes only.
One product defect, same class as FN-9059 and found the same way — by a test
that leaked state between runs:
`projectTableNames` was missing SEVENTEEN tables the schema declares
(current_plan_evidence, spec_locks, spec_drift_reports, symbol_locks,
configuration_revisions, chat_tags, chat_session_tags, mission_lineage_stops,
task_verification_requests, unplanned_execution_blocks,
workflow_agent_capacity_leases and the six task_lifecycle_* tables). That list
drives BOTH the PG test-harness per-test reset and production health
compaction, so those tables were never truncated between tests (a plan-evidence
version counter carried forward, making whole-file runs disagree with isolated
ones) and never VACUUM/ANALYZEd in production. Registered, with
project-table-registry.test.ts as a ratchet — verified it fails on an
unregistered new table naming the offender.
Everything else was drift behind deliberate changes: branch-write provenance,
FN-073 dependency validation, the FN-9191 pre-merge merge gate, U11's triage/
planning lane merge, refinement workflow coming from the ORIGIN selection,
async-converted provider registration, a barrel mock missing exports a guard
added, and several source-pinned inventories broken by module moves. Tests for
removed features were deleted with their removing commit cited.
Also fixes a vitest config gap where @fusion/core/mcp-builtin-servers resolved
only to dist/, which was breaking test COLLECTION in unrelated CLI files and had
been misread as transient cross-agent noise.
Quarantines mission-store.pg's concurrent-claim race (second sighting): it holds
a transaction open, sleeps 250ms and asserts the rival has not settled, which
fails under parallel load. An A/B against the registry change above looked
causal on one run and did not reproduce on three — that coincidence is the flake
itself, and rescue needs a real lock-wait probe rather than a longer sleep.
Core's config now inlines its exclude array, because check-quarantine-ledger.mjs
cannot resolve a variable reference and silently reported the ledger unpaired.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`handoff-to-review-atomicity.pg.test.ts` aborted its `beforeAll` at the 15s
budget on the first `pnpm test:gate` of a session; not reproduced in 8 later
runs across three shapes (gate x2, pg-gate x3, isolated x3).
Same mode as entries 6 and 7, but narrower: it happened under the capped
four-fork lane with two selected files, so fork oversubscription does not
explain it. Recorded the cold-cluster correlation as a hypothesis rather than a
finding — reproducing it means stopping the embedded cluster, and this host runs
a live Fusion instance.
Discloses that the failing run's full output was lost to a tail pipe, and that
inline quarantine was unavailable regardless (quarantinedCoreTests must stay
empty); eviction of a transactional-invariant gate file is owner-escalated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The U4 executor peel (#3317) rewrote executor.ts from a pre-change base and
dropped `isPrincipalHoldCoolingDown`, re-inlining the read inside
executeWorkflowGraph behind `!opts?.alreadyClaimed` — a flag its only caller,
executeCore, always sets. The ladder kept recording and clearing correctly, so
it read as working while never once deferring a dispatch.
Without it, an unroutable role pool re-enters the graph on every dispatch only
to re-fence and re-park: one graph run, two work-item writes and two audit rows
per pass, for a condition that clears only when an operator enables or adds an
agent. The `!repeated` log suppression keeps that flood invisible after the
first line.
Restore the guard in executeCore, ahead of the graphRouting claim. Position is
load-bearing in both directions: returning after the claim would strand it
(graphRunnerOwnsClaim stops the finally from cleaning up), which is also why
the inner check must keep its alreadyClaimed gate.
Make the ladder a primitive with one exported writer and one exported reader so
a lost reader is a lost reference the compiler can see, rather than a .get()
that quietly moved somewhere its guard could never be true. Its test-mode zero
is now read at record time; bound at module load it collapsed the cooldown to
until === now under VITEST, so no test could have caught this.
Regression test asserts the invariant on both entry surfaces plus the negatives
that keep the guard from becoming a permanent block. Mutation-checked: with the
guard disabled the two dispatch-deferral cases fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Full engine suite at a97aa84a20: 3 failures out of 12,414. All three diagnosed:
- self-healing-pending-wedge-notification's marker-selection case fails ONLY in
a full-suite run (expects 1 elapsed marker, sees 2) and passes deterministically
alone. This is its SECOND sighting, so per AGENTS.md it is an on-sight
quarantine with no further discretion: ledger entry + matching vitest exclude,
same commit, 2026-09-06 deletion deadline.
- spec-drift-reconciler's exponential-backoff case shows the same shape on a
FIRST sighting, so it is recorded in the observed register instead of evicting
that file's other passing coverage. Both are timer-driven reconciler tests that
only fail alongside other suites, pointing at cross-file fake-timer state.
- merge-orphan-durable-write-inventory drift was pure lineHint movement (19
changed, zero newly unclassified entries) after product edits shifted lines.
Regenerated.
Also fixes check-quarantine-ledger.mjs, which could not see the exclude I added:
its comment stripper treated the `/**` inside glob literals like "node_modules/**"
and "src/**/*.slow.test.ts" as a block-comment opener and deleted through to the
next "*/", swallowing whole array literals and every entry after them. It now
scans string-aware, so the lockstep check actually holds. Nothing was appeased:
no timeout widened, no retry added, no assertion relaxed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#3514 removed the last unwired task-merge review-lane call site but left
the ratchet allowance at 1, so every PR against main failed
check:lane-wiring on a drop.
## Summary
- thread resolved review lanes through `isTaskReadyForMerge`
- preserve required pre-merge step filtering
- add coverage for a renamed review lane
## Test plan
- `pnpm --filter @fusion/core exec vitest run --silent=passed-only
--reporter=dot src/__tests__/task-merge.test.ts`
- `pnpm --filter @fusion/core typecheck`
- `pnpm check:lane-wiring`
- `pnpm check:changesets`
- `pnpm exec eslint packages/core/src/merge/task-merge.ts
packages/core/src/__tests__/task-merge.test.ts`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Custom review lanes are now honored during merge-readiness checks and
auto-merge processing.
* Renamed workflow lanes correctly determine whether tasks can merge.
* Tasks resumed from a paused state are routed and evaluated using the
appropriate review lane.
* The default `in-review` lane remains supported when no custom review
lanes are configured.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
ab9789f0a8 was committed with `git add -A` while an agent was mid-investigation
in this same checkout, so it captured that agent's temporary instrumentation:
eight `process.stderr.write('[F] …')` probe lines inside product code
(executor/mark-stuck-aborted.ts) and a 359-line scratch copy of a test file.
Both were pushed. Reverting both; no product behavior was ever intended to
change in those files.
Also lands the executor-stuck-requeue fix that investigation produced: the
grace-timeout assertion ran before the product finished, because the callback
continues past its timer into resetStepsIfWorkLost -> loadWorkspaceConfig, real
async fs I/O that `vi.advanceTimersByTimeAsync` does not await. The test now
awaits a completion barrier resolved by the requeue's own final moveTask rather
than a timeout or retry. The product was correct.
Lesson for this checkout: stage by explicit path while agents are running.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two product defects surfaced by workspace-e2e's remaining failures.
1. A merge-boundary fence silently did not apply. captureWorkspaceReviewEvidence
computes a repository's file list over baseCommitSha..<resolved task branch>,
but computeReviewDiffFingerprint hardcoded baseRef..HEAD. For a workspace
entry whose checkout sits on the integration branch those are different
ranges, so the fingerprint did not describe the files captured beside it: a
diverged checkout hard-failed an approved repository as content-changed,
and a checkout at the base produced an empty diff -> undefined fingerprint ->
the repo dropped out of mergeBoundaryFingerprints, so BOTH the
approval-missing and content-changed fences stopped applying to it at all.
computeReviewDiffFingerprint now takes an optional headRef; workspace
evidence passes the resolved task branch. The singular-review caller, whose
worktree IS the branch, keeps the ambient HEAD default.
2. Land intents were recorded and resolved under different conditions.
landOneRepo records an intent only when ctx.workspaceLand is set, which
landWorkspaceTask passes only for remote targets, but the resolve side was
gated on durableLandLease alone. A local-only land therefore resolved an
intent that was never recorded, got "missing", and failed a fully-landed
repo as a partial land AFTER its integration ref had advanced. Resolve now
uses the same condition as record.
The approveWorkspaceReview helper's "reviewStep called exactly once" constant
only held because defect 1 suppressed a repository; it now derives the expected
count from the same production capture the review loop uses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An investigation reported that under PostgreSQL `updateTask(id, { steps: [] })`
silently no-ops while a non-empty array writes fine, and worked around it in
fixtures. Reproduced and traced: the write is literal and correct — the row and
task.json both hold `[]`. What actually happens is that an empty array means
"plan not parsed yet", not "this task has no steps", so all four read paths
re-derive steps from PROMPT.md when the stored array is empty: getTaskImpl, the
two list hydrations (reads.ts), and updateStep's auto-init, whose range error
already says outright that "its steps are defined in PROMPT.md".
No product change: removing the re-derivation would strand every task whose plan
lives only in PROMPT.md. Instead both halves of the contract are now pinned by a
PG test and documented at the write site, so the next reader sees the mechanism
instead of re-diagnosing it as a lost write. To make a task genuinely stepless,
remove the step headings from PROMPT.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six parallel agents worked the 150 remaining failures. Engine suite: 297 failing
tests at baseline 3f448f7292 -> 8. 12,391 passing.
The failures were mostly pointing at live regressions, not stale tests. Eleven
product defects found and fixed:
- Operator approval mail dropped from BOTH executor gate closures: a gate paused
a task for approval and no mailbox row was ever written.
- FN-8840 title-duplicate handling reverted in triage.ts, self-healing.ts, and
scheduler.ts: a title-only "DUPLICATE: <id>" card consumed a full planner
session, an operator-authored PROMPT.md could be erased, and a title-only
redirect became dispatchable again.
- A failed plan-admission audit write set its dedupe marker anyway, silencing
the stall permanently (FN-8600 regression); engine now has an outcome-reporting
bounded-audit seam mirroring core's FN-9182.
- A best-effort plan mirror could abort a whole planning attempt after the
authoritative PROMPT.md had already been written.
- AI-merge cleanup lost its alreadyAbsent/idempotent signal on the real-git path.
- Workspace merge-boundary file comparison ran without its review-evidence fence,
hard-failing every file for callers with no review episode.
- After a file-scope violation the retry re-selected the rejected squash and
never re-merged.
- Parallel step branches leaked: a name-based classifier read executor-created
fusion/step-* branches as operator-owned and skipped cleanup.
- workspace_coordination_leases / workspace_land_intents were missing from
projectTableNames, so the PG harness never truncated them and leases leaked
across tests.
Four of those are silent reversions from ONE commit, 1cf86baa1c, labeled a
behavior-preserving "executor pure peels" refactor. It passed its own targeted
verification; only a full-suite audit found them.
Test-side repairs are root-cause fixes at shared factories: required pre-merge
gate declarations, branch-write provenance, fake stores missing production write
seams, dead vi.mock specifiers that silently mocked nothing (allowlist ratcheted
11 -> 8), and stale expectations after deliberate IR/tool/error-class changes.
Tests for deleted features were deleted with their removing commit cited.
Left red deliberately (4): executor-worktree-liveness's unrouted-graph-run
assertion and three workspace-e2e landing-stack layers, each needing a design
ruling rather than a test edit. Two durable-write call sites remain flagged
unresolved rather than given invented fencing verdicts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three more fixtures wrote a task branch without an origin, so the
`updateTaskUnlockedImpl` provenance boundary threw before any scenario ran —
the same guard, and the same missed-fixture class, as the shared reliability
helper fixed earlier. Each fixture binds a task to its worktree branch on the
engine's behalf, so each now says so.
worktree-lifecycle-certification 0/4 -> 4/4, audit-and-recovery 1/3 -> 3/3,
self-healing-interactions 6/7 -> 7/7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fusion agents keep shipping behavior changes that leave stale tests behind, and
targeted verification structurally cannot catch it: it runs the tests for the
files the agent TOUCHED, while the assertions encoding the old behavior live in
files it did not. Measured on one full engine suite run (2026-08-24): 297 failing
tests, ~135 of them from exactly five such changes — the FN-158 pre-merge-gate
guard (~70 fixtures across 13 files), the branch-write provenance guard (18 from
ONE shared fixture), a workflow-IR reorder (10 stale topology assertions), an
updateTaskAtomic seam missing from fake stores (~9), and FN-074's splitting
removal leaving 4 reviewer-prompt tests asserting a deleted contract. Every one
passed its own targeted verification.
- AGENTS.md: new standing rule with the four search triggers (guard added,
feature removed, order/default/constant/prompt changed, public method added),
a fix-at-the-shared-factory preference, and an explicit ban on weakening a
stale test or restoring removed behavior to satisfy one.
- Executor prompt: the same searches, stated as a finish condition.
- Full triage prompt: specs for behavior-changing tasks must include a step that
NAMES the search, so it is planned rather than left to chance.
The fast triage prompt is deliberately excluded: it carries a hard size budget
(agent-prompts.test.ts caps it at 7500 chars) and adding this pushed it over —
an instance of the very rule being documented, now cited in it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mark isAtOrPastReviewLane as DELIBERATE-LITERAL so the lifecycle-column
census ratchet stops treating FN-158's physical pre-cutover lane IDs as
new unexamined guards. Refresh the pinned baseline to match.
Four tests asserted DEFAULT_REVIEWER_PROMPT still carried the task-SPLITTING
contract: "Subtask breakdown", "12+ implementation steps", "The bar for
splitting is high", and a REVISE directing the planner to fn_task_create 2-5
child tasks. FN-074 removed task splitting across core, dashboard, and engine,
and FN-125 removed the reviewer's ability to create tasks at all. FN-074's
message says it updated affected tests; these were missed and sat red asserting
a contract the product deliberately dropped.
Removed rather than repaired: restoring that prompt text to make them pass would
re-add removed behaviour. The two tests in this block covering the prompt
contract that still exists are untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The engine suite's failures are not independent bugs; they are a handful of
systemic drifts where a guard shipped and its fixtures were never updated.
- Required pre-merge gates (FN-158): the door refuses a card whose enabled
optional groups produced no result, and the built-in workflow enables Plan and
Code Review by default. Merge-mechanics fixtures now declare an explicit empty
list, stating the intent they always had. group-merge-coordinator's
"post-Code-Review member" instead gets real PASSING workflowStepResults,
because recording the pass is what that fixture actually means.
- Branch-write provenance: the shared reliability fixture creates a task with a
branch, which now requires an explicit origin. It stands in for an
engine-created branch, so it says so.
- updateTaskAtomic: a production write seam missing from several fake stores,
copied from the faithful fake in merger-ai.test.ts.
- Durable-write inventory: eight unclassified TaskStore surfaces classified,
including the two batched reads this branch added.
- workflow-graph-merge-region-collapse asserted completion-summary AFTER
code-review; the IR wires it before, and production logs agree.
merger-ai.test.ts alone goes 37 -> 0. Engine failures 288 -> ~200.
Also records a first-sighting suite-only flake in the observed register per the
standing rule, rather than quarantining a file with substantial coverage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Full-suite set-diff against 3f448f7292 caught three regressions the raw counts
hid (that suite is chronically red: 297 failures at baseline, 294 with the
change).
The step exemption was too broad. It also applied to
recoverAlreadyMergedReviewTasks, the content-scan recovery where mergeDetails is
ABSENT and landing is inferred by finding matching content on the base branch.
That heuristic can match a cherry-pick, so exempting incomplete steps there
would launder a genuinely unfinished task to done on a guess — which is exactly
what landed-content-soft-blocker.real-git.test.ts exists to prevent. The
exemption now requires mergeConfirmed AND a commitSha: FN-9193's actual state,
and nothing weaker. Content-scan recovery and no-op merges keep the blocker.
Also seeds mergeSweepHoldReasons in the shared merge-lane fixture, which the
fixture-drift guard requires of every auto-merge state field.
Verified by set-diff: zero test files now fail that did not fail at baseline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FN-9193's branch landed on main as eaa1d47c, but a Code Review revision request
had reset its steps while the approved merge was in flight. The card was left
mergeConfirmed WITH incomplete steps, and every finalization site refused with
"task has incomplete steps" — so it sat failed, re-reading its own contradiction.
Restarting it made things worse: replanning issued seven fresh pending steps, so
the retry re-created the exact condition blocking it. A loop with no exit.
Holding a landed card out of done un-merges nothing; the code is on the target
branch either way. All four finalization sites now use
getMergeConfirmedFinalizationBlocker, which exempts incomplete steps once
landing is proven and records the unfinished ones on the task instead of
dropping them. A no-op merge that landed no content still blocks — that is the
protective half of the guard being replaced, and the executor's no-op branch
depends on it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Use a consistent icon-and-label back affordance across direct and room chat threads.
- Render the lucide ArrowLeft icon with localized Back text and preserved accessible naming.
- Cover mobile direct and room thread back buttons with icon, text, and accessibility assertions.
- Add a patch changeset for the published Fusion package.
Files changed:
.changeset/fix-chat-back-arrow.md | 6 ++++++
packages/dashboard/app/components/ChatView.css | 2 +-
packages/dashboard/app/components/ChatView.tsx | 14 ++++++++++++--
.../app/components/__tests__/ChatView.mobile.test.tsx | 4 +++-
.../app/components/__tests__/ChatView.rooms.test.tsx | 7 ++++++-
5 files changed, 28 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-9199
Fusion-Task-Lineage: aab2a300-cc2a-4768-b60b-81d7477b8fab
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
ProjectEngine's in-review auto-merge sweep was a second merge authority. It
judged eligibility from column, status, steps and retry budget alone, with no
idea where the card sat in its workflow graph, so it merged work the graph had
never authorized: FN-9191 merged ~2s after fn_task_done, before Code Review had
ever started, and FN-9193 merged while Code Review was re-running — the gate
then requested revision and reset the steps, but the in-flight merge landed the
pre-remediation branch anyway and left the card mergeConfirmed WITH incomplete
steps, unfinalizable for five hours.
- classifyMergeSweepAdmission (core) admits only merge-confirmed finalization,
a card parked at a merge-region node, an interrupted attempt, or a fenced
quiescent stall. Every initiation is fenced on satisfied pre-merge gates.
- All four doors prove authority: the sweep, the 300ms column-entry handoff
(which matches FN-9191's timing better than any sweep tick), the unpause
re-enqueue, and a position-only pre-dispatch re-check for cards the graph
moved out of the merge lane while they were queued.
- workflow-merge-region.ts holds the canonical merge-region predicate;
INTERPRETER_ENTRY_NODE_KINDS now aliases it so the two cannot drift.
- Multi-repo: branch-group integration/promotion are merge-region nodes, an
in-flight sub-repo land reads as foreign liveness, and a cross-node
merge-dispatch lease defers.
- Sweep reads are batched, so admission costs O(1) queries per poll.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An enabled pre-merge gate that has not reported yet is a not-yet condition,
not a failure. FN-9191 proved the difference is load-bearing: the in-review
auto-merge sweep enqueued the card ~2s after fn_task_done and ~18s before the
graph started its own Code Review node, the merge door correctly refused, and
the auto-merge error path parked it status="failed". Code Review APPROVED two
minutes later, but every subsequent merge — including the graph's own merge
node — then died on "task is marked 'failed'".
- Merge doors throw the typed PreMergeStepsNotRunError for that blocker.
- The auto-merge error path treats it as a deferral: no status write, no
mergeRetries burn, no operator handoff.
- enqueueEligibleInReviewTasks holds a card out of the merge queue until every
enabled pre-merge group has a result, so the race stops at admission.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The correct `docker run` is long and every piece of it fails confusingly when
omitted: the OAuth callback ports are fixed by the providers' registered redirect
URIs and unreachable without PI_OAUTH_CALLBACK_HOST=0.0.0.0, /home/node must be a
volume or the Tailscale login and embedded Postgres are lost on recreate, and
--tailscale is an entrypoint flag that must precede the CLI arguments.
- Every knob is an environment variable, so per-container config lives in an
--env-file outside the repo rather than a committed token.
- FUSION_STATE_VOLUME supports setups that mount /home/node/.fusion as its own
volume nested inside /home/node; omitting it is not destructive but silently
points the dashboard at a different database, so the board comes up empty.
- An existing container is never replaced without --recreate, and volumes are
never removed, so a recreate keeps the database, settings, and tailnet login.
- --dry-run prints the command without running it, including when the container
already exists, which is exactly when previewing a recreate is most useful.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Starting the daemon unconditionally gave every container a process, a listening
socket, and an identity in someone's tailnet that it never asked for. Most
containers never use remote access, so the daemon is now opt-in.
- The entrypoint consumes a leading `--tailscale` argument (or FUSION_TAILSCALE=1,
with `--no-tailscale` to override it back off) and strips it from the argument
list, so everything after it stays a normal Fusion CLI invocation.
- Arguments are rotated through shift/append rather than string concatenation, so
values containing spaces survive as single argv entries.
- Replaces the FUSION_DISABLE_TAILSCALED opt-out, which is redundant now that the
default is off.
- Document the flag, the userspace-networking mode (no NET_ADMIN/tun caps), the
one-time `tailscale up`, and the /home/node mount that persists that login.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tailscale remote access failed in the container with a bare "process exited 1":
the image ships the `tailscale` CLI but nothing ever ran `tailscaled`, so the
`tailscale funnel <port>` spawn died instantly on "failed to connect to local
tailscaled".
- Add scripts/docker-entrypoint.sh, which best-effort starts tailscaled in
userspace-networking mode (needs neither NET_ADMIN nor /dev/net/tun, so the
documented `docker run` is unchanged) and then execs the CLI with CMD verbatim.
Opt out with FUSION_DISABLE_TAILSCALED=1.
- Symlink /var/lib/tailscale into /home/node/.tailscale so the documented
`-v <vol>:/home/node` mount persists the node login across container recreates,
and pre-create the daemon's socket/log paths node-owned before the USER switch.
- Preflight daemon reachability and backend state with `tailscale status --json`
in evaluateRemoteLifecycle instead of only `which tailscale`, so unreachable,
logged-out, and stopped backends all report an actionable
runtime_prerequisite_missing reason rather than an unexplained exit 1.
Regression coverage asserts the invariant across all three unusable-backend
surfaces, not just the reported container repro.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Migrations 0061 (activity-log task-id index), 0062 (remove task/subtask
splitting), 0063 (AI merge review reconciliation), and 0064 (task repository
scope) advanced SCHEMA_BASELINE_VERSION to 0064 without updating this
non-gate PG integration test, leaving it red on the non-blocking suite:
- Bump the immutable-identity assertion to 0064 and pin 0062-0064 identities.
- Append the four new versions to all five getAppliedMigrations() lists.
- Add central.central_activity_log to the hand-built 0000 upgrade fixture so
the 0061 index migration finds the relation real 0000 DBs have from
0000_initial.sql (fixture gap, not a product regression).
FN-149 shipped migration 0065_fn_149_review_convergence_stage.sql and registered
REVIEW_CONVERGENCE_STAGE_VERSION but left SCHEMA_BASELINE_VERSION at "0064". The first
store open applied and recorded 0065; the next open (project store, same boot) hit
assertBinaryNotOlderThanDatabase, saw 0065 > 0064 and threw StaleBinarySchemaError, so
every startup died with "this binary only knows up to 0064" on fresh and upgraded
databases alike.
- Bump SCHEMA_BASELINE_VERSION to "0065" (marker only: applies no SQL, touches no data).
- Move the DB-free migration-wiring assertions out of the PostgreSQL integration file
into src/__tests__/migration-wiring-integrity.test.ts and wire it into test:unit-gate,
so the ceiling/migration drift now fails the merge gate instead of main's boot.
- Refresh the stale migration-identity expectations (0062-0065).
Symptom verification: `node scripts/dev-with-memory.mjs --isolated=<tmp> --prebuild none`
exited 1 with the guard error before; it now boots and serves the dashboard.