chore: sync the frontend template from 4dc033c to 8157ad4

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>
This commit is contained in:
DongHyeonka
2026-08-15 12:04:58 +09:00
co-authored by Claude Opus 5
parent 002ba3624e
commit 4bff9ca151
142 changed files with 23010 additions and 1544 deletions
+202
View File
@@ -0,0 +1,202 @@
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";
/**
* 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}`);
}
}
// A fixture that links the repository's node_modules with a single directory
// symlink is destructive: pnpm running inside that fixture purges the modules
// directory it does not recognise, follows the link, and deletes the real
// dependencies mid-run. `linkFixtureNodeModules` is the only sanctioned form.
const sources = spawnSync(
"git",
["grep", "-n", "-e", 'symlink(', "--", "scripts", "tests"],
{ encoding: "utf8" },
);
if (sources.status === 0) {
for (const line of sources.stdout.split("\n").filter(Boolean)) {
if (!line.includes("node_modules")) continue;
if (line.startsWith("scripts/lib/fixture-node-modules.ts:")) continue;
problems.push(
`fixture node_modules: use linkFixtureNodeModules instead — ${line}`,
);
}
}
const linkedFixtures = spawnSync(
"git",
["grep", "-l", "linkFixtureNodeModules", "--", "scripts", "tests"],
{ encoding: "utf8" },
);
if (
linkedFixtures.status !== 0 ||
linkedFixtures.stdout.split("\n").filter(Boolean).length < 2
) {
problems.push(
"fixture node_modules: the shared linker has no callers, so it is not the sanctioned path",
);
}
// 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"],
{ encoding: "utf8" },
);
const importers = (
primitiveImporters.status === 0 ? primitiveImporters.stdout : ""
)
.split("\n")
.filter(Boolean)
.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: 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; ` +
`shared abort primitive: ${importers.length} importers ` +
`(${importers.join(", ")}) PASS`,
);
}
await main();
+165
View File
@@ -0,0 +1,165 @@
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();
@@ -12,7 +12,6 @@ import {
readFile,
readdir,
rm,
symlink,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
@@ -42,6 +41,7 @@ import {
releaseCandidateManifestSchema,
} from "./lib/release-candidate.ts";
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
import { linkFixtureNodeModules } from "./lib/fixture-node-modules.ts";
const repositoryRoot = process.cwd();
const fixtureRoot = await mkdtemp(path.join(tmpdir(), "provider-exact-five-fixture-"));
@@ -59,7 +59,7 @@ try {
recursive: true,
});
await rm(path.join(fixtureRoot, "artifacts/release"), { recursive: true, force: true });
await symlink(path.join(repositoryRoot, "node_modules"), path.join(fixtureRoot, "node_modules"), "dir");
await linkFixtureNodeModules(fixtureRoot, repositoryRoot);
const git = spawnSync("git", ["show", "-s", "--format=%H%n%ct", "HEAD"], {
cwd: repositoryRoot,
encoding: "utf8",
+37 -28
View File
@@ -1,4 +1,11 @@
import { createHash } from "node:crypto";
import {
CACHEABLE_ASSET_CONTENT_TYPES,
canonicalStaticManifestBytes,
decodeStaticAssetManifest,
isCanonicalStaticAssetUrl,
} from "../src/contracts/service-worker-static-manifest.ts";
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
import path from "node:path";
@@ -18,15 +25,9 @@ import {
const OUTPUT = ".generated/frontend-runtime/service-worker-assets.ts";
const CACHEABLE_EXTENSIONS: Readonly<Record<string, string>> = Object.freeze({
".js": "text/javascript",
".mjs": "text/javascript",
".css": "text/css",
".woff2": "font/woff2",
".svg": "image/svg+xml",
".png": "image/png",
".webp": "image/webp",
});
// SW-RR-03. The generator and the shared decoder read the same table, so a
// manifest this script produces can never be one the runtime contract refuses.
const CACHEABLE_EXTENSIONS = CACHEABLE_ASSET_CONTENT_TYPES;
const EXCLUDED_FILES: ReadonlySet<string> = new Set([
"index.html",
@@ -58,8 +59,17 @@ export async function collectStaticAssets(
if (bytes.byteLength > SERVICE_WORKER_BOUNDS.singleAssetBytes) {
throw new Error(`Static asset exceeds its byte bound: ${relative}`);
}
// SW-02. The URL is checked against the same predicate the runtime decoder
// applies. Emitting a path the decoder will refuse turned a correct build
// into a runtime contract failure discovered only at install time.
const url = `/${relative.split(path.sep).join("/")}`;
if (!isCanonicalStaticAssetUrl(url)) {
throw new Error(
`Static asset path is not canonical for the service worker manifest: ${relative}`,
);
}
assets.push({
url: `/${relative.split(path.sep).join("/")}`,
url,
sha256: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
bytes: bytes.byteLength,
contentType,
@@ -74,32 +84,31 @@ export async function collectStaticAssets(
throw new Error("Static asset set exceeds its byte bound.");
}
// The set digest is a length-prefixed hash over the sorted asset identities,
// so a reordered directory listing cannot change it.
const hash = createHash("sha256");
hash.update("CA_STATIC_ASSET_SET_V1\0");
for (const asset of assets) {
hash.update(lengthPrefixed(asset.url));
hash.update(lengthPrefixed(asset.sha256));
hash.update(lengthPrefixed(String(asset.bytes)));
hash.update(lengthPrefixed(asset.contentType));
}
// SW-05. The canonical byte serialization lives in the shared runtime-neutral
// codec so the worker can recompute the identical digest with WebCrypto.
const setDigest: `sha256:${string}` = `sha256:${createHash("sha256")
.update(canonicalStaticManifestBytes(assets))
.digest("hex")}`;
return {
const manifest: StaticAssetManifestV1 = {
schemaVersion: 1,
buildId,
releaseId,
setDigest: `sha256:${hash.digest("hex")}`,
setDigest,
assets,
};
// SW-02. Every manifest this generator returns has already passed the exact
// decoder the runtime will apply to it, so the build stops here rather than
// at install time.
const decoded = decodeStaticAssetManifest(manifest);
if (!decoded.ok) {
throw new Error(
`Generated service worker manifest is not decodable: ${decoded.error.reason}`,
);
}
return manifest;
}
function lengthPrefixed(value: string): Buffer {
const bytes = Buffer.from(value, "utf8");
const prefix = Buffer.alloc(4);
prefix.writeUInt32BE(bytes.byteLength, 0);
return Buffer.concat([prefix, bytes]);
}
async function walk(root: string, current: string): Promise<string[]> {
const entries = await readdir(current, { withFileTypes: true });
+31
View File
@@ -0,0 +1,31 @@
import { lstat, mkdir, readdir, symlink } from "node:fs/promises";
import path from "node:path";
/**
* Links the repository's installed dependencies into a throwaway fixture root.
*
* The obvious form — one directory symlink at `<fixture>/node_modules` — is
* destructive. A fixture runs `pnpm` inside itself, pnpm does not recognise the
* modules directory it finds there, and its purge follows the symlink and
* deletes the *repository's* real dependencies mid-run. With `CI=true` that
* happens without a prompt, so a test suite silently uninstalls the workspace
* it is running in.
*
* `node_modules` is therefore a real directory here, and every entry inside it
* is an individual symlink. Package resolution is unchanged, but a recursive
* delete unlinks the fixture's own symlinks instead of walking through one link
* into the shared tree.
*/
export async function linkFixtureNodeModules(
fixtureRoot: string,
sourceRoot: string = process.cwd(),
): Promise<void> {
const source = path.join(sourceRoot, "node_modules");
const target = path.join(fixtureRoot, "node_modules");
await mkdir(target, { recursive: true });
for (const entry of await readdir(source)) {
const from = path.join(source, entry);
const stats = await lstat(from);
await symlink(from, path.join(target, entry), stats.isDirectory() ? "dir" : "file");
}
}
+2 -2
View File
@@ -5,7 +5,6 @@ import {
readFile,
readdir,
rm,
symlink,
writeFile,
} from "node:fs/promises";
import path from "node:path";
@@ -14,6 +13,7 @@ import {
loadCiGateContract,
parseCiGateContract,
} from "../contracts/ci-gates.ts";
import { linkFixtureNodeModules } from "./fixture-node-modules.ts";
import { generateCiWorkflow } from "../generate-ci-workflow.ts";
export const REMOVAL_FIXTURE_COPY_TARGETS = Object.freeze([
@@ -42,7 +42,7 @@ export async function prepareRemovalFixture(
for (const target of copyTargets) {
await cp(target, path.join(root, target), { recursive: true });
}
await symlink(path.resolve("node_modules"), path.join(root, "node_modules"), "dir");
await linkFixtureNodeModules(root);
}
export function runRemovalFixturePnpm(
+27 -11
View File
@@ -1,3 +1,10 @@
import { createHash } from "node:crypto";
import {
canonicalStaticManifestBytes,
decodeStaticAssetManifest,
} from "../../src/contracts/service-worker-static-manifest.ts";
import type {
InstalledServiceWorkerSelection,
ServiceWorkerHandlerId,
@@ -79,19 +86,28 @@ export function resolveServiceWorkerBuildInput(input: Readonly<{
});
}
/**
* SW-05. The build gate no longer type-casts the manifest. It decodes every row
* through the shared runtime-neutral codec and recomputes the set digest from
* the same canonical bytes the generator hashed, so a tampered row, a reordered
* set or a stale digest fails admission instead of shipping.
*/
function parseAssets(value: unknown): StaticAssetManifestV1 {
const candidate = record(value);
if (
candidate?.schemaVersion !== 1 ||
typeof candidate.buildId !== "string" ||
typeof candidate.releaseId !== "string" ||
typeof candidate.setDigest !== "string" ||
!DIGEST.test(candidate.setDigest) ||
!Array.isArray(candidate.assets)
) {
throw new TypeError("Generated Service Worker asset manifest is invalid.");
const decoded = decodeStaticAssetManifest(value);
if (!decoded.ok) {
throw new TypeError(
`Generated Service Worker asset manifest is invalid: ${decoded.error.reason}`,
);
}
return candidate as unknown as StaticAssetManifestV1;
const expected = `sha256:${createHash("sha256")
.update(canonicalStaticManifestBytes(decoded.manifest.assets))
.digest("hex")}`;
if (expected !== decoded.manifest.setDigest) {
throw new TypeError(
"Generated Service Worker asset manifest set digest does not match its assets.",
);
}
return decoded.manifest as unknown as StaticAssetManifestV1;
}
function record(value: unknown): Record<string, unknown> | null {