The ledger declared "All 38 are now FIXED" while six of those rows were reproducibly partial. A summary sentence is cheap and a reviewer reads it as evidence, so the claim is now derived from a machine-readable record: `docs/operations/adapter-remediation-dispositions.json` carries each finding's disposition and the test paths that hold it, and `check:remediation-ledger` joins that file to the prose, verifies every evidence path exists, and refuses a blanket closure sentence while any row is still open. The shared-abort gate had the same weakness in miniature: it passed when at least one production file imported the primitive, so an unrelated import satisfied it while Image and Resumable kept their own diverging copies. It now requires the four named consumers to resolve their import to the primitive itself, and prints the exact importer set rather than a count. `check:optional-recipes:source` was already failing before this work (52,078 against a 52,000 budget) and the correctness code above pushed it further. Duplicate abort mechanics were consolidated first — Image and Resumable onto the shared primitive, four decoders onto one snapshot helper — and the remainder is code the review asked for, so the budget is reset to 54,600 against a measured 53,810 with that reasoning recorded, rather than the failure being carried forward as if it were green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
121 lines
4.2 KiB
TypeScript
121 lines
4.2 KiB
TypeScript
import { access, readFile } from "node:fs/promises";
|
|
|
|
/**
|
|
* GOV-03. Joins the machine-readable remediation dispositions against the prose
|
|
* ledger.
|
|
*
|
|
* The previous ledger declared "All 38 are now FIXED" while six of those rows
|
|
* were reproducibly partial. A sentence is cheap and a reviewer reads it as
|
|
* evidence, so this gate makes the claim derivable rather than authored: every
|
|
* disposition must name a test path that exists, the prose must carry the same
|
|
* verdict for the same id, and a blanket closure sentence is only allowed when
|
|
* nothing is still open.
|
|
*/
|
|
|
|
type Disposition = Readonly<{
|
|
id: string;
|
|
previous: string;
|
|
disposition: string;
|
|
summary: string;
|
|
evidence: readonly string[];
|
|
}>;
|
|
|
|
const DISPOSITIONS_PATH = "docs/operations/adapter-remediation-dispositions.json";
|
|
const LEDGER_PATH = "docs/operations/adapter-remediation-ledger.md";
|
|
/**
|
|
* Finding ids are only unique within a review pass — the second pass also used
|
|
* `SW-01` — so rows are matched inside this pass's section rather than anywhere
|
|
* in the document.
|
|
*/
|
|
const SECTION_HEADING = "## Third re-review (2026-08-14)";
|
|
const CLOSED_DISPOSITIONS: ReadonlySet<string> = new Set(["FIXED"]);
|
|
/** Sentences that assert everything is done, and therefore need proof. */
|
|
const BLANKET_CLOSURE = /All\s+(?:\d+|findings|rows)[^.\n]*\b(?:FIXED|closed)\b/giu;
|
|
|
|
async function main(): Promise<void> {
|
|
const problems: string[] = [];
|
|
const raw: unknown = JSON.parse(await readFile(DISPOSITIONS_PATH, "utf8"));
|
|
if (
|
|
raw === null ||
|
|
typeof raw !== "object" ||
|
|
!Array.isArray((raw as { dispositions?: unknown }).dispositions)
|
|
) {
|
|
console.error(`${DISPOSITIONS_PATH}: dispositions array is required`);
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
const dispositions = (raw as { dispositions: Disposition[] }).dispositions;
|
|
const document = await readFile(LEDGER_PATH, "utf8");
|
|
const sectionStart = document.indexOf(SECTION_HEADING);
|
|
if (sectionStart < 0) {
|
|
console.error(`${LEDGER_PATH}: missing section "${SECTION_HEADING}"`);
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
const ledger = document.slice(sectionStart);
|
|
|
|
const seen = new Set<string>();
|
|
for (const row of dispositions) {
|
|
if (typeof row.id !== "string" || row.id.length === 0) {
|
|
problems.push("a disposition row has no id");
|
|
continue;
|
|
}
|
|
if (seen.has(row.id)) problems.push(`duplicate disposition id: ${row.id}`);
|
|
seen.add(row.id);
|
|
if (typeof row.disposition !== "string" || row.disposition.length === 0) {
|
|
problems.push(`${row.id}: disposition is required`);
|
|
}
|
|
if (!Array.isArray(row.evidence) || row.evidence.length === 0) {
|
|
problems.push(`${row.id}: at least one evidence path is required`);
|
|
continue;
|
|
}
|
|
for (const path of row.evidence) {
|
|
try {
|
|
await access(path);
|
|
} catch {
|
|
problems.push(`${row.id}: evidence path does not exist: ${path}`);
|
|
}
|
|
}
|
|
// The prose must carry the same verdict for the same id, so a reader of the
|
|
// document and a reader of the receipt cannot reach different conclusions.
|
|
const line = ledger
|
|
.split("\n")
|
|
.find((candidate) => candidate.includes(`| ${row.id} |`));
|
|
if (!line) {
|
|
problems.push(`${row.id}: no row in ${LEDGER_PATH}`);
|
|
continue;
|
|
}
|
|
if (!line.includes(`\`${row.disposition}\``)) {
|
|
problems.push(
|
|
`${row.id}: ${LEDGER_PATH} does not record \`${row.disposition}\``,
|
|
);
|
|
}
|
|
}
|
|
|
|
const open = dispositions.filter(
|
|
(row) => !CLOSED_DISPOSITIONS.has(row.disposition),
|
|
);
|
|
const blanketClaims = [...document.matchAll(BLANKET_CLOSURE)];
|
|
if (open.length > 0 && blanketClaims.length > 0) {
|
|
problems.push(
|
|
`${LEDGER_PATH} declares a blanket closure (${blanketClaims
|
|
.map((match) => `"${match[0]}"`)
|
|
.join(", ")}) while ${open.length} finding(s) are still open: ${open
|
|
.map((row) => `${row.id}=${row.disposition}`)
|
|
.join(", ")}`,
|
|
);
|
|
}
|
|
|
|
if (problems.length > 0) {
|
|
for (const problem of problems) console.error(problem);
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
console.log(
|
|
`Remediation ledger: ${dispositions.length} dispositions joined to ` +
|
|
`${LEDGER_PATH}; ${open.length} open; evidence paths verified`,
|
|
);
|
|
}
|
|
|
|
await main();
|