GOV-01. The adapter inventory claimed 118/118 while the tree held 119 files, so src/adapters/platform/abortable-operation.ts sat outside every review's coverage without anything failing. The row is restored and scripts/check-adapter-inventory.ts now diffs the document against git ls-files src/adapters, so the count is an equality rather than a number someone has to remember. The same gate pins that the Service Worker asset generator reads the shared extension table instead of declaring its own. GOV-02. The previous ledger closed rows as FIXED_NOT_RELEASED that the re-review found partial. The new section is written the other way round: a row reads FIXED only where a named adversarial test failed on the pre-fix source and passes on the landed one, and the twelve findings this pass did not reach — RPC-RR-01, RT-RR-01 through RT-RR-04 and TR-RR-01 through TR-RR-07 — are recorded as NOT_STARTED with the reason each needs a lifecycle change rather than a contained edit. None of them may be treated as closed and no capability they cover may be promoted without its own evidence row. The structural gate for the shared abortable-operation primitive is deliberately not added yet: it still has zero production importers, and a gate that fails CI for a documented, unfixed defect would report the wrong thing. Also records the destructive test hazard found while running the suites: scripts/lib/removal-fixture.ts and scripts/check-supply-chain-provider-fixtures.ts symlink the real node_modules into a temp fixture root and run pnpm there, which purges the repository's own dependencies through the symlink mid-run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
import { readFile } from "node:fs/promises";
|
|
import { spawnSync } from "node:child_process";
|
|
|
|
import { CACHEABLE_ASSET_CONTENT_TYPES } from "../src/contracts/service-worker-static-manifest.ts";
|
|
|
|
/**
|
|
* GOV-01 / SW-RR-03. Structural gates for facts that a hand-maintained document
|
|
* cannot keep true.
|
|
*
|
|
* The adapter review inventory claimed 118/118 while the tree held 119 files,
|
|
* so a whole adapter was outside every review's coverage without anything
|
|
* failing. And the Service Worker asset generator and the shared manifest
|
|
* decoder each carried their own extension table, so a build could emit an
|
|
* asset the runtime contract then refused. Both are now equalities this script
|
|
* checks rather than numbers someone has to remember to update.
|
|
*/
|
|
|
|
const INVENTORY_PATH = "docs/reviews/adapters/INVENTORY.md";
|
|
const GENERATOR_PATH = "scripts/generate-service-worker-assets.ts";
|
|
|
|
function trackedAdapterFiles(): readonly string[] {
|
|
const listed = spawnSync("git", ["ls-files", "src/adapters"], {
|
|
encoding: "utf8",
|
|
});
|
|
if (listed.status !== 0) {
|
|
throw new Error(`git ls-files failed: ${listed.stderr}`);
|
|
}
|
|
return listed.stdout.split("\n").filter(Boolean).sort();
|
|
}
|
|
|
|
function inventoryRows(markdown: string): readonly string[] {
|
|
const rows: string[] = [];
|
|
for (const line of markdown.split("\n")) {
|
|
const match = /^\|\s*\d+\s*\|\s*`([^`]+)`\s*\|/u.exec(line);
|
|
if (match?.[1]) rows.push(match[1]);
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
function reportDifference(
|
|
label: string,
|
|
expected: readonly string[],
|
|
actual: readonly string[],
|
|
): readonly string[] {
|
|
const missing = expected.filter((value) => !actual.includes(value));
|
|
const extra = actual.filter((value) => !expected.includes(value));
|
|
const problems: string[] = [];
|
|
for (const value of missing) problems.push(`${label}: missing ${value}`);
|
|
for (const value of extra) problems.push(`${label}: unexpected ${value}`);
|
|
return problems;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const problems: string[] = [];
|
|
|
|
const tracked = trackedAdapterFiles();
|
|
const markdown = await readFile(INVENTORY_PATH, "utf8");
|
|
const listed = inventoryRows(markdown);
|
|
problems.push(...reportDifference("adapter inventory", tracked, listed));
|
|
if (listed.length !== new Set(listed).size) {
|
|
problems.push("adapter inventory: duplicate row");
|
|
}
|
|
const total = /합계: \*\*(\d+)\/(\d+)\*\*/u.exec(markdown);
|
|
if (
|
|
!total ||
|
|
Number(total[1]) !== tracked.length ||
|
|
Number(total[2]) !== tracked.length
|
|
) {
|
|
problems.push(
|
|
`adapter inventory: total does not equal ${tracked.length} tracked files`,
|
|
);
|
|
}
|
|
|
|
// SW-RR-03. The generator must read the shared table rather than declare one.
|
|
const generator = await readFile(GENERATOR_PATH, "utf8");
|
|
if (!generator.includes("CACHEABLE_ASSET_CONTENT_TYPES")) {
|
|
problems.push(
|
|
"service worker assets: generator does not use the shared extension table",
|
|
);
|
|
}
|
|
if (/const CACHEABLE_EXTENSIONS[^=]*=\s*Object\.freeze\(\{/u.test(generator)) {
|
|
problems.push(
|
|
"service worker assets: generator declares its own extension table",
|
|
);
|
|
}
|
|
for (const [extension, contentType] of Object.entries(
|
|
CACHEABLE_ASSET_CONTENT_TYPES,
|
|
)) {
|
|
if (!extension.startsWith(".") || contentType.length === 0) {
|
|
problems.push(`service worker assets: invalid table row ${extension}`);
|
|
}
|
|
}
|
|
|
|
if (problems.length > 0) {
|
|
for (const problem of problems) console.error(problem);
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
console.log(
|
|
`Adapter inventory: ${tracked.length} files PASS; ` +
|
|
`service worker asset table: ${
|
|
Object.keys(CACHEABLE_ASSET_CONTENT_TYPES).length
|
|
} shared extensions PASS`,
|
|
);
|
|
}
|
|
|
|
await main();
|