ci(server): fail the Postgres probe on an unmeasured baseline

The probe shipped with scripts/pg-probe-baseline.json at `failing: null`,
and an unmeasured baseline only reported. Every repository statement
Postgres refused, new SQLite-only SQL included, still passed the job;
only the dialect helper cases could fail it, so the ratchet was off until
some later commit seeded the file.

An unmeasured baseline now fails the run outside --update, and the log,
step summary and annotation say which artifact file to commit. The job
stays red until the first measurement lands, so the gate cannot reach dev
holding nothing. A run that called no repository method (an unreadable
tsconfig, a moved repositories directory) fails as well, also under
--update, so a broken plan can neither pass nor seed an empty baseline.
This commit is contained in:
Maurice
2026-10-08 16:31:32 +02:00
parent b8144bb92a
commit 12bf6c0da3
8 changed files with 102 additions and 25 deletions
+4 -3
View File
@@ -10,8 +10,9 @@
* `--update` lowers entries and drops the ones that reach zero or whose
* method is gone; it never raises or adds one. The only exception is the
* first run: a baseline whose `failing` is `null` has never been measured,
* so the check reports and passes, and `--update` (or the CI artifact)
* writes the first measurement.
* so there is nothing to compare and the run fails (scripts/pg-probe.ts)
* until the first measurement, written by `--update` or taken from the CI
* artifact, is committed.
*/
import { readFileSync } from 'node:fs';
@@ -22,7 +23,7 @@ export interface ProbeBaseline {
}
export interface BaselineVerdict {
/** The baseline has never been measured: report only. */
/** The baseline has never been measured: nothing to compare, and the run fails until it is seeded. */
unseeded: boolean;
/** Methods failing more statements than their entry allows. */
grown: { method: string; allowed: number; now: number }[];
+36 -5
View File
@@ -101,9 +101,28 @@ export function helperFailures(helpers: readonly HelperResult[]): HelperResult[]
return helpers.filter((result) => result.failure !== null);
}
/** True when the run passes: every helper case on both engines, and the ratchet. */
export function passed(input: Pick<ReportInput, 'verdict' | 'helpers'>): boolean {
return helperFailures(input.helpers).length === 0 && input.verdict.grown.length === 0 && input.verdict.stale.length === 0;
/**
* True when the probe called no repository method at all. A broken plan (an
* unreadable tsconfig, a moved repositories directory) would otherwise pass
* with nothing failing, and its empty measurement could seed the baseline.
*/
export function probedNothing(summary: Pick<ProbeSummary, 'called'>): boolean {
return summary.called === 0;
}
/**
* True when the run passes: every helper case on both engines, at least one
* repository method called, a measured baseline, and the ratchet held. An
* unmeasured baseline fails, so the gate cannot merge before it holds anything.
*/
export function passed(input: Pick<ReportInput, 'verdict' | 'helpers' | 'summary'>): boolean {
return (
helperFailures(input.helpers).length === 0 &&
!probedNothing(input.summary) &&
!input.verdict.unseeded &&
input.verdict.grown.length === 0 &&
input.verdict.stale.length === 0
);
}
export function formatConsole(input: ReportInput): string {
@@ -128,8 +147,14 @@ export function formatConsole(input: ReportInput): string {
lines.push(` ${failed.method} ${failed.code} ${failed.message}`);
lines.push(` ${oneLine(failed.sql)}`);
}
if (probedNothing(summary)) {
lines.push(`FAIL No repository method was called (${summary.methods} planned); there is nothing to hold to the baseline.`);
}
if (verdict.unseeded) {
lines.push('Baseline: not measured yet (failing is null). This run passes and writes the first measurement as the next baseline.');
lines.push(
'FAIL Baseline: not measured yet (failing is null). Commit the next baseline this run wrote ' +
'(the pg-probe-baseline artifact in CI) as scripts/pg-probe-baseline.json.',
);
}
for (const entry of verdict.grown) {
lines.push(`FAIL ${entry.method} sends ${entry.now} statement(s) Postgres refuses, ${entry.allowed} allowed.`);
@@ -153,7 +178,13 @@ export function formatMarkdown(input: ReportInput): string {
out.push(`| Distinct statements | ${summary.statements} |`);
out.push(`| Refused by Postgres | ${summary.failedStatements.length} |`);
out.push('');
if (verdict.unseeded) out.push('The baseline has not been measured yet; the `pg-probe-baseline` artifact holds the first one.', '');
if (probedNothing(summary)) out.push('No repository method was called, so there is nothing to hold to the baseline.', '');
if (verdict.unseeded) {
out.push(
'The baseline has not been measured yet, which fails the run. Commit `pg-probe-baseline.json` from the `pg-probe-baseline` artifact as `server/scripts/pg-probe-baseline.json`.',
'',
);
}
if (verdict.grown.length > 0 || verdict.stale.length > 0) {
out.push('### Ratchet', '', '| Method | Allowed | Now |', '|---|---|---|');
for (const entry of [...verdict.grown, ...verdict.stale]) out.push(`| ${cell(entry.method)} | ${entry.allowed} | ${entry.now} |`);