## Summary
Bundled plugins now persist shared runtime state in project-scoped
PostgreSQL tables instead of maintaining independent SQLite authority.
Reports, CLI Printing Press, Compound Engineering, Roadmap, Even
Realities, and WhatsApp all follow the same ownership and startup
contract as Fusion core.
## Design decisions
- Plugin schema hooks run through the host’s PostgreSQL owner and
enforce project isolation.
- The SDK exposes the host contract needed by bundled plugins without
importing engine internals.
- Legacy Roadmap ownership fixtures use the supported empty-owner
sentinel, preserving current composite primary/foreign keys while
exercising backfill behavior.
- The lockfile travels with the Even Realities PostgreSQL dependency so
packaged installs remain reproducible.
## Validation
- All six affected plugin builds pass.
- Affected plugin suites pass: 773 tests across Printing Press, Compound
Engineering, Even Realities, Reports, Roadmap, and WhatsApp.
- `pnpm test:gate` passes all 478 gate tests.
- This PR changes 40 files.
## Stack
- Depends on #2110 → #2109 → #2108.
- The documentation/release PR completes the stack.
Related: #2105
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Breaking Changes**
* PostgreSQL is now required for runtime storage; SQLite files are used
only as one-time migration inputs.
* The legacy `FUSION_NO_EMBEDDED_PG` fallback has been removed.
* **New Features**
* Added project-isolated PostgreSQL storage for plugins, reports, tasks,
notifications, and other plugin data.
* Added agent tools for reports and CLI service drafts.
* Added PostgreSQL schema initialization support for plugin authors.
* **Bug Fixes**
* Improved migration and recovery of legacy plugin state.
* Prevented cross-project data access and strengthened transactional
schema updates.
* **Documentation**
* Updated storage, migration, deployment, plugin authoring, CLI, and
dashboard guidance for PostgreSQL.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Adds a bounded-TTL delete tombstone to AiSessionStore so a straggling post-delete generation write can never resurrect a session the user explicitly deleted.
- AiSessionStore now records a 10-minute delete tombstone (id -> deletion timestamp) in delete(), deleteByIdAndType(), and bulk cleanup paths (cleanupOld/cleanupStaleSessions/emitDeletedSessions).
- upsert() checks the tombstone first and drops (no-ops) any write for a tombstoned id without touching SQLite or emitting ai_session:updated, fixing the root cause once in the shared store rather than per-producer (planning.ts, subtask-breakdown.ts, mission-interview.ts, milestone-slice-interview.ts).
- Tombstone entries are pruned lazily on check and piggyback on the existing cleanupStaleSessions() cadence so the in-memory map cannot grow unbounded.
- Adds a changeset (patch) documenting the user-facing fix.
- Updates docs/architecture.md and docs/storage.md with the new "AI session delete tombstones" behavior.
- Adds regression tests covering the tombstone guard in ai-session-store.test.ts and routes-planning.test.ts.
Files changed:
.changeset/fn-7949-ai-session-delete-tombstone.md | 7 +
docs/architecture.md | 2 +-
docs/storage.md | 12 +-
packages/dashboard/src/__tests__/ai-session-store.test.ts | 145 +++++++++++++++
packages/dashboard/src/__tests__/routes-planning.test.ts | 200 ++++++++++++++++++++-
packages/dashboard/src/ai-session-store.ts | 83 +++++++++
6 files changed, 446 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7949
Fusion-Task-Lineage: 8e509dae-0cc5-46cd-9c4b-9048cfda56d3
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
- contain fn_artifact_register path payloads: realpath-canonicalized
containment before stat/read — relative paths require and must stay
inside baseDir, absolute paths allowed only under baseDir or the OS
temp dir (deliberate allowance for browser/screenshot tooling);
the process.cwd() fallback is gone, symlink escapes rejected
- bind task-scoped heartbeat artifact registration to the acquired
worktree (baseDir: sessionCwd rebind after acquisition); no-task
heartbeat prompt now says to pass absolute temp-dir paths
- enforce exactly-one payload source (content/uri/dataBase64/path);
content+uri combos are now rejected to match the documented contract
- add FNXC rationale comments at both visual-artifact instruction sites
in the planning prompts (sync contract with the executor prompt)
- media route: statSync -> await stat from node:fs/promises
- range tests ride the in-memory MockSocket harness (TestResponse gains
binary-safe bodyBuffer; real-TCP helper deleted) and assert the full
206 Content-Range/Content-Length contract for every range form
- add PdfViewer coverage (iframe src/title) in DocumentsView tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Video was registrable but effectively unusable, and HTML/PDF deliverables
had no first-class path from agents to the gallery.
- media route now serves HTTP byte ranges (Accept-Ranges, 206 +
Content-Range, 416 on unsatisfiable) so <video>/<audio> seeking works
and Safari plays media at all
- video attachments (mp4/webm/mov, 100MB cap vs 5MB for other types)
bridge into the artifact registry like images; multer transport ceiling
raised to 100MB with per-type caps enforced in the store
- fn_artifact_register path payloads are signature-validated for video
(ftyp box / EBML header) and PDF (%PDF- prefix), mirroring images
- HTML doc artifacts (mimeType text/html) render as live sandboxed
iframe previews by default in the doc viewer, with a Preview/Source
toggle and the same FileEditor edit mode
- executor/heartbeat/planning prompts and tool descriptions now cover
the full type matrix: images, videos, audio, HTML mockups, PDFs, and
markdown docs, each with the registration recipe
Verified live: range requests (200/206/416) via curl, an ffmpeg-generated
mp4 playing to completion in the gallery lightbox, and an interactive
HTML mockup rendering in the sandboxed preview.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agents could never get screenshots/wireframes/mocks into the Artifacts view:
fn_artifact_register was gated on assignedAgentId (never set in default
ephemeral mode), the only image payload source was inline base64, and no
prompt ever told agents to register visual deliverables.
- always expose fn_artifact_register to executor sessions ("executor" author
fallback), resolve relative paths against the task worktree, and default
taskId to the executing task (heartbeat task lane too)
- add a `path` payload source: file read with 50MB cap, extension MIME
inference, PNG/JPEG/GIF/WebP signature + SVG sniff validation, persisted
through managed artifact storage
- executor/heartbeat/planning prompts + engine-tools reference now instruct
agents to register screenshots, wireframes, mockups, and recordings
- new ArtifactsGallery: Images/Docs/PDFs/Videos/Audio/Other category sections
and filter chips, visual tile grid + lightbox, embedded PDF viewer, audio
player rows, download rows; mobile-responsive down to the 768px breakpoint
- doc artifacts open a full viewer rendered as markdown by default with an
in-place edit mode using the shared CodeMirror FileEditor; persisted via new
GET/PATCH /api/artifacts/:id + TaskStore.updateArtifact and live-refreshed
through the new artifact:updated SSE event
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Image attachments added via TaskStore.addAttachment now surface as first-class image artifacts, reusing the existing artifact listing/SSE/media pipeline instead of duplicating bytes.
- addAttachment() registers a URI-only "image" artifact (metadata.source: "attachment") pointing at the already-written attachments/<file> path whenever an image mimeType is attached; registration is best-effort and swallows the archived/soft-deleted-task rejection so addAttachment keeps its always-succeeds contract for valid images.
- deleteAttachment() now removes any bridged artifact rows for a filename before deleting the attachment file, so /api/artifacts/:id/media can never point at a deleted attachment.
- register-task-workflow-routes.ts's resolveArtifactMediaPath now accepts task-scoped attachments/<file> URIs (in addition to artifacts/<file>) so the media route can stream bridged image-attachment artifacts; task-less artifacts remain restricted to .fusion/artifacts/.
- docs/storage.md documents the attachment→artifact bridge behavior and the media route's accepted URI prefixes.
- Added a changeset (@runfusion/fusion: minor) describing the user-facing Artifacts view change.
- Extended store-attachments and artifacts-route-integration tests to cover the new bridging and deletion behavior.
Files changed:
.changeset/fn-7791-image-attachments-artifacts.md | 7 +++
docs/storage.md | 3 +-
packages/core/src/__tests__/store-attachments.test.ts | 59 +++++++++++++++++++++-
packages/core/src/store.ts | 58 ++++++++++++++++++++-
packages/dashboard/src/routes/__tests__/artifacts-route-integration.test.ts | 40 +++++++++++++++
packages/dashboard/src/routes/register-task-workflow-routes.ts | 9 +++-
6 files changed, 172 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7791
Fusion-Task-Lineage: 4df47880-6161-4a8b-933a-2f6fc2fed953
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Board mutations (fn_task_update, CEO override, direct SQL) issued from a pi-extension tool session could silently write into a task's throwaway, never-synced worktree-local .fusion/fusion.db instead of the true project root when git CLI resolution failed (missing git binary, Docker "dubious ownership" refusal, or a non-default settings.worktreesDir). This fixes root-cause resolution and adds regression coverage plus a docs writeup.
- getProjectRootFromGitLinkedWorktree now resolves a linked worktree's project root from git's own on-disk .git/commondir metadata via pure filesystem reads before falling back to the git rev-parse CLI, so writes no longer fall through to a local hydrated copy on git-invocation failure.
- Added getMainRepoRootFromGitFile and resolveCommonGitDirFromWorktreeGitFile helpers with FNXC:Storage comments documenting the FN-7730 root cause and fix rationale.
- Added packages/core/src/__tests__/pi-extensions-write-path-durability.test.ts regression coverage for the write-path durability invariant.
- Extended packages/core/src/__tests__/pi-extensions.test.ts with additional resolution-path assertions.
- Documented the failure mode and fix in docs/storage.md ("Silent board-mutation write loss (FN-7730)").
- Added a patch changeset for @runfusion/fusion describing the user-facing fix.
Files changed:
.changeset/fn-7730-worktree-project-root-resolution.md | 7 ++
docs/storage.md | 54 ++++++++++
packages/core/src/__tests__/pi-extensions-write-path-durability.test.ts | 98 ++++++++++++++++++
packages/core/src/__tests__/pi-extensions.test.ts | 87 +++++++++++++++-
packages/core/src/pi-extensions.ts | 114 +++++++++++++++++++++
5 files changed, 359 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7730
Fusion-Task-Lineage: 00753a2d-a934-42cf-8fde-0f9b8ad98142
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes fn_task_archive and fn_task_delete rejecting tasks still referenced as a lineage parent, with no tool-exposed way to clear that reference.
- Add optional removeLineageReferences boolean param to fn_task_archive and fn_task_delete tool schemas, forwarded to store.archiveTask/store.deleteTask
- Update tool descriptions and prompt guidelines to advertise the recovery path (removeLineageReferences:true) when a lineage-parent block occurs
- Add task-lineage-unlink.test.ts covering the new parameter behavior
- Document the change in docs/storage.md
- Add changeset (@runfusion/fusion minor, category: fix)
Files changed:
.changeset/fn-7661-lineage-unlink-tools.md | 7 +
docs/storage.md | 1 +
packages/cli/src/__tests__/task-lineage-unlink.test.ts | 199 +++++++++++++++++++++
packages/cli/src/extension.ts | 28 ++-
4 files changed, 232 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7661
Fusion-Task-Lineage: 414c046c-43df-4995-85a5-ff00b345de50
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fix agent-created artifacts not showing live in the dashboard because a second TaskStore instance (e.g. engine writing while dashboard polls) never detected or re-emitted artifact:registered for rows it did not insert itself.
- Track a per-instance lastArtifactRowId cursor seeded from the max artifacts rowid at watch() startup
- In checkForChanges(), pick up artifact rows with rowid > cursor written by other instances and re-emit artifact:registered, advancing the cursor
- Advance the cursor on local inserts (insertArtifactRow) so this instance never double-emits its own writes
- Call db.bumpLastModified() in registerArtifact() so other instances' pollers actually look at the artifacts table
- Add regression tests covering cross-instance artifact registration in core store and dashboard artifacts route integration
- Add changeset and doc note for the cross-instance live-refresh fix
Files changed:
.changeset/fn-7544-artifact-cross-instance-live-refresh.md | 7 ++
docs/storage.md | 1 +
packages/core/src/__tests__/artifacts.test.ts | 85 +++++++++++++++
packages/core/src/store.ts | 63 ++++++++++-
packages/dashboard/src/routes/__tests__/artifacts-route-integration.test.ts | 120 ++++++++++++++++++++-
5 files changed, 274 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7544
Fusion-Task-Lineage: 76570505-082e-4f9a-8b06-29591c06c435
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Refresh the storage docs with updated evidence for deferring the separate live-FTS database migration.
- update the FN-6008 live `tasks_fts` telemetry summary with current bounded size measurements
- record the sampled maintenance-window merge/optimize counts and latest byte deltas
- note the reviewed malformed-database log without overstating it as a recurring post-FN-5943 FTS issue
Files changed:
docs/storage.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-6008
Fusion-Task-Lineage: d8c7c05c-f003-453d-bbd1-d9f0dda62f7c
Refresh the FTS migration guidance with fresh evidence and reconcile workflow interpreter naming.
- document post-FN-5943 production evidence showing the single-file tasks_fts index remains bounded and rebuild-free
- keep the storage guidance explicitly deferred until recurring corruption or sustained bloat is observed
- relax the foreach parser test to accept the current nested loop/foreach error wording
- align the authoritative workflow driver ID with the task branch change during conflict resolution
Files changed:
docs/storage.md | 7 ++++++-
packages/core/src/__tests__/workflow-ir-foreach.test.ts | 2 +-
packages/engine/src/workflow-authoritative-driver.ts | 2 +-
3 files changed, 8 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-6008
Fusion-Task-Lineage: d8c7c05c-f003-453d-bbd1-d9f0dda62f7c
Reduce SQLite and FTS churn on hot task-update paths.
- switch hot task writes to column-scoped UPDATE statements that touch only changed fields plus updatedAt
- keep create and replication-style persistence on full-row INSERT/UPSERT paths and rewrite task.json from the refreshed DB row after partial updates
- route executor lease renewals through renewCheckoutLease and add regression coverage plus storage docs for the narrower write paths
Files changed:
docs/storage.md | 5 +-
.../core/src/__tests__/task-partial-update.test.ts | 191 ++++++
packages/core/src/store.ts | 638 +++++++++++----------
.../src/__tests__/executor-lease-renewal.test.ts | 66 +++
packages/engine/src/executor.ts | 2 +-
5 files changed, 603 insertions(+), 299 deletions(-)
Fusion-Task-Id: FN-5945
Fusion-Task-Lineage: ad0c5b64-f411-4379-85ab-f81edfc18101
Document why live task FTS tables should stay in fusion.db for now.
- expand the storage audit note to cover FN-5976 alongside FN-5943
- explain why moving tasks_fts into an attached database would require abandoning external-content FTS and trigger-based sync
- capture the operational tradeoffs, current code paths, and defer/revisit criteria for a future redesign
Files changed:
docs/storage.md | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-5976
Fusion-Task-Lineage: 5e6c6eea-d1d6-4997-9f2b-a8597a7640be
Introduces a Goals REST API (`GET/POST/PUT /api/goals` and `GET/PUT /api/goals/:id`) backed by a new `@fusion/core` goal store and typed goal types, including comprehensive route and store test coverage. Documentation on architecture and storage is updated to reflect the new domain, and a changeset
Fusion-Task-Id: FN-5622
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5622
Implements project identity tracking and recovery across the Fusion system (FN-5411), enabling persistent identity for projects across storage migrations, daemon reattaches, and CLI session management. Adds a project identity metadata API and central reattach ensure mechanism, wires identity stampin
Fusion-Task-Id: FN-5411
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5411
FN-5407 adds paired central backup support to the Fusion task management system, with both the core backup engine and CLI commands updated to handle central database backup pairs. Documentation was updated to reflect the new capability, and two stabilization fixes were included to handle central bac
Fusion-Task-Id: FN-5407
Clears agent logs when a task is soft-deleted, with new regression tests covering the end-to-end behavior and documentation updated in the storage guide. The core logic lives in `store.ts` while `store-upsert.test.ts` is updated to reflect the new expectations.
Fusion-Task-Id: FN-5143
The merge introduces an atomic review handoff seam in the core store (`packages/core/src/store.ts`) and migrates executor and self-healing transitions to use it, replacing the previous multi-step mutable-state handoff with a single transactional operation. Extensive reliability backstops and regress
Fusion-Task-Id: FN-5241