fix(census): 4 RED ratchet tests on main, and the report said nothing at zero (#3218)

Two problems, both caused by the backlog actually shrinking.

## 1. Four failing tests on main

**Pre-existing, not introduced here** — running this file on clean
`origin/main` gives `49 passed / 4 failed` with identical messages. I
checked that before touching anything, because the failures surfaced
while I was editing the same file.

The ratchet cases build their fixture like this:

```ts
Object.entries(baseline.byFile).find(([, c]) => c > 1)   // needs a file with MORE THAN ONE guard
```

After the tail reclassification no such entry exists. `find` returns
undefined → `byFile[undefined] = NaN` → the baseline is corrupt → every
case fails with `expected … to contain 'TIGHTENED'`, a message that
points squarely at the CLI when the **fixture** is at fault. That
misdirection is why this sat red.

The ratchet doesn't care *which* file it tightens, only that an
allowance exceeds the measured count. So `inflate` now takes any entry,
and synthesises one against a real scanned file when the backlog is
empty.

`deflate` is the harder half: a RISE needs an allowance **below** the
real count, and once every measured count is 0 the only value below is
negative. The empty case uses `-1`. That is not a realistic baseline
value and the comment says so — it is the sole way to exercise the
`measured > allowed` comparison against a tree with nothing left to
count, which is the tree this suite now runs on.

Same class as the unbounded-slice rot in #3207: **census self-tests
coupled to the size of a shrinking backlog.** That is now twice, so it
is a pattern rather than an accident.

## 2. The report went silent at the finish line

The verdict was two inline branches and neither fired at zero —
`CONVERSION QUEUE EMPTY` required `totals.column > 0`. So the one state
the entire fleet phase was working toward printed **nothing**, which
reads as a broken scan rather than the protected end state.

Extracted to a pure `describeBacklogState({ columnGuards,
unexaminedGuards })` returning lines, so the caller stays a dumb
printer:

```
BACKLOG ZERO: no lifecycle-column guard remains.
This is the protected end state, not an empty scan — `--strict` fails on any RISE, so a new
guard cannot land silently. Use the role helpers (resolveLifecycleColumns / columnHasRole).
```

Pure **specifically** so the zero state is testable before the tree
reaches zero. While it was inline, only the *current* backlog state was
observable — and a message nobody can test before they need it is the
one that is wrong when they do.

## Evidence

| check | result |
|---|---|
| census test file | **53 passed** (was 49 passed / 4 failed) |
| behaviour on today's tree | **unchanged** — identical `CONVERSION
QUEUE EMPTY` block |
| empty-baseline probe | exits 1, `column-guard count ROSE` |
| forced zero verdict | prints `BACKLOG ZERO … not an empty scan` |
| `--strict` / `check-fnxc-future-dates` / eslint | 0 / 0 / clean |
| `pnpm test:gate` | exit 0 (744 tests) |

Four new tests pin all three states, including that the unexamined
branch must **not** claim the queue is empty while real work is
outstanding.

## Census

No guard converted — this is tooling and test repair. Backlog unchanged
at 1, which #3215 takes to 0.
This commit is contained in:
gsxdsm
2026-07-31 11:50:14 -07:00
committed by GitHub
parent 78d87f0a10
commit cfcbba6f81
3 changed files with 111 additions and 12 deletions

View File

@@ -23,6 +23,7 @@ import {
summarize,
mixedVocabularyFiles,
hasDeferralNote,
describeBacklogState,
} from "../../../../scripts/lib/lifecycle-column-census.mjs";
function census(source: string) {
@@ -786,10 +787,25 @@ describe("the ratchet follows the count down", () => {
}
/** Inflate one file's allowance, which is a DROP from the CLI's point of view. */
/*
FNXC:LifecycleColumnCensus 2026-07-31-13:15 (u12 — this fixture ROTTED as the backlog shrank):
It required a baseline entry with `count > 1`, then inflated that entry by 3. Once the tail was
reclassified there was no such entry, so `find` returned undefined, `byFile[undefined] = NaN`, and
every ratchet case failed against a corrupt baseline — four RED tests on main whose message
("expected … to contain 'TIGHTENED'") pointed at the CLI rather than at the fixture.
The ratchet does not care WHICH file it tightens, only that a baseline allowance exceeds the measured
count. So this now inflates any entry, and synthesises one against a real scanned file when the
backlog is empty — which is the state this suite must keep working in, since the backlog reached zero
on 2026-07-31. Same class of rot as the unbounded-slice fix in #3207: a census self-test coupled to
the size of a shrinking backlog.
*/
const INFLATE_FALLBACK_FILE = "packages/core/src/store.ts";
const inflate = (baseline: any): string => {
const [file, count] = Object.entries(baseline.byFile as Record<string, number>).find(([, c]) => c > 1) ?? [];
baseline.byFile[file as string] = (count as number) + 3;
return file as string;
const byFile = baseline.byFile as Record<string, number>;
const [file, count] = Object.entries(byFile)[0] ?? [INFLATE_FALLBACK_FILE, 0];
byFile[file] = (count as number) + 3;
return file;
};
it("TIGHTENS on a drop and exits 0, so somebody else's merge cannot redden the gate", async () => {
@@ -833,10 +849,23 @@ describe("the ratchet follows the count down", () => {
}, 30_000);
it("still FAILS on a rise, which is the check's actual purpose", async () => {
/*
FNXC:LifecycleColumnCensus 2026-07-31-13:20 (u12 — same rot as `inflate`, plus the zero-backlog case):
A RISE means "measured exceeds allowed", so it needs an allowance BELOW the real count. While guards
exist, deflating any entry gives that. Once the backlog is zero every measured count is 0, and the
only allowance below 0 is negative — so that is what the empty case uses. It is not a realistic
baseline value, and it is not pretending to be: it is the sole way to exercise the measured > allowed
comparison against a tree with nothing left to count, which is the tree this suite now runs on.
*/
const deflate = (baseline: any): string => {
const [file, count] = Object.entries(baseline.byFile as Record<string, number>).find(([, c]) => c > 1) ?? [];
baseline.byFile[file as string] = (count as number) - 1;
return file as string;
const byFile = baseline.byFile as Record<string, number>;
const entry = Object.entries(byFile)[0];
if (!entry) {
byFile[INFLATE_FALLBACK_FILE] = -1;
return INFLATE_FALLBACK_FILE;
}
byFile[entry[0]] = (entry[1] as number) - 1;
return entry[0];
};
const run1 = await run(deflate, ["--strict"]);
@@ -909,3 +938,43 @@ describe("the deferral-note rule the queue-empty verdict is computed from", () =
}
});
});
/*
FNXC:LifecycleColumnCensus 2026-07-31-13:00 (u12 — the state the tree cannot demonstrate yet):
`describeBacklogState` is pure precisely so the ZERO state is testable before the tree reaches zero.
While it was an inline branch in the CLI, only the CURRENT backlog state was observable, and the zero
branch did not exist at all — the report printed nothing at the finish line.
*/
describe("the backlog-state verdict the bare command prints", () => {
it("states ZERO as a protected end state, not an empty scan", () => {
const lines = describeBacklogState({ columnGuards: 0, unexaminedGuards: 0 });
expect(lines.join(" ")).toContain("BACKLOG ZERO");
// The load-bearing half: a bare "0" reads as a broken scan unless the report says otherwise.
expect(lines.join(" ")).toContain("not an empty scan");
expect(lines.join(" ")).toContain("--strict");
});
it("calls a fully-deferred backlog DEBT rather than a work queue", () => {
const lines = describeBacklogState({ columnGuards: 7, unexaminedGuards: 0 });
expect(lines.join(" ")).toContain("CONVERSION QUEUE EMPTY");
expect(lines.join(" ")).toContain("7 remaining column guard(s)");
expect(lines.join(" ")).toContain("DEBT, not a work queue");
});
it("reports unexamined guards as claimable work", () => {
const lines = describeBacklogState({ columnGuards: 7, unexaminedGuards: 3 });
expect(lines.join(" ")).toContain("3 unexamined guard(s) remain");
// Must NOT tell a worker the queue is empty while real work is outstanding.
expect(lines.join(" ")).not.toContain("QUEUE EMPTY");
expect(lines.join(" ")).not.toContain("BACKLOG ZERO");
});
it("prefers the ZERO state over the unexamined branch when both could apply", () => {
// Defensive: zero guards cannot have unexamined ones. If a caller ever passes both, the honest
// answer is still zero — reporting "N unexamined" against an empty backlog would be a fabrication.
expect(describeBacklogState({ columnGuards: 0, unexaminedGuards: 3 }).join(" ")).toContain("BACKLOG ZERO");
});
});

View File

@@ -285,3 +285,34 @@ export const FLAG_MARKERS = /FLAGGED|LEFT COUNTED|left counted|deliberately NOT
export function hasDeferralNote(lines, line) {
return FLAG_MARKERS.test(lines.slice(Math.max(0, line - 41), line).join(" "));
}
/*
FNXC:LifecycleColumnCensus 2026-07-31-12:55 (u12 — the report went SILENT at the finish line):
The backlog-state verdict was two inline branches in the CLI, and neither fired at a count of ZERO —
`CONVERSION QUEUE EMPTY` required `totals.column > 0`. So the one state the whole fleet phase was
working toward printed nothing at all, which reads as a broken scan rather than the protected end
state. Reached zero on 2026-07-31 (722 at the start of the phase).
Pure and exported so all three states are unit-testable. Against the real tree only the CURRENT state
is observable, so an inline branch for zero could not be tested until the tree was already zero — and
a message nobody can test before they need it is the one that is wrong when they do.
Returns an array of lines (empty = print nothing), so the caller stays a dumb printer.
*/
export function describeBacklogState({ columnGuards, unexaminedGuards }) {
if (columnGuards === 0) {
return [
"BACKLOG ZERO: no lifecycle-column guard remains.",
"This is the protected end state, not an empty scan — `--strict` fails on any RISE, so a new",
"guard cannot land silently. Use the role helpers (resolveLifecycleColumns / columnHasRole).",
];
}
if (unexaminedGuards === 0) {
return [
`CONVERSION QUEUE EMPTY: all ${columnGuards} remaining column guard(s) carry a documented deferral note.`,
"There is no unexamined guard to claim. A nonzero backlog above is DEBT, not a work queue.",
"Re-read the note at a site before converting it; run --claims to also check open-PR ownership.",
];
}
return [`${unexaminedGuards} unexamined guard(s) remain (no deferral note) — run --triage to list them by file.`];
}

View File

@@ -48,6 +48,7 @@ import {
summarize as summarizeText,
mixedVocabularyFiles,
hasDeferralNote,
describeBacklogState,
} from "./lib/lifecycle-column-census.mjs";
const HERE = dirname(fileURLToPath(import.meta.url));
@@ -238,12 +239,10 @@ if (json) {
`--claims` can see open PRs.
*/
const { open: unexaminedGuards } = triageFindings();
if (summary.totals.column > 0 && unexaminedGuards.length === 0) {
console.log(`\n CONVERSION QUEUE EMPTY: all ${summary.totals.column} remaining column guard(s) carry a documented deferral note.`);
console.log(` There is no unexamined guard to claim. A nonzero backlog above is DEBT, not a work queue.`);
console.log(` Re-read the note at a site before converting it; run --claims to also check open-PR ownership.`);
} else if (unexaminedGuards.length > 0) {
console.log(`\n ${unexaminedGuards.length} unexamined guard(s) remain (no deferral note) — run --triage to list them by file.`);
const verdict = describeBacklogState({ columnGuards: summary.totals.column, unexaminedGuards: unexaminedGuards.length });
if (verdict.length > 0) {
console.log("");
for (const line of verdict) console.log(` ${line}`);
}
if (triage) {
const { flagged, open } = triageFindings();