Files
fusion/plugins/fusion-plugin-reports/src/index.ts
gsxdsm a242f1b449 fix(FN-7952): migrate bundled plugins to PostgreSQL (#2111)
## 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 -->
2026-07-15 00:27:59 -07:00

144 lines
5.2 KiB
TypeScript

import type { PluginContext } from "@fusion/core";
import { definePlugin } from "@fusion/plugin-sdk";
import { initializeApprovalState } from "./approval.js";
import { runReviewPanel } from "./review-panel.js";
import { ensureReportSchema } from "./report-schema.js";
import { createReportApprovalRoutes } from "./routes/report-approval-routes.js";
import { createReportExportRoutes } from "./routes/report-export-routes.js";
import { createReportListRoutes } from "./routes/report-list-routes.js";
import type { CombinedReview, ReviewPanelMember, RunReviewPanelInput } from "./review-types.js";
import {
getApprovalRequired,
getApproverAgentIds,
getAutoPublishOnApproval,
getPublishTargets,
settingsSchema,
} from "./settings.js";
import type { ReportCadence, ReportCreateInput } from "./store/report-types.js";
import { getReportStore } from "./store/report-store-provider.js";
import { reportTools } from "./tools.js";
/*
FNXC:ReportsAgentVocabulary 2026-07-14-18:47:
Reports are an agent-native project domain. Prompt contributions name the primitive tools and approval vocabulary so agents discover the same review and publication workflow exposed by the dashboard, without inventing direct database mutations.
*/
const plugin = definePlugin({
manifest: {
id: "fusion-plugin-reports",
name: "Reports",
version: "0.1.0",
description: "Generates beautiful HTML system-activity reports with multi-agent review.",
author: "Fusion Team",
fusionVersion: ">=0.1.0",
settingsSchema,
promptSurfaces: ["executor-system", "executor-task"],
},
state: "installed",
hooks: {
onSchemaInit: ensureReportSchema,
},
routes: [...createReportListRoutes(), ...createReportExportRoutes(), ...createReportApprovalRoutes()],
tools: reportTools,
promptContributions: {
enabledByDefault: true,
contributions: [
{
surface: "executor-system",
content: "Reports are project-scoped records with review status and approval state. Use reports_list/reports_get to inspect them and reports_export_html only after generation completes. Approval and publication remain authenticated dashboard actions.",
},
{
surface: "executor-task",
content: "For report work, inspect the persisted report entity and return its report id, status, approval state, and any publish targets or export result.",
},
],
},
dashboardViews: [
{
viewId: "reports",
label: "Reports",
componentPath: "./dashboard-view",
icon: "FileText",
placement: "primary",
order: 35,
},
],
});
export interface RunGeneratedReportReviewInput {
reportDraft: string;
reportMetadata: RunReviewPanelInput["reportMetadata"];
panel: ReviewPanelMember[];
cwd: string;
}
function toCadence(cadence: RunReviewPanelInput["reportMetadata"]["cadence"]): ReportCadence {
return cadence;
}
export async function runGeneratedReportReview(input: RunGeneratedReportReviewInput, ctx: PluginContext): Promise<CombinedReview> {
const store = getReportStore(ctx);
const reportInput: ReportCreateInput = {
cadence: toCadence(input.reportMetadata.cadence),
periodStart: input.reportMetadata.periodStart,
periodEnd: input.reportMetadata.periodEnd,
title: `Generated ${input.reportMetadata.cadence} report`,
draftMarkdown: input.reportDraft,
metadata: {
reportMetadata: input.reportMetadata,
source: "runGeneratedReportReview",
},
};
const report = await store.createReportAsync(reportInput);
await store.setStatusAsync(report.id, "review_pending");
await store.setStatusAsync(report.id, "review_in_progress");
const combinedReview = await runReviewPanel({
reportDraft: input.reportDraft,
reportMetadata: {
...input.reportMetadata,
reportId: report.id,
},
panel: input.panel,
cwd: input.cwd,
}, ctx);
const reviewed = await store.attachReviewAsync(report.id, combinedReview);
const nextApprovalState = initializeApprovalState(reviewed.status, {
approvalRequired: getApprovalRequired(ctx.settings),
autoPublishOnApproval: getAutoPublishOnApproval(ctx.settings),
approverAgentIds: getApproverAgentIds(ctx.settings),
publishTargets: getPublishTargets(ctx.settings),
});
if (nextApprovalState !== "not_required") {
const now = new Date().toISOString();
await store.updateReportAsync(report.id, {
approvalState: nextApprovalState,
...(nextApprovalState === "approved"
? { status: "approved", approvedAt: now, approvedBy: "system" }
: {}),
...(nextApprovalState === "published" ? { status: "published", publishedAt: now } : {}),
});
}
return combinedReview;
}
export default plugin;
export * from "./settings.js";
export * from "./cadence.js";
export * from "./aggregation.js";
export * from "./pipeline.js";
export * from "./runs-store.js";
export * from "./review-types.js";
export * from "./review-panel.js";
export { ensureReportSchema } from "./report-schema.js";
export { ReportStore, ReportStoreError, type ReportStoreEvents } from "./store/report-store.js";
export { getReportStore } from "./store/report-store-provider.js";
export { createReportTools, reportTools } from "./tools.js";
export * from "./store/report-types.js";
export * from "./render/index.js";