Files
fusion/scripts/pg-ddl-lane-metric.mjs
gsxdsm 8bb56a2185 FN-9134: add drift-resistant PostgreSQL DDL lane metric
Add a report-only acceptance metric and terminal evidence for PostgreSQL DDL structural experiments.

- require seven ordered control/candidate pairs with green Vitest summaries and zero leaked databases
- reject missing tests, failed summaries, nonzero exits, and unhandled runner errors
- document the no-improvement campaign result and retain the next candidate direction
- cover timing statistics, ordering, leak rejection, and runner-log validation

Files changed:
 .../test-failures/postgres-ddl-admission-bound.md  |   8 ++
 .../suite-only-flakes-observed-register.md         |   2 +-
 docs/testing.md                                    |  24 ++++
 scripts/__tests__/pg-ddl-lane-metric.test.mjs      |  62 +++++++++
 scripts/pg-ddl-lane-metric.mjs                     | 139 +++++++++++++++++++++
 5 files changed, 234 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-9134

Fusion-Task-Lineage: 3466ebf8-7125-475b-9574-6c4c92198bb3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-08-16 20:17:12 -07:00

140 lines
5.9 KiB
JavaScript

#!/usr/bin/env node
import { readFileSync } from "node:fs";
import { pathToFileURL } from "node:url";
/*
FNXC:PgDdlLaneMetric 2026-08-17-00:59:
FN-9130 showed that inline DROP DATABASE watchdog counts vary too widely on
unchanged code to judge a structural remedy. This parser is report-only: it
reads already-captured runner/diagnostic files and never opens PostgreSQL or
changes test execution. The acceptance band is derived from interleaved control
runs so moving work out of a teardown hook cannot masquerade as an improvement.
FNXC:PgDdlLaneMetric 2026-08-17-03:14:
The acceptance verdict requires ordered control/candidate pairs and affirmative
Vitest pass summaries without runner or unhandled errors. Grouped lanes and
failed or empty runs must never satisfy the timing band.
*/
export const MINIMUM_RUNS_PER_LANE = 7;
export function parseDiagnosticsJsonl(text) {
return text.split(/\r?\n/).filter(Boolean).flatMap((line) => {
try {
const row = JSON.parse(line);
return row && typeof row === "object" ? [row] : [];
} catch {
return [];
}
});
}
export function percentile(values, fraction) {
const sorted = values.filter(Number.isFinite).toSorted((a, b) => a - b);
if (sorted.length === 0) return null;
const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * fraction) - 1));
return sorted[index];
}
export function orderStatistics(values) {
const sorted = values.filter(Number.isFinite).toSorted((a, b) => a - b);
return {
count: sorted.length,
median: percentile(sorted, 0.5),
p95: percentile(sorted, 0.95),
max: sorted.at(-1) ?? null,
};
}
export function stripAnsi(text) {
return text.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g"), "");
}
export function extractWallTimeMs(log) {
const matches = [...stripAnsi(log).matchAll(/Duration\s+(?:(\d+(?:\.\d+)?)\s*m\s*)?(\d+(?:\.\d+)?)\s*(ms|s)/gi)];
const match = matches.at(-1);
if (!match) return null;
const minutes = match[1] == null ? 0 : Number(match[1]);
const duration = Number(match[2]);
if (!Number.isFinite(minutes) || !Number.isFinite(duration)) return null;
return Math.round(minutes * 60_000 + (match[3].toLowerCase() === "s" ? duration * 1_000 : duration));
}
export function summarizeRun({ label, diagnostics = [], log = "", leaks = 0 }) {
const dropRecords = diagnostics.filter((row) => row.phase === "dropDatabase");
// Completed durations and watchdog snapshots are different descriptive series.
const durations = dropRecords
.filter((row) => row.trigger === "phase-complete")
.map((row) => Number(row.phaseDurationsMs?.dropDatabase))
.filter(Number.isFinite);
const watchdogs = dropRecords.filter((row) => row.trigger === "phase-watchdog" || row.phaseIncomplete).length;
const cleanLog = stripAnsi(log);
const hasPassingFiles = /^\s*Test Files\s+.*\b[1-9]\d*\s+passed\b/im.test(cleanLog);
const hasPassingTests = /^\s*Tests\s+.*\b[1-9]\d*\s+passed\b/im.test(cleanLog);
const hasFailure =
/^\s*(?:Test Files|Tests)\s+.*\b[1-9]\d*\s+failed\b/im.test(cleanLog) ||
/^\s*Errors?\s+.*\b[1-9]\d*\s+errors?\b/im.test(cleanLog) ||
/\bUnhandled Errors?\b/i.test(cleanLog) ||
/\bNo test files found\b/i.test(cleanLog) ||
/\bexiting with code\s+[1-9]\d*\b/i.test(cleanLog);
return {
label,
wallTimeMs: extractWallTimeMs(cleanLog),
green: hasPassingFiles && hasPassingTests && !hasFailure,
leaks: Number.isInteger(leaks) && leaks >= 0 ? leaks : 0,
watchdogs,
degradationCount: diagnostics.filter((row) => row.trigger === "teardown-watchdog").length,
dropDatabase: orderStatistics(durations),
};
}
function hasAlternatingPairs(runs) {
return runs.length % 2 === 0 && runs.every((run, index) => run.label === (index % 2 === 0 ? "control" : "candidate"));
}
export function decideVerdict(runs) {
const control = runs.filter((run) => run.label === "control");
const candidate = runs.filter((run) => run.label === "candidate");
if (control.length < MINIMUM_RUNS_PER_LANE || candidate.length < MINIMUM_RUNS_PER_LANE) return "insufficient-data";
if (!hasAlternatingPairs(runs)) return "no-improvement";
if (runs.some((run) => !run.green || run.leaks !== 0 || run.wallTimeMs == null)) return "no-improvement";
const controlWalls = control.map((run) => run.wallTimeMs);
const candidateWalls = candidate.map((run) => run.wallTimeMs);
const controlMedian = percentile(controlWalls, 0.5);
const controlLowerQuartile = percentile(controlWalls, 0.25);
const candidateMedian = percentile(candidateWalls, 0.5);
const candidateWorst = Math.max(...candidateWalls);
return candidateMedian < controlLowerQuartile && candidateWorst <= controlMedian ? "improved" : "no-improvement";
}
function parseRunArgument(argument) {
const [label, diagnosticsPath, logPath, leaks = "0"] = argument.split(":");
if (!(["control", "candidate"].includes(label) && diagnosticsPath && logPath && /^\d+$/.test(leaks))) {
throw new Error("Expected --run <control|candidate>:<diagnostics.jsonl>:<runner.log>:<leaks>");
}
return summarizeRun({
label,
diagnostics: parseDiagnosticsJsonl(readFileSync(diagnosticsPath, "utf8")),
log: readFileSync(logPath, "utf8"),
leaks: Number(leaks),
});
}
export function report(runs) {
const byLane = (label) => runs.filter((run) => run.label === label);
return {
runs,
control: orderStatistics(byLane("control").map((run) => run.wallTimeMs).filter(Number.isFinite)),
candidate: orderStatistics(byLane("candidate").map((run) => run.wallTimeMs).filter(Number.isFinite)),
verdict: decideVerdict(runs),
};
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const args = process.argv.slice(2);
const runArguments = args.filter((value, index) => args[index - 1] === "--run");
if (runArguments.length === 0) throw new Error("Supply one or more --run arguments");
console.log(JSON.stringify(report(runArguments.map(parseRunArgument)), null, 2));
}