Root paths with extensions (global.json, Directory.Packages.props, MyApp.slnx)
were rejected because isValidFileScopeEntry required a slash, which failed GitHub
imports whose issue bodies declare those paths and dropped them from effective
write scope. Share one validator between create/update and classification.
FN-8453 merge-admission registration called getTaskStore() before
runtime.start(), which threw, left the singleton lock held, and made every
later engine start fail with blocked-by-lockfile. Use config.projectId instead.
Store-open adoption runs as fusion_runtime, which lacked grants on
public.fusion_schema_migrations, so the drained-marker write failed every
boot. Migration 0032 grants SELECT plus a SECURITY DEFINER helper limited
to the exact marker, and store-open calls that helper instead of raw INSERT.
Post-extension modelRegistry.refresh() had no timeout, so a hung remote
catalog fetch left the TUI on "Loading extensions…" forever. Use a shared
15s-bounded refresh across dashboard/serve/daemon and related registration paths.
Post at most one in-progress comment per Fusion task while keeping failed deliveries retryable. Persist a durable marker and fall back to the task log when local marker storage fails after GitHub accepts the comment.
Re-lands #2336 directly on current main after its temporary base branch
was merged and deleted.\n\n- allows validating → implementing during
startup recovery\n- shares the transition table across sync and
PostgreSQL mission stores\n- preserves retry-budget enforcement\n-
includes sync and PostgreSQL regressions plus a release
changeset\n\nValidation on current main: focused sync regression 1/1 and
@fusion/core typecheck passed.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved mission recovery after an engine restart when a feature was
interrupted during validation.
* Features can resume implementation correctly after recovering from
loop-state transitions.
* Synchronous and PostgreSQL mission storage now apply the same
validation-to-implementation recovery behavior.
* **Tests**
* Added integration and unit test coverage for startup recovery and
rejection of unknown persisted loop states.
* **Release**
* Included in a patch update for `@runfusion/fusion`.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: v <v@v.speedport.ip>
## What changed
- Construct one `UsageLimitPauser` per project runtime and wire it into
both executor and triage.
- Replace the project-wide emergency stop for 429/quota failures with
provider-scoped task parking.
- Resolve execution, planning, validator, and merger providers for
active tasks; park only tasks routed through the unavailable provider.
- Preserve the actual reviewer provider on `ReviewerProviderError`, so a
Claude Plan Review 429 does not stop Codex work.
- Record `provider-rate-limit:<provider>` pause provenance without
storing provider response bodies in pause metadata.
- Run one daemon-owned provider-health monitor that probes only
providers with persisted rate-limit parks.
- Resume exact matching provider parks across every project only after
the existing authenticated usage probe succeeds and all reported
capacity windows are usable.
- Probe at five-minute intervals for the first five checks, then back
off independently per provider to 10/20/40/60 minutes with a one-hour
cap.
## Root cause and impact
The runtime refactor left `usageLimitPauser` undefined for
`TriageProcessor`. In the observed FN-922 incident, Claude Plan Review
returned four explicit 429 responses; Fusion backed off for roughly
60/120/240 seconds and then failed the task, but never invoked its pause
coordinator. The older coordinator also used `globalPause`, which would
terminate healthy sessions on every other provider.
After this change, active tasks using the unavailable provider are
parked while work routed exclusively through healthy providers
continues. Recovery is a provider-health state transition: the daemon
checks Claude/Codex authentication and metered capacity independently of
task execution, including after restart, and clears only exact
`provider-rate-limit:<provider>` parks. Logged-out, errored, exhausted,
manually paused, user-paused, and other-provider tasks remain parked.
Explicit global/engine pause controls remain unchanged.
## Surface enumeration
- executor usage-limit catches
- triage planner and Plan Review catches
- reviewer provider-error propagation
- merger usage-limit catches
- per-project runtime construction and wiring
- task model overrides plus project/global execution, planning,
validator, and merger resolution
- daemon startup/listen and shutdown lifecycle
- multi-project provider-probe deduplication
- Claude and Codex authenticated usage/capacity probes
- done/archived/already-paused task exclusions
- manual, user, generic, and other-provider pause provenance
## Symptom verification
**Original symptom:** Anthropic/Claude 429s retried and failed FN-922
without pausing Claude-routed work; a functioning global pauser would
also have stopped Codex, and provider parks had no positive-health
recovery path.
**Exact reproduction:** Raise `ReviewerProviderError("429
overloaded_error", "usage-limit", { provider: "anthropic" })` during
Plan Review with Anthropic and Codex tasks present, then return
logged-out/error/exhausted and finally healthy Claude usage responses
from the daemon probe.
**Assertion it is gone:** Anthropic-routed active tasks receive
`provider-rate-limit:anthropic`; Codex-only tasks are not paused and
`globalPause` is never changed. Unhealthy probes leave the Anthropic
tasks parked; a positive authenticated response with remaining capacity
resumes only exact Anthropic provider parks without executing a model
call as a probe.
## Validation
- `packages/engine/src/__tests__/usage-limit-detector.test.ts`: 49
passed
- `packages/dashboard/src/__tests__/provider-health-monitor.test.ts`: 8
passed
- Engine TypeScript check passed
- Dashboard server and app TypeScript checks passed
- Scoped ESLint passed
- Changeset strict format check passed
- Reapply script passed `bash -n`, two consecutive fixture applications,
and `node --check`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Tasks paused due to a provider’s rate limits can now automatically
resume when capacity returns.
- Provider health is monitored in the background, including retry
backoff for unavailable providers.
- **Bug Fixes**
- Rate-limit issues now pause only affected provider-routed tasks
instead of stopping unrelated work.
- Provider failures are handled separately from invalid review results,
improving recovery behavior.
- Healthy providers remain available while another provider is
rate-limited.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: v <v@v.speedport.ip>
## What changed
- Preserve blocking merge-review reasons when `main` advances and the
clean-room squash must be rebuilt.
- Recover the latest unresolved blocking reason from task history when a
later merge retry starts.
- Require reviewers to validate prior blockers against the complete
resulting tree, not only a smaller residual diff.
- Add regression coverage for both concurrent-main rebuilds and durable
retry recovery.
## Why
A corrective clean-room squash can be approved and then discarded when
`main` advances before landing. The rebuild previously reset the
reviewer context, allowing a later, smaller squash to be approved and
the task to be finalized as Done without rechecking the original
correctness blocker.
## Impact
Tasks with unresolved blocking review findings can no longer become Done
merely because a concurrent rebuild or later retry loses that review
context.
## Validation
- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/merger-ai.test.ts` — 45 passed
- `pnpm --filter @fusion/engine typecheck`
- ESLint on the changed merger source files
- Changeset format check
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Merge and review blockers now remain active across rebuilds and retry
attempts.
* Previous blocking reasons are preserved alongside newly identified
issues.
* Empty corrective rebuilds are reviewed before being accepted as
complete.
* Tasks can no longer be finalized solely because a rebuilt diff is
smaller when unresolved blockers remain.
* **Documentation**
* Updated release notes to describe the improved blocker behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: v <v@v.speedport.ip>
## Summary
Workflows could reach Plan Review without an authoritative PROMPT.md,
producing misleading approvals or stranding the task. Planning now
verifies durable prompt persistence before releasing the card, and every
workflow entry/review surface fails closed when its required plan is
absent. Confirmed absence triggers bounded automatic replanning;
TaskStore read outages retry in place; exhausted recovery parks visibly
without consuming review-fix budget or overriding pause, manual-review,
terminal, or merge-confirmed state.
Related: FN-8455
## Validation
- Focused workflow-artifact, graph-recovery, review, writer, and triage
regression suites pass.
- @fusion/engine typecheck passes.
- Repository lint, changeset validation, and diff checks pass.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Plan Review now fails closed when `PROMPT.md` is missing or blank,
returning a revision request with a typed `failureValue`.
* Required workflow artifacts are treated as missing unless they exist
with non-empty content; read failures are handled separately.
* Recovery now deterministically chooses replan vs “park-failed” with
bounded retries, and records a `task:required-artifact-missing` audit
event.
* **Workflow Improvements**
* Triage and approval now persist `PROMPT.md` through the dedicated
prompt-write flow and verify it was stored exactly.
* Optional-group remediation preserves typed required-artifact missing
failures for pre-merge fixes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What changed
- Detect the Claude CLI `Not logged in · Run /login` screen and return
an actionable error immediately.
- Detect Claude 2.1.x `API Usage Billing` session-stat output, which has
no subscription quota windows, instead of waiting for the PTY timeout.
- Keep the existing quota-window parser unchanged for authenticated
subscription sessions.
## Root cause and impact
Fusion only recognized the older `/usage` quota view. Newer Claude CLI
builds can render a login prompt or API-billing session statistics after
accepting `/usage`; neither contains quota windows, so Fusion waited 60
seconds and reported a misleading timeout even though it had already
received conclusive output.
Operators now get an immediate instruction to run `claude /login` on the
Fusion host.
## Validation
- `vitest run src/__tests__/usage.test.ts`: 196 passed
- Dashboard TypeScript typecheck passed
- Changeset format check passed
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Claude usage detection now recognizes authentication-required states
and stops immediately instead of waiting and reporting a misleading
“usage timeout.”
* Enhanced parsing/fallback behavior for Claude “usage” and
billing/session statistics screens, improving reliability when
subscription quota data isn’t available.
* **Tests**
* Added coverage for Claude CLI fallback scenarios after HTTP 429
responses, including validation of correct error messaging and process
termination behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: v <v@v.speedport.ip>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
## Summary
- preserve workflow IR hashes in production column-transition audit
metadata
- centralize active workflow-continuation states across release,
runtime, and executor paths
- extract and test actionable planning-continuation selection
- expand Coding (Ideas) remapping/removal coverage and add required
lifecycle decision records
Follow-up to the review body on #2378 after that PR was merged.
## Validation
- `pnpm lint`
- 123 focused core/engine tests
- `pnpm verify:fast`
- `pnpm test:gate` (487 tests)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Improved workflow continuation handling by centralizing
“active/continuation-eligible” state selection across executor,
hold/release logic, and in-process runtime.
- Persisted richer task column-transition metadata (including `irHash`)
to preserve workflow provenance.
- Ensured planning continuations exclude paused/missing/invalid tasks
and that task resolution failures surface instead of being ignored.
- Corrected fresh-worktree step execution ordering to return expected
`baselineSha`/`checkpointId` behavior.
- **New Features**
- Added and exposed `ACTIVE_WORKFLOW_WORK_ITEM_STATES` for consistent
work-item “active” semantics.
- Introduced a shared planning-continuation candidate selector to
standardize dispatchable planning work filtering.
- **Documentation**
- Clarified the small coding-ideas workflow preset omits verification
while preserving a continuous executable path.
- **Tests**
- Added coverage for planning continuation filtering and fresh-worktree
ordering behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
The Coding (Ideas) workflow now behaves like the board it presents:
Ideas stays inert, Todo owns planning and plan review, In progress owns
implementation, and In review owns code review and merge. The restored
preset is intentionally limited to that five-stage path, while the
existing Coding workflow remains unchanged.
Workflow execution now suspends at Todo→In progress instead of running
the implementation node early. A durable, single-owner continuation
records the exact resume node and survives process restarts; the
scheduler remains the only component allowed to admit the task into WIP.
Disabled optional review groups traverse the same boundary without
invoking a reviewer, avoiding the prior stuck-task behavior.
Workflow validation also rejects capacity holds with no reachable WIP
destination, so deterministic lifecycle deadlocks fail at authoring time
rather than after a task is running.
Session-settled decisions carried from planning: columns are execution
invariants, scheduler-owned WIP admission is preserved, the existing
Coding (Ideas) preset is restored and simplified, and invalid release
topology is rejected (user-approved).
## Validation
- `pnpm lint`
- `pnpm verify:fast`
- `pnpm test:gate` (296 engine, 128 PostgreSQL core, and 63 CI-shape
tests)
- Focused workflow lifecycle tests (106 assertions)
- PostgreSQL regression coverage proves atomic continuation replacement
and database rejection of a second active owner
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added durable, resumable workflow execution across capacity boundaries
(including explicit suspend/resume at the correct node).
* Introduced Todo “plan review” workflow continuations and automated
planning/capacity draining.
* Restored Coding (Ideas) as a selectable built-in and updated its lane
placement; improved optional-step group enablement support.
* **Bug Fixes**
* User moves back to Todo now cancels active workflow continuations.
* Rejected workflow boundary transitions now surface as errors (instead
of silently continuing).
* Workflows with undriveable capacity-hold configurations are now
rejected.
* **Tests / Data**
* Expanded coverage for workflow suspension, continuations, and
continuation replacement; updated database schema to persist
continuation metadata and enforce single active continuation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Keep planning questions in their dedicated surface while preserving ntfy alerts, and tighten the desktop planning panes without changing compact or shared layouts.
## Summary
Chat messages, chat room messages, and agent/user mailbox sends could
crash mid-conversation when the persisted content or metadata contained
a raw U+0000 (NUL) byte — e.g. Windows CLI diagnostic/tool output piped
directly into a message body. PostgreSQL text/jsonb columns reject NUL
outright (`unsupported Unicode escape sequence` / `\u0000 cannot be
converted to text`), which surfaced as an uncaught `PostgresError` that
aborted the write and killed the conversation turn.
A NUL-byte sanitizer already existed for the one-time SQLite →
PostgreSQL first-boot migration (`sqlite-migrator.ts`'s
`stripNulChars`/`deepStripNulChars`), but it was never wired into the
**live** write paths — only into that one-shot migration.
## What changed
- Extracted `stripNulChars`/`deepStripNulChars` into a shared
`packages/core/src/postgres/nul-sanitize.ts` module
(`sqlite-migrator.ts` now imports from it instead of defining its own
copy).
- Wired sanitization into the three live write paths that persist
free-form content/metadata:
- `async-chat-store.ts`: `addChatMessage`, `addChatRoomMessage`
- `async-message-store.ts`: `sendMessage`
- Each of these functions now also **returns the sanitized value** —
previously they returned the original, unsanitized input object even
though the sanitized value is what was actually persisted to the
database, which was a latent inconsistency I found while adding test
coverage.
## Bonus fix: embedded-Postgres startup race
While rebuilding and testing this locally via `pnpm smoke:boot`, I hit a
separate, pre-existing, reproducible race: a process joining an existing
embedded-Postgres data dir (via `postmaster.pid`, per the existing
`FNXC:PostgresStartupRace 2026-07-15-20:45` comment in
`embedded-lifecycle.ts`) can race the true owner's TCP listener bind and
get `ECONNREFUSED` on its very first connection attempt.
`bootSchemaBackendOnce` turned this into a hard `startup-factory: failed
to initialize PostgreSQL schema backend` failure with no retry.
I verified this is **not** caused by my NUL-sanitize change — it
reproduces identically on unmodified `main` (confirmed via `git stash`).
Added `JoinedInstanceUnreachableError` and one retry (mirroring the
existing `NonUtf8EmbeddedClusterError` one-retry pattern already in the
same file) instead of failing the whole boot outright.
## Tests
- New unit tests for the shared sanitizer:
`packages/core/src/__tests__/nul-sanitize.test.ts` (10 tests, including
a regression test reproducing the exact production failure signature).
- New PostgreSQL integration test coverage in the existing `.pg.test.ts`
suites, reproducing the exact production failure payload for both
`addChatMessage` and `sendMessage` and asserting both the in-memory
return value and the re-read-from-database value are NUL-free.
- Verified end-to-end against a real, disposable PostgreSQL 16 instance
(outside the vitest harness, since this dev machine lacked a local
`psql`/`pg_dump` client at the time) using a standalone script that
calls the actual patched functions with the production crash payload —
all checks passed before and after the return-value fix was added.
- `pnpm --filter @fusion/core typecheck` clean.
## Changeset
Included (`patch`, category `fix`).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Prevented crashes and PostgreSQL insertion failures when chat or
mailbox content/JSON metadata contains raw NUL (`U+0000`) bytes.
* NUL characters are now stripped from message text and deeply from
nested metadata (including JSON object keys) before writes, and
sanitized values are reflected in returned messages.
* Improved embedded PostgreSQL startup reliability by retrying once on
transient joined-instance connection-refused failures.
* **Tests**
* Added unit and PostgreSQL regression coverage for NUL sanitization
across message/chat paths and for the embedded startup retry scenario.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Treat active creation claims as transient coordination, keep the created-task handoff visible, and provide direct task and session navigation across desktop and mobile.
Keep Planning Mode history focused on questions and answers until operators explicitly expand AI thinking.
- Render planning history without forcing AI thinking open
- Verify thinking starts collapsed and expands through its existing toggle
- Add a patch changeset for the operator-facing fix
Files changed:
.changeset/fn-8449-planning-history-thinking.md | 7 +++++++
packages/dashboard/app/components/PlanningModeModal.tsx | 3 ++-
.../__tests__/PlanningModeModal.planning-flow.test.tsx | 10 ++++++++--
3 files changed, 17 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-8449
Fusion-Task-Lineage: e318eb85-561b-4d02-8fe8-1b82dcd87cc1
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Make refinement freeform-only, tighten responsive Planning controls, and keep restored idle sessions synchronized without reconnecting the stream that caused reopen errors.
Honor explicit workflow dependencies across completion writers, keep the progress cursor aligned with unfinished work, and fail closed when dependency metadata is malformed.
Open refinement areas on demand with multi-select and custom focus support, then consume synchronous AI responses so restored sessions can continue reliably. Rename the primary review action to Proceed with plan and preserve the responsive Markdown review layout.
The pg-gate rebuilt a full schema baseline (~530ms of DDL) per isolated test
file. Fanned across forks against one Postgres, those baselines and their
CREATE DATABASE calls serialized and pushed per-file beforeAll past the 15s
hookTimeout nondeterministically on high-core machines. Complements the
committed fork cap: apply the baseline ONCE per run into a run-shared,
advisory-lock-coordinated golden template, then copy each test DB directly
from it (concurrent, connection-free copies are safe). Per-module templates
and their lifecycle hooks are retained for the concurrency regression test.
No timeout was changed. pg-gate: ~46s tests / ~17s wall, 6/6 clean runs
(was flaky/364s).
Fusion-Task-Id: automation-slow-test
The test:pg-gate suite runs only *.pg.test.ts files, each building/copying a
per-file schema-template database (heavy CREATE/DROP DATABASE DDL serialized by
the single shared Postgres). Worker count derived from CPU cores over-scales on
high-core machines (6 forks on a 28-core box), oversubscribing the one Postgres
until every beforeAll exceeds the 15s hookTimeout (23/23 hook timeouts). CI's
low-core runners stay near 2 forks and pass, so it only bites high-core locals.
Add a maxCap clamp to computeMaxWorkers and a dedicated vitest.pg.config.ts
(maxCap=4) for the pg-gate, right-sizing concurrency to the actual constraint (a
single shared Postgres) rather than raising the timeout (forbidden appeasement).
Low-core machines keep their smaller CPU-derived count via min(4, cpuCap).
Verified: full test:pg-gate now passes 23 files / 126 tests on a 28-core host.
Render the canonical plan as sanitized Markdown and keep responsive review actions reachable outside the scroll owner. Require Markdown-oriented planning output and preserve stable plan.md list round-trips.
Preserve every valid suggested refinement through prompt generation, server normalization, and desktop/mobile rendering instead of truncating the list to three.
Grant the restricted runtime role read-only access to its own SQLite cutover marker. Repair existing databases with migration 0030 and apply the same row-scoped policy when first-boot migration creates the ledger.
Generate a reviewable initial plan before clarification, persist generation purpose across refreshes, and surface concrete changes and acceptance criteria with focused refinement choices.