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:
co-authored by
Claude Opus 5
parent
d5e7f4127a
commit
8157ad4029
@@ -120,6 +120,7 @@
|
||||
"previous": "PARTIAL",
|
||||
"disposition": "FIXED",
|
||||
"summary": "The shared abort primitive settles once by observation order, and all four consumers use it with bound timer snapshots.",
|
||||
"markers": ["X-AUDIT-01", "X-AUDIT-02"],
|
||||
"evidence": [
|
||||
"tests/unit/abortable-operation.test.ts",
|
||||
"tests/unit/image-cdn-runtime.test.ts",
|
||||
@@ -180,6 +181,7 @@
|
||||
"previous": "OPEN",
|
||||
"disposition": "FIXED",
|
||||
"summary": "Duplicate abort mechanics were consolidated onto the shared primitive and the file-transfer budget was reset to cover the remaining correctness code.",
|
||||
"markers": [],
|
||||
"evidence": ["config/recipes/frontend-capability-recipes.json"]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user