Instruments a surface no existing check can see. Follows #2839, and
**corrects the count I reported there** (12 → 14).
## Why it was invisible
The lifecycle census parses TypeScript **comparisons**; a legacy id
inside a SQL string is string data. The inert-seam gate reasons about
parameters and call sites. Neither has ever looked here.
**What it cost:** `cleanupStaleMergeQueueRowsImpl` filtered on `t.column
!= 'in-review'`, so on a renamed board every queued card looked stale,
its `merge_queue` row was deleted, and the card became **unleaseable**.
The operator found it reviewing #2819 — in SQL I had already read past
during that same work.
The quieter half is analytics: five sites count `"column" = 'done'`, so
throughput, cycle time, and team dashboards report **zero completed
work** on a renamed board. Nothing errors, which is why nobody files it.
## What this does, and does not do
It does **not** fix the sites. `resolveProjectColumnsForRoles` is the
mechanism and its migration has an owner (#2839). This freezes the
population so the surface cannot grow underneath that migration: a new
file or a higher count fails, **and a lower count fails too** — so the
baseline ratchets down as sites migrate rather than leaving slots to
silently regrow into. That is the same rot as an allow-list entry for a
deleted function, which this repo already hit once.
AST-based, deliberately: a line grep for the same pattern reports **37**
hits, **25 of them prose** quoting `column === "done"` in explanatory
notes. A guard that is 68% false positives trains its readers to skip it
— a lesson this program has already paid for.
## Two corrections found by mutation-testing my own gate
**1. Clause fragments were missed.** Requiring a SQL keyword *in the
same literal* skipped `team-analytics.ts`, which builds
`["assignedAgentId IS NOT NULL", `"column" = 'done'`, ...]` and joins
them into a `WHERE` later. That fragment is as vocabulary-bound as any
full query but contains no keyword. Fixing it took the population **12 →
14**, so the number I put on #2839 was low.
**2. My first mutation test proved a direction it had not.** I replaced
the first textual occurrence in a file — which was inside a **comment**
— and read the unchanged count as the scanner being broken. The scanner
was right; my test was wrong. All three directions are now driven
against real SQL:
| mutation | result |
|---|---|
| add a full query with a legacy comparison | `3 SQL column literal(s),
baseline allows 2` |
| add a bare clause **fragment** (no keyword) | caught — same failure |
| migrate one away (count drops) | `1 site(s) now, baseline still allows
2 — re-record it` |
| restore | exit 0 |
I am flagging that second one because it is the exact failure mode this
program keeps finding: a green result read as evidence when the
experiment was invalid.
## Verification
`pnpm test:gate` green with the new check in it · lint 0 · single AST
pass. Wired into `test:gate` and both `pretest` hooks.
🤖 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**
* Added automated checks to detect increases in legacy SQL column
literals.
* Added baseline tracking to ensure known SQL literal counts do not
regress.
* **Tests**
* Expanded pre-test and gated verification steps with SQL literal and
mock completeness checks.
* Updated test validation workflows to enforce the new safeguards.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
**`pnpm test` and `pnpm test:full` fail on `main` right now** (commit
`51934931e1`). Found by running the gates against `main` after my
earlier PRs landed, not by CI telling me.
> **Correction to this PR's first version.** I originally wrote that
`pnpm test:gate` was failing. It is not: `check-inert-flag-seams` runs
in the `pretest` and `pretest:full` hooks, not in `test:gate`. So the
blocking merge gate (Lint / Typecheck / Build / Gate) is unaffected and
PRs are not blocked — what is broken is every local `pnpm test` run,
which fails before a single test executes. Lower urgency than I claimed,
still worth fixing promptly, and I would rather correct the scope than
leave an overstated one standing.
Three causes, each surfaced by fixing the one before it.
## 1. Stale exemptions — the mechanism working
#2819 and #2823 merged, so the two `ALLOWED_OMISSIONS` entries covering
those call sites became stale and the staleness check failed them.
Removed. This is my cleanup: the entries were designed so they could not
outlive their fixes, and they didn t.
## 2. Renamed imports were not resolved
Removing the first entry surfaced:
```
enqueueMergeQueue() — best call passes 2 of 5
```
Its only production caller passes all five — through `import {
enqueueMergeQueue as enqueueMergeQueueAsync }`. Call sites were recorded
under the **local** name, so an aliased supplier was invisible and the
seam read as unsupplied. The local name is now mapped back to the
exported one.
## 3. Method calls were conflated with module functions
That fix then surfaced two engine sites as omitting — but
`store.enqueueMergeQueue(taskId, opts)` is a **2-arg `TaskStore`
method** that resolves the review columns internally (#2819), not the
5-arg module function sharing its name. Property-access calls are no
longer attributed to module-level seams.
**Tradeoff, stated at the site:** a genuine `namespace.fn(...)` call is
now skipped. This codebase calls module functions as bare identifiers,
and aliases are resolved by fix 2, so that shape does not currently
occur. Recorded rather than left for someone to discover.
## Both directions re-verified
A fix that quietly disarms the gate would be worse than the red, so I
re-ran the defects it exists to catch:
| mutation | result |
|---|---|
| drop `Column.tsx`'s flags argument (partial supply) | `supplied by
10/11 call sites; omitted at .../Column.tsx:1 (of 2)` |
| drop the aliased 5-arg supplier (wholly unsupplied) | `best call
passes 3 of 5` |
| restored | exit 0 |
My first attempt at the second row grepped for the wrong message shape
and printed nothing. **I re-ran it rather than reading silence as
success** — which is the failure this gate exists to prevent, and one I
have made in this same file before.
## Verification
`pnpm test:gate` green (it was never affected) · `node
scripts/check-inert-flag-seams.mjs` exit 0 · lint 0 · gate reports `21
lane/flag seams, all supplied at every production call site`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Summary
- Advance the matched Pi runtime pin (`pi-ai`, `pi-coding-agent`,
`pi-agent-core`, `pi-tui`) from **0.82.0 → 0.82.1** so
electron-builder's production-dependency walk accepts `pi-agent-core`'s
`pi-ai@^0.82.1` requirement.
- Fixes the Desktop packaging PR-lane failure:
`Production dependency @earendil-works/pi-ai not found for package
@earendil-works/pi-agent-core` (required `^0.82.1`).
- Keep the workspace override guard; update pin-policy fixtures and CLI
package-config expectations.
- Tighten the advisory packaging step-order test so it asserts against
the real `electron-builder --dir` step (not a missing release-only step
name that previously passed via `indexOf === -1`).
- Run `pnpm dedupe` so the packaging lane's lockfile dedupe
early-warning is clean.
## Context
#2439 pinned the full Pi closure at 0.82.0 and made recent main-based
packaging runs green. This advances to the current upstream patch so
deploy + electron-builder stay aligned with `pi-agent-core@0.82.1`'s
declared dependency range.
## Test plan
- [x] `node scripts/check-pi-versions-pinned.mjs`
- [x] `node --test scripts/__tests__/check-pi-versions-pinned.test.mjs`
- [x] `pnpm --filter @runfusion/fusion exec vitest run
src/__tests__/package-config.test.ts`
- [x] `pnpm --filter @fusion/desktop exec vitest run
src/__tests__/release-workflow.test.ts`
- [x] `pnpm dedupe --check`
- [ ] GitHub: Desktop packaging (should run full packaging walk —
lockfile/package.json touched)
- [ ] GitHub: PR Checks (Lint, Typecheck, Build, Gate)
Add an automated guard that keeps CI test-sharding timings usable.
- Validate snapshot structure, freshness, and recorded test-file paths.
- Confirm planning loads the snapshot and shard dry-runs use it without stale warnings.
Files changed:
scripts/__tests__/ci-test-shard-timings.test.mjs | 50 +++++++++++++++++++++++-
1 file changed, 49 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-8626
Fusion-Task-Lineage: ada6525f-8dcc-4494-8586-3aa2e41618f3
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
- distillation runs on opus (env-overridable) with a 4-minute budget
- highlights must name the surface and outcome; vague filler is banned
- tweets target 200-280 chars with concrete changes and varied structure
- betas get their own tester-facing draft carrying `fn update --channel beta`
- prerelease openers read as "Fusion 0.74 beta:" instead of "Fusion 0.74-beta.0"
After a stable release, main stayed inside the old pre-mode cycle, so the next
beta numbered below the published stable (v0.73.0-beta.7 after v0.73.0) and the
dev checkout kept reporting the last beta.
- beta releases re-anchor a stale pre-mode cycle on the newest stable tag
- both channels refuse a version at or below the newest published stable
- stable promotion now back-merges release into main automatically (fail-soft
on conflict) so the local dev version is the stable version
- Shard watchdog floor 25min (was 15): the July PG-cutover test growth pushed
@fusion/core past 900s on contended CI runners; run 30075604930 killed a
healthy core run at exactly the floor because the 27-day-old (still "fresh")
undercounting timings snapshot tightened the budget to it — the same
false-kill class as the 5->15min raise. Floor pin + in-band example updated.
- full-suite.yml timing upload: include-hidden-files — the .timings/ dot-dirs
were silently excluded by upload-artifact@v4, so the step has uploaded
nothing since it was added and the snapshot could never be refreshed from CI.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-mode preserves consumed changeset .md files, so every beta's distilled notes and GitHub prerelease body aggregated the entire cycle since the last stable (v0.73.0-beta.4 shipped the full 0.72.0→0.73.0 aggregate). Betas now distill only changesets not yet recorded in pre.json's consumed ledger, and fail loudly when a beta would ship nothing new. Stable promotion still feeds the full preserved set, keeping its notes an explicit rollup of every beta in the cycle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
syncRootChangelog rewrote every prior release from raw package notes, so only
the latest distilled Highlights view survived. Re-emit already-distilled bodies
on sync, keep the archive pointer outside version sections, and restore wiped
summaries from release history.
## Summary
- restores the missing Simplified Chinese duplicate-roadmap report label
- restores the missing Traditional Chinese duplicate-roadmap report
label
- adds a patch changeset for the catalog correction
## Test plan
- `pnpm --filter @fusion/i18n test` (5 files, 29 tests)
- `pnpm build`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Restored Simplified and Traditional Chinese translations for duplicate
roadmap report titles.
* Updated the roadmap reporting UI text to clarify when a report is
already in the roadmap and ask whether to add the user’s data point.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Prevent automated tests from inheriting production PostgreSQL URLs and route global test-mode startups to a dedicated external or embedded test database.
## Summary
- Extend the workspace content-hash skip cache to **all** packages (not
just plugins), with `--force` / `--full` flags
- Default local CLI packaging to a **fast mode** (bin/extension +
migrations only); full desktop/plugin/DTS staging runs on CI or `pnpm
build:full`
- Enable TypeScript `incremental` builds for warm recompiles
- Add `maxConcurrentVerifications` (default **1**) so concurrent tasks
cannot stack monorepo typecheck/build and peg CPU
Warm `pnpm build` measured ~**126s → ~0.8s** when nothing changed.
## Test plan
- [x] `node --test scripts/__tests__/build-workspace.test.mjs` (12 pass)
- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/verification-concurrency.test.ts`
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/settings-parity.test.ts`
- [x] Local: first `pnpm build` rebuilds as needed; second warm `pnpm
build` skips all packages (~0.8s)
- [x] Fast CLI packaging logs skip of desktop/plugin staging without
`FUSION_CLI_FULL_PACKAGE`
- [ ] CI: `pnpm build` still full-packages under `CI=true` (plugin
staging / release surfaces)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added a Scheduling setting to limit concurrent verification tasks from
1–8, with a default of 1.
* Verification tasks now support cancellation while waiting or running.
* Added options for forced and full workspace builds.
* **Performance**
* Local builds can skip unchanged packages and use incremental
compilation for faster rebuilds.
* Local CLI packaging is faster by default, while full packaging remains
available when needed.
* **Documentation**
* Updated the settings reference with the new verification concurrency
option.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
CLI commands, daemon/dashboard startup, packaged desktop startup, and
live-data maintenance scripts now share the mandatory PostgreSQL
lifecycle. Operators no longer risk a command silently reading or
writing a disconnected SQLite shadow when PostgreSQL setup fails.
## Design decisions
- Every startup owner retains and awaits its PostgreSQL shutdown
callback, including partial-startup failure paths.
- CLI project context and lock-retry flows resolve through asynchronous
project stores.
- Maintenance scripts use the shared backend helper; explicit database
migration/inspection remains the only CLI surface allowed to read legacy
SQLite sources.
## Validation
- CLI and Desktop typechecks pass on the stacked branch.
- `pnpm test:gate` passes all 478 gate tests.
- This PR changes 54 files.
## Stack
- Depends on #2109, which depends on #2108.
- Bundled plugins and docs/release follow in later PRs.
Related: #2105
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* PostgreSQL is now the authoritative store for structured project and
task metadata.
* Projects can be recognized and initialized using
`.fusion/project.json`, without creating a legacy SQLite database.
* CLI commands now retry transient PostgreSQL contention errors.
* **Bug Fixes**
* Improved cleanup when commands complete, fail, or run in the
background, preventing lingering resources.
* Improved desktop, server, and session shutdown reliability.
* **Documentation**
* Updated storage and standalone binary guidance to reflect PostgreSQL
and legacy SQLite compatibility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- make SQLite-to-PostgreSQL cutover retryable, fail-closed, versioned,
and transactionally serialized
- isolate migration sessions from runtime traffic and apply schema
upgrades through `0002`
- enforce tenant ownership across automations, analytics, activity,
usage, agent runs, evals, and todos
- replace expired SQLite-only coverage with PostgreSQL parity and
concurrency coverage
This is PR 1 of 2. The stacked follow-up restores PostgreSQL parity for
CLI, engine, dashboard, and bundled integrations.
## Verification
- `pnpm check:changesets --strict`
- `pnpm --filter @fusion/core typecheck`
- migration schema, connection, and SQLite cutover suite: 57 tests
passed
- `pnpm test:gate`: 463 tests passed
## Post-Deploy Monitoring & Validation
- take a restorable PostgreSQL backup before deploy
- confirm `fusion_schema_migrations` contains `0002`
- confirm each expected project has a complete
`fusion_sqlite_migrations` row
- verify no null or empty tenant ownership in automations, activity
logs, agent runs, and usage events
- monitor for ownership inference failures, cutover verification
failures, and migration session errors
- restore the backup for data rollback; do not downgrade the
tenant-isolation schema in place
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* PostgreSQL-backed analytics and live dashboard metrics are now
project-scoped (activity, tools, monitor, signals, and live snapshots).
* Evaluation runs and scheduled eval batches received lifecycle
improvements (ordering, updates, and execution flow).
* Todo list changes now emit events; WhatsApp persistence and
project-scoped roadmap data are supported.
* **Bug Fixes**
* SQLite-to-PostgreSQL cutovers now fail safely with stronger
verification, serialized cutover handling, and safer project ownership.
* PostgreSQL backend writes and reads are now strictly project-isolated
and fail closed when project context is missing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Raise the durable archive cutoff to 0.60.0, keep only the current release in CHANGELOG.md, and rewrite labeled summary/category/dev package aggregates for 0.47–0.59 into operator-facing Highlights/New/Fixed notes.
# Migrate storage from SQLite to PostgreSQL — full dashboard cutover
Migrates Fusion's storage layer to the embedded PostgreSQL
`AsyncDataLayer` (the default backend) and **completes the
satellite-store + feature cutover** so every dashboard and Command
Center surface works in PG mode.
## Status — every surface works in embedded-PG mode
Verified live against a running embedded-Postgres dashboard (all
**200**, zero 5xx) and gate-tested (**23 files / 99 tests** on embedded
PG, plus engine-core 294 and ci-shape 63 in the blocking merge gate;
core/engine/cli/dashboard typecheck clean).
| Area | Surfaces | State |
|---|---|---|
| Satellite stores | workflows, todos, insights, research, missions,
goals, mailbox | ✅ |
| Views | artifacts, documents, evals | ✅ |
| Command Center | activity, productivity, team, tokens, tools,
**workflows**, **github**, **signals**, **plugin-activations**, **live**
(all 10) | ✅ |
| Run execution | insight generation, research run execution | ✅
(store-path; AI step needs a provider) |
| Live updates | SSE push for mission/research/insight events | ✅ |
| Workflow editing | create / update / delete / select (+ id counter) |
✅ |
| Engine | mission autopilot, incident-signal ingestion, regression
storm-guard, agent wake-on-message | ✅ |
| Core | tasks, agents, secrets, automations, memory, chat, usage, PRs,
git | ✅ |
## Approach
Each satellite store gets an `Async<Store>` wrapper exposing the sync
store's method names over the existing `async-*-store.ts` helpers;
`get<Store>Store()` returns a `Sync | Async` union; consumers `await`
(harmless on sync), and engine/CLI paths that can't convert use
`instanceof Sync` graceful fallback. Analytics aggregators branch on
`"ping" in dbOrLayer` to run schema-qualified raw SQL over `project.*`
(snake_case) in PG. Executors/orchestrators/autopilot are
await-converted to drive the union store; the async store wrappers
extend `EventEmitter` so SSE live-push fires in both backends.
Not-yet-ported capabilities degrade gracefully (never 500) and are
individually called out in commits.
## Sync with main
The branch is kept continuously merged with `main` (currently through
FN-7845, 2026-07-12); the earlier "final rebase deferred" note no longer
applies. Use **Create a merge commit** (or squash) to land it — GitHub's
rebase-merge cannot replay a merge-maintained branch.
## Residual Review Findings
Multi-agent code review of the PostgreSQL satellite-store ports (U1–U5)
applied 3 safe fixes (see `fix(review): apply autofix feedback`). The
following are **real but gated** — recorded here as follow-up work
rather than auto-applied. All are SQLite→PostgreSQL
**concurrency/atomicity regressions**: the sync stores were immune only
by SQLite's single-writer, single-threaded-handler execution; the async
ports open multi-await read-modify-write windows. **Reachability is low
today** because the execution engines that generate concurrent same-run
mutations (insight run executor, research orchestrator/dispatcher) are
`instanceof`-gated to sync mode in PG. No process-crash class survived
(all engine fallbacks correctly guard the sync store).
- **[P1] Research `appendResearchEvent` dual-write is non-atomic**
(`packages/core/src/async-research-store.ts`, corroborated: adversarial
+ reliability). The `research_run_events` insert (own transaction) and
the `run.events` jsonb update are separate writes — a crash between
them, or two concurrent appends, splits the table count from the jsonb
array. **Fix:** perform the seq-insert and the jsonb update in one
`layer.transactionImmediate`.
- **[P1] Research run terminal-reversion via stale full-row persist**
(`async-research-store.ts` `persistResearchRun`/`updateResearchStatus`).
Concurrent `PATCH /runs/:id/status` + `POST /runs/:id/events` can revert
a terminal run to `running` by overwriting the whole row, bypassing the
transition guard. **Fix:** scoped column `UPDATE`s with a `WHERE status
…` guard, or optimistic version column.
- **[P2] `updateResearchRun`/`updateInsightRun` read-then-write TOCTOU**
— concurrent PATCHes last-writer-wins on the lifecycle merge. **Fix:**
`SELECT … FOR UPDATE` / enclosing transaction.
- **[P2] `upsertRun`/`createRunOrThrowConflict` check-then-create race**
(`async-insight-store.ts`) — two callers can each create an "active"
run. **Fix:** partial unique index on `(projectId, trigger) WHERE status
IN ('pending','running')`.
- **[P3] `createResearchRetryRun` return-value divergence** — sync
returns the pre-update `queued` snapshot; async returns the reloaded
`retry_waiting` run (persisted state is identical). Pick one side for
cross-backend parity.
- **[P2/perf] Mission `getMissionWithHierarchy`/`getMissionHealth` N+1
fan-out** — O(milestones×slices) sequential round-trips hold one pool
slot per request; can starve the pool for large hierarchies. **Fix:**
batched/joined reads.
- **Testing gaps:** no PG-mode concurrency tests (interleaved
status/event mutations), no sync↔async parity assertion for the
lifecycle-error codes, and no mission status/health rollup parity test
vs the sync `MissionStore`.
~~Out of scope (deferred): AI run *execution* (insight/research) +
mission autopilot + live SSE mission events remain sync-gated/degraded
in PG mode.~~ **Since ported** — insight/research run execution, mission
autopilot, and SSE live push all run on the async layer now, which also
makes the concurrency findings above genuinely reachable; they remain
open follow-ups.
---
## Update — 2026-07-12: production-readiness hardening & live acceptance
Everything below landed on this branch since the description above was
written:
**Production blockers from review — fixed**
- `recoverStaleTransitionPending` ported to the async layer (backend
moves write + clear the crash-safe marker; startup/maintenance sweeps no
longer throw).
- Lost-update class fixed: `atomicWriteTaskJson`/`WithAudit` write
changed columns only (full-row upserts silently resurrected stale fields
across concurrent store instances — the "task stuck unplanned forever"
bug).
- First-boot **auto-migration**: booting the PG backend over a project
with a legacy `fusion.db` migrates it automatically (loud failure,
SQLite kept as backup), and the dashboard shows a one-time **"your data
was migrated" banner** with the backup paths and a Need-help Discord
link.
- `pg_dump`/`pg_restore` discovered from common install locations for
embedded-mode backups.
- The PG suite is part of the blocking merge gate (`test:pg-gate`).
**Multi-project isolation (PR #2007, merged into this branch)**
- `project_id` partition key on tasks / archived tasks / config,
`taskProjectScope` threaded through every scan/claim/count, per-project
config rows, layer bound to the project at startup.
- Review P1 follow-up: the shared cold-storage `archive.archived_tasks`
table is also partitioned and all archived-board reads/counts/searches
are scoped.
- Schema drift self-heal generalized to schema-qualified columns so
existing databases upgrade in place.
**Other changes**
- Node settings sync **removed** in PG mode (409
`settings-sync-disabled-postgres`) — nodes share state by connecting to
the same database; auth sync kept (per-machine file).
- Perf (review findings): `listTasks` pushes column filter + ORDER BY +
LIMIT/OFFSET into SQL; `getConversation` capped to the most recent 200
messages.
- Fixed a false "operator action required" pause-abort log fired on
every successfully auto-merged task.
**Live acceptance — PASSED (2026-07-12)**
A sandboxed instance (isolated HOME, embedded PG, real Opus executor)
ran a task through the complete cycle: create → triage (AI spec) →
execute → in-review → AI squash-merge landed on the project's `main` →
done. A write+read sweep of every data surface (settings, comments,
documents, attachments + artifact bridge + artifact edit, chat with real
generation, goals, missions, agent mail, secrets, workflows, memory, CC
analytics) was green on embedded PG.
**Known remaining work**
- The per-project `config` PK re-key has no upgrade path for
pre-isolation embedded-PG databases (needs a real `DROP
CONSTRAINT`/re-key migration; fresh databases are fine).
- `pg_dump`/`pg_restore` binaries are not yet bundled in release
artifacts (PATH/common-location discovery only).
- The satellite-store concurrency findings listed above.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Phil Larson <hello@phillarson.xyz>
Co-authored-by: fusion-merge <fusion-merge@local>
Claude-author Highlights plus an engagement-oriented tweet (≤280) during changelog distillation, with scheme-free links, Fusion version openers that drop .0 patch, and a soft deterministic fallback when Claude is offline.
Add a report-only script that surfaces flaky-test quarantine entries approaching their 14-day deletion clock, so maintainers can make deliberate rescue-or-expire decisions before entries silently expire.
- Add scripts/check-quarantine-ledger.mjs: reads scripts/lib/test-quarantine.json, computes days-remaining against the existing 14-day deletion clock (shared DELETION_CLOCK_DAYS from scripts/test-velocity-baseline.mjs), and buckets each entry as expired/near/healthy/unknown
- Support --warn-within=<days> (default 5) to tune the near-deadline window, --json for machine-readable output, and --strict as an opt-in local/CI gate (exits 1 on expired/near entries) while default mode stays exit-0 and non-blocking
- Wire pnpm check:quarantine-ledger script in package.json
- Add scripts/__tests__/check-quarantine-ledger.test.mjs covering deadline bucketing/sorting, empty/missing ledger handling, --strict behavior, and --json output shape
- Document the new command and its flags in docs/testing.md under the quarantine ledger/deletion ratchet section
Files changed:
docs/testing.md | 10 +
package.json | 1 +
scripts/__tests__/check-quarantine-ledger.test.mjs | 159 ++++++++++++++++
scripts/check-quarantine-ledger.mjs | 202 +++++++++++++++++++++
4 files changed, 372 insertions(+)
Fusion-Task-Id: FN-7912
Fusion-Task-Lineage: c08e2e09-473a-4ad0-8c27-43cbc3355168
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a deterministic changelog-archive split so scripts/release.mjs stops regenerating one ever-growing root CHANGELOG.md and instead keeps only current release notes at the root while durably archiving pre-0.50.0 history.
- Add scripts/lib/changelog-archive.mjs with partitionVersionsByCutoff (splits a version-ordered list at the 0.50.0 cutoff, preserving order and treating non-parseable keys as archived) and archivePointerLine (renders the "older releases" pointer appended to the current changelog).
- Rework scripts/release.mjs's syncRootChangelog to build CHANGELOG.md (current versions + archive pointer) and a new CHANGELOG-archive.md (versions before 0.50.0) via a shared buildRootChangelogLines/normalizeChangelogLines helper instead of one monolithic file.
- Add scripts/__tests__/changelog-archive.test.mjs covering cutoff partitioning, boundary/patch handling, non-parseable keys, custom cutoffs, and the archive pointer text.
- Regenerate CHANGELOG.md (now only 0.50.0+) and add CHANGELOG-archive.md containing the pre-0.50.0 history moved out of the root file.
Files changed:
CHANGELOG-archive.md | 10882 +++++++++++++++++++++++
CHANGELOG.md | 11717 ++-----------------------
scripts/__tests__/changelog-archive.test.mjs | 58 +
scripts/lib/changelog-archive.mjs | 56 +
scripts/release.mjs | 49 +-
5 files changed, 11710 insertions(+), 11052 deletions(-)
Fusion-Task-Id: FN-7875
Fusion-Task-Lineage: 220e6aa1-54fb-4800-a86e-6d8d21f6bf18
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The Grok stale-dist bug was possible because the dev/build path never
refreshed plugin dist:
- The `client` prebuild (default `pnpm dev dashboard`) rebuilt only
@fusion/core + @fusion/engine + @fusion/dashboard, never plugins.
- The FN-6638 stale-dist startup warning only scanned packages/, never
plugins/, so a source-ahead plugin dist ran phantom-old with no warning.
Changes:
- build-workspace.mjs: add `--plugins-only` to plan/build just the plugins
that changed, reusing the existing content-hash skip cache (cheap no-op when
unchanged).
- scripts/dev-prebuild-client.mjs: new orchestrator — fast core/engine/
dashboard build, then incremental changed-plugin rebuild. The `client`
prebuild now runs this single cross-platform command.
- dist-freshness.mjs: scan plugin roots (plugins/, plugins/examples/) so a
stale plugin dist is warned like a stale package dist; the warning names the
plugin dir.
Verified: --plugins-only plans only plugins, skips unchanged on the second
run, and re-plans exactly the one plugin whose source changed. All script and
CLI lib tests pass.
Fusion-Task-Id: FN-7779
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the self-grantable FUSION_RELEASE_AUTHORIZED env signal and replace
it with an interactive prompt: a real release now requires a live human to
type "authorized" at a TTY. Releases can no longer run non-interactively
(no TTY is blocked outright), and --yes does not bypass the typed phrase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Boot-smoke's post-verdict temp-dir cleanup could throw ENOTEMPTY on macOS when async writers (fsevents/Spotlight, the just-killed child) still touched the throwaway HOME/project dirs, turning an already-decided PASS into a pnpm verify:fast failure.
- Add removeTempDir() helper in scripts/boot-smoke.mjs: wraps rmSync with maxRetries/retryDelay and swallows any residual error (never throws) since cleanup always runs after the smoke verdict is decided/printed
- Replace both isolatedHome/isolatedProject rmSync call sites (exit handler and EADDRINUSE retry-port branch) with removeTempDir()
- Guard main() behind an import.meta.url === pathToFileURL(process.argv[1]) check so requiring boot-smoke.mjs from tests doesn't spawn a real server
- Add scripts/__tests__/boot-smoke.test.mjs covering ENOTEMPTY tolerance, always-failing rm, retry/backoff params, and the no-boot-on-import guard
Files changed:
scripts/__tests__/boot-smoke.test.mjs | 77 +++++++++++++++++++++++++++++++++++
scripts/boot-smoke.mjs | 44 +++++++++++++++++---
2 files changed, 115 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-7662
Fusion-Task-Lineage: 3288d055-4f70-4487-8462-933563214ce5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Refresh the test velocity baseline and coverage so quarantine counts come from the live ledger.\n\n- Update the weekly baseline report to show zero active quarantines and a neutral delta.\n- Strengthen the report-only regression test to cover stale report and history quarantine values.\n- Document the FN-7420 requirement near the regression scenario.\n\nFiles changed:\n docs/test-velocity-baseline.md | 10 +++++-----\n scripts/__tests__/test-velocity-baseline.test.mjs | 10 +++++++---\n 2 files changed, 12 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-7420
Fusion-Task-Lineage: 9144ddb5-3a72-4a37-a1d1-471871f3cbc4
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Refresh the velocity baseline artifacts so live quarantine counts are reflected consistently.
- Add regression coverage for live quarantine ledger counts overriding stale report and history values.
- Update the weekly velocity baseline report to show the current quarantine count and deltas.
- Append the latest measured velocity history row with quarantine count one.
Files changed:
docs/test-velocity-baseline.md | 22 ++---
scripts/__tests__/test-velocity-baseline.test.mjs | 75 +++++++++++++++
scripts/test-velocity-history.json | 112 ++++++++++++++++++++++
3 files changed, 198 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7414
Fusion-Task-Lineage: 7c6cd47c-482d-426c-a714-79da33d4591d
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Correct the velocity baseline to reflect an empty quarantine ledger and cover that report-only path.
- Update the published test velocity baseline quarantine totals, buckets, trend row, and #leads summary to zero.
- Add a regression test that regenerates the report from an empty live quarantine ledger without measuring lanes.
- Assert deletion-due and bucket rows stay zero and stale nonzero quarantine text is removed.
Files changed:
docs/test-velocity-baseline.md | 10 ++--
scripts/__tests__/test-velocity-baseline.test.mjs | 72 ++++++++++++++++++++++-
2 files changed, 76 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-7079
Fusion-Task-Lineage: d641adc3-f991-4d95-bcb2-7b20bbfeb0bb
greptile: my earlier existence-check anchor was necessary but insufficient.
`rootDir` (= process.cwd() when FUSION_PROJECT_DIR is unset) drives ALL workspace
discovery (readWorkspacePatterns / listWorkspacePackageInfos /
packageHasVitestConfig). Launched from a package subdir, cwd-based discovery
found no packages, so decideExecutionPlan saw "no affected package", ran only the
gate, and exited successfully WITHOUT running the live changed package tests.
Fix the root cause: resolveRepoRoot() resolves the git toplevel as the fallback
(FUSION_PROJECT_DIR still the explicit override; cwd only when git can't report a
toplevel). This is correct from any cwd inside the repo, including a git worktree
(how the engine runs per-task verification). repoRootForExistence is now
redundant and removed; the existence check defaults back to rootDir.
Demonstrated: resolveRepoRoot() from packages/core (no FUSION_PROJECT_DIR) now
resolves the repo root and finds the workspace. +1 regression test. 121/121.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- P2 (greptile): anchor the changed-test existence check at the git repo root
(repoRootForExistence via `git rev-parse --show-toplevel`) instead of rootDir,
so a script run from a package subdir without FUSION_PROJECT_DIR no longer
forms a doubled path and silently drops live tests into the delegate path.
+1 regression test (default root resolves to repo root).
- coderabbit: fix stale "1-worker lane" wording in the delegation log (now
"heavy memory-envelope lane") and the "single-worker envelope" test title,
both stale after the 1->4 worker change.
test-changed 118/118, eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three changes to make `pnpm test` reliably minimal and fail gracefully:
- @fusion/core is now a memory-envelope/wide-fan-out package (was unguarded).
It's the hub nearly everything imports (~354 test files), so a core source
edit made `vitest --changed` expand to ~the whole core suite and blow past the
engine's 15-min verification kill -> SIGKILL + task restart. Adding it to
SCOPED_AFFECTED_MEMORY_ENVELOPES applies the wide-fan-out guard (run only
directly-changed core tests, else delegate) and the bounded env. core is NOT
gate-covered, so delegation warns loudly rather than false-greens.
- Lower CLASS_BUDGET_BANDS.changed ceiling 20min -> 13min so the script watchdog
fails a runaway local lane itself (exit 124, no restart) BEFORE the engine's
15-min kill restarts the whole task. A tightening, not a timeout-widening.
Guard test pins ceiling < 900_000ms.
- Raise scoped-affected worker fan-out 1 -> 4 (operator decision). Was 1 only
for OOM safety (FN-6854/FN-6874); the fan-out guard now bounds the set so the
hundreds-of-files OOM driver no longer reaches these workers. Heap stays
6144MB/worker (~4x6GB on the lane) — revisit if a RAM-constrained CI runner
OOMs. Trades FN-5048 worker-knob guidance for throughput, scoped to the
bounded affected lanes only.
Tests: test-changed 117/117, watchdog 15/15, eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>