Files
fusion/scripts/pg-setup-boundary-probe.mjs
gsxdsm 1a3e68de5d FN-9139: add bounded PostgreSQL pre-admission measurement
Establish an inert PostgreSQL setup signal and repeatable evidence tooling without changing harness behavior.

- add explicit setup participation semantics and inertness coverage
- survey Vitest setup boundaries with isolated report-only fixtures
- bound interleaved campaigns by process group and campaign deadline
- reject missing backend samples and enable candidate diagnostics
- document the rejected boundary result and successor protocol

Files changed:
 .../test-failures/postgres-ddl-admission-bound.md  |  15 ++
 docs/testing.md                                    |  10 ++
 packages/core/package.json                         |   2 +-
 .../src/__test-utils__/pg-setup-participation.ts   |  22 +++
 .../src/__tests__/pg-setup-participation.test.ts   |  21 +++
 .../__tests__/vitest-setup-pg-inertness.test.ts    |  10 ++
 packages/core/vitest.pg.config.ts                  |  10 ++
 .../__tests__/pg-preadmission-campaign.test.mjs    |  63 +++++++
 scripts/__tests__/pg-setup-boundary-probe.test.mjs |  92 ++++++++++
 scripts/pg-preadmission-campaign.mjs               | 194 +++++++++++++++++++++
 scripts/pg-setup-boundary-probe.mjs                | 182 +++++++++++++++++++
 11 files changed, 620 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-9139

Fusion-Task-Lineage: 2cf8ccbf-37f9-4fdc-8e8f-326df823e1cd

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-08-16 21:09:58 -07:00

183 lines
7.8 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from "node:child_process";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
/*
FNXC:PgSetupBoundaryProbe 2026-08-17-03:25:
FN-9139 must establish whether Vitest lifecycle setup can host a future PostgreSQL admission
without changing the PostgreSQL harness. This isolated fixture observes only lifecycle timing and
never loads a repository config, a database client, or a project test suite.
*/
const require = createRequire(import.meta.url);
const BOUNDARIES = ["global-setup", "setup-top-level-await", "setup-before-all", "per-file-before-all"];
const DEFAULTS = Object.freeze({ delayMs: 40, timeoutMs: 15, workers: 2 });
export function parseProbeArgs(argv) {
const options = { ...DEFAULTS };
const names = new Map([
["--delay-ms", "delayMs"],
["--timeout-ms", "timeoutMs"],
["--workers", "workers"],
]);
for (let index = 0; index < argv.length; index += 1) {
const argument = argv[index];
if (argument === "--help") return { help: true };
const name = names.get(argument);
if (!name) throw new Error(`Unknown argument: ${argument}`);
const value = Number(argv[++index]);
if (!Number.isSafeInteger(value) || value <= 0) {
throw new Error(`${argument} must be a positive integer`);
}
options[name] = value;
}
return options;
}
/** Extract deliberately prefixed fixture events without coupling classification to Vitest's reporter. */
export function parseProbeEvents(output) {
return output.split(/\r?\n/).flatMap((line, index) => {
const marker = line.indexOf("PG_SETUP_BOUNDARY_EVENT:");
if (marker < 0) return [];
try {
const event = JSON.parse(line.slice(marker + "PG_SETUP_BOUNDARY_EVENT:".length));
return [{ ...event, outputIndex: index }];
} catch {
return [];
}
});
}
/** Classify timeout ownership from stable Vitest diagnostic phrases, preserving unknown output. */
export function classifyVitestOutput(output, exitCode = 0) {
if (/Hook timed out in \d+ms\./i.test(output)) return "hook-timeout";
if (/Test timed out in \d+ms\./i.test(output)) return "test-timeout";
if (exitCode === 0) return "off-budget";
return "unknown-failure";
}
export function summarizeBoundary(boundary, normalRun, stressedRun) {
const events = parseProbeEvents(normalRun.output);
const starts = events.filter((event) => event.boundary === boundary && event.phase === "start");
const firstTest = events.find((event) => event.type === "test" && event.phase === "start");
const lastBoundary = starts.at(-1);
const files = new Set(starts.map((event) => event.file).filter(Boolean));
const pids = new Set(starts.map((event) => event.pid).filter(Boolean));
const granularity = boundary === "global-setup"
? "per invocation"
: files.size > 1
? "per file"
: pids.size > 1
? "per worker"
: "per invocation";
return {
boundary,
granularity,
executions: starts.length,
workersObserved: pids.size,
filesObserved: files.size,
timeoutCharge: classifyVitestOutput(stressedRun.output, stressedRun.exitCode),
orderingRelativeToFirstTest: !firstTest || !lastBoundary
? "not-observed"
: lastBoundary.outputIndex < firstTest.outputIndex
? "before-first-test"
: "after-first-test",
};
}
function eventSource(type, boundary, phase, file = "") {
return `console.log("PG_SETUP_BOUNDARY_EVENT:" + JSON.stringify({type:${JSON.stringify(type)},boundary:${JSON.stringify(boundary)},phase:${JSON.stringify(phase)},pid:process.pid,file:${JSON.stringify(file)}}));`;
}
function fixtureFiles(boundary, delayMs, timeoutMs, workers, vitestApiUrl) {
const delay = `await new Promise((resolve) => setTimeout(resolve, ${delayMs}));`;
// The fixture is outside the repository, so its Vitest API import must be absolute.
const config = `export default { test: { include: ["tests/**/*.test.mjs"], pool: "forks", maxWorkers: ${workers}, minWorkers: ${workers}, testTimeout: ${timeoutMs}, hookTimeout: ${timeoutMs}, setupFiles: ["./setup.mjs"]${boundary === "global-setup" ? ', globalSetup: ["./global-setup.mjs"]' : ""} } };\n`;
const setup = boundary === "setup-top-level-await"
? `${eventSource("boundary", boundary, "start")}${delay}${eventSource("boundary", boundary, "end")}`
: boundary === "setup-before-all"
? `import { beforeAll } from ${JSON.stringify(vitestApiUrl)};\nbeforeAll(async () => { ${eventSource("boundary", boundary, "start")}${delay}${eventSource("boundary", boundary, "end")} });`
: "";
const global = boundary === "global-setup"
? `export default async function setup() { ${eventSource("boundary", boundary, "start")}${delay}${eventSource("boundary", boundary, "end")} }`
: "export default async function setup() {}";
const test = (file) => `import { beforeAll, test } from ${JSON.stringify(vitestApiUrl)};\n${boundary === "per-file-before-all" ? `beforeAll(async () => { ${eventSource("boundary", boundary, "start", file)}${delay}${eventSource("boundary", boundary, "end", file)} });` : ""}\ntest(${JSON.stringify(file)}, () => { ${eventSource("test", "test", "start", file)} });\n`;
return {
"vitest.config.mjs": config,
"setup.mjs": setup,
"global-setup.mjs": global,
"tests/one.test.mjs": test("one"),
"tests/two.test.mjs": test("two"),
};
}
async function writeFixture(directory, files) {
await Promise.all(Object.entries(files).map(async ([file, content]) => {
const path = join(directory, file);
await mkdir(dirname(path), { recursive: true });
await writeFile(path, content, "utf8");
}));
}
function runVitest(directory) {
const vitest = join(dirname(require.resolve("vitest", { paths: [resolve(process.cwd(), "packages/core")] })), "vitest.mjs");
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [vitest, "run", "--config", "vitest.config.mjs", "--reporter=verbose"], {
cwd: directory,
stdio: ["ignore", "pipe", "pipe"],
});
let output = "";
child.stdout.on("data", (chunk) => { output += chunk; });
child.stderr.on("data", (chunk) => { output += chunk; });
child.on("error", reject);
child.on("close", (exitCode) => resolve({ output, exitCode: exitCode ?? 1 }));
});
}
async function runBoundary(boundary, options, stressed) {
const directory = await mkdtemp(join(tmpdir(), "fusion-pg-setup-boundary-"));
try {
const delayMs = stressed ? Math.max(options.delayMs, options.timeoutMs + 20) : Math.min(options.delayMs, Math.max(1, options.timeoutMs - 1));
const vitestApiUrl = pathToFileURL(require.resolve("vitest", { paths: [resolve(process.cwd(), "packages/core")] })).href;
await writeFixture(directory, fixtureFiles(boundary, delayMs, options.timeoutMs, options.workers, vitestApiUrl));
return await runVitest(directory);
} finally {
await rm(directory, { recursive: true, force: true });
}
}
export async function runProbe(options) {
const summaries = [];
for (const boundary of BOUNDARIES) {
const normalRun = await runBoundary(boundary, options, false);
const stressedRun = await runBoundary(boundary, options, true);
summaries.push(summarizeBoundary(boundary, normalRun, stressedRun));
}
return summaries;
}
function printHelp() {
console.log("Usage: node scripts/pg-setup-boundary-probe.mjs [--delay-ms N] [--timeout-ms N] [--workers N]");
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
try {
const options = parseProbeArgs(process.argv.slice(2));
if (options.help) {
printHelp();
} else {
console.table(await runProbe(options));
}
} catch (error) {
console.error(`[pg-setup-boundary-probe] ${error.message}`);
process.exitCode = 1;
}
}