fix: make the ledger gate check the column it claims to check

Adversarial re-verification of the gate itself found three holes.

The disposition check matched the finding's verdict anywhere in the table
row, and every row also carries the prior verdict — so `| NS-01 | PARTIAL
| FIXED |` satisfied a receipt that said either. It now reads the
disposition from its own column, which is the check the gate was supposed
to be performing all along.

An evidence path only had to exist. A row could point at an unrelated
suite and look substantiated, so each evidence file must now name the
finding it is evidence for; rows the review labelled differently declare
their own markers rather than the check being loosened.

The file-transfer bundle budget was a number typed into prose next to a
number in config, which is exactly the evidence drift the cross-audit
raised. The gate now compares them.

Each hole was confirmed by breaking the input and watching the gate fail:
a disposition disagreement, a budget changed to 60,000, and NS-07 pointed
at the public cache suite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-15 04:31:07 +09:00
co-authored by Claude Opus 5
parent d5e7f4127a
commit 8157ad4029
2 changed files with 50 additions and 3 deletions
+48 -3
View File
@@ -18,6 +18,12 @@ type Disposition = Readonly<{
disposition: string;
summary: string;
evidence: readonly string[];
/**
* Labels the evidence files actually carry. Defaults to the finding id; a row
* declares its own when the review labelled the work differently, as the
* cross-audit ids did.
*/
markers?: readonly string[];
}>;
const DISPOSITIONS_PATH = "docs/operations/adapter-remediation-dispositions.json";
@@ -29,6 +35,9 @@ const LEDGER_PATH = "docs/operations/adapter-remediation-ledger.md";
*/
const SECTION_HEADING = "## Third re-review (2026-08-14)";
const CLOSED_DISPOSITIONS: ReadonlySet<string> = new Set(["FIXED"]);
const RECIPES_PATH = "config/recipes/frontend-capability-recipes.json";
/** The table is `| id | prior verdict | disposition | evidence |`. */
const DISPOSITION_OFFSET_FROM_ID = 2;
/** Sentences that assert everything is done, and therefore need proof. */
const BLANKET_CLOSURE = /All\s+(?:\d+|findings|rows)[^.\n]*\b(?:FIXED|closed)\b/giu;
@@ -69,11 +78,22 @@ async function main(): Promise<void> {
problems.push(`${row.id}: at least one evidence path is required`);
continue;
}
const markers = row.markers ?? [row.id];
for (const path of row.evidence) {
let contents: string;
try {
await access(path);
contents = await readFile(path, "utf8");
} catch {
problems.push(`${row.id}: evidence path does not exist: ${path}`);
continue;
}
// A path that exists proves nothing on its own. The file has to name the
// finding it is evidence for, so a row cannot point at an unrelated suite
// and look substantiated.
if (markers.length > 0 && !markers.some((mark) => contents.includes(mark))) {
problems.push(
`${row.id}: ${path} does not mention ${markers.join(" or ")}`,
);
}
}
// The prose must carry the same verdict for the same id, so a reader of the
@@ -85,9 +105,16 @@ async function main(): Promise<void> {
problems.push(`${row.id}: no row in ${LEDGER_PATH}`);
continue;
}
if (!line.includes(`\`${row.disposition}\``)) {
// The row carries the prior verdict as well, so the disposition is read
// from its own column. Matching anywhere in the line let the "previous"
// cell satisfy the check and hid a disagreement between the two records.
const cells = line.split("|").map((cell) => cell.trim());
const recorded = cells[cells.indexOf(row.id) + DISPOSITION_OFFSET_FROM_ID];
if (recorded !== `\`${row.disposition}\``) {
problems.push(
`${row.id}: ${LEDGER_PATH} does not record \`${row.disposition}\``,
`${row.id}: ${LEDGER_PATH} records ${
recorded ?? "nothing"
} where the receipt says \`${row.disposition}\``,
);
}
}
@@ -106,6 +133,24 @@ async function main(): Promise<void> {
);
}
// GOV-05 / X-AUDIT-04. The most drift-prone evidence in this document is a
// number someone typed. The one number that gates a release is checked
// against its source of truth rather than trusted.
const recipes: unknown = JSON.parse(await readFile(RECIPES_PATH, "utf8"));
const fileTransfer = (
(recipes as { recipes?: readonly Record<string, unknown>[] }).recipes ?? []
).find((recipe) => recipe["id"] === "file-transfer");
const budget = fileTransfer?.["bundleBudgetGzipBytes"];
if (typeof budget !== "number") {
problems.push(`${RECIPES_PATH}: file-transfer has no bundle budget`);
} else if (!ledger.includes(budget.toLocaleString("en-US"))) {
problems.push(
`${LEDGER_PATH} does not state the configured file-transfer budget of ${budget.toLocaleString(
"en-US",
)} gzip bytes`,
);
}
if (problems.length > 0) {
for (const problem of problems) console.error(problem);
process.exitCode = 1;