fix(FN-7952): establish PostgreSQL core authority#2108
Conversation
Make the async PostgreSQL data layer authoritative and migrate the minimum runtime composition seams needed to keep the repository buildable as the first layer of the cutover stack. Fusion-Task-Id: FN-7952
|
Warning Review limit reached
Next review available in: 10 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (24)
📝 WalkthroughWalkthroughThe PR makes PostgreSQL the required runtime backend, adds project-scoped persistence and plugin schema contracts, migrates chat, session, mission, archive, and dashboard stores to async APIs, and centralizes startup and shutdown ownership across CLI, desktop, dashboard, and engine runtimes. ChangesPostgreSQL backend cutover
Plugin schemas and migrations
Mission and lifecycle behavior
Validation coverage
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR makes PostgreSQL the authoritative metadata store across Fusion. The main changes are:
Confidence Score: 5/5The latest mission isolation changes look safe to merge.
Important Files Changed
Reviews (3): Last reviewed commit: "test(FN-7952): align plugin schema owner..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (9)
packages/engine/src/plugin-runner.ts (1)
242-254: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUpdate
plugin-runner.test.tsfor the directrunPluginSchemaInitscallpackages/engine/src/__tests__/plugin-runner.test.tsstill expectsmockTaskStore.getDatabase()anddb.runPluginSchemaInits(...); this needs to switch tomockTaskStore.runPluginSchemaInits(...)or the test will break.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/plugin-runner.ts` around lines 242 - 254, The plugin-runner tests still assert the old database-level schema initialization path. Update the relevant test setup and expectations in plugin-runner.test.ts to mock and verify mockTaskStore.runPluginSchemaInits(schemaInitHooks) directly, removing assertions against getDatabase() and db.runPluginSchemaInits(...).packages/dashboard/src/server.ts (1)
2790-2858: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winWait for backend shutdown before closing CentralCore
evictAllProjectStores()is synchronous, butevictProjectStore()firesbackendShutdown()and does not await it. That meansawait evictAllProjectStores()only waits forstore.close(), not for the embedded PostgreSQL teardown the shutdown order depends on. Return/await the backend shutdown promises here beforeoptions?.centralCore?.close().🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dashboard/src/server.ts` around lines 2790 - 2858, Update evictAllProjectStores and the underlying evictProjectStore flow so backendShutdown promises are returned and awaited, ensuring await evictAllProjectStores() completes PostgreSQL teardown before options?.centralCore?.close(). Preserve the existing store cleanup behavior while propagating each asynchronous backend shutdown result through the shutdown handler.packages/core/src/task-store/async-archive-lineage.ts (1)
321-363: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winKeep the archive snapshot until the row is restored
deleteArchivedTaskEntry(tx, entry.id, layer.projectId)should not run on the hard-delete path.unarchiveTaskImplcalls this helper before any reconstruction and does not rebuild the task here, so deleting the snapshot first can drop the only remaining copy.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/task-store/async-archive-lineage.ts` around lines 321 - 363, Update the transaction in the unarchive helper around readTaskRowInTransaction and deleteArchivedTaskEntry so the archive snapshot is deleted only when an existing soft-deleted row is successfully restored. Preserve the snapshot in the hard-delete else branch, allowing unarchiveTaskImpl to reconstruct the task from it before removal.packages/core/src/cli-session-store.ts (1)
119-152: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winUse the full UUID for CLI session IDs
randomUUID().slice(0, 8)drops this default to 32 bits of entropy. That can collide, overwrite a different in-memory session, and make the queued insert fail against thecli_sessions.idprimary key — which leaves subsequentflush()calls failing for unrelated later sessions in the same process.Proposed fix
- id: input.id ?? `cli-${randomUUID().slice(0, 8)}`, + id: input.id ?? `cli-${randomUUID()}`,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/cli-session-store.ts` around lines 119 - 152, Update the default ID generation in createSession to use the full randomUUID() value instead of truncating it with slice(0, 8). Preserve caller-provided input.id values and all other session creation behavior.packages/core/src/task-store/async-persistence.ts (1)
220-243: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winConsider making
projectIda required parameter, not optional.Both
softDeleteTaskRowInTransactionandreadTaskRowInTransactionfall back to the"__legacy_unscoped__"sentinel whenprojectIdis omitted — a silent, non-throwing degradation rather than a compile-time contract. A sibling file in this same PR (remaining-ops-2.ts'srenewCheckoutLeaseImpl) shows exactly this class of gap: a task-row mutation left unscoped by project. MakingprojectIdrequired here would force the compiler to catch any future caller that forgets to thread it through, which is cheap given the current small number of call sites.♻️ Proposed signature tightening
export async function softDeleteTaskRowInTransaction( tx: DbTransaction, id: string, deletedAt: string, allowResurrection = false, - projectId?: string, + projectId: string, ): Promise<void> { ... - eq(schema.project.tasks.projectId, projectId?.trim() || "__legacy_unscoped__"), + eq(schema.project.tasks.projectId, projectId.trim() || "__legacy_unscoped__"),export async function readTaskRowInTransaction( tx: DbTransaction, id: string, options?: { includeDeleted?: boolean }, - projectId?: string, + projectId: string, ): Promise<Record<string, unknown> | undefined> { const conditions = [ - eq(schema.project.tasks.projectId, projectId?.trim() || "__legacy_unscoped__"), + eq(schema.project.tasks.projectId, projectId.trim() || "__legacy_unscoped__"),Also applies to: 293-311
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/task-store/async-persistence.ts` around lines 220 - 243, Make projectId a required parameter in softDeleteTaskRowInTransaction and the corresponding readTaskRowInTransaction function, removing the optional fallback to "__legacy_unscoped__". Update all callers to pass the owning project ID so both task-row operations always use the composite projectId and id predicate.packages/core/src/task-store/remaining-ops-2.ts (1)
497-517: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick winScope the checkout-lease update by
projectId. The transaction already reads the row withlayer.projectId, but theUPDATEonly filters byid/deletedAt.project.tasksis project-partitioned (PRIMARY KEY (projectId, id)), so this can stamp checkout-lease fields on same-id tasks from other projects. Add the same project predicate used elsewhere in task-store updates.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/task-store/remaining-ops-2.ts` around lines 497 - 517, The UPDATE in the backend transaction within the checkout-lease update flow must be scoped to the current project. Extend its WHERE predicate to include the projectId associated with layer.projectId, alongside the existing taskId and non-deleted conditions, so only the matching project’s task is updated.packages/core/src/central-core.ts (1)
311-345: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winConcurrent
init()calls on a layer-less instance can leak a backend connection pool.The layer-less branch now does real async work (creating a PostgreSQL connection pool / embedded-postgres bootstrap) before setting
this.initialized = true. Theif (this.initialized) return;guard only protects re-entrant calls made after a prior call finished — it does not protect against two overlapping in-flight calls, since both will readinitialized === false, both will callcreateCentralBackendLayer(...), and whichever completes last simply overwritesownedBackendShutdown/ownedBackendReleaseConnections, silently orphaning the other backend's pool (and, ifembeddedPgRequestedapplies, its embedded-postgres process) with no way forclose()to ever release it. The docstring's "Idempotent — safe to call multiple times" claim doesn't hold under concurrency.🔒️ Proposed fix: memoize the in-flight init promise
+ private initPromise: Promise<void> | null = null; + async init(): Promise<void> { if (this.initialized) return; + if (this.initPromise) { + return this.initPromise; + } + this.initPromise = this.doInit(); + try { + await this.initPromise; + } finally { + this.initPromise = null; + } + } + + private async doInit(): Promise<void> { if (this.backendMode) { await asyncCentralCore.ensureBackendBootstrap(this.asyncLayer!); this.initialized = true; return; } ... (rest of existing body)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/central-core.ts` around lines 311 - 345, Make CentralCore.init concurrency-safe by memoizing the in-flight initialization promise, rather than relying only on initialized. Ensure overlapping calls await the same initialization work and only one layer-less backend is created and assigned to ownedBackendShutdown/ownedBackendReleaseConnections; clear the memoized promise after completion as appropriate while preserving the existing backend bootstrap and failure cleanup behavior.packages/core/src/task-store/remaining-ops-4.ts (1)
111-157: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick winScope the UPDATE by
project_idtoo
packages/core/src/task-store/remaining-ops-4.tsstill updatesschema.project.taskswithwhere(eq(schema.project.tasks.id, id))only.packages/core/src/postgres/schema/project.tsdefinestaskswith a composite primary key on(projectId, id), so this can write the wrong row when IDs overlap across projects.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/task-store/remaining-ops-4.ts` around lines 111 - 157, The UPDATE in the transaction callback around readTaskRowInTransaction must be scoped to both task identity and project partition. Extend the where condition in the tx.update call to match schema.project.tasks.projectId with layer.projectId alongside the existing id predicate, preserving the changed-column update behavior.packages/engine/src/scheduler.ts (1)
708-730: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDon’t swallow dependency read failures as “missing”
this.store.getTask(dependencyId).catch(() => null)makes read failures indistinguishable from absent/soft-deleted dependencies, andgetUnmetSchedulingDependencies()then treats them as satisfied. Split “not found” from real store errors in both reconciliation handlers so a temporary read failure can’t unblock dependents early.
packages/engine/src/scheduler.ts#L708-L730packages/engine/src/scheduler.ts#L876-L895🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/scheduler.ts` around lines 708 - 730, Update both dependency reconciliation handlers in packages/engine/src/scheduler.ts at lines 708-730 and 876-895: change the getTask handling so only an explicit not-found or soft-deleted result is treated as absent, while other store read errors are propagated or preserved as unresolved. Ensure getUnmetSchedulingDependencies cannot treat a temporary dependency read failure as satisfied; both sites require the same distinction.
🧹 Nitpick comments (8)
packages/engine/src/mission-execution-loop.ts (1)
323-327: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
refreshedById.get(feature.id)is a no-op fallback here — it always resolves to the exact same object asfeature.
refreshedByIdis built directly fromrefreshedFeatures(new Map(refreshedFeatures.map((f) => [f.id, f]))), and the enclosing loop isfor (const feature of refreshedFeatures). SorefreshedById.get(feature.id) ?? featurecan never differ fromfeatureitself within this same pass — the lookup doesn't pick up any state that a prior iteration of this same loop may have mutated (e.g. viatransitionLoopState/processTaskOutcomeon an earlier feature). This doesn't break anything today sincefeatureis already the freshest value the map has, but it doesn't provide the "coherent snapshot re-check" protection the accompanying comment implies for cross-feature effects within a single recovery pass.Also applies to: 388-414
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/mission-execution-loop.ts` around lines 323 - 327, Replace the ineffective refreshedById lookup in the recovery loop around feature and the corresponding logic at the later recovery block with a lookup that can observe state changes caused by earlier iterations, or otherwise explicitly re-read the current feature state before processing. Ensure cross-feature effects from transitionLoopState/processTaskOutcome are reflected during the same recovery pass, rather than falling back to the unchanged feature object.packages/core/src/async-mission-store.ts (1)
1359-1437: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
seedContractAssertionsForFeatureshas no cross-call concurrency guard, unlikecreateGeneratedFixFeature.Duplicate detection here is computed from a pre-transaction read (
listLinkedAssertionsForFeatures) and only dedups within the same batch. Two concurrent calls seeding overlapping assertions for the same feature/milestone can each pass the "already exists" check before the other commits, inserting true duplicate assertion rows — nothing here usesonConflictDoNothing/a unique constraint/row lock the waycreateGeneratedFixFeaturedoes with.for("update"). The mission-store.pg.test.ts idempotency test only covers two sequential calls, so this gap isn't caught by the current suite.If concurrent seeding for the same milestone is a realistic call pattern (e.g. multiple triage/planning workers), consider locking the milestone row (or the target features) for the duration of the insert, mirroring the pattern already used in
createGeneratedFixFeature.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/async-mission-store.ts` around lines 1359 - 1437, Update seedContractAssertionsForFeatures to serialize overlapping seed operations by acquiring row-level locks on the affected milestone rows within the transaction before inserting assertions and links, mirroring the locking approach used by createGeneratedFixFeature. Ensure duplicate detection and insertion occur under the same lock so concurrent calls for the same milestone cannot create duplicate assertion rows.packages/core/src/async-mission-store-queries.ts (1)
1038-1043: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
getMaxEventSeq+ insert is a classic race for concurrent mission-event writers.
getMaxEventSeqreadsmax(seq)with no row lock; its only caller (logMissionEventinasync-mission-store.ts) computesmaxSeq + 1and inserts inside atransactionImmediate. Under the documented READ COMMITTED default isolation, two concurrentlogMissionEventcalls for the same (or even different) missions can both read the same max before either commits, producing duplicateseqvalues. SincelistMissionEvents/getMissionEventsPageorder bydesc(seq), desc(id), a collision means two chronologically-distinct events can sort in an effectively arbitrary order relative to each other.Consider a Postgres
SEQUENCE/IDENTITYcolumn forseq, orSELECT ... FOR UPDATEon a per-mission counter row, instead ofmax()+1.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/async-mission-store-queries.ts` around lines 1038 - 1043, Replace the max(seq)+1 allocation used by getMaxEventSeq and logMissionEvent with a concurrency-safe sequence or per-mission counter-row mechanism; ensure each inserted mission event receives a unique, monotonically increasing seq without relying on an unlocked aggregate read. Update the schema and insert path consistently, preserving existing event ordering behavior.packages/core/src/__tests__/postgres/store-archive-reads.pg.test.ts (1)
91-123: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winSearch order should follow the ranking contract. This page assertion is coupled to
ts_rank DESC+createdAt ASCand the live/archive merge; if that exact order matters, derive the expected ids from the sort key instead of hard-coding a fragile sequence.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/__tests__/postgres/store-archive-reads.pg.test.ts` around lines 91 - 123, Update the searchTasks assertion in “keeps globally ordered pages exact across multiple live/cold boundaries” to derive expected IDs using the documented ts_rank DESC, then createdAt ASC ordering and the live/archive merge, rather than hard-coding the sequence. Preserve the pagination parameters and verify the returned page matches the contract-driven sort order.packages/core/src/chat-store.ts (1)
622-635: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRaw-SQL insert vs. query-builder select for the same table.
recordTokenUsagehand-writes column names in a rawsqlINSERT whilelistTokenUsageAsync(just below) uses the typed query builder againstschema.project.chatTokenUsagefor the same table. Usingdb.insert(schema.project.chatTokenUsage).values(record)here would remove the manual column-name/order duplication and get compile-time checking against schema changes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/chat-store.ts` around lines 622 - 635, Update recordTokenUsage to insert through the typed query builder using schema.project.chatTokenUsage and the existing record values instead of the hand-written raw SQL INSERT. Preserve the awaited database operation and return record after the insert, while removing the duplicated table and column definitions.packages/core/src/task-store/async-maintenance.ts (1)
18-80: 🚀 Performance & Scalability | 🔵 TrivialOperational note: confirm indexes support these retention deletes.
runAuditEventsandagentHeartbeatsdelete via subqueries againstproject.tasks/project.agents, andagentConfigRevisionsuses a window function over the whole per-project revision set. On large installs, running this periodically without indexes onproject_id/timestamp/task_id/agent_idcould produce long-running deletes that lock rows under concurrent writers. Worth confirming the underlying schema migrations already index these columns.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/task-store/async-maintenance.ts` around lines 18 - 80, Confirm the schema migrations provide indexes supporting the retention queries in pruneOperationalLogsAsync: run_audit_events should cover task_id and timestamp, agent_heartbeats should cover project_id, agent_id, and timestamp, and agent_config_revisions should support project_id with the revision ordering/filter columns. Add only missing migration indexes, preserving existing schema conventions.packages/engine/src/executor.ts (1)
17623-17623: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueIsolate the best-effort execution to prevent misleading error logs.
If
clearStaleExecutionStartBranchReferencesthrows an error, the outer catch block will logfailed to delete conflicting branch, which is misleading because the branch was already successfully deleted. Consider wrapping it in atry...catchblock, similar to how it is handled incleanupStaleBranchand the dep-abort cleanup path.♻️ Proposed refactor
- await this.store.clearStaleExecutionStartBranchReferences([branch], taskId); + try { await this.store.clearStaleExecutionStartBranchReferences([branch], taskId); } catch { /* best-effort */ }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/executor.ts` at line 17623, Wrap the best-effort call to clearStaleExecutionStartBranchReferences in its own try/catch, following the handling used by cleanupStaleBranch and the dep-abort cleanup path. Prevent any cleanup error from reaching the outer catch that reports failed to delete conflicting branch, while preserving the successful branch deletion flow.packages/engine/src/scheduler.ts (1)
1450-1460: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoffPer-tick sequential mission-store round trips in the scheduling hot path.
For each distinct blocked
sliceIdamongtodotasks, this now performs up to three sequential awaited network calls (getSlice→getMilestone→getMission) against PostgreSQL instead of the previous in-memory/local-SQLite lookups. It's deduplicated persliceIdand guarded by try/catch, but on boards with many distinct mission slices this adds latency to every scheduling pass (default poll interval 15s).Given this PR is explicitly described as an intermediate cutover step, this is likely acceptable for now, but consider batching these lookups (e.g. a single
getSlicesWithHierarchycall) in a follow-up once the mission-store async API surface stabilizes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/scheduler.ts` around lines 1450 - 1460, Leave the current sequential mission-store lookups in the todo scheduling loop unchanged for this intermediate cutover; track a follow-up optimization around the blockedSliceIds logic to batch slice, milestone, and mission retrieval through a single hierarchy lookup once the mission-store async API stabilizes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/commands/daemon.ts`:
- Around line 551-561: Remove the post-load batch call to
store.runPluginSchemaInits(schemaHooks) from the daemon flow after
loadAllPlugins(), including its now-unneeded surrounding error-handling block.
Also remove the equivalent redundant schema initialization in the plugin-runner
flow after its own loadAllPlugins() call, while preserving per-plugin
initialization performed by pluginLoader.loadPlugin().
In `@packages/cli/src/commands/dashboard.ts`:
- Line 1167: Update disposeAsync() to explicitly await store.close() during
teardown, since closeProjectStores() excludes the current workspace TaskStore.
Add this to the existing disposal chain so the cwd store’s watcher and timers
are always shut down through the exported dispose() path.
In `@packages/cli/src/commands/serve.ts`:
- Around line 617-628: Ensure failures from
store.runPluginSchemaInits(schemaHooks) propagate beyond the surrounding
plugin-load catch so serve startup fails closed. Update the enclosing
plugin-loading error flow around schemaHooks and its outer catch to preserve the
specific schema error while rethrowing it, rather than logging "[plugins] Failed
to load plugins" and continuing startup; leave non-fatal plugin-load handling
unchanged.
In `@packages/core/src/async-knowledge.ts`:
- Around line 117-134: Update queryKnowledgePagesInPostgres to use
case-insensitive matching consistent with the sibling PostgreSQL search paths,
and escape each term’s literal wildcard characters before building the pattern.
Preserve the existing project, sourceKind, ordering, limit, and mapping behavior
while replacing the unescaped like predicate construction.
In `@packages/core/src/central-core.ts`:
- Around line 356-389: Update the daemon shutdown sequence to call the store’s
close method before centralCore.close(), ensuring the store remains available
while CentralCore.markLocalNodeOffline() persists the terminal state. Preserve
the existing shutdown behavior after reordering these calls.
In `@packages/core/src/postgres/migration-stamping.ts`:
- Around line 192-226: Wrap the entire check-then-update flow in
stampMigratedProjectRows, including the fusion_sqlite_migrations ownership query
and all three partition UPDATEs, in a single db.transaction(...) callback. Use
the transaction handle for every query so canClaimLegacyQuarantine is evaluated
against the same snapshot used for the writes, matching
rekeyFallbackProjectPartition’s pattern.
In `@packages/core/src/task-store/archive-lifecycle-2.ts`:
- Around line 298-313: The task update query in the unarchive flow duplicates
project partition fallback logic. Reuse the existing projectPartition helper or
introduce the same private helper for this module, and replace the inline
projectId trim-or-sentinel expression in the update where clause while
preserving the current partition behavior.
- Around line 261-274: Update the unarchive flow around restoreTaskFromArchive
so a missing live row is rebuilt from the archive snapshot before that snapshot
is deleted. Ensure the archive entry is only cleared after the restored row is
successfully persisted and retrievable, while preserving the existing path for
already-active live rows.
In `@packages/core/src/task-store/async-archive-lineage.ts`:
- Around line 42-49: Extract the duplicated projectPartition logic into one
shared exported helper, preserving the existing trimming behavior,
"__legacy_unscoped__" sentinel, and FNXC annotation. Update projectPartition
usages in async-archive-lineage.ts, async-lifecycle.ts, and the inline copy in
archive-lifecycle-2.ts to import and reuse that helper, removing the local
definitions.
In `@packages/core/src/task-store/async-comments-attachments.ts`:
- Around line 112-129: Thread projectId through getLiveTaskColumn and every
affected caller in packages/core/src/task-store/async-comments-attachments.ts
(112-129), packages/core/src/task-store/comments-ops.ts (22-27), and
packages/core/src/task-store/remaining-ops-7.ts (626-630). Scope the tasks
lookup by both task id and schema.project.tasks.projectId, preserving the
existing archived, deleted, and missing-task behavior.
In `@packages/core/src/task-store/async-lifecycle.ts`:
- Around line 55-61: Remove the duplicate projectPartition() implementation from
async-lifecycle.ts and reuse the shared helper consolidated from
async-archive-lineage.ts, preserving the existing trimmed project ID behavior
and "__legacy_unscoped__" fallback for unbound callers.
In `@packages/core/src/task-store/async-maintenance.ts`:
- Line 26: Update the projectId handling in the async maintenance flow to log a
warning when layer.projectId is missing or blank before using the
"__legacy_unscoped__" fallback. Preserve the existing safe sentinel behavior and
retention-delete logic, but make the resulting no-op observable.
- Line 74: The usage event deletion in the maintenance flow uses an inconsistent
key for deletedByTable; update the count call around the usage_events DELETE to
use the existing camelCase key convention, matching activityLog, runAuditEvents,
agentHeartbeats, agentRuns, and agentConfigRevisions.
In `@packages/core/src/task-store/async-phantom-reservations.ts`:
- Around line 130-156: Update the reconciliation loop around
store.recordRunAuditEvent so an audit-write failure is handled per taskId rather
than by the surrounding batch catch. Preserve successfully completed tasks in
result.reconciled, and add only the task whose audit/reconciliation fails to
result.skipped with the existing error reason format; avoid reprocessing all
filesystemApproved ids after one failure.
---
Outside diff comments:
In `@packages/core/src/central-core.ts`:
- Around line 311-345: Make CentralCore.init concurrency-safe by memoizing the
in-flight initialization promise, rather than relying only on initialized.
Ensure overlapping calls await the same initialization work and only one
layer-less backend is created and assigned to
ownedBackendShutdown/ownedBackendReleaseConnections; clear the memoized promise
after completion as appropriate while preserving the existing backend bootstrap
and failure cleanup behavior.
In `@packages/core/src/cli-session-store.ts`:
- Around line 119-152: Update the default ID generation in createSession to use
the full randomUUID() value instead of truncating it with slice(0, 8). Preserve
caller-provided input.id values and all other session creation behavior.
In `@packages/core/src/task-store/async-archive-lineage.ts`:
- Around line 321-363: Update the transaction in the unarchive helper around
readTaskRowInTransaction and deleteArchivedTaskEntry so the archive snapshot is
deleted only when an existing soft-deleted row is successfully restored.
Preserve the snapshot in the hard-delete else branch, allowing unarchiveTaskImpl
to reconstruct the task from it before removal.
In `@packages/core/src/task-store/async-persistence.ts`:
- Around line 220-243: Make projectId a required parameter in
softDeleteTaskRowInTransaction and the corresponding readTaskRowInTransaction
function, removing the optional fallback to "__legacy_unscoped__". Update all
callers to pass the owning project ID so both task-row operations always use the
composite projectId and id predicate.
In `@packages/core/src/task-store/remaining-ops-2.ts`:
- Around line 497-517: The UPDATE in the backend transaction within the
checkout-lease update flow must be scoped to the current project. Extend its
WHERE predicate to include the projectId associated with layer.projectId,
alongside the existing taskId and non-deleted conditions, so only the matching
project’s task is updated.
In `@packages/core/src/task-store/remaining-ops-4.ts`:
- Around line 111-157: The UPDATE in the transaction callback around
readTaskRowInTransaction must be scoped to both task identity and project
partition. Extend the where condition in the tx.update call to match
schema.project.tasks.projectId with layer.projectId alongside the existing id
predicate, preserving the changed-column update behavior.
In `@packages/dashboard/src/server.ts`:
- Around line 2790-2858: Update evictAllProjectStores and the underlying
evictProjectStore flow so backendShutdown promises are returned and awaited,
ensuring await evictAllProjectStores() completes PostgreSQL teardown before
options?.centralCore?.close(). Preserve the existing store cleanup behavior
while propagating each asynchronous backend shutdown result through the shutdown
handler.
In `@packages/engine/src/plugin-runner.ts`:
- Around line 242-254: The plugin-runner tests still assert the old
database-level schema initialization path. Update the relevant test setup and
expectations in plugin-runner.test.ts to mock and verify
mockTaskStore.runPluginSchemaInits(schemaInitHooks) directly, removing
assertions against getDatabase() and db.runPluginSchemaInits(...).
In `@packages/engine/src/scheduler.ts`:
- Around line 708-730: Update both dependency reconciliation handlers in
packages/engine/src/scheduler.ts at lines 708-730 and 876-895: change the
getTask handling so only an explicit not-found or soft-deleted result is treated
as absent, while other store read errors are propagated or preserved as
unresolved. Ensure getUnmetSchedulingDependencies cannot treat a temporary
dependency read failure as satisfied; both sites require the same distinction.
---
Nitpick comments:
In `@packages/core/src/__tests__/postgres/store-archive-reads.pg.test.ts`:
- Around line 91-123: Update the searchTasks assertion in “keeps globally
ordered pages exact across multiple live/cold boundaries” to derive expected IDs
using the documented ts_rank DESC, then createdAt ASC ordering and the
live/archive merge, rather than hard-coding the sequence. Preserve the
pagination parameters and verify the returned page matches the contract-driven
sort order.
In `@packages/core/src/async-mission-store-queries.ts`:
- Around line 1038-1043: Replace the max(seq)+1 allocation used by
getMaxEventSeq and logMissionEvent with a concurrency-safe sequence or
per-mission counter-row mechanism; ensure each inserted mission event receives a
unique, monotonically increasing seq without relying on an unlocked aggregate
read. Update the schema and insert path consistently, preserving existing event
ordering behavior.
In `@packages/core/src/async-mission-store.ts`:
- Around line 1359-1437: Update seedContractAssertionsForFeatures to serialize
overlapping seed operations by acquiring row-level locks on the affected
milestone rows within the transaction before inserting assertions and links,
mirroring the locking approach used by createGeneratedFixFeature. Ensure
duplicate detection and insertion occur under the same lock so concurrent calls
for the same milestone cannot create duplicate assertion rows.
In `@packages/core/src/chat-store.ts`:
- Around line 622-635: Update recordTokenUsage to insert through the typed query
builder using schema.project.chatTokenUsage and the existing record values
instead of the hand-written raw SQL INSERT. Preserve the awaited database
operation and return record after the insert, while removing the duplicated
table and column definitions.
In `@packages/core/src/task-store/async-maintenance.ts`:
- Around line 18-80: Confirm the schema migrations provide indexes supporting
the retention queries in pruneOperationalLogsAsync: run_audit_events should
cover task_id and timestamp, agent_heartbeats should cover project_id, agent_id,
and timestamp, and agent_config_revisions should support project_id with the
revision ordering/filter columns. Add only missing migration indexes, preserving
existing schema conventions.
In `@packages/engine/src/executor.ts`:
- Line 17623: Wrap the best-effort call to
clearStaleExecutionStartBranchReferences in its own try/catch, following the
handling used by cleanupStaleBranch and the dep-abort cleanup path. Prevent any
cleanup error from reaching the outer catch that reports failed to delete
conflicting branch, while preserving the successful branch deletion flow.
In `@packages/engine/src/mission-execution-loop.ts`:
- Around line 323-327: Replace the ineffective refreshedById lookup in the
recovery loop around feature and the corresponding logic at the later recovery
block with a lookup that can observe state changes caused by earlier iterations,
or otherwise explicitly re-read the current feature state before processing.
Ensure cross-feature effects from transitionLoopState/processTaskOutcome are
reflected during the same recovery pass, rather than falling back to the
unchanged feature object.
In `@packages/engine/src/scheduler.ts`:
- Around line 1450-1460: Leave the current sequential mission-store lookups in
the todo scheduling loop unchanged for this intermediate cutover; track a
follow-up optimization around the blockedSliceIds logic to batch slice,
milestone, and mission retrieval through a single hierarchy lookup once the
mission-store async API stabilizes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f542e394-9d77-4bbe-960e-4941fc847f9b
📒 Files selected for processing (99)
packages/cli/src/commands/daemon.tspackages/cli/src/commands/dashboard.tspackages/cli/src/commands/serve.tspackages/core/src/__tests__/planner-intervention.test.tspackages/core/src/__tests__/plugin-hot-reload.test.tspackages/core/src/__tests__/postgres-project-discovery.test.tspackages/core/src/__tests__/postgres/archive-project-isolation.pg.test.tspackages/core/src/__tests__/postgres/cli-session-store.pg.test.tspackages/core/src/__tests__/postgres/embedded-lifecycle.test.tspackages/core/src/__tests__/postgres/mission-store.pg.test.tspackages/core/src/__tests__/postgres/operational-maintenance.pg.test.tspackages/core/src/__tests__/postgres/plugin-schema-hook.test.tspackages/core/src/__tests__/postgres/schema-applier.test.tspackages/core/src/__tests__/postgres/startup-factory-integration.test.tspackages/core/src/__tests__/postgres/startup-factory.test.tspackages/core/src/__tests__/postgres/store-archive-reads.pg.test.tspackages/core/src/__tests__/postgres/store-safe-defaults.pg.test.tspackages/core/src/__tests__/postgres/task-lifecycle-e2e.pg.test.tspackages/core/src/__tests__/postgres/workflow-authoritative-reads.pg.test.tspackages/core/src/__tests__/project-identity.test.tspackages/core/src/__tests__/sqlite-validation.test.tspackages/core/src/__tests__/store-phantom-reservation-reconcile.test.tspackages/core/src/agent-prompts.tspackages/core/src/agent-store.tspackages/core/src/async-archive-db.tspackages/core/src/async-central-core.tspackages/core/src/async-central-db.tspackages/core/src/async-knowledge.tspackages/core/src/async-mission-store-queries.tspackages/core/src/async-mission-store.tspackages/core/src/central-core.tspackages/core/src/central-db.tspackages/core/src/chat-store.tspackages/core/src/cli-session-store.tspackages/core/src/fs-watch-poll-controller.tspackages/core/src/index.gate.tspackages/core/src/index.tspackages/core/src/migration.tspackages/core/src/pi-extensions.tspackages/core/src/planner-intervention.tspackages/core/src/plugin-loader.tspackages/core/src/plugin-types.tspackages/core/src/postgres/embedded-lifecycle.tspackages/core/src/postgres/index.tspackages/core/src/postgres/migration-stamping.tspackages/core/src/postgres/migrations/0009_mission_fix_idempotency.sqlpackages/core/src/postgres/plugin-schema-hook.tspackages/core/src/postgres/schema-applier.tspackages/core/src/postgres/schema/index.tspackages/core/src/postgres/schema/plugin.tspackages/core/src/postgres/schema/project.tspackages/core/src/postgres/sqlite-migrator.tspackages/core/src/postgres/startup-factory.tspackages/core/src/project-identity.tspackages/core/src/project-root-guard.tspackages/core/src/settings-export.tspackages/core/src/sqlite-adapter.tspackages/core/src/sqlite-validation.tspackages/core/src/store.tspackages/core/src/task-store/archive-lifecycle-2.tspackages/core/src/task-store/async-archive-lineage.tspackages/core/src/task-store/async-comments-attachments.tspackages/core/src/task-store/async-lifecycle.tspackages/core/src/task-store/async-maintenance.tspackages/core/src/task-store/async-persistence.tspackages/core/src/task-store/async-phantom-reservations.tspackages/core/src/task-store/async-search.tspackages/core/src/task-store/audit-ops.tspackages/core/src/task-store/branch-group-ops.tspackages/core/src/task-store/comments-ops.tspackages/core/src/task-store/moves.tspackages/core/src/task-store/reads.tspackages/core/src/task-store/remaining-ops-1.tspackages/core/src/task-store/remaining-ops-2.tspackages/core/src/task-store/remaining-ops-3.tspackages/core/src/task-store/remaining-ops-4.tspackages/core/src/task-store/remaining-ops-5.tspackages/core/src/task-store/remaining-ops-6.tspackages/core/src/task-store/remaining-ops-7.tspackages/core/src/task-store/remaining-ops-8.tspackages/core/src/task-store/workflow-ops.tspackages/core/src/types.tspackages/dashboard/src/ai-session-store.tspackages/dashboard/src/chat-project-services.tspackages/dashboard/src/require-async-layer.tspackages/dashboard/src/routes/register-settings-sync-routes.tspackages/dashboard/src/server.tspackages/desktop/src/local-runtime.tspackages/desktop/src/local-server.tspackages/engine/src/cli-agent/runtime.tspackages/engine/src/executor.tspackages/engine/src/merger.tspackages/engine/src/mesh-lease-manager.tspackages/engine/src/mission-execution-loop.tspackages/engine/src/plugin-runner.tspackages/engine/src/runtimes/in-process-runtime.tspackages/engine/src/scheduler.tspackages/engine/src/self-healing.tspackages/engine/src/workflow-authoritative-driver.ts
Fusion-Task-Id: FN-7952
Fusion-Task-Id: FN-7952
Avoid replaying plugin schema hooks after PluginLoader has initialized each plugin, preserve fatal serve schema failures, and shut down daemon/dashboard resources in their owning order.
## Summary Engine and dashboard traffic now stays on the authoritative PostgreSQL layer across execution, recovery, project discovery, planning sessions, analytics, and shutdown. The dashboard no longer presents a migration notice for a cutover that is already mandatory. ## Design decisions - Runtime composition requires an async data layer instead of constructing a hidden SQLite fallback. - Engine workflow, mission, claim, and self-healing reads await their PostgreSQL-backed store contracts. - Project-scoped dashboard stores retain and close their backend owner exactly once. - The dashboard test quarantine entry remains paired with its Vitest exclusion, preserving the repository’s deletion-ratchet policy. ## Validation - Core, Engine, Dashboard, CLI, and Desktop typechecks pass on the stacked branch. - `pnpm test:gate` passes all 478 gate tests. - This PR changes 62 files. ## Stack - Depends on #2108. - CLI/desktop/ops, 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** * Project discovery now recognizes projects using the `.fusion/project.json` marker. * Knowledge indexing and search are more reliable across project-scoped storage. * **Bug Fixes** * Improved session, audit timeline, approval, monitoring, and analytics data consistency. * Prevented stale planning-session updates and project-store shutdown races. * Ensured chat usage and CLI session status are saved before continuing. * **UI Changes** * Removed the storage migration notice banner now that the PostgreSQL transition is complete. * **Reliability** * Improved shutdown handling, workflow execution, and worktree behavior. <!-- 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 Operators and contributors now have one current contract for the completed PostgreSQL cutover: PostgreSQL is mandatory runtime authority, legacy SQLite files are read-only migration evidence, and rollback is restore-based rather than a fallback toggle. The major changeset communicates the same boundary to published CLI users. ## Operational guidance - Documents the authoritative storage inventory and the five intentional legacy SQLite readers. - Defines maintenance-window migration, backup, restore-test, ownership, and isolation checks. - Clarifies lifecycle ownership for CLI, desktop, dashboard, engine, and bundled plugins. - Supersedes the earlier readiness review without deleting its historical record. ## Validation - Changeset format check passes. - `pnpm test:gate` passes all 478 gate tests on the completed stack. - Every file from the original oversized PR is represented in the replacement stack; this PR changes 22 files. ## Stack - Depends on #2111 → #2110 → #2109 → #2108. - Merge bottom-up in that order. Related: #2105
## Summary Operators and contributors now have one current contract for the completed PostgreSQL cutover: PostgreSQL is mandatory runtime authority, legacy SQLite files are read-only migration evidence, and rollback is restore-based rather than a fallback toggle. The major changeset communicates the same boundary to published CLI users. ## Operational guidance - Documents the authoritative storage inventory and the five intentional legacy SQLite readers. - Defines maintenance-window migration, backup, restore-test, ownership, and isolation checks. - Clarifies lifecycle ownership for CLI, desktop, dashboard, engine, and bundled plugins. - Supersedes the earlier readiness review without deleting its historical record. ## Validation - Changeset format check passes. - `pnpm test:gate` passes all 478 gate tests on the completed stack. - Every file from the original oversized PR is represented in the replacement stack; this PR changes 22 files. ## Stack - Depends on #2111 → #2110 → #2109 → #2108. - Merge bottom-up in that order. Related: #2105
## Summary Operators and contributors now have one current contract for the completed PostgreSQL cutover: PostgreSQL is mandatory runtime authority, legacy SQLite files are read-only migration evidence, and rollback is restore-based rather than a fallback toggle. The major changeset communicates the same boundary to published CLI users. ## Operational guidance - Documents the authoritative storage inventory and the five intentional legacy SQLite readers. - Defines maintenance-window migration, backup, restore-test, ownership, and isolation checks. - Clarifies lifecycle ownership for CLI, desktop, dashboard, engine, and bundled plugins. - Supersedes the earlier readiness review without deleting its historical record. ## Validation - Changeset format check passes. - `pnpm test:gate` passes all 478 gate tests on the completed stack. - Every file from the original oversized PR is represented in the replacement stack; this PR changes 22 files. ## Stack - Depends on #2111 → #2110 → #2109 → #2108. - Merge bottom-up in that order. Related: #2105
## 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 -->
## Summary Fusion’s core runtime now treats PostgreSQL as the authoritative metadata store without leaving current CLI, dashboard, desktop, or engine composition roots uncompilable between stack layers. This is the 99-file foundation for the larger cutover: subsequent PRs migrate the remaining consumers, plugins, and operator surfaces. ## Design decisions - Runtime store construction fails closed when an asynchronous PostgreSQL layer is unavailable; SQLite remains readable only at explicit migration and identity-recovery boundaries. - Project ownership is enforced across active, archived, workflow, mission, analytics, and plugin-schema data. - The small set of cross-package files in this layer are compatibility-critical call sites required for a green intermediate commit, not the complete consumer migration. - Schema migration 0008 remains assigned to session-advisor state from current `main`; mission lineage idempotency advances to 0009 so neither invariant can be skipped. ## Validation - All affected package typechecks pass: Core, Engine, Dashboard, CLI, and Desktop. - `pnpm test:gate` passes: 478 tests across the engine gate, PostgreSQL core gate, and CLI workflow shape. - The PR changes exactly 99 files. ## Stack This is the base PR. Engine/dashboard, CLI/desktop/ops, plugins, and docs/release follow as stacked PRs, each below 100 changed files. Related: #2105 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * PostgreSQL is now the standard runtime backend, with embedded PostgreSQL enabled by default. * Added project-scoped storage for tasks, archives, chat sessions, missions, knowledge pages, and operational data. * Improved archived-task search, filtering, pagination, and restoration. * Added safer plugin schema initialization with validation and project isolation. * Added PostgreSQL-backed workflow, mission, validator, and dashboard capabilities. * **Bug Fixes** * Improved startup timeout cancellation and resource cleanup. * Prevented cross-project data access and phantom reservation cleanup errors. * Ensured archived tasks remain read-only and asynchronous writes complete reliably. * Retired SQLite opt-out settings with clear startup errors. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary Engine and dashboard traffic now stays on the authoritative PostgreSQL layer across execution, recovery, project discovery, planning sessions, analytics, and shutdown. The dashboard no longer presents a migration notice for a cutover that is already mandatory. ## Design decisions - Runtime composition requires an async data layer instead of constructing a hidden SQLite fallback. - Engine workflow, mission, claim, and self-healing reads await their PostgreSQL-backed store contracts. - Project-scoped dashboard stores retain and close their backend owner exactly once. - The dashboard test quarantine entry remains paired with its Vitest exclusion, preserving the repository’s deletion-ratchet policy. ## Validation - Core, Engine, Dashboard, CLI, and Desktop typechecks pass on the stacked branch. - `pnpm test:gate` passes all 478 gate tests. - This PR changes 62 files. ## Stack - Depends on #2108. - CLI/desktop/ops, 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** * Project discovery now recognizes projects using the `.fusion/project.json` marker. * Knowledge indexing and search are more reliable across project-scoped storage. * **Bug Fixes** * Improved session, audit timeline, approval, monitoring, and analytics data consistency. * Prevented stale planning-session updates and project-store shutdown races. * Ensured chat usage and CLI session status are saved before continuing. * **UI Changes** * Removed the storage migration notice banner now that the PostgreSQL transition is complete. * **Reliability** * Improved shutdown handling, workflow execution, and worktree behavior. <!-- 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 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 -->
Summary
Fusion’s core runtime now treats PostgreSQL as the authoritative metadata store without leaving current CLI, dashboard, desktop, or engine composition roots uncompilable between stack layers. This is the 99-file foundation for the larger cutover: subsequent PRs migrate the remaining consumers, plugins, and operator surfaces.
Design decisions
main; mission lineage idempotency advances to 0009 so neither invariant can be skipped.Validation
pnpm test:gatepasses: 478 tests across the engine gate, PostgreSQL core gate, and CLI workflow shape.Stack
This is the base PR. Engine/dashboard, CLI/desktop/ops, plugins, and docs/release follow as stacked PRs, each below 100 changed files.
Related: #2105
Summary by CodeRabbit
New Features
Bug Fixes