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
+494
View File
@@ -6,6 +6,7 @@ import type {
} from "../../src/application/ports/browser-file-storage/cache-storage-ports.ts";
import {
createDefaultPublicCachePolicy,
resolvePublicCachePolicy,
type PublicCacheRuntimePolicy,
} from "../../src/adapters/cache-storage/public-cache-policy.ts";
import {
@@ -140,6 +141,159 @@ async function manifestFor(
}
describe("public response Cache Storage adapter", () => {
it("rejects a policy that enables variants but strips Vary", () => {
const base = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
// STO-03. Enabling variants while dropping Vary from the response
// allowlist makes every stored variant collide on the same key.
expect(() =>
resolvePublicCachePolicy({
...base,
allowedRequestHeaderNames: ["accept", "accept-language"],
allowedVaryHeaderNames: ["accept-language"],
allowedResponseHeaderNames: base.allowedResponseHeaderNames.filter(
(name) => name !== "vary",
),
}),
).toThrow(TypeError);
// The same policy with Vary preserved is accepted.
expect(() =>
resolvePublicCachePolicy({
...base,
allowedRequestHeaderNames: ["accept", "accept-language"],
allowedVaryHeaderNames: ["accept-language"],
}),
).not.toThrow();
});
it("restages an evicted entry even when the release marker remains", async () => {
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const bytes = new Uint8Array([9, 9, 9, 9]);
const asset: PublicCacheAsset = {
absoluteUrl: "https://assets.example.test/evicted.js",
expectedByteLength: bytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(bytes),
},
};
const cacheStorage = new MemoryCacheStorage();
let fetches = 0;
const adapter = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: immediateLock,
policy,
fetcher: async () => {
fetches += 1;
return new Response(bytes, {
headers: {
"cache-control": "public",
"content-type": "application/javascript",
},
});
},
});
const manifest = await manifestFor("evicted-release", [asset], policy);
expect(await adapter.admin.stageRelease(manifest)).toMatchObject({
ok: true,
});
expect(fetches).toBe(1);
// The browser evicts the payload but leaves the marker behind.
const cacheName = [...cacheStorage.caches.keys()].find((name) =>
name.includes("evicted-release"),
);
if (!cacheName) throw new Error("staged cache missing");
const cache = cacheStorage.caches.get(cacheName)!;
const payloadIndex = cache.responses.findIndex(
(entry) => entry.request.url === asset.absoluteUrl,
);
expect(payloadIndex).toBeGreaterThanOrEqual(0);
cache.responses.splice(payloadIndex, 1);
// A marker is a claim, not evidence: restaging must repair.
expect(await adapter.admin.stageRelease(manifest)).toMatchObject({
ok: true,
});
expect(fetches).toBe(2);
expect(
await adapter.admin.activateRelease(
manifest.releaseRegistryId,
manifest.manifestDigestHex,
),
).toMatchObject({ ok: true });
});
it("activates a verified prestaged release without a fetcher", async () => {
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const bytes = new Uint8Array([4, 4, 4, 4]);
const asset: PublicCacheAsset = {
absoluteUrl: "https://assets.example.test/offline.js",
expectedByteLength: bytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(bytes),
},
};
const cacheStorage = new MemoryCacheStorage();
const online = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: immediateLock,
policy,
fetcher: async () =>
new Response(bytes, {
headers: {
"cache-control": "public",
"content-type": "application/javascript",
},
}),
});
const manifest = await manifestFor("offline-release", [asset], policy);
expect(await online.admin.stageRelease(manifest)).toMatchObject({
ok: true,
});
// STO-05. Activation and cleanup perform no network I/O, so a missing
// fetcher must not make an offline rollback UNSUPPORTED.
const offline = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: immediateLock,
policy,
});
expect(
await offline.admin.activateRelease(
manifest.releaseRegistryId,
manifest.manifestDigestHex,
),
).toMatchObject({ ok: true });
});
it("cleans exact owned caches without a fetcher", async () => {
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const cacheStorage = new MemoryCacheStorage();
const offline = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: immediateLock,
policy,
});
expect(await offline.admin.cleanupOwned()).toMatchObject({
ok: true,
});
});
it("stages, re-verifies and atomically activates an exact release", async () => {
const cacheStorage = new MemoryCacheStorage();
const bytes = new Uint8Array([1, 2, 3, 4]);
@@ -374,6 +528,150 @@ describe("public response Cache Storage adapter", () => {
await expect(cacheStorage.keys()).resolves.toEqual([]);
});
/**
* NS-08. Handing the signal to each `Request` only asked a cooperative fetch
* to stop. A stream that ignored it held the mutation lock forever, and work
* that finished after the abort still wrote its asset and its marker.
*/
it("does not wait for a non-cooperative fetch after the caller aborts", async () => {
const cacheStorage = new MemoryCacheStorage();
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const bytes = new Uint8Array([1, 2, 3, 4]);
const asset: PublicCacheAsset = {
absoluteUrl: "https://assets.example.test/never-settles.js",
expectedByteLength: bytes.byteLength,
expectedContentType: "application/javascript",
integrity: { algorithm: "SHA-256", digestHex: await digestHex(bytes) },
};
const manifest = await manifestFor("never-settles", [asset], policy);
const fetchStarted = deferred<void>();
let locksHeld = 0;
const adapter = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: {
async run(signal, operation) {
locksHeld += 1;
try {
return await operation();
} finally {
locksHeld -= 1;
}
},
},
policy,
fetcher: () => {
fetchStarted.resolve(undefined);
// Ignores the signal entirely.
return new Promise<Response>(() => {});
},
});
const controller = new AbortController();
const staging = adapter.admin.stageRelease(manifest, {
signal: controller.signal,
});
await fetchStarted.promise;
controller.abort();
await expect(staging).resolves.toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
expect(locksHeld).toBe(0);
await expect(cacheStorage.keys()).resolves.toEqual([]);
});
it("writes neither asset nor marker when a digest completes after the abort", async () => {
const cacheStorage = new MemoryCacheStorage();
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const bytes = new Uint8Array([9, 9, 9, 9]);
const asset: PublicCacheAsset = {
absoluteUrl: "https://assets.example.test/late-digest.js",
expectedByteLength: bytes.byteLength,
expectedContentType: "application/javascript",
integrity: { algorithm: "SHA-256", digestHex: await digestHex(bytes) },
};
const manifest = await manifestFor("late-digest", [asset], policy);
const digestStarted = deferred<void>();
const releaseDigest = deferred<void>();
let delayFirstDigest = true;
const realCrypto = globalThis.crypto;
const delayedCrypto = {
subtle: {
async digest(
algorithm: AlgorithmIdentifier,
data: BufferSource,
): Promise<ArrayBuffer> {
if (delayFirstDigest) {
delayFirstDigest = false;
digestStarted.resolve(undefined);
await releaseDigest.promise;
}
return await realCrypto.subtle.digest(algorithm, data);
},
},
} as unknown as Crypto;
let puts = 0;
const adapter = createPublicResponseCacheAdapter({
cacheStorage: new Proxy(cacheStorage, {
get(target, key, receiver) {
if (key === "open") {
return async (name: string) => {
const cache = await target.open(name);
return new Proxy(cache, {
get(cacheTarget, cacheKey, cacheReceiver) {
if (cacheKey === "put") {
return async (...args: readonly unknown[]) => {
puts += 1;
return await (
cacheTarget.put as (
...values: readonly unknown[]
) => Promise<void>
)(...args);
};
}
return Reflect.get(cacheTarget, cacheKey, cacheReceiver);
},
});
};
}
return Reflect.get(target, key, receiver);
},
}) as unknown as CacheStorage,
crypto: delayedCrypto,
mutationLock: immediateLock,
policy,
fetcher: async () =>
new Response(bytes, {
headers: {
"cache-control": "public, max-age=60",
"content-type": "application/javascript",
},
}),
});
const controller = new AbortController();
const staging = adapter.admin.stageRelease(manifest, {
signal: controller.signal,
});
await digestStarted.promise;
controller.abort();
releaseDigest.resolve(undefined);
await expect(staging).resolves.toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
// Neither the asset nor the activation marker may be written by work the
// abort already disowned.
expect(puts).toBe(0);
});
it("keeps the original activation signal while waiting for the mutation lock", async () => {
const cacheStorage = new MemoryCacheStorage();
const policy = createDefaultPublicCachePolicy(
@@ -1215,3 +1513,199 @@ describe("public response Cache Storage adapter", () => {
expect(await cacheStorage.keys()).toEqual([]);
});
});
/**
* STO-RR-04 / STO-RR-05. A release cache that is currently serving traffic is
* the last thing a repair may destroy. A transient marker read failure is not
* evidence of damage, and a repair that has not yet fetched anything has not
* yet earned the right to delete what still works.
*/
describe("public response cache repair is failure-atomic", () => {
async function stagedRelease(releaseRegistryId: string) {
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const firstBytes = new Uint8Array([1, 1, 1, 1]);
const secondBytes = new Uint8Array([2, 2, 2, 2]);
const assets: readonly PublicCacheAsset[] = [
{
absoluteUrl: "https://assets.example.test/first.js",
expectedByteLength: firstBytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(firstBytes),
},
},
{
absoluteUrl: "https://assets.example.test/second.js",
expectedByteLength: secondBytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(secondBytes),
},
},
];
const bodies = new Map<string, Uint8Array>([
[assets[0]!.absoluteUrl, firstBytes],
[assets[1]!.absoluteUrl, secondBytes],
]);
const cacheStorage = new MemoryCacheStorage();
const fetchLog: string[] = [];
let failFrom: string | null = null;
const adapter = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: immediateLock,
policy,
fetcher: async (request: Request) => {
fetchLog.push(request.url);
if (failFrom !== null && request.url === failFrom) {
throw new TypeError("network is down");
}
const body = bodies.get(request.url);
if (!body) throw new TypeError(`unknown asset ${request.url}`);
return new Response(Uint8Array.from(body), {
headers: {
"cache-control": "public",
"content-type": "application/javascript",
},
});
},
});
const manifest = await manifestFor(releaseRegistryId, assets, policy);
expect(await adapter.admin.stageRelease(manifest)).toMatchObject({
ok: true,
});
expect(
await adapter.admin.activateRelease(
manifest.releaseRegistryId,
manifest.manifestDigestHex,
),
).toMatchObject({ ok: true });
const cacheName = [...cacheStorage.caches.keys()].find((name) =>
name.includes(releaseRegistryId),
);
if (!cacheName) throw new Error("staged cache missing");
return {
adapter,
assets,
cacheName,
cacheStorage,
fetchLog,
manifest,
setFailure(url: string | null) {
failFrom = url;
},
};
}
it("does not delete an active candidate when the marker read fails transiently", async () => {
const release = await stagedRelease("transient-marker");
const cache = release.cacheStorage.caches.get(release.cacheName)!;
const realMatch = cache.match.bind(cache);
let markerReads = 0;
const assetUrls = new Set(release.assets.map((asset) => asset.absoluteUrl));
cache.match = async (request: RequestInfo | URL) => {
const url =
request instanceof Request ? request.url : String(request);
if (!assetUrls.has(url)) {
markerReads += 1;
throw new DOMException("Storage is busy", "InvalidStateError");
}
return await realMatch(request);
};
const restaged = await release.adapter.admin.stageRelease(release.manifest);
expect(markerReads).toBeGreaterThan(0);
expect(restaged.ok).toBe(false);
expect(release.cacheStorage.caches.has(release.cacheName)).toBe(true);
cache.match = realMatch;
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[0]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
});
it("keeps every healthy asset when one repair fetch fails", async () => {
const release = await stagedRelease("partial-repair");
const cache = release.cacheStorage.caches.get(release.cacheName)!;
// Corrupt only the first asset's stored bytes.
const corrupted = cache.responses.findIndex(
(entry) => entry.request.url === release.assets[0]!.absoluteUrl,
);
expect(corrupted).toBeGreaterThanOrEqual(0);
cache.responses.splice(corrupted, 1);
release.setFailure(release.assets[0]!.absoluteUrl);
const restaged = await release.adapter.admin.stageRelease(release.manifest);
expect(restaged.ok).toBe(false);
// The cache still exists and the healthy asset is still served.
expect(release.cacheStorage.caches.has(release.cacheName)).toBe(true);
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[1]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
});
it("still removes a candidate this call created when staging fails", async () => {
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const bytes = new Uint8Array([7, 7, 7, 7]);
const asset: PublicCacheAsset = {
absoluteUrl: "https://assets.example.test/fresh.js",
expectedByteLength: bytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(bytes),
},
};
const cacheStorage = new MemoryCacheStorage();
const adapter = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: immediateLock,
policy,
fetcher: async () => {
throw new TypeError("network is down");
},
});
const manifest = await manifestFor("fresh-release", [asset], policy);
expect(await adapter.admin.stageRelease(manifest)).toMatchObject({
ok: false,
});
expect(await cacheStorage.keys()).toEqual([]);
});
it("repairs an evicted asset in place and keeps the release usable", async () => {
const release = await stagedRelease("in-place-repair");
const cache = release.cacheStorage.caches.get(release.cacheName)!;
const evicted = cache.responses.findIndex(
(entry) => entry.request.url === release.assets[1]!.absoluteUrl,
);
cache.responses.splice(evicted, 1);
expect(
await release.adapter.admin.stageRelease(release.manifest),
).toMatchObject({ ok: true });
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[1]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[0]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
});
});