Files
fusion/plugins/fusion-plugin-quality/src/__tests__/no-sqlite-runtime.test.ts
gsxdsm 446c969968 fix(FN-2127): harden Quality Postgres store (project scope, races) (#2230)
## 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)
2026-07-16 11:38:34 -07:00

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/);
});
});