## 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 -->
67 lines
3.3 KiB
TypeScript
67 lines
3.3 KiB
TypeScript
/*
|
|
FNXC:PostgresCutover 2026-07-13:
|
|
Plugin-local Drizzle shapes for the CE pipeline tables (U7/U8). These used to
|
|
be imported from @fusion/core's postgresSchema.plugin.*, but FN-7936 aliases
|
|
bare `@fusion/core` imports in bundled plugin output to a tiny runtime shim —
|
|
which cannot carry the real schema objects — so the published bundled.js
|
|
failed to build ("No matching export ... for import 'postgresSchema'"). The
|
|
tables are plugin-OWNED (created by ensureCeSchema's raw DDL / the
|
|
plugin-schema-hook); defining their typed shapes here keeps the bundle
|
|
self-contained. Must stay column-identical to ensureCeSchema
|
|
(../schema.ts) and core's mirror in postgres/schema/plugin.ts.
|
|
|
|
FNXC:CompoundEngineeringSchema 2026-07-14-23:53:
|
|
The published plugin bundle still cannot import core's canonical Drizzle objects because the CLI aliases @fusion/core to a deliberately minimal runtime shim. Keep these bundle-local definitions until that boundary exports schema objects, and enforce exact column-name parity through pipeline-store.pg.test.ts so either side cannot drift silently.
|
|
*/
|
|
import { text, index, primaryKey, uniqueIndex } from "drizzle-orm/pg-core";
|
|
import { pgSchema } from "drizzle-orm/pg-core";
|
|
|
|
// Same fixed schema name core uses (postgres/schema/_shared.ts PROJECT_SCHEMA).
|
|
const projectSchema = pgSchema("project");
|
|
|
|
/** ce_pipeline_links (U7) — board-task ↔ CE-pipeline/stage/artifact back-ref. */
|
|
export const cePipelineLinks = projectSchema.table("ce_pipeline_links", {
|
|
projectId: text("project_id").notNull(),
|
|
id: text("id").notNull(),
|
|
taskId: text("task_id").notNull(),
|
|
cePipelineId: text("ce_pipeline_id").notNull(),
|
|
ceStageId: text("ce_stage_id").notNull(),
|
|
ceArtifactPath: text("ce_artifact_path"),
|
|
createdAt: text("created_at").notNull(),
|
|
}, (t) => [
|
|
primaryKey({ columns: [t.projectId, t.id] }),
|
|
index("idxCePipelineLinksPipeline").on(t.projectId, t.cePipelineId, t.createdAt, t.id),
|
|
uniqueIndex("idxCePipelineLinksTask").on(t.projectId, t.taskId),
|
|
]);
|
|
|
|
/** ce_pipeline_state (U8) — CE pipeline's OWN state machine (vs board columns). */
|
|
export const cePipelineState = projectSchema.table("ce_pipeline_state", {
|
|
projectId: text("project_id").notNull(),
|
|
cePipelineId: text("ce_pipeline_id").notNull(),
|
|
currentStage: text("current_stage").notNull(),
|
|
status: text("status").notNull(),
|
|
lastArtifactPath: text("last_artifact_path"),
|
|
createdAt: text("created_at").notNull(),
|
|
updatedAt: text("updated_at").notNull(),
|
|
}, (t) => [
|
|
primaryKey({ columns: [t.projectId, t.cePipelineId] }),
|
|
index("idxCePipelineStateStatus").on(t.projectId, t.status, t.updatedAt, t.cePipelineId),
|
|
]);
|
|
|
|
/** ce_pipeline_sync_queue (U8 / FN-5719) — board→pipeline sync signal queue. */
|
|
export const cePipelineSyncQueue = projectSchema.table("ce_pipeline_sync_queue", {
|
|
projectId: text("project_id").notNull(),
|
|
id: text("id").notNull(),
|
|
cePipelineId: text("ce_pipeline_id").notNull(),
|
|
taskId: text("task_id").notNull(),
|
|
reason: text("reason").notNull(),
|
|
fromColumn: text("from_column"),
|
|
toColumn: text("to_column"),
|
|
enqueuedAt: text("enqueued_at").notNull(),
|
|
processedAt: text("processed_at"),
|
|
}, (t) => [
|
|
primaryKey({ columns: [t.projectId, t.id] }),
|
|
index("idxCePipelineSyncQueuePending").on(t.projectId, t.processedAt, t.enqueuedAt, t.id),
|
|
index("idxCePipelineSyncQueuePipeline").on(t.projectId, t.cePipelineId, t.enqueuedAt, t.id),
|
|
]);
|