refactor: 리펙토링

This commit is contained in:
DongHyeonka
2026-08-01 19:39:59 +09:00
parent 9c959ea2a5
commit c6da03369c
171 changed files with 20329 additions and 782 deletions
@@ -4,7 +4,7 @@ import {
type Page,
} from "../support/browser/strict-browser-test.ts";
const TOPIC = "reference-resources";
const TOPIC = "sample-topic-alpha";
const TOPIC_VERSION = 1;
const STATE_KEY = "__crossContextInvalidationCapability";
const STORAGE_PULSE_KEY =
+332
View File
@@ -20,10 +20,44 @@ import {
type QueryInvalidationCoordinator,
} from "../../src/contracts/query-invalidation.ts";
import { createFailure } from "../../src/contracts/errors.ts";
import { createRuntimeIdentityRegistry } from "../../src/contracts/query-keys.ts";
import {
bindQuery,
type QueryResultMeasure,
} from "../../src/contracts/server-state.ts";
import type { CacheScopeSnapshot } from "../../src/contracts/server-state-scope.ts";
const RESOURCE_INVALIDATION_TOPIC =
defineQueryInvalidationTopic("resource");
/**
* §24.12: the scope-bound commit fence is common runtime, so it is verified
* here with a local scope fixture rather than through the removable sample
* feature.
*/
function scopeSnapshot(): CacheScopeSnapshot & { fence(): void } {
let current = true;
const lifetime = new AbortController();
const identities = createRuntimeIdentityRegistry({
tokenFactory: () => "scope-identity-token-0001",
});
return {
generation: 1,
fingerprint: "scope-fingerprint-0001",
identities,
signal: lifetime.signal,
isCurrent: () => current,
fence() {
current = false;
lifetime.abort();
},
};
}
function measureOne(): QueryResultMeasure {
return { itemCount: 1, estimatedBytes: 8 };
}
function queryClient() {
return new QueryClient({
defaultOptions: {
@@ -181,7 +215,303 @@ describe("application query inbound bridge", () => {
});
});
describe("scope-bound query commit fence", () => {
it("discards a successful result whose scope was fenced during execution", async () => {
const client = queryClient();
const scope = scopeSnapshot();
let complete: (value: ApplicationResult<string>) => void = () => {};
const hook = renderHook(
() =>
useApplicationQuery(
bindQuery(
{
definitionId: "fenced-detail-v1",
definitionVersion: 1,
owner: "platform-test",
namespace: "fenced",
namespaceVersion: 1,
operationId: "GET_FENCED",
profileId: "DETAIL_STANDARD",
measureResult: measureOne,
execute: () =>
new Promise<ApplicationResult<string>>((resolve) => {
complete = resolve;
}),
},
"input",
scope,
),
),
{ wrapper: wrapper(client) },
);
// §10.8: the scope goes stale after dispatch but before commit.
scope.fence();
await act(async () => {
complete({ ok: true, value: "late" });
});
await waitFor(() =>
expect(hook.result.current.state.failure?.kind).toBe(
"SCOPE_GENERATION_CHANGED",
),
);
expect(hook.result.current.data).toBeUndefined();
});
it("refuses to start when the captured scope is already stale", async () => {
const client = queryClient();
const scope = scopeSnapshot();
const execute = vi.fn(async () => ({ ok: true as const, value: "v" }));
scope.fence();
const hook = renderHook(
() =>
useApplicationQuery(
bindQuery(
{
definitionId: "stale-detail-v1",
definitionVersion: 1,
owner: "platform-test",
namespace: "stale",
namespaceVersion: 1,
operationId: "GET_STALE",
profileId: "DETAIL_STANDARD",
measureResult: measureOne,
execute,
},
"input",
scope,
),
),
{ wrapper: wrapper(client) },
);
await waitFor(() =>
expect(hook.result.current.state.failure?.kind).toBe(
"SCOPE_GENERATION_CHANGED",
),
);
expect(execute).not.toHaveBeenCalled();
});
it("rejects a result that exceeds the profile budget instead of caching it", async () => {
const client = queryClient();
const scope = scopeSnapshot();
const hook = renderHook(
() =>
useApplicationQuery(
bindQuery(
{
definitionId: "oversized-list-v1",
definitionVersion: 1,
owner: "platform-test",
namespace: "oversized",
namespaceVersion: 1,
operationId: "LIST_OVERSIZED",
profileId: "VOLATILE_STATUS",
// §10.4: VOLATILE_STATUS admits 1 item and 64KiB.
measureResult: (): QueryResultMeasure => ({
itemCount: 2,
estimatedBytes: 8,
}),
execute: async () => ({ ok: true as const, value: "value" }),
},
"input",
scope,
),
),
{ wrapper: wrapper(client) },
);
await waitFor(() =>
expect(hook.result.current.state.failure?.kind).toBe(
"RESULT_LIMIT_EXCEEDED",
),
);
expect(hook.result.current.data).toBeUndefined();
});
it("treats a throwing measurement as a measurement failure, not a cache commit", async () => {
const client = queryClient();
const scope = scopeSnapshot();
const hook = renderHook(
() =>
useApplicationQuery(
bindQuery(
{
definitionId: "unmeasurable-v1",
definitionVersion: 1,
owner: "platform-test",
namespace: "unmeasurable",
namespaceVersion: 1,
operationId: "GET_UNMEASURABLE",
profileId: "DETAIL_STANDARD",
measureResult: (): QueryResultMeasure => {
throw new Error("estimator defect");
},
execute: async () => ({ ok: true as const, value: "value" }),
},
"input",
scope,
),
),
{ wrapper: wrapper(client) },
);
await waitFor(() =>
expect(hook.result.current.state.failure?.kind).toBe(
"RESULT_LIMIT_EXCEEDED",
),
);
expect(hook.result.current.data).toBeUndefined();
});
});
describe("scope-bound mutation fence", () => {
it("rejects a submit whose scope is already fenced", async () => {
const client = queryClient();
const scope = scopeSnapshot();
const execute = vi.fn(async () => ({ ok: true as const, value: "v" }));
const hook = renderHook(
() =>
useApplicationMutation<string, string>({
definitionId: "fenced-mutation-v1",
definitionVersion: 1,
operationId: "CREATE_FENCED",
owner: "platform-test",
duplicatePolicy: "REJECT_WHILE_ACTIVE",
scope,
execute,
invalidate: [],
}),
{ wrapper: wrapper(client) },
);
scope.fence();
const outcome = await hook.result.current.submit("value");
expect(outcome).toMatchObject({
ok: false,
error: { kind: "SCOPE_GENERATION_CHANGED" },
});
expect(execute).not.toHaveBeenCalled();
});
it("discards a mutation result whose scope was fenced after dispatch", async () => {
const client = queryClient();
const scope = scopeSnapshot();
const execute = vi.fn(async () => {
scope.fence();
return { ok: true as const, value: "committed" };
});
const hook = renderHook(
() =>
useApplicationMutation<string, string>({
definitionId: "late-mutation-v1",
definitionVersion: 1,
operationId: "CREATE_LATE",
owner: "platform-test",
duplicatePolicy: "ALLOW_PARALLEL",
scope,
execute,
invalidate: [],
}),
{ wrapper: wrapper(client) },
);
const outcome = await hook.result.current.submit("value");
expect(outcome).toMatchObject({
ok: false,
error: { kind: "SCOPE_GENERATION_CHANGED" },
});
expect(execute).toHaveBeenCalledOnce();
});
it("aborts a hung mutation when its captured scope is fenced", async () => {
const client = queryClient();
const scope = scopeSnapshot();
let observedSignal: AbortSignal | undefined;
const execute = vi.fn(
(_input: string, context: Readonly<{ signal: AbortSignal }>) =>
new Promise<ApplicationResult<string>>((resolve) => {
observedSignal = context.signal;
context.signal.addEventListener(
"abort",
() =>
resolve({
ok: false,
error: createFailure("REQUEST_ABORTED", "CREATE_HUNG", 0),
}),
{ once: true },
);
}),
);
const hook = renderHook(
() =>
useApplicationMutation<string, string>({
definitionId: "hung-mutation-v1",
definitionVersion: 1,
operationId: "CREATE_HUNG",
owner: "platform-test",
duplicatePolicy: "REJECT_WHILE_ACTIVE",
scope,
execute,
invalidate: [],
}),
{ wrapper: wrapper(client) },
);
const outcome = hook.result.current.submit("value");
await waitFor(() => expect(observedSignal).toBe(scope.signal));
scope.fence();
expect(observedSignal?.aborted).toBe(true);
await expect(outcome).resolves.toMatchObject({
ok: false,
error: {
kind: "SCOPE_GENERATION_CHANGED",
effect: "MAYBE_APPLIED",
retryable: false,
action: "contact-support",
},
});
});
});
describe("application mutation inbound bridge", () => {
it("rejects a duplicate submit by default while one is active", async () => {
const client = queryClient();
let complete: (value: ApplicationResult<string>) => void = () => {};
const execute = vi.fn(
() =>
new Promise<ApplicationResult<string>>((resolve) => {
complete = resolve;
}),
);
const hook = renderHook(
() => useApplicationMutation<string, string>({ execute }),
{ wrapper: wrapper(client) },
);
let first: Promise<ApplicationResult<string>> | null = null;
let duplicate: Promise<ApplicationResult<string>> | null = null;
act(() => {
first = hook.result.current.submit("created");
duplicate = hook.result.current.submit("created");
});
if (!first || !duplicate) throw new Error("expected two submissions");
await expect(duplicate).resolves.toMatchObject({
ok: false,
error: { kind: "DUPLICATE_IN_FLIGHT" },
});
expect(execute).toHaveBeenCalledOnce();
complete({ ok: true, value: "created" });
await act(() => first as Promise<ApplicationResult<string>>);
});
it("deduplicates submit and commits one optimistic mutation", async () => {
const client = queryClient();
const key = ["resource", "list"];
@@ -196,6 +526,8 @@ describe("application mutation inbound bridge", () => {
const hook = renderHook(
() =>
useApplicationMutation<string, string>({
// §11.2: joining is opt-in. The default is REJECT_WHILE_ACTIVE.
duplicatePolicy: "JOIN_IDENTICAL",
execute,
invalidate: [RESOURCE_INVALIDATION_TOPIC],
currentData: client.getQueryData(key),
@@ -0,0 +1,164 @@
// @vitest-environment jsdom
import { render, screen, waitFor, within } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { SERVER_STATE_PROFILES } from "../../src/contracts/server-state.ts";
import { COMPOSED_CONTRACT_CONTRIBUTIONS } from "../../src/features/installed-contract-contributions.ts";
import { ROUTE_REGISTRY } from "../../src/features/installed-feature-contracts.ts";
import PlatformOverviewPage from "../../src/presentation/examples/platform-overview-page.tsx";
import { LocaleProvider } from "../../src/presentation/i18n/index.ts";
import { ApplicationProvider } from "../../src/presentation/providers/application-provider.tsx";
import { createTestApplication } from "../helpers/create-test-application.ts";
import { createRuntimeCapabilitiesStub } from "../helpers/runtime-capabilities-stub.ts";
/**
* The page must stay a projection of the installed registries. Every assertion
* below derives its expectation from the same registry the page reads, so a
* template that removes its sample feature still satisfies this suite.
*/
function renderPage() {
return render(
<ApplicationProvider application={createTestApplication()}>
<LocaleProvider>
<PlatformOverviewPage />
</LocaleProvider>
</ApplicationProvider>,
);
}
function tableByCaption(caption: string): HTMLElement {
return screen.getByRole("table", { name: caption });
}
/** A metric is a `dt`/`dd` pair, which carries no ARIA role to query by. */
function metricByLabel(scope: HTMLElement, label: string): HTMLElement {
const term = within(scope).getByText(label).closest(".platform-metric");
if (!(term instanceof HTMLElement)) {
throw new Error(`No metric is labelled ${label}`);
}
return term;
}
describe("platform overview page", () => {
it("renders one route row per installed route registry entry", () => {
renderPage();
const table = tableByCaption("설치된 라우트 목록");
const dataRows = within(table).getAllByRole("row").slice(1);
expect(dataRows).toHaveLength(Object.keys(ROUTE_REGISTRY).length);
for (const definition of Object.values(ROUTE_REGISTRY)) {
expect(
within(table).getByText(definition.routeId),
).toBeInTheDocument();
}
});
it("renders one operation row per installed HTTP contract", () => {
const operationIds = [
...COMPOSED_CONTRACT_CONTRIBUTIONS.httpByOperationId.keys(),
];
renderPage();
if (operationIds.length === 0) {
expect(
screen.getByRole("heading", {
name: "설치된 HTTP 오퍼레이션이 없습니다.",
}),
).toBeVisible();
return;
}
const table = tableByCaption("설치된 HTTP 오퍼레이션");
expect(within(table).getAllByRole("row").slice(1)).toHaveLength(
operationIds.length,
);
for (const operationId of operationIds) {
expect(within(table).getByText(operationId)).toBeInTheDocument();
}
});
it("renders every fixed server-state profile with its own budget", () => {
renderPage();
const table = tableByCaption("서버 상태 프로파일");
for (const profile of Object.values(SERVER_STATE_PROFILES)) {
expect(within(table).getByText(profile.profileId)).toBeInTheDocument();
}
expect(within(table).getAllByRole("row").slice(1)).toHaveLength(
Object.keys(SERVER_STATE_PROFILES).length,
);
});
it("labels a capability that was never selected as unselected", () => {
renderPage();
const region = screen.getByRole("region", { name: "선택적 런타임 능력" });
expect(within(region).getAllByText("미선택")).toHaveLength(4);
expect(within(region).queryByText("운영자가 비활성화함")).toBeNull();
});
it("separates an operator disable from a capability that was never selected", () => {
render(
<ApplicationProvider
application={createTestApplication({
runtimeCapabilities: createRuntimeCapabilitiesStub({
SERVICE_WORKER: { selected: 1, active: 0, override: "DISABLED" },
OFFLINE_COMMANDS: { selected: 1, active: 1 },
}),
})}
>
<LocaleProvider>
<PlatformOverviewPage />
</LocaleProvider>
</ApplicationProvider>,
);
const region = screen.getByRole("region", { name: "선택적 런타임 능력" });
expect(within(region).getByText("운영자가 비활성화함")).toBeVisible();
expect(within(region).getByText("활성 (1)")).toBeVisible();
expect(within(region).getAllByText("미선택")).toHaveLength(2);
});
it("counts installed contract packages separately from template fixtures", () => {
const fixtures = COMPOSED_CONTRACT_CONTRIBUTIONS.contributions.filter(
(contribution) => contribution.source.kind === "TEMPLATE_FIXTURE",
).length;
renderPage();
const summary = screen.getByRole("region", { name: "설치 요약" });
const packages = metricByLabel(summary, "외부 계약 패키지");
expect(
within(packages).getByText(
`${COMPOSED_CONTRACT_CONTRIBUTIONS.externalPackages.length}`,
),
).toBeVisible();
expect(
within(packages).getByText(`템플릿 픽스처 ${fixtures}`),
).toBeVisible();
expect(
within(metricByLabel(summary, "라우트")).getByText(
`${Object.keys(ROUTE_REGISTRY).length}`,
),
).toBeVisible();
});
it("renders the verified release identity once the runtime resolves it", async () => {
renderPage();
const region = screen.getByRole("region", { name: "릴리스 신원" });
await waitFor(() =>
expect(
within(metricByLabel(region, "빌드")).getByText("test-build"),
).toBeVisible(),
);
expect(
within(metricByLabel(region, "릴리스")).getByText("test-release"),
).toBeVisible();
});
});
+48 -4
View File
@@ -1,6 +1,6 @@
// @vitest-environment jsdom
import { render, screen } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
@@ -22,6 +22,29 @@ function renderRouter() {
}
describe("generic application router", () => {
it("reaches the platform overview from the home starter actions", async () => {
const user = userEvent.setup();
window.history.pushState({}, "", "/");
renderRouter();
// The starter action lives inside the lazily loaded home chunk, so this is
// the first wait in the file that has to outlast a chunk load rather than
// an already-mounted shell element.
await user.click(
await screen.findByRole(
"link",
{ name: "플랫폼 구성 보기" },
{ timeout: 5000 },
),
);
await waitFor(() =>
expect(
screen.getByRole("heading", { name: "플랫폼 구성", level: 1 }),
).toHaveFocus(),
);
});
it("renders the app shell and not-found route without a feature input", async () => {
window.history.pushState({}, "", "/missing");
renderRouter();
@@ -49,8 +72,29 @@ describe("generic application router", () => {
).toBeVisible();
expect(window.location.pathname).toBe("/examples/ui");
expect(document.title).toBe("UI 구성요소 · Frontend Skeleton");
expect(
screen.getByRole("heading", { name: "UI 구성요소", level: 1 }),
).toHaveFocus();
await waitFor(() =>
expect(
screen.getByRole("heading", { name: "UI 구성요소", level: 1 }),
).toHaveFocus(),
);
});
it("focuses the route heading again when the lazy chunk is already cached", async () => {
// §9.7. The first visit resolves the route module asynchronously, so the
// router lifecycle and the page header commit separately. A later visit
// renders the cached module in the same commit, which is the ordering that
// must still hand focus to the heading rather than leaving it on main.
const user = userEvent.setup();
window.history.pushState({}, "", "/");
renderRouter();
for (const label of ["UI 구성요소", "화면 상태", "UI 구성요소"]) {
await user.click(await screen.findByRole("link", { name: label }));
await waitFor(() =>
expect(
screen.getByRole("heading", { name: label, level: 1 }),
).toHaveFocus(),
);
}
});
});
@@ -0,0 +1,74 @@
// @vitest-environment jsdom
import { act, render, waitFor } from "@testing-library/react";
import { QueryClient, useQueryClient } from "@tanstack/react-query";
import { describe, expect, it } from "vitest";
import { createServerStateScopeRuntime } from "../../src/adapters/query-cache/server-state-scope-runtime.ts";
import { createServerStateGenerationStore } from "../../src/bootstrap/server-state-generation-store.ts";
import { ServerStateGenerationProvider } from "../../src/presentation/adapters/query/server-state-generation-provider.tsx";
describe("server-state generation provider", () => {
it("remounts consumers with the QueryClient owned by the READY generation", async () => {
const store = createServerStateGenerationStore(() => {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
return {
queryClient,
queryInvalidation: {
invalidate: async () => {},
beginMutation: () => ({ release: async () => {} }),
resetLocal: async () => {
await queryClient.cancelQueries();
queryClient.clear();
},
dispose() {},
},
crossContextStatus: () => "DEGRADED_LOCAL_ONLY" as const,
};
});
let sessionListener: () => void = () => {};
let token = 0;
const scope = createServerStateScopeRuntime({
session: {
subscribe(listener) {
sessionListener = listener;
return () => {};
},
},
queryInvalidation: { resetLocal: () => store.resetCurrent() },
activateNextGeneration: () => store.activateNext(),
tokenFactory: () =>
`scope-generation-provider-${String(token++).padStart(4, "0")}`,
});
const renderedClients: QueryClient[] = [];
function Probe() {
renderedClients.push(useQueryClient());
return <div>generation-content</div>;
}
render(
<ServerStateGenerationProvider
store={store}
scope={scope}
transitionFallback={<div>scope-transition</div>}
>
<Probe />
</ServerStateGenerationProvider>,
);
const firstClient = renderedClients.at(-1);
if (!firstClient) throw new Error("expected initial QueryClient");
act(() => sessionListener());
await waitFor(() => expect(scope.getPhase()).toBe("READY"));
await waitFor(() =>
expect(renderedClients.at(-1)).toBe(store.getSnapshot().queryClient),
);
expect(renderedClients.at(-1)).not.toBe(firstClient);
scope.dispose();
store.dispose();
});
});
@@ -0,0 +1,83 @@
// @vitest-environment jsdom
import { act, render, screen, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { createServerStateScopeRuntime } from "../../src/adapters/query-cache/server-state-scope-runtime.ts";
import { ServerStateScopeProvider } from "../../src/presentation/adapters/query/server-state-scope-provider.tsx";
const activeRuntimes: Array<{ dispose(): void }> = [];
afterEach(() => {
for (const runtime of activeRuntimes.splice(0)) runtime.dispose();
});
function scopeFixture(resetLocal: () => Promise<void>) {
let sessionListener: () => void = () => {};
let token = 0;
const runtime = createServerStateScopeRuntime({
session: {
subscribe(listener) {
sessionListener = listener;
return () => {};
},
},
queryInvalidation: {
resetLocal,
},
tokenFactory: () => `scope-provider-token-${String(token++).padStart(4, "0")}`,
});
activeRuntimes.push(runtime);
return { runtime, triggerSessionChange: () => sessionListener() };
}
describe("server-state scope provider", () => {
it("removes previous-scope children synchronously while reset is pending", async () => {
let completeReset: () => void = () => {};
const reset = new Promise<void>((resolve) => {
completeReset = resolve;
});
const fixture = scopeFixture(async () => reset);
render(
<ServerStateScopeProvider
runtime={fixture.runtime}
transitionFallback={<div>scope-transition</div>}
>
<div>previous-account-secret</div>
</ServerStateScopeProvider>,
);
expect(screen.getByText("previous-account-secret")).toBeVisible();
act(() => fixture.triggerSessionChange());
expect(screen.queryByText("previous-account-secret")).toBeNull();
expect(screen.getByText("scope-transition")).toBeVisible();
completeReset();
await waitFor(() =>
expect(screen.getByText("previous-account-secret")).toBeVisible(),
);
});
it("never remounts previous-scope children after mandatory cleanup failure", async () => {
const fixture = scopeFixture(async () => {
throw new Error("reset failed");
});
render(
<ServerStateScopeProvider
runtime={fixture.runtime}
transitionFallback={<div>scope-transition</div>}
>
<div>previous-account-secret</div>
</ServerStateScopeProvider>,
);
act(() => fixture.triggerSessionChange());
await waitFor(() => expect(fixture.runtime.getPhase()).toBe("FAILED"));
expect(screen.queryByText("previous-account-secret")).toBeNull();
expect(screen.getByText("scope-transition")).toBeVisible();
});
});
+1 -1
View File
@@ -25,7 +25,7 @@ test("@a11y keyboard reaches the primary route action with visible focus", async
page,
}) => {
await page.goto("/");
const action = page.getByRole("link", { name: "UI 구성요소 보기" });
const action = page.getByRole("link", { name: "플랫폼 구성 보기" });
await expect(action).toBeVisible();
await page.keyboard.press("Tab");
await expect(action).toBeFocused();
+8 -2
View File
@@ -6,8 +6,14 @@ test("boots and navigates the compact production shell", async ({ page }) => {
const menu = page.getByRole("button", { name: "메뉴", exact: true });
await expect(menu).toHaveCSS("min-width", "44px");
await menu.click();
await expect(page.getByRole("navigation", { name: "주요 탐색" })).toBeVisible();
await page.getByRole("link", { name: "UI 구성요소" }).click();
const navigation = page.getByRole("navigation", { name: "주요 탐색" });
await expect(navigation).toBeVisible();
// The drawer is a non-modal dialog, so page content stays in the
// accessibility tree while it is open. Scope to the navigation and match the
// whole name, or a route call to action on the page behind it also matches.
await navigation
.getByRole("link", { name: "UI 구성요소", exact: true })
.click();
await expect(page).toHaveURL(/\/examples\/ui$/);
await expect(page.locator("html")).toHaveAttribute("data-build-id", "local-build");
const overflow = await page.evaluate(
+58
View File
@@ -0,0 +1,58 @@
import { expect, test } from "../support/browser/strict-browser-test.ts";
import { SERVER_STATE_PROFILES } from "../../src/contracts/server-state.ts";
import { ROUTE_REGISTRY } from "../../src/features/installed-feature-contracts.ts";
/**
* The overview is a projection of the installed registries. These assertions
* read the same registries the shipped bundle was built from, so they keep
* meaning after a feature is added or removed.
*/
test("projects the installed route registry into the shipped bundle", async ({
page,
}) => {
await page.goto("/examples/platform");
await expect(
page.getByRole("heading", { name: "플랫폼 구성", level: 1 }),
).toBeVisible();
const table = page.getByRole("table", { name: "설치된 라우트 목록" });
await expect(table.locator("tbody tr")).toHaveCount(
Object.keys(ROUTE_REGISTRY).length,
);
for (const routeId of Object.keys(ROUTE_REGISTRY)) {
await expect(table.getByText(routeId, { exact: true })).toBeVisible();
}
});
test("shows every fixed server-state profile", async ({ page }) => {
await page.goto("/examples/platform");
const table = page.getByRole("table", { name: "서버 상태 프로파일" });
await expect(table.locator("tbody tr")).toHaveCount(
Object.keys(SERVER_STATE_PROFILES).length,
);
});
test("states the release contract identity verified at boot", async ({
page,
}) => {
await page.goto("/examples/platform");
const release = page.locator(
"section[aria-labelledby='platform-release-title']",
);
await expect(release.getByText(/^sha256:/)).toBeVisible();
});
test("reports an unselected capability without claiming it was disabled", async ({
page,
}) => {
await page.goto("/examples/platform");
const capabilities = page.locator(
"section[aria-labelledby='platform-capabilities-title']",
);
await expect(capabilities.getByText("미선택")).toHaveCount(4);
await expect(capabilities.getByText("운영자가 비활성화함")).toHaveCount(0);
});
+3 -7
View File
@@ -1,25 +1,21 @@
import { expect, test } from "../support/browser/strict-browser-test.ts";
import { successEnvelope } from "../mocks/contracts/envelopes.ts";
import { ROUTE_REGISTRY } from "../../src/features/installed-feature-contracts.ts";
test("opens the protected integration route through the local demo seam", async ({
page,
}) => {
const protectedRoute = Object.values(ROUTE_REGISTRY).find(
(definition) => definition.access === "integration-defined",
);
if (!protectedRoute) throw new Error("An integration route is required");
const protectedRoute = ROUTE_REGISTRY.REFERENCE_RESOURCE_LIST;
await page.route(
"http://localhost:8080/api/reference-resources?*",
(route) =>
route.fulfill({
json: successEnvelope([
json: [
{
id: "browser-reference",
name: "Browser reference",
createdAt: "2026-07-26T00:00:00.000Z",
},
]),
],
}),
);
await page.goto(protectedRoute.path);
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import {
buildRouteUrl,
@@ -19,8 +19,34 @@ import {
} from "../../../src/features/reference-feature/contracts/reference-feature-contract.ts";
import type { ReferenceFeatureInput } from "../../../src/features/reference-feature/application/reference-feature-api.ts";
import { createTestApplication } from "../../helpers/create-test-application.ts";
import { createReferenceFeatureInstalledInput } from "../../../src/features/reference-feature/adapters/create-reference-feature-input.ts";
describe("reference feature boundary contracts", () => {
it("propagates uncertain command effect certainty into the application failure", async () => {
const execute = vi.fn(async () => ({
kind: "CONTRACT_VIOLATION" as const,
violation: {
kind: "SUCCESS_SCHEMA_INVALID" as const,
operation: "VALIDATION" as const,
},
effect: "MAYBE_APPLIED" as const,
}));
const installed = createReferenceFeatureInstalledInput({
contractOperations: { execute },
});
await expect(
installed.input.createResource({ name: "uncertain" }),
).resolves.toMatchObject({
ok: false,
error: {
effect: "MAYBE_APPLIED",
retryable: false,
action: "contact-support",
},
});
});
it("round-trips one canonical filter through URL and query identity", () => {
const filters = {
tags: ["open", "new"],
+8
View File
@@ -0,0 +1,8 @@
{
"total": {
"lines": { "pct": 0 },
"statements": { "pct": 0 },
"functions": { "pct": 0 },
"branches": { "pct": 0 }
}
}
+3
View File
@@ -4,6 +4,7 @@ import {
type ApplicationOutputPorts,
} from "../../src/application/create-application.ts";
import type { ApplicationFeatureInputs } from "../../src/application/ports/in/application-api.ts";
import { createRuntimeCapabilitiesStub } from "./runtime-capabilities-stub.ts";
type TestApplicationOverrides = Partial<ApplicationOutputPorts> &
Readonly<{
@@ -49,6 +50,8 @@ export function createTestApplication(
},
}),
},
runtimeCapabilities:
overrides.runtimeCapabilities ?? createRuntimeCapabilitiesStub(),
navigation: overrides.navigation ?? { reload: () => {} },
},
overrides.featureInputs,
@@ -0,0 +1,34 @@
import type {
RuntimeCapabilityId,
RuntimeCapabilityStatus,
} from "../../src/contracts/runtime-capabilities.ts";
import type { RuntimeCapabilitiesPort } from "../../src/application/ports/runtime-capabilities-port.ts";
const CAPABILITY_IDS = Object.freeze([
"REALTIME",
"WEB_WORKER",
"SERVICE_WORKER",
"OFFLINE_COMMANDS",
] as const);
/**
* A capability port whose snapshot is fixed by the test rather than by the
* repository's installed selection, so a suite asserting presentation behaviour
* does not change meaning when a capability is later installed.
*/
export function createRuntimeCapabilitiesStub(
overrides: Readonly<Partial<Record<RuntimeCapabilityId, Partial<RuntimeCapabilityStatus>>>> = {},
): RuntimeCapabilitiesPort {
const snapshot = Object.freeze(
CAPABILITY_IDS.map((capabilityId) =>
Object.freeze({
capabilityId,
selected: 0,
active: 0,
override: "DEFAULT" as const,
...overrides[capabilityId],
}),
),
);
return Object.freeze({ getSnapshot: () => snapshot });
}
+20 -17
View File
@@ -1,9 +1,5 @@
import { delay, http, HttpResponse } from "msw";
import { delay, http, HttpResponse, type JsonBodyType } from "msw";
import {
failureEnvelope,
successEnvelope,
} from "../contracts/envelopes.ts";
import type { HttpScenarioId } from "../scenarios/catalog.ts";
import { assertOperationScenario } from "../scenarios/catalog.ts";
@@ -36,7 +32,7 @@ const DEFAULT_RESOURCE = Object.freeze({
async function scenarioResponse(
scenario: HttpScenarioId,
payload: unknown,
payload: JsonBodyType,
attempt: number,
) {
if (scenario === "slow") await delay(50);
@@ -56,35 +52,33 @@ async function scenarioResponse(
return HttpResponse.json({ data: payload });
}
if (scenario === "schema-mismatch") {
return HttpResponse.json(successEnvelope({ unexpected: true }));
return HttpResponse.json({ unexpected: true });
}
if (
scenario === "auth-persistent-401" ||
(scenario === "auth-recover-once" && attempt === 1)
) {
return HttpResponse.json(failureEnvelope("AUTH_REQUIRED"), {
return HttpResponse.json(problem(401, "AUTH_REQUIRED"), {
status: 401,
});
}
if (scenario === "forbidden-403") {
return HttpResponse.json(failureEnvelope("FORBIDDEN"), { status: 403 });
return HttpResponse.json(problem(403, "FORBIDDEN"), { status: 403 });
}
if (scenario === "not-found-404") {
return HttpResponse.json(failureEnvelope("NOT_FOUND"), { status: 404 });
return HttpResponse.json(problem(404, "NOT_FOUND"), { status: 404 });
}
if (scenario === "conflict-409") {
return HttpResponse.json(failureEnvelope("CONFLICT"), { status: 409 });
return HttpResponse.json(problem(409, "CONFLICT"), { status: 409 });
}
if (scenario === "validation-422") {
return HttpResponse.json(
failureEnvelope("VALIDATION_REJECTED", {
issues: [{ path: "name", code: "too_small" }],
}),
problem(422, "VALIDATION_REJECTED"),
{ status: 422 },
);
}
if (scenario === "rate-limited-429") {
return HttpResponse.json(failureEnvelope("RATE_LIMITED"), {
return HttpResponse.json(problem(429, "RATE_LIMITED"), {
status: 429,
headers: { "Retry-After": "1" },
});
@@ -93,11 +87,20 @@ async function scenarioResponse(
scenario === "server-terminal-500" ||
(scenario === "server-retry-success" && attempt === 1)
) {
return HttpResponse.json(failureEnvelope("SERVER_FAILURE"), {
return HttpResponse.json(problem(503, "SERVER_FAILURE"), {
status: 503,
});
}
return HttpResponse.json(successEnvelope(payload));
return HttpResponse.json(payload);
}
function problem(status: number, code: string) {
return Object.freeze({
type: `https://api.test/problems/${code.toLowerCase()}`,
title: code,
status,
code,
});
}
export function createReferenceScenarioHandlers(options: ScenarioOptions = {}) {
+140 -18
View File
@@ -4,6 +4,9 @@ import {
loadReleaseManifest,
ReleaseManifestError,
} from "../../src/bootstrap/load-release-manifest.ts";
import { computeContractSetDigest } from "../../src/contracts/contract-set-canonical.ts";
const EMPTY_SET_DIGEST = await computeContractSetDigest([]);
const runtime: Parameters<typeof loadReleaseManifest>[0] = {
build: {
@@ -22,12 +25,45 @@ const runtime: Parameters<typeof loadReleaseManifest>[0] = {
RELEASE_MANIFEST_URL: "/release-manifest.json",
BUILD_ID: "build-a",
RELEASE_ID: "release-a",
CONFIG_SCHEMA_VERSION: "1",
API_CONTRACT_VERSION: "1",
CONFIG_SCHEMA_VERSION: "2.0",
CAPABILITY_OVERRIDES: {
REALTIME: "DEFAULT",
WEB_WORKER: "DEFAULT",
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
},
},
configSchema: "V2",
validationDurationMs: 0,
};
const manifest = {
schemaVersion: 2,
appVersion: "0.1.0",
buildId: "build-a",
commitSha: "abc123",
configSchemaVersion: "2.0",
assetManifestHash: "hash-a",
releaseId: "release-a",
builtAt: "2026-07-25T00:00:00Z",
routeChunks: { "route-home": "assets/home.js" },
contractSet: {
setAlgorithm: "CA_CONTRACT_SET_V1",
setDigest: EMPTY_SET_DIGEST,
packages: [],
},
};
const runtimeV1: Parameters<typeof loadReleaseManifest>[0] = {
...runtime,
config: {
...runtime.config,
CONFIG_SCHEMA_VERSION: "1",
LEGACY_API_CONTRACT_VERSION: "1",
},
configSchema: "V1",
};
const manifestV1 = {
schemaVersion: 1,
appVersion: "0.1.0",
buildId: "build-a",
@@ -40,12 +76,18 @@ const manifest = {
routeChunks: { "route-home": "assets/home.js" },
};
function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
headers: { "content-type": "application/json" },
});
}
describe("release manifest boot boundary", () => {
it("loads a coherent release tuple", async () => {
await expect(
loadReleaseManifest(
runtime,
{ fetcher: async () => new Response(JSON.stringify(manifest)) },
{ fetcher: async () => jsonResponse(manifest) },
),
).resolves.toMatchObject({ releaseId: "release-a" });
});
@@ -56,7 +98,7 @@ describe("release manifest boot boundary", () => {
runtime,
{
fetcher: async () =>
new Response(JSON.stringify({ ...manifest, buildId: "build-b" })),
jsonResponse({ ...manifest, buildId: "build-b" }),
},
),
).rejects.toMatchObject({
@@ -66,18 +108,6 @@ describe("release manifest boot boundary", () => {
});
it.each([
[
{ configSchemaVersion: "2" },
{},
"CONFIG_MISMATCH",
"MANIFEST_CONFIG_SCHEMA_MISMATCH",
],
[
{ apiContractVersion: "2" },
{},
"API_CONTRACT_MISMATCH",
"MANIFEST_API_CONTRACT_MISMATCH",
],
[
{ releaseId: "release-b" },
{},
@@ -98,7 +128,7 @@ describe("release manifest boot boundary", () => {
runtime,
{
fetcher: async () =>
Response.json({ ...manifest, ...manifestOverride }),
jsonResponse({ ...manifest, ...manifestOverride }),
...options,
},
),
@@ -106,6 +136,98 @@ describe("release manifest boot boundary", () => {
},
);
it.each(["3.0", "9.0"])(
"rejects unsupported V2 manifest config version %s at the schema boundary",
async (configSchemaVersion) => {
await expect(
loadReleaseManifest(runtime, {
fetcher: async () =>
jsonResponse({ ...manifest, configSchemaVersion }),
}),
).rejects.toMatchObject({
kind: "RELEASE_MANIFEST_FAILURE",
code: "MANIFEST_SCHEMA_INVALID",
});
},
);
it("rejects a contract set the build did not compile", async () => {
await expect(
loadReleaseManifest(runtime, {
fetcher: async () =>
jsonResponse({
...manifest,
contractSet: {
setAlgorithm: "CA_CONTRACT_SET_V1",
setDigest: EMPTY_SET_DIGEST,
packages: [
{
packageId: "@org-contracts/worklog",
version: "1.2.3",
digest: `sha256:${"a".repeat(64)}`,
runtimeProtocolVersion: 1,
sourceRevision: "abc1234",
},
],
},
}),
}),
).rejects.toMatchObject({
kind: "CONTRACT_SET_MISMATCH",
code: "CONTRACT_SET_PACKAGE_UNEXPECTED",
});
});
it("still reads a V1 manifest during the compatibility window", async () => {
await expect(
loadReleaseManifest(
runtimeV1,
{
fetcher: async () => jsonResponse(manifestV1),
},
),
).resolves.toMatchObject({ schemaVersion: 1, contractSet: null });
});
it("rejects a V2 runtime paired with a V1 manifest", async () => {
await expect(
loadReleaseManifest(runtime, {
fetcher: async () => jsonResponse({
...manifestV1,
configSchemaVersion: "2.0",
}),
}),
).rejects.toMatchObject({
kind: "PROTOCOL_PAIR_MISMATCH",
code: "MANIFEST_PROTOCOL_PAIR_MISMATCH",
});
});
it("rejects a V1 runtime paired with a V2 manifest", async () => {
await expect(
loadReleaseManifest(runtimeV1, {
fetcher: async () => jsonResponse(manifest),
}),
).rejects.toMatchObject({
kind: "PROTOCOL_PAIR_MISMATCH",
code: "MANIFEST_PROTOCOL_PAIR_MISMATCH",
});
});
it("requires matching legacy scalar versions for a V1 pair", async () => {
await expect(
loadReleaseManifest(runtimeV1, {
fetcher: async () => jsonResponse({
...manifestV1,
apiContractVersion: "2",
}),
}),
).rejects.toMatchObject({
kind: "API_CONTRACT_MISMATCH",
code: "MANIFEST_API_CONTRACT_MISMATCH",
});
});
it("rejects a manifest without a complete route chunk map", async () => {
const malformed = Object.fromEntries(
Object.entries(manifest).filter(([key]) => key !== "routeChunks"),
@@ -113,7 +235,7 @@ describe("release manifest boot boundary", () => {
await expect(
loadReleaseManifest(
runtime,
{ fetcher: async () => Response.json(malformed) },
{ fetcher: async () => jsonResponse(malformed) },
),
).rejects.toBeInstanceOf(ReleaseManifestError);
});
+108 -7
View File
@@ -6,17 +6,22 @@ import { assertSafeConfigNames } from "../../src/contracts/env.ts";
const validConfig = {
APP_ENV: "local",
API_BASE_URL: "http://localhost:8080",
API_BASE_URL: "http://localhost:8080/",
REQUEST_TIMEOUT_MS: 10_000,
MAX_RETRY_ATTEMPTS: 2,
TELEMETRY_ENABLED: false,
AUTH_MODE: "external",
CONFIG_SCHEMA_VERSION: "1",
API_CONTRACT_VERSION: "1",
CONFIG_SCHEMA_VERSION: "2.0",
RELEASE_MANIFEST_URL: "/release-manifest.json",
BUILD_ID: "build-a",
};
const validV1Config = {
...validConfig,
CONFIG_SCHEMA_VERSION: "1",
API_CONTRACT_VERSION: "1.4.0",
};
describe("runtime configuration boundary", () => {
it.each([
[{ ...validConfig, API_BASE_URL: undefined }, "required key"],
@@ -24,6 +29,30 @@ describe("runtime configuration boundary", () => {
[{ ...validConfig, MAX_RETRY_ATTEMPTS: 3 }, "retry cap"],
[{ ...validConfig, TELEMETRY_ENABLED: "false" }, "ambiguous boolean"],
[{ ...validConfig, CONFIG_SCHEMA_VERSION: "next" }, "version"],
[
{ ...validConfig, API_CONTRACT_VERSION: "1" },
"§5.1 scalar contract version is removed from V2",
],
[
{ ...validConfig, API_BASE_URL: "http://localhost:8080/api" },
"§6.2 base URL path must end with /",
],
[
{ ...validConfig, API_BASE_URL: "http://localhost:8080/#frag" },
"§6.2 hash in base URL",
],
[
{ ...validConfig, API_BASE_URL: "http://user:pw@localhost:8080/" },
"§6.2 credentials in URL",
],
[
{ ...validConfig, RELEASE_MANIFEST_URL: "/a/../b.json" },
"§6.2 dot segment traversal",
],
[
{ ...validConfig, RELEASE_MANIFEST_URL: "/manifest.json?v=1" },
"§6.2 query in manifest URL",
],
[{ ...validConfig, UNKNOWN_KEY: true }, "unknown key"],
])("rejects invalid config: %s (%s)", (candidate, _reason) => {
void _reason;
@@ -44,12 +73,55 @@ describe("runtime configuration boundary", () => {
validateRuntimeConfig({
...validConfig,
APP_ENV: "production",
API_BASE_URL: "https://api.example.test",
API_BASE_URL: "https://api.example.test/",
AUTH_MODE: "demo",
}).success,
).toBe(false);
});
it("accepts only the explicitly supported V1 and V2 boot versions", () => {
expect(validateRuntimeConfig(validV1Config)).toMatchObject({
success: true,
schema: "V1",
});
expect(validateRuntimeConfig(validConfig)).toMatchObject({
success: true,
schema: "V2",
});
for (const version of ["0", "1.0", "2.0.1", "3.0"]) {
expect(
validateRuntimeConfig({
...validV1Config,
CONFIG_SCHEMA_VERSION: version,
}).success,
).toBe(false);
}
});
it.each([
"file:///tmp/api/",
"data:text/plain,/",
"blob:https://example.test/00000000-0000-0000-0000-000000000000",
])("rejects a non-HTTP API endpoint in local mode: %s", (API_BASE_URL) => {
expect(validateRuntimeConfig({ ...validConfig, API_BASE_URL }).success).toBe(
false,
);
});
it.each(["file:///tmp/telemetry", "data:text/plain,telemetry"])(
"rejects a non-HTTP telemetry endpoint in local mode: %s",
(TELEMETRY_ENDPOINT) => {
expect(
validateRuntimeConfig({
...validConfig,
TELEMETRY_ENABLED: true,
TELEMETRY_ENDPOINT,
}).success,
).toBe(false);
},
);
it("validates a fetched config under the 500ms budget excluding network", async () => {
let current = 100;
const result = await loadRuntimeConfig({
@@ -59,12 +131,38 @@ describe("runtime configuration boundary", () => {
routerBasePath: "/",
runtimeConfigUrl: "/config.json",
},
fetcher: async () => new Response(JSON.stringify(validConfig)),
fetcher: async () =>
new Response(JSON.stringify(validConfig), {
headers: { "content-type": "application/json" },
}),
now: () => (current += 2),
});
expect(result.validationDurationMs).toBeLessThanOrEqual(500);
expect(result.config.API_BASE_URL).toBe("http://localhost:8080");
expect(result.config.API_BASE_URL).toBe("http://localhost:8080/");
expect(result.configSchema).toBe("V2");
expect(result.config.CAPABILITY_OVERRIDES.SERVICE_WORKER).toBe("DEFAULT");
});
it("does not include network acquisition in validationDurationMs", async () => {
let current = 0;
const result = await loadRuntimeConfig({
buildConfig: {
buildId: "build-a",
commitSha: "local",
routerBasePath: "/",
runtimeConfigUrl: "/config.json",
},
fetcher: async () => {
current = 10_000;
return new Response(JSON.stringify(validConfig), {
headers: { "content-type": "application/json" },
});
},
now: () => current,
});
expect(result.validationDurationMs).toBe(0);
});
it("returns only safe boot fields on failure", async () => {
@@ -76,7 +174,10 @@ describe("runtime configuration boundary", () => {
routerBasePath: "/",
runtimeConfigUrl: "/config.json",
},
fetcher: async () => new Response("{"),
fetcher: async () =>
new Response("{", {
headers: { "content-type": "application/json" },
}),
}),
).rejects.toMatchObject({
safe: {
+2
View File
@@ -5,6 +5,7 @@ import {
type ApplicationOutputPorts,
} from "../../src/application/create-application.ts";
import { createTestApplication } from "../helpers/create-test-application.ts";
import { createRuntimeCapabilitiesStub } from "../helpers/runtime-capabilities-stub.ts";
declare module "../../src/application/ports/in/application-api.ts" {
interface ApplicationFeatureInputs {
@@ -97,6 +98,7 @@ describe("application input/output boundary", () => {
routeChunks: { "route-home": "assets/home.js" },
}),
},
runtimeCapabilities: createRuntimeCapabilitiesStub(),
navigation: { reload: () => {} },
} satisfies ApplicationOutputPorts;
const application = createApplication(ports);
@@ -5,6 +5,7 @@ import { createAnonymousSessionAdapter } from "../../src/adapters/auth/external-
import type { StoragePort } from "../../src/application/ports/storage-port.ts";
import type { DiagnosticsPort } from "../../src/application/ports/diagnostics-port.ts";
import type { TelemetryPort } from "../../src/application/ports/telemetry-port.ts";
import { createRuntimeCapabilitiesStub } from "../helpers/runtime-capabilities-stub.ts";
type ReleaseFixture = {
buildId: string;
@@ -51,6 +52,7 @@ function applicationWith(options: {
preferences: options.storage ?? memoryStorage(),
diagnostics: options.diagnostics ?? { record: () => {} },
telemetry: options.telemetry ?? { emit: () => {} },
runtimeCapabilities: createRuntimeCapabilitiesStub(),
releaseInfo: {
getCurrent: async () => current,
refresh:
+148
View File
@@ -0,0 +1,148 @@
import { describe, expect, it } from "vitest";
import { classifyGateStepResult } from "../../scripts/lib/ci-step-result.ts";
describe("CI gate step result classification", () => {
it("accepts a negative fixture only with its exact exit and diagnostic identity", () => {
expect(
classifyGateStepResult(
{
kind: "fail",
expectedExitCode: 2,
expectedDiagnosticId: "error TS2322:",
},
{
status: 2,
signal: null,
stdout: "fixture.ts(1,1): error TS2322: incompatible type\n",
stderr: "",
},
),
).toEqual({ kind: "EXPECTED_FAILURE", expectationMet: true });
});
it("rejects a negative fixture with the wrong non-zero exit code", () => {
expect(
classifyGateStepResult(
{
kind: "fail",
expectedExitCode: 2,
expectedDiagnosticId: "error TS2322:",
},
{
status: 1,
signal: null,
stdout: "fixture.ts(1,1): error TS2322: incompatible type\n",
stderr: "",
},
),
).toEqual({ kind: "UNEXPECTED_EXIT", expectationMet: false });
});
it("rejects a negative fixture without the exact case-sensitive diagnostic identity", () => {
expect(
classifyGateStepResult(
{
kind: "fail",
expectedExitCode: 2,
expectedDiagnosticId: "error TS2322:",
},
{
status: 2,
signal: null,
stdout: "fixture.ts(1,1): error ts2322: incompatible type\n",
stderr: "",
},
),
).toEqual({ kind: "UNEXPECTED_DIAGNOSTIC", expectationMet: false });
});
it("matches an exact diagnostic identity emitted on stderr", () => {
expect(
classifyGateStepResult(
{
kind: "fail",
expectedExitCode: 1,
expectedDiagnosticId: "Risk coverage failed:",
},
{
status: 1,
signal: null,
stdout: "",
stderr: "Risk coverage failed:\n- expected fixture finding\n",
},
),
).toEqual({ kind: "EXPECTED_FAILURE", expectationMet: true });
});
it.each(["ENOENT", "EACCES", "ETIMEDOUT", "ENOBUFS"])(
"never treats spawn infrastructure error %s as an expected negative fixture",
(code) => {
expect(
classifyGateStepResult(
{
kind: "fail",
expectedExitCode: 1,
expectedDiagnosticId: "fixture failed:",
},
{
status: null,
signal: null,
stdout: "",
stderr: "",
error: { code },
},
),
).toEqual({
kind: "INFRASTRUCTURE_FAILURE",
expectationMet: false,
detail: code,
});
},
);
it("treats a code-less spawn error as infrastructure failure", () => {
expect(
classifyGateStepResult(
{
kind: "fail",
expectedExitCode: 1,
expectedDiagnosticId: "fixture failed:",
},
{
status: 1,
signal: null,
stdout: "",
stderr: "fixture failed:\n",
error: {},
},
),
).toEqual({
kind: "INFRASTRUCTURE_FAILURE",
expectationMet: false,
detail: "SPAWN_ERROR",
});
});
it("treats a signal-terminated or status-less child as infrastructure failure", () => {
expect(
classifyGateStepResult(
{
kind: "fail",
expectedExitCode: 1,
expectedDiagnosticId: "fixture failed:",
},
{
status: null,
signal: "SIGTERM",
stdout: "",
stderr: "",
},
),
).toEqual({
kind: "INFRASTRUCTURE_FAILURE",
expectationMet: false,
detail: "SIGTERM",
});
});
});
@@ -5,6 +5,7 @@ import { createRuntimeIdentityRegistry } from "../../src/contracts/query-keys.ts
function scope() {
let current = true;
const lifetime = new AbortController();
return {
snapshot: {
generation: 3,
@@ -12,10 +13,12 @@ function scope() {
identities: createRuntimeIdentityRegistry({
tokenFactory: () => crypto.randomUUID(),
}),
signal: lifetime.signal,
isCurrent: () => current,
},
expire: () => {
current = false;
lifetime.abort();
},
};
}
+27 -2
View File
@@ -9,6 +9,7 @@ import {
} from "../../src/contracts/cache-invalidation.ts";
import {
createBrowserCrossContextInvalidation,
createBrowserCrossContextInvalidationFromHost,
type BroadcastChannelFacade,
type BroadcastMessageListener,
type BrowserCrossContextInvalidationDependencies,
@@ -21,7 +22,7 @@ import {
const NOW = 1_000_000;
const CACHE_EPOCH = "cache-epoch-0001";
const TOPIC = "reference-resources";
const TOPIC = "sample-topic-alpha";
const CHANNEL_NAME = "cache-invalidation-v1";
const STORAGE_PULSE_KEY = "ca-frontend:cache-invalidation:v1:pulse";
@@ -307,6 +308,30 @@ describe("cache invalidation wire contract", () => {
});
});
describe("browser cross-context host", () => {
it("does not inspect browser capabilities when no topic is installed", () => {
const reads: string[] = [];
const host = new Proxy<Record<string, unknown>>(
{},
{
get(_target, property) {
reads.push(String(property));
throw new DOMException("Capability access denied", "SecurityError");
},
},
);
expect(
createBrowserCrossContextInvalidationFromHost({
host,
cacheEpoch: CACHE_EPOCH,
topics: [],
}),
).toBeUndefined();
expect(reads).toEqual([]);
});
});
describe("browser cross-context invalidation transport", () => {
it("publishes one exact BroadcastChannel event and filters self echo", () => {
const network = new FakeBroadcastNetwork();
@@ -525,7 +550,7 @@ describe("browser cross-context invalidation transport", () => {
true,
);
expect(JSON.stringify(observations)).not.toMatch(
/sensitive-event-identifier|reference-resources|cache-epoch-0001/,
/sensitive-event-identifier|sample-topic-alpha|cache-epoch-0001/,
);
runtime.close();
});
@@ -0,0 +1,85 @@
import { describe, expect, it } from "vitest";
import {
ContractContributionError,
composeContractContributions,
type InstalledContractContribution,
} from "../../src/contracts/external-contract-runtime.ts";
import { REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION } from "../../src/features/reference-feature/contracts/reference-feature-contract-contribution.ts";
function contribution(
contributionId: string,
http: InstalledContractContribution["http"],
): InstalledContractContribution & Readonly<{ contributionId: string }> {
return Object.freeze({
...REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION,
contributionId,
http: Object.freeze([...http]),
});
}
function captureContributionError(operation: () => unknown): ContractContributionError {
try {
operation();
} catch (error) {
expect(error).toBeInstanceOf(ContractContributionError);
return error as ContractContributionError;
}
throw new Error("Expected contract composition to fail.");
}
describe("external contract contribution composition", () => {
it("allows one feature to install multiple uniquely identified contributions", () => {
const operations = REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION.http;
const composed = composeContractContributions([
contribution("reference-read-contracts", operations.slice(0, 2)),
contribution("reference-command-contracts", operations.slice(2)),
]);
expect(composed.contributions).toHaveLength(2);
expect(composed.httpByOperationId.size).toBe(3);
});
it("rejects duplicate contribution identities even across different feature entries", () => {
const first = contribution(
"reference-contracts",
REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION.http.slice(0, 1),
);
const second = contribution(
"reference-contracts",
REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION.http.slice(1),
);
const error = captureContributionError(() =>
composeContractContributions([first, second]),
);
expect(error.reason).toContain("duplicate contributionId");
});
it("maps malformed source values to the closed composition error", () => {
const malformed = {
...contribution("malformed-source", []),
source: null,
} as unknown as InstalledContractContribution;
const error = captureContributionError(() =>
composeContractContributions([malformed]),
);
expect(error.reason).toContain("source");
});
it("rejects method and body vocabulary outside the runtime protocol", () => {
const installed = REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION.http[0]!;
const malformedOperation = {
...installed,
contract: { ...installed.contract, method: "TRACE" },
} as unknown as InstalledContractContribution["http"][number];
const error = captureContributionError(() =>
composeContractContributions([
contribution("invalid-http-vocabulary", [malformedOperation]),
]),
);
expect(error.reason).toContain("method");
});
});
+283
View File
@@ -0,0 +1,283 @@
import { describe, expect, it, vi } from "vitest";
import { createContractHttpExecutor } from "../../src/adapters/http/http-execution-v3.ts";
import type { InstalledHttpContract } from "../../src/contracts/external-contract-runtime.ts";
import { COMPOSED_CONTRACT_CONTRIBUTIONS } from "../../src/features/installed-contract-contributions.ts";
const installed = (() => {
const candidate = COMPOSED_CONTRACT_CONTRIBUTIONS.httpByOperationId.get(
"LIST_REFERENCE_RESOURCES",
);
if (!candidate) throw new Error("reference list contract is not installed");
return candidate;
})();
const scope = Object.freeze({
generation: 1,
fingerprint: "scope-1",
identities: Object.freeze({}) as never,
signal: new AbortController().signal,
isCurrent: () => true,
});
const createInstalled = (() => {
const candidate = COMPOSED_CONTRACT_CONTRIBUTIONS.httpByOperationId.get(
"CREATE_REFERENCE_RESOURCE",
);
if (!candidate) throw new Error("reference create contract is not installed");
return candidate;
})();
function operation(
overrides: Readonly<{
deadlineMs?: number;
responseBody?: "REQUIRED_JSON" | "OPTIONAL_JSON" | "NONE";
}> = {},
): InstalledHttpContract<unknown, unknown, unknown> {
return {
...installed,
contract: {
...installed.contract,
responseBody: overrides.responseBody ?? installed.contract.responseBody,
},
frontend: {
...installed.frontend,
totalDeadlineMs: overrides.deadlineMs ?? installed.frontend.totalDeadlineMs,
},
};
}
async function flushMicrotasks(): Promise<void> {
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
}
describe("descriptor-driven HTTP execution lifetime", () => {
it.each([
[{}, "limit=20"],
[{ limit: "7" }, "limit=7"],
])("projects the canonical validated input %#", async (input, expectedQuery) => {
const fetcher = vi.fn(async () => Response.json([]));
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
attachCredentials: () => ({
kind: "READY",
headers: {},
credentials: "omit",
}),
fetcher,
});
await expect(executor.execute(installed, input, { scope })).resolves.toMatchObject({
kind: "SUCCESS",
});
expect(String((fetcher.mock.calls as unknown[][])[0]?.[0])).toContain(
expectedQuery,
);
});
it("contains throwing and malformed external request projections", async () => {
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
attachCredentials: () => ({
kind: "READY",
headers: {},
credentials: "omit",
}),
fetcher: vi.fn(),
});
const throwing = {
...installed,
contract: {
...installed.contract,
projectRequest: () => {
throw new Error("external descriptor defect");
},
},
};
const malformed = {
...installed,
contract: {
...installed.contract,
projectRequest: () => ({ pathValues: {}, queryEntries: [["limit"]], body: null }),
},
} as unknown as typeof installed;
await expect(executor.execute(throwing, { limit: 20 }, { scope })).resolves.toMatchObject({
kind: "CONTRACT_VIOLATION",
effect: "NOT_APPLICABLE",
});
await expect(executor.execute(malformed, { limit: 20 }, { scope })).resolves.toMatchObject({
kind: "CONTRACT_VIOLATION",
effect: "NOT_APPLICABLE",
});
});
it("preserves MAYBE_APPLIED for a malformed command response after dispatch", async () => {
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
attachCredentials: () => ({
kind: "READY",
headers: {},
credentials: "omit",
}),
fetcher: vi.fn(async () => Response.json({ malformed: true }, { status: 201 })),
});
await expect(
executor.execute(
createInstalled,
{ name: "created" },
{
scope,
intent: { intentId: "intent-1", startedBy: "USER", idempotencyKey: "key-1" },
},
),
).resolves.toMatchObject({
kind: "CONTRACT_VIOLATION",
violation: { kind: "SUCCESS_SCHEMA_INVALID" },
effect: "MAYBE_APPLIED",
});
});
it("preserves MAYBE_APPLIED when a command response arrives after its scope fence", async () => {
let current = true;
const lifetime = new AbortController();
const fencedScope = Object.freeze({
...scope,
signal: lifetime.signal,
isCurrent: () => current,
});
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
attachCredentials: () => ({
kind: "READY",
headers: {},
credentials: "omit",
}),
fetcher: vi.fn(async () => {
current = false;
lifetime.abort();
return Response.json(
{ id: "created", name: "Created" },
{ status: 201 },
);
}),
});
await expect(
executor.execute(
createInstalled,
{ name: "created" },
{
scope: fencedScope,
intent: { intentId: "intent-2", startedBy: "USER", idempotencyKey: "key-2" },
},
),
).resolves.toMatchObject({
kind: "CONTRACT_VIOLATION",
violation: { kind: "SCOPE_FENCED" },
effect: "MAYBE_APPLIED",
});
});
it("settles a credential hang at the total operation deadline", async () => {
vi.useFakeTimers();
let settled = false;
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
attachCredentials: () => new Promise(() => {}),
});
const result = executor
.execute(operation({ deadlineMs: 5 }), { limit: 20 }, { scope })
.then((outcome) => {
settled = true;
return outcome;
});
await vi.advanceTimersByTimeAsync(5);
await flushMicrotasks();
expect(settled).toBe(true);
await expect(result).resolves.toMatchObject({
kind: "TRANSPORT_FAILURE",
failure: { kind: "TIMEOUT" },
});
vi.useRealTimers();
});
it("cancels a non-cooperative retry sleep when the caller aborts", async () => {
const caller = new AbortController();
let sleepSignal: AbortSignal | undefined;
let settled = false;
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 2,
attachCredentials: () => ({
kind: "READY",
headers: {},
credentials: "omit",
}),
fetcher: vi.fn(async () =>
Response.json(
{ type: "about:blank", title: "temporary", status: 503 },
{ status: 503 },
),
),
sleep: (_ms, signal) => {
sleepSignal = signal;
return new Promise(() => {});
},
random: () => 0,
});
const result = executor
.execute(installed, { limit: 20 }, { scope, signal: caller.signal })
.then((outcome) => {
settled = true;
return outcome;
});
await vi.waitFor(() => expect(sleepSignal).toBeDefined());
caller.abort();
await flushMicrotasks();
expect(sleepSignal?.aborted).toBe(true);
expect(settled).toBe(true);
await expect(result).resolves.toMatchObject({ kind: "CANCELLED" });
});
it("treats an unreadable forbidden-body probe as a transport failure", async () => {
const body = new ReadableStream<Uint8Array>({
pull(controller) {
controller.error(new TypeError("stream failed"));
},
});
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
attachCredentials: () => ({
kind: "READY",
headers: {},
credentials: "omit",
}),
fetcher: vi.fn(async () => new Response(body, { status: 200 })),
});
await expect(
executor.execute(
operation({ responseBody: "NONE" }),
{ limit: 20 },
{ scope },
),
).resolves.toMatchObject({
kind: "TRANSPORT_FAILURE",
failure: { kind: "RESPONSE_STREAM_FAILURE" },
});
});
});
+86
View File
@@ -380,6 +380,48 @@ describe("IndexedDB runtime", () => {
});
});
it("shares one native open request across concurrent callers", async () => {
const memory = new MemoryIndexedDbFactory();
const nativeOpen = vi.spyOn(memory.factory, "open");
const runtime = createIndexedDbRuntime(dependencies(memory));
const results = await Promise.all([
runtime.open(),
runtime.open(),
runtime.open(),
]);
expect(nativeOpen).toHaveBeenCalledOnce();
expect(results).toEqual([
{ ok: true, value: undefined },
{ ok: true, value: undefined },
{ ok: true, value: undefined },
]);
});
it("isolates a caller abort from the shared native open request", async () => {
const memory = new MemoryIndexedDbFactory();
memory.blockNextOpen();
const nativeOpen = vi.spyOn(memory.factory, "open");
const runtime = createIndexedDbRuntime(dependencies(memory));
const cancelledCaller = new AbortController();
const surviving = runtime.open();
const cancelled = runtime.open(cancelledCaller.signal);
await Promise.resolve();
cancelledCaller.abort();
expect(await cancelled).toMatchObject({
ok: false,
error: { code: "ABORTED", operation: "INDEXEDDB_OPEN" },
});
expect(nativeOpen).toHaveBeenCalledOnce();
memory.releaseBlockedOpen();
expect(await surviving).toEqual({ ok: true, value: undefined });
expect(runtime.getStatus()).toEqual({ kind: "READY", schemaVersion: 1 });
});
it("times out a blocked upgrade, then closes a late successful connection", async () => {
const memory = new MemoryIndexedDbFactory();
memory.blockNextOpen();
@@ -422,6 +464,50 @@ describe("IndexedDB runtime", () => {
});
});
it("retains native open ownership after a blocked timeout until late success", async () => {
const memory = new MemoryIndexedDbFactory();
memory.blockNextOpen();
const nativeOpen = vi.spyOn(memory.factory, "open");
let timeout: (() => void) | undefined;
const runtime = createIndexedDbRuntime(
dependencies(memory, {
blockedTimeoutMs: 25,
scheduler: {
setTimeout: (callback) => {
timeout = callback;
return "blocked-timer";
},
clearTimeout: () => undefined,
},
}),
);
const first = runtime.open();
await Promise.resolve();
timeout?.();
await expect(first).resolves.toMatchObject({
ok: false,
error: { code: "BLOCKED" },
});
const second = runtime.open();
await Promise.resolve();
const nativeOpenCountBeforeLateSuccess = nativeOpen.mock.calls.length;
memory.releaseBlockedOpen();
await expect(second).resolves.toMatchObject({
ok: false,
error: { code: "BLOCKED" },
});
await Promise.resolve();
expect(nativeOpenCountBeforeLateSuccess).toBe(1);
expect(memory.isConnectionClosed()).toBe(true);
expect(runtime.getStatus()).toEqual({
kind: "CLOSED",
reason: "NOT_OPENED",
});
});
it("closes immediately on versionchange and isolates listener failures", async () => {
const memory = new MemoryIndexedDbFactory();
const onVersionChange = vi.fn(() => {
+58
View File
@@ -0,0 +1,58 @@
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
import { assertMatchesJsonSchema } from "../../scripts/lib/json-schema.ts";
async function json(path: string): Promise<unknown> {
return JSON.parse(await readFile(path, "utf8")) as unknown;
}
describe("checked-in JSON Schema execution", () => {
it("accepts a build manifest and rejects undeclared output fields", async () => {
const schema = await json("schemas/artifacts/build-manifest.schema.json");
const manifest = {
schemaVersion: 1,
buildId: "build-1",
commitSha: "commit-1",
releaseId: "release-1",
moduleInventoryHash: "inventory-hash",
generatedAt: "2026-08-01T00:00:00.000Z",
buildContext: {
nodeVersion: "v24.11.0",
packageManagerVersion: "11.17.0",
runnerImage: "test-runner",
sourceDateEpoch: null,
},
outputs: {
directory: "dist",
viteManifest: "dist/.vite/manifest.json",
moduleInventory: "artifacts/quality/vite-module-inventory.json",
routeChunks: { home: "assets/home.js" },
runtimeConfigSchema: "dist/runtime-config.schema.json",
},
};
expect(() =>
assertMatchesJsonSchema(schema, manifest, "build manifest"),
).not.toThrow();
expect(() =>
assertMatchesJsonSchema(
schema,
{ ...manifest, undocumented: true },
"build manifest",
),
).toThrow(/checked-in JSON Schema/u);
});
it("resolves local schema definitions in the recipe catalog", async () => {
const [schema, catalog] = await Promise.all([
json("schemas/config/frontend-capability-recipes.schema.json"),
json("config/recipes/frontend-capability-recipes.json"),
]);
expect(() =>
assertMatchesJsonSchema(schema, catalog, "optional recipe catalog"),
).not.toThrow();
});
});
+12
View File
@@ -28,6 +28,15 @@ describe("installed route registry", () => {
});
});
it("treats every non-public route as explicitly session-required", () => {
expect(ROUTE_REGISTRY.REFERENCE_RESOURCE_LIST.access).toBe(
"session-required",
);
expect(
decideRouteAccess("REFERENCE_RESOURCE_LIST", "unauthenticated"),
).toEqual({ allowed: false, action: "show-sign-in" });
});
it("bounds automatic redirects by pair and maximum hops", () => {
const guard = createRedirectLoopGuard(2);
expect(guard.allow("/private", "/signin")).toBe(true);
@@ -36,5 +45,8 @@ describe("installed route registry", () => {
expect(guard.allow("/signin", "/continue")).toBe(true);
expect(guard.allow("/continue", "/final")).toBe(false);
expect(guard.hopCount).toBe(2);
guard.reset();
expect(guard.allow("/unrelated", "/canonical-unrelated")).toBe(true);
});
});
+81
View File
@@ -537,6 +537,87 @@ describe("OPFS dedicated worker runtime", () => {
});
describe("OPFS worker client lifecycle", () => {
it("rejects a colliding concurrent request id without replacing the first RPC", async () => {
let listener: ((event: MessageEvent<unknown>) => void) | undefined;
const posted: OpfsWorkerRequest[] = [];
const worker: OpfsWorkerLike = {
postMessage(message) {
posted.push(message);
},
addEventListener(_type, next) {
listener = next;
},
removeEventListener() {
listener = undefined;
},
};
const gateway = createOpfsWorkerGateway({
worker,
policy: runtimePolicy,
createRequestId: () => "request_collision_1234",
});
const first = gateway.capabilities();
const second = gateway.capabilities();
expect(posted).toHaveLength(1);
listener?.({
data: {
requestId: "request_collision_1234",
ok: true,
value: {
available: true,
dedicatedWorkerRequired: true,
crossContextMutationLockAvailable: true,
synchronousAccessHandleAvailable: true,
},
},
} as MessageEvent<unknown>);
const secondResult = await second;
gateway.close();
await expect(first).resolves.toMatchObject({ ok: true });
expect(secondResult).toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
});
it("rejects pending RPCs immediately when the worker reports a fatal event", async () => {
let failureListener: ((event: Event) => void) | undefined;
const worker: OpfsWorkerLike = {
postMessage() {},
addEventListener() {},
removeEventListener() {},
addFailureEventListener(listener) {
failureListener = listener;
},
removeFailureEventListener() {
failureListener = undefined;
},
};
const gateway = createOpfsWorkerGateway({
worker,
policy: runtimePolicy,
createRequestId: () => "request_crash_1234",
});
const pending = gateway.capabilities();
expect(failureListener).toBeTypeOf("function");
if (!failureListener) {
gateway.close();
return;
}
failureListener(new Event("error"));
await expect(pending).resolves.toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
await expect(gateway.capabilities()).resolves.toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
});
it("scopes the open signal to verification and leaves the acquired source readable", async () => {
let listener:
| ((event: MessageEvent<unknown>) => void)
@@ -6,6 +6,7 @@ import { createRuntimeIdentityRegistry } from "../../src/contracts/query-keys.ts
function scope() {
let current = true;
const lifetime = new AbortController();
return {
snapshot: {
generation: 1,
@@ -13,10 +14,12 @@ function scope() {
identities: createRuntimeIdentityRegistry({
tokenFactory: () => crypto.randomUUID(),
}),
signal: lifetime.signal,
isCurrent: () => current,
},
expire: () => {
current = false;
lifetime.abort();
},
};
}
+90
View File
@@ -0,0 +1,90 @@
import { describe, expect, it, vi } from "vitest";
import { createOptionalRuntimeHost } from "../../src/bootstrap/optional-runtime-host.ts";
import type { ResolvedRuntimeCapabilities } from "../../src/contracts/runtime-capabilities.ts";
import type {
ServiceWorkerRuntimeHost,
ServiceWorkerStartOutcome,
} from "../../src/contracts/service-worker.ts";
const capabilities: ResolvedRuntimeCapabilities = Object.freeze({
realtime: Object.freeze([]),
webWorkers: Object.freeze([]),
serviceWorker: null,
serviceWorkerDisabledCleanup: false,
offlineCommands: null,
});
function deferred<Value>() {
let resolve!: (value: Value) => void;
const promise = new Promise<Value>((settle) => {
resolve = settle;
});
return { promise, resolve } as const;
}
describe("optional runtime host", () => {
it("keeps stop-before-start terminal without acquiring browser resources", async () => {
const serviceWorker: ServiceWorkerRuntimeHost = {
start: vi.fn(
async (): Promise<ServiceWorkerStartOutcome> => ({
kind: "ACTIVE",
buildId: "build-1",
}),
),
requestActivation: vi.fn(),
resetOwnedCaches: vi.fn(),
stop: vi.fn(async () => {}),
};
const addEventListener = vi.fn();
const runtime = createOptionalRuntimeHost({
capabilities,
routerBasePath: "/",
buildId: "build-1",
serviceWorkerHost: serviceWorker,
browserLifecycleHost: {
addEventListener,
removeEventListener: vi.fn(),
},
});
await runtime.stop();
await runtime.startAfterMount();
expect(runtime.browserLifecycle()).toBeNull();
expect(runtime.health().serviceWorker).toBe("DISABLED");
expect(addEventListener).not.toHaveBeenCalled();
expect(serviceWorker.start).not.toHaveBeenCalled();
expect(serviceWorker.stop).not.toHaveBeenCalled();
});
it("serializes stop behind an in-flight start and suppresses late health", async () => {
const start = deferred<ServiceWorkerStartOutcome>();
const serviceWorker: ServiceWorkerRuntimeHost = {
start: vi.fn(() => start.promise),
requestActivation: vi.fn(),
resetOwnedCaches: vi.fn(),
stop: vi.fn(async () => {}),
};
const runtime = createOptionalRuntimeHost({
capabilities,
routerBasePath: "/",
buildId: "build-1",
serviceWorkerHost: serviceWorker,
browserLifecycleHost: {
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
},
});
const starting = runtime.startAfterMount();
const stopping = runtime.stop();
start.resolve({ kind: "ACTIVE", buildId: "build-1" });
await Promise.all([starting, stopping]);
expect(serviceWorker.stop).toHaveBeenCalledTimes(1);
expect(runtime.health().serviceWorker).toBe("DISABLED");
expect(runtime.browserLifecycle()).toBeNull();
});
});
@@ -0,0 +1,26 @@
import { describe, expect, it } from "vitest";
import { indexInvalidationRegistry } from "../../src/contracts/query-invalidation.ts";
describe("query invalidation registry", () => {
it.each([
{
label: "topic",
registry: {
topics: ["orders\u0000private"],
namespaces: ["orders"],
edges: [{ topicId: "orders\u0000private", namespace: "orders" }],
},
},
{
label: "namespace",
registry: {
topics: ["orders"],
namespaces: ["orders\u001fprivate"],
edges: [{ topicId: "orders", namespace: "orders\u001fprivate" }],
},
},
])("rejects control characters in a registry $label", ({ registry }) => {
expect(() => indexInvalidationRegistry(registry)).toThrow(/is invalid/u);
});
});
+31
View File
@@ -0,0 +1,31 @@
import { describe, expect, it } from "vitest";
import {
BOOT_JSON_POLICIES,
readBoundedBootJson,
} from "../../src/bootstrap/read-bounded-boot-json.ts";
describe("bounded boot JSON admission", () => {
it("does not call fetch when the caller signal is already aborted", async () => {
const caller = new AbortController();
caller.abort();
let fetchCalls = 0;
const outcome = await readBoundedBootJson(
"/config.json",
BOOT_JSON_POLICIES.RUNTIME_CONFIG,
{
signal: caller.signal,
fetcher: async () => {
fetchCalls += 1;
return new Response("{}", {
headers: { "content-type": "application/json" },
});
},
},
);
expect(fetchCalls).toBe(0);
expect(outcome).toEqual({ ok: false, failure: "FETCH_FAILED" });
});
});
+4 -4
View File
@@ -80,7 +80,7 @@ export const TEST_MAPPER: InstalledBoundaryMapper = Object.freeze({
mapperVersion: 1,
inputSchemaId: "ReferenceRealtimePayload",
outputContractId: "ReferenceRealtimeEvent",
owner: "reference-feature",
owner: "sample-owner",
maxOutputItems: 1,
map(input) {
if (
@@ -113,7 +113,7 @@ const snapshotOperation: ApiOperation = Object.freeze({
requestSource: "none",
requestSchema: "NoRequest",
responseSchema: "ReferenceRealtimeCheckpoint",
owner: "reference-feature",
owner: "sample-owner",
contractVersion: 2,
protocol: "REST",
semantics: "QUERY",
@@ -161,7 +161,7 @@ export function createTestRealtimeRegistry(
} as const);
const eventType: RealtimeEventTypeRegistration = {
id: EVENT_TYPE,
owner: "reference-feature",
owner: "sample-owner",
payloadSchemaId: "ReferenceRealtimePayload",
mapperId: "ReferenceRealtimeMapper",
effectProfileId: EFFECT_PROFILE_ID,
@@ -170,7 +170,7 @@ export function createTestRealtimeRegistry(
const stream: RealtimeStreamRegistration = {
id: STREAM_ID,
protocol: "REALTIME_EVENT_V1",
owner: "reference-feature",
owner: "sample-owner",
scope: "ACCOUNT_BOUND",
primaryTransport: "SSE",
endpointId: ENDPOINT_ID,
@@ -51,7 +51,7 @@ describe("realtime policy registry", () => {
limits: { maxQueueEvents: TEST_LIMITS.maxQueueEvents },
});
expect(registry.findEventType(EVENT_TYPE)?.owner).toBe(
"reference-feature",
"sample-owner",
);
expect(
registry.findStreamEventType(STREAM_ID, EVENT_TYPE)?.id,
+129
View File
@@ -0,0 +1,129 @@
import { describe, expect, it } from "vitest";
import {
parseBuildManifestArtifact,
parseReleaseArtifact,
parseRuntimeConfigArtifact,
projectReleaseTokens,
} from "../../scripts/contracts/release-artifacts.ts";
const EMPTY_CONTRACT_SET_DIGEST =
"sha256:ad6aab71fea6a9ff87cbd170b984b339965afc90d85bb57f87801c9e0c020da2";
const releaseV2 = {
schemaVersion: 2,
appVersion: "0.1.0",
buildId: "build-a",
commitSha: "abc1234",
configSchemaVersion: "2.0",
assetManifestHash: "asset-hash",
releaseId: "release-a",
builtAt: "2026-08-01T00:00:00.000Z",
routeChunks: { "route-home": "assets/home.js" },
contractSet: {
setAlgorithm: "CA_CONTRACT_SET_V1",
setDigest: EMPTY_CONTRACT_SET_DIGEST,
packages: [],
},
} as const;
const buildManifest = {
schemaVersion: 1,
buildId: "build-a",
commitSha: "abc1234",
releaseId: "release-a",
moduleInventoryHash: "inventory-hash",
generatedAt: "2026-08-01T00:00:00.000Z",
buildContext: {
nodeVersion: "v24.14.0",
packageManagerVersion: "11.17.0",
runnerImage: "linux-x64",
sourceDateEpoch: null,
},
outputs: {
directory: "dist",
viteManifest: "dist/.vite/manifest.json",
moduleInventory: "artifacts/quality/vite-module-inventory.json",
routeChunks: { "route-home": "assets/home.js" },
runtimeConfigSchema: "dist/runtime-config.schema.json",
},
} as const;
describe("release artifact contracts", () => {
it("projects the nested V2 contract-set digest without a legacy scalar", () => {
const release = parseReleaseArtifact(releaseV2);
expect(projectReleaseTokens(release)).toMatchObject({
schemaVersion: 2,
buildId: "build-a",
contractSetDigest: EMPTY_CONTRACT_SET_DIGEST,
});
expect(projectReleaseTokens(release)).not.toHaveProperty(
"apiContractVersion",
);
});
it("projects the legacy scalar only for V1", () => {
const { contractSet: _contractSet, ...releaseWithoutContractSet } = releaseV2;
void _contractSet;
const release = parseReleaseArtifact({
...releaseWithoutContractSet,
schemaVersion: 1,
configSchemaVersion: "1",
apiContractVersion: "1.4.0",
});
expect(projectReleaseTokens(release)).toMatchObject({
schemaVersion: 1,
apiContractVersion: "1.4.0",
});
expect(projectReleaseTokens(release)).not.toHaveProperty(
"contractSetDigest",
);
});
it("rejects a V2 release carrying the removed scalar", () => {
expect(() =>
parseReleaseArtifact({
...releaseV2,
apiContractVersion: "1",
}),
).toThrow();
});
it("accepts the exact build manifest emitted by the generator", () => {
expect(parseBuildManifestArtifact(buildManifest)).toEqual(buildManifest);
});
it("rejects unknown build manifest output fields", () => {
expect(() =>
parseBuildManifestArtifact({
...buildManifest,
outputs: { ...buildManifest.outputs, unexpected: "value" },
}),
).toThrow();
});
it("accepts Runtime Config V2 without a legacy API scalar", () => {
expect(
parseRuntimeConfigArtifact({
APP_ENV: "local",
API_BASE_URL: "http://localhost:8080/",
REQUEST_TIMEOUT_MS: 10_000,
MAX_RETRY_ATTEMPTS: 2,
TELEMETRY_ENABLED: false,
AUTH_MODE: "demo",
CONFIG_SCHEMA_VERSION: "2.0",
RELEASE_MANIFEST_URL: "/release-manifest.json",
BUILD_ID: "build-a",
RELEASE_ID: "release-a",
CAPABILITY_OVERRIDES: {
REALTIME: "DEFAULT",
WEB_WORKER: "DEFAULT",
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
},
}),
).not.toHaveProperty("API_CONTRACT_VERSION");
});
});
+6 -2
View File
@@ -6,8 +6,12 @@ import {
} from "../../src/contracts/release-tokens.ts";
describe("release coherence", () => {
it("owns all eight release tokens and keeps builtAt diagnostic-only", () => {
expect(Object.keys(RELEASE_TOKEN_REGISTRY)).toHaveLength(8);
it("owns all nine release tokens and keeps builtAt diagnostic-only", () => {
// §5.2 adds contractSetDigest beside the legacy apiContractVersion scalar.
expect(Object.keys(RELEASE_TOKEN_REGISTRY)).toHaveLength(9);
expect(RELEASE_TOKEN_REGISTRY.contractSetDigest.compatibilityRole).toContain(
"multi-package",
);
expect(RELEASE_TOKEN_REGISTRY.builtAt.compatibilityRole).toContain(
"never cache identity",
);
+45 -13
View File
@@ -1,11 +1,49 @@
import { describe, expect, it } from "vitest";
import { defineRestOperation } from "../../src/contracts/api-operations.ts";
import {
createRestProviderProfile,
resolveRestSecurityProfiles,
validateRestProfileBindings,
} from "../../src/contracts/rest-profiles.ts";
import { REFERENCE_FEATURE_CONTRACT } from "../../src/features/reference-feature/contracts/reference-feature-contract.ts";
/**
* §24.12: the common REST profile contract must stay independently verifiable
* after the sample feature is removed, so this suite owns its own operation
* fixture instead of importing an installed feature contract.
*/
const SAMPLE_COMMAND = defineRestOperation({
method: "POST",
path: "/api/sample-resources",
operationId: "CREATE_SAMPLE_RESOURCE",
auth: "external-session",
timeoutMs: null,
idempotency: "keyed",
retry: "runtime",
requestSource: "body",
requestSchema: "SampleCommand",
responseSchema: "SamplePayload",
owner: "platform-test-fixture",
contractVersion: 2,
protocol: "REST",
semantics: "COMMAND",
replayPolicy: "KEYED_COMMAND",
idempotencyKeyPolicy: "REQUIRED",
mapperId: "SampleMapper",
successStatuses: [200, 201],
responseMediaTypes: ["application/json"],
maxResponseBytes: 32_768,
providerId: "PRIMARY_API",
authProfileId: "REFERENCE_EXTERNAL_BEARER",
csrfProfileId: "NO_CSRF_BEARER",
pathSchema: "NoRequest",
pathParameterNames: [],
maxEncodedSearchBytes: 0,
});
const SAMPLE_OPERATIONS = Object.freeze({
CREATE_SAMPLE_RESOURCE: SAMPLE_COMMAND,
});
describe("REST provider/auth/CSRF profiles", () => {
it("preserves the provider prefix and rejects unsafe endpoint forms", () => {
@@ -33,10 +71,8 @@ describe("REST provider/auth/CSRF profiles", () => {
});
it("resolves bearer auth to omit credentials and no CSRF", () => {
const operation =
REFERENCE_FEATURE_CONTRACT.apiOperations.CREATE_REFERENCE_RESOURCE;
const resolved = resolveRestSecurityProfiles(
operation,
SAMPLE_COMMAND,
createRestProviderProfile("PRIMARY_API", "https://api.test", ["omit"]),
);
expect(resolved).toMatchObject({
@@ -49,18 +85,14 @@ describe("REST provider/auth/CSRF profiles", () => {
});
});
it("validates every installed reference profile binding as a set", () => {
it("validates every installed profile binding as a set", () => {
expect(
validateRestProfileBindings(
REFERENCE_FEATURE_CONTRACT.apiOperations,
{ PRIMARY_API: ["omit"] },
),
validateRestProfileBindings(SAMPLE_OPERATIONS, {
PRIMARY_API: ["omit"],
}),
).toBe(true);
expect(() =>
validateRestProfileBindings(
REFERENCE_FEATURE_CONTRACT.apiOperations,
{},
),
validateRestProfileBindings(SAMPLE_OPERATIONS, {}),
).toThrow("Unregistered REST provider binding");
});
});
+161 -10
View File
@@ -4,6 +4,8 @@ import {
createRuntimeAdapters,
createRuntimeHttpClient,
} from "../../src/bootstrap/runtime-adapters.ts";
import { QUERY_REGISTRY } from "../../src/features/installed-feature-contracts.ts";
import { REFERENCE_FEATURE_ID } from "../../src/features/reference-feature/contracts/reference-feature-contract.ts";
import { TEST_HTTP_CONTRACT } from "../helpers/http-contract-fixture.ts";
type Runtime = Parameters<typeof createRuntimeAdapters>[0]["runtime"];
@@ -18,9 +20,15 @@ const runtime: Runtime = {
REQUEST_TIMEOUT_MS: 4321,
MAX_RETRY_ATTEMPTS: 0,
RELEASE_MANIFEST_URL: "/release-manifest.json",
CONFIG_SCHEMA_VERSION: "1",
API_CONTRACT_VERSION: "1",
CONFIG_SCHEMA_VERSION: "2.0",
CAPABILITY_OVERRIDES: {
REALTIME: "DEFAULT",
WEB_WORKER: "DEFAULT",
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
},
},
configSchema: "V2",
build: {
buildId: "build-a",
commitSha: "abc123",
@@ -30,12 +38,16 @@ const runtime: Runtime = {
validationDurationMs: 0,
};
const release: Release = {
schemaVersion: 1,
schemaVersion: 2,
appVersion: "0.1.0",
buildId: "build-a",
commitSha: "abc123",
configSchemaVersion: "1",
apiContractVersion: "1",
configSchemaVersion: "2.0",
contractSet: {
setAlgorithm: "CA_CONTRACT_SET_V1",
setDigest: `sha256:${"0".repeat(64)}`,
packages: [],
},
assetManifestHash: "hash-a",
releaseId: "release-a",
builtAt: "2026-07-25T00:00:00Z",
@@ -67,6 +79,67 @@ describe("runtime adapter composition", () => {
adapters.infrastructure.dispose();
});
it("executes installed feature HTTP through the composed contract registry", async () => {
const fetcher = vi.fn(async () =>
Response.json([{ id: "reference-1", name: "Direct contract payload" }]),
);
const adapters = await createRuntimeAdapters({
runtime,
release,
host: {},
fetcher,
});
await adapters.outputPorts.session.beginSignIn();
await vi.waitFor(() =>
expect(adapters.infrastructure.serverStateScope.getPhase()).toBe("READY"),
);
await expect(
adapters.featureInputs[REFERENCE_FEATURE_ID].listResources({ limit: 20 }),
).resolves.toEqual({
ok: true,
value: [
{
resourceId: "reference-1",
title: "Direct contract payload",
createdAt: null,
},
],
});
expect(fetcher).toHaveBeenCalledWith(
"http://localhost:8080/api/reference-resources?limit=20",
expect.objectContaining({
method: "GET",
redirect: "error",
cache: "no-store",
}),
);
adapters.infrastructure.dispose();
});
it("replaces the QueryClient and coordinator for each session generation", async () => {
const adapters = await createRuntimeAdapters({ runtime, release, host: {} });
const previousClient = adapters.infrastructure.queryClient;
const previousCoordinator = adapters.infrastructure.queryInvalidation;
const invalidatePrevious = vi.spyOn(previousClient, "invalidateQueries");
await adapters.outputPorts.session.beginSignIn();
await vi.waitFor(() =>
expect(adapters.infrastructure.queryClient).not.toBe(previousClient),
);
expect(adapters.infrastructure.queryInvalidation).not.toBe(
previousCoordinator,
);
const topic = Object.values(QUERY_REGISTRY)[0]?.invalidationTopic;
if (!topic) throw new Error("expected an installed invalidation topic");
await previousCoordinator.invalidate([topic]);
expect(invalidatePrevious).not.toHaveBeenCalled();
adapters.infrastructure.dispose();
});
it("does not fail boot when Web Storage capability getters throw", async () => {
const host: Record<string, unknown> = {};
Object.defineProperties(host, {
@@ -108,6 +181,73 @@ describe("runtime adapter composition", () => {
expect(adapters.outputPorts.session.getState()).toBe("integration-failed");
});
it("reports the static selection when no capability override disables it", async () => {
const adapters = await createRuntimeAdapters({ runtime, release, host: {} });
const snapshot = adapters.outputPorts.runtimeCapabilities.getSnapshot();
expect(snapshot.map((status) => status.capabilityId)).toEqual([
"REALTIME",
"WEB_WORKER",
"SERVICE_WORKER",
"OFFLINE_COMMANDS",
]);
expect(snapshot.every((status) => status.override === "DEFAULT")).toBe(true);
});
it("carries a disabling override into the capability snapshot", async () => {
const adapters = await createRuntimeAdapters({
runtime: {
...runtime,
config: {
...runtime.config,
CAPABILITY_OVERRIDES: {
...runtime.config.CAPABILITY_OVERRIDES,
SERVICE_WORKER: "DISABLED",
},
},
},
release,
host: {},
});
const serviceWorker = adapters.outputPorts.runtimeCapabilities
.getSnapshot()
.find((status) => status.capabilityId === "SERVICE_WORKER");
expect(serviceWorker?.override).toBe("DISABLED");
expect(serviceWorker?.active).toBe(0);
});
it("states contract identity as a digest for a V2 release manifest", async () => {
const adapters = await createRuntimeAdapters({ runtime, release, host: {} });
const current = await adapters.outputPorts.releaseInfo.getCurrent();
expect(current.contractSetDigest).toBe(release.contractSet?.setDigest);
expect(current.apiContractVersion).toBeUndefined();
expect(current).not.toHaveProperty("contractSet");
});
it("states contract identity as the legacy scalar for a V1 release manifest", async () => {
const adapters = await createRuntimeAdapters({
runtime,
release: {
...release,
schemaVersion: 1,
contractSet: null,
legacyApiContractVersion: "1.4",
},
host: {},
});
const current = await adapters.outputPorts.releaseInfo.getCurrent();
expect(current.apiContractVersion).toBe("1.4");
expect(current.contractSetDigest).toBeUndefined();
expect(current).not.toHaveProperty("legacyApiContractVersion");
});
it("refetches the active release manifest with no-store semantics", async () => {
const activeRelease = {
...release,
@@ -115,7 +255,11 @@ describe("runtime adapter composition", () => {
releaseId: "release-b",
routeChunks: { "route-home": "assets/home-b.js" },
};
const fetcher = vi.fn(async () => Response.json(activeRelease));
const fetcher = vi.fn(async () =>
new Response(JSON.stringify(activeRelease), {
headers: { "content-type": "application/json" },
}),
);
const adapters = await createRuntimeAdapters({
runtime,
release,
@@ -127,10 +271,17 @@ describe("runtime adapter composition", () => {
buildId: "build-b",
releaseId: "release-b",
});
expect(fetcher).toHaveBeenCalledWith("/release-manifest.json", {
cache: "no-store",
headers: { Accept: "application/json" },
});
expect(fetcher).toHaveBeenCalledWith(
"/release-manifest.json",
expect.objectContaining({
method: "GET",
cache: "no-store",
credentials: "same-origin",
redirect: "error",
referrerPolicy: "no-referrer",
headers: { Accept: "application/json" },
}),
);
});
it("injects runtime timeout and max-attempt policy into HTTP execution", async () => {
@@ -0,0 +1,104 @@
import { describe, expect, it } from "vitest";
import {
describeRuntimeCapabilities,
type CapabilityOverrideMap,
type InstalledRuntimeCapabilities,
} from "../../src/contracts/runtime-capabilities.ts";
import { SERVICE_WORKER_SCRIPT_PATH } from "../../src/contracts/service-worker.ts";
const DEFAULTS: CapabilityOverrideMap = Object.freeze({
REALTIME: "DEFAULT",
WEB_WORKER: "DEFAULT",
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
});
const EMPTY: InstalledRuntimeCapabilities = Object.freeze({
realtime: Object.freeze([]),
webWorkers: Object.freeze([]),
serviceWorker: null,
offlineCommands: null,
});
const SELECTED: InstalledRuntimeCapabilities = Object.freeze({
realtime: Object.freeze([]),
webWorkers: Object.freeze([]),
serviceWorker: Object.freeze({
mode: "ACTIVE" as const,
scriptPath: SERVICE_WORKER_SCRIPT_PATH,
handlers: Object.freeze(["PWA_STATIC_ASSETS" as const]),
}),
offlineCommands: null,
});
describe("runtime capability snapshot", () => {
it("describes every capability id in a fixed order", () => {
const snapshot = describeRuntimeCapabilities(EMPTY, DEFAULTS);
expect(snapshot.map((status) => status.capabilityId)).toEqual([
"REALTIME",
"WEB_WORKER",
"SERVICE_WORKER",
"OFFLINE_COMMANDS",
]);
});
it("reports nothing selected and nothing active for an empty selection", () => {
const snapshot = describeRuntimeCapabilities(EMPTY, DEFAULTS);
for (const status of snapshot) {
expect(status.selected).toBe(0);
expect(status.active).toBe(0);
expect(status.override).toBe("DEFAULT");
}
});
it("keeps a selected capability active while the override is DEFAULT", () => {
const snapshot = describeRuntimeCapabilities(SELECTED, DEFAULTS);
const serviceWorker = snapshot.find(
(status) => status.capabilityId === "SERVICE_WORKER",
);
expect(serviceWorker).toMatchObject({
selected: 1,
active: 1,
override: "DEFAULT",
});
});
it("keeps a disabled capability selected but not active", () => {
const snapshot = describeRuntimeCapabilities(SELECTED, {
...DEFAULTS,
SERVICE_WORKER: "DISABLED",
});
const serviceWorker = snapshot.find(
(status) => status.capabilityId === "SERVICE_WORKER",
);
expect(serviceWorker).toMatchObject({
selected: 1,
active: 0,
override: "DISABLED",
});
});
it("cannot activate a capability that was never selected", () => {
const snapshot = describeRuntimeCapabilities(EMPTY, {
...DEFAULTS,
REALTIME: "DEFAULT",
});
const realtime = snapshot.find(
(status) => status.capabilityId === "REALTIME",
);
expect(realtime).toMatchObject({ selected: 0, active: 0 });
});
it("returns a frozen snapshot and frozen entries", () => {
const snapshot = describeRuntimeCapabilities(EMPTY, DEFAULTS);
expect(Object.isFrozen(snapshot)).toBe(true);
expect(snapshot.every((status) => Object.isFrozen(status))).toBe(true);
});
});
+153 -8
View File
@@ -1,15 +1,17 @@
import { describe, expect, it, vi } from "vitest";
import { createServerStateScopeRuntime } from "../../src/adapters/query-cache/server-state-scope-runtime.ts";
import type { ClientScopeLifecycleEvent } from "../../src/contracts/server-state-scope.ts";
describe("server-state session generation runtime", () => {
it("fences the old generation before reset and publishes the new scope after reset", async () => {
it("fences the old generation synchronously and publishes READY after the reset order", async () => {
let sessionListener: () => void = () => {};
let completeReset: () => void = () => {};
const reset = new Promise<void>((resolve) => {
completeReset = resolve;
});
const resetLocal = vi.fn(() => reset);
const participantOrder: string[] = [];
let tokenSequence = 0;
const runtime = createServerStateScopeRuntime({
session: {
@@ -19,34 +21,177 @@ describe("server-state session generation runtime", () => {
},
},
queryInvalidation: {
invalidate: async () => {},
beginMutation: () => ({ release: async () => {} }),
resetLocal,
dispose() {},
},
tokenFactory: () => `scope-token-${String(tokenSequence++).padStart(8, "0")}`,
participants: [
{
order: 9,
label: "realtime",
close: () => void participantOrder.push("realtime"),
},
{
order: 4,
label: "admission",
close: () => void participantOrder.push("admission"),
},
],
tokenFactory: () =>
`scope-token-${String(tokenSequence++).padStart(8, "0")}`,
});
const changed = vi.fn();
const lifecycle: ClientScopeLifecycleEvent[] = [];
runtime.subscribe(changed);
runtime.subscribeLifecycle((event) => lifecycle.push(event));
const before = runtime.getSnapshot();
const identity = before.identities.intern({ id: "private" });
identity.acquire();
sessionListener();
// §10.6 steps 1-3 are synchronous: the old snapshot is immediately stale,
// FENCED is published, and subscribers are notified before any await.
expect(before.isCurrent()).toBe(false);
expect(runtime.getSnapshot()).toBe(before);
expect(changed).not.toHaveBeenCalled();
expect(before.signal.aborted).toBe(true);
expect(runtime.getPhase()).toBe("FENCED");
expect(lifecycle[0]).toEqual({ kind: "FENCED", previousGeneration: 1 });
expect(changed).toHaveBeenCalledOnce();
// Nothing may read as current while the scope is fenced.
expect(runtime.getSnapshot().isCurrent()).toBe(false);
await vi.waitFor(() => expect(resetLocal).toHaveBeenCalledOnce());
// Participants close in §10.6 step order, before the local cache reset.
expect(participantOrder).toEqual(["admission", "realtime"]);
completeReset();
await vi.waitFor(() => expect(changed).toHaveBeenCalledOnce());
await vi.waitFor(() => expect(runtime.getPhase()).toBe("READY"));
const after = runtime.getSnapshot();
expect(after.generation).toBe(before.generation + 1);
expect(after.fingerprint).not.toBe(before.fingerprint);
expect(after.isCurrent()).toBe(true);
expect(after.signal.aborted).toBe(false);
expect(after.signal).not.toBe(before.signal);
expect(before.identities.inspect().closed).toBe(true);
expect(lifecycle.at(-1)).toMatchObject({ kind: "READY" });
expect(changed).toHaveBeenCalledTimes(2);
runtime.dispose();
expect(runtime.getPhase()).toBe("DISPOSED");
expect(after.identities.inspect().closed).toBe(true);
expect(after.isCurrent()).toBe(false);
});
it("does not let a throwing snapshot subscriber prevent reset", async () => {
let sessionListener: () => void = () => {};
const resetLocal = vi.fn(async () => {});
const runtime = createServerStateScopeRuntime({
session: {
subscribe(listener) {
sessionListener = listener;
return () => {};
},
},
queryInvalidation: {
resetLocal,
},
tokenFactory: () => "scope-listener-token-0001",
});
runtime.subscribe(() => {
throw new Error("subscriber defect");
});
expect(() => sessionListener()).not.toThrow();
await vi.waitFor(() => expect(resetLocal).toHaveBeenCalledOnce());
await vi.waitFor(() => expect(runtime.getPhase()).toBe("READY"));
});
it("remains failed when a mandatory participant cannot close", async () => {
let sessionListener: () => void = () => {};
const resetLocal = vi.fn(async () => {});
const runtime = createServerStateScopeRuntime({
session: {
subscribe(listener) {
sessionListener = listener;
return () => {};
},
},
queryInvalidation: {
resetLocal,
},
participants: [
{
order: 4,
label: "mandatory-admission",
close: async () => {
throw new Error("close failed");
},
},
],
tokenFactory: () => "scope-participant-token-0001",
});
sessionListener();
await vi.waitFor(() => expect(resetLocal).toHaveBeenCalledOnce());
await vi.waitFor(() =>
expect(runtime.getPhase() as string).toBe("FAILED"),
);
expect(runtime.getSnapshot().isCurrent()).toBe(false);
});
it("remains failed when local cache reset rejects", async () => {
let sessionListener: () => void = () => {};
const runtime = createServerStateScopeRuntime({
session: {
subscribe(listener) {
sessionListener = listener;
return () => {};
},
},
queryInvalidation: {
resetLocal: async () => {
throw new Error("reset failed");
},
},
tokenFactory: () => "scope-reset-token-0000001",
});
sessionListener();
await vi.waitFor(() =>
expect(runtime.getPhase() as string).toBe("FAILED"),
);
expect(runtime.getSnapshot().isCurrent()).toBe(false);
});
it("activates the next generation after reset and fails closed on activation error", async () => {
let sessionListener: () => void = () => {};
const steps: string[] = [];
const runtime = createServerStateScopeRuntime({
session: {
subscribe(listener) {
sessionListener = listener;
return () => {};
},
},
queryInvalidation: {
resetLocal: async () => {
steps.push("reset");
},
},
activateNextGeneration: async () => {
steps.push("activate");
throw new Error("activation failed");
},
tokenFactory: () => "scope-activation-token-001",
} as Parameters<typeof createServerStateScopeRuntime>[0] & {
activateNextGeneration(): Promise<void>;
});
sessionListener();
await vi.waitFor(() => expect(steps).toEqual(["reset", "activate"]));
await vi.waitFor(() =>
expect(runtime.getPhase() as string).toBe("FAILED"),
);
});
});
@@ -0,0 +1,87 @@
import { describe, expect, it } from "vitest";
import { resolveServiceWorkerBuildInput } from "../../scripts/lib/service-worker-build-input.ts";
const digest = (character: string) => `sha256:${character.repeat(64)}`;
describe("service worker build input", () => {
const selection = {
mode: "ACTIVE" as const,
scriptPath: "service-worker.js" as const,
handlers: ["PWA_STATIC_ASSETS" as const],
};
const assets = {
schemaVersion: 1 as const,
buildId: "build-1",
releaseId: "release-1",
setDigest: digest("a"),
assets: [],
};
it("rejects a direct worker build when ACTIVE selection or generated inputs are absent", () => {
expect(() =>
resolveServiceWorkerBuildInput({
selection: null,
assets,
contractSet: { setDigest: digest("b") },
runtimeConfig: { RELEASE_MANIFEST_URL: "/release-manifest.json" },
buildId: "build-1",
releaseId: "release-1",
}),
).toThrow(/ACTIVE/u);
expect(() =>
resolveServiceWorkerBuildInput({
selection,
assets: null,
contractSet: { setDigest: digest("b") },
runtimeConfig: { RELEASE_MANIFEST_URL: "/release-manifest.json" },
buildId: "build-1",
releaseId: "release-1",
}),
).toThrow(/asset manifest/u);
});
it("rejects stale generated identity instead of compiling a mismatched worker", () => {
expect(() =>
resolveServiceWorkerBuildInput({
selection,
assets: { ...assets, buildId: "old-build" },
contractSet: { setDigest: digest("b") },
runtimeConfig: { RELEASE_MANIFEST_URL: "/release-manifest.json" },
buildId: "build-1",
releaseId: "release-1",
}),
).toThrow(/identity/u);
});
it("rejects WEB_PUSH selection until its product-owned worker contribution exists", () => {
expect(() =>
resolveServiceWorkerBuildInput({
selection: { ...selection, handlers: ["WEB_PUSH"] },
assets,
contractSet: { setDigest: digest("b") },
runtimeConfig: { RELEASE_MANIFEST_URL: "/release-manifest.json" },
buildId: "build-1",
releaseId: "release-1",
}),
).toThrow(/WEB_PUSH.*contribution/u);
});
it("returns only fully matched, digest-bearing generated inputs", () => {
expect(
resolveServiceWorkerBuildInput({
selection,
assets,
contractSet: { setDigest: digest("b") },
runtimeConfig: { RELEASE_MANIFEST_URL: "/release-manifest.json" },
buildId: "build-1",
releaseId: "release-1",
}),
).toMatchObject({
assets,
handlers: ["PWA_STATIC_ASSETS"],
contractSetDigest: digest("b"),
releaseManifestUrl: "/release-manifest.json",
});
});
});
+567
View File
@@ -0,0 +1,567 @@
import { describe, expect, it, vi } from "vitest";
import { createServiceWorkerPageController } from "../../src/adapters/service-worker/service-worker-page-controller.ts";
import { createServiceWorkerRuntime } from "../../src/adapters/service-worker/service-worker-lifecycle.ts";
import { createServiceWorkerMessage } from "../../src/adapters/service-worker/service-worker-protocol.ts";
import { installStaticAssets } from "../../src/adapters/service-worker/service-worker-static-assets.ts";
import {
SERVICE_WORKER_BOUNDS,
SERVICE_WORKER_SCRIPT_PATH,
STATIC_CACHE_PREFIX,
staticCacheName,
type ServiceWorkerProtocolIdentity,
type StaticAssetManifestV1,
} from "../../src/contracts/service-worker.ts";
const ORIGIN = "https://app.example";
const SCRIPT_URL = `${ORIGIN}/service-worker.js`;
function pageContainer(options: { waiting?: boolean; controlled?: boolean } = {}) {
const listeners = new Set<(event: MessageEvent) => void>();
const waitingMessages: unknown[] = [];
const controllerMessages: unknown[] = [];
const worker = (messages: unknown[]) =>
({
scriptURL: SCRIPT_URL,
postMessage(message: unknown) {
messages.push(message);
},
}) as unknown as ServiceWorker;
const waiting = options.waiting ? worker(waitingMessages) : null;
const active = options.waiting ? null : worker([]);
const controlled = options.controlled ? worker(controllerMessages) : null;
const registration = {
scope: `${ORIGIN}/`,
installing: null,
waiting,
active,
update: vi.fn(async () => {}),
} as unknown as ServiceWorkerRegistration;
const container = {
controller: controlled,
register: vi.fn(async () => registration),
addEventListener(_type: string, listener: (event: MessageEvent) => void) {
listeners.add(listener);
},
removeEventListener(_type: string, listener: (event: MessageEvent) => void) {
listeners.delete(listener);
},
} as unknown as ServiceWorkerContainer;
return {
container,
registration,
waitingMessages,
controllerMessages,
listenerCount: () => listeners.size,
dispatch(data: unknown, source?: { postMessage(message: unknown): void }) {
const event = { data, origin: ORIGIN, source } as unknown as MessageEvent;
for (const listener of [...listeners]) listener(event);
},
};
}
function pageController(container: ServiceWorkerContainer, blockers = [() => false]) {
return createServiceWorkerPageController({
selection: {
mode: "ACTIVE",
scriptPath: SERVICE_WORKER_SCRIPT_PATH,
handlers: [],
},
disabledCleanup: false,
routerBasePath: "/",
origin: ORIGIN,
buildId: "page-build",
container,
blockers,
});
}
const identity: ServiceWorkerProtocolIdentity = {
serviceWorkerProtocolVersion: 1,
cacheSchemaVersion: 1,
buildId: "worker-build",
releaseId: "release-1",
contractSetDigest: `sha256:${"1".repeat(64)}`,
staticAssetSetDigest: `sha256:${"2".repeat(64)}`,
};
function workerScope() {
const deleted: string[] = [];
const clients = ["client-a", "client-b"].map((id) => ({
id,
url: `${ORIGIN}/app/${id}`,
messages: [] as unknown[],
postMessage(message: unknown) {
this.messages.push(message);
},
}));
const scope = {
caches: {
open: vi.fn(),
keys: vi.fn(async () => [
`${STATIC_CACHE_PREFIX}${"a".repeat(16)}`,
`${STATIC_CACHE_PREFIX}${"b".repeat(16)}`,
"foreign-cache",
]),
delete: vi.fn(async (name: string) => {
deleted.push(name);
return true;
}),
match: vi.fn(),
},
clients: { matchAll: vi.fn(async () => clients) },
registrationScope: `${ORIGIN}/app/`,
skipWaiting: vi.fn(async () => {}),
fetcher: vi.fn(),
digest: vi.fn(),
};
return { scope, clients, deleted };
}
describe("service worker page protocol", () => {
it("does not attach late listeners when stopped during registration", async () => {
const browser = pageContainer();
let completeRegistration: ((value: ServiceWorkerRegistration) => void) | undefined;
const deferredRegistration = new Promise<ServiceWorkerRegistration>((resolve) => {
completeRegistration = resolve;
});
vi.mocked(browser.container.register).mockReturnValue(deferredRegistration);
const controller = pageController(browser.container);
const starting = controller.start();
await Promise.resolve();
await controller.stop();
completeRegistration?.(browser.registration);
await expect(starting).resolves.toEqual({ kind: "FAILED", code: "STOPPED" });
expect(browser.listenerCount()).toBe(0);
});
it("answers a worker drain request only after local blockers are clear", async () => {
const browser = pageContainer({ waiting: true });
const controller = pageController(browser.container);
await controller.start();
const source = { postMessage: vi.fn() };
browser.dispatch(
createServiceWorkerMessage({
kind: "CLIENT_DRAIN_REQUEST",
sourceBuildId: "worker-build",
targetBuildId: "page-build",
nonce: "drain-1",
}),
source,
);
expect(source.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
kind: "CLIENT_DRAINED",
sourceBuildId: "page-build",
targetBuildId: "worker-build",
nonce: "drain-1",
}),
);
await controller.stop();
});
it("settles a pending activation and removes its listener when stopped", async () => {
const browser = pageContainer({ waiting: true });
const controller = pageController(browser.container);
await controller.start();
const activation = controller.requestActivation();
await Promise.resolve();
expect(browser.listenerCount()).toBe(2);
await controller.stop();
await expect(activation).resolves.toEqual({ kind: "FAILED", code: "STOPPED" });
expect(browser.listenerCount()).toBe(0);
});
it("waits for the correlated cache reset result", async () => {
const browser = pageContainer({ controlled: true });
const controller = pageController(browser.container);
await controller.start();
let settled = false;
const result = controller.resetOwnedCaches().then((outcome) => {
settled = true;
return outcome;
});
await Promise.resolve();
expect(settled).toBe(false);
const request = browser.controllerMessages.at(-1) as { nonce?: string };
browser.dispatch({
...createServiceWorkerMessage({
kind: "CACHE_RESET_RESULT",
sourceBuildId: "worker-build",
targetBuildId: "page-build",
nonce: request.nonce,
}),
cachesDeleted: 2,
});
await expect(result).resolves.toEqual({ kind: "RESET", cachesDeleted: 2 });
await controller.stop();
});
});
describe("service worker worker-side protocol", () => {
it("retains the immediately previous verified static cache on activation", async () => {
const current = staticCacheName(identity.staticAssetSetDigest);
const stale = `${STATIC_CACHE_PREFIX}${"3".repeat(16)}`;
const previous = `${STATIC_CACHE_PREFIX}${"4".repeat(16)}`;
const names = [stale, previous, current, "foreign-cache"];
const deleted: string[] = [];
const markerPut = vi.fn(async () => {});
const cache = {
match: vi.fn(async () => undefined),
put: markerPut,
delete: vi.fn(async () => true),
} as unknown as Cache;
const scope = {
caches: {
open: vi.fn(async () => cache),
keys: vi.fn(async () => names),
delete: vi.fn(async (name: string) => {
deleted.push(name);
return true;
}),
match: vi.fn(),
},
clients: { matchAll: vi.fn(async () => []) },
skipWaiting: vi.fn(async () => {}),
fetcher: vi.fn(),
digest: vi.fn(),
};
const runtime = createServiceWorkerRuntime(scope as never, {
identity,
handlers: ["PWA_STATIC_ASSETS"],
manifest: {
schemaVersion: 1,
buildId: identity.buildId,
releaseId: identity.releaseId,
setDigest: identity.staticAssetSetDigest as `sha256:${string}`,
assets: [],
},
runtimeConfigUrl: "/runtime-config.json",
releaseManifestUrl: "/release-manifest.json",
});
await expect(runtime.onActivate()).resolves.toBe(1);
expect(deleted).toEqual([stale]);
expect(markerPut).toHaveBeenCalledOnce();
});
it("waits for every exact client drain acknowledgement before activation", async () => {
const fixture = workerScope();
const runtime = createServiceWorkerRuntime(fixture.scope as never, {
identity,
handlers: [],
manifest: null,
runtimeConfigUrl: "/runtime-config.json",
releaseManifestUrl: "/release-manifest.json",
});
const request = createServiceWorkerMessage({
kind: "ACTIVATE_REQUEST",
sourceBuildId: "page-build",
targetBuildId: "worker-build",
nonce: "activation-1",
});
const activation = runtime.onActivateRequest(request);
await Promise.resolve();
expect(fixture.scope.skipWaiting).not.toHaveBeenCalled();
runtime.onClientMessage(
createServiceWorkerMessage({
kind: "CLIENT_DRAINED",
sourceBuildId: "page-build",
targetBuildId: "worker-build",
nonce: "activation-1",
}),
"client-a",
);
await Promise.resolve();
expect(fixture.scope.skipWaiting).not.toHaveBeenCalled();
runtime.onClientMessage(
createServiceWorkerMessage({
kind: "CLIENT_DRAINED",
sourceBuildId: "page-build",
targetBuildId: "worker-build",
nonce: "activation-1",
}),
"client-b",
);
await expect(activation).resolves.toBe("ACCEPTED");
expect(fixture.scope.skipWaiting).toHaveBeenCalledTimes(1);
});
it("enumerates uncontrolled window clients before an activation drain", async () => {
const fixture = workerScope();
const runtime = createServiceWorkerRuntime(fixture.scope as never, {
identity,
handlers: [],
manifest: null,
runtimeConfigUrl: "/runtime-config.json",
releaseManifestUrl: "/release-manifest.json",
});
const request = createServiceWorkerMessage({
kind: "ACTIVATE_REQUEST",
sourceBuildId: "page-build",
targetBuildId: "worker-build",
nonce: "activation-uncontrolled",
});
const activation = runtime.onActivateRequest(request);
await Promise.resolve();
for (const client of fixture.clients) {
runtime.onClientMessage(
createServiceWorkerMessage({
kind: "CLIENT_DRAINED",
sourceBuildId: "page-build",
targetBuildId: "worker-build",
nonce: "activation-uncontrolled",
}),
client.id,
);
}
await expect(activation).resolves.toBe("ACCEPTED");
expect(fixture.scope.clients.matchAll).toHaveBeenCalledWith({
type: "window",
includeUncontrolled: true,
});
});
it("drains only page clients inside the exact registration scope", async () => {
const inScope = {
id: "client-in-scope",
url: `${ORIGIN}/app/nested/page`,
messages: [] as unknown[],
postMessage(message: unknown) {
this.messages.push(message);
},
};
const outOfScope = {
id: "client-out-of-scope",
url: `${ORIGIN}/application/page`,
messages: [] as unknown[],
postMessage(message: unknown) {
this.messages.push(message);
},
};
const fixture = workerScope();
fixture.scope.clients.matchAll.mockResolvedValue([
inScope,
outOfScope,
]);
const runtime = createServiceWorkerRuntime(fixture.scope as never, {
identity,
handlers: [],
manifest: null,
runtimeConfigUrl: "/runtime-config.json",
releaseManifestUrl: "/release-manifest.json",
});
const activation = runtime.onActivateRequest(
createServiceWorkerMessage({
kind: "ACTIVATE_REQUEST",
sourceBuildId: "page-build",
targetBuildId: "worker-build",
nonce: "activation-scope",
}),
);
await Promise.resolve();
runtime.onClientMessage(
createServiceWorkerMessage({
kind: "CLIENT_DRAINED",
sourceBuildId: "page-build",
targetBuildId: "worker-build",
nonce: "activation-scope",
}),
inScope.id,
);
runtime.onClientMessage(
createServiceWorkerMessage({
kind: "CLIENT_DRAINED",
sourceBuildId: "page-build",
targetBuildId: "worker-build",
nonce: "activation-scope",
}),
outOfScope.id,
);
await expect(activation).resolves.toBe("ACCEPTED");
expect(inScope.messages).not.toHaveLength(0);
expect(outOfScope.messages).toEqual([]);
});
it("deletes only owned caches and returns the correlated reset count", async () => {
const fixture = workerScope();
const runtime = createServiceWorkerRuntime(fixture.scope as never, {
identity,
handlers: [],
manifest: null,
runtimeConfigUrl: "/runtime-config.json",
releaseManifestUrl: "/release-manifest.json",
});
const source = {
id: "client-a",
url: `${ORIGIN}/app/client-a`,
postMessage: vi.fn(),
};
await runtime.onCacheResetRequest(
createServiceWorkerMessage({
kind: "CACHE_RESET_REQUEST",
sourceBuildId: "page-build",
targetBuildId: "worker-build",
nonce: "reset-1",
}),
source,
);
expect(fixture.deleted).toEqual([
`${STATIC_CACHE_PREFIX}${"a".repeat(16)}`,
`${STATIC_CACHE_PREFIX}${"b".repeat(16)}`,
]);
expect(source.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
kind: "CACHE_RESET_RESULT",
nonce: "reset-1",
cachesDeleted: 2,
}),
);
});
});
describe("service worker static asset install", () => {
const manifest: StaticAssetManifestV1 = {
schemaVersion: 1,
buildId: "worker-build",
releaseId: "release-1",
setDigest: `sha256:${"a".repeat(64)}`,
assets: [
{
url: `${ORIGIN}/assets/app.js`,
sha256: `sha256:${"b".repeat(64)}`,
bytes: 1,
contentType: "application/javascript",
},
],
};
it("aborts and rolls back a candidate cache at the overall install deadline", async () => {
vi.useFakeTimers();
const deleteCache = vi.fn(async () => true);
const fetcher = vi.fn(
(_input: RequestInfo | URL, init?: RequestInit) =>
new Promise<Response>((_resolve, reject) => {
init?.signal?.addEventListener("abort", () => {
reject(new DOMException("Install deadline", "AbortError"));
});
}),
);
const result = installStaticAssets(manifest, {
caches: {
open: vi.fn(async () => ({ put: vi.fn() }) as unknown as Cache),
delete: deleteCache,
},
fetcher: fetcher as typeof fetch,
digest: vi.fn(),
});
await vi.advanceTimersByTimeAsync(SERVICE_WORKER_BOUNDS.installDeadlineMs);
await expect(result).resolves.toEqual({
kind: "REJECTED",
code: "INSTALL_DEADLINE_EXCEEDED",
});
expect(fetcher.mock.calls[0]?.[1]?.signal?.aborted).toBe(true);
expect(deleteCache).toHaveBeenCalledTimes(1);
vi.useRealTimers();
});
it("stops reading as soon as the streamed body exceeds declared bytes", async () => {
const put = vi.fn(async () => {});
const cancel = vi.fn(async () => {});
const body = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new Uint8Array([1, 2]));
},
cancel,
});
await expect(
installStaticAssets(manifest, {
caches: {
open: vi.fn(async () => ({ put }) as unknown as Cache),
delete: vi.fn(async () => true),
},
fetcher: vi.fn(async () =>
new Response(body, {
status: 200,
headers: { "content-type": "application/javascript" },
}),
),
digest: vi.fn(),
}),
).resolves.toEqual({ kind: "REJECTED", code: "BYTES_MISMATCH" });
expect(put).not.toHaveBeenCalled();
expect(cancel).toHaveBeenCalledTimes(1);
});
it("aborts sibling asset fetches after the first install failure", async () => {
vi.useFakeTimers();
try {
let siblingSignal: AbortSignal | undefined;
const twoAssetManifest: StaticAssetManifestV1 = {
...manifest,
assets: [
manifest.assets[0]!,
{
url: `${ORIGIN}/assets/chunk.js`,
sha256: `sha256:${"c".repeat(64)}`,
bytes: 1,
contentType: "application/javascript",
},
],
};
const fetcher = vi.fn(
async (input: RequestInfo | URL, init?: RequestInit) => {
if (String(input).endsWith("app.js")) {
return new Response(null, { status: 500 });
}
siblingSignal = init?.signal ?? undefined;
await new Promise<void>((resolve) => setTimeout(resolve, 10));
return new Response(new Uint8Array([1]), {
status: 200,
headers: { "content-type": "application/javascript" },
});
},
);
const installing = installStaticAssets(twoAssetManifest, {
caches: {
open: vi.fn(async () => ({ put: vi.fn() }) as unknown as Cache),
delete: vi.fn(async () => true),
},
fetcher: fetcher as typeof fetch,
digest: vi.fn(async () => twoAssetManifest.assets[1]!.sha256),
});
await vi.advanceTimersByTimeAsync(10);
await expect(installing).resolves.toEqual({
kind: "REJECTED",
code: "STATUS_INVALID",
});
expect(siblingSignal?.aborted).toBe(true);
} finally {
vi.useRealTimers();
}
});
});
@@ -219,6 +219,41 @@ describe("TanStack cross-context cache coordinator", () => {
expect(harness.publish).not.toHaveBeenCalled();
});
it("rejects a mandatory local reset when query cancellation fails", async () => {
const client = createClient();
client.setQueryData(["resource-a", 1, "list"], ["private-old-scope"]);
vi.spyOn(client, "cancelQueries").mockRejectedValue(
new Error("cancellation failed"),
);
const clear = vi.spyOn(client, "clear");
const coordinator = createTanStackCacheCoordinator({
queryClient: client,
queryRegistry: queryRegistry(),
});
await expect(coordinator.resetLocal()).rejects.toThrow(
"mandatory query cancellation failed",
);
expect(clear).toHaveBeenCalledOnce();
});
it("composes with no installed query topics", () => {
// §24.12: removing the reference feature leaves the common runtime intact.
// A template with no installed feature has zero invalidation topics, which
// is a legitimate state, not a configuration defect.
const harness = crossContextHarness();
const coordinator = createTanStackCacheCoordinator({
queryClient: createClient(),
queryRegistry: Object.freeze({}),
crossContext: harness.transport,
});
// A remote hint for a topic this build does not install is ignored, not fatal.
harness.deliver("qinv.topic-a");
expect(() => coordinator.beginMutation([])).not.toThrow();
coordinator.dispose();
});
it("rejects an unregistered topic before opening a mutation lease", () => {
const coordinator = createTanStackCacheCoordinator({
queryClient: createClient(),
@@ -0,0 +1,28 @@
import { describe, expect, it } from "vitest";
import type { IndexedDbRepositoryPort } from "../../src/application/ports/browser-file-storage/indexeddb-port.ts";
import type {
PushControlRepository,
} from "../../src/adapters/web-push/push-association-fence-store.ts";
import type { PushControlV1 } from "../../src/contracts/web-push.ts";
/**
* The Web Push fence store declares its own narrow durable-store port so the
* Web Push and browser file/storage capabilities stay independently removable.
*
* This suite is what keeps that decoupling honest: it asserts at type level
* that the generic IndexedDB repository still satisfies the narrow port, so the
* composition root can join the two without an adapter shim. It lives in the
* browser-data import graph on purpose, so the storage removal harness drops it
* along with the runtime it checks.
*/
describe("push control store port compatibility", () => {
it("is satisfied by the generic IndexedDB repository", () => {
type GenericRepository = IndexedDbRepositoryPort<PushControlV1, never>;
const satisfiesNarrowPort = (
repository: GenericRepository,
): PushControlRepository => repository;
expect(typeof satisfiesNarrowPort).toBe("function");
});
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

+11
View File
@@ -41,3 +41,14 @@ test("loading empty error and access surfaces visual contract", async ({
"state-surfaces-light.png",
);
});
test("platform composition overview visual contract", async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 1100 });
await page.goto("/examples/platform");
await expect(
page.locator("section[aria-labelledby='platform-capabilities-title']"),
).toBeVisible();
await expect(page.getByRole("main")).toHaveScreenshot(
"platform-overview-light.png",
);
});