chore: derive the remediation closure claim instead of authoring it

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>
This commit is contained in:
DongHyeonka
2026-08-15 01:26:27 +09:00
co-authored by Claude Opus 5
parent d7b35cfca3
commit d5e7f4127a
6 changed files with 444 additions and 13 deletions
+49 -8
View File
@@ -1,4 +1,6 @@
import { readFile } from "node:fs/promises";
import { readFileSync } from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { CACHEABLE_ASSET_CONTENT_TYPES } from "../src/contracts/service-worker-static-manifest.ts";
@@ -123,10 +125,17 @@ async function main(): Promise<void> {
);
}
// TR-RR-05. The shared abort/deadline primitive is only shared if production
// code imports it. A helper with zero importers is a second implementation
// waiting to happen, which is exactly how the four hand-written copies of
// these mechanics diverged in the first place.
// TR-RR-05 / GOV-04. Every consumer the re-review named must use the shared
// primitive, not merely one file somewhere. Checking `importers.length > 0`
// let an unrelated production import satisfy the gate while Image and
// Resumable kept their own diverging copies of the same mechanics — which is
// exactly how the four hand-written versions drifted apart in the first place.
const REQUIRED_ABORT_CONSUMERS: readonly string[] = [
"src/adapters/browser-transfer/presigned/presigned-capability-http-provider.ts",
"src/adapters/browser-transfer/presigned/presigned-transfer-executor.ts",
"src/adapters/browser-transfer/image-cdn/browser-image-probe.ts",
"src/adapters/browser-transfer/resumable-upload/fetch-json-transport.ts",
];
const primitiveImporters = spawnSync(
"git",
["grep", "-l", "platform/abortable-operation.ts", "--", "src"],
@@ -137,24 +146,56 @@ async function main(): Promise<void> {
)
.split("\n")
.filter(Boolean)
.filter((file) => !file.endsWith("platform/abortable-operation.ts"));
if (importers.length === 0) {
.filter((file) => !file.endsWith("platform/abortable-operation.ts"))
.sort();
const importerSet = new Set(importers);
const missingConsumers = REQUIRED_ABORT_CONSUMERS.filter(
(consumer) => !importerSet.has(consumer),
);
if (missingConsumers.length > 0) {
problems.push(
"abortable-operation: the shared primitive has no production importers",
`abortable-operation: required consumers do not import the shared primitive: ${missingConsumers.join(
", ",
)}`,
);
}
// The importer must reach the primitive by a specifier that resolves to the
// primitive itself, so a same-named local helper cannot satisfy the gate.
const PRIMITIVE_PATH = path.resolve(
"src/adapters/platform/abortable-operation.ts",
);
for (const consumer of REQUIRED_ABORT_CONSUMERS) {
if (!importerSet.has(consumer)) continue;
const source = readFileSync(consumer, "utf8");
const specifiers = [
...source.matchAll(/from\s+"([^"]*platform\/abortable-operation\.ts)"/gu),
].map((match) => match[1] ?? "");
const resolved = specifiers.some(
(specifier) =>
path.resolve(path.dirname(consumer), specifier) === PRIMITIVE_PATH,
);
if (!resolved) {
problems.push(
`abortable-operation: ${consumer} does not resolve its import to the shared primitive`,
);
}
}
if (problems.length > 0) {
for (const problem of problems) console.error(problem);
process.exitCode = 1;
return;
}
// GOV-04. The exact importer set is part of the receipt, so a reviewer can
// see which consumers the gate actually verified rather than a bare count.
console.log(
`Adapter inventory: ${tracked.length} files PASS; ` +
`service worker asset table: ${
Object.keys(CACHEABLE_ASSET_CONTENT_TYPES).length
} shared extensions PASS; ` +
`fixture node_modules linking PASS`,
`fixture node_modules linking PASS; ` +
`shared abort primitive: ${importers.length} importers ` +
`(${importers.join(", ")}) PASS`,
);
}
+120
View File
@@ -0,0 +1,120 @@
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();