The product was materialized from the template at `4dc033c` and has stayed on it through 43 template commits, so it was missing all three rounds of adapter remediation — including files it never had, such as the shared `abortable-operation` primitive and the `exact-snapshot` decoder that later fixes are written against. Taking only the newest round was not possible for that reason: the delta is coherent only as a whole. The product had not touched `src/adapters` at all since materialization, so the 140-file delta applied with a three-way merge and no conflicts. `package.json` was the single overlap and merged cleanly: the product owns `name`, the template contributed `check:adapter-inventory`, `check:remediation-ledger` and the image-resolve-signal type fixture. All 24 product-owned files — README, index.html, CI workflow, i18n catalog, home page, generated schemas, evidence scripts, component and visual snapshots — are byte-identical to `main`. `template.lock.json` now pins the synced revision and tree. Verified in this repository, not inherited from the template: six type projects, lint, nine gates (adapter inventory, remediation ledger, registries, diagnostics, realtime boundaries, architecture, browser file/storage boundaries, optional recipes, documentation), the production build, and 2,054 of 2,073 tests. The 19 failures are all in `tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template records; four suites that failed once under parallel load pass in isolation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
166 lines
6.3 KiB
TypeScript
166 lines
6.3 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[];
|
|
/**
|
|
* 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";
|
|
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"]);
|
|
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;
|
|
|
|
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;
|
|
}
|
|
const markers = row.markers ?? [row.id];
|
|
for (const path of row.evidence) {
|
|
let contents: string;
|
|
try {
|
|
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
|
|
// 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;
|
|
}
|
|
// 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} records ${
|
|
recorded ?? "nothing"
|
|
} where the receipt says \`${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(", ")}`,
|
|
);
|
|
}
|
|
|
|
// 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;
|
|
return;
|
|
}
|
|
console.log(
|
|
`Remediation ledger: ${dispositions.length} dispositions joined to ` +
|
|
`${LEDGER_PATH}; ${open.length} open; evidence paths verified`,
|
|
);
|
|
}
|
|
|
|
await main();
|