## Summary Follow-up after **#2164** and main’s **FN-8103 / FN-8104** (Postgres-only data access / SQLite retirement). Main already routes Quality through `AsyncQualityStore` + `getAsyncLayer()`. This PR keeps the **review hardening** that was still missing: - **Project binding** — reject request `projectId` mismatches vs bound AsyncDataLayer; all SQL uses bound project - **`createRunIfNoActive`** — advisory lock so concurrent starts cannot double-queue - **Cancel-safe runner** — cancel slot registered before the running write; pre-spawn cancel skips process - **`finalizeRun`** — never overwrites a `cancelled` terminal status - **Detached execute** — catch only execution failures; prune fail-soft in `finally` - Guardrail tests + Quality v2 plan doc ## Test plan - [ ] Task QA loads under PostgreSQL (no SQLite/backend-mode error) - [ ] Concurrent start for same task → 409 second start - [ ] Cancel during start does not leave a live orphan process - [ ] Cancelled run stays cancelled after process exit - [ ] `pnpm --filter @fusion-plugin-examples/quality test` (37 tests)
46 lines
1.5 KiB
TypeScript
46 lines
1.5 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
/*
|
|
FNXC:QualityPostgres 2026-07-16-09:03:
|
|
Guardrail: QA runtime modules must not call TaskStore.getDatabase() / SQLite.
|
|
Production Fusion is PostgreSQL-only; backend mode throws if getDatabase is used.
|
|
*/
|
|
|
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
|
function readSrc(rel: string): string {
|
|
return readFileSync(join(root, rel), "utf8");
|
|
}
|
|
|
|
/** Strip block/line comments so FNXC docs mentioning getDatabase do not fail the guard. */
|
|
function codeOnly(src: string): string {
|
|
return src
|
|
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
|
.replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
}
|
|
|
|
describe("Quality runtime has no SQLite TaskStore access", () => {
|
|
it("routes never call getDatabase", () => {
|
|
const src = codeOnly(readSrc("routes/create-routes.ts"));
|
|
expect(src).not.toMatch(/getDatabase\s*\(/);
|
|
expect(src).toMatch(/getQualityStore/);
|
|
});
|
|
|
|
it("store provider requires AsyncDataLayer", () => {
|
|
const src = codeOnly(readSrc("store/quality-store-provider.ts"));
|
|
expect(src).toMatch(/getAsyncLayer/);
|
|
expect(src).toMatch(/AsyncQualityStore/);
|
|
expect(src).not.toMatch(/getDatabase\s*\(/);
|
|
expect(src).not.toMatch(/new QualityStore/);
|
|
});
|
|
|
|
it("command runner uses async QualityStoreApi only", () => {
|
|
const src = codeOnly(readSrc("runner/command-runner.ts"));
|
|
expect(src).toMatch(/QualityStoreApi/);
|
|
expect(src).not.toMatch(/getDatabase/);
|
|
});
|
|
});
|