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:
co-authored by
Claude Opus 5
parent
002ba3624e
commit
4bff9ca151
@@ -2,6 +2,7 @@ import {
|
||||
SERVICE_WORKER_BOUNDS,
|
||||
type InstalledServiceWorkerSelection,
|
||||
type ServiceWorkerActivationOutcome,
|
||||
type ServiceWorkerRemovalOutcome,
|
||||
type ServiceWorkerResetOutcome,
|
||||
type ServiceWorkerRuntimeHost,
|
||||
type ServiceWorkerStartOutcome,
|
||||
@@ -57,6 +58,9 @@ export function createServiceWorkerPageController(
|
||||
let registration: ServiceWorkerRegistration | null = null;
|
||||
let messageListener: ((event: MessageEvent) => void) | null = null;
|
||||
let updateTimer: ReturnType<typeof setInterval> | null = null;
|
||||
/** SW-06. Single-flight command state. */
|
||||
let activationInFlight: Promise<ServiceWorkerActivationOutcome> | null = null;
|
||||
let resetInFlight: Promise<ServiceWorkerResetOutcome> | null = null;
|
||||
let stopped = false;
|
||||
const pendingStops = new Set<() => void>();
|
||||
|
||||
@@ -75,6 +79,29 @@ export function createServiceWorkerPageController(
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* SW-04. Staged removal reports what actually happened.
|
||||
*
|
||||
* Returning DISABLED for every outcome let a later release delete the worker
|
||||
* source and handlers while a registration or an owned cache was still
|
||||
* present, or while the registration belonged to someone else.
|
||||
*/
|
||||
function removalStartOutcome(
|
||||
outcome: ServiceWorkerRemovalOutcome,
|
||||
failureReason: string,
|
||||
): ServiceWorkerStartOutcome {
|
||||
switch (outcome.kind) {
|
||||
case "ABSENT":
|
||||
case "UNREGISTERED":
|
||||
case "PURGED":
|
||||
return Object.freeze({ kind: "DISABLED" as const });
|
||||
case "OWNERSHIP_MISMATCH":
|
||||
return Object.freeze({ kind: "INCOMPATIBLE" as const });
|
||||
case "FAILED":
|
||||
return failed(failureReason);
|
||||
}
|
||||
}
|
||||
|
||||
async function start(): Promise<ServiceWorkerStartOutcome> {
|
||||
if (stopped) return failed("STOPPED");
|
||||
const container = dependencies.container;
|
||||
@@ -90,8 +117,7 @@ export function createServiceWorkerPageController(
|
||||
origin: dependencies.origin,
|
||||
});
|
||||
observe("disable_cleanup", outcome.kind);
|
||||
if (outcome.kind === "FAILED") return failed("DISABLE_CLEANUP_FAILED");
|
||||
return Object.freeze({ kind: "DISABLED" as const });
|
||||
return removalStartOutcome(outcome, "DISABLE_CLEANUP_FAILED");
|
||||
}
|
||||
|
||||
const selection = dependencies.selection;
|
||||
@@ -108,7 +134,7 @@ export function createServiceWorkerPageController(
|
||||
origin: dependencies.origin,
|
||||
});
|
||||
observe("remove_registration", outcome.kind);
|
||||
return Object.freeze({ kind: "DISABLED" as const });
|
||||
return removalStartOutcome(outcome, "REMOVE_FAILED");
|
||||
}
|
||||
if (selection.mode === "PURGE_OWNED_RESOURCES") {
|
||||
const outcome = await purgeOwnedResources({
|
||||
@@ -118,7 +144,7 @@ export function createServiceWorkerPageController(
|
||||
origin: dependencies.origin,
|
||||
});
|
||||
observe("purge_owned_resources", outcome.kind);
|
||||
return Object.freeze({ kind: "DISABLED" as const });
|
||||
return removalStartOutcome(outcome, "PURGE_FAILED");
|
||||
}
|
||||
|
||||
// §17.5. StrictMode's repeated effect returns the same in-flight promise
|
||||
@@ -194,6 +220,13 @@ export function createServiceWorkerPageController(
|
||||
observe("client_drain", "MALFORMED");
|
||||
return;
|
||||
}
|
||||
// SW-06. An arbitrary same-origin source must not be able to close this
|
||||
// page's admission. The request has to come from the worker we are
|
||||
// actually waiting on or the one currently controlling us.
|
||||
if (!isExpectedWorkerSource(source)) {
|
||||
observe("client_drain", "SOURCE_MISMATCH");
|
||||
return;
|
||||
}
|
||||
const rejected = isBlocked();
|
||||
source.postMessage(
|
||||
createServiceWorkerMessage({
|
||||
@@ -211,6 +244,23 @@ export function createServiceWorkerPageController(
|
||||
container.addEventListener("message", messageListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* SW-06. Source identity is checked by object identity against the
|
||||
* registration's waiting/installing/active worker and the container's
|
||||
* controller. An empty `event.origin` is never used as a trust signal.
|
||||
*/
|
||||
function isExpectedWorkerSource(source: unknown): boolean {
|
||||
const expected = [
|
||||
registration?.waiting,
|
||||
registration?.installing,
|
||||
registration?.active,
|
||||
dependencies.container?.controller,
|
||||
];
|
||||
return expected.some(
|
||||
(candidate) => candidate != null && candidate === source,
|
||||
);
|
||||
}
|
||||
|
||||
function scheduleUpdateChecks(): void {
|
||||
// §17.14. At most one check per 6 hours, and none while the page is hidden.
|
||||
if (updateTimer) return;
|
||||
@@ -228,7 +278,16 @@ export function createServiceWorkerPageController(
|
||||
* §17.11. Activation is a handshake: every controlled client must close new
|
||||
* admission and acknowledge within 30s. One missing client rejects it.
|
||||
*/
|
||||
async function requestActivation(): Promise<ServiceWorkerActivationOutcome> {
|
||||
function requestActivation(): Promise<ServiceWorkerActivationOutcome> {
|
||||
// SW-06. Concurrent callers share one command: a second call must not issue
|
||||
// a second nonce, a second listener or a second postMessage.
|
||||
activationInFlight ??= runActivation().finally(() => {
|
||||
activationInFlight = null;
|
||||
});
|
||||
return activationInFlight;
|
||||
}
|
||||
|
||||
async function runActivation(): Promise<ServiceWorkerActivationOutcome> {
|
||||
const waiting = registration?.waiting;
|
||||
if (!waiting) return Object.freeze({ kind: "NO_WAITING_WORKER" as const });
|
||||
if (isBlocked()) {
|
||||
@@ -264,6 +323,18 @@ export function createServiceWorkerPageController(
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// SW-06 / SW-RR-02. The reply must come from the exact worker this
|
||||
// request was sent to. A matching nonce is not identity: `null`
|
||||
// source means the sender cannot be established, so it is refused
|
||||
// like any other mismatch rather than accepted as this worker.
|
||||
if (event.source !== waiting) {
|
||||
finish(Object.freeze({ kind: "PROTOCOL_MISMATCH" as const }));
|
||||
return;
|
||||
}
|
||||
if (registration?.waiting !== waiting) {
|
||||
finish(Object.freeze({ kind: "PROTOCOL_MISMATCH" as const }));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
parsed.message.kind === "ACTIVATE_REJECTED" &&
|
||||
parsed.message.nonce === nonce
|
||||
@@ -306,11 +377,21 @@ export function createServiceWorkerPageController(
|
||||
}
|
||||
|
||||
/** §18.10. Static caches only; the registration itself is left in place. */
|
||||
async function resetOwnedCaches(): Promise<ServiceWorkerResetOutcome> {
|
||||
function resetOwnedCaches(): Promise<ServiceWorkerResetOutcome> {
|
||||
// SW-06. Single-flight, like activation.
|
||||
resetInFlight ??= runReset().finally(() => {
|
||||
resetInFlight = null;
|
||||
});
|
||||
return resetInFlight;
|
||||
}
|
||||
|
||||
async function runReset(): Promise<ServiceWorkerResetOutcome> {
|
||||
const container = dependencies.container;
|
||||
if (!container?.controller) {
|
||||
return Object.freeze({ kind: "NOT_CONTROLLED" as const });
|
||||
}
|
||||
// SW-06. The reply must come from the controller this request was sent to.
|
||||
const requestedController = container.controller;
|
||||
const nonce = nonces.issue();
|
||||
return new Promise<ServiceWorkerResetOutcome>((resolve) => {
|
||||
let settled = false;
|
||||
@@ -334,6 +415,20 @@ export function createServiceWorkerPageController(
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// SW-RR-02. An unattributable reset result is never proof this
|
||||
// controller performed the reset.
|
||||
if (
|
||||
event.source !== requestedController ||
|
||||
container.controller !== requestedController
|
||||
) {
|
||||
finish(
|
||||
Object.freeze({
|
||||
kind: "FAILED" as const,
|
||||
code: "PROTOCOL_MISMATCH",
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
finish(
|
||||
Object.freeze({
|
||||
kind: "RESET" as const,
|
||||
|
||||
Reference in New Issue
Block a user