## 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.
Convert supported runtime question-tool calls into Fusion's durable awaiting-user-input contract so workflow execution cannot continue while the operator question is unanswered.
Fusion-Task-Id: FN-8426
Planning Mode now generates an initial work-product plan and refines it after every answer.
- Prompt both initial agent paths to create a running plan before asking questions
- Provide meaningful fallback summaries and deliverables when model plan fields are absent
- Cover streaming and non-streaming initial-plan behavior and document the refinement contract
Files changed:
.changeset/fn-8438-running-plan-generate-refine.md | 7 ++
docs/dashboard-guide.md | 3 +-
.../__tests__/planning-infinite-interview.test.ts | 76 ++++++++++++++++++++--
packages/dashboard/src/planning.ts | 58 ++++++++++++-----
4 files changed, 122 insertions(+), 22 deletions(-)
Fusion-Task-Id: FN-8438
Fusion-Task-Lineage: 29e155ec-480e-462a-be3d-07ac02d7236b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>