feat: 기능 추가 과정중

This commit is contained in:
donghyeon-ka
2026-07-30 15:58:20 +09:00
parent d3ef801fe6
commit 6c52cdb916
648 changed files with 126325 additions and 6680 deletions
+58
View File
@@ -0,0 +1,58 @@
import { describe, expect, it } from "vitest";
import {
composeApiOperations,
defineRestOperation,
} from "../../src/contracts/api-operations.ts";
function operation(operationId = "GET_RESOURCE") {
return defineRestOperation({
contractVersion: 2,
protocol: "REST",
semantics: "QUERY",
method: "GET",
path: "/resources/{resourceId}",
operationId,
auth: "external-session",
timeoutMs: 5_000,
idempotency: "safe",
retry: "runtime",
replayPolicy: "SAFE",
idempotencyKeyPolicy: "NONE",
requestSource: "none",
requestSchema: "NoRequest",
responseSchema: "ResourcePayload",
mapperId: "ResourceMapper",
successStatuses: [200],
responseMediaTypes: ["application/json"],
maxResponseBytes: 32_768,
providerId: "PRIMARY_API",
authProfileId: "REFERENCE_EXTERNAL_BEARER",
csrfProfileId: "NO_CSRF_BEARER",
pathSchema: "ResourceParams",
pathParameterNames: ["resourceId"],
maxEncodedSearchBytes: 0,
owner: "test",
});
}
describe("REST operation v2 registry", () => {
it("rejects duplicate contributions before an object spread can overwrite them", () => {
const selected = operation();
expect(() =>
composeApiOperations([
{ GET_RESOURCE: selected },
{ GET_RESOURCE: selected },
]),
).toThrow("Duplicate API operation");
});
it("rejects an unsafe query/replay policy combination", () => {
expect(() =>
defineRestOperation({
...operation(),
method: "POST",
}),
).toThrow("Incoherent REST replay contract");
});
});
+45 -10
View File
@@ -3,8 +3,16 @@ import { describe, expect, it, vi } from "vitest";
import {
createApplication,
type ApplicationOutputPorts,
} from "../../src/application/create-application.js";
import { createTestApplication } from "../helpers/create-test-application.js";
} from "../../src/application/create-application.ts";
import { createTestApplication } from "../helpers/create-test-application.ts";
declare module "../../src/application/ports/in/application-api.ts" {
interface ApplicationFeatureInputs {
"test-feature": Readonly<{
run(): "ok";
}>;
}
}
describe("application input/output boundary", () => {
it("exposes intent-oriented input APIs without leaking output ports", async () => {
@@ -22,9 +30,11 @@ describe("application input/output boundary", () => {
expect(application).not.toHaveProperty("telemetry");
expect(application).not.toHaveProperty("releaseInfo");
expect(application.features.has("not-installed")).toBe(false);
expect(() => application.features.get("not-installed")).toThrow(
"Application feature is not installed",
);
expect(() =>
Reflect.apply(application.features.get, application.features, [
"not-installed",
]),
).toThrow("Application feature is not installed");
await expect(application.runtime.getReleaseSummary()).resolves.toEqual({
buildId: "test-build",
releaseId: "test-release",
@@ -33,17 +43,34 @@ describe("application input/output boundary", () => {
});
});
it("uses fake output ports for preference, session, and safe diagnostics flows", () => {
it("returns a typed input for an installed generic feature", () => {
const featureInput = Object.freeze({
run: () => "ok" as const,
});
const application = createTestApplication({
featureInputs: { "test-feature": featureInput },
});
expect(application.features.has("test-feature")).toBe(true);
expect(application.features.get("test-feature")).toBe(featureInput);
expect(application.features.get("test-feature").run()).toBe("ok");
});
it("uses fake output ports for preference, session, and safe diagnostics flows", async () => {
const write = vi.fn(() => ({ ok: true as const }));
const emit = vi.fn();
const record = vi.fn();
const subscribe = vi.fn(() => () => {});
const beginSignIn = vi.fn(async () => {});
const signOut = vi.fn(async () => {});
const recover = vi.fn(async () => "restored" as const);
const ports = {
session: {
getState: () => "authenticated" as const,
subscribe: () => () => {},
beginSignIn: async () => {},
signOut: async () => {},
recover: async () => "restored" as const,
subscribe,
beginSignIn,
signOut,
recover,
},
preferences: {
read: () => ({ ok: true as const, value: "dark" }),
@@ -75,6 +102,14 @@ describe("application input/output boundary", () => {
const application = createApplication(ports);
expect(application.session.getSnapshot()).toBe("authenticated");
const listener = vi.fn();
expect(application.session.subscribe(listener)).toBeTypeOf("function");
expect(subscribe).toHaveBeenCalledWith(listener);
await application.session.beginSignIn("/return");
expect(beginSignIn).toHaveBeenCalledWith("/return");
await application.session.signOut();
expect(signOut).toHaveBeenCalledOnce();
await expect(application.session.recover()).resolves.toBe("restored");
expect(application.preferences.getColorScheme()).toBe("dark");
expect(application.preferences.setColorScheme("light")).toEqual({ ok: true });
expect(write).toHaveBeenCalledWith("COLOR_SCHEME", "light");
@@ -4,7 +4,7 @@ import {
createAnonymousSessionAdapter,
createDemoSessionAdapter,
createExternalAuthSessionAdapter,
} from "../../src/adapters/auth/external-session-adapter.js";
} from "../../src/adapters/auth/external-session-adapter.ts";
describe("external AuthSessionPort adapter", () => {
it("attaches opaque credentials without exposing a token-shaped session", async () => {
@@ -13,17 +13,19 @@ describe("external AuthSessionPort adapter", () => {
subscribe: () => () => {},
beginSignIn: async () => {},
signOut: async () => {},
attachCredential: async (request) => {
const headers = new Headers(request.headers);
headers.set("X-Session-Attached", "true");
return new Request(request, { headers });
},
attachCredential: async () => ({
headers: { Authorization: "Bearer opaque" },
}),
recoverSession: async () => "restored",
notifyUnauthenticated: vi.fn(),
});
const request = await adapter.attach(new Request("https://api.test/resource"));
expect(request.headers.get("X-Session-Attached")).toBe("true");
const patch = await adapter.credentialPatch({
origin: "https://api.test",
method: "GET",
operationId: "GET_RESOURCE",
});
expect(patch.headers.authorization).toBe("Bearer opaque");
expect(adapter.getState()).toBe("authenticated");
expect(adapter).not.toHaveProperty("accessToken");
expect(adapter).not.toHaveProperty("refreshToken");
@@ -35,7 +37,7 @@ describe("external AuthSessionPort adapter", () => {
subscribe: () => () => {},
beginSignIn: async () => {},
signOut: async () => {},
attachCredential: async (request) => request,
attachCredential: async () => ({ headers: {} }),
// @ts-expect-error Deliberately violates the external-owner contract.
recoverSession: async () => "unexpected",
notifyUnauthenticated: vi.fn(),
@@ -44,6 +46,28 @@ describe("external AuthSessionPort adapter", () => {
await expect(adapter.recover()).rejects.toThrow("invalid recovery state");
});
it("rejects credential patches that can alter transport-owned headers", async () => {
const adapter = createExternalAuthSessionAdapter({
readState: () => "authenticated",
subscribe: () => () => {},
beginSignIn: async () => {},
signOut: async () => {},
attachCredential: async () => ({
headers: { Host: "attacker.test" },
}),
recoverSession: async () => "restored",
notifyUnauthenticated: vi.fn(),
});
await expect(
adapter.credentialPatch({
origin: "https://api.test",
method: "GET",
operationId: "GET_RESOURCE",
}),
).rejects.toThrow("forbidden credential patch");
});
it("provides a safe anonymous adapter", async () => {
const adapter = createAnonymousSessionAdapter();
expect(adapter.getState()).toBe("unauthenticated");
-11
View File
@@ -1,11 +0,0 @@
import { describe, expect, it } from "vitest";
import { mapOperationPayload } from "../../src/adapters/http/resource-mapper.js";
describe("platform DTO mapper boundary", () => {
it("fails closed when no feature mapper was injected", () => {
expect(() => mapOperationPayload("UNKNOWN", {})).toThrow(
"No boundary mapper registered",
);
});
});
+34
View File
@@ -0,0 +1,34 @@
import { describe, expect, it } from "vitest";
import { mapOperationPayload } from "../../src/adapters/http/resource-mapper.ts";
import {
composeBoundaryMapperRegistry,
mappingSuccess,
} from "../../src/contracts/boundary-mapper.ts";
describe("platform DTO mapper boundary", () => {
it("fails closed when no feature mapper was injected", () => {
expect(mapOperationPayload("UNKNOWN", {})).toEqual({
ok: false,
code: "MAPPING_INVARIANT_REJECTED",
});
});
it("rejects duplicate mapper contributions before installation", () => {
const mapper = {
mapperId: "ResourceMapper",
mapperVersion: 1,
inputSchemaId: "ResourcePayload",
outputContractId: "Resource",
owner: "test",
maxOutputItems: 1,
map: (input: unknown) => mappingSuccess(input),
};
expect(() =>
composeBoundaryMapperRegistry([
{ ResourceMapper: mapper },
{ ResourceMapper: mapper },
]),
).toThrow("duplicate boundary mapper");
});
});
+758
View File
@@ -0,0 +1,758 @@
import { describe, expect, it, vi } from "vitest";
import type {
FileVerificationReceipt,
LocalFileRef,
} from "../../src/application/ports/browser-file-storage/file.ts";
import { BrowserFileVault } from "../../src/adapters/browser-files/browser-file-vault.ts";
import {
BrowserTransientPreview,
ObjectUrlLeaseRegistry,
} from "../../src/adapters/browser-files/object-url-lease.ts";
import {
sanitizeSuggestedFileName,
type RegisteredFileInspectionPolicy,
type RegisteredFileSelectionPolicy,
} from "../../src/adapters/browser-files/file-policy.ts";
import {
BrowserFilePolicyRegistry,
browserFilePolicyReference,
} from "../../src/adapters/browser-files/browser-file-policy-registry.ts";
const pngSelectionPolicy: RegisteredFileSelectionPolicy = Object.freeze({
policyId: "avatar-v1",
purpose: "avatar",
classification: "PERSONAL",
multiple: false,
maxCount: 1,
maxFileBytes: 1_024,
maxTotalBytes: 1_024,
allowEmpty: false,
accept: Object.freeze([
Object.freeze({
mediaType: "image/png",
extensions: Object.freeze([".png"]),
}),
]),
});
const pngInspectionPolicy: RegisteredFileInspectionPolicy = Object.freeze({
policyId: "avatar-v1",
maxInspectionBytes: 16,
acceptedSignatures: Object.freeze([
Object.freeze({
mediaType: "image/png",
extensions: Object.freeze([".png"]),
patterns: Object.freeze([
Object.freeze({
offset: 0,
bytes: Object.freeze([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
]),
}),
]),
}),
]),
});
const pngPolicy = browserFilePolicyReference(
"avatar",
"inspect-and-preview-png",
);
function pngPolicies(
maxPreviewBytes = 10,
): BrowserFilePolicyRegistry {
return new BrowserFilePolicyRegistry({
profiles: [
{
reference: pngPolicy,
inspection: pngInspectionPolicy,
preview: {
allowedMediaTypes: ["image/png"],
maxPreviewBytes,
},
},
],
hardLimits: {
maxInspectionBytes: 64 * 1024,
maxRetainedFileBytes: 2_048,
maxPreviewBytes,
maxObjectUrlBytes: 2_048,
maxTransferBytes: 2_048,
},
});
}
describe("browser file content policy", () => {
it("neutralizes path, bidi, device-name and executable filename tricks", () => {
expect(
sanitizeSuggestedFileName("..\\CON\u202Egpj.exe", {
safeExtension: ".pdf",
}),
).toBe("CONgpj.pdf");
expect(
sanitizeSuggestedFileName("invoice.exe.pdf", {
safeExtension: ".pdf",
}),
).toBe("invoice_exe.pdf");
expect(
sanitizeSuggestedFileName("CON.txt", {
safeExtension: ".txt",
}),
).toBe("download.txt");
expect(
sanitizeSuggestedFileName("report.tar.gz", {
safeExtension: ".tar.gz",
}),
).toBe("report.tar.gz");
const bounded = sanitizeSuggestedFileName("가".repeat(100), {
safeExtension: ".txt",
maxUtf8Bytes: 24,
});
expect(new TextEncoder().encode(bounded).byteLength).toBeLessThanOrEqual(
24,
);
expect(
sanitizeSuggestedFileName("😀", {
safeExtension: ".bin",
maxUtf8Bytes: 5,
}),
).toBe("d.bin");
});
it("keeps native files behind opaque refs and performs bounded reads", async () => {
const policies = pngPolicies();
const vault = new BrowserFileVault({
policies,
createReference: () => "file:opaque-1",
});
const bytes = new Uint8Array([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 1, 2,
]);
const captured = vault.captureFiles(
[
new File([bytes], "portrait.png", {
type: "image/png",
lastModified: 100,
}),
],
pngSelectionPolicy,
);
expect(captured.ok).toBe(true);
if (!captured.ok) return;
const candidate = captured.value[0];
expect(candidate).toBeDefined();
if (!candidate) return;
expect(candidate.ref).not.toContain(candidate.displayName);
const signal = new AbortController().signal;
expect(
await vault.inspect({
ref: candidate.ref,
policy: pngPolicy,
signal,
}),
).toMatchObject({
ok: true,
value: {
detectedMediaType: "image/png",
normalizedExtension: ".png",
signature: "MATCHED",
},
});
expect(
await vault.readRange({
ref: candidate.ref,
offset: 8,
length: 2,
signal,
}),
).toEqual({ ok: true, value: new Uint8Array([1, 2]) });
const opened = await vault.openSource({
ref: candidate.ref,
signal,
});
expect(opened.ok).toBe(true);
if (opened.ok) {
const streamed: number[] = [];
for await (const chunk of opened.value.stream(signal)) {
expect(chunk.ok).toBe(true);
if (chunk.ok) streamed.push(...chunk.value);
}
expect(streamed).toEqual([...bytes]);
}
vault.release(candidate.ref);
expect(vault.activeVerificationCount).toBe(0);
expect(
await vault.readRange({
ref: candidate.ref,
offset: 0,
length: 1,
signal,
}),
).toMatchObject({
ok: false,
error: { code: "NOT_FOUND", recovery: "RESELECT" },
});
});
it("snapshots vault requests before asynchronous file resolution", async () => {
const references = ["file:a", "file:b"];
const policies = pngPolicies(32);
const vault = new BrowserFileVault({
policies,
createReference: () =>
references.shift() ?? "file:unexpected",
createVerificationReceipt: () => "verification:a",
});
const pngHeader = [
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
];
const captured = vault.captureFiles(
[
new File(
[new Uint8Array([...pngHeader, 1, 2])],
"a.png",
{ type: "image/png", lastModified: 1 },
),
new File(
[new Uint8Array([...pngHeader, 9, 8])],
"b.png",
{ type: "image/png", lastModified: 2 },
),
],
{
...pngSelectionPolicy,
multiple: true,
maxCount: 2,
maxTotalBytes: 2_048,
},
);
expect(captured.ok).toBe(true);
if (
!captured.ok ||
!captured.value[0] ||
!captured.value[1]
) {
return;
}
const [first, second] = captured.value;
const active = new AbortController();
const replacement = new AbortController();
replacement.abort();
const readRequest = {
ref: first.ref,
offset: 8,
length: 1,
signal: active.signal,
};
const pendingRead = vault.readRange(readRequest);
readRequest.ref = second.ref;
readRequest.offset = 9;
readRequest.signal = replacement.signal;
expect(await pendingRead).toEqual({
ok: true,
value: new Uint8Array([1]),
});
const inspectionRequest = {
ref: first.ref,
policy: pngPolicy,
signal: active.signal,
};
const pendingInspection = vault.inspect(inspectionRequest);
inspectionRequest.ref = second.ref;
inspectionRequest.policy = browserFilePolicyReference(
"forged",
"forged",
);
inspectionRequest.signal = replacement.signal;
expect(await pendingInspection).toMatchObject({
ok: true,
value: {
byteLength: 10,
verificationReceipt: "verification:a",
},
});
const sourceRequest = {
ref: first.ref,
signal: active.signal,
};
const pendingSource = vault.openSource(sourceRequest);
sourceRequest.ref = second.ref;
sourceRequest.signal = replacement.signal;
const opened = await pendingSource;
expect(opened.ok).toBe(true);
if (!opened.ok) return;
const streamed: number[] = [];
for await (const chunk of opened.value.stream(active.signal)) {
expect(chunk.ok).toBe(true);
if (chunk.ok) streamed.push(...chunk.value);
}
expect(streamed).toEqual([...pngHeader, 1, 2]);
});
it("snapshots file handles and loader methods before awaiting them", async () => {
const policies = pngPolicies();
let reference = 0;
const vault = new BrowserFileVault({
policies,
createReference: () => `file:handle-${reference++}`,
});
let resolveFirst: ((file: File) => void) | undefined;
const firstFile = new File(["a"], "a.txt", {
type: "text/plain",
lastModified: 1,
});
const secondFile = new File(["b"], "b.txt", {
type: "text/plain",
lastModified: 2,
});
const originalFirst = vi.fn(
() =>
new Promise<File>((resolve) => {
resolveFirst = resolve;
}),
);
const originalSecond = vi.fn(async () => secondFile);
const replaced = vi.fn(async () =>
new File(["x"], "x.txt"),
);
const firstHandle = {
kind: "file" as const,
name: "a.txt",
getFile: originalFirst,
};
const secondHandle = {
kind: "file" as const,
name: "b.txt",
getFile: originalSecond,
};
const handles = [firstHandle, secondHandle];
const pending = vault.captureHandles(
handles,
{
...pngSelectionPolicy,
multiple: true,
maxCount: 2,
accept: Object.freeze([]),
},
new AbortController().signal,
);
firstHandle.getFile = replaced;
secondHandle.getFile = replaced;
handles.splice(1, 1, {
kind: "file",
name: "x.txt",
getFile: replaced,
});
resolveFirst?.(firstFile);
const result = await pending;
expect(result).toMatchObject({
ok: true,
value: [
{ displayName: "a.txt" },
{ displayName: "b.txt" },
],
});
expect(originalFirst).toHaveBeenCalledOnce();
expect(originalSecond).toHaveBeenCalledOnce();
expect(replaced).not.toHaveBeenCalled();
if (!result.ok || !result.value[1]) return;
expect(
await vault.resolveFile(
result.value[1].ref,
new AbortController().signal,
),
).toMatchObject({
ok: true,
value: secondFile,
});
expect(originalSecond).toHaveBeenCalledTimes(2);
});
it("marks MIME/extension/signature disagreement and stale handles", async () => {
const refs = ["file:mismatch", "file:stale"];
const policies = pngPolicies();
const vault = new BrowserFileVault({
policies,
createReference: () => refs.shift() ?? "file:unexpected",
});
const permissive = {
...pngSelectionPolicy,
accept: Object.freeze([]),
};
const mismatch = vault.captureFiles(
[
new File(
[
new Uint8Array([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
]),
],
"portrait.jpg",
{ type: "image/jpeg", lastModified: 100 },
),
],
permissive,
);
expect(mismatch.ok).toBe(true);
if (!mismatch.ok || !mismatch.value[0]) return;
expect(
await vault.inspect({
ref: mismatch.value[0].ref,
policy: pngPolicy,
signal: new AbortController().signal,
}),
).toMatchObject({
ok: true,
value: {
detectedMediaType: "image/png",
signature: "MISMATCHED",
verificationReceipt: null,
},
});
let current = new File([new Uint8Array([1])], "state.bin", {
lastModified: 1,
});
const handle = {
kind: "file" as const,
name: "state.bin",
getFile: vi.fn(async () => current),
};
const handleCapture = await vault.captureHandles(
[handle],
permissive,
new AbortController().signal,
);
expect(handleCapture.ok).toBe(true);
if (!handleCapture.ok || !handleCapture.value[0]) return;
current = new File([new Uint8Array([1, 2])], "state.bin", {
lastModified: 2,
});
expect(
await vault.resolveFile(
handleCapture.value[0].ref,
new AbortController().signal,
),
).toMatchObject({
ok: false,
error: { code: "STALE_RESULT", recovery: "RESELECT" },
});
});
it("owns preview object URL leases and rejects active content", async () => {
const revoked: string[] = [];
const createObjectURL = vi.fn(() => "blob:test-1");
const leases = new ObjectUrlLeaseRegistry({
createObjectURL,
revokeObjectURL: (url) => revoked.push(url),
});
const policies = pngPolicies();
const vault = new BrowserFileVault({
policies,
createReference: () => "file:preview",
createVerificationReceipt: () => "verification:preview",
});
const capture = vault.captureFiles(
[
new File(
[
new Uint8Array([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
]),
],
"preview.png",
{
type: "image/png",
lastModified: 1,
},
),
],
pngSelectionPolicy,
);
expect(capture.ok).toBe(true);
if (!capture.ok || !capture.value[0]) return;
const inspected = await vault.inspect({
ref: capture.value[0].ref,
policy: pngPolicy,
signal: new AbortController().signal,
});
expect(inspected.ok).toBe(true);
if (!inspected.ok || !inspected.value.verificationReceipt) return;
const verificationReceipt =
inspected.value.verificationReceipt;
const preview = new BrowserTransientPreview({
files: vault,
policies,
leases,
hardMaxPreviewBytes: 10,
observer: {
record() {
throw new Error("ignored");
},
},
});
const created = await preview.create({
ref: capture.value[0].ref,
verificationReceipt,
policy: pngPolicy,
maxPreviewBytes: 10,
signal: new AbortController().signal,
});
expect(created.ok).toBe(true);
if (created.ok) {
created.value.release();
created.value.release();
}
expect(revoked).toEqual(["blob:test-1"]);
expect(leases.activeLeaseCount).toBe(0);
expect(
await preview.create({
ref: capture.value[0].ref,
verificationReceipt,
policy: pngPolicy,
maxPreviewBytes: 11,
signal: new AbortController().signal,
}),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(createObjectURL).toHaveBeenCalledOnce();
expect(
await preview.create({
ref: capture.value[0].ref as LocalFileRef,
verificationReceipt:
"verification:forged" as FileVerificationReceipt,
policy: pngPolicy,
maxPreviewBytes: 10,
signal: new AbortController().signal,
}),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
});
it("cannot weaken the built-in active-content preview denylist", async () => {
const htmlPolicy = browserFilePolicyReference(
"active-content",
"preview-html",
);
const policies = new BrowserFilePolicyRegistry({
profiles: [
{
reference: htmlPolicy,
inspection: {
policyId: "html-signature-v1",
maxInspectionBytes: 6,
acceptedSignatures: [
{
mediaType: "text/html",
extensions: [".html"],
patterns: [
{
offset: 0,
bytes: [60, 104, 116, 109, 108],
},
],
},
],
},
preview: {
allowedMediaTypes: ["text/html"],
maxPreviewBytes: 10,
},
},
],
hardLimits: {
maxInspectionBytes: 64 * 1024,
maxRetainedFileBytes: 1_024,
maxPreviewBytes: 10,
maxObjectUrlBytes: 1_024,
maxTransferBytes: 1_024,
},
});
const vault = new BrowserFileVault({
policies,
createReference: () => "file:html",
createVerificationReceipt: () => "verification:html",
});
const captured = vault.captureFiles(
[
new File([new TextEncoder().encode("<html>")], "page.html", {
type: "text/html",
lastModified: 1,
}),
],
{
...pngSelectionPolicy,
accept: Object.freeze([]),
},
);
expect(captured.ok).toBe(true);
if (!captured.ok || !captured.value[0]) return;
const inspected = await vault.inspect({
ref: captured.value[0].ref,
policy: htmlPolicy,
signal: new AbortController().signal,
});
expect(inspected.ok).toBe(true);
if (!inspected.ok || !inspected.value.verificationReceipt) return;
const createObjectURL = vi.fn(() => "blob:must-not-exist");
const preview = new BrowserTransientPreview({
files: vault,
policies,
hardMaxPreviewBytes: 10,
hardForbiddenMediaTypes: new Set(["image/jpeg"]),
leases: new ObjectUrlLeaseRegistry({
createObjectURL,
revokeObjectURL() {},
}),
});
expect(
await preview.create({
ref: captured.value[0].ref,
verificationReceipt: inspected.value.verificationReceipt,
policy: htmlPolicy,
maxPreviewBytes: 10,
signal: new AbortController().signal,
}),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(createObjectURL).not.toHaveBeenCalled();
});
it("rejects count and zero-byte policy violations before retaining refs", () => {
const vault = new BrowserFileVault({
policies: pngPolicies(),
});
expect(vault.captureFiles([], pngSelectionPolicy)).toMatchObject({
ok: false,
error: { code: "INVALID_INPUT" },
});
expect(
vault.captureFiles(
[new File([], "empty.png", { type: "image/png" })],
pngSelectionPolicy,
),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(vault.activeReferenceCount).toBe(0);
});
it("caps retained files and aggregate object URL leases", () => {
let referenceSequence = 0;
const vault = new BrowserFileVault({
policies: pngPolicies(),
createReference: () => `file:bounded-${referenceSequence++}`,
hardMaxActiveReferences: 1,
hardMaxRetainedBytes: 3,
});
const permissive = {
...pngSelectionPolicy,
accept: Object.freeze([]),
};
const first = vault.captureFiles(
[new File([new Uint8Array(2)], "one.bin")],
permissive,
);
expect(first.ok).toBe(true);
expect(vault.retainedByteLength).toBe(2);
expect(
vault.captureFiles(
[new File([new Uint8Array(2)], "two.bin")],
permissive,
),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
if (first.ok && first.value[0]) vault.release(first.value[0].ref);
expect(vault.retainedByteLength).toBe(0);
let urlSequence = 0;
const leases = new ObjectUrlLeaseRegistry(
{
createObjectURL: () => `blob:bounded-${urlSequence++}`,
revokeObjectURL() {},
},
{
hardMaxActiveLeases: 1,
hardMaxSingleLeaseBytes: 6,
hardMaxAggregateLeaseBytes: 6,
},
);
const lease = leases.create(new Blob([new Uint8Array(6)]));
expect(leases.aggregateLeaseByteLength).toBe(6);
expect(() =>
leases.create(new Blob([new Uint8Array(1)])),
).toThrowError(DOMException);
lease.release();
expect(leases.aggregateLeaseByteLength).toBe(0);
});
it("converts native stream exceptions to closed chunk failures", async () => {
const file = new File([new Uint8Array([1])], "broken.bin", {
lastModified: 1,
});
Object.defineProperty(file, "stream", {
value: () =>
new ReadableStream<Uint8Array>({
pull() {
throw new DOMException("native detail", "NotReadableError");
},
}),
});
const vault = new BrowserFileVault({
policies: pngPolicies(),
createReference: () => "file:broken",
});
const captured = vault.captureFiles(
[file],
{ ...pngSelectionPolicy, accept: Object.freeze([]) },
);
expect(captured.ok).toBe(true);
if (!captured.ok || !captured.value[0]) return;
const opened = await vault.openSource({
ref: captured.value[0].ref,
signal: new AbortController().signal,
});
expect(opened.ok).toBe(true);
if (!opened.ok) return;
const results = [];
for await (const result of opened.value.stream(
new AbortController().signal,
)) {
results.push(result);
}
expect(results).toEqual([
{
ok: false,
error: {
code: "NOT_READABLE",
operation: "FILE_READ",
retryable: true,
recovery: "REOPEN",
},
},
]);
});
});
+909
View File
@@ -0,0 +1,909 @@
import { describe, expect, it, vi } from "vitest";
import type {
BrowserManagedDownloadCapabilityReceipt,
BrowserManagedDownloadCapabilityResolver,
DownloadDeliveryPort,
DownloadSource,
DownloadStrategy,
FileByteSource,
} from "../../src/application/ports/browser-file-storage/file.ts";
import {
DEFAULT_OBJECT_URL_RELEASE_GRACE_MS,
createDownloadDeliveryAdapter,
type SaveFileHandle,
} from "../../src/adapters/browser-files/download-delivery-adapter.ts";
import { ObjectUrlLeaseRegistry } from "../../src/adapters/browser-files/object-url-lease.ts";
import {
BrowserFilePolicyRegistry,
browserFilePolicyReference,
} from "../../src/adapters/browser-files/browser-file-policy-registry.ts";
const HARD_LIMITS = Object.freeze({
hardMaxObjectUrlBytes: 64,
hardMaxTransferBytes: 128,
});
const capabilityReceipt =
"capability:download-1" as BrowserManagedDownloadCapabilityReceipt;
const policyByStrategy = Object.freeze({
BROWSER_MANAGED: browserFilePolicyReference(
"download",
"browser-managed-pdf",
),
PROMPT_AND_STREAM: browserFilePolicyReference(
"download",
"prompt-and-stream-pdf",
),
BOUNDED_OBJECT_URL: browserFilePolicyReference(
"download",
"bounded-object-url-pdf",
),
});
const policies = new BrowserFilePolicyRegistry({
profiles: (
Object.entries(policyByStrategy) as Array<
[DownloadStrategy, (typeof policyByStrategy)[DownloadStrategy]]
>
).map(([strategy, reference]) => ({
reference,
download: {
strategy,
mediaType: "application/pdf",
safeExtension: ".pdf",
maxTransferBytes: 32,
maxBufferedBytes: 16,
integrity: "OPTIONAL" as const,
},
})),
hardLimits: {
maxInspectionBytes: 64,
maxRetainedFileBytes: 128,
maxPreviewBytes: 64,
maxObjectUrlBytes: 64,
maxTransferBytes: 128,
},
});
function capabilityResolver(
href: (resourceId: string) => string = () => "/unused",
overrides: Readonly<
Partial<{
capabilityReceipt: BrowserManagedDownloadCapabilityReceipt;
resourceId: string;
mediaType: string;
safeExtension: string;
maxBytes: number;
expectedSha256: string;
expiresAtEpochMs: number;
}>
> = {},
): BrowserManagedDownloadCapabilityResolver {
return {
resolve(input) {
return {
ok: true,
value: {
capabilityReceipt:
overrides.capabilityReceipt ??
input.capabilityReceipt,
href: href(input.resourceId),
resourceId:
overrides.resourceId ?? input.resourceId,
mediaType:
overrides.mediaType ?? "application/pdf",
safeExtension: overrides.safeExtension ?? ".pdf",
maxBytes: overrides.maxBytes ?? 32,
...(overrides.expectedSha256
? { expectedSha256: overrides.expectedSha256 }
: {}),
expiresAtEpochMs:
overrides.expiresAtEpochMs ??
Number.MAX_SAFE_INTEGER,
},
};
},
};
}
function successfulChunk(bytes: Uint8Array) {
return Object.freeze({ ok: true as const, value: bytes });
}
function byteSource(
chunks: readonly Uint8Array[],
byteLength: number | null = chunks.reduce(
(total, chunk) => total + chunk.byteLength,
0,
),
afterChunk?: (index: number) => void,
): FileByteSource {
return Object.freeze({
byteLength,
async *stream(
signal: AbortSignal,
): AsyncIterable<ReturnType<typeof successfulChunk>> {
for (const [index, chunk] of chunks.entries()) {
if (signal.aborted) {
throw new DOMException("aborted", "AbortError");
}
yield successfulChunk(chunk);
afterChunk?.(index);
}
},
});
}
function deliveryInput(
source: DownloadSource,
strategy: DownloadStrategy,
signal = new AbortController().signal,
) {
return {
policy: policyByStrategy[strategy],
source,
suggestedFileName: "../../invoice.exe.pdf",
maxTransferBytes: 32,
maxBufferedBytes: 16,
signal,
onProgress: vi.fn(),
} satisfies Parameters<DownloadDeliveryPort["deliver"]>[0];
}
function writableHandle(events: string[]): SaveFileHandle {
return Object.freeze({
async createWritable() {
return new WritableStream<Uint8Array>({
write(chunk) {
events.push(`write:${chunk.byteLength}`);
},
close() {
events.push("close");
},
abort() {
events.push("abort");
},
});
},
});
}
describe("browser download delivery", () => {
it("captures composition dependencies instead of re-reading mutable option objects", async () => {
const originalHandoff = vi.fn();
const replacedHandoff = vi.fn();
const originalResolve = vi.fn(
capabilityResolver(
() => "/downloads/composition-bound",
).resolve,
);
const replacedResolve = vi.fn(
capabilityResolver(
() => "https://evil.example/replaced",
).resolve,
);
const host = { handoff: originalHandoff };
const browserManagedCapabilities = {
resolve: originalResolve,
};
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host,
browserManagedCapabilities,
baseOrigin: "https://app.example",
createTransferId: () => "transfer:composition",
userActivation: { isActive: true },
});
host.handoff = replacedHandoff;
browserManagedCapabilities.resolve = replacedResolve;
expect(
await adapter.deliver(
deliveryInput(
{
kind: "BROWSER_MANAGED_RESOURCE",
resourceId: "artifact-1",
capabilityReceipt,
},
"BROWSER_MANAGED",
),
),
).toMatchObject({
ok: true,
value: { kind: "BROWSER_HANDOFF" },
});
expect(originalResolve).toHaveBeenCalledOnce();
expect(replacedResolve).not.toHaveBeenCalled();
expect(originalHandoff).toHaveBeenCalledOnce();
expect(replacedHandoff).not.toHaveBeenCalled();
});
it("sanitizes browser-managed handoff and reports only handoff truth", async () => {
const handoff = vi.fn();
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff },
browserManagedCapabilities: capabilityResolver(
(resourceId) => `/downloads/${resourceId}`,
),
baseOrigin: "https://app.example",
createTransferId: () => "transfer:1",
userActivation: { isActive: true },
});
expect(
await adapter.deliver(
deliveryInput(
{
kind: "BROWSER_MANAGED_RESOURCE",
resourceId: "artifact-1",
capabilityReceipt,
},
"BROWSER_MANAGED",
),
),
).toEqual({
ok: true,
value: { kind: "BROWSER_HANDOFF", transferId: "transfer:1" },
});
expect(handoff).toHaveBeenCalledWith(
"/downloads/artifact-1",
"invoice_exe.pdf",
);
});
it("rejects cross-origin or query-bearing browser-managed targets", async () => {
const handoff = vi.fn();
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff },
browserManagedCapabilities: capabilityResolver(
() => "https://evil.example/a?token=secret",
),
baseOrigin: "https://app.example",
userActivation: { isActive: true },
});
expect(
await adapter.deliver(
deliveryInput(
{
kind: "BROWSER_MANAGED_RESOURCE",
resourceId: "artifact-1",
capabilityReceipt,
},
"BROWSER_MANAGED",
),
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(handoff).not.toHaveBeenCalled();
});
it("requires an exact, unexpired, server-bound synchronous capability receipt", async () => {
const source = {
kind: "BROWSER_MANAGED_RESOURCE" as const,
resourceId: "artifact-1",
capabilityReceipt,
};
const cases = [
{
resolver: capabilityResolver(() => "/unused", {
capabilityReceipt:
"capability:other" as BrowserManagedDownloadCapabilityReceipt,
}),
expectedCode: "POLICY_REJECTED",
},
{
resolver: capabilityResolver(undefined, {
resourceId: "artifact-2",
}),
expectedCode: "POLICY_REJECTED",
},
{
resolver: capabilityResolver(undefined, {
mediaType: "text/plain",
}),
expectedCode: "POLICY_REJECTED",
},
{
resolver: capabilityResolver(undefined, {
expiresAtEpochMs: 99,
}),
expectedCode: "EXPIRED_RESOURCE",
},
] as const;
for (const testCase of cases) {
const handoff = vi.fn();
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff },
browserManagedCapabilities: testCase.resolver,
now: () => 100,
baseOrigin: "https://app.example",
userActivation: { isActive: true },
});
expect(
await adapter.deliver(
deliveryInput(source, "BROWSER_MANAGED"),
),
).toMatchObject({
ok: false,
error: { code: testCase.expectedCode },
});
expect(handoff).not.toHaveBeenCalled();
}
const missingReceipt = {
kind: "BROWSER_MANAGED_RESOURCE",
resourceId: "artifact-1",
} as unknown as DownloadSource;
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff: vi.fn() },
browserManagedCapabilities: capabilityResolver(),
userActivation: { isActive: true },
});
expect(
await adapter.deliver(
deliveryInput(missingReceipt, "BROWSER_MANAGED"),
),
).toMatchObject({
ok: false,
error: { code: "INVALID_INPUT" },
});
});
it("streams with backpressure and succeeds only after close", async () => {
const events: string[] = [];
const controller = new AbortController();
const handle = writableHandle(events);
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff: vi.fn() },
browserManagedCapabilities: capabilityResolver(),
showSaveFilePicker: async () => handle,
createTransferId: () => "transfer:stream",
progressMinIntervalMs: 0,
userActivation: { isActive: true },
});
const input = deliveryInput(
{
kind: "GENERATED",
bytes: byteSource([
new Uint8Array([1, 2]),
new Uint8Array([3]),
]),
},
"PROMPT_AND_STREAM",
controller.signal,
);
expect(await adapter.deliver(input)).toEqual({
ok: true,
value: {
kind: "SAVED",
transferId: "transfer:stream",
bytesWritten: 3,
integrity: "NOT_PROVIDED",
},
});
expect(events).toEqual(["write:2", "write:1", "close"]);
expect(input.onProgress.mock.calls.map(([progress]) => progress.phase)).toEqual(
["PREPARING", "TRANSFERRING", "TRANSFERRING", "VERIFYING", "FINALIZING"],
);
});
it("snapshots the generated source before awaiting the save picker", async () => {
const events: string[] = [];
let resolvePicker:
| ((handle: SaveFileHandle) => void)
| undefined;
const originalStream = vi.fn(
async function* () {
yield successfulChunk(new Uint8Array([1, 2]));
},
);
const replacedStream = vi.fn(
async function* () {
yield successfulChunk(new Uint8Array([9]));
},
);
const bytes = {
byteLength: 2,
stream: originalStream,
};
const source = {
kind: "GENERATED" as const,
bytes,
};
const request = deliveryInput(
source,
"PROMPT_AND_STREAM",
);
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff: vi.fn() },
browserManagedCapabilities: capabilityResolver(),
showSaveFilePicker: () =>
new Promise((resolve) => {
resolvePicker = resolve;
}),
createTransferId: () => "transfer:snapshot",
userActivation: { isActive: true },
});
const pending = adapter.deliver(request);
bytes.byteLength = 1;
bytes.stream = replacedStream;
(
request as { source: DownloadSource }
).source = {
kind: "BROWSER_MANAGED_RESOURCE",
resourceId: "replaced",
capabilityReceipt,
};
resolvePicker?.(writableHandle(events));
expect(await pending).toMatchObject({
ok: true,
value: { kind: "SAVED", bytesWritten: 2 },
});
expect(originalStream).toHaveBeenCalledOnce();
expect(replacedStream).not.toHaveBeenCalled();
expect(events).toEqual(["write:2", "close"]);
});
it("does not turn a completed close into a late abort failure", async () => {
const controller = new AbortController();
const handle: SaveFileHandle = {
async createWritable() {
return new WritableStream<Uint8Array>({
close() {
controller.abort();
},
});
},
};
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff: vi.fn() },
browserManagedCapabilities: capabilityResolver(),
showSaveFilePicker: async () => handle,
createTransferId: () => "transfer:committed",
userActivation: { isActive: true },
});
expect(
await adapter.deliver(
deliveryInput(
{
kind: "GENERATED",
bytes: byteSource([new Uint8Array([1])]),
},
"PROMPT_AND_STREAM",
controller.signal,
),
),
).toMatchObject({
ok: true,
value: { kind: "SAVED", transferId: "transfer:committed" },
});
});
it("aborts the writable before close on integrity failure", async () => {
const events: string[] = [];
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff: vi.fn() },
browserManagedCapabilities: capabilityResolver(),
showSaveFilePicker: async () => writableHandle(events),
createIntegrityVerifier: () => ({
update: () => {
events.push("hash");
},
verify: () => false,
}),
userActivation: { isActive: true },
});
const input = {
...deliveryInput(
{
kind: "GENERATED" as const,
bytes: byteSource([new Uint8Array([1, 2])]),
expectedSha256: "a".repeat(64),
},
"PROMPT_AND_STREAM",
),
};
expect(await adapter.deliver(input)).toMatchObject({
ok: false,
error: { code: "INTEGRITY_FAILED" },
});
expect(events).toEqual(["hash", "write:2", "abort"]);
});
it("distinguishes save-picker dismissal from active cancellation", async () => {
const dismissed = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff: vi.fn() },
browserManagedCapabilities: capabilityResolver(),
showSaveFilePicker: async () => {
throw new DOMException("dismissed", "AbortError");
},
userActivation: { isActive: true },
});
expect(
await dismissed.deliver(
deliveryInput(
{
kind: "GENERATED",
bytes: byteSource([new Uint8Array([1])]),
},
"PROMPT_AND_STREAM",
),
),
).toEqual({
ok: true,
value: { kind: "DISMISSED" },
});
const controller = new AbortController();
let rejectPicker:
| ((reason: DOMException) => void)
| undefined;
const aborted = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff: vi.fn() },
browserManagedCapabilities: capabilityResolver(),
showSaveFilePicker: () =>
new Promise((_, reject: (reason: DOMException) => void) => {
rejectPicker = reject;
}),
userActivation: { isActive: true },
});
const pending = aborted.deliver(
deliveryInput(
{
kind: "GENERATED",
bytes: byteSource([new Uint8Array([1])]),
},
"PROMPT_AND_STREAM",
controller.signal,
),
);
controller.abort();
rejectPicker?.(new DOMException("closed", "AbortError"));
expect(await pending).toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
});
it("bounds Blob buffering and revokes the lease after handoff", async () => {
const revoked: string[] = [];
const leases = new ObjectUrlLeaseRegistry({
createObjectURL: () => "blob:download-1",
revokeObjectURL: (url) => revoked.push(url),
});
let scheduled: (() => void) | undefined;
let scheduledDelay: number | undefined;
const handoff = vi.fn();
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff },
browserManagedCapabilities: capabilityResolver(),
objectUrls: leases,
scheduler: {
setTimeout(callback, delayMs) {
scheduled = callback;
scheduledDelay = delayMs;
return 1;
},
},
createTransferId: () => "transfer:blob",
userActivation: { isActive: true },
});
expect(
await adapter.deliver(
deliveryInput(
{
kind: "GENERATED",
bytes: byteSource([
new Uint8Array([1, 2]),
new Uint8Array([3]),
]),
},
"BOUNDED_OBJECT_URL",
),
),
).toEqual({
ok: true,
value: {
kind: "BROWSER_HANDOFF",
transferId: "transfer:blob",
},
});
expect(handoff).toHaveBeenCalledWith(
"blob:download-1",
"invoice_exe.pdf",
);
expect(leases.activeLeaseCount).toBe(1);
expect(scheduledDelay).toBe(
DEFAULT_OBJECT_URL_RELEASE_GRACE_MS,
);
scheduled?.();
expect(leases.activeLeaseCount).toBe(0);
expect(revoked).toEqual(["blob:download-1"]);
});
it("captures object URL methods at registry construction", () => {
const originalCreate = vi.fn(() => "blob:captured");
const originalRevoke = vi.fn();
const replacedCreate = vi.fn(() => "blob:replaced");
const replacedRevoke = vi.fn();
const urlApi = {
createObjectURL: originalCreate,
revokeObjectURL: originalRevoke,
};
const leases = new ObjectUrlLeaseRegistry(urlApi);
urlApi.createObjectURL = replacedCreate;
urlApi.revokeObjectURL = replacedRevoke;
const lease = leases.create(new Blob(["safe"]));
lease.release();
expect(lease.url).toBe("blob:captured");
expect(originalCreate).toHaveBeenCalledOnce();
expect(originalRevoke).toHaveBeenCalledWith(
"blob:captured",
);
expect(replacedCreate).not.toHaveBeenCalled();
expect(replacedRevoke).not.toHaveBeenCalled();
});
it("rejects a stream that crosses the bounded Blob cap", async () => {
const handoff = vi.fn();
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff },
browserManagedCapabilities: capabilityResolver(),
userActivation: { isActive: true },
});
const input = {
...deliveryInput(
{
kind: "GENERATED" as const,
bytes: byteSource(
[new Uint8Array(10), new Uint8Array(10)],
null,
),
},
"BOUNDED_OBJECT_URL",
),
maxBufferedBytes: 16,
};
expect(await adapter.deliver(input)).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(handoff).not.toHaveBeenCalled();
});
it("rejects caller limits above runtime hard caps before side effects", async () => {
const handoff = vi.fn();
const showSaveFilePicker = vi.fn(async () => writableHandle([]));
const stream = vi.fn(
async function* (): AsyncIterable<
ReturnType<typeof successfulChunk>
> {
yield successfulChunk(new Uint8Array([1]));
},
);
const adapter = createDownloadDeliveryAdapter({
hardMaxObjectUrlBytes: 8,
hardMaxTransferBytes: 12,
policies,
host: { handoff },
browserManagedCapabilities: capabilityResolver(),
showSaveFilePicker,
userActivation: { isActive: true },
});
const source = {
kind: "GENERATED" as const,
bytes: { byteLength: null, stream },
};
expect(
await adapter.deliver({
...deliveryInput(source, "BOUNDED_OBJECT_URL"),
maxBufferedBytes: 9,
maxTransferBytes: 12,
}),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(
await adapter.deliver({
...deliveryInput(source, "PROMPT_AND_STREAM"),
maxBufferedBytes: 8,
maxTransferBytes: 13,
}),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(
await adapter.deliver({
...deliveryInput(
{
kind: "GENERATED",
bytes: { byteLength: 13, stream },
},
"PROMPT_AND_STREAM",
),
maxBufferedBytes: 8,
maxTransferBytes: 12,
}),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(stream).not.toHaveBeenCalled();
expect(showSaveFilePicker).not.toHaveBeenCalled();
expect(handoff).not.toHaveBeenCalled();
});
it("aborts streaming when actual bytes cross the transfer hard cap", async () => {
const events: string[] = [];
const adapter = createDownloadDeliveryAdapter({
hardMaxObjectUrlBytes: 8,
hardMaxTransferBytes: 8,
policies,
host: { handoff: vi.fn() },
browserManagedCapabilities: capabilityResolver(),
showSaveFilePicker: async () => writableHandle(events),
userActivation: { isActive: true },
});
expect(
await adapter.deliver({
...deliveryInput(
{
kind: "GENERATED",
bytes: byteSource(
[new Uint8Array(5), new Uint8Array(4)],
null,
),
},
"PROMPT_AND_STREAM",
),
maxBufferedBytes: 8,
maxTransferBytes: 8,
}),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(events).toEqual(["write:5", "abort"]);
});
it("treats a declared-length mismatch as integrity failure", async () => {
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff: vi.fn() },
browserManagedCapabilities: capabilityResolver(),
userActivation: { isActive: true },
});
expect(
await adapter.deliver(
deliveryInput(
{
kind: "GENERATED",
bytes: byteSource([new Uint8Array([1])], 2),
},
"BOUNDED_OBJECT_URL",
),
),
).toMatchObject({
ok: false,
error: { code: "INTEGRITY_FAILED" },
});
});
it("maps typed and defensive raw stream failures to closed download errors", async () => {
const adapter = createDownloadDeliveryAdapter({
...HARD_LIMITS,
policies,
host: { handoff: vi.fn() },
browserManagedCapabilities: capabilityResolver(),
userActivation: { isActive: true },
});
const common = {
...deliveryInput(
{
kind: "GENERATED" as const,
bytes: {
byteLength: null,
async *stream() {
yield {
ok: false as const,
error: {
code: "NOT_READABLE" as const,
operation: "FILE_READ" as const,
retryable: true,
recovery: "REOPEN" as const,
},
};
},
},
},
"BOUNDED_OBJECT_URL",
),
};
expect(await adapter.deliver(common)).toMatchObject({
ok: false,
error: {
code: "NOT_READABLE",
operation: "DOWNLOAD",
},
});
expect(
await adapter.deliver({
...common,
source: {
kind: "GENERATED",
bytes: {
byteLength: null,
async *stream(signal: AbortSignal) {
if (signal.aborted) {
yield {
ok: false as const,
error: {
code: "ABORTED" as const,
operation: "FILE_READ" as const,
retryable: false,
recovery: "NONE" as const,
},
};
return;
}
throw new DOMException(
"native detail",
"NotReadableError",
);
},
},
},
}),
).toMatchObject({
ok: false,
error: {
code: "NOT_READABLE",
operation: "DOWNLOAD",
},
});
});
});
+496
View File
@@ -0,0 +1,496 @@
// @vitest-environment jsdom
import { describe, expect, it, vi } from "vitest";
import {
DEFAULT_NATIVE_PICKER_FOCUS_GRACE_MS,
EnhancedFilePicker,
NativeInputFilePicker,
} from "../../src/adapters/browser-files/browser-file-picker.ts";
import { BrowserFileVault } from "../../src/adapters/browser-files/browser-file-vault.ts";
import {
BrowserFilePolicyRegistry,
browserFilePolicyReference,
} from "../../src/adapters/browser-files/browser-file-policy-registry.ts";
import type { RegisteredFileSelectionPolicy } from "../../src/adapters/browser-files/file-policy.ts";
const policyDefinition: RegisteredFileSelectionPolicy = Object.freeze({
policyId: "attachment-v1",
purpose: "attachment",
classification: "PERSONAL",
multiple: false,
maxCount: 1,
maxFileBytes: 100,
maxTotalBytes: 100,
allowEmpty: false,
accept: Object.freeze([
Object.freeze({
mediaType: "text/plain",
extensions: Object.freeze([".txt"]),
}),
]),
});
const policy = browserFilePolicyReference(
"attachments",
"select-text-attachment",
);
function createHarness(
selection: RegisteredFileSelectionPolicy = policyDefinition,
vaultOptions: Readonly<{
createReference?: () => string;
}> = {},
): Readonly<{
policies: BrowserFilePolicyRegistry;
vault: BrowserFileVault;
}> {
const policies = new BrowserFilePolicyRegistry({
profiles: [{ reference: policy, selection }],
hardLimits: {
maxInspectionBytes: 64 * 1024,
maxRetainedFileBytes: 1_024,
maxPreviewBytes: 1_024,
maxObjectUrlBytes: 1_024,
maxTransferBytes: 1_024,
},
});
return {
policies,
vault: new BrowserFileVault({ policies, ...vaultOptions }),
};
}
function labelledFileInput(): HTMLInputElement {
const label = document.createElement("label");
label.textContent = "Choose attachment";
const input = document.createElement("input");
input.type = "file";
label.append(input);
document.body.append(label);
return input;
}
describe("browser file pickers", () => {
it("treats native input cancellation as a normal dismissed outcome", async () => {
const input = labelledFileInput();
Object.defineProperty(input, "showPicker", {
configurable: true,
value: vi.fn(() => {
queueMicrotask(() => input.dispatchEvent(new Event("cancel")));
}),
});
const harness = createHarness();
const picker = new NativeInputFilePicker({
input,
...harness,
userActivation: { isActive: true },
});
expect(await picker.select({ policy })).toEqual({
ok: true,
value: { kind: "DISMISSED" },
});
});
it("resets the native input and supports same-file reselection", async () => {
const input = labelledFileInput();
const selected = new File(["hello"], "notes.txt", {
type: "text/plain",
lastModified: 1,
});
Object.defineProperty(input, "files", {
configurable: true,
value: [selected],
});
const showPicker = vi.fn(() => {
queueMicrotask(() => input.dispatchEvent(new Event("change")));
});
Object.defineProperty(input, "showPicker", {
configurable: true,
value: showPicker,
});
let sequence = 0;
const { policies, vault } = createHarness(policyDefinition, {
createReference: () => `file:${sequence++}`,
});
const picker = new NativeInputFilePicker({
input,
vault,
policies,
userActivation: { isActive: true },
});
const first = await picker.select({ policy });
const second = await picker.select({ policy });
expect(first).toMatchObject({
ok: true,
value: { kind: "SELECTED" },
});
expect(second).toMatchObject({
ok: true,
value: { kind: "SELECTED" },
});
expect(showPicker).toHaveBeenCalledTimes(2);
expect(input.value).toBe("");
expect(vault.activeReferenceCount).toBe(2);
});
it("snapshots selection policy before awaiting picker events", async () => {
const input = labelledFileInput();
Object.defineProperty(input, "showPicker", {
configurable: true,
value: vi.fn(),
});
const mutablePolicy = {
policyId: "mutable-v1",
purpose: "attachment",
classification: "PERSONAL" as const,
multiple: false,
maxCount: 1,
maxFileBytes: 100,
maxTotalBytes: 100,
allowEmpty: false,
accept: [
{ mediaType: "text/plain", extensions: [".txt"] },
],
};
const harness = createHarness(mutablePolicy, {
createReference: () => "file:snapshot",
});
const picker = new NativeInputFilePicker({
input,
...harness,
userActivation: { isActive: true },
});
const pending = picker.select({ policy });
mutablePolicy.maxFileBytes = 1;
mutablePolicy.maxTotalBytes = 1;
mutablePolicy.accept[0]!.extensions[0] = ".png";
Object.defineProperty(input, "files", {
configurable: true,
value: [
new File(["hello"], "notes.txt", {
type: "text/plain",
lastModified: 1,
}),
],
});
input.dispatchEvent(new Event("change"));
expect(await pending).toMatchObject({
ok: true,
value: { kind: "SELECTED" },
});
});
it("honors AbortSignal while a native dialog is pending", async () => {
const input = labelledFileInput();
Object.defineProperty(input, "showPicker", {
configurable: true,
value: vi.fn(),
});
const harness = createHarness();
const picker = new NativeInputFilePicker({
input,
...harness,
userActivation: { isActive: true },
});
const controller = new AbortController();
const pending = picker.select({ policy, signal: controller.signal });
controller.abort();
expect(await pending).toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
});
it("gives a late selected FileList a grace window after focus returns", async () => {
const input = labelledFileInput();
const selected = new File(["late"], "late.txt", {
type: "text/plain",
lastModified: 1,
});
let fallback: (() => void) | undefined;
let delay: number | undefined;
const clearTimeout = vi.fn();
Object.defineProperty(input, "showPicker", {
configurable: true,
value: vi.fn(() => {
window.dispatchEvent(new Event("focus"));
}),
});
const { policies, vault } = createHarness(policyDefinition, {
createReference: () => "file:late",
});
const picker = new NativeInputFilePicker({
input,
vault,
policies,
userActivation: { isActive: true },
scheduler: {
setTimeout(callback, delayMs) {
fallback = callback;
delay = delayMs;
return 1;
},
clearTimeout,
},
});
const pending = picker.select({ policy });
expect(delay).toBe(DEFAULT_NATIVE_PICKER_FOCUS_GRACE_MS);
Object.defineProperty(input, "files", {
configurable: true,
value: [selected],
});
fallback?.();
expect(await pending).toMatchObject({
ok: true,
value: {
kind: "SELECTED",
files: [{ displayName: "late.txt" }],
},
});
expect(vault.activeReferenceCount).toBe(1);
});
it("lets the native cancel event settle before the focus fallback", async () => {
const input = labelledFileInput();
let fallback: (() => void) | undefined;
const clearTimeout = vi.fn();
Object.defineProperty(input, "showPicker", {
configurable: true,
value: vi.fn(() => {
window.dispatchEvent(new Event("focus"));
input.dispatchEvent(new Event("cancel"));
}),
});
const { policies, vault } = createHarness();
const picker = new NativeInputFilePicker({
input,
vault,
policies,
userActivation: { isActive: true },
scheduler: {
setTimeout(callback) {
fallback = callback;
return 1;
},
clearTimeout,
},
});
expect(await picker.select({ policy })).toEqual({
ok: true,
value: { kind: "DISMISSED" },
});
expect(clearTimeout).toHaveBeenCalledWith(1);
fallback?.();
expect(vault.activeReferenceCount).toBe(0);
});
it("captures native input, window, and scheduler methods at construction", async () => {
const input = labelledFileInput();
let focus: EventListener | undefined;
const originalWindowAdd = vi.fn(
(_type: string, listener: EventListener) => {
focus = listener;
},
);
const originalWindowRemove = vi.fn();
const windowHost = {
addEventListener: originalWindowAdd,
removeEventListener: originalWindowRemove,
};
const originalSetTimeout = vi.fn(
(callback: () => void) => {
queueMicrotask(callback);
return 1;
},
);
const originalClearTimeout = vi.fn();
const scheduler = {
setTimeout: originalSetTimeout,
clearTimeout: originalClearTimeout,
};
const originalShowPicker = vi.fn(() => {
focus?.(new Event("focus"));
});
const replacedShowPicker = vi.fn();
Object.defineProperty(input, "showPicker", {
configurable: true,
writable: true,
value: originalShowPicker,
});
const harness = createHarness();
const picker = new NativeInputFilePicker({
input,
...harness,
window: windowHost as Pick<
Window,
"addEventListener" | "removeEventListener"
>,
scheduler,
userActivation: { isActive: true },
focusFallbackGraceMs: 0,
});
input.showPicker = replacedShowPicker;
windowHost.addEventListener = vi.fn();
windowHost.removeEventListener = vi.fn();
scheduler.setTimeout = vi.fn();
scheduler.clearTimeout = vi.fn();
expect(await picker.select({ policy })).toEqual({
ok: true,
value: { kind: "DISMISSED" },
});
expect(originalShowPicker).toHaveBeenCalledOnce();
expect(replacedShowPicker).not.toHaveBeenCalled();
expect(originalWindowAdd).toHaveBeenCalledOnce();
expect(originalWindowRemove).toHaveBeenCalledOnce();
expect(originalSetTimeout).toHaveBeenCalledOnce();
});
it("requires a connected and labelled file input", async () => {
const input = document.createElement("input");
input.type = "file";
const harness = createHarness();
const picker = new NativeInputFilePicker({
input,
...harness,
userActivation: { isActive: true },
});
expect(await picker.select({ policy })).toMatchObject({
ok: false,
error: { code: "INVALID_INPUT" },
});
});
it("keeps enhanced dismissal distinct from an active abort", async () => {
const dismissedHarness = createHarness();
const dismissed = new EnhancedFilePicker({
showOpenFilePicker: async () => {
throw new DOMException("closed", "AbortError");
},
...dismissedHarness,
userActivation: { isActive: true },
});
expect(await dismissed.select({ policy })).toEqual({
ok: true,
value: { kind: "DISMISSED" },
});
let rejectPicker:
| ((reason: DOMException) => void)
| undefined;
const controller = new AbortController();
const abortedHarness = createHarness();
const aborted = new EnhancedFilePicker({
showOpenFilePicker: () =>
new Promise((_, reject: (reason: DOMException) => void) => {
rejectPicker = reject;
}),
...abortedHarness,
userActivation: { isActive: true },
});
const pending = aborted.select({
policy,
signal: controller.signal,
});
controller.abort();
rejectPicker?.(new DOMException("closed", "AbortError"));
expect(await pending).toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
});
it("rejects excessive enhanced selections before reading handles", async () => {
const getFile = vi.fn(async () => new File(["a"], "a.txt"));
const multiplePolicy = {
...policyDefinition,
multiple: true,
maxCount: 1,
};
const harness = createHarness(multiplePolicy);
const picker = new EnhancedFilePicker({
showOpenFilePicker: async () => [
{ kind: "file", name: "a.txt", getFile },
{ kind: "file", name: "b.txt", getFile },
],
...harness,
userActivation: { isActive: true },
});
expect(await picker.select({ policy })).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(getFile).not.toHaveBeenCalled();
});
it("snapshots an enhanced picker request before awaiting the host", async () => {
let resolvePicker:
| ((handles: readonly {
kind: "file";
name: string;
getFile(): Promise<File>;
}[]) => void)
| undefined;
const harness = createHarness();
const picker = new EnhancedFilePicker({
showOpenFilePicker: () =>
new Promise((resolve) => {
resolvePicker = resolve;
}),
...harness,
userActivation: { isActive: true },
});
const originalController = new AbortController();
const replacementController = new AbortController();
const request = {
policy,
signal: originalController.signal,
};
const pending = picker.select(request);
(
request as {
policy: typeof policy;
signal: AbortSignal;
}
).policy = browserFilePolicyReference(
"forged",
"forged",
);
(
request as { signal: AbortSignal }
).signal = replacementController.signal;
replacementController.abort();
resolvePicker?.([
{
kind: "file",
name: "safe.txt",
async getFile() {
return new File(["safe"], "safe.txt", {
type: "text/plain",
lastModified: 1,
});
},
},
]);
expect(await pending).toMatchObject({
ok: true,
value: {
kind: "SELECTED",
files: [{ displayName: "safe.txt" }],
},
});
});
});
@@ -0,0 +1,281 @@
import { describe, expect, it, vi } from "vitest";
import {
BrowserFilePolicyRegistry,
browserFilePolicyReference,
} from "../../src/adapters/browser-files/browser-file-policy-registry.ts";
import { BrowserFileVault } from "../../src/adapters/browser-files/browser-file-vault.ts";
import {
BrowserTransientPreview,
ObjectUrlLeaseRegistry,
} from "../../src/adapters/browser-files/object-url-lease.ts";
const hardLimits = Object.freeze({
maxInspectionBytes: 64,
maxRetainedFileBytes: 1_024,
maxPreviewBytes: 1_024,
maxObjectUrlBytes: 1_024,
maxTransferBytes: 1_024,
});
describe("browser file composition policy registry", () => {
it("deep-snapshots every policy axis and permits only limit reductions", () => {
const reference = browserFilePolicyReference(
"documents",
"select-preview-download-pdf",
);
const selection = {
policyId: "documents-v1",
purpose: "document",
classification: "PERSONAL" as const,
multiple: false,
maxCount: 1,
maxFileBytes: 100,
maxTotalBytes: 100,
allowEmpty: false,
accept: [
{ mediaType: "application/pdf", extensions: [".pdf"] },
],
};
const inspection = {
policyId: "documents-v1",
maxInspectionBytes: 8,
acceptedSignatures: [
{
mediaType: "application/pdf",
extensions: [".pdf"],
patterns: [{ offset: 0, bytes: [0x25, 0x50] }],
},
],
};
const preview = {
allowedMediaTypes: ["application/pdf"],
maxPreviewBytes: 100,
};
const download = {
strategy: "PROMPT_AND_STREAM" as const,
mediaType: "application/pdf",
safeExtension: ".pdf",
maxTransferBytes: 100,
maxBufferedBytes: 50,
integrity: "REQUIRED" as const,
};
const registry = new BrowserFilePolicyRegistry({
profiles: [
{
reference,
selection,
inspection,
preview,
download,
},
],
hardLimits,
});
selection.maxFileBytes = 1;
selection.accept[0]!.extensions[0] = ".exe";
inspection.acceptedSignatures[0]!.patterns[0]!.bytes[0] = 0;
preview.allowedMediaTypes[0] = "text/html";
(
download as { strategy: string }
).strategy = "BROWSER_MANAGED";
download.safeExtension = ".exe";
expect(
registry.resolveSelection(reference, {
maxFileBytes: 80,
}),
).toMatchObject({
ok: true,
value: {
maxFileBytes: 80,
accept: [{ extensions: [".pdf"] }],
},
});
expect(
registry.resolveSelection(reference, {
maxFileBytes: 101,
}),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(registry.resolveInspection(reference)).toMatchObject({
ok: true,
value: {
acceptedSignatures: [
{ patterns: [{ bytes: [0x25, 0x50] }] },
],
},
});
const resolvedPreview = registry.resolvePreview(reference);
expect(resolvedPreview.ok).toBe(true);
if (resolvedPreview.ok) {
expect(
resolvedPreview.value.allowedMediaTypes.has(
"application/pdf",
),
).toBe(true);
expect(
resolvedPreview.value.allowedMediaTypes.has("text/html"),
).toBe(false);
(
resolvedPreview.value.allowedMediaTypes as Set<string>
).clear();
expect(
registry.resolvePreview(reference),
).toMatchObject({
ok: true,
value: {
allowedMediaTypes: new Set(["application/pdf"]),
},
});
}
expect(
registry.resolveDownload(reference, {}),
).toMatchObject({
ok: true,
value: {
strategy: "PROMPT_AND_STREAM",
safeExtension: ".pdf",
},
});
expect(
registry.resolveDownload(
browserFilePolicyReference("unknown", "unknown"),
{},
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(
registry.resolveSelection(
browserFilePolicyReference(
"documents",
"select-preview-download-pdf",
),
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(
registry.resolveSelection(
{
policyKey: "documents",
intention: "select-preview-download-pdf",
} as typeof reference,
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
});
it("binds verification receipts to the exact profile, not a reused policyId", async () => {
const profileA = browserFilePolicyReference(
"images-a",
"preview-avatar",
);
const profileB = browserFilePolicyReference(
"images-b",
"preview-banner",
);
const inspection = {
policyId: "shared-png-v1",
maxInspectionBytes: 8,
acceptedSignatures: [
{
mediaType: "image/png",
extensions: [".png"],
patterns: [{ offset: 0, bytes: [0x89, 0x50] }],
},
],
};
const policies = new BrowserFilePolicyRegistry({
profiles: [profileA, profileB].map((reference) => ({
reference,
inspection,
preview: {
allowedMediaTypes: ["image/png"],
maxPreviewBytes: 16,
},
})),
hardLimits,
});
const vault = new BrowserFileVault({
policies,
createReference: () => "file:profile-bound",
createVerificationReceipt: () =>
"verification:profile-bound",
});
const captured = vault.captureFiles(
[
new File(
[new Uint8Array([0x89, 0x50, 1])],
"avatar.png",
{ type: "image/png", lastModified: 1 },
),
],
{
policyId: "capture-v1",
purpose: "capture",
classification: "PERSONAL",
multiple: false,
maxCount: 1,
maxFileBytes: 16,
maxTotalBytes: 16,
allowEmpty: false,
accept: [
{ mediaType: "image/png", extensions: [".png"] },
],
},
);
expect(captured.ok).toBe(true);
if (!captured.ok || !captured.value[0]) return;
const inspected = await vault.inspect({
ref: captured.value[0].ref,
policy: profileA,
signal: new AbortController().signal,
});
expect(inspected.ok).toBe(true);
if (!inspected.ok || !inspected.value.verificationReceipt) {
return;
}
const createObjectURL = vi.fn(() => "blob:profile-bound");
const previews = new BrowserTransientPreview({
files: vault,
policies,
hardMaxPreviewBytes: 16,
leases: new ObjectUrlLeaseRegistry({
createObjectURL,
revokeObjectURL() {},
}),
});
expect(
await previews.create({
ref: captured.value[0].ref,
verificationReceipt:
inspected.value.verificationReceipt,
policy: profileB,
signal: new AbortController().signal,
}),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(createObjectURL).not.toHaveBeenCalled();
expect(
await previews.create({
ref: captured.value[0].ref,
verificationReceipt:
inspected.value.verificationReceipt,
policy: profileA,
signal: new AbortController().signal,
}),
).toMatchObject({ ok: true });
});
});
+304
View File
@@ -0,0 +1,304 @@
// @vitest-environment jsdom
import { describe, expect, it, vi } from "vitest";
import type {
FileVerificationReceipt,
LocalFileRef,
} from "../../src/application/ports/browser-file-storage/file.ts";
import {
browserFilePolicyReference,
createBrowserFileRuntime,
type BrowserFilePolicyProfile,
} from "../../src/adapters/browser-files/index.ts";
const filePolicy = browserFilePolicyReference(
"runtime-file",
"select-and-preview",
);
const downloadPolicy = browserFilePolicyReference(
"runtime-download",
"bounded-generated-bin",
);
const policies: readonly BrowserFilePolicyProfile[] = [
{
reference: filePolicy,
selection: {
policyId: "attachment-v1",
purpose: "attachment",
classification: "PERSONAL",
multiple: false,
maxCount: 1,
maxFileBytes: 8,
maxTotalBytes: 8,
allowEmpty: false,
accept: [{ mediaType: "text/plain", extensions: [".txt"] }],
},
inspection: {
policyId: "preview-v1",
maxInspectionBytes: 8,
acceptedSignatures: [
{
mediaType: "image/png",
extensions: [".png"],
patterns: [{ offset: 0, bytes: [0x89] }],
},
],
},
preview: {
allowedMediaTypes: ["image/png"],
maxPreviewBytes: 8,
},
},
{
reference: downloadPolicy,
download: {
strategy: "BOUNDED_OBJECT_URL",
mediaType: "application/octet-stream",
safeExtension: ".bin",
maxTransferBytes: 8,
maxBufferedBytes: 8,
integrity: "OPTIONAL",
},
},
];
const unusedBrowserManagedCapabilities = {
resolve() {
return {
ok: false as const,
error: {
code: "POLICY_REJECTED" as const,
operation: "DOWNLOAD" as const,
retryable: false,
recovery: "NONE" as const,
},
};
},
};
function fileInput(): HTMLInputElement {
const label = document.createElement("label");
label.textContent = "Attachment";
const input = document.createElement("input");
input.type = "file";
label.append(input);
document.body.append(label);
return input;
}
describe("browser file runtime hard limits and disposal", () => {
it("revokes active download leases immediately on runtime dispose", async () => {
const revoked: string[] = [];
let scheduled: (() => void) | undefined;
const runtime = createBrowserFileRuntime({
input: fileInput(),
policies,
limits: {
hardMaxPreviewBytes: 8,
hardMaxObjectUrlBytes: 8,
hardMaxTransferBytes: 16,
},
objectUrlApi: {
createObjectURL: () => "blob:runtime-download",
revokeObjectURL: (url) => revoked.push(url),
},
download: {
host: { handoff: vi.fn() },
browserManagedCapabilities:
unusedBrowserManagedCapabilities,
scheduler: {
setTimeout(callback) {
scheduled = callback;
return 1;
},
},
},
});
const delivery = {
policy: downloadPolicy,
source: {
kind: "GENERATED" as const,
bytes: {
byteLength: 3,
async *stream() {
yield {
ok: true as const,
value: new Uint8Array([1, 2, 3]),
};
},
},
},
suggestedFileName: "artifact",
maxTransferBytes: 8,
maxBufferedBytes: 8,
signal: new AbortController().signal,
onProgress() {},
};
expect(await runtime.downloads.deliver(delivery)).toMatchObject({
ok: true,
value: { kind: "BROWSER_HANDOFF" },
});
expect(revoked).toEqual([]);
runtime.dispose();
expect(revoked).toEqual(["blob:runtime-download"]);
expect(await runtime.downloads.deliver(delivery)).toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
expect(
await runtime.content.readRange({
ref: "file:disposed" as LocalFileRef,
offset: 0,
length: 0,
signal: new AbortController().signal,
}),
).toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
expect(
await runtime.previews.create({
ref: "file:disposed" as LocalFileRef,
verificationReceipt:
"verification:disposed" as FileVerificationReceipt,
policy: filePolicy,
maxPreviewBytes: 1,
signal: new AbortController().signal,
}),
).toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
scheduled?.();
runtime.dispose();
expect(revoked).toEqual(["blob:runtime-download"]);
});
it("aborts a pending native picker and prevents event resurrection", async () => {
const input = fileInput();
Object.defineProperty(input, "showPicker", {
configurable: true,
value: vi.fn(),
});
const runtime = createBrowserFileRuntime({
input,
policies,
limits: {
hardMaxPreviewBytes: 8,
hardMaxObjectUrlBytes: 8,
hardMaxTransferBytes: 16,
},
vault: {
createReference: () => "file:must-not-exist",
},
download: {
host: { handoff() {} },
browserManagedCapabilities:
unusedBrowserManagedCapabilities,
},
});
const pending = runtime.baselinePicker.select({
policy: filePolicy,
});
runtime.dispose();
expect(await pending).toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
Object.defineProperty(input, "files", {
configurable: true,
value: [
new File(["late"], "late.txt", {
type: "text/plain",
lastModified: 1,
}),
],
});
input.dispatchEvent(new Event("change"));
expect(
await runtime.baselinePicker.select({
policy: filePolicy,
}),
).toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
});
it("aborts a generated download whose producer ignores cancellation", async () => {
let reportStarted: (() => void) | undefined;
const started = new Promise<void>((resolve) => {
reportStarted = resolve;
});
let unblockProducer: (() => void) | undefined;
const producerBlock = new Promise<void>((resolve) => {
unblockProducer = resolve;
});
const runtime = createBrowserFileRuntime({
input: fileInput(),
policies,
limits: {
hardMaxPreviewBytes: 8,
hardMaxObjectUrlBytes: 8,
hardMaxTransferBytes: 16,
},
download: {
host: { handoff() {} },
browserManagedCapabilities:
unusedBrowserManagedCapabilities,
},
});
const pending = runtime.downloads.deliver({
policy: downloadPolicy,
source: {
kind: "GENERATED",
bytes: {
byteLength: 1,
async *stream() {
reportStarted?.();
await producerBlock;
yield {
ok: true as const,
value: new Uint8Array([1]),
};
},
},
},
suggestedFileName: "blocked",
maxTransferBytes: 8,
maxBufferedBytes: 8,
signal: new AbortController().signal,
onProgress() {},
});
await started;
runtime.dispose();
expect(await pending).toMatchObject({
ok: false,
error: { code: "ABORTED", operation: "DOWNLOAD" },
});
unblockProducer?.();
});
it("rejects inconsistent absolute limits during composition", () => {
expect(() =>
createBrowserFileRuntime({
input: fileInput(),
policies,
limits: {
hardMaxPreviewBytes: 8,
hardMaxObjectUrlBytes: 17,
hardMaxTransferBytes: 16,
},
download: {
host: { handoff() {} },
browserManagedCapabilities:
unusedBrowserManagedCapabilities,
},
}),
).toThrowError("Browser file runtime hard limits are invalid.");
});
});
@@ -0,0 +1,264 @@
import { describe, expect, it } from "vitest";
import {
composeBrowserRpcOperationRegistry,
composeBrowserRpcProviderProfileRegistry,
composeBrowserRpcRequestEncoderRegistry,
defineBrowserRpcOperation,
defineBrowserRpcProviderProfile,
validateBrowserRpcContractBindings,
type BrowserRpcProviderProfile,
} from "../../../src/contracts/browser-rpc.ts";
import {
DESCRIPTOR_DIGEST,
MAPPERS,
RUNTIME_DIGEST,
SCHEMA_CODECS,
STREAM_ENCODER,
UNARY_ENCODER,
streamOperation,
streamProfile,
unaryOperation,
unaryProfile,
} from "./fixture.ts";
describe("Browser RPC contract registry", () => {
it("closes exact operation, provider, schema, mapper and encoder bindings", () => {
const operation = unaryOperation();
const profile = unaryProfile();
const operations = composeBrowserRpcOperationRegistry([
{ GET_RPC_RESOURCE: operation },
]);
const profiles = composeBrowserRpcProviderProfileRegistry([
{ CONNECT_REFERENCE_UNARY: profile },
]);
const encoders = composeBrowserRpcRequestEncoderRegistry([
{ RpcResourceRequestEncoder: UNARY_ENCODER },
]);
expect(
validateBrowserRpcContractBindings({
operations,
profiles,
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: encoders,
}),
).toBe(true);
expect(Object.isFrozen(operations)).toBe(true);
expect(Object.isFrozen(profiles.CONNECT_REFERENCE_UNARY)).toBe(true);
expect(
Object.isFrozen(
profiles.CONNECT_REFERENCE_UNARY?.allowedProcedures,
),
).toBe(true);
});
it("rejects duplicate rows and descriptor/provider drift", () => {
const operation = unaryOperation();
expect(() =>
composeBrowserRpcOperationRegistry([
{ GET_RPC_RESOURCE: operation },
{ GET_RPC_RESOURCE: operation },
]),
).toThrow("duplicate Browser RPC operation");
expect(() =>
validateBrowserRpcContractBindings({
operations: { GET_RPC_RESOURCE: operation },
profiles: {
CONNECT_REFERENCE_UNARY: unaryProfile({
descriptorDigest: "c".repeat(64),
}),
},
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
}),
).toThrow("provider binding is invalid");
});
it("permits only a public headerless NO_SIDE_EFFECTS Connect GET", () => {
const getProfile = defineBrowserRpcProviderProfile({
...unaryProfile(),
runtimeProfileId: "CONNECT_PUBLIC_GET",
requestMethod: "GET",
authProfileId: "ANONYMOUS",
csrfProfileId: "NONE",
allowedProcedures: [
"example.resource.v1.ResourceService/GetResource",
],
});
const validGet = defineBrowserRpcOperation({
...unaryOperation(),
runtimeProfileId: "CONNECT_PUBLIC_GET",
authProfileId: "ANONYMOUS",
csrfProfileId: "NONE",
idempotencyLevel: "NO_SIDE_EFFECTS",
dataClassification: "PUBLIC",
});
expect(
validateBrowserRpcContractBindings({
operations: { GET_RPC_RESOURCE: validGet },
profiles: { CONNECT_PUBLIC_GET: getProfile },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
}),
).toBe(true);
expect(() =>
validateBrowserRpcContractBindings({
operations: {
GET_RPC_RESOURCE: defineBrowserRpcOperation({
...validGet,
dataClassification: "CONFIDENTIAL",
}),
},
profiles: { CONNECT_PUBLIC_GET: getProfile },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
}),
).toThrow("GET binding is invalid");
});
it("separates official grpc-web, Connect-Web and Connect tuples", () => {
expect(() =>
defineBrowserRpcProviderProfile({
...streamProfile(),
runtimeProfileId: "OFFICIAL_BINARY_STREAM",
runtimeId: "official-grpc-web",
runtimeVersion: "1.5.0",
runtimeDigest: RUNTIME_DIGEST,
protocol: "GRPC_WEB",
runtimeKind: "OFFICIAL_GRPC_WEB_XHR",
clientApiKind: "CALLBACK_STREAM",
messageEncoding: "PROTO",
framing: "GRPC_WEB_BINARY_ENVELOPE",
deadlineDialect: "OFFICIAL_DEADLINE_METADATA",
cancelDialect: "CLIENT_READABLE_STREAM_CANCEL",
descriptorDigest: DESCRIPTOR_DIGEST,
}),
).toThrow("provider profile is invalid");
expect(() =>
defineBrowserRpcProviderProfile({
...unaryProfile(),
framing: "GRPC_WEB_BINARY_ENVELOPE",
}),
).toThrow("provider profile is invalid");
expect(
defineBrowserRpcProviderProfile({
...unaryProfile(),
runtimeProfileId: "CONNECT_GRPC_WEB_UNARY",
protocol: "GRPC_WEB",
framing: "GRPC_WEB_BINARY_ENVELOPE",
deadlineDialect: "GRPC_TIMEOUT",
}),
).toMatchObject({
protocol: "GRPC_WEB",
runtimeKind: "CONNECT_WEB_FETCH",
deadlineDialect: "GRPC_TIMEOUT",
});
});
it("revalidates raw registry rows instead of trusting TypeScript assertions", () => {
const invalidProfile = {
...unaryProfile(),
messageEncoding: "XML",
} as unknown as BrowserRpcProviderProfile;
expect(() =>
validateBrowserRpcContractBindings({
operations: { GET_RPC_RESOURCE: unaryOperation() },
profiles: { CONNECT_REFERENCE_UNARY: invalidProfile },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
}),
).toThrow("provider profile is invalid");
expect(() =>
validateBrowserRpcContractBindings({
operations: { WRONG_REGISTRY_KEY: unaryOperation() },
profiles: { CONNECT_REFERENCE_UNARY: unaryProfile() },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
}),
).toThrow("operation registry is invalid");
});
it("disallows frontend stream retry and unsafe unary replay", () => {
expect(() =>
defineBrowserRpcProviderProfile({
...streamProfile(),
retryOwner: "FRONTEND_ADAPTER",
maxAttempts: 2,
backoffMs: [10],
retryableFailures: ["UNAVAILABLE"],
}),
).toThrow("provider profile is invalid");
const retryProfile = unaryProfile({
retryProfileId: "RPC_RETRY_TWO",
retryOwner: "FRONTEND_ADAPTER",
maxAttempts: 2,
backoffMs: [10],
retryableFailures: ["UNAVAILABLE"],
});
expect(() =>
validateBrowserRpcContractBindings({
operations: {
CREATE_RPC_RESOURCE: defineBrowserRpcOperation({
...unaryOperation(),
operationId: "CREATE_RPC_RESOURCE",
semantics: "COMMAND",
replayPolicy: "NON_REPLAYABLE",
runtimeProfileId: retryProfile.runtimeProfileId,
retryProfileId: retryProfile.retryProfileId,
}),
},
profiles: { CONNECT_REFERENCE_UNARY: retryProfile },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: {
...UNARY_ENCODER,
operationId: "CREATE_RPC_RESOURCE",
},
},
}),
).toThrow("retry binding is invalid");
});
it("supports a bounded Connect server-stream contract without composing it", () => {
expect(
validateBrowserRpcContractBindings({
operations: {
WATCH_RPC_RESOURCES: streamOperation(),
},
profiles: {
CONNECT_REFERENCE_STREAM: streamProfile(),
},
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceStreamRequestEncoder: STREAM_ENCODER,
},
}),
).toBe(true);
});
});
@@ -0,0 +1,451 @@
import { describe, expect, it } from "vitest";
import {
createBrowserRpcRuntime,
createUnavailableBrowserRpcTransport,
defineBrowserRpcTransport,
type BrowserRpcObservation,
type BrowserRpcStreamFrame,
type BrowserRpcTransport,
} from "../../../src/adapters/browser-rpc/index.ts";
import type { Result } from "../../../src/application/result.ts";
import type { AppFailure } from "../../../src/contracts/errors.ts";
import {
MAPPERS,
SCHEMA_CODECS,
STREAM_ENCODER,
UNARY_ENCODER,
isResourceView,
streamOperation,
streamProfile,
unaryOperation,
unaryProfile,
type ResourceView,
} from "./fixture.ts";
describe("Browser RPC provider-neutral runtime", () => {
it("validates, encodes, maps and admits only the typed unary result", async () => {
const observations: BrowserRpcObservation[] = [];
const transport = defineBrowserRpcTransport({
runtimeProfileId: "CONNECT_REFERENCE_UNARY",
providerId: "REFERENCE_RPC",
protocol: "CONNECT_HTTP",
rpcKind: "UNARY",
async invokeUnary(call) {
expect(call.request).toEqual({ resourceId: "resource-1" });
expect(call.timeoutMs).toBeGreaterThan(0);
return Object.freeze({
ok: true,
message: Object.freeze({ id: "resource-1", name: "Resource one" }),
encodedBytes: 48,
});
},
});
const port = unaryRuntime(transport, {
observe(value) {
observations.push(value);
},
}).bindUnary("GET_RPC_RESOURCE", isResourceView);
await expect(port.execute({ resourceId: "resource-1" })).resolves.toEqual({
ok: true,
value: { id: "resource-1", label: "Resource one" },
});
expect(observations).toEqual([
{
operationId: "GET_RPC_RESOURCE",
protocol: "CONNECT_HTTP",
runtimeProfileId: "CONNECT_REFERENCE_UNARY",
rpcKind: "UNARY",
outcome: "SUCCESS",
attemptCount: 1,
messageCount: 1,
},
]);
});
it("fails before transport on invalid input or unexpected idempotency metadata", async () => {
let calls = 0;
const transport = defineBrowserRpcTransport({
runtimeProfileId: "CONNECT_REFERENCE_UNARY",
providerId: "REFERENCE_RPC",
protocol: "CONNECT_HTTP",
rpcKind: "UNARY",
async invokeUnary() {
calls += 1;
return {
ok: false,
failure: { code: "UNAVAILABLE" },
};
},
});
const port = unaryRuntime(transport).bindUnary(
"GET_RPC_RESOURCE",
isResourceView,
);
const invalid = await port.execute({ resourceId: 42 });
expect(invalid).toMatchObject({
ok: false,
error: {
kind: "VALIDATION_REJECTED",
code: "RPC_REQUEST_SCHEMA_INVALID",
},
});
const metadata = await port.execute(
{ resourceId: "resource-1" },
{ idempotencyKey: "caller-key-is-not-allowed" },
);
expect(metadata).toMatchObject({
ok: false,
error: {
kind: "VALIDATION_REJECTED",
code: "RPC_IDEMPOTENCY_KEY_INVALID",
},
});
expect(calls).toBe(0);
});
it("keeps the frontend retry owner bounded by replay policy and one total deadline", async () => {
let calls = 0;
const profile = unaryProfile({
retryProfileId: "RPC_RETRY_TWO",
retryOwner: "FRONTEND_ADAPTER",
maxAttempts: 2,
backoffMs: [0],
retryableFailures: ["UNAVAILABLE"],
maxRetryAfterMs: 100,
});
const operation = unaryOperation({
retryProfileId: "RPC_RETRY_TWO",
});
const transport = defineBrowserRpcTransport({
runtimeProfileId: profile.runtimeProfileId,
providerId: profile.providerId,
protocol: profile.protocol,
rpcKind: profile.rpcKind,
async invokeUnary() {
calls += 1;
if (calls === 1) {
return {
ok: false,
failure: { code: "UNAVAILABLE" },
};
}
return {
ok: true,
message: { id: "resource-2", name: "Retried resource" },
encodedBytes: 32,
};
},
});
const runtime = createBrowserRpcRuntime({
operations: { GET_RPC_RESOURCE: operation },
profiles: { CONNECT_REFERENCE_UNARY: profile },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
transports: { CONNECT_REFERENCE_UNARY: transport },
});
await expect(
runtime
.bindUnary("GET_RPC_RESOURCE", isResourceView)
.execute({ resourceId: "resource-2" }),
).resolves.toMatchObject({
ok: true,
value: { id: "resource-2", label: "Retried resource" },
});
expect(calls).toBe(2);
});
it("drops a late unary result after its scope generation changes", async () => {
const transport = defineBrowserRpcTransport({
runtimeProfileId: "CONNECT_REFERENCE_UNARY",
providerId: "REFERENCE_RPC",
protocol: "CONNECT_HTTP",
rpcKind: "UNARY",
async invokeUnary() {
return {
ok: true,
message: { id: "late", name: "Late resource" },
encodedBytes: 24,
};
},
});
const runtime = createBrowserRpcRuntime({
...baseDependencies(transport),
generationFence: {
capture: () => 1,
isCurrent: () => false,
},
});
await expect(
runtime
.bindUnary("GET_RPC_RESOURCE", isResourceView)
.execute({ resourceId: "late" }),
).resolves.toMatchObject({
ok: false,
error: {
kind: "SCOPE_GENERATION_CHANGED",
code: "RPC_SCOPE_GENERATION_CHANGED",
},
});
});
it("uses an explicit unavailable adapter without network fallback", async () => {
const transport = createUnavailableBrowserRpcTransport({
runtimeProfileId: "CONNECT_REFERENCE_UNARY",
providerId: "REFERENCE_RPC",
protocol: "CONNECT_HTTP",
rpcKind: "UNARY",
});
const result = await unaryRuntime(transport)
.bindUnary("GET_RPC_RESOURCE", isResourceView)
.execute({ resourceId: "resource-1" });
expect(result).toMatchObject({
ok: false,
error: {
kind: "SERVER_FAILURE",
code: "RPC_UNAVAILABLE",
},
});
});
it("classifies valid oversized transport metadata as a response limit", async () => {
const transport = defineBrowserRpcTransport({
runtimeProfileId: "CONNECT_REFERENCE_UNARY",
providerId: "REFERENCE_RPC",
protocol: "CONNECT_HTTP",
rpcKind: "UNARY",
async invokeUnary() {
return {
ok: true,
message: { id: "large", name: "Large resource" },
encodedBytes: 4_097,
};
},
});
await expect(
unaryRuntime(transport)
.bindUnary("GET_RPC_RESOURCE", isResourceView)
.execute({ resourceId: "large" }),
).resolves.toMatchObject({
ok: false,
error: {
kind: "RESPONSE_BODY_LIMIT",
code: "RPC_RESPONSE_MESSAGE_LIMIT",
},
});
});
it("rejects transports that expose the wrong call shape", () => {
expect(() =>
defineBrowserRpcTransport({
runtimeProfileId: "CONNECT_REFERENCE_UNARY",
providerId: "REFERENCE_RPC",
protocol: "CONNECT_HTTP",
rpcKind: "UNARY",
async invokeUnary() {
return {
ok: false,
failure: { code: "UNAVAILABLE" },
};
},
async *openServerStream() {
yield Object.freeze({ kind: "TERMINAL", ok: true });
},
}),
).toThrow("transport is invalid");
});
it("commits mapped stream messages only before one valid terminal envelope", async () => {
const transport = streamTransport(async function* () {
yield message("resource-1", "One", 24);
yield message("resource-2", "Two", 24);
yield Object.freeze({ kind: "TERMINAL", ok: true });
});
const observations: BrowserRpcObservation[] = [];
const stream = streamRuntime(transport, {
observe(value) {
observations.push(value);
},
})
.bindServerStream("WATCH_RPC_RESOURCES", isResourceView)
.open({ resourceId: "scope-1" });
await expect(collect(stream)).resolves.toEqual([
{
ok: true,
value: { id: "resource-1", label: "One" },
},
{
ok: true,
value: { id: "resource-2", label: "Two" },
},
]);
expect(observations).toEqual([
{
operationId: "WATCH_RPC_RESOURCES",
protocol: "CONNECT_HTTP",
runtimeProfileId: "CONNECT_REFERENCE_STREAM",
rpcKind: "SERVER_STREAM",
outcome: "SUCCESS",
attemptCount: 1,
messageCount: 2,
},
]);
});
it("rejects EOF without terminal and data after terminal", async () => {
const missingTerminal = streamTransport(async function* () {
yield message("resource-1", "One", 24);
});
const missingResults = await collect(
streamRuntime(missingTerminal)
.bindServerStream("WATCH_RPC_RESOURCES", isResourceView)
.open({ resourceId: "scope-1" }),
);
expect(missingResults.at(-1)).toMatchObject({
ok: false,
error: {
kind: "API_CONTRACT_MISMATCH",
code: "RPC_PROTOCOL_MISMATCH",
},
});
const afterTerminal = streamTransport(async function* () {
yield Object.freeze({ kind: "TERMINAL", ok: true });
yield message("resource-2", "Two", 24);
});
await expect(
collect(
streamRuntime(afterTerminal)
.bindServerStream("WATCH_RPC_RESOURCES", isResourceView)
.open({ resourceId: "scope-1" }),
),
).resolves.toMatchObject([
{
ok: false,
error: {
kind: "API_CONTRACT_MISMATCH",
code: "RPC_PROTOCOL_MISMATCH",
},
},
]);
});
it("aborts the transport when stream message limits are exceeded", async () => {
let cleaned = false;
const transport = streamTransport(async function* (signal) {
try {
yield message("resource-1", "One", 24);
yield message("resource-2", "Two", 24);
yield Object.freeze({ kind: "TERMINAL", ok: true });
} finally {
cleaned = signal.aborted;
}
});
const operation = streamOperation({ maxResponseMessages: 1 });
const results = await collect(
streamRuntime(transport, undefined, operation)
.bindServerStream("WATCH_RPC_RESOURCES", isResourceView)
.open({ resourceId: "scope-1" }),
);
expect(results.at(-1)).toMatchObject({
ok: false,
error: {
kind: "RESPONSE_BODY_LIMIT",
code: "RPC_STREAM_MESSAGE_LIMIT",
},
});
expect(cleaned).toBe(true);
});
});
function unaryRuntime(
transport: BrowserRpcTransport,
observations?: Readonly<{
observe(value: BrowserRpcObservation): void;
}>,
) {
return createBrowserRpcRuntime({
...baseDependencies(transport),
observations,
});
}
function baseDependencies(transport: BrowserRpcTransport) {
return {
operations: { GET_RPC_RESOURCE: unaryOperation() },
profiles: { CONNECT_REFERENCE_UNARY: unaryProfile() },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceRequestEncoder: UNARY_ENCODER,
},
transports: { CONNECT_REFERENCE_UNARY: transport },
} as const;
}
function streamRuntime(
transport: BrowserRpcTransport,
observations?: Readonly<{
observe(value: BrowserRpcObservation): void;
}>,
operation = streamOperation(),
) {
return createBrowserRpcRuntime({
operations: { WATCH_RPC_RESOURCES: operation },
profiles: { CONNECT_REFERENCE_STREAM: streamProfile() },
schemaCodecs: SCHEMA_CODECS,
mappers: MAPPERS,
requestEncoders: {
RpcResourceStreamRequestEncoder: STREAM_ENCODER,
},
transports: { CONNECT_REFERENCE_STREAM: transport },
observations,
});
}
function streamTransport(
source: (
signal: AbortSignal,
) => AsyncIterable<BrowserRpcStreamFrame>,
): BrowserRpcTransport {
return defineBrowserRpcTransport({
runtimeProfileId: "CONNECT_REFERENCE_STREAM",
providerId: "REFERENCE_RPC",
protocol: "CONNECT_HTTP",
rpcKind: "SERVER_STREAM",
openServerStream(call) {
return source(call.signal);
},
});
}
function message(
id: string,
name: string,
encodedBytes: number,
): BrowserRpcStreamFrame {
return Object.freeze({
kind: "MESSAGE",
message: Object.freeze({ id, name }),
encodedBytes,
});
}
async function collect<Value>(
iterable: AsyncIterable<Result<Value, AppFailure>>,
): Promise<readonly Result<Value, AppFailure>[]> {
const values: Result<Value, AppFailure>[] = [];
for await (const value of iterable) values.push(value);
return values;
}
+241
View File
@@ -0,0 +1,241 @@
import {
defineBrowserRpcOperation,
defineBrowserRpcProviderProfile,
defineBrowserRpcRequestEncoder,
type BrowserRpcOperationV3,
type BrowserRpcProviderProfile,
} from "../../../src/contracts/browser-rpc.ts";
import {
mappingSuccess,
type InstalledBoundaryMapper,
} from "../../../src/contracts/boundary-mapper.ts";
import type { RuntimeSchemaCodec } from "../../../src/contracts/schema-registry.ts";
export const DESCRIPTOR_DIGEST = "a".repeat(64);
export const RUNTIME_DIGEST = "b".repeat(64);
export type ResourceView = Readonly<{ id: string; label: string }>;
export function unaryProfile(
overrides: Partial<BrowserRpcProviderProfile> = {},
): BrowserRpcProviderProfile {
return defineBrowserRpcProviderProfile({
runtimeProfileId: "CONNECT_REFERENCE_UNARY",
providerId: "REFERENCE_RPC",
fixedBaseUrl: "https://rpc.example.test/base/",
runtimeId: "connect-es-web",
runtimeVersion: "2.1.0",
runtimeDigest: RUNTIME_DIGEST,
protocol: "CONNECT_HTTP",
runtimeKind: "CONNECT_WEB_FETCH",
clientApiKind: "PROMISE_UNARY",
rpcKind: "UNARY",
messageEncoding: "PROTO",
framing: "CONNECT_BARE",
requestMethod: "POST",
descriptorArtifactId: "buf.example.resource.v1",
descriptorDigest: DESCRIPTOR_DIGEST,
allowedProcedures: ["example.resource.v1.ResourceService/GetResource"],
authProfileId: "REFERENCE_EXTERNAL_BEARER",
csrfProfileId: "NO_CSRF_BEARER",
corsProfileId: "SAME_ORIGIN_RPC",
errorProfileId: "CONNECT_ERROR_V1",
deadlineProfileId: "RPC_TOTAL_5S",
retryProfileId: "RPC_RETRY_NONE",
retryOwner: "NONE",
maxAttempts: 1,
backoffMs: [],
retryableFailures: [],
maxRetryAfterMs: 0,
deadlineDialect: "CONNECT_TIMEOUT_MS",
cancelDialect: "ABORT_SIGNAL",
rawByteCeilingOwner: "EDGE_AND_TRANSPORT",
streamMessageCompression: "IDENTITY_ONLY",
...overrides,
});
}
export function unaryOperation(
overrides: Partial<BrowserRpcOperationV3> = {},
): BrowserRpcOperationV3 {
return defineBrowserRpcOperation({
contractVersion: 3,
operationId: "GET_RPC_RESOURCE",
owner: "feature-browser-rpc-test",
protocol: "CONNECT_HTTP",
semantics: "QUERY",
replayPolicy: "SAFE",
idempotencyKeyPolicy: "NONE",
idempotencyLevel: "IDEMPOTENT",
dataClassification: "INTERNAL",
runtimeProfileId: "CONNECT_REFERENCE_UNARY",
providerId: "REFERENCE_RPC",
fullyQualifiedService: "example.resource.v1.ResourceService",
method: "GetResource",
rpcKind: "UNARY",
requestMessageId: "example.resource.v1.GetResourceRequest",
responseMessageId: "example.resource.v1.Resource",
descriptorArtifactId: "buf.example.resource.v1",
descriptorDigest: DESCRIPTOR_DIGEST,
requestSchemaId: "RpcResourceRequest",
responseSchemaId: "RpcResourceResponse",
requestEncoderId: "RpcResourceRequestEncoder",
mapperId: "RpcResourceMapper",
authProfileId: "REFERENCE_EXTERNAL_BEARER",
csrfProfileId: "NO_CSRF_BEARER",
errorProfileId: "CONNECT_ERROR_V1",
deadlineProfileId: "RPC_TOTAL_5S",
retryProfileId: "RPC_RETRY_NONE",
serverStateProfileId: "RpcResourceQuery",
maxRequestMessageBytes: 1_024,
maxResponseMessageBytes: 4_096,
maxResponseMessages: 1,
maxTotalResponseBytes: 4_096,
maxBufferedBytes: 4_096,
idleDeadlineMs: null,
totalDeadlineMs: 5_000,
...overrides,
});
}
export function streamProfile(
overrides: Partial<BrowserRpcProviderProfile> = {},
): BrowserRpcProviderProfile {
return defineBrowserRpcProviderProfile({
...unaryProfile(),
runtimeProfileId: "CONNECT_REFERENCE_STREAM",
clientApiKind: "ASYNC_ITERABLE",
rpcKind: "SERVER_STREAM",
framing: "CONNECT_ENVELOPE",
allowedProcedures: [
"example.resource.v1.ResourceService/WatchResources",
],
retryProfileId: "RPC_STREAM_RETRY_NONE",
...overrides,
});
}
export function streamOperation(
overrides: Partial<BrowserRpcOperationV3> = {},
): BrowserRpcOperationV3 {
return defineBrowserRpcOperation({
...unaryOperation(),
operationId: "WATCH_RPC_RESOURCES",
semantics: "SERVER_STREAM",
runtimeProfileId: "CONNECT_REFERENCE_STREAM",
method: "WatchResources",
rpcKind: "SERVER_STREAM",
requestEncoderId: "RpcResourceStreamRequestEncoder",
retryProfileId: "RPC_STREAM_RETRY_NONE",
serverStateProfileId: null,
maxResponseMessages: 4,
maxTotalResponseBytes: 16_384,
maxBufferedBytes: 8_192,
idleDeadlineMs: 1_000,
totalDeadlineMs: 10_000,
...overrides,
});
}
export const SCHEMA_CODECS = Object.freeze({
RpcResourceRequest: Object.freeze({
schemaId: "RpcResourceRequest",
parse(value: unknown) {
if (
value &&
typeof value === "object" &&
typeof (value as { resourceId?: unknown }).resourceId === "string"
) {
return Object.freeze({
success: true,
data: Object.freeze({
resourceId: (value as { resourceId: string }).resourceId,
}),
});
}
return Object.freeze({
success: false,
issues: Object.freeze([
Object.freeze({ path: "resourceId", code: "INVALID_STRING" }),
]),
});
},
}),
RpcResourceResponse: Object.freeze({
schemaId: "RpcResourceResponse",
parse(value: unknown) {
if (
value &&
typeof value === "object" &&
typeof (value as { id?: unknown }).id === "string" &&
typeof (value as { name?: unknown }).name === "string"
) {
return Object.freeze({
success: true,
data: Object.freeze({
id: (value as { id: string }).id,
name: (value as { name: string }).name,
}),
});
}
return Object.freeze({
success: false,
issues: Object.freeze([
Object.freeze({ path: "", code: "INVALID_RESOURCE" }),
]),
});
},
}),
} satisfies Readonly<Record<string, RuntimeSchemaCodec>>);
export const MAPPERS = Object.freeze({
RpcResourceMapper: Object.freeze({
mapperId: "RpcResourceMapper",
mapperVersion: 1,
inputSchemaId: "RpcResourceResponse",
outputContractId: "ResourceView",
owner: "feature-browser-rpc-test",
maxOutputItems: 1,
map(input: unknown) {
const resource = input as Readonly<{ id: string; name: string }>;
return mappingSuccess<ResourceView>(
Object.freeze({ id: resource.id, label: resource.name }),
);
},
}),
} satisfies Readonly<Record<string, InstalledBoundaryMapper>>);
export const UNARY_ENCODER = defineBrowserRpcRequestEncoder({
encoderId: "RpcResourceRequestEncoder",
operationId: "GET_RPC_RESOURCE",
encode(value) {
const request = value as Readonly<{ resourceId: string }>;
return Object.freeze({
ok: true,
value: Object.freeze({ resourceId: request.resourceId }),
encodedBytes: request.resourceId.length,
});
},
});
export const STREAM_ENCODER = defineBrowserRpcRequestEncoder({
encoderId: "RpcResourceStreamRequestEncoder",
operationId: "WATCH_RPC_RESOURCES",
encode(value) {
const request = value as Readonly<{ resourceId: string }>;
return Object.freeze({
ok: true,
value: Object.freeze({ resourceId: request.resourceId }),
encodedBytes: request.resourceId.length,
});
},
});
export function isResourceView(value: unknown): value is ResourceView {
return (
!!value &&
typeof value === "object" &&
typeof (value as { id?: unknown }).id === "string" &&
typeof (value as { label?: unknown }).label === "string"
);
}
+83
View File
@@ -0,0 +1,83 @@
import { describe, expect, it } from "vitest";
import {
assertValidStoragePolicy,
type BrowserStoragePolicy,
} from "../../src/application/ports/browser-file-storage/shared.ts";
const localFirstPolicy: BrowserStoragePolicy = Object.freeze({
owner: "frontend-platform",
namespace: "draft-attachments",
classification: "PERSONAL",
authority: "LOCAL_FIRST",
accountScope: "OPAQUE_PARTITION",
retention: Object.freeze({ kind: "UNTIL_SYNCED" }),
softBudgetBytes: 8 * 1024 * 1024,
hardBudgetBytes: 16 * 1024 * 1024,
evictionPriority: "USER_AUTHORED",
logoutAction: "EXPORT_THEN_PURGE",
accountDeletionAction: "PURGE_PARTITION",
pressureAction: "RETAIN",
unavailableFallback: "EXPORT_REQUIRED",
});
describe("browser storage policy", () => {
it("accepts a coherent dataset policy snapshot", () => {
expect(() => assertValidStoragePolicy(localFirstPolicy)).not.toThrow();
});
it("accepts explicitly evictable reconstructable origin data", () => {
expect(() =>
assertValidStoragePolicy({
...localFirstPolicy,
classification: "PUBLIC",
authority: "RECONSTRUCTABLE",
accountScope: "ORIGIN_SHARED",
retention: { kind: "TTL", maxAgeMs: 60_000 },
evictionPriority: "RECONSTRUCTABLE",
logoutAction: "KEEP_ORIGIN_SHARED",
accountDeletionAction: "KEEP_ORIGIN_SHARED",
pressureAction: "EVICT_RECONSTRUCTABLE",
unavailableFallback: "ONLINE_ONLY",
}),
).not.toThrow();
});
it.each([
{
name: "unsafe governance owner",
patch: { owner: " account@example.com " },
},
{
name: "personal origin-shared data",
patch: {
accountScope: "ORIGIN_SHARED",
logoutAction: "KEEP_ORIGIN_SHARED",
accountDeletionAction: "KEEP_ORIGIN_SHARED",
},
},
{
name: "partition without purge",
patch: { logoutAction: "KEEP_ORIGIN_SHARED" },
},
{
name: "unsynced partition without export",
patch: { logoutAction: "PURGE_PARTITION" },
},
{
name: "unsafe pressure eviction",
patch: { pressureAction: "EVICT_RECONSTRUCTABLE" },
},
{
name: "zero hard budget",
patch: { softBudgetBytes: 0, hardBudgetBytes: 0 },
},
] as const)("rejects $name", ({ patch }) => {
expect(() =>
assertValidStoragePolicy({
...localFirstPolicy,
...patch,
} as BrowserStoragePolicy),
).toThrow(TypeError);
});
});
+71
View File
@@ -0,0 +1,71 @@
import { describe, expect, it } from "vitest";
import {
assertCiBuildEnvironment,
buildDate,
ciCheckoutIdentityFailures,
ciBuildEnvironmentFailures,
isValidSourceDateEpoch,
} from "../../scripts/lib/build-environment.ts";
const validCiEnvironment = {
CI: "true",
VITE_BUILD_ID: "gitea-42-1",
VITE_COMMIT_SHA: "a".repeat(40),
RELEASE_ID: "refs/heads/develop-42-1",
CI_RUNNER_IMAGE: `registry.test/frontend-runner@sha256:${"b".repeat(64)}`,
SOURCE_DATE_EPOCH: "946684800",
};
describe("CI build environment", () => {
it("requires complete release identity only in CI", () => {
expect(ciBuildEnvironmentFailures({ CI: "false" })).toEqual([]);
expect(ciBuildEnvironmentFailures({ CI: "true" })).toEqual(
expect.arrayContaining([
"missing required CI build environment: VITE_BUILD_ID",
"missing required CI build environment: VITE_COMMIT_SHA",
"missing required CI build environment: RELEASE_ID",
"missing required CI build environment: CI_RUNNER_IMAGE",
"missing required CI build environment: SOURCE_DATE_EPOCH",
]),
);
expect(() => assertCiBuildEnvironment(validCiEnvironment)).not.toThrow();
});
it("rejects abbreviated commit IDs and invalid epochs", () => {
expect(
ciBuildEnvironmentFailures({
...validCiEnvironment,
VITE_COMMIT_SHA: "abc123",
SOURCE_DATE_EPOCH: "-1",
CI_RUNNER_IMAGE: "ubuntu-latest-node24",
}),
).toEqual(
expect.arrayContaining([
"VITE_COMMIT_SHA must be a full 40- or 64-character hexadecimal commit ID",
"SOURCE_DATE_EPOCH must be non-negative epoch seconds",
"CI_RUNNER_IMAGE must end with an immutable @sha256 image digest",
]),
);
});
it("binds the configured identity and timestamp to the checkout", () => {
expect(
ciCheckoutIdentityFailures(validCiEnvironment, {
commitSha: "b".repeat(40),
sourceDateEpoch: "946684801",
}),
).toEqual([
"VITE_COMMIT_SHA does not identify the checked-out commit",
"SOURCE_DATE_EPOCH does not match the checked-out commit timestamp",
]);
});
it("uses SOURCE_DATE_EPOCH as the deterministic build timestamp", () => {
expect(isValidSourceDateEpoch("946684800")).toBe(true);
expect(isValidSourceDateEpoch("not-an-epoch")).toBe(false);
expect(buildDate(validCiEnvironment).toISOString()).toBe(
"2000-01-01T00:00:00.000Z",
);
});
});
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { classifyViteJavascript } from "../../scripts/lib/classify-vite-bundle.mjs";
import { classifyViteJavascript } from "../../scripts/lib/classify-vite-bundle.ts";
describe("Vite bundle classification", () => {
it("counts transitive static imports as initial and keeps dynamic chunks lazy", () => {
+6 -6
View File
@@ -1,10 +1,10 @@
import { describe, expect, it, vi } from "vitest";
import { createApplication } from "../../src/application/create-application.js";
import { createAnonymousSessionAdapter } from "../../src/adapters/auth/external-session-adapter.js";
import type { StoragePort } from "../../src/application/ports/storage-port.js";
import type { DiagnosticsPort } from "../../src/application/ports/diagnostics-port.js";
import type { TelemetryPort } from "../../src/application/ports/telemetry-port.js";
import { createApplication } from "../../src/application/create-application.ts";
import { createAnonymousSessionAdapter } from "../../src/adapters/auth/external-session-adapter.ts";
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";
type ReleaseFixture = {
buildId: string;
@@ -125,7 +125,7 @@ describe("production chunk recovery application input", () => {
read: () => ({
ok: false as const,
error: {
kind: "STORAGE_UNAVAILABLE",
kind: "STORAGE_UNAVAILABLE" as const,
code: "STORAGE_UNAVAILABLE",
retryable: false,
operationId: "STORAGE",
@@ -1,24 +1,24 @@
import { describe, expect, it } from "vitest";
import { decideChunkRecovery } from "../../src/application/use-cases/decide-chunk-recovery.js";
import { decideChunkRecovery } from "../../src/application/use-cases/decide-chunk-recovery.ts";
import type { StoragePort } from "../../src/application/ports/storage-port.ts";
function memoryStorage() {
/** @type {unknown} */
let value;
return /** @type {import("../../src/application/ports/storage-port.js").StoragePort} */ ({
read: () => ({ ok: /** @type {const} */ (true), value }),
write: (_key, next) => {
function memoryStorage(): StoragePort {
let value: unknown;
return {
read: () => ({ ok: true, value }),
write: (_key: string, next: unknown) => {
value = next;
return { ok: /** @type {const} */ (true) };
return { ok: true };
},
remove: () => ({ ok: /** @type {const} */ (true) }),
});
remove: () => ({ ok: true }),
};
}
describe("controlled chunk recovery", () => {
it("records the release pair before allowing one reload", () => {
const storage = memoryStorage();
const input = {
const input: Parameters<typeof decideChunkRecovery>[0] = {
failureKind: "CHUNK_LOAD_FAILURE",
manifestLoaded: true,
currentBuildId: "build-a",
@@ -5,8 +5,8 @@ import { describe, expect, it } from "vitest";
import {
normalizeColorSchemePreference,
resolveColorScheme,
} from "../../src/application/policies/color-scheme.js";
import { initializeColorScheme } from "../../src/bootstrap/initialize-color-scheme.js";
} from "../../src/application/policies/color-scheme.ts";
import { initializeColorScheme } from "../../src/bootstrap/initialize-color-scheme.ts";
describe("color scheme policy", () => {
it("normalizes unknown persisted values to the safe system default", () => {
@@ -25,8 +25,7 @@ describe("color scheme policy", () => {
getColorScheme: () => "system",
}, {
documentElement: document.documentElement,
matchMedia: () =>
/** @type {MediaQueryList} */ ({ matches: true }),
matchMedia: () => ({ matches: true }) as MediaQueryList,
});
expect(result).toEqual({ preference: "system", resolved: "dark" });
@@ -5,7 +5,7 @@ import {
isVersionCompatible,
parseNumericVersion,
verifyCompatibilityTuple,
} from "../../src/application/policies/compatibility.js";
} from "../../src/application/policies/compatibility.ts";
describe("contract compatibility", () => {
it("uses numeric version parsing rather than lexical comparison", () => {
@@ -0,0 +1,63 @@
import { describe, expect, it } from "vitest";
import { createConditionalValidatorStore } from "../../src/adapters/query-cache/conditional-validator-store.ts";
import { createRuntimeIdentityRegistry } from "../../src/contracts/query-keys.ts";
function scope() {
let current = true;
return {
snapshot: {
generation: 3,
fingerprint: "scope-token-00000003",
identities: createRuntimeIdentityRegistry({
tokenFactory: () => crypto.randomUUID(),
}),
isCurrent: () => current,
},
expire: () => {
current = false;
},
};
}
describe("conditional validator CAS sidecar", () => {
it("accepts 304 only for the exact scope, representation and cache revision", () => {
const selectedScope = scope();
const binding = {
definitionId: "resource-detail-v1",
identityToken: "identity-token-00000001",
representationVersion: 1,
scope: selectedScope.snapshot,
};
const store = createConditionalValidatorStore();
expect(store.install(binding, '"etag-a"', 7)).toBe(true);
expect(store.prepare(binding, 7)).toBe('"etag-a"');
expect(store.acceptNotModified(binding, 7, true)).toBe(true);
expect(store.acceptNotModified(binding, 8, true)).toBe(false);
expect(store.acceptNotModified(binding, 7, false)).toBe(false);
selectedScope.expire();
expect(store.prepare(binding, 7)).toBeNull();
expect(store.acceptNotModified(binding, 7, true)).toBe(false);
});
it("rejects malformed validators and bounded-capacity overflow", () => {
const firstScope = scope();
const store = createConditionalValidatorStore(1);
const first = {
definitionId: "resource-detail-v1",
identityToken: "identity-token-00000001",
representationVersion: 1,
scope: firstScope.snapshot,
};
expect(store.install(first, "raw-etag", 1)).toBe(false);
expect(store.install(first, 'W/"etag-a"', 1)).toBe(true);
expect(
store.install(
{ ...first, identityToken: "identity-token-00000002" },
'"etag-b"',
1,
),
).toBe(false);
});
});
+626
View File
@@ -0,0 +1,626 @@
import { describe, expect, it, vi } from "vitest";
import {
CACHE_INVALIDATION_PROTOCOL_VERSION,
CACHE_INVALIDATION_WIRE_LIMITS,
decodeCacheInvalidationWireEvent,
parseCacheInvalidationWireEvent,
type CacheInvalidationWireEvent,
} from "../../src/contracts/cache-invalidation.ts";
import {
createBrowserCrossContextInvalidation,
type BroadcastChannelFacade,
type BroadcastMessageListener,
type BrowserCrossContextInvalidationDependencies,
type CrossContextInvalidationDelivery,
type CrossContextInvalidationObservation,
type StorageEventTargetFacade,
type StoragePulseFacade,
type StoragePulseListener,
} from "../../src/adapters/cross-context-invalidation/index.ts";
const NOW = 1_000_000;
const CACHE_EPOCH = "cache-epoch-0001";
const TOPIC = "reference-resources";
const CHANNEL_NAME = "cache-invalidation-v1";
const STORAGE_PULSE_KEY = "ca-frontend:cache-invalidation:v1:pulse";
class FakeBroadcastNetwork {
readonly channels: FakeBroadcastChannel[] = [];
readonly messages: unknown[] = [];
createChannel = (name: string): FakeBroadcastChannel => {
const channel = new FakeBroadcastChannel(name, this);
this.channels.push(channel);
return channel;
};
emit(value: unknown): void {
this.messages.push(value);
for (const channel of this.channels) channel.emit(value);
}
}
class FakeBroadcastChannel implements BroadcastChannelFacade {
readonly listeners = new Set<BroadcastMessageListener>();
closed = false;
failPost = false;
closeCount = 0;
removeCount = 0;
constructor(
readonly name: string,
private readonly network: FakeBroadcastNetwork,
) {}
postMessage(value: unknown): void {
if (this.closed || this.failPost) {
throw new DOMException("Broadcast failed", "InvalidStateError");
}
// Deliberately includes the sender. Native BroadcastChannel can still
// deliver through another same-context channel, so the adapter must use
// source identity rather than relying on provider echo behavior.
this.network.emit(value);
}
addEventListener(
_type: "message",
listener: BroadcastMessageListener,
): void {
this.listeners.add(listener);
}
removeEventListener(
_type: "message",
listener: BroadcastMessageListener,
): void {
this.removeCount += 1;
this.listeners.delete(listener);
}
close(): void {
this.closeCount += 1;
this.closed = true;
this.listeners.clear();
}
emit(value: unknown): void {
if (this.closed) return;
for (const listener of [...this.listeners]) {
listener({ data: value });
}
}
}
class FakeStorageEventTarget implements StorageEventTargetFacade {
readonly listeners = new Set<StoragePulseListener>();
removeCount = 0;
addEventListener(
_type: "storage",
listener: StoragePulseListener,
): void {
this.listeners.add(listener);
}
removeEventListener(
_type: "storage",
listener: StoragePulseListener,
): void {
this.removeCount += 1;
this.listeners.delete(listener);
}
emit(key: string | null, newValue: string | null): void {
for (const listener of [...this.listeners]) {
listener({ key, newValue });
}
}
}
class FakeStorageBus {
readonly values = new Map<string, string>();
readonly targets = new Set<FakeStorageEventTarget>();
createEndpoint(): Readonly<{
storage: FakeStorageEndpoint;
target: FakeStorageEventTarget;
}> {
const target = new FakeStorageEventTarget();
this.targets.add(target);
return Object.freeze({
storage: new FakeStorageEndpoint(this, target),
target,
});
}
set(
owner: FakeStorageEventTarget,
key: string,
value: string,
): void {
this.values.set(key, value);
for (const target of this.targets) {
if (target !== owner) target.emit(key, value);
}
}
remove(owner: FakeStorageEventTarget, key: string): void {
this.values.delete(key);
for (const target of this.targets) {
if (target !== owner) target.emit(key, null);
}
}
}
class FakeStorageEndpoint implements StoragePulseFacade {
failSet = false;
failRemove = false;
setCount = 0;
removeCount = 0;
constructor(
private readonly bus: FakeStorageBus,
private readonly owner: FakeStorageEventTarget,
) {}
setItem(key: string, value: string): void {
this.setCount += 1;
if (this.failSet) {
throw new DOMException("Storage denied", "SecurityError");
}
this.bus.set(this.owner, key, value);
}
removeItem(key: string): void {
this.removeCount += 1;
if (this.failRemove) {
throw new DOMException("Storage denied", "SecurityError");
}
this.bus.remove(this.owner, key);
}
}
function dependencies(
id: string,
overrides: Partial<BrowserCrossContextInvalidationDependencies> = {},
): BrowserCrossContextInvalidationDependencies {
let eventNumber = 0;
return {
channelName: CHANNEL_NAME,
storagePulseKey: STORAGE_PULSE_KEY,
sourceId: `${id}-source`,
sourceEpoch: `${id}-epoch`,
cacheEpoch: CACHE_EPOCH,
topics: [{ topic: TOPIC, topicVersion: 1 }],
createEventId: () =>
`${id}-event-${String(++eventNumber).padStart(8, "0")}`,
nowEpochMilliseconds: () => NOW,
...overrides,
};
}
function wireEvent(
overrides: Partial<CacheInvalidationWireEvent> = {},
): CacheInvalidationWireEvent {
return {
protocolVersion: CACHE_INVALIDATION_PROTOCOL_VERSION,
eventId: "remote-event-00000001",
sourceId: "remote-source-0000001",
sourceEpoch: "remote-epoch-0000001",
sequence: 1,
cacheEpoch: CACHE_EPOCH,
topic: TOPIC,
topicVersion: 1,
emittedAt: NOW,
expiresAt: NOW + 60_000,
...overrides,
};
}
describe("cache invalidation wire contract", () => {
const policy = {
cacheEpoch: CACHE_EPOCH,
topicVersions: { [TOPIC]: 1 },
nowEpochMilliseconds: NOW,
} as const;
it("accepts only the exact payload-free, query-key-free envelope", () => {
const accepted = parseCacheInvalidationWireEvent(wireEvent(), policy);
expect(accepted).toMatchObject({ ok: true });
if (!accepted.ok) throw new Error("Expected a valid event");
expect(Object.keys(accepted.value).sort()).toEqual([
"cacheEpoch",
"emittedAt",
"eventId",
"expiresAt",
"protocolVersion",
"sequence",
"sourceEpoch",
"sourceId",
"topic",
"topicVersion",
]);
expect(accepted.value).not.toHaveProperty("payload");
expect(accepted.value).not.toHaveProperty("queryKey");
expect(
parseCacheInvalidationWireEvent(
{ ...wireEvent(), payload: { secret: "must-not-cross" } },
policy,
),
).toEqual({ ok: false, reason: "INVALID_ENVELOPE" });
expect(
parseCacheInvalidationWireEvent(
{ ...wireEvent(), queryKey: ["resource", "sensitive-id"] },
policy,
),
).toEqual({ ok: false, reason: "INVALID_ENVELOPE" });
});
it("bounds bytes, protocol, allowlist, cache epoch and event lifetime", () => {
expect(
decodeCacheInvalidationWireEvent("{not-json", policy),
).toEqual({ ok: false, reason: "MALFORMED_JSON" });
expect(
decodeCacheInvalidationWireEvent(
"x".repeat(
CACHE_INVALIDATION_WIRE_LIMITS.maxWireBytes + 1,
),
policy,
),
).toEqual({ ok: false, reason: "OVERSIZED" });
expect(
parseCacheInvalidationWireEvent(
{ ...wireEvent(), protocolVersion: 2 },
policy,
),
).toEqual({ ok: false, reason: "PROTOCOL_MISMATCH" });
expect(
parseCacheInvalidationWireEvent(
{ ...wireEvent(), topic: "unknown-topic" },
policy,
),
).toEqual({ ok: false, reason: "TOPIC_REJECTED" });
expect(
parseCacheInvalidationWireEvent(
{ ...wireEvent(), topicVersion: 2 },
policy,
),
).toEqual({ ok: false, reason: "TOPIC_REJECTED" });
expect(
parseCacheInvalidationWireEvent(
{ ...wireEvent(), cacheEpoch: "other-cache-epoch" },
policy,
),
).toEqual({
ok: false,
reason: "CACHE_EPOCH_MISMATCH",
});
expect(
parseCacheInvalidationWireEvent(
wireEvent({ emittedAt: NOW - 60_000, expiresAt: NOW }),
policy,
),
).toEqual({ ok: false, reason: "EXPIRED" });
});
});
describe("browser cross-context invalidation transport", () => {
it("publishes one exact BroadcastChannel event and filters self echo", () => {
const network = new FakeBroadcastNetwork();
const first = createBrowserCrossContextInvalidation(
dependencies("tab-a", {
createBroadcastChannel: network.createChannel,
}),
);
const second = createBrowserCrossContextInvalidation(
dependencies("tab-b", {
createBroadcastChannel: network.createChannel,
}),
);
const local = vi.fn();
const remote = vi.fn();
first.subscribe(local);
second.subscribe(remote);
expect(first.getStatus()).toBe("ACTIVE_BROADCAST");
expect(first.publish({ topic: TOPIC, topicVersion: 1 })).toEqual({
ok: true,
transport: "BROADCAST",
});
expect(local).not.toHaveBeenCalled();
expect(remote).toHaveBeenCalledOnce();
expect(remote.mock.calls[0]?.[0]).toMatchObject({
ordering: "NEXT",
transport: "BROADCAST",
event: {
protocolVersion: 1,
sequence: 1,
cacheEpoch: CACHE_EPOCH,
topic: TOPIC,
topicVersion: 1,
},
});
expect(network.messages).toHaveLength(1);
expect(JSON.stringify(network.messages[0])).not.toMatch(
/payload|queryKey|sensitive/,
);
first.close();
second.close();
});
it("de-duplicates the same event delivered by both transports", () => {
const network = new FakeBroadcastNetwork();
const storage = new FakeStorageBus().createEndpoint();
const runtime = createBrowserCrossContextInvalidation(
dependencies("tab-b", {
createBroadcastChannel: network.createChannel,
storage: storage.storage,
storageEvents: storage.target,
}),
);
const received = vi.fn();
runtime.subscribe(received);
const event = wireEvent();
network.emit(event);
storage.target.emit(STORAGE_PULSE_KEY, JSON.stringify(event));
expect(received).toHaveBeenCalledOnce();
expect(received.mock.calls[0]?.[0]).toMatchObject({
transport: "BROADCAST",
ordering: "NEXT",
});
runtime.close();
});
it("reports per-source gaps and drops stale out-of-order events", () => {
const network = new FakeBroadcastNetwork();
const observations: CrossContextInvalidationObservation[] = [];
const runtime = createBrowserCrossContextInvalidation(
dependencies("tab-b", {
createBroadcastChannel: network.createChannel,
observe: (observation) => observations.push(observation),
}),
);
const delivered: CrossContextInvalidationDelivery[] = [];
runtime.subscribe((delivery) => delivered.push(delivery));
network.emit(wireEvent({ eventId: "remote-event-00000001", sequence: 1 }));
network.emit(wireEvent({ eventId: "remote-event-00000003", sequence: 3 }));
network.emit(wireEvent({ eventId: "remote-event-00000002", sequence: 2 }));
network.emit(
wireEvent({
eventId: "remote-new-epoch-event",
sourceEpoch: "remote-epoch-0000002",
sequence: 1,
}),
);
expect(
delivered.map(({ event, ordering }) => [
event.sequence,
ordering,
]),
).toEqual([
[1, "NEXT"],
[3, "GAP"],
[1, "NEXT"],
]);
expect(observations).toContainEqual({
operation: "RECEIVE",
outcome: "DROPPED",
transport: "BROADCAST",
reason: "STALE",
});
runtime.close();
});
it("falls back when BroadcastChannel open or publish fails", () => {
const storageBus = new FakeStorageBus();
const firstStorage = storageBus.createEndpoint();
const secondStorage = storageBus.createEndpoint();
const firstNetwork = new FakeBroadcastNetwork();
const secondNetwork = new FakeBroadcastNetwork();
const first = createBrowserCrossContextInvalidation(
dependencies("tab-a", {
createBroadcastChannel: firstNetwork.createChannel,
storage: firstStorage.storage,
storageEvents: firstStorage.target,
}),
);
const second = createBrowserCrossContextInvalidation(
dependencies("tab-b", {
createBroadcastChannel: () => {
throw new DOMException("Denied", "SecurityError");
},
storage: secondStorage.storage,
storageEvents: secondStorage.target,
}),
);
firstNetwork.channels[0]!.failPost = true;
const received = vi.fn();
second.subscribe(received);
expect(second.getStatus()).toBe("ACTIVE_STORAGE_FALLBACK");
expect(first.publish({ topic: TOPIC, topicVersion: 1 })).toEqual({
ok: true,
transport: "STORAGE",
});
expect(first.getStatus()).toBe("ACTIVE_STORAGE_FALLBACK");
expect(received).toHaveBeenCalledOnce();
expect(firstStorage.storage.setCount).toBe(1);
expect(firstStorage.storage.removeCount).toBe(1);
expect(storageBus.values.has(STORAGE_PULSE_KEY)).toBe(false);
expect(secondNetwork.channels).toHaveLength(0);
first.close();
second.close();
});
it("enters explicit local-only degradation when every transport fails", () => {
const storageBus = new FakeStorageBus();
const endpoint = storageBus.createEndpoint();
endpoint.storage.failSet = true;
const observations: CrossContextInvalidationObservation[] = [];
const runtime = createBrowserCrossContextInvalidation(
dependencies("tab-a", {
createBroadcastChannel: () => {
throw new DOMException("Denied", "SecurityError");
},
storage: endpoint.storage,
storageEvents: endpoint.target,
observe: (observation) => observations.push(observation),
}),
);
expect(runtime.getStatus()).toBe("ACTIVE_STORAGE_FALLBACK");
expect(runtime.publish({ topic: TOPIC, topicVersion: 1 })).toEqual({
ok: false,
reason: "TRANSPORT_UNAVAILABLE",
});
expect(runtime.getStatus()).toBe("DEGRADED_LOCAL_ONLY");
expect(observations).toContainEqual({
operation: "PUBLISH",
outcome: "DEGRADED",
transport: "STORAGE",
reason: "STORAGE_PUBLISH_FAILED",
});
runtime.close();
});
it("isolates handler and diagnostics failures without leaking identifiers", () => {
const network = new FakeBroadcastNetwork();
const observations: CrossContextInvalidationObservation[] = [];
const runtime = createBrowserCrossContextInvalidation(
dependencies("tab-b", {
createBroadcastChannel: network.createChannel,
observe(observation) {
observations.push(observation);
if (observation.reason === "HANDLER_FAILED") {
throw new Error("diagnostics unavailable");
}
},
}),
);
const healthy = vi.fn();
runtime.subscribe(() => {
throw new Error("listener secret");
});
runtime.subscribe(healthy);
network.emit(
wireEvent({
eventId: "sensitive-event-identifier",
topic: TOPIC,
}),
);
expect(healthy).toHaveBeenCalledOnce();
expect(observations.some(({ reason }) => reason === "HANDLER_FAILED")).toBe(
true,
);
expect(JSON.stringify(observations)).not.toMatch(
/sensitive-event-identifier|reference-resources|cache-epoch-0001/,
);
runtime.close();
});
it("bounds TTL and LRU tracking instead of growing with remote sources", () => {
const network = new FakeBroadcastNetwork();
let currentTime = NOW;
const runtime = createBrowserCrossContextInvalidation(
dependencies("tab-b", {
createBroadcastChannel: network.createChannel,
nowEpochMilliseconds: () => currentTime,
dedupeCapacity: 2,
sourceCapacity: 2,
}),
);
const received = vi.fn();
runtime.subscribe(received);
const distinctSource = (
index: number,
eventId: string,
emittedAt = currentTime,
) =>
wireEvent({
eventId,
sourceId: `remote-source-${index}`,
sourceEpoch: `remote-epoch-${index}`,
emittedAt,
expiresAt: emittedAt + 1_000,
});
network.emit(distinctSource(1, "bounded-event-1"));
network.emit(distinctSource(2, "bounded-event-2"));
network.emit(distinctSource(3, "bounded-event-3"));
network.emit(distinctSource(1, "bounded-event-1"));
expect(received).toHaveBeenCalledTimes(4);
currentTime += 1_001;
network.emit(
distinctSource(3, "bounded-event-3", currentTime),
);
expect(received).toHaveBeenCalledTimes(5);
runtime.close();
});
it("cleans up listeners once and rejects queued work after close", () => {
const network = new FakeBroadcastNetwork();
const storage = new FakeStorageBus().createEndpoint();
const runtime = createBrowserCrossContextInvalidation(
dependencies("tab-b", {
createBroadcastChannel: network.createChannel,
storage: storage.storage,
storageEvents: storage.target,
}),
);
const received = vi.fn();
const unsubscribe = runtime.subscribe(received);
unsubscribe();
unsubscribe();
runtime.subscribe(received);
runtime.close();
runtime.close();
network.emit(wireEvent());
storage.target.emit(
STORAGE_PULSE_KEY,
JSON.stringify(wireEvent()),
);
expect(runtime.getStatus()).toBe("CLOSED");
expect(runtime.publish({ topic: TOPIC, topicVersion: 1 })).toEqual({
ok: false,
reason: "CLOSED",
});
expect(received).not.toHaveBeenCalled();
expect(network.channels[0]?.closeCount).toBe(1);
expect(network.channels[0]?.removeCount).toBe(1);
expect(storage.target.removeCount).toBe(1);
expect(storage.target.listeners.size).toBe(0);
});
it("fails invalid publish inputs closed without touching a provider", () => {
const network = new FakeBroadcastNetwork();
const runtime = createBrowserCrossContextInvalidation(
dependencies("tab-a", {
createBroadcastChannel: network.createChannel,
}),
);
expect(
runtime.publish({ topic: "not-allowlisted", topicVersion: 1 }),
).toEqual({ ok: false, reason: "INVALID_EVENT" });
expect(
runtime.publish({ topic: TOPIC, topicVersion: 2 }),
).toEqual({ ok: false, reason: "INVALID_EVENT" });
expect(network.messages).toHaveLength(0);
runtime.close();
});
});
@@ -0,0 +1,121 @@
import { describe, expect, it, vi } from "vitest";
import { createCursorPaginationRuntime } from "../../src/adapters/query-cache/cursor-pagination-runtime.ts";
const profile = {
profileId: "bounded-cursor-v1",
maxPages: 3,
maxTotalItems: 4,
maxEstimatedBytes: 1_024,
maxCursorBytes: 64,
allowSparsePage: false,
} as const;
describe("bounded cursor pagination runtime", () => {
it("loads a stable finite chain without exposing cursors in its value", async () => {
const loadPage = vi
.fn()
.mockResolvedValueOnce({
ok: true,
value: {
items: ["one"],
nextCursor: "cursor-2",
hasMore: true,
snapshotToken: "snapshot-a",
},
})
.mockResolvedValueOnce({
ok: true,
value: {
items: ["two"],
nextCursor: null,
hasMore: false,
snapshotToken: "snapshot-a",
},
});
const runtime = createCursorPaginationRuntime({
definitionId: "LIST_ALL",
profile,
loadPage,
});
await expect(runtime.loadAll({})).resolves.toEqual({
ok: true,
value: ["one", "two"],
});
expect(loadPage.mock.calls.map(([cursor]) => cursor)).toEqual([
null,
"cursor-2",
]);
});
it.each([
{
page: {
items: [],
nextCursor: "next",
hasMore: true,
snapshotToken: null,
},
code: "PAGINATION_PAGE_INVALID",
},
{
page: {
items: ["one"],
nextCursor: null,
hasMore: true,
snapshotToken: null,
},
code: "PAGINATION_PAGE_INVALID",
},
])("rejects invalid page invariants", async ({ page, code }) => {
const runtime = createCursorPaginationRuntime({
definitionId: "LIST_ALL",
profile,
loadPage: async () => ({ ok: true, value: page }),
});
await expect(runtime.loadAll({})).resolves.toMatchObject({
ok: false,
error: { kind: "PAGINATION_CONTRACT_VIOLATION", code },
});
});
it("rejects cursor loops and snapshot drift", async () => {
const loop = createCursorPaginationRuntime({
definitionId: "LIST_ALL",
profile,
loadPage: async () => ({
ok: true,
value: {
items: ["one"],
nextCursor: "same",
hasMore: true,
snapshotToken: null,
},
}),
});
await expect(loop.loadAll({})).resolves.toMatchObject({
ok: false,
error: { code: "PAGINATION_CURSOR_LOOP" },
});
let page = 0;
const drift = createCursorPaginationRuntime({
definitionId: "LIST_ALL",
profile,
loadPage: async () => ({
ok: true,
value: {
items: [String(page)],
nextCursor: page++ === 0 ? "next" : null,
hasMore: page === 1,
snapshotToken: page === 1 ? "snapshot-a" : "snapshot-b",
},
}),
});
await expect(drift.loadAll({})).resolves.toMatchObject({
ok: false,
error: { code: "PAGINATION_SNAPSHOT_CHANGED" },
});
});
});
+1 -1
View File
@@ -6,7 +6,7 @@ import {
REQUIRED_COMPONENT_TOKENS,
REQUIRED_PRIMITIVE_TOKENS,
REQUIRED_SEMANTIC_TOKENS,
} from "../../src/presentation/design-system/index.js";
} from "../../src/presentation/design-system/index.ts";
describe("design token contract", () => {
it("defines every registered token in its owning layer", async () => {
+2 -2
View File
@@ -5,11 +5,11 @@ import {
getLastBootEvidence,
noOpDiagnostics,
recordBootFailure,
} from "../../src/adapters/diagnostics/bounded-diagnostics.js";
} from "../../src/adapters/diagnostics/bounded-diagnostics.ts";
import {
projectDiagnosticRecord,
safeErrorKind,
} from "../../src/contracts/diagnostics.js";
} from "../../src/contracts/diagnostics.ts";
describe("structured diagnostics contract", () => {
it("projects only registered, bounded context with deterministic time", () => {
@@ -5,11 +5,11 @@ import {
createFailure,
kindForStatus,
normalizeUnknownFailure,
} from "../../src/contracts/errors.js";
} from "../../src/contracts/errors.ts";
describe("frontend failure classification", () => {
it("defines all 26 stable error kinds with the seven contract fields", () => {
expect(Object.keys(ERROR_REGISTRY)).toHaveLength(31);
it("defines all stable error kinds with the seven contract fields", () => {
expect(Object.keys(ERROR_REGISTRY)).toHaveLength(38);
for (const definition of Object.values(ERROR_REGISTRY)) {
expect(definition).toEqual(
expect.objectContaining({
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { validateFieldEvidenceInput } from "../../scripts/lib/field-vitals-evidence.mjs";
import { validateFieldEvidenceInput } from "../../scripts/lib/field-vitals-evidence.ts";
const input = {
schemaVersion: 1,
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { classifyLiveHostingBaseUrl } from "../../scripts/lib/hosting-probe.mjs";
import { classifyLiveHostingBaseUrl } from "../../scripts/lib/hosting-probe.ts";
describe("live hosting evidence target", () => {
it("accepts a canonical production HTTPS root", () => {
+2 -2
View File
@@ -14,12 +14,12 @@ import {
resolveMessage,
selectMessage,
selectPlural,
} from "../../src/presentation/i18n/index.js";
} from "../../src/presentation/i18n/index.ts";
import {
EN_MESSAGES,
KO_MESSAGES,
MESSAGE_CATALOGS,
} from "../../src/presentation/i18n/catalog.js";
} from "../../src/presentation/i18n/catalog.ts";
describe("internationalization message contract", () => {
it("keeps every registered catalog and interpolation contract in parity", () => {
File diff suppressed because it is too large Load Diff
+811
View File
@@ -0,0 +1,811 @@
import { describe, expect, it, vi } from "vitest";
import { createIndexedDbMaintenance } from "../../src/adapters/storage/indexeddb/indexeddb-maintenance.ts";
import { createIndexedDbRuntime } from "../../src/adapters/storage/indexeddb/indexeddb-runtime.ts";
import type {
IndexedDbDataMigrationPolicy,
IndexedDbObservation,
} from "../../src/adapters/storage/indexeddb/indexeddb-types.ts";
import { MemoryIndexedDbFactory } from "../helpers/memory-indexeddb.ts";
type CurrentPayload = Readonly<{
label: string;
}>;
const TEST_SCOPE = Object.freeze({
authorityToken: "authoritytoken_002",
namespaceToken: "namespacetoken_002",
partitionToken: "partitiontoken_002",
accountScope: "ORIGIN_SHARED" as const,
});
const TEST_STORAGE_POLICY = Object.freeze({
owner: "platform-storage",
namespace: "indexeddb-maintenance-test",
classification: "INTERNAL" as const,
authority: "SERVER" as const,
accountScope: "ORIGIN_SHARED" as const,
retention: Object.freeze({ kind: "EXPLICIT_DELETE" as const }),
softBudgetBytes: 1_000_000,
hardBudgetBytes: 2_000_000,
evictionPriority: "SYNCED_COPY" as const,
logoutAction: "KEEP_ORIGIN_SHARED" as const,
accountDeletionAction: "KEEP_ORIGIN_SHARED" as const,
pressureAction: "RETAIN" as const,
unavailableFallback: "ONLINE_ONLY" as const,
});
function createSchemaRuntime(memory: MemoryIndexedDbFactory) {
return createIndexedDbRuntime<CurrentPayload, CurrentPayload, null>({
scope: TEST_SCOPE,
storagePolicy: TEST_STORAGE_POLICY,
schemaVersion: 1,
recordStore: "records",
governanceStore: "governance",
retentionStore: "retention",
retentionEligibilityIndex: "by-eligibility",
lifecycleMetadataStores: ["maintenance"],
idempotencyStore: "receipts",
idempotencyExpiryIndex: "by-expiry",
receiptRetentionMs: 60_000,
maxIdempotencyReceipts: 1_000,
migrations: [
{
id: "schema-v1",
fromVersion: 0,
toVersion: 1,
operations: [
{
kind: "CREATE_STORE",
name: "governance",
keyPath: "bindingKey",
},
{
kind: "CREATE_STORE",
name: "retention",
keyPath: "recordKey",
indexes: [
{
name: "by-eligibility",
keyPath: "eligibleAtEpochMs",
},
],
},
{
kind: "CREATE_STORE",
name: "records",
keyPath: "key",
},
{
kind: "CREATE_STORE",
name: "receipts",
keyPath: "idempotencyKey",
indexes: [
{
name: "by-expiry",
keyPath: "expiresAtEpochMs",
},
],
},
{
kind: "CREATE_STORE",
name: "maintenance",
keyPath: "checkpointKey",
},
],
},
],
codec: {
currentVersion: 2,
encode: (value) => ({ ok: true, value }),
measureStoredBytes: (value) =>
new TextEncoder().encode(JSON.stringify(value)).byteLength,
decode: (version, value) =>
version === 2 &&
value !== null &&
typeof value === "object" &&
typeof (value as Partial<CurrentPayload>).label === "string"
? { ok: true, value: value as CurrentPayload }
: { ok: false },
fingerprint: () => "0".repeat(64),
},
queryPolicy: {
plan: () => ({ limit: 10 }),
},
factory: memory.factory,
authorizeLifecycle: () => ({
authorized: true,
proofToken: "authorityproof_002",
}),
});
}
async function prepareSchema(
memory: MemoryIndexedDbFactory,
): Promise<void> {
const runtime = createSchemaRuntime(memory);
expect(await runtime.open()).toMatchObject({ ok: true });
runtime.close();
}
function seedLegacy(
memory: MemoryIndexedDbFactory,
key: string,
legacyLabel: string,
revision = 1,
): void {
const measuredBytes =
new TextEncoder().encode(JSON.stringify({ label: legacyLabel }))
.byteLength +
512 +
key.length * 2;
memory.seed("records", {
key,
codecVersion: 1,
revision,
payload: { legacyLabel },
});
memory.seed("retention", {
recordKey: key,
writtenAtEpochMs: 1,
synchronization: "NONE",
measuredBytes,
});
const budget = memory.readRaw(
"governance",
"dataset-budget",
) as Readonly<{
bindingKey: string;
budgetVersion: number;
usedBytes: number;
receiptCount: number;
}>;
memory.seed("governance", {
...budget,
usedBytes: budget.usedBytes + measuredBytes,
});
}
function defaultPolicy(
migrate = vi.fn(
async ({ payload }: { payload: unknown }) => {
const legacyLabel =
payload &&
typeof payload === "object" &&
typeof (
payload as Readonly<{ legacyLabel?: unknown }>
).legacyLabel === "string"
? (
payload as Readonly<{ legacyLabel: string }>
).legacyLabel
: null;
return legacyLabel === null
? ({ ok: false } as const)
: ({
ok: true,
value: { label: legacyLabel },
} as const);
},
),
): IndexedDbDataMigrationPolicy<CurrentPayload> {
return {
migrationId: "records-to-codec-v2",
targetCodecVersion: 2,
measureStoredBytes: (value) =>
new TextEncoder().encode(JSON.stringify(value)).byteLength,
isOldWriterDrainConfirmed: () => true,
migrate,
};
}
function createMaintenance(
memory: MemoryIndexedDbFactory,
policy: IndexedDbDataMigrationPolicy<CurrentPayload>,
options: Readonly<{
now?: () => number;
nowEpochMilliseconds?: () => number;
observe?: (event: IndexedDbObservation) => void;
}> = {},
) {
return createIndexedDbMaintenance<CurrentPayload>({
scope: TEST_SCOPE,
storagePolicy: TEST_STORAGE_POLICY,
schemaVersion: 1,
recordStore: "records",
governanceStore: "governance",
retentionStore: "retention",
checkpointStore: "maintenance",
checkpointKey: "records-codec",
idempotencyStore: "receipts",
idempotencyExpiryIndex: "by-expiry",
migrationPolicy: policy,
factory: memory.factory,
keyRange: memory.keyRange,
...options,
});
}
function seedReceipt(
memory: MemoryIndexedDbFactory,
idempotencyKey: string,
expiresAtEpochMs: number,
): void {
memory.seed("receipts", {
idempotencyKey,
operation: "PUT",
recordKey: `record-${idempotencyKey}`,
expectedRevision: null,
fingerprint: "a".repeat(64),
synchronization: "NONE",
revision: 1,
expiresAtEpochMs,
});
const budget = memory.readRaw(
"governance",
"dataset-budget",
) as Readonly<{
bindingKey: string;
budgetVersion: number;
usedBytes: number;
receiptCount: number;
}>;
memory.seed("governance", {
...budget,
receiptCount: budget.receiptCount + 1,
});
}
async function waitForWriteTransaction(
memory: MemoryIndexedDbFactory,
): Promise<void> {
for (let attempt = 0; attempt < 20; attempt += 1) {
if (memory.lastTransaction?.mode === "readwrite") return;
await Promise.resolve();
}
throw new Error("Timed out waiting for receipt prune transaction.");
}
describe("IndexedDB bounded codec maintenance", () => {
it("refuses keyset migration until old-codec writers are durably drained", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedLegacy(memory, "a-before-checkpoint", "old-writer");
const policy = {
...defaultPolicy(),
isOldWriterDrainConfirmed: () => false,
};
const maintenance = createMaintenance(memory, policy);
expect(
await maintenance.migrateCodecBatch({
maxRows: 10,
maxDurationMs: 10_000,
}),
).toEqual({
ok: false,
error: {
code: "BLOCKED",
operation: "INDEXEDDB_MIGRATE",
retryable: true,
recovery: "RELOAD_OTHER_CONTEXTS",
},
});
expect(policy.migrate).not.toHaveBeenCalled();
expect(
memory.readRaw("maintenance", "records-codec"),
).toBeUndefined();
expect(memory.readRaw("records", "a-before-checkpoint")).toMatchObject({
codecVersion: 1,
});
});
it("resumes from a durable checkpoint and completes in bounded row batches", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedLegacy(memory, "a", "alpha", 4);
seedLegacy(memory, "b", "beta", 7);
seedLegacy(memory, "c", "gamma", 9);
const observations: IndexedDbObservation[] = [];
const policy = defaultPolicy();
const maintenance = createMaintenance(memory, policy, {
observe: (event) => observations.push(event),
});
expect(
await maintenance.migrateCodecBatch({
maxRows: 2,
maxDurationMs: 10_000,
}),
).toEqual({
ok: true,
value: {
state: "MORE",
scannedRows: 2,
checkpointedRows: 2,
migratedRows: 2,
concurrentlyChangedRows: 0,
budgetExhausted: false,
},
});
expect(memory.readRaw("maintenance", "records-codec")).toEqual({
checkpointKey: "records-codec",
migrationId: "records-to-codec-v2",
targetCodecVersion: 2,
lastKey: "b",
state: "MORE",
});
expect(
await maintenance.migrateCodecBatch({
maxRows: 2,
maxDurationMs: 10_000,
}),
).toEqual({
ok: true,
value: {
state: "COMPLETE",
scannedRows: 1,
checkpointedRows: 1,
migratedRows: 1,
concurrentlyChangedRows: 0,
budgetExhausted: false,
},
});
expect(
await maintenance.migrateCodecBatch({
maxRows: 2,
maxDurationMs: 10_000,
}),
).toEqual({
ok: true,
value: {
state: "COMPLETE",
scannedRows: 0,
checkpointedRows: 0,
migratedRows: 0,
concurrentlyChangedRows: 0,
budgetExhausted: false,
},
});
expect(policy.migrate).toHaveBeenCalledTimes(3);
expect(memory.readRaw("records", "a")).toEqual({
key: "a",
codecVersion: 2,
revision: 4,
payload: { label: "alpha" },
});
expect(memory.readRaw("records", "c")).toEqual({
key: "c",
codecVersion: 2,
revision: 9,
payload: { label: "gamma" },
});
const migratedBudget = memory.readRaw(
"governance",
"dataset-budget",
) as Readonly<{ usedBytes: number }>;
const migratedSidecars = ["a", "b", "c"].map(
(key) =>
memory.readRaw(
"retention",
key,
) as Readonly<{ measuredBytes: number }>,
);
expect(migratedBudget.usedBytes).toBe(
migratedSidecars.reduce(
(total, row) => total + row.measuredBytes,
0,
),
);
expect(JSON.stringify(observations)).not.toMatch(
/alpha|beta|gamma|records-codec/,
);
});
it("commits migrated records and their checkpoint atomically", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedLegacy(memory, "atomic", "before");
const maintenance = createMaintenance(
memory,
defaultPolicy(),
);
memory.failNextWriteCommit(
new DOMException("private payload", "QuotaExceededError"),
);
expect(
await maintenance.migrateCodecBatch({
maxRows: 10,
maxDurationMs: 10_000,
}),
).toMatchObject({
ok: false,
error: {
code: "QUOTA_EXCEEDED",
operation: "INDEXEDDB_MIGRATE",
},
});
expect(memory.readRaw("records", "atomic")).toEqual({
key: "atomic",
codecVersion: 1,
revision: 1,
payload: { legacyLabel: "before" },
});
expect(
memory.readRaw("maintenance", "records-codec"),
).toBeUndefined();
expect(
await maintenance.migrateCodecBatch({
maxRows: 10,
maxDurationMs: 10_000,
}),
).toMatchObject({
ok: true,
value: { state: "COMPLETE", migratedRows: 1 },
});
});
it("runs async domain transforms outside transactions and honors abort before commit", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedLegacy(memory, "abort", "unchanged");
let entered: (() => void) | undefined;
let release: (() => void) | undefined;
const transformStarted = new Promise<void>((resolve) => {
entered = resolve;
});
const transformGate = new Promise<void>((resolve) => {
release = resolve;
});
const policy = defaultPolicy(
vi.fn(async () => {
entered?.();
await transformGate;
return {
ok: true,
value: { label: "must-not-commit" },
} as const;
}),
);
const maintenance = createMaintenance(memory, policy);
const controller = new AbortController();
const pending = maintenance.migrateCodecBatch({
maxRows: 10,
maxDurationMs: 10_000,
signal: controller.signal,
});
await transformStarted;
controller.abort();
release?.();
expect(await pending).toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
expect(memory.readRaw("records", "abort")).toEqual({
key: "abort",
codecVersion: 1,
revision: 1,
payload: { legacyLabel: "unchanged" },
});
expect(
memory.readRaw("maintenance", "records-codec"),
).toBeUndefined();
});
it("rechecks revision and codec fencing before every migrated write", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedLegacy(memory, "concurrent", "first", 1);
let changed = false;
const policy = defaultPolicy(
vi.fn(async () => {
if (!changed) {
changed = true;
seedLegacy(memory, "concurrent", "newer", 2);
}
return {
ok: true,
value: { label: changed ? "newer" : "first" },
} as const;
}),
);
const maintenance = createMaintenance(memory, policy);
expect(
await maintenance.migrateCodecBatch({
maxRows: 10,
maxDurationMs: 10_000,
}),
).toEqual({
ok: true,
value: {
state: "MORE",
scannedRows: 1,
checkpointedRows: 0,
migratedRows: 0,
concurrentlyChangedRows: 1,
budgetExhausted: false,
},
});
expect(memory.readRaw("records", "concurrent")).toEqual({
key: "concurrent",
codecVersion: 1,
revision: 2,
payload: { legacyLabel: "newer" },
});
expect(
await maintenance.migrateCodecBatch({
maxRows: 10,
maxDurationMs: 10_000,
}),
).toMatchObject({
ok: true,
value: {
state: "COMPLETE",
migratedRows: 1,
},
});
expect(memory.readRaw("records", "concurrent")).toEqual({
key: "concurrent",
codecVersion: 2,
revision: 2,
payload: { label: "newer" },
});
});
it("stops before transform work when its cooperative time budget is exhausted", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedLegacy(memory, "budget", "later");
const policy = defaultPolicy();
let currentTime = 0;
const maintenance = createMaintenance(memory, policy, {
now: () => {
const value = currentTime;
currentTime += 5;
return value;
},
});
expect(
await maintenance.migrateCodecBatch({
maxRows: 10,
maxDurationMs: 1,
}),
).toEqual({
ok: true,
value: {
state: "MORE",
scannedRows: 0,
checkpointedRows: 0,
migratedRows: 0,
concurrentlyChangedRows: 0,
budgetExhausted: true,
},
});
expect(policy.migrate).not.toHaveBeenCalled();
expect(memory.readRaw("records", "budget")).toMatchObject({
codecVersion: 1,
revision: 1,
});
});
it("fails closed when a historical payload cannot be transformed", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedLegacy(memory, "invalid", "value");
const maintenance = createMaintenance(
memory,
defaultPolicy(vi.fn(async () => ({ ok: false } as const))),
);
expect(
await maintenance.migrateCodecBatch({
maxRows: 10,
maxDurationMs: 10_000,
}),
).toEqual({
ok: false,
error: {
code: "MIGRATION_FAILED",
operation: "INDEXEDDB_MIGRATE",
retryable: false,
recovery: "READ_ONLY",
},
});
expect(memory.readRaw("records", "invalid")).toMatchObject({
codecVersion: 1,
});
});
it("rejects a size-increasing migration that exceeds the dataset hard budget", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedLegacy(memory, "oversized", "legacy");
const beforeBudget = memory.readRaw(
"governance",
"dataset-budget",
);
const policy = {
...defaultPolicy(),
measureStoredBytes: () => 2_000_000,
};
const maintenance = createMaintenance(memory, policy);
expect(
await maintenance.migrateCodecBatch({
maxRows: 10,
maxDurationMs: 10_000,
}),
).toMatchObject({
ok: false,
error: {
code: "MIGRATION_FAILED",
recovery: "READ_ONLY",
},
});
expect(memory.readRaw("records", "oversized")).toMatchObject({
codecVersion: 1,
});
expect(
memory.readRaw("governance", "dataset-budget"),
).toEqual(beforeBudget);
});
it("prunes only expired receipts and bounds each committed batch", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedReceipt(memory, "expired-a", 100);
seedReceipt(memory, "expired-b", 200);
seedReceipt(memory, "expires-now", 300);
seedReceipt(memory, "inside-replay-window", 301);
const maintenance = createMaintenance(
memory,
defaultPolicy(),
{ nowEpochMilliseconds: () => 300 },
);
expect(
await maintenance.pruneExpiredReceipts({
maxRows: 2,
maxDurationMs: 10_000,
}),
).toEqual({
ok: true,
value: {
state: "MORE",
scannedRows: 2,
deletedRows: 2,
budgetExhausted: false,
},
});
expect(
memory.readRaw("receipts", "inside-replay-window"),
).toBeDefined();
expect(
await maintenance.pruneExpiredReceipts({
maxRows: 2,
maxDurationMs: 10_000,
}),
).toEqual({
ok: true,
value: {
state: "COMPLETE",
scannedRows: 1,
deletedRows: 1,
budgetExhausted: false,
},
});
expect(
memory.readRaw("receipts", "expires-now"),
).toBeUndefined();
expect(
memory.readRaw("receipts", "inside-replay-window"),
).toBeDefined();
expect(
memory.readRaw("governance", "dataset-budget"),
).toMatchObject({ receiptCount: 1 });
});
it("reports prune success only after commit and rolls back quota failure", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedReceipt(memory, "quota-receipt", 100);
const maintenance = createMaintenance(
memory,
defaultPolicy(),
{ nowEpochMilliseconds: () => 200 },
);
memory.failNextWriteCommit(
new DOMException("private receipt", "QuotaExceededError"),
);
expect(
await maintenance.pruneExpiredReceipts({
maxRows: 10,
maxDurationMs: 10_000,
}),
).toMatchObject({
ok: false,
error: { code: "QUOTA_EXCEEDED" },
});
expect(
memory.readRaw("receipts", "quota-receipt"),
).toBeDefined();
});
it("aborts an in-flight prune transaction without deleting receipts", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedReceipt(memory, "abort-receipt", 100);
const maintenance = createMaintenance(
memory,
defaultPolicy(),
{ nowEpochMilliseconds: () => 200 },
);
memory.clearLastTransaction();
memory.pauseTransactions();
const controller = new AbortController();
const pending = maintenance.pruneExpiredReceipts({
maxRows: 10,
maxDurationMs: 10_000,
signal: controller.signal,
});
await waitForWriteTransaction(memory);
controller.abort();
expect(await pending).toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
memory.resumeTransactions();
await Promise.resolve();
expect(
memory.readRaw("receipts", "abort-receipt"),
).toBeDefined();
});
it("honors the prune time budget before deleting a replay receipt", async () => {
const memory = new MemoryIndexedDbFactory();
await prepareSchema(memory);
seedReceipt(memory, "budget-receipt", 100);
let monotonicTime = 0;
const maintenance = createMaintenance(
memory,
defaultPolicy(),
{
now: () => {
const value = monotonicTime;
monotonicTime += 5;
return value;
},
nowEpochMilliseconds: () => 200,
},
);
expect(
await maintenance.pruneExpiredReceipts({
maxRows: 10,
maxDurationMs: 1,
}),
).toEqual({
ok: true,
value: {
state: "MORE",
scannedRows: 0,
deletedRows: 0,
budgetExhausted: true,
},
});
expect(
memory.readRaw("receipts", "budget-receipt"),
).toBeDefined();
});
});
+431
View File
@@ -0,0 +1,431 @@
import { describe, expect, it } from "vitest";
import type {
BeginOpfsJournalTransaction,
OpfsPreparedObject,
OpfsStorageScope,
} from "../../src/application/ports/browser-file-storage/opfs-ports.ts";
import type {
BrowserStoragePolicy,
} from "../../src/application/ports/browser-file-storage/shared.ts";
import {
createIndexedDbOpfsJournal,
opfsJournalDatabaseName,
} from "../../src/adapters/storage/opfs/indexeddb-opfs-journal.ts";
import { MemoryIndexedDbFactory } from "../helpers/memory-indexeddb.ts";
const authorityToken = "authority_12345678";
const scope: OpfsStorageScope = Object.freeze({
namespace: "durable-objects",
authorityToken,
namespaceToken: "namespace_12345678",
partitionToken: "partition_12345678",
});
const otherPartition: OpfsStorageScope = Object.freeze({
...scope,
partitionToken: "partition_87654321",
});
const policy: BrowserStoragePolicy = Object.freeze({
owner: "test-owner",
namespace: scope.namespace,
classification: "PERSONAL",
authority: "LOCAL_FIRST",
accountScope: "OPAQUE_PARTITION",
retention: Object.freeze({ kind: "EXPLICIT_DELETE" }),
softBudgetBytes: 8,
hardBudgetBytes: 10,
evictionPriority: "USER_AUTHORED",
logoutAction: "EXPORT_THEN_PURGE",
accountDeletionAction: "PURGE_PARTITION",
pressureAction: "RETAIN",
unavailableFallback: "EXPORT_REQUIRED",
});
function beginInput(
transactionId: string,
objectId: string,
byteLength: number,
targetScope = scope,
): BeginOpfsJournalTransaction {
return {
transactionId,
mutation: "PUT",
scope: targetScope,
objectId,
expectedGeneration: null,
targetGeneration: 1,
targetByteLength: byteLength,
targetStoragePolicy: {
...policy,
namespace: targetScope.namespace,
},
startedAtEpochMs: 100,
};
}
function prepared(
input: BeginOpfsJournalTransaction,
): OpfsPreparedObject {
return {
physicalSchemaVersion: 1,
descriptor: {
objectId: input.objectId,
scope: input.scope,
generation: input.targetGeneration,
byteLength: input.targetByteLength,
mediaType: "application/octet-stream",
createdAtEpochMs: input.startedAtEpochMs,
integrity: {
algorithm: "SHA-256-TREE-V1",
rootDigestHex: "a".repeat(64),
chunkSizeBytes: 64 * 1024,
},
storagePolicy: input.targetStoragePolicy,
},
chunks:
input.targetByteLength === 0
? []
: [
{
sequence: 0,
byteLength: input.targetByteLength,
digestHex: "b".repeat(64),
},
],
};
}
function createHarness(factory = new MemoryIndexedDbFactory()) {
let tokenSequence = 0;
const journal = createIndexedDbOpfsJournal({
authorityToken,
factory: factory.factory,
createFencingToken: () =>
`fencing_${String(++tokenSequence).padStart(8, "0")}`,
});
return { factory, journal };
}
describe("IndexedDB OPFS journal", () => {
it("fences stale writers and atomically publishes object, budget and chunk refs", async () => {
const { factory, journal } = createHarness();
const input = beginInput(
"transaction_12345678",
"object_12345678",
3,
);
const begun = await journal.begin(input);
expect(begun).toMatchObject({ ok: true });
if (!begun.ok) throw new Error("begin failed");
expect(
await journal.markFilesReady(
input.transactionId,
"fencing_stale_1234",
prepared(input),
),
).toMatchObject({ ok: false, error: { code: "CONFLICT" } });
expect(
await journal.markFilesReady(
input.transactionId,
begun.value.fencingToken,
prepared(input),
),
).toMatchObject({ ok: true, value: { phase: "FILES_READY" } });
factory.failNextWriteCommit(
new DOMException("quota", "QuotaExceededError"),
);
expect(
await journal.commitPut(
input.transactionId,
begun.value.fencingToken,
),
).toMatchObject({ ok: false, error: { code: "QUOTA_EXCEEDED" } });
expect(
await journal.getCommittedObject(scope, input.objectId),
).toEqual({ ok: true, value: null });
expect(
await journal.isChunkReferenced(scope, "b".repeat(64)),
).toEqual({ ok: true, value: false });
expect(
await journal.commitPut(
input.transactionId,
begun.value.fencingToken,
),
).toMatchObject({ ok: true, value: { phase: "COMMITTED" } });
expect(
await journal.getCommittedObject(scope, input.objectId),
).toMatchObject({
ok: true,
value: { descriptor: { byteLength: 3, generation: 1 } },
});
expect(
await journal.isChunkReferenced(scope, "b".repeat(64)),
).toEqual({ ok: true, value: true });
expect(
await journal.isChunkReferenced(otherPartition, "b".repeat(64)),
).toEqual({ ok: true, value: false });
});
it("reserves the hard budget in the same transaction and releases it on rollback", async () => {
const { journal } = createHarness();
const first = await journal.begin(
beginInput("transaction_11111111", "object_11111111", 6),
);
expect(first).toMatchObject({ ok: true });
if (!first.ok) throw new Error("begin failed");
expect(
await journal.begin(
beginInput("transaction_22222222", "object_22222222", 5),
),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(
await journal.begin(
beginInput(
"transaction_33333333",
"object_33333333",
10,
otherPartition,
),
),
).toMatchObject({ ok: true });
expect(
await journal.rollback(
"transaction_11111111",
first.value.fencingToken,
),
).toEqual({ ok: true, value: undefined });
expect(
await journal.begin(
beginInput("transaction_44444444", "object_44444444", 5),
),
).toMatchObject({ ok: true });
});
it("commits and completes a DELETE row without requiring a prepared object", async () => {
const { journal } = createHarness();
const putInput = beginInput(
"transaction_55555555",
"object_55555555",
2,
);
const put = await journal.begin(putInput);
if (!put.ok) throw new Error("put begin failed");
await journal.markFilesReady(
putInput.transactionId,
put.value.fencingToken,
prepared(putInput),
);
await journal.commitPut(
putInput.transactionId,
put.value.fencingToken,
);
await journal.complete(
putInput.transactionId,
put.value.fencingToken,
);
const deletion = await journal.begin({
transactionId: "transaction_66666666",
mutation: "DELETE",
scope,
objectId: putInput.objectId,
expectedGeneration: 1,
targetGeneration: 2,
targetByteLength: 0,
targetStoragePolicy: policy,
startedAtEpochMs: 200,
});
if (!deletion.ok) throw new Error("delete begin failed");
expect(
await journal.commitDelete(
"transaction_66666666",
deletion.value.fencingToken,
),
).toMatchObject({
ok: true,
value: { mutation: "DELETE", phase: "COMMITTED" },
});
expect(
await journal.complete(
"transaction_66666666",
deletion.value.fencingToken,
),
).toEqual({ ok: true, value: undefined });
expect(
await journal.getCommittedObject(scope, putInput.objectId),
).toEqual({ ok: true, value: null });
});
it("binds one opaque physical authority to one deterministic database", async () => {
expect(opfsJournalDatabaseName(authorityToken)).toBe(
`ca-frontend-opfs-metadata-v1:${authorityToken}`,
);
expect(() =>
createIndexedDbOpfsJournal({
authorityToken,
databaseName: "unrelated-database",
factory: new MemoryIndexedDbFactory().factory,
}),
).toThrow();
const { journal } = createHarness();
const binding = await journal.begin(
beginInput(
"transaction_binding_1234",
"object_binding_12345678",
1,
),
);
if (!binding.ok) throw new Error("scope binding failed");
await journal.rollback(
"transaction_binding_1234",
binding.value.fencingToken,
);
const remappedScope: OpfsStorageScope = {
...scope,
namespace: "different-namespace",
};
expect(
await journal.begin(
beginInput(
"transaction_remap_1234",
"object_remap_12345678",
1,
remappedScope,
),
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
const retokenedScope: OpfsStorageScope = {
...scope,
namespaceToken: "namespace_87654321",
};
expect(
await journal.begin(
beginInput(
"transaction_retoken_1234",
"object_retoken_12345678",
1,
retokenedScope,
),
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(
await journal.begin({
...beginInput(
"transaction_policy_1234",
"object_policy_12345678",
1,
),
targetStoragePolicy: {
...policy,
owner: "different-owner",
},
}),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
const foreignScope: OpfsStorageScope = {
...scope,
authorityToken: "authority_87654321",
};
expect(
await journal.getCommittedObject(
foreignScope,
"object_12345678",
),
).toMatchObject({ ok: false, error: { code: "INVALID_INPUT" } });
expect(
await journal.begin(
beginInput(
"transaction_77777777",
"object_77777777",
1,
foreignScope,
),
),
).toMatchObject({ ok: false, error: { code: "INVALID_INPUT" } });
});
it("maps synchronous open failures and blocked upgrades to Result failures", async () => {
const throwingFactory = {
open() {
throw new DOMException("denied", "SecurityError");
},
} as unknown as IDBFactory;
const throwingJournal = createIndexedDbOpfsJournal({
authorityToken,
factory: throwingFactory,
});
await expect(
throwingJournal.getCommittedObject(scope, "object_12345678"),
).resolves.toMatchObject({
ok: false,
error: { code: "PERMISSION_DENIED" },
});
const factory = new MemoryIndexedDbFactory();
factory.blockNextOpen();
const callbacks: Array<() => void> = [];
const blockedJournal = createIndexedDbOpfsJournal({
authorityToken,
factory: factory.factory,
blockedTimeoutMs: 1,
scheduler: {
setTimeout(callback) {
callbacks.push(callback);
return callback;
},
clearTimeout() {},
},
});
const opening = blockedJournal.getCommittedObject(
scope,
"object_12345678",
);
await Promise.resolve();
callbacks.forEach((callback) => callback());
await expect(opening).resolves.toMatchObject({
ok: false,
error: { code: "BLOCKED" },
});
});
it("reopens after versionchange and rejects corrupt persisted journal rows", async () => {
const { factory, journal } = createHarness();
expect(
await journal.getCommittedObject(scope, "object_12345678"),
).toEqual({ ok: true, value: null });
factory.triggerVersionChange(2);
expect(factory.isConnectionClosed()).toBe(true);
expect(
await journal.getCommittedObject(scope, "object_12345678"),
).toEqual({ ok: true, value: null });
factory.seed("opfs-journal", {
transactionId: "transaction_88888888",
startedAtEpochMs: 1,
corrupt: true,
});
expect(await journal.listIncomplete(10)).toMatchObject({
ok: false,
error: { code: "CORRUPT_DATA" },
});
});
});
File diff suppressed because it is too large Load Diff
@@ -3,8 +3,8 @@ import { describe, expect, it } from "vitest";
import {
MANUAL_A11Y_ROUTE_IDS,
validateManualA11yEvidence,
} from "../../scripts/lib/manual-a11y-evidence.mjs";
import { ROUTE_REGISTRY } from "../../src/features/installed-feature-contracts.js";
} from "../../scripts/lib/manual-a11y-evidence.ts";
import { ROUTE_REGISTRY } from "../../src/features/installed-feature-contracts.ts";
const reviewed = `Status: reviewed
Route ID: APP_HOME
@@ -3,11 +3,11 @@ import { describe, expect, it } from "vitest";
import {
NAVIGATION_ROUTES,
ROUTE_REGISTRY,
} from "../../src/features/installed-feature-contracts.js";
} from "../../src/features/installed-feature-contracts.ts";
import {
createRedirectLoopGuard,
decideRouteAccess,
} from "../../src/presentation/routes/navigation-policy.js";
} from "../../src/presentation/routes/navigation-policy.ts";
describe("installed route registry", () => {
it("derives visible navigation in explicit order", () => {
@@ -15,8 +15,7 @@ describe("installed route registry", () => {
.filter((route) => route.navigationOrder !== null)
.sort(
(left, right) =>
/** @type {number} */ (left.navigationOrder) -
/** @type {number} */ (right.navigationOrder),
left.navigationOrder! - right.navigationOrder!,
)
.map((route) => route.routeId);
expect(NAVIGATION_ROUTES.map(({ routeId }) => routeId)).toEqual(expected);
File diff suppressed because it is too large Load Diff
+649
View File
@@ -0,0 +1,649 @@
import { describe, expect, it } from "vitest";
import type {
OpfsPreparedObject,
OpfsStorageScope,
} from "../../src/application/ports/browser-file-storage/opfs-ports.ts";
import type {
BrowserStoragePolicy,
} from "../../src/application/ports/browser-file-storage/shared.ts";
import {
resolveOpfsRuntimePolicy,
} from "../../src/adapters/storage/opfs/opfs-policy.ts";
import {
createOpfsWorkerGateway,
type OpfsWorkerLike,
} from "../../src/adapters/storage/opfs/opfs-worker-client.ts";
import type {
OpfsWorkerRequest,
OpfsWorkerResponse,
} from "../../src/adapters/storage/opfs/opfs-worker-protocol.ts";
import {
createOpfsWorkerRuntime,
type OpfsMutationLeaseManager,
} from "../../src/adapters/storage/opfs/opfs-worker-runtime.ts";
const scopeA: OpfsStorageScope = Object.freeze({
namespace: "durable-objects",
authorityToken: "authority_12345678",
namespaceToken: "namespace_12345678",
partitionToken: "partition_12345678",
});
const scopeB: OpfsStorageScope = Object.freeze({
...scopeA,
authorityToken: "authority_87654321",
});
const storagePolicy: BrowserStoragePolicy = Object.freeze({
owner: "test-owner",
namespace: scopeA.namespace,
classification: "PERSONAL",
authority: "LOCAL_FIRST",
accountScope: "OPAQUE_PARTITION",
retention: Object.freeze({ kind: "EXPLICIT_DELETE" }),
softBudgetBytes: 1024 * 1024,
hardBudgetBytes: 2 * 1024 * 1024,
evictionPriority: "USER_AUTHORED",
logoutAction: "EXPORT_THEN_PURGE",
accountDeletionAction: "PURGE_PARTITION",
pressureAction: "RETAIN",
unavailableFallback: "EXPORT_REQUIRED",
});
const runtimePolicy = resolveOpfsRuntimePolicy({
chunkSizeBytes: 64 * 1024,
maxObjectBytes: 64 * 1024,
maxChunkCount: 1,
orphanGracePeriodMs: 60_000,
});
type MemoryNode = MemoryDirectory | MemoryFile;
class MemoryFile {
readonly kind = "file";
bytes = new Uint8Array();
lastModified = 0;
}
class MemoryDirectory {
readonly kind = "directory";
readonly children = new Map<string, MemoryNode>();
async getDirectoryHandle(
name: string,
options: FileSystemGetDirectoryOptions = {},
): Promise<FileSystemDirectoryHandle> {
const current = this.children.get(name);
if (current instanceof MemoryDirectory) {
return current as unknown as FileSystemDirectoryHandle;
}
if (current || !options.create) throw notFound();
const created = new MemoryDirectory();
this.children.set(name, created);
return created as unknown as FileSystemDirectoryHandle;
}
async getFileHandle(
name: string,
options: FileSystemGetFileOptions = {},
): Promise<FileSystemFileHandle> {
const current = this.children.get(name);
if (current instanceof MemoryFile) {
return fileHandle(current);
}
if (current || !options.create) throw notFound();
const created = new MemoryFile();
this.children.set(name, created);
return fileHandle(created);
}
async removeEntry(
name: string,
options: FileSystemRemoveOptions = {},
): Promise<void> {
const current = this.children.get(name);
if (!current) throw notFound();
if (
current instanceof MemoryDirectory &&
current.children.size > 0 &&
!options.recursive
) {
throw new DOMException("Directory is not empty.", "InvalidModificationError");
}
this.children.delete(name);
}
async *entries(): AsyncIterableIterator<
[string, FileSystemDirectoryHandle | FileSystemFileHandle]
> {
for (const [name, node] of this.children) {
yield [
name,
node instanceof MemoryDirectory
? (node as unknown as FileSystemDirectoryHandle)
: fileHandle(node),
];
}
}
has(path: readonly string[]): boolean {
let current: MemoryNode = this;
for (const segment of path) {
if (!(current instanceof MemoryDirectory)) return false;
const next = current.children.get(segment);
if (!next) return false;
current = next;
}
return true;
}
}
function fileHandle(file: MemoryFile): FileSystemFileHandle {
return {
kind: "file",
name: "memory",
async getFile() {
const blob = new Blob([Uint8Array.from(file.bytes)]);
Object.defineProperty(blob, "lastModified", {
value: file.lastModified,
});
return blob as File;
},
async createWritable() {
let pending = Uint8Array.from(file.bytes);
return {
async write(data: FileSystemWriteChunkType) {
if (!(data instanceof Uint8Array)) {
throw new TypeError("The test writer accepts Uint8Array only.");
}
pending = Uint8Array.from(data);
},
async close() {
file.bytes = pending;
file.lastModified = Date.now();
},
async abort() {},
} as unknown as FileSystemWritableFileStream;
},
} as FileSystemFileHandle;
}
function notFound(): DOMException {
return new DOMException("Entry was not found.", "NotFoundError");
}
function beginRequest(
requestId: string,
transactionId: string,
scope: OpfsStorageScope,
): OpfsWorkerRequest {
return {
requestId,
kind: "BEGIN_PUT",
transactionId,
scope,
objectId: "object_12345678",
generation: 1,
declaredByteLength: 1,
mediaType: "application/octet-stream",
createdAtEpochMs: 100,
storagePolicy,
chunkSizeBytes: runtimePolicy.chunkSizeBytes,
};
}
function immediateLeases(releases: { count: number }): OpfsMutationLeaseManager {
return {
async acquire() {
let released = false;
return {
release() {
if (released) return;
released = true;
releases.count += 1;
},
};
},
};
}
function expectFailureCode(
response: OpfsWorkerResponse | null,
code: string,
): void {
expect(response).toMatchObject({ ok: false, failure: { code } });
}
function preparedValue(
response: OpfsWorkerResponse | null,
): OpfsPreparedObject {
if (
!response?.ok ||
!("value" in response) ||
!response.value ||
typeof response.value !== "object" ||
response.value instanceof ArrayBuffer ||
!("descriptor" in response.value)
) {
throw new Error("Expected a prepared OPFS object.");
}
return response.value;
}
describe("OPFS dedicated worker runtime", () => {
it("cancels an exact BEGIN while waiting for a lock and releases a late lease", async () => {
const root = new MemoryDirectory();
let resolveLease:
| ((lease: { release(): void }) => void)
| undefined;
let acquireSignal: AbortSignal | undefined;
let releases = 0;
const leaseManager: OpfsMutationLeaseManager = {
acquire(signal) {
acquireSignal = signal;
return new Promise((resolve) => {
resolveLease = resolve;
});
},
};
const runtime = createOpfsWorkerRuntime({
root: root as unknown as FileSystemDirectoryHandle,
crypto: globalThis.crypto,
policy: runtimePolicy,
leaseManager,
dedicatedWorker: true,
supportsSynchronousAccessHandles: false,
});
const beginning = runtime.handleRequest(
beginRequest(
"request_begin_1234",
"transaction_12345678",
scopeA,
),
);
await Promise.resolve();
const aborted = await runtime.handleRequest({
requestId: "request_abort_1234",
kind: "ABORT_PUT",
scope: scopeA,
transactionId: "transaction_12345678",
});
expect(aborted).toMatchObject({ ok: true });
expect(acquireSignal?.aborted).toBe(true);
resolveLease?.({
release() {
releases += 1;
},
});
expectFailureCode(await beginning, "ABORTED");
expect(releases).toBe(1);
expect(root.children.size).toBe(0);
});
it("serializes APPEND against ABORT so no receipt or generation is resurrected", async () => {
const root = new MemoryDirectory();
const releases = { count: 0 };
let resolveDigest:
| ((digest: ArrayBuffer) => void)
| undefined;
const delayedCrypto = {
subtle: {
digest: () =>
new Promise<ArrayBuffer>((resolve) => {
resolveDigest = resolve;
}),
},
} as unknown as Crypto;
const runtime = createOpfsWorkerRuntime({
root: root as unknown as FileSystemDirectoryHandle,
crypto: delayedCrypto,
policy: runtimePolicy,
leaseManager: immediateLeases(releases),
dedicatedWorker: true,
supportsSynchronousAccessHandles: false,
});
expect(
await runtime.handleRequest(
beginRequest(
"request_begin_5678",
"transaction_56785678",
scopeA,
),
),
).toMatchObject({ ok: true });
const appending = runtime.handleRequest({
requestId: "request_append_5678",
kind: "APPEND_CHUNK",
scope: scopeA,
transactionId: "transaction_56785678",
sequence: 0,
bytes: new Uint8Array([7]).buffer,
});
await Promise.resolve();
const aborting = runtime.handleRequest({
requestId: "request_abort_5678",
kind: "ABORT_PUT",
scope: scopeA,
transactionId: "transaction_56785678",
});
resolveDigest?.(new Uint8Array(32).buffer);
expectFailureCode(await appending, "ABORTED");
expect(await aborting).toMatchObject({ ok: true });
expect(releases.count).toBe(1);
expect(
root.has([
"authorities",
scopeA.authorityToken,
scopeA.namespaceToken,
scopeA.partitionToken,
"staging",
"transaction_56785678",
]),
).toBe(false);
});
it("isolates physical chunks and cancellation tombstones by opaque authority", async () => {
const root = new MemoryDirectory();
const releases = { count: 0 };
const runtime = createOpfsWorkerRuntime({
root: root as unknown as FileSystemDirectoryHandle,
crypto: globalThis.crypto,
policy: runtimePolicy,
leaseManager: immediateLeases(releases),
dedicatedWorker: true,
supportsSynchronousAccessHandles: false,
});
const transactionId = "transaction_99999999";
for (const [index, targetScope] of [scopeA, scopeB].entries()) {
expect(
await runtime.handleRequest(
beginRequest(
`request_begin_iso_${index}`,
transactionId,
targetScope,
),
),
).toMatchObject({ ok: true });
expect(
await runtime.handleRequest({
requestId: `request_append_iso_${index}`,
kind: "APPEND_CHUNK",
scope: targetScope,
transactionId,
sequence: 0,
bytes: new Uint8Array([42]).buffer,
}),
).toMatchObject({ ok: true });
}
expect(
await runtime.handleRequest({
requestId: "request_abort_iso_a",
kind: "ABORT_PUT",
scope: scopeA,
transactionId,
}),
).toMatchObject({ ok: true });
const finished = await runtime.handleRequest({
requestId: "request_finish_iso_b",
kind: "FINISH_PUT",
scope: scopeB,
transactionId,
});
expect(finished).toMatchObject({ ok: true });
const preparedObject = preparedValue(finished);
expect(
await runtime.handleRequest({
requestId: "request_verify_iso_b",
kind: "VERIFY_OBJECT",
preparedObject,
}),
).toEqual({
requestId: "request_verify_iso_b",
ok: true,
value: true,
});
});
it("garbage-collects orphan chunks only inside the requested physical authority", async () => {
const root = new MemoryDirectory();
const releases = { count: 0 };
const runtime = createOpfsWorkerRuntime({
root: root as unknown as FileSystemDirectoryHandle,
crypto: globalThis.crypto,
policy: runtimePolicy,
leaseManager: immediateLeases(releases),
dedicatedWorker: true,
supportsSynchronousAccessHandles: false,
});
const preparedByScope = new Map<string, OpfsPreparedObject>();
for (const [index, targetScope] of [scopeA, scopeB].entries()) {
const transactionId = `transaction_gc_${index}_1234`;
await runtime.handleRequest(
beginRequest(
`request_gc_begin_${index}`,
transactionId,
targetScope,
),
);
await runtime.handleRequest({
requestId: `request_gc_append_${index}`,
kind: "APPEND_CHUNK",
scope: targetScope,
transactionId,
sequence: 0,
bytes: new Uint8Array([99]).buffer,
});
const object = preparedValue(
await runtime.handleRequest({
requestId: `request_gc_finish_${index}`,
kind: "FINISH_PUT",
scope: targetScope,
transactionId,
}),
);
preparedByScope.set(targetScope.authorityToken, object);
await runtime.handleRequest({
requestId: `request_gc_finalize_${index}`,
kind: "FINALIZE_PUT",
transactionId,
preparedObject: object,
});
}
const digestHex = preparedByScope.get(
scopeA.authorityToken,
)!.chunks[0]!.digestHex;
const cutoff = Date.now() + 10_000;
expect(
await runtime.handleRequest({
requestId: "request_gc_list_a",
kind: "LIST_ORPHAN_CANDIDATES",
scope: scopeA,
olderThanEpochMs: cutoff,
maxEntries: 10,
}),
).toMatchObject({ ok: true, value: { digests: [digestHex] } });
expect(
await runtime.handleRequest({
requestId: "request_gc_delete_a",
kind: "DELETE_ORPHAN_CHUNK",
scope: scopeA,
digestHex,
olderThanEpochMs: cutoff,
}),
).toMatchObject({ ok: true, value: { deleted: true } });
expect(
await runtime.handleRequest({
requestId: "request_gc_verify_a",
kind: "VERIFY_OBJECT",
preparedObject: preparedByScope.get(scopeA.authorityToken)!,
}),
).toEqual({
requestId: "request_gc_verify_a",
ok: true,
value: false,
});
expect(
await runtime.handleRequest({
requestId: "request_gc_verify_b",
kind: "VERIFY_OBJECT",
preparedObject: preparedByScope.get(scopeB.authorityToken)!,
}),
).toEqual({
requestId: "request_gc_verify_b",
ok: true,
value: true,
});
});
it("reports every required capability and fails closed when the lock is absent", async () => {
const runtime = createOpfsWorkerRuntime({
root: new MemoryDirectory() as unknown as FileSystemDirectoryHandle,
crypto: globalThis.crypto,
policy: runtimePolicy,
leaseManager: null,
dedicatedWorker: true,
supportsSynchronousAccessHandles: true,
});
expect(
await runtime.handleRequest({
requestId: "request_caps_1234",
kind: "CAPABILITIES",
}),
).toEqual({
requestId: "request_caps_1234",
ok: true,
value: {
available: false,
dedicatedWorkerRequired: true,
crossContextMutationLockAvailable: false,
synchronousAccessHandleAvailable: true,
},
});
expectFailureCode(
await runtime.handleRequest(
beginRequest(
"request_begin_caps",
"transaction_caps_1234",
scopeA,
),
),
"UNSUPPORTED",
);
});
});
describe("OPFS worker client lifecycle", () => {
it("scopes the open signal to verification and leaves the acquired source readable", async () => {
let listener:
| ((event: MessageEvent<unknown>) => void)
| undefined;
const worker: OpfsWorkerLike = {
postMessage(message) {
const response: OpfsWorkerResponse =
message.kind === "VERIFY_OBJECT"
? {
requestId: message.requestId,
ok: true,
value: true,
}
: {
requestId: message.requestId,
ok: true,
value: new Uint8Array([4, 2]).buffer,
};
queueMicrotask(() =>
listener?.({ data: response } as MessageEvent<unknown>),
);
},
addEventListener(_type, nextListener) {
listener = nextListener;
},
removeEventListener() {
listener = undefined;
},
};
let requestSequence = 0;
const gateway = createOpfsWorkerGateway({
worker,
policy: runtimePolicy,
createRequestId: () => `request_open_${++requestSequence}_1234`,
});
const preparedObject: OpfsPreparedObject = {
physicalSchemaVersion: 1,
descriptor: {
objectId: "object_open_12345678",
scope: scopeA,
generation: 1,
byteLength: 2,
mediaType: "application/octet-stream",
createdAtEpochMs: 100,
integrity: {
algorithm: "SHA-256-TREE-V1",
rootDigestHex: "a".repeat(64),
chunkSizeBytes: runtimePolicy.chunkSizeBytes,
},
storagePolicy,
},
chunks: [
{
sequence: 0,
byteLength: 2,
digestHex: "b".repeat(64),
},
],
};
const openController = new AbortController();
const opened = await gateway.openObject(
preparedObject,
openController.signal,
);
expect(opened.ok).toBe(true);
openController.abort();
const chunks: number[][] = [];
if (opened.ok) {
for await (const result of opened.value.stream(
new AbortController().signal,
)) {
expect(result.ok).toBe(true);
if (result.ok) chunks.push([...result.value]);
}
}
expect(chunks).toEqual([[4, 2]]);
gateway.close();
});
it("removes its listener and rejects all pending RPCs before Worker termination", async () => {
const listeners = new Set<(event: MessageEvent<unknown>) => void>();
const worker: OpfsWorkerLike = {
postMessage() {},
addEventListener(_type, listener) {
listeners.add(listener);
},
removeEventListener(_type, listener) {
listeners.delete(listener);
},
};
const gateway = createOpfsWorkerGateway({
worker,
policy: runtimePolicy,
createRequestId: () => "request_pending_1234",
});
const pending = gateway.capabilities();
expect(listeners.size).toBe(1);
gateway.close();
expect(listeners.size).toBe(0);
await expect(pending).resolves.toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
await expect(gateway.capabilities()).resolves.toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
});
});
@@ -0,0 +1,87 @@
import { QueryClient } from "@tanstack/react-query";
import { describe, expect, it } from "vitest";
import { createOptimisticLayerRuntime } from "../../src/presentation/adapters/query/optimistic-layer-runtime.ts";
import { createRuntimeIdentityRegistry } from "../../src/contracts/query-keys.ts";
function scope() {
let current = true;
return {
snapshot: {
generation: 1,
fingerprint: "scope-token-00000001",
identities: createRuntimeIdentityRegistry({
tokenFactory: () => crypto.randomUUID(),
}),
isCurrent: () => current,
},
expire: () => {
current = false;
},
};
}
describe("revision-safe optimistic layer runtime", () => {
it("removes only the failed layer when commands settle out of order", () => {
const client = new QueryClient();
const key = ["query", "resources"];
client.setQueryData(key, ["base"]);
const selectedScope = scope();
const runtime = createOptimisticLayerRuntime(client);
const append = (previous: unknown, input: string) => [
...(previous as string[]),
input,
];
const first = runtime.begin(
key,
"first",
append,
selectedScope.snapshot,
);
const second = runtime.begin(
key,
"second",
append,
selectedScope.snapshot,
);
expect(client.getQueryData(key)).toEqual(["base", "first", "second"]);
second?.commit();
first?.rollback();
expect(client.getQueryData(key)).toEqual(["base", "second"]);
});
it("reapplies pending layers over an authoritative external cache update", () => {
const client = new QueryClient();
const key = ["query", "resources"];
client.setQueryData(key, ["base"]);
const selectedScope = scope();
const runtime = createOptimisticLayerRuntime(client);
runtime.begin(
key,
"pending",
(previous, input) => [...(previous as string[]), input],
selectedScope.snapshot,
);
client.setQueryData(key, ["server"]);
expect(client.getQueryData(key)).toEqual(["server", "pending"]);
});
it("removes scoped data instead of restoring it after scope expiry", () => {
const client = new QueryClient();
const key = ["query", "resources"];
client.setQueryData(key, ["base"]);
const selectedScope = scope();
const runtime = createOptimisticLayerRuntime(client);
const layer = runtime.begin(
key,
"pending",
(previous, input) => [...(previous as string[]), input],
selectedScope.snapshot,
);
selectedScope.expire();
layer?.rollback();
expect(client.getQueryData(key)).toBeUndefined();
});
});
@@ -5,7 +5,7 @@ import {
evaluateFieldBudget,
evaluateLabBudget,
percentile75,
} from "../../src/application/policies/performance-budgets.js";
} from "../../src/application/policies/performance-budgets.ts";
describe("performance budgets", () => {
it("rejects initial and lazy JavaScript above their named limits", () => {
File diff suppressed because it is too large Load Diff
@@ -3,13 +3,13 @@ import { describe, expect, it } from "vitest";
import {
evaluatePromotionReadiness,
PROMOTION_FORMULA,
} from "../../src/application/policies/promotion-readiness.js";
} from "../../src/application/policies/promotion-readiness.ts";
const allGateIds = Object.values(PROMOTION_FORMULA).flat();
const passing =
/** @type {Record<string, "PASS" | "FAIL" | "UNVERIFIED">} */ (
Object.fromEntries(allGateIds.map((gateId) => [gateId, "PASS"]))
);
type GateResults = Parameters<typeof evaluatePromotionReadiness>[0];
const passing: GateResults = Object.fromEntries(
allGateIds.map((gateId) => [gateId, "PASS"]),
);
describe("promotion readiness formula", () => {
it("requires every upstream tier before downstream readiness", () => {
@@ -28,16 +28,12 @@ describe("promotion readiness formula", () => {
["FE-GATE-016", "PROD_PROMOTION_READY"],
["FE-GATE-018", "FIELD_SLO_READY"],
["FE-GATE-017", "DOCUMENTATION_READY"],
])("fails closed when %s fails", (failedGate, readiness) => {
] as const)("fails closed when %s fails", (failedGate, readiness) => {
const result = evaluatePromotionReadiness({
...passing,
[failedGate]: "FAIL",
});
const readinessKey =
/** @type {keyof ReturnType<typeof evaluatePromotionReadiness>} */ (
readiness
);
expect(result[readinessKey]).toBe(false);
expect(result[readiness]).toBe(false);
});
it("does not treat missing or unverified gates as pass", () => {
File diff suppressed because it is too large Load Diff
@@ -4,15 +4,14 @@ import { describe, expect, it, vi } from "vitest";
import {
createQueryCacheAdapter,
createQueryClient,
} from "../../src/adapters/query-cache/tanstack-query-cache.js";
import { canonicalize } from "../../src/contracts/query-keys.js";
} from "../../src/adapters/query-cache/tanstack-query-cache.ts";
import { canonicalize } from "../../src/contracts/query-keys.ts";
const queryKeys = Object.freeze({
all: () => Object.freeze(["entity", 1]),
list: (filters = {}) =>
list: (filters: Readonly<Record<string, unknown>> = {}) =>
Object.freeze(["entity", 1, "list", canonicalize(filters)]),
/** @param {string} entityId */
detail: (entityId) =>
detail: (entityId: string) =>
Object.freeze(["entity", 1, "detail", String(entityId)]),
});
+73
View File
@@ -0,0 +1,73 @@
import { describe, expect, it } from "vitest";
import {
canonicalize,
createRuntimeIdentityRegistry,
runtimeIdentityToken,
} from "../../src/contracts/query-keys.ts";
const sparseValue = Array<string>(2);
sparseValue[1] = "sparse";
describe("strict server-state identity codec", () => {
it("is stable across plain-object key ordering without exposing input in the token", () => {
const left = { limit: 20, filters: { state: "open" } };
const right = { filters: { state: "open" }, limit: 20 };
expect(canonicalize(left)).toEqual(canonicalize(right));
const token = runtimeIdentityToken(left);
expect(token).toBe(runtimeIdentityToken(right));
expect(token).not.toContain("open");
});
it.each([
{ value: { missing: undefined }, label: "undefined" },
{ value: { number: Number.NaN }, label: "NaN" },
{ value: { date: new Date() }, label: "Date" },
{ value: sparseValue, label: "sparse array" },
])("rejects $label", ({ value }) => {
expect(() => canonicalize(value)).toThrow();
});
it("rejects cycles and shared-reference ambiguity", () => {
const shared = {};
expect(() => canonicalize({ left: shared, right: shared })).toThrow();
const cycle: Record<string, unknown> = {};
cycle.self = cycle;
expect(() => canonicalize(cycle)).toThrow();
});
it("keeps active identity leases non-evictable and evicts released LRU rows", () => {
let sequence = 0;
const registry = createRuntimeIdentityRegistry({
maxEntries: 1,
maxCanonicalBytes: 1_024,
tokenFactory: () => `identity-token-${sequence++}`,
});
const first = registry.intern({ resource: "first" });
first.acquire();
expect(() => registry.intern({ resource: "second" })).toThrow(
"capacity exceeded",
);
expect(registry.inspect().activeLeases).toBe(1);
first.release();
const second = registry.intern({ resource: "second" });
expect(second.token).not.toBe(first.token);
expect(registry.inspect()).toMatchObject({
entries: 1,
activeLeases: 0,
closed: false,
});
});
it("fails closed when token collisions cannot be resolved", () => {
const registry = createRuntimeIdentityRegistry({
maxEntries: 2,
tokenFactory: () => "identity-token-fixed",
});
registry.intern({ resource: "first" });
expect(() => registry.intern({ resource: "second" })).toThrow(
"token collision",
);
});
});
@@ -0,0 +1,876 @@
import { describe, expect, it, vi } from "vitest";
import type { ClockPort } from "../../../src/application/ports/clock-port.ts";
import { definePollLeasePolicy } from "../../../src/application/policies/bounded-polling.ts";
import {
createBoundedPollCoordinator,
type BoundedPollAttemptResult,
type BoundedPollEnvironment,
} from "../../../src/adapters/realtime/polling/bounded-poll-coordinator.ts";
type Sleeper = {
dueAt: number;
resolve(): void;
reject(): void;
signal?: AbortSignal;
onAbort?: () => void;
};
class ManualClock implements ClockPort {
current = 0;
readonly sleepers: Sleeper[] = [];
now(): number {
return this.current;
}
sleep(milliseconds: number, signal?: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => {
if (signal?.aborted) {
reject(new DOMException("Aborted", "AbortError"));
return;
}
const sleeper: Sleeper = {
dueAt: this.current + milliseconds,
resolve: () => {
signal?.removeEventListener("abort", sleeper.onAbort!);
resolve();
},
reject: () => {
signal?.removeEventListener("abort", sleeper.onAbort!);
reject(new DOMException("Aborted", "AbortError"));
},
signal,
};
sleeper.onAbort = () => {
this.remove(sleeper);
sleeper.reject();
};
signal?.addEventListener("abort", sleeper.onAbort, {
once: true,
});
this.sleepers.push(sleeper);
});
}
advance(milliseconds: number): void {
this.current += milliseconds;
const ready = this.sleepers
.filter((sleeper) => sleeper.dueAt <= this.current)
.sort((left, right) => left.dueAt - right.dueAt);
for (const sleeper of ready) {
this.remove(sleeper);
sleeper.resolve();
}
}
private remove(target: Sleeper): void {
const index = this.sleepers.indexOf(target);
if (index >= 0) this.sleepers.splice(index, 1);
}
}
class MutableEnvironment implements BoundedPollEnvironment {
visible = true;
connected = true;
readonly visibilityListeners = new Set<
(visibility: "HIDDEN" | "VISIBLE") => void
>();
readonly onlineListeners = new Set<(online: boolean) => void>();
visibility(): "HIDDEN" | "VISIBLE" {
return this.visible ? "VISIBLE" : "HIDDEN";
}
online(): boolean {
return this.connected;
}
subscribeVisibility(
listener: (visibility: "HIDDEN" | "VISIBLE") => void,
): () => void {
this.visibilityListeners.add(listener);
return () => this.visibilityListeners.delete(listener);
}
subscribeOnline(listener: (online: boolean) => void): () => void {
this.onlineListeners.add(listener);
return () => this.onlineListeners.delete(listener);
}
hide(): void {
this.visible = false;
for (const listener of this.visibilityListeners) listener("HIDDEN");
}
}
const policy = definePollLeasePolicy({
operationId: "GET_JOB_STATUS",
owner: "reference-job",
minimumIntervalMs: 5_000,
successIntervalMs: 5_000,
maxIntervalMs: 60_000,
maxAttempts: 3,
maxElapsedMs: 60_000,
maxResponseBytes: 1_024,
visibility: "VISIBLE_ONLY",
fallbackReason: "CONVERGENCE",
terminalStates: ["COMPLETED", "FAILED"],
});
const operation = {
operationId: "GET_JOB_STATUS",
contractVersion: 2,
protocol: "REST",
semantics: "QUERY",
method: "GET",
replayPolicy: "SAFE",
retry: "never",
maxResponseBytes: 1_024,
transportMaxAttempts: 1,
authRecoveryCount: 0,
maxCumulativeSleepMs: 0,
serverStream: false,
} as const;
async function flush(): Promise<void> {
for (let turn = 0; turn < 12; turn += 1) {
await Promise.resolve();
}
}
describe("bounded poll coordinator", () => {
it("chains completed attempts without overlap and stops at a terminal state", async () => {
const clock = new ManualClock();
const environment = new MutableEnvironment();
let active = 0;
let highWatermark = 0;
const execute = vi
.fn<
(
input: Readonly<{
operationId: string;
attempt: number;
maxResponseBytes: number;
signal: AbortSignal;
}>,
) => Promise<BoundedPollAttemptResult<string>>
>()
.mockImplementation(async ({ attempt }) => {
active += 1;
highWatermark = Math.max(highWatermark, active);
await Promise.resolve();
active -= 1;
return {
ok: true,
value: {
kind: "VALUE",
value: attempt === 1 ? "working" : "done",
responseBytes: 32,
state: attempt === 1 ? "RUNNING" : "COMPLETED",
},
};
});
const onValue = vi.fn();
const coordinator = createBoundedPollCoordinator({
policy,
operation,
execute,
environment,
clock,
random: () => 0.5,
});
const result = coordinator.run({ onValue });
expect(execute).not.toHaveBeenCalled();
clock.advance(5_000);
await flush();
expect(execute).toHaveBeenCalledTimes(1);
expect(clock.sleepers).toHaveLength(1);
clock.advance(5_000);
await flush();
await expect(result).resolves.toEqual({
ok: true,
value: {
kind: "TERMINAL",
attempts: 2,
state: "COMPLETED",
value: "done",
},
});
expect(onValue.mock.calls.map(([value]) => value)).toEqual([
"working",
"done",
]);
expect(highWatermark).toBe(1);
expect(coordinator.getState()).toBe("IDLE");
expect(environment.visibilityListeners.size).toBe(0);
expect(environment.onlineListeners.size).toBe(0);
});
it("passes the strictest response ceiling to the executor before decode", async () => {
const clock = new ManualClock();
const execute = vi.fn(
async ({
maxResponseBytes,
}: Readonly<{ maxResponseBytes: number }>) => ({
ok: true as const,
value: {
kind: "VALUE" as const,
value: "done",
responseBytes: maxResponseBytes,
state: "COMPLETED",
},
}),
);
const coordinator = createBoundedPollCoordinator<string>({
policy,
operation: {
...operation,
maxResponseBytes: 4_096,
},
execute,
environment: new MutableEnvironment(),
clock,
});
const pending = coordinator.run();
clock.advance(5_000);
await expect(pending).resolves.toMatchObject({
ok: true,
value: { state: "COMPLETED" },
});
expect(execute).toHaveBeenCalledWith(
expect.objectContaining({ maxResponseBytes: 1_024 }),
);
});
it("does not start another attempt while a request is unresolved", async () => {
const clock = new ManualClock();
const environment = new MutableEnvironment();
let resolveFirst:
| ((result: BoundedPollAttemptResult<string>) => void)
| undefined;
const execute = vi
.fn()
.mockImplementationOnce(
() =>
new Promise<BoundedPollAttemptResult<string>>((resolve) => {
resolveFirst = resolve;
}),
)
.mockResolvedValue({
ok: true,
value: {
kind: "VALUE",
value: "done",
responseBytes: 10,
state: "COMPLETED",
},
});
const coordinator = createBoundedPollCoordinator<string>({
policy,
operation,
execute,
environment,
clock,
random: () => 0.5,
});
const pending = coordinator.run();
clock.advance(5_000);
await flush();
expect(execute).toHaveBeenCalledTimes(1);
clock.advance(30_000);
await flush();
expect(execute).toHaveBeenCalledTimes(1);
resolveFirst?.({
ok: true,
value: { kind: "UNCHANGED", responseBytes: 0 },
});
await flush();
clock.advance(5_000);
await flush();
expect(execute).toHaveBeenCalledTimes(2);
await expect(pending).resolves.toMatchObject({
ok: true,
value: { attempts: 2, state: "COMPLETED" },
});
});
it("ends the lease when an in-flight executor ignores abort and exceeds max elapsed", async () => {
const clock = new ManualClock();
let attemptSignal: AbortSignal | undefined;
let settleAttempt:
| ((result: BoundedPollAttemptResult<string>) => void)
| undefined;
const execute = vi.fn(
({ signal }: Readonly<{ signal: AbortSignal }>) => {
attemptSignal = signal;
return new Promise<BoundedPollAttemptResult<string>>(
(resolve) => {
settleAttempt = resolve;
},
);
},
);
const coordinator = createBoundedPollCoordinator<string>({
policy,
operation,
execute,
environment: new MutableEnvironment(),
clock,
});
const pending = coordinator.run();
clock.advance(5_000);
await flush();
expect(execute).toHaveBeenCalledTimes(1);
expect(attemptSignal?.aborted).toBe(false);
clock.advance(54_999);
await flush();
expect(coordinator.getState()).toBe("RUNNING");
clock.advance(1);
await flush();
await expect(pending).resolves.toEqual({
ok: false,
error: {
kind: "POLL_BUDGET_EXHAUSTED",
operation: "POLL",
retryable: false,
},
});
expect(attemptSignal?.aborted).toBe(true);
expect(coordinator.getState()).toBe("DRAINING");
await expect(coordinator.run()).resolves.toMatchObject({
ok: false,
error: { kind: "PROTOCOL_MISMATCH" },
});
settleAttempt?.({
ok: false,
error: {
kind: "ABORTED",
operation: "POLL",
retryable: false,
},
});
await flush();
expect(coordinator.getState()).toBe("IDLE");
});
it("returns promptly when a caller aborts an executor that ignores its signal", async () => {
const clock = new ManualClock();
const caller = new AbortController();
let settleAttempt:
| ((result: BoundedPollAttemptResult<string>) => void)
| undefined;
const execute = vi.fn(
() =>
new Promise<BoundedPollAttemptResult<string>>(
(resolve) => {
settleAttempt = resolve;
},
),
);
const coordinator = createBoundedPollCoordinator<string>({
policy,
operation,
execute,
environment: new MutableEnvironment(),
clock,
});
const pending = coordinator.run({ signal: caller.signal });
clock.advance(5_000);
await flush();
expect(execute).toHaveBeenCalledTimes(1);
caller.abort();
await expect(pending).resolves.toEqual({
ok: false,
error: {
kind: "ABORTED",
operation: "POLL",
retryable: false,
},
});
expect(coordinator.getState()).toBe("DRAINING");
settleAttempt?.({
ok: false,
error: {
kind: "ABORTED",
operation: "POLL",
retryable: false,
},
});
await flush();
expect(coordinator.getState()).toBe("IDLE");
});
it("fails closed and aborts the attempt when the lease deadline clock is unavailable", async () => {
let current = 0;
let sleeps = 0;
const clock: ClockPort = {
now: () => current,
sleep: async (milliseconds) => {
sleeps += 1;
if (sleeps > 1) throw new Error("deadline unavailable");
current += milliseconds;
},
};
let attemptSignal: AbortSignal | undefined;
const execute = vi.fn(
async ({ signal }: Readonly<{ signal: AbortSignal }>) => {
attemptSignal = signal;
return {
ok: true as const,
value: {
kind: "UNCHANGED" as const,
responseBytes: 0 as const,
},
};
},
);
const coordinator = createBoundedPollCoordinator<string>({
policy,
operation,
execute,
environment: new MutableEnvironment(),
clock,
});
await expect(coordinator.run()).resolves.toEqual({
ok: false,
error: {
kind: "PROVIDER_UNAVAILABLE",
operation: "POLL",
retryable: false,
},
});
expect(execute).toHaveBeenCalledOnce();
expect(attemptSignal?.aborted).toBe(true);
});
it("fences a non-cooperative apply callback at the lease deadline", async () => {
const clock = new ManualClock();
let applyContext:
| Readonly<{
signal: AbortSignal;
isCurrent(): boolean;
}>
| undefined;
let releaseApply: (() => void) | undefined;
const coordinator = createBoundedPollCoordinator<string>({
policy,
operation,
execute: async () => ({
ok: true,
value: {
kind: "VALUE",
value: "working",
responseBytes: 10,
state: "RUNNING",
},
}),
environment: new MutableEnvironment(),
clock,
});
const pending = coordinator.run({
onValue: (_value, context) => {
applyContext = context;
return new Promise<void>((resolve) => {
releaseApply = resolve;
});
},
});
clock.advance(5_000);
await flush();
expect(applyContext?.isCurrent()).toBe(true);
clock.advance(55_000);
await flush();
await expect(pending).resolves.toEqual({
ok: false,
error: {
kind: "POLL_BUDGET_EXHAUSTED",
operation: "POLL",
retryable: false,
},
});
expect(applyContext?.signal.aborted).toBe(true);
expect(applyContext?.isCurrent()).toBe(false);
expect(coordinator.getState()).toBe("DRAINING");
releaseApply?.();
await flush();
expect(coordinator.getState()).toBe("IDLE");
});
it("honors Retry-After as a floor and exhausts finite attempts", async () => {
const clock = new ManualClock();
const execute = vi
.fn()
.mockResolvedValueOnce({
ok: false,
error: {
kind: "RATE_LIMITED",
operation: "POLL",
retryable: true,
retryAfterMs: 10_000,
},
})
.mockResolvedValue({
ok: true,
value: { kind: "UNCHANGED", responseBytes: 0 },
});
const coordinator = createBoundedPollCoordinator<string>({
policy: definePollLeasePolicy({
...policy,
maxAttempts: 2,
}),
operation,
execute,
environment: new MutableEnvironment(),
clock,
random: () => 0,
});
const pending = coordinator.run();
clock.advance(5_000);
await flush();
expect(execute).toHaveBeenCalledTimes(1);
clock.advance(9_999);
await flush();
expect(execute).toHaveBeenCalledTimes(1);
clock.advance(1);
await flush();
await expect(pending).resolves.toEqual({
ok: false,
error: {
kind: "POLL_BUDGET_EXHAUSTED",
operation: "POLL",
retryable: false,
},
});
expect(execute).toHaveBeenCalledTimes(2);
});
it.each(["RATE_LIMITED", "PROVIDER_UNAVAILABLE"] as const)(
"does not retry %s without a bounded server hint",
async (kind) => {
const clock = new ManualClock();
const execute = vi.fn().mockResolvedValue({
ok: false,
error: {
kind,
operation: "POLL",
retryable: true,
},
});
const coordinator = createBoundedPollCoordinator<string>({
policy,
operation,
execute,
environment: new MutableEnvironment(),
clock,
random: () => 0,
});
const pending = coordinator.run();
clock.advance(5_000);
await flush();
await expect(pending).resolves.toEqual({
ok: false,
error: {
kind,
operation: "POLL",
retryable: false,
},
});
expect(execute).toHaveBeenCalledTimes(1);
},
);
it("ignores Retry-After for retryable failure kinds that cannot carry the hint", async () => {
const clock = new ManualClock();
const execute = vi
.fn()
.mockResolvedValueOnce({
ok: false,
error: {
kind: "CONNECT_TIMEOUT",
operation: "POLL",
retryable: true,
retryAfterMs: 10_000,
},
})
.mockResolvedValue({
ok: true,
value: {
kind: "VALUE",
value: "done",
responseBytes: 10,
state: "COMPLETED",
},
});
const coordinator = createBoundedPollCoordinator<string>({
policy,
operation,
execute,
environment: new MutableEnvironment(),
clock,
random: () => 0,
});
const pending = coordinator.run();
clock.advance(5_000);
await flush();
expect(execute).toHaveBeenCalledTimes(1);
clock.advance(4_999);
await flush();
expect(execute).toHaveBeenCalledTimes(1);
clock.advance(1);
await expect(pending).resolves.toMatchObject({
ok: true,
value: { attempts: 2, state: "COMPLETED" },
});
expect(execute).toHaveBeenCalledTimes(2);
});
it.each(["AUTH_REQUIRED", "FORBIDDEN"] as const)(
"never retries terminal %s failures even when the executor marks them retryable",
async (kind) => {
const clock = new ManualClock();
const execute = vi.fn().mockResolvedValue({
ok: false,
error: {
kind,
operation: "POLL",
retryable: true,
retryAfterMs: 10_000,
},
});
const coordinator = createBoundedPollCoordinator<string>({
policy,
operation,
execute,
environment: new MutableEnvironment(),
clock,
});
const pending = coordinator.run();
clock.advance(5_000);
await expect(pending).resolves.toEqual({
ok: false,
error: {
kind,
operation: "POLL",
retryable: false,
},
});
expect(execute).toHaveBeenCalledOnce();
},
);
it("aborts in-flight work on hidden lifecycle and rejects late scope results", async () => {
const hiddenClock = new ManualClock();
const hiddenEnvironment = new MutableEnvironment();
const hiddenCoordinator = createBoundedPollCoordinator<string>({
policy,
operation,
environment: hiddenEnvironment,
clock: hiddenClock,
execute: ({ signal }) =>
new Promise((resolve) => {
signal.addEventListener(
"abort",
() =>
resolve({
ok: false,
error: {
kind: "ABORTED",
operation: "POLL",
retryable: false,
},
}),
{ once: true },
);
}),
});
const hidden = hiddenCoordinator.run();
hiddenClock.advance(5_000);
await flush();
hiddenEnvironment.hide();
await expect(hidden).resolves.toEqual({
ok: false,
error: {
kind: "ABORTED",
operation: "POLL",
retryable: false,
},
});
const scopeClock = new ManualClock();
let current = true;
let resolveAttempt:
| ((result: BoundedPollAttemptResult<string>) => void)
| undefined;
const onValue = vi.fn();
const scopeCoordinator = createBoundedPollCoordinator<string>({
policy,
operation,
environment: new MutableEnvironment(),
clock: scopeClock,
isCurrent: () => current,
execute: () =>
new Promise((resolve) => {
resolveAttempt = resolve;
}),
});
const fenced = scopeCoordinator.run({ onValue });
scopeClock.advance(5_000);
await flush();
current = false;
resolveAttempt?.({
ok: true,
value: {
kind: "VALUE",
value: "late",
responseBytes: 10,
state: "COMPLETED",
},
});
await expect(fenced).resolves.toEqual({
ok: false,
error: {
kind: "SCOPE_FENCED",
operation: "POLL",
retryable: false,
},
});
expect(onValue).not.toHaveBeenCalled();
});
it("rejects accessor and extra-key attempt results without re-reading them", async () => {
const accessorClock = new ManualClock();
const readOk = vi.fn(() => true);
const accessorResult = Object.defineProperties(
{},
{
ok: {
enumerable: true,
get: readOk,
},
value: {
enumerable: true,
value: {
kind: "VALUE",
value: "unsafe",
responseBytes: 1,
state: "COMPLETED",
},
},
},
) as BoundedPollAttemptResult<string>;
const accessorCoordinator = createBoundedPollCoordinator<string>({
policy,
operation,
environment: new MutableEnvironment(),
clock: accessorClock,
execute: async () => accessorResult,
});
const accessorRun = accessorCoordinator.run();
accessorClock.advance(5_000);
await expect(accessorRun).resolves.toMatchObject({
ok: false,
error: { kind: "PROTOCOL_MISMATCH" },
});
expect(readOk).not.toHaveBeenCalled();
const extraClock = new ManualClock();
const extraCoordinator = createBoundedPollCoordinator<string>({
policy,
operation,
environment: new MutableEnvironment(),
clock: extraClock,
execute: async () =>
({
ok: true,
value: {
kind: "VALUE",
value: "unsafe",
responseBytes: 1,
state: "COMPLETED",
},
extra: true,
}) as BoundedPollAttemptResult<string>,
});
const extraRun = extraCoordinator.run();
extraClock.advance(5_000);
await expect(extraRun).resolves.toMatchObject({
ok: false,
error: { kind: "PROTOCOL_MISMATCH" },
});
});
it("fails closed on response ceilings, concurrent runs and close", async () => {
const clock = new ManualClock();
const coordinator = createBoundedPollCoordinator<string>({
policy,
operation: {
...operation,
maxResponseBytes: 512,
},
environment: new MutableEnvironment(),
clock,
execute: async () => ({
ok: true,
value: {
kind: "VALUE",
value: "oversized",
responseBytes: 513,
state: "RUNNING",
},
}),
});
const first = coordinator.run();
await expect(coordinator.run()).resolves.toEqual({
ok: false,
error: {
kind: "PROTOCOL_MISMATCH",
operation: "POLL",
retryable: false,
},
});
clock.advance(5_000);
await expect(first).resolves.toEqual({
ok: false,
error: {
kind: "PROTOCOL_MISMATCH",
operation: "POLL",
retryable: false,
},
});
const pending = coordinator.run();
coordinator.close();
coordinator.close();
await expect(pending).resolves.toEqual({
ok: false,
error: {
kind: "CLOSED",
operation: "POLL",
retryable: false,
},
});
expect(coordinator.getState()).toBe("CLOSED");
});
});
@@ -0,0 +1,105 @@
import { describe, expect, it } from "vitest";
import {
BOUNDED_POLLING_CEILINGS,
assertBoundedPollOperation,
definePollLeasePolicy,
type BoundedPollOperationContract,
} from "../../../src/application/policies/bounded-polling.ts";
const input = {
operationId: "GET_JOB_STATUS",
owner: "reference-job",
minimumIntervalMs: 5_000,
successIntervalMs: 10_000,
maxIntervalMs: 60_000,
maxAttempts: 5,
maxElapsedMs: 120_000,
maxResponseBytes: 16_384,
visibility: "VISIBLE_ONLY",
fallbackReason: "CONVERGENCE",
terminalStates: ["COMPLETED", "FAILED"],
} as const;
describe("bounded polling policy", () => {
it("copies and freezes a finite immutable lease", () => {
const terminalStates = ["COMPLETED", "FAILED"];
const policy = definePollLeasePolicy({
...input,
terminalStates,
});
terminalStates.push("CANCELLED");
expect(policy.terminalStates).toEqual(["COMPLETED", "FAILED"]);
expect(Object.isFrozen(policy)).toBe(true);
expect(Object.isFrozen(policy.terminalStates)).toBe(true);
});
it("rejects push-like cadence and unbounded convergence", () => {
expect(() =>
definePollLeasePolicy({
...input,
minimumIntervalMs: 4_999,
}),
).toThrow(TypeError);
expect(() =>
definePollLeasePolicy({
...input,
terminalStates: [],
}),
).toThrow(TypeError);
expect(() =>
definePollLeasePolicy({
...input,
maxAttempts: 121,
}),
).toThrow(TypeError);
});
it("admits only a one-request terminal replay-safe REST query", () => {
const policy = definePollLeasePolicy(input);
const operation: BoundedPollOperationContract = {
operationId: "GET_JOB_STATUS",
contractVersion: 2,
protocol: "REST",
semantics: "QUERY",
method: "GET",
replayPolicy: "SAFE",
retry: "never",
maxResponseBytes: 16_384,
transportMaxAttempts: 1,
authRecoveryCount: 0,
maxCumulativeSleepMs: 0,
serverStream: false,
};
expect(() =>
assertBoundedPollOperation(policy, operation),
).not.toThrow();
expect(() =>
assertBoundedPollOperation(policy, {
...operation,
transportMaxAttempts: 2 as 1,
}),
).toThrow(TypeError);
expect(() =>
assertBoundedPollOperation(policy, {
...operation,
maxResponseBytes: 8_192,
}),
).not.toThrow();
expect(() =>
assertBoundedPollOperation(policy, {
...operation,
maxResponseBytes: 0,
}),
).toThrow(TypeError);
expect(() =>
assertBoundedPollOperation(policy, {
...operation,
maxResponseBytes:
BOUNDED_POLLING_CEILINGS.maxResponseBytes + 1,
}),
).toThrow(TypeError);
});
});
+243
View File
@@ -0,0 +1,243 @@
import { describe, expect, it } from "vitest";
import {
compareRealtimeSequences,
isCanonicalRealtimeSequence,
isRealtimeResumeCursor,
isStrictRealtimeTimestamp,
nextRealtimeSequence,
REALTIME_MAX_SEQUENCE,
} from "../../../src/contracts/realtime-events.ts";
import {
isValidatedRealtimeEventDto,
} from "../../../src/adapters/realtime/event-codec.ts";
import {
TEST_LIMITS,
createTestRealtimeCodec,
createTestRealtimeRegistry,
realtimeEventJson,
realtimeEventValue,
} from "./fixture.ts";
describe("REALTIME_EVENT_V1 codec", () => {
it("accepts an exact registered envelope and snapshots schema output", () => {
const codec = createTestRealtimeCodec();
const result = codec.decode(realtimeEventJson());
expect(result).toMatchObject({
ok: true,
value: {
envelope: {
protocol: "REALTIME_EVENT_V1",
sequence: "1",
recoveryMode: "CURSOR",
resumeCursor: "cursor-00000001",
payload: { value: "changed" },
},
},
});
if (!result.ok) return;
expect(isValidatedRealtimeEventDto(result.value)).toBe(true);
expect(Object.isFrozen(result.value)).toBe(true);
expect(Object.isFrozen(result.value.envelope)).toBe(true);
expect(Object.isFrozen(result.value.envelope.payload)).toBe(true);
expect(result.value.wireBytes).toBeGreaterThan(0);
expect(result.value.fingerprintBytes).toBeGreaterThan(0);
});
it("rejects extra keys, unknown registrations and schema failures", () => {
const codec = createTestRealtimeCodec();
expect(
codec.decode(
JSON.stringify({
...realtimeEventValue(),
arbitrary: "override",
}),
),
).toMatchObject({
ok: false,
error: { kind: "MALFORMED_EVENT", operation: "DECODE" },
});
expect(
codec.decode(
realtimeEventJson({ streamId: "UNKNOWN_STREAM" }),
),
).toMatchObject({
ok: false,
error: { kind: "MALFORMED_EVENT" },
});
expect(
codec.decode(
realtimeEventJson({ eventType: "UNKNOWN_EVENT" }),
),
).toMatchObject({
ok: false,
error: { kind: "MALFORMED_EVENT" },
});
expect(
codec.decode(realtimeEventJson({ payload: { value: 42 } })),
).toMatchObject({
ok: false,
error: { kind: "MALFORMED_EVENT" },
});
const duplicateEnvelopeMember =
`{"eventId":"shadowed",${JSON.stringify(
realtimeEventValue(),
).slice(1)}`;
expect(codec.decode(duplicateEnvelopeMember)).toMatchObject({
ok: false,
error: { kind: "MALFORMED_EVENT", operation: "DECODE" },
});
const duplicatePayloadMember = realtimeEventJson().replace(
'"payload":{"value":"changed"}',
'"payload":{"value":"first","\\u0076alue":"changed"}',
);
expect(codec.decode(duplicatePayloadMember)).toMatchObject({
ok: false,
error: { kind: "MALFORMED_EVENT", operation: "DECODE" },
});
});
it("fails closed on version, sequence, timestamp, scope and cursor syntax", () => {
const codec = createTestRealtimeCodec();
expect(
codec.decode(realtimeEventJson({ protocol: "REALTIME_EVENT_V2" })),
).toMatchObject({
ok: false,
error: { kind: "PROTOCOL_MISMATCH" },
});
for (const overrides of [
{ sequence: "01" },
{ sequence: "18446744073709551616" },
{ occurredAt: "2026-02-30T01:02:03Z" },
{ occurredAt: "2026-07-28 01:02:03Z" },
{ scopeBinding: "scope\r\ninjected" },
{ resumeCursor: "" },
{ resumeCursor: "cursor\ninjected" },
{ recoveryMode: "SNAPSHOT_ONLY", resumeCursor: null },
]) {
expect(codec.decode(realtimeEventJson(overrides))).toMatchObject({
ok: false,
error: { kind: "MALFORMED_EVENT" },
});
}
});
it("enforces global, per-stream and payload structure ceilings", () => {
const strictCodec = createTestRealtimeCodec(
createTestRealtimeRegistry({
limits: {
...TEST_LIMITS,
maxEventBytes: 512,
maxPayloadNodes: 1,
},
}),
);
expect(
strictCodec.decode(realtimeEventJson()),
).toMatchObject({
ok: false,
error: { kind: "MALFORMED_EVENT" },
});
const byteBoundCodec = createTestRealtimeCodec(
createTestRealtimeRegistry({
limits: {
...TEST_LIMITS,
maxEventBytes: 512,
},
}),
);
expect(
byteBoundCodec.decode(
realtimeEventJson({ payload: { value: "x".repeat(600) } }),
),
).toMatchObject({
ok: false,
error: { kind: "EVENT_TOO_LARGE" },
});
expect(
createTestRealtimeCodec().decode(
"x".repeat(64 * 1024 + 1),
),
).toMatchObject({
ok: false,
error: { kind: "EVENT_TOO_LARGE" },
});
});
it("supports the exact null-cursor SESSION_REBUILD discriminant", () => {
const registry = createTestRealtimeRegistry({
recovery: {
mode: "SESSION_REBUILD",
rebuildInputId: "referenceRealtimeRebuild",
},
delivery: "EPHEMERAL",
stateBearing: false,
});
const codec = createTestRealtimeCodec(registry);
expect(
codec.decode(
realtimeEventJson({
recoveryMode: "SESSION_REBUILD",
resumeCursor: null,
}),
),
).toMatchObject({
ok: true,
value: {
envelope: {
recoveryMode: "SESSION_REBUILD",
resumeCursor: null,
},
},
});
expect(
codec.decode(
realtimeEventJson({
recoveryMode: "SESSION_REBUILD",
resumeCursor: "synthetic",
}),
),
).toMatchObject({
ok: false,
error: { kind: "MALFORMED_EVENT" },
});
});
it("normalizes semantic identity independently of JSON key order", () => {
const codec = createTestRealtimeCodec();
const value = realtimeEventValue();
const reversed = Object.fromEntries(
Object.entries(value).reverse(),
);
const first = codec.decode(JSON.stringify(value));
const second = codec.decode(JSON.stringify(reversed));
expect(first.ok).toBe(true);
expect(second.ok).toBe(true);
if (!first.ok || !second.ok) return;
expect(first.value.semanticFingerprint).toBe(
second.value.semanticFingerprint,
);
});
it("uses bounded uint64 sequence and header-safe cursor helpers", () => {
expect(isCanonicalRealtimeSequence("0")).toBe(true);
expect(isCanonicalRealtimeSequence(REALTIME_MAX_SEQUENCE)).toBe(true);
expect(isCanonicalRealtimeSequence("00")).toBe(false);
expect(isCanonicalRealtimeSequence("18446744073709551616")).toBe(false);
expect(compareRealtimeSequences("9", "10")).toBe(-1);
expect(nextRealtimeSequence("9")).toBe("10");
expect(nextRealtimeSequence(REALTIME_MAX_SEQUENCE)).toBeNull();
expect(isRealtimeResumeCursor("cursor:/+=._~-")).toBe(true);
expect(isRealtimeResumeCursor("cursor\nunsafe")).toBe(false);
expect(isStrictRealtimeTimestamp("2024-02-29T23:59:59Z")).toBe(true);
expect(isStrictRealtimeTimestamp("2023-02-29T23:59:59Z")).toBe(false);
});
});
+211
View File
@@ -0,0 +1,211 @@
import { describe, expect, it, vi } from "vitest";
import type {
RealtimeAcceptDisposition,
RealtimeRecoveryCheckpoint,
} from "../../../src/application/ports/realtime/event-authority.ts";
import type {
RealtimeResult,
} from "../../../src/application/ports/realtime/shared.ts";
import {
createRealtimeEventConsumer,
} from "../../../src/adapters/realtime/event-consumer.ts";
import type {
RealtimeEventCodec,
ValidatedRealtimeEventDto,
} from "../../../src/adapters/realtime/event-codec.ts";
import {
realtimeFailure,
realtimeSuccess,
} from "../../../src/adapters/realtime/result.ts";
import type {
RealtimeStreamCoordinator,
} from "../../../src/adapters/realtime/stream-coordinator.ts";
function setup(
recoveryMode:
| "CURSOR"
| "SNAPSHOT_ONLY"
| "SESSION_REBUILD",
resumeCursor: string | null,
) {
const dto = {
envelope: {
recoveryMode,
resumeCursor,
streamId: "REFERENCE_STREAM",
},
} as ValidatedRealtimeEventDto;
const codec: RealtimeEventCodec = {
decode: vi.fn(() => realtimeSuccess(dto)),
};
const accept = vi.fn<
(
event: ValidatedRealtimeEventDto,
signal?: AbortSignal,
) => Promise<RealtimeResult<RealtimeAcceptDisposition>>
>(() =>
Promise.resolve(
realtimeSuccess({
outcome: "DROPPED" as const,
reason: "DUPLICATE_EVENT" as const,
}),
),
);
const consumer = createRealtimeEventConsumer({
codec,
coordinator: {
accept,
} as Pick<RealtimeStreamCoordinator, "accept">,
});
return { consumer, accept, codec };
}
describe("realtime transport event consumer", () => {
it("requires exact equality between SSE id and CURSOR envelope", async () => {
const matching = setup("CURSOR", "cursor.0001");
await expect(
matching.consumer.consume("{}", {
kind: "SSE_DIRECT_CURSOR",
eventId: "cursor.0001",
}),
).resolves.toMatchObject({ ok: true });
expect(matching.accept).toHaveBeenCalledTimes(1);
const advanced = setup("CURSOR", "cursor.0002");
await expect(
advanced.consumer.consume("{}", {
kind: "SSE_DIRECT_CURSOR",
eventId: "cursor.0001",
}),
).resolves.toEqual(
realtimeFailure("PROTOCOL_MISMATCH", "RECEIVE"),
);
expect(advanced.accept).not.toHaveBeenCalled();
});
it("forbids SSE id semantics for non-CURSOR recovery", async () => {
const runtime = setup("SNAPSHOT_ONLY", null);
await expect(
runtime.consumer.consume("{}", {
kind: "SSE_NO_CURSOR",
}),
).resolves.toMatchObject({ ok: true });
await expect(
runtime.consumer.consume("{}", {
kind: "SSE_DIRECT_CURSOR",
eventId: "cursor.0001",
}),
).resolves.toEqual(
realtimeFailure("PROTOCOL_MISMATCH", "RECEIVE"),
);
});
it("serializes an encapsulated WebSocket envelope through the codec", async () => {
const runtime = setup("SESSION_REBUILD", null);
await expect(
runtime.consumer.consumeEncapsulated(
Object.freeze({ protocol: "REALTIME_EVENT_V1" }),
),
).resolves.toMatchObject({ ok: true });
expect(runtime.codec.decode).toHaveBeenCalledWith(
'{"protocol":"REALTIME_EVENT_V1"}',
);
});
it("projects common dispositions into the canonical transport outcome", async () => {
const runtime = setup("SNAPSHOT_ONLY", null);
await expect(
runtime.consumer.consumeForTransport("{}", {
kind: "SSE_NO_CURSOR",
}),
).resolves.toEqual({
ok: true,
value: { kind: "CONTINUE" },
});
const checkpoint = Object.freeze({
recoveryMode: "SNAPSHOT_ONLY" as const,
streamEpoch: "stream-epoch.0001",
lastAppliedSequence: "0",
resumeCursor: null,
}) as RealtimeRecoveryCheckpoint;
runtime.accept.mockResolvedValueOnce(
realtimeSuccess({
outcome: "RECOVERED",
reason: "INITIALIZE",
resumeState: checkpoint,
}),
);
await expect(
runtime.consumer.consumeForTransport("{}", {
kind: "SSE_NO_CURSOR",
}),
).resolves.toEqual({
ok: true,
value: {
kind: "RECOVERY_COMMITTED",
streamId: "REFERENCE_STREAM",
checkpoint,
},
});
runtime.accept.mockResolvedValueOnce(
realtimeSuccess({
outcome: "DROPPED",
reason: "RECOVERY_IN_PROGRESS",
}),
);
await expect(
runtime.consumer.consumeForTransport("{}", {
kind: "SSE_NO_CURSOR",
}),
).resolves.toEqual(
realtimeFailure("PROTOCOL_MISMATCH", "RECEIVE"),
);
runtime.accept.mockResolvedValueOnce(
realtimeSuccess({
outcome: "DROPPED",
reason: "SCOPE_FENCED",
}),
);
await expect(
runtime.consumer.consumeForTransport("{}", {
kind: "SSE_NO_CURSOR",
}),
).resolves.toEqual(
realtimeFailure("SCOPE_FENCED", "RECEIVE"),
);
});
it("threads transport cancellation into the common authority", async () => {
const runtime = setup("SNAPSHOT_ONLY", null);
const active = new AbortController();
await runtime.consumer.consume(
"{}",
{ kind: "SSE_NO_CURSOR" },
active.signal,
);
expect(runtime.accept).toHaveBeenLastCalledWith(
expect.anything(),
active.signal,
);
const aborted = new AbortController();
aborted.abort();
await expect(
runtime.consumer.consume(
"{}",
{ kind: "SSE_NO_CURSOR" },
aborted.signal,
),
).resolves.toEqual(
realtimeFailure("ABORTED", "RECEIVE"),
);
expect(runtime.accept).toHaveBeenCalledTimes(1);
});
});
@@ -0,0 +1,568 @@
import { describe, expect, it, vi } from "vitest";
import {
SSE_CONTINUE,
createFetchSseConnection,
} from "../../../src/adapters/realtime/sse/fetch-sse-connection.ts";
import {
realtimeTransportRecoveryCommitted,
type RealtimeRecoveryCheckpoint,
} from "../../../src/application/ports/realtime/event-authority.ts";
import type { RealtimeResult } from "../../../src/application/ports/realtime/shared.ts";
import type {
StreamRegistrationId,
} from "../../../src/contracts/realtime-streams.ts";
import {
realtimeFailure,
realtimeSuccess,
} from "../../../src/adapters/realtime/result.ts";
const encoder = new TextEncoder();
const continueEvent = () => realtimeSuccess(SSE_CONTINUE);
const RECOVERY_CHECKPOINT = Object.freeze({
recoveryMode: "CURSOR" as const,
streamEpoch: "stream-epoch.0001",
lastAppliedSequence: "1",
resumeCursor: "cursor-1",
}) as RealtimeRecoveryCheckpoint;
const RECOVERY_OUTCOME = realtimeTransportRecoveryCommitted(
"REFERENCE_STREAM" as StreamRegistrationId,
RECOVERY_CHECKPOINT,
);
function eventStream(
chunks: readonly string[],
options: Readonly<{
status?: number;
contentType?: string;
}> = {},
): Response {
return new Response(
new ReadableStream<Uint8Array>({
start(controller) {
for (const chunk of chunks) {
controller.enqueue(encoder.encode(chunk));
}
controller.close();
},
}),
{
status: options.status ?? 200,
headers: {
"Content-Type":
options.contentType ?? "text/event-stream; charset=utf-8",
},
},
);
}
function connection(
fetcher: typeof fetch,
recoveryMode: "CURSOR" | "SESSION_REBUILD" | "SNAPSHOT_ONLY" =
"CURSOR",
) {
return createFetchSseConnection({
endpoint: "https://app.example.test/events",
applicationOrigin: "https://app.example.test",
recoveryMode,
fetcher,
});
}
describe("fetch SSE connection", () => {
it("uses the fixed request profile and streams events sequentially", async () => {
const response = eventStream([
": heartbeat\r\n",
"retry: 2500\n",
"id: cursor-1\ndata: first\n",
"data: second\n\n",
]);
const fetcher = vi.fn(
async (_input: RequestInfo | URL, _init?: RequestInit) =>
response,
);
const received: string[] = [];
const comments = vi.fn();
const hints = vi.fn();
const runtime = connection(fetcher as typeof fetch);
await expect(
runtime.read({
resumeCursor: "cursor-0",
onEvent: async (event) => {
received.push(event.data);
return realtimeSuccess(SSE_CONTINUE);
},
onComment: comments,
onRetryHint: hints,
}),
).resolves.toEqual({
ok: true,
value: {
kind: "EOF",
incompleteEventDiscarded: false,
retryHintMs: 2_500,
},
});
expect(received).toEqual(["first\nsecond"]);
expect(comments).toHaveBeenCalledOnce();
expect(hints).toHaveBeenCalledWith(2_500);
const [target, init] = fetcher.mock.calls[0] ?? [];
expect(target).toBe("https://app.example.test/events");
expect(init).toMatchObject({
method: "GET",
credentials: "same-origin",
redirect: "error",
cache: "no-store",
referrerPolicy: "no-referrer",
});
expect(new Headers(init?.headers)).toEqual(
new Headers({
Accept: "text/event-stream",
"Last-Event-ID": "cursor-0",
}),
);
});
it("holds event consumption behind the validated open barrier gate", async () => {
const runtime = connection(
(async () =>
eventStream([
"id: cursor-1\ndata: after-barrier\n\n",
])) as typeof fetch,
);
let release:
| ((result: RealtimeResult<void>) => void)
| undefined;
const gate = new Promise<RealtimeResult<void>>((resolve) => {
release = resolve;
});
const onOpen = vi.fn(() => gate);
const onEvent = vi.fn(continueEvent);
const reading = runtime.read({
resumeCursor: null,
onOpen,
onEvent,
});
await vi.waitFor(() => expect(onOpen).toHaveBeenCalledOnce());
expect(onEvent).not.toHaveBeenCalled();
release?.(realtimeSuccess(undefined));
await expect(reading).resolves.toMatchObject({
ok: true,
value: { kind: "EOF" },
});
expect(onEvent).toHaveBeenCalledOnce();
});
it("propagates an exact open-barrier failure before reading events", async () => {
const runtime = connection(
(async () =>
eventStream([
"id: cursor-1\ndata: unreachable\n\n",
])) as typeof fetch,
);
const barrierFailure = realtimeFailure(
"SCOPE_FENCED",
"RECOVER",
false,
);
const onEvent = vi.fn(continueEvent);
await expect(
runtime.read({
resumeCursor: null,
onOpen: () => barrierFailure,
onEvent,
}),
).resolves.toBe(barrierFailure);
expect(onEvent).not.toHaveBeenCalled();
});
it("treats 204 as terminal and rejects incorrect media types", async () => {
const terminal = connection(
(async () => new Response(null, { status: 204 })) as typeof fetch,
);
await expect(
terminal.read({
resumeCursor: null,
onEvent: continueEvent,
}),
).resolves.toEqual({
ok: true,
value: { kind: "NO_RECONNECT" },
});
const wrongType = connection(
(async () =>
eventStream(["data: value\n\n"], {
contentType: "application/json",
})) as typeof fetch,
);
await expect(
wrongType.read({
resumeCursor: null,
onEvent: continueEvent,
}),
).resolves.toEqual({
ok: false,
error: {
kind: "PROTOCOL_MISMATCH",
operation: "CONNECT",
retryable: false,
},
});
});
it.each([
[401, "AUTH_REQUIRED", false],
[403, "FORBIDDEN", false],
[409, "CURSOR_EXPIRED", false],
[410, "CURSOR_EXPIRED", false],
[429, "RATE_LIMITED", true],
[502, "PROVIDER_UNAVAILABLE", true],
[503, "PROVIDER_UNAVAILABLE", true],
[504, "PROVIDER_UNAVAILABLE", true],
[500, "PROTOCOL_MISMATCH", false],
] as const)(
"maps HTTP %i to %s without exposing a response body",
async (status, kind, retryable) => {
const runtime = connection(
(async () =>
new Response("private backend text", {
status,
headers:
status === 429 || status === 503
? { "Retry-After": "10" }
: undefined,
})) as typeof fetch,
);
const result = await runtime.read({
resumeCursor: null,
onEvent: continueEvent,
});
expect(result).toEqual({
ok: false,
error: {
kind,
operation: "CONNECT",
retryable,
},
});
expect(JSON.stringify(result)).not.toContain("private backend");
},
);
it.each([
[429, "RATE_LIMITED"],
[503, "PROVIDER_UNAVAILABLE"],
] as const)(
"does not retry HTTP %i without a bounded server hint",
async (status, kind) => {
const runtime = connection(
(async () => new Response(null, { status })) as typeof fetch,
);
await expect(
runtime.read({
resumeCursor: null,
onEvent: continueEvent,
}),
).resolves.toEqual({
ok: false,
error: {
kind,
operation: "CONNECT",
retryable: false,
},
});
},
);
it("does not clamp an excessive Retry-After into an early retry", async () => {
const runtime = connection(
(async () =>
new Response(null, {
status: 429,
headers: { "Retry-After": "120" },
})) as typeof fetch,
);
await expect(
runtime.read({
resumeCursor: null,
onEvent: continueEvent,
}),
).resolves.toEqual({
ok: false,
error: {
kind: "RATE_LIMITED",
operation: "CONNECT",
retryable: false,
},
});
});
it("enforces direct event IDs only for CURSOR recovery", async () => {
const missingCursorId = connection(
(async () =>
eventStream(["data: value\n\n"])) as typeof fetch,
);
await expect(
missingCursorId.read({
resumeCursor: null,
onEvent: continueEvent,
}),
).resolves.toEqual({
ok: false,
error: {
kind: "PROTOCOL_MISMATCH",
operation: "DECODE",
retryable: false,
},
});
const unexpectedCursorId = connection(
(async () =>
eventStream([
"id: cursor-1\ndata: value\n\n",
])) as typeof fetch,
"SNAPSHOT_ONLY",
);
await expect(
unexpectedCursorId.read({
resumeCursor: null,
onEvent: continueEvent,
}),
).resolves.toEqual({
ok: false,
error: {
kind: "PROTOCOL_MISMATCH",
operation: "DECODE",
retryable: false,
},
});
});
it("stops the old stream after authority recovery commits", async () => {
const runtime = connection(
(async () =>
eventStream([
"id: cursor-1\ndata: first\n\n",
"id: cursor-2\ndata: stale-generation\n\n",
])) as typeof fetch,
);
const onEvent = vi.fn(() =>
realtimeSuccess(RECOVERY_OUTCOME),
);
await expect(
runtime.read({
resumeCursor: null,
onEvent,
}),
).resolves.toEqual({
ok: true,
value: RECOVERY_OUTCOME,
});
expect(onEvent).toHaveBeenCalledOnce();
});
it("propagates a canonical consumer failure and aborts its event generation", async () => {
const runtime = connection(
(async () =>
eventStream([
"id: cursor-1\ndata: forbidden\n\n",
])) as typeof fetch,
);
let handlerSignal: AbortSignal | undefined;
await expect(
runtime.read({
resumeCursor: null,
onEvent: (_event, signal) => {
handlerSignal = signal;
return realtimeFailure("FORBIDDEN", "RECEIVE");
},
}),
).resolves.toEqual(
realtimeFailure("FORBIDDEN", "RECEIVE"),
);
expect(handlerSignal?.aborted).toBe(true);
});
it("fences recovery immediately and waits for bounded reader cancellation", async () => {
let releaseCancellation: (() => void) | undefined;
let cancellationStarted = false;
const stream = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(
encoder.encode("id: cursor-1\ndata: first\n\n"),
);
},
cancel() {
cancellationStarted = true;
return new Promise<void>((resolve) => {
releaseCancellation = resolve;
});
},
});
const runtime = connection(
(async () =>
new Response(stream, {
headers: { "Content-Type": "text/event-stream" },
})) as typeof fetch,
);
const pending = runtime.read({
resumeCursor: null,
onEvent: () =>
realtimeSuccess(RECOVERY_OUTCOME),
});
let settled = false;
void pending.then(() => {
settled = true;
});
for (let turn = 0; turn < 20; turn += 1) {
await Promise.resolve();
}
expect(cancellationStarted).toBe(true);
expect(settled).toBe(false);
releaseCancellation?.();
await expect(pending).resolves.toEqual({
ok: true,
value: RECOVERY_OUTCOME,
});
});
it("discards incomplete EOF and closes an active reader idempotently", async () => {
const incomplete = connection(
(async () =>
eventStream([
"id: cursor-1\ndata: incomplete\n",
])) as typeof fetch,
);
await expect(
incomplete.read({
resumeCursor: null,
onEvent() {
throw new Error("must not run");
},
}),
).resolves.toEqual({
ok: true,
value: {
kind: "EOF",
incompleteEventDiscarded: true,
retryHintMs: null,
},
});
const pendingStream = new ReadableStream<Uint8Array>({
pull: () => new Promise<void>(() => {}),
});
const active = connection(
(async () =>
new Response(pendingStream, {
headers: { "Content-Type": "text/event-stream" },
})) as typeof fetch,
);
const pending = active.read({
resumeCursor: null,
onEvent: continueEvent,
});
await Promise.resolve();
await Promise.resolve();
active.close();
active.close();
await expect(pending).resolves.toMatchObject({
ok: false,
error: {
kind: "ABORTED",
operation: "CONNECT",
retryable: false,
},
});
await expect(
active.read({
resumeCursor: null,
onEvent: continueEvent,
}),
).resolves.toEqual({
ok: false,
error: {
kind: "CLOSED",
operation: "CONNECT",
retryable: false,
},
});
});
it("dispatches a CR-terminated blank block completed at EOF", async () => {
const runtime = connection(
(async () =>
eventStream([
"id: cursor-1\rdata: final\r\r",
])) as typeof fetch,
);
const received = vi.fn(() =>
realtimeSuccess(SSE_CONTINUE),
);
await expect(
runtime.read({
resumeCursor: null,
onEvent: received,
}),
).resolves.toMatchObject({
ok: true,
value: {
kind: "EOF",
incompleteEventDiscarded: false,
},
});
expect(received).toHaveBeenCalledWith(
expect.objectContaining({ data: "final" }),
expect.any(AbortSignal),
);
});
it("rejects arbitrary endpoints and mode-incoherent cursors before fetch", async () => {
expect(() =>
createFetchSseConnection({
endpoint: "https://other.example.test/events",
applicationOrigin: "https://app.example.test",
recoveryMode: "CURSOR",
}),
).toThrow(TypeError);
expect(() =>
createFetchSseConnection({
endpoint:
"https://app.example.test/events?token=not-allowed",
applicationOrigin: "https://app.example.test",
recoveryMode: "CURSOR",
}),
).toThrow(TypeError);
const fetcher = vi.fn(
async () => eventStream(["data: unreachable\n\n"]),
);
const snapshotOnly = connection(
fetcher as unknown as typeof fetch,
"SNAPSHOT_ONLY",
);
await expect(
snapshotOnly.read({
resumeCursor: "cursor-not-allowed",
onEvent: continueEvent,
}),
).resolves.toEqual({
ok: false,
error: {
kind: "PROTOCOL_MISMATCH",
operation: "CONNECT",
retryable: false,
},
});
expect(fetcher).not.toHaveBeenCalled();
});
});
+252
View File
@@ -0,0 +1,252 @@
import {
mappingSuccess,
type InstalledBoundaryMapper,
} from "../../../src/contracts/boundary-mapper.ts";
import type { ApiOperation } from "../../../src/contracts/api-operations.ts";
import {
createRealtimePolicyRegistry,
defineEventTypeId,
defineExternalEventEffectProfileId,
defineRealtimeEndpointId,
defineRealtimeKillSwitchId,
defineStreamRegistrationId,
type RealtimeEventTypeRegistration,
type RealtimeLimits,
type RealtimePolicyRegistry,
type RealtimeRecoveryProfile,
type RealtimeStreamRegistration,
} from "../../../src/contracts/realtime-streams.ts";
import type { RuntimeSchemaCodec } from "../../../src/contracts/schema-registry.ts";
import {
createRealtimeEventCodec,
type RealtimeEventCodec,
} from "../../../src/adapters/realtime/event-codec.ts";
export const STREAM_ID = defineStreamRegistrationId("REFERENCE_STREAM");
export const EVENT_TYPE = defineEventTypeId("REFERENCE_CHANGED");
export const ENDPOINT_ID = defineRealtimeEndpointId("REFERENCE_ENDPOINT");
export const EFFECT_PROFILE_ID =
defineExternalEventEffectProfileId("REFERENCE_INVALIDATE");
export const KILL_SWITCH_ID =
defineRealtimeKillSwitchId("REFERENCE_KILL_SWITCH");
export const TEST_LIMITS: RealtimeLimits = Object.freeze({
maxEventBytes: 4_096,
maxPayloadDepth: 8,
maxPayloadNodes: 128,
maxQueueEvents: 8,
maxQueueBytes: 32_768,
maxDedupeEntries: 16,
maxDedupeBytes: 32_768,
dedupeTtlMs: 60_000,
});
const eventPayloadCodec: RuntimeSchemaCodec = Object.freeze({
schemaId: "ReferenceRealtimePayload",
parse(value) {
if (
!value ||
typeof value !== "object" ||
Array.isArray(value) ||
Object.keys(value).length !== 1 ||
typeof (value as Readonly<Record<string, unknown>>).value !== "string"
) {
return {
success: false,
issues: [{ path: "value", code: "INVALID_TYPE" }],
};
}
return {
success: true,
data: {
value: (value as Readonly<Record<string, string>>).value,
},
};
},
});
const checkpointCodec: RuntimeSchemaCodec = Object.freeze({
schemaId: "ReferenceRealtimeCheckpoint",
parse: (value) => ({ success: true, data: value }),
});
export const TEST_SCHEMA_CODECS = Object.freeze({
ReferenceRealtimePayload: eventPayloadCodec,
ReferenceRealtimeCheckpoint: checkpointCodec,
});
export const TEST_MAPPER: InstalledBoundaryMapper = Object.freeze({
mapperId: "ReferenceRealtimeMapper",
mapperVersion: 1,
inputSchemaId: "ReferenceRealtimePayload",
outputContractId: "ReferenceRealtimeEvent",
owner: "reference-feature",
maxOutputItems: 1,
map(input) {
if (
!input ||
typeof input !== "object" ||
typeof (input as Readonly<Record<string, unknown>>).value !== "string"
) {
return { ok: false, code: "MAPPING_INVARIANT_REJECTED" };
}
return mappingSuccess(
Object.freeze({
value: (input as Readonly<Record<string, string>>).value,
}),
);
},
});
export const TEST_MAPPERS = Object.freeze({
ReferenceRealtimeMapper: TEST_MAPPER,
});
const snapshotOperation: ApiOperation = Object.freeze({
method: "GET",
path: "/api/reference-snapshot",
operationId: "GET_REFERENCE_REALTIME_SNAPSHOT",
auth: "external-session",
timeoutMs: 10_000,
idempotency: "safe",
retry: "never",
requestSource: "none",
requestSchema: "NoRequest",
responseSchema: "ReferenceRealtimeCheckpoint",
owner: "reference-feature",
contractVersion: 2,
protocol: "REST",
semantics: "QUERY",
replayPolicy: "SAFE",
idempotencyKeyPolicy: "NONE",
mapperId: "ReferenceRealtimeSnapshotMapper",
successStatuses: [200],
responseMediaTypes: ["application/json"],
maxResponseBytes: 16_384,
providerId: "PRIMARY_API",
authProfileId: "EXTERNAL_SESSION",
csrfProfileId: "NONE",
pathSchema: "NoRequest",
pathParameterNames: [],
maxEncodedSearchBytes: 0,
});
export const TEST_API_OPERATIONS = Object.freeze({
GET_REFERENCE_REALTIME_SNAPSHOT: snapshotOperation,
});
export type TestRegistryOptions = Readonly<{
recovery?: RealtimeRecoveryProfile;
delivery?: RealtimeStreamRegistration["delivery"];
stateBearing?: boolean;
limits?: RealtimeLimits;
streamMutator?: (
stream: RealtimeStreamRegistration,
) => RealtimeStreamRegistration;
eventTypeMutator?: (
eventType: RealtimeEventTypeRegistration,
) => RealtimeEventTypeRegistration;
}>;
export function createTestRealtimeRegistry(
options: TestRegistryOptions = {},
): RealtimePolicyRegistry {
const recovery =
options.recovery ??
({
mode: "CURSOR",
snapshotOperationId: "GET_REFERENCE_REALTIME_SNAPSHOT",
checkpointCodecId: "ReferenceRealtimeCheckpoint",
barrier: "REPLAY",
} as const);
const eventType: RealtimeEventTypeRegistration = {
id: EVENT_TYPE,
owner: "reference-feature",
payloadSchemaId: "ReferenceRealtimePayload",
mapperId: "ReferenceRealtimeMapper",
effectProfileId: EFFECT_PROFILE_ID,
stateBearing: options.stateBearing ?? true,
};
const stream: RealtimeStreamRegistration = {
id: STREAM_ID,
protocol: "REALTIME_EVENT_V1",
owner: "reference-feature",
scope: "ACCOUNT_BOUND",
primaryTransport: "SSE",
endpointId: ENDPOINT_ID,
eventTypeIds: [EVENT_TYPE],
delivery: options.delivery ?? "AUTHORITATIVE_DELTA",
recovery,
fallback:
recovery.mode === "SESSION_REBUILD"
? "EXPLICITLY_STALE"
: "BOUNDED_POLLING",
hiddenPolicy: "CLOSE",
limits: options.limits ?? TEST_LIMITS,
killSwitchId: KILL_SWITCH_ID,
};
return createRealtimePolicyRegistry({
streams: [options.streamMutator?.(stream) ?? stream],
eventTypes: [
options.eventTypeMutator?.(eventType) ?? eventType,
],
bindings: {
schemaCodecs: TEST_SCHEMA_CODECS,
mappers: TEST_MAPPERS,
apiOperations: TEST_API_OPERATIONS,
endpointIds: [ENDPOINT_ID],
effectProfileIds: [EFFECT_PROFILE_ID],
killSwitchIds: [KILL_SWITCH_ID],
rebuildInputIds: ["referenceRealtimeRebuild"],
},
});
}
export function createTestRealtimeCodec(
registry = createTestRealtimeRegistry(),
): RealtimeEventCodec {
return createRealtimeEventCodec({
registry,
schemaCodecs: TEST_SCHEMA_CODECS,
});
}
export type EventOverrides = Readonly<{
protocol?: unknown;
streamId?: unknown;
streamEpoch?: unknown;
eventType?: unknown;
eventId?: unknown;
sequence?: unknown;
recoveryMode?: unknown;
resumeCursor?: unknown;
occurredAt?: unknown;
scopeBinding?: unknown;
payload?: unknown;
}>;
export function realtimeEventValue(
overrides: EventOverrides = {},
): Readonly<Record<string, unknown>> {
return {
protocol: "REALTIME_EVENT_V1",
streamId: STREAM_ID,
streamEpoch: "stream-epoch-0001",
eventType: EVENT_TYPE,
eventId: "event-00000001",
sequence: "1",
recoveryMode: "CURSOR",
resumeCursor: "cursor-00000001",
occurredAt: "2026-07-28T01:02:03.123Z",
scopeBinding: "scope-binding-0001",
payload: { value: "changed" },
...overrides,
};
}
export function realtimeEventJson(
overrides: EventOverrides = {},
): string {
return JSON.stringify(realtimeEventValue(overrides));
}
@@ -0,0 +1,548 @@
import { describe, expect, it, vi } from "vitest";
import type { ClockPort } from "../../../src/application/ports/clock-port.ts";
import type { RealtimeResult } from "../../../src/application/ports/realtime/shared.ts";
import {
createLivePollHandoffCoordinator,
type LivePollHandoffCoordinatorDependencies,
type LivePollHandoffLimits,
type LiveProbeLease,
} from "../../../src/adapters/realtime/live-poll-handoff-coordinator.ts";
import {
realtimeFailure,
realtimeSuccess,
} from "../../../src/adapters/realtime/result.ts";
type Sleeper = Readonly<{
dueAt: number;
resolve(): void;
reject(): void;
signal?: AbortSignal;
onAbort(): void;
}>;
class ManualClock implements ClockPort {
current = 0;
readonly sleepers: Sleeper[] = [];
now(): number {
return this.current;
}
sleep(milliseconds: number, signal?: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => {
if (signal?.aborted) {
reject(new DOMException("Aborted", "AbortError"));
return;
}
let sleeper: Sleeper;
const onAbort = () => {
this.remove(sleeper);
reject(new DOMException("Aborted", "AbortError"));
};
sleeper = {
dueAt: this.current + milliseconds,
resolve: () => {
signal?.removeEventListener("abort", onAbort);
resolve();
},
reject: () => {
signal?.removeEventListener("abort", onAbort);
reject(new DOMException("Aborted", "AbortError"));
},
signal,
onAbort,
};
signal?.addEventListener("abort", onAbort, { once: true });
this.sleepers.push(sleeper);
});
}
advance(milliseconds: number): void {
this.current += milliseconds;
const ready = this.sleepers.filter(
(sleeper) => sleeper.dueAt <= this.current,
);
for (const sleeper of ready) {
this.remove(sleeper);
sleeper.resolve();
}
}
private remove(target: Sleeper): void {
const index = this.sleepers.indexOf(target);
if (index >= 0) this.sleepers.splice(index, 1);
}
}
const limits: LivePollHandoffLimits = Object.freeze({
quiescenceTimeoutMs: 100,
maxActiveQueueCount: 3,
maxActiveQueueBytes: 30,
maxProbeBufferedEvents: 3,
maxProbeBufferedBytes: 30,
maxItemBytes: 10,
});
function createHarness(
input: Readonly<{
initial?: "LIVE" | "POLL";
limits?: LivePollHandoffLimits;
apply?: LivePollHandoffCoordinatorDependencies<string>["apply"];
recover?: LivePollHandoffCoordinatorDependencies<string>["establishAuthoritativeCheckpoint"];
}> = {},
) {
const clock = new ManualClock();
const effects: string[] = [];
const recoveries: string[] = [];
const apply =
input.apply ??
vi.fn(async ({ writer, value }) => {
effects.push(`${writer}:${value}`);
return realtimeSuccess(undefined);
});
const recover =
input.recover ??
vi.fn(async ({ from, to }) => {
recoveries.push(`${from}->${to}`);
return realtimeSuccess(undefined);
});
const coordinator = createLivePollHandoffCoordinator({
initial: {
writer: input.initial ?? "LIVE",
authoritativeCheckpointEstablished: true,
},
limits: input.limits ?? limits,
apply,
establishAuthoritativeCheckpoint: recover,
clock,
});
return { apply, clock, coordinator, effects, recover, recoveries };
}
async function flush(): Promise<void> {
for (let turn = 0; turn < 12; turn += 1) {
await Promise.resolve();
}
}
describe("live/poll authoritative writer handoff", () => {
it("serializes effects through the one current writer lease", async () => {
const first = deferred<RealtimeResult<void>>();
const starts: string[] = [];
let activeEffects = 0;
let highWatermark = 0;
const harness = createHarness({
apply: vi.fn(async ({ value }) => {
starts.push(value);
activeEffects += 1;
highWatermark = Math.max(highWatermark, activeEffects);
if (value === "first") await first.promise;
activeEffects -= 1;
return realtimeSuccess(undefined);
}),
});
const writer = harness.coordinator.currentWriter();
expect(writer?.writer).toBe("LIVE");
const firstWrite = writer!.write("first", 5);
const secondWrite = writer!.write("second", 6);
await flush();
expect(starts).toEqual(["first"]);
first.resolve(realtimeSuccess(undefined));
await expect(firstWrite).resolves.toMatchObject({
ok: true,
value: { kind: "APPLIED", writer: "LIVE" },
});
await expect(secondWrite).resolves.toMatchObject({
ok: true,
value: { kind: "APPLIED", writer: "LIVE" },
});
expect(starts).toEqual(["first", "second"]);
expect(highWatermark).toBe(1);
});
it("fails closed when a non-cooperative active writer fills the bounded tail", async () => {
let firstSignal: AbortSignal | undefined;
let firstIsCurrent: (() => boolean) | undefined;
const starts: string[] = [];
const harness = createHarness({
limits: {
...limits,
maxActiveQueueCount: 2,
maxActiveQueueBytes: 10,
},
apply: vi.fn(async ({ value, signal, isCurrent }) => {
starts.push(value);
if (value === "first") {
firstSignal = signal;
firstIsCurrent = isCurrent;
return await new Promise<RealtimeResult<void>>(() => {});
}
return realtimeSuccess(undefined);
}),
});
const writer = harness.coordinator.currentWriter()!;
const first = writer.write("first", 5);
const second = writer.write("second", 5);
await flush();
expect(starts).toEqual(["first"]);
expect(firstIsCurrent?.()).toBe(true);
await expect(writer.write("overflow", 1)).resolves.toMatchObject({
ok: false,
error: {
kind: "QUEUE_OVERFLOW",
operation: "APPLY",
retryable: false,
},
});
expect(firstSignal?.aborted).toBe(true);
expect(firstIsCurrent?.()).toBe(false);
expect(writer.isCurrent()).toBe(false);
expect(harness.coordinator.inspect()).toMatchObject({
state: "CLOSED",
activeWriter: null,
});
expect(starts).toEqual(["first"]);
await expect(writer.write("after-close", 1)).resolves.toMatchObject({
ok: false,
error: { kind: "CLOSED" },
});
void first;
void second;
});
it("fences and aborts live, waits for quiescence, then recovers before activating poll", async () => {
const liveEffect = deferred<RealtimeResult<void>>();
const checkpoint = deferred<RealtimeResult<void>>();
const order: string[] = [];
let liveSignal: AbortSignal | undefined;
let liveIsCurrent: (() => boolean) | undefined;
const harness = createHarness({
apply: vi.fn(async ({ writer, value, signal, isCurrent }) => {
order.push(`apply:${writer}:${value}`);
if (writer === "LIVE") {
liveSignal = signal;
liveIsCurrent = isCurrent;
return await liveEffect.promise;
}
return realtimeSuccess(undefined);
}),
recover: vi.fn(async ({ from, to }) => {
order.push(`recover:${from}->${to}`);
return await checkpoint.promise;
}),
});
const live = harness.coordinator.currentWriter()!;
const pendingEffect = live.write("in-flight", 9);
await flush();
const transition = harness.coordinator.switchToPoll();
expect(liveSignal?.aborted).toBe(true);
expect(liveIsCurrent?.()).toBe(false);
expect(live.isCurrent()).toBe(false);
await expect(live.write("stale", 999)).resolves.toMatchObject({
ok: false,
error: { kind: "SCOPE_FENCED" },
});
expect(harness.recover).not.toHaveBeenCalled();
await expect(
harness.coordinator.switchToPoll(),
).resolves.toMatchObject({
ok: false,
error: { kind: "PROTOCOL_MISMATCH" },
});
liveEffect.resolve(realtimeSuccess(undefined));
await expect(pendingEffect).resolves.toMatchObject({
ok: false,
error: { kind: "SCOPE_FENCED" },
});
await flush();
expect(order).toEqual([
"apply:LIVE:in-flight",
"recover:LIVE->POLL",
]);
expect(harness.coordinator.currentWriter()).toBeNull();
checkpoint.resolve(realtimeSuccess(undefined));
const result = await transition;
expect(result).toMatchObject({
ok: true,
value: { writer: "POLL" },
});
if (!result.ok) throw new Error("expected poll writer");
await result.value.write("polled", 6);
expect(order.at(-1)).toBe("apply:POLL:polled");
expect(result.value.generation).toBeGreaterThan(live.generation);
expect(harness.coordinator.inspect().state).toBe("POLL_ACTIVE");
});
it("fails closed when the prior writer cannot quiesce before the bound", async () => {
const harness = createHarness({
apply: vi.fn(
async () =>
await new Promise<RealtimeResult<void>>(() => {}),
),
});
const live = harness.coordinator.currentWriter()!;
void live.write("hung", 4);
await flush();
const transition = harness.coordinator.switchToPoll();
await flush();
expect(harness.clock.sleepers).toHaveLength(1);
harness.clock.advance(100);
await expect(transition).resolves.toMatchObject({
ok: false,
error: { kind: "IDLE_TIMEOUT", operation: "RECOVER" },
});
expect(harness.recover).not.toHaveBeenCalled();
expect(harness.coordinator.inspect()).toMatchObject({
state: "CLOSED",
activeWriter: null,
});
});
it("keeps poll authoritative while probing, then recovers and drains live values serially", async () => {
const firstLive = deferred<RealtimeResult<void>>();
const order: string[] = [];
let activeEffects = 0;
let highWatermark = 0;
const harness = createHarness({
initial: "POLL",
apply: vi.fn(async ({ writer, value }) => {
activeEffects += 1;
highWatermark = Math.max(highWatermark, activeEffects);
order.push(`apply:${writer}:${value}`);
if (value === "live-1") await firstLive.promise;
activeEffects -= 1;
return realtimeSuccess(undefined);
}),
recover: vi.fn(async ({ from, to }) => {
order.push(`recover:${from}->${to}`);
return realtimeSuccess(undefined);
}),
});
const poll = harness.coordinator.currentWriter()!;
const opened = harness.coordinator.beginLiveProbe();
expect(opened.ok).toBe(true);
if (!opened.ok) throw new Error("expected live probe");
const live = opened.value;
await expect(live.write("live-1", 6)).resolves.toMatchObject({
ok: true,
value: { kind: "BUFFERED" },
});
await live.write("live-2", 6);
expect(live.isCurrent()).toBe(false);
expect(poll.isCurrent()).toBe(true);
await poll.write("poll-during-probe", 8);
expect(order).toEqual(["apply:POLL:poll-during-probe"]);
const activation = live.activate();
await vi.waitFor(() =>
expect(order).toEqual([
"apply:POLL:poll-during-probe",
"recover:POLL->LIVE",
"apply:LIVE:live-1",
]),
);
await live.write("live-3", 6);
expect(harness.coordinator.inspect()).toMatchObject({
state: "LIVE_PROBING",
bufferedEvents: 2,
transitioning: true,
});
firstLive.resolve(realtimeSuccess(undefined));
const activated = await activation;
expect(activated).toMatchObject({
ok: true,
value: { writer: "LIVE" },
});
expect(order).toEqual([
"apply:POLL:poll-during-probe",
"recover:POLL->LIVE",
"apply:LIVE:live-1",
"apply:LIVE:live-2",
"apply:LIVE:live-3",
]);
expect(highWatermark).toBe(1);
expect(live.isCurrent()).toBe(true);
expect(poll.isCurrent()).toBe(false);
await expect(poll.write("stale-poll", 5)).resolves.toMatchObject({
ok: false,
error: { kind: "SCOPE_FENCED" },
});
expect(harness.coordinator.inspect()).toMatchObject({
state: "LIVE_ACTIVE",
bufferedEvents: 0,
bufferedBytes: 0,
});
});
it("drops an overflowing probe without displacing poll and never reuses its generation", async () => {
const harness = createHarness({
initial: "POLL",
limits: {
...limits,
maxProbeBufferedEvents: 2,
},
});
const poll = harness.coordinator.currentWriter()!;
const first = expectProbe(harness.coordinator.beginLiveProbe());
await first.write("one", 3);
await first.write("two", 3);
await expect(first.write("overflow", 3)).resolves.toMatchObject({
ok: false,
error: { kind: "QUEUE_OVERFLOW" },
});
expect(harness.coordinator.inspect().state).toBe("POLL_ACTIVE");
expect(poll.isCurrent()).toBe(true);
await expect(first.write("stale", 999)).resolves.toMatchObject({
ok: false,
error: { kind: "SCOPE_FENCED" },
});
const second = expectProbe(harness.coordinator.beginLiveProbe());
expect(second.generation).toBeGreaterThan(first.generation);
const canceled = second.cancel();
expect(canceled).toMatchObject({
ok: true,
value: { writer: "POLL", generation: poll.generation },
});
expect(second.signal.aborted).toBe(true);
expect(harness.coordinator.inspect().state).toBe("POLL_ACTIVE");
});
it("closes if authoritative recovery fails and never activates the candidate", async () => {
const harness = createHarness({
recover: vi.fn(async () =>
realtimeFailure("CURSOR_EXPIRED", "RECOVER"),
),
});
const live = harness.coordinator.currentWriter()!;
await expect(
harness.coordinator.switchToPoll(),
).resolves.toMatchObject({
ok: false,
error: { kind: "CURSOR_EXPIRED", operation: "RECOVER" },
});
expect(live.signal.aborted).toBe(true);
expect(harness.coordinator.inspect()).toMatchObject({
state: "CLOSED",
activeWriter: null,
});
});
it("bounds a non-cooperative authoritative checkpoint", async () => {
let checkpointIsCurrent: (() => boolean) | undefined;
const harness = createHarness({
recover: vi.fn(async ({ isCurrent }) => {
checkpointIsCurrent = isCurrent;
return await new Promise<RealtimeResult<void>>(() => {});
}),
});
const transition = harness.coordinator.switchToPoll();
await flush();
expect(checkpointIsCurrent?.()).toBe(true);
expect(harness.clock.sleepers).toHaveLength(1);
harness.clock.advance(100);
await expect(transition).resolves.toMatchObject({
ok: false,
error: { kind: "IDLE_TIMEOUT", operation: "RECOVER" },
});
expect(checkpointIsCurrent?.()).toBe(false);
expect(harness.coordinator.inspect().state).toBe("CLOSED");
});
it("aborts and bounds close quiescence when an effect ignores cancellation", async () => {
let effectSignal: AbortSignal | undefined;
const harness = createHarness({
apply: vi.fn(
async ({ signal }) => {
effectSignal = signal;
return await new Promise<RealtimeResult<void>>(() => {});
},
),
});
const live = harness.coordinator.currentWriter()!;
void live.write("hung-close", 8);
await flush();
const closing = harness.coordinator.close();
expect(effectSignal?.aborted).toBe(true);
expect(harness.coordinator.inspect().state).toBe("CLOSED");
await flush();
harness.clock.advance(100);
await expect(closing).resolves.toMatchObject({
ok: false,
error: { kind: "IDLE_TIMEOUT", operation: "CLOSE" },
});
await expect(harness.coordinator.close()).resolves.toMatchObject({
ok: false,
error: { kind: "IDLE_TIMEOUT", operation: "CLOSE" },
});
});
it("rejects invalid initial authority and resource ceilings", () => {
expect(() =>
createLivePollHandoffCoordinator({
initial: {
writer: "LIVE",
authoritativeCheckpointEstablished: false,
} as never,
limits,
apply: async () => realtimeSuccess(undefined),
establishAuthoritativeCheckpoint: async () =>
realtimeSuccess(undefined),
}),
).toThrow(/initial checkpoint/u);
expect(() =>
createLivePollHandoffCoordinator({
initial: {
writer: "POLL",
authoritativeCheckpointEstablished: true,
},
limits: { ...limits, maxProbeBufferedEvents: 257 },
apply: async () => realtimeSuccess(undefined),
establishAuthoritativeCheckpoint: async () =>
realtimeSuccess(undefined),
}),
).toThrow(/limits/u);
expect(() =>
createLivePollHandoffCoordinator({
initial: {
writer: "POLL",
authoritativeCheckpointEstablished: true,
},
limits: { ...limits, maxActiveQueueCount: 257 },
apply: async () => realtimeSuccess(undefined),
establishAuthoritativeCheckpoint: async () =>
realtimeSuccess(undefined),
}),
).toThrow(/limits/u);
});
});
function expectProbe(
result: RealtimeResult<LiveProbeLease<string>>,
): LiveProbeLease<string> {
if (!result.ok) throw new Error("expected live probe");
return result.value;
}
function deferred<Value>() {
let resolve!: (value: Value) => void;
const promise = new Promise<Value>((selectedResolve) => {
resolve = selectedResolve;
});
return { promise, resolve };
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,160 @@
import { describe, expect, it } from "vitest";
import {
calculateReconnectDelay,
defineReconnectPolicy,
isReconnectAttemptResetEligible,
parseRetryAfterDelay,
REALTIME_RECONNECT_CEILINGS,
reconnectBudgetRemaining,
type ReconnectPolicy,
} from "../../../src/adapters/realtime/reconnect-policy.ts";
const policy = defineReconnectPolicy({
baseDelayMs: 1_000,
maxDelayMs: 60_000,
maxAttempts: 10,
maxElapsedMs: 300_000,
stableOpenMs: 30_000,
});
describe("realtime reconnect policy", () => {
it("uses full jitter and treats a server hint as a not-before floor", () => {
expect(
calculateReconnectDelay({
policy,
attemptIndex: 2,
remainingElapsedMs: 10_000,
random: () => 0.5,
}),
).toBe(2_000);
expect(
calculateReconnectDelay({
policy,
attemptIndex: 2,
remainingElapsedMs: 10_000,
random: () => 0.5,
serverNotBeforeMs: 3_000,
}),
).toBe(3_000);
});
it("stops instead of clamping a hint past a hard or remaining budget", () => {
expect(
calculateReconnectDelay({
policy,
attemptIndex: 0,
remainingElapsedMs: 100_000,
random: () => 0,
serverNotBeforeMs: 60_001,
}),
).toBeNull();
expect(
calculateReconnectDelay({
policy,
attemptIndex: 0,
remainingElapsedMs: 3_000,
random: () => 0,
serverNotBeforeMs: 3_000,
}),
).toBeNull();
expect(
calculateReconnectDelay({
policy,
attemptIndex: 10,
remainingElapsedMs: 100_000,
random: () => 0,
}),
).toBeNull();
});
it("parses only bounded-shape Retry-After syntax for caller validation", () => {
expect(parseRetryAfterDelay("12", 0)).toBe(12_000);
expect(
parseRetryAfterDelay(
"Thu, 01 Jan 1970 00:00:20 GMT",
5_000,
),
).toBe(15_000);
expect(parseRetryAfterDelay("1.5", 0)).toBeNull();
expect(parseRetryAfterDelay("-1", 0)).toBeNull();
});
it("resets attempts only after stable open or a valid signal", () => {
expect(
isReconnectAttemptResetEligible({
policy,
openedAtMs: 10,
nowMs: 29_000,
observedValidHeartbeatOrEvent: false,
}),
).toBe(false);
expect(
isReconnectAttemptResetEligible({
policy,
openedAtMs: 10,
nowMs: 30_010,
observedValidHeartbeatOrEvent: false,
}),
).toBe(true);
expect(
isReconnectAttemptResetEligible({
policy,
openedAtMs: 10,
nowMs: 11,
observedValidHeartbeatOrEvent: true,
}),
).toBe(true);
expect(reconnectBudgetRemaining(policy, 1_000, 2_000)).toBe(
299_000,
);
expect(reconnectBudgetRemaining(policy, 2_000, 1_000)).toBe(0);
});
it("rejects policies above the implementation ceiling", () => {
expect(() =>
defineReconnectPolicy({
...policy,
maxAttempts: 11,
}),
).toThrow(TypeError);
});
it("keeps aborted-task drain below the absolute implementation ceiling", () => {
expect(REALTIME_RECONNECT_CEILINGS.drainTimeoutMs).toBe(2_000);
expect(
REALTIME_RECONNECT_CEILINGS.drainTimeoutMs,
).toBeLessThanOrEqual(
REALTIME_RECONNECT_CEILINGS.maxDrainTimeoutMs,
);
expect(
REALTIME_RECONNECT_CEILINGS.maxDrainTimeoutMs,
).toBe(30_000);
});
it("snapshots only exact own data properties", () => {
const inherited = Object.create(policy) as ReconnectPolicy;
const accessor = Object.defineProperties(
{},
{
baseDelayMs: { enumerable: true, value: 1_000 },
maxDelayMs: {
enumerable: true,
get: () => 60_000,
},
maxAttempts: { enumerable: true, value: 10 },
maxElapsedMs: { enumerable: true, value: 300_000 },
stableOpenMs: { enumerable: true, value: 30_000 },
},
) as ReconnectPolicy;
expect(() => defineReconnectPolicy(inherited)).toThrow(TypeError);
expect(() => defineReconnectPolicy(accessor)).toThrow(TypeError);
expect(() =>
defineReconnectPolicy({
...policy,
extra: true,
} as ReconnectPolicy),
).toThrow(TypeError);
});
});
@@ -0,0 +1,222 @@
import { describe, expect, it } from "vitest";
import {
createRealtimePolicyRegistry,
defineEventTypeId,
defineStreamRegistrationId,
REALTIME_HARD_LIMITS,
type RealtimeLimits,
type RealtimeStreamRegistration,
} from "../../../src/contracts/realtime-streams.ts";
import {
EFFECT_PROFILE_ID,
ENDPOINT_ID,
EVENT_TYPE,
KILL_SWITCH_ID,
STREAM_ID,
TEST_API_OPERATIONS,
TEST_LIMITS,
TEST_MAPPERS,
TEST_SCHEMA_CODECS,
createTestRealtimeRegistry,
} from "./fixture.ts";
describe("realtime policy registry", () => {
it("deep-snapshots registrations and resolves only stream-owned event types", () => {
const baseline = createTestRealtimeRegistry();
const sourceEventIds = [EVENT_TYPE];
const sourceLimits = { ...TEST_LIMITS };
const sourceStream: RealtimeStreamRegistration = {
...baseline.listStreams()[0]!,
eventTypeIds: sourceEventIds,
limits: sourceLimits,
};
const sourceEventType = {
...baseline.listEventTypes()[0]!,
};
const registry = createRealtimePolicyRegistry({
streams: [sourceStream],
eventTypes: [sourceEventType],
bindings: bindings(),
});
sourceEventIds[0] = defineEventTypeId("MUTATED_EVENT");
sourceLimits.maxQueueEvents = 1;
sourceEventType.owner = "mutated-owner";
const installed = registry.findStream(STREAM_ID);
expect(installed).toMatchObject({
id: STREAM_ID,
eventTypeIds: [EVENT_TYPE],
limits: { maxQueueEvents: TEST_LIMITS.maxQueueEvents },
});
expect(registry.findEventType(EVENT_TYPE)?.owner).toBe(
"reference-feature",
);
expect(
registry.findStreamEventType(STREAM_ID, EVENT_TYPE)?.id,
).toBe(EVENT_TYPE);
expect(
registry.findStreamEventType(STREAM_ID, "MUTATED_EVENT"),
).toBeUndefined();
expect(Object.isFrozen(installed)).toBe(true);
expect(Object.isFrozen(installed?.eventTypeIds)).toBe(true);
expect(Object.isFrozen(installed?.limits)).toBe(true);
});
it("rejects duplicates, unknown references and extra keys", () => {
const baseline = createTestRealtimeRegistry();
const stream = baseline.listStreams()[0]!;
const eventType = baseline.listEventTypes()[0]!;
expect(() =>
createRealtimePolicyRegistry({
streams: [stream, stream],
eventTypes: [eventType],
bindings: bindings(),
}),
).toThrow("stream is duplicated");
expect(() =>
createRealtimePolicyRegistry({
streams: [
{
...stream,
eventTypeIds: [defineEventTypeId("UNKNOWN_EVENT")],
},
],
eventTypes: [eventType],
bindings: bindings(),
}),
).toThrow("stream registration is invalid");
expect(() =>
createRealtimePolicyRegistry({
streams: [
{
...stream,
unregisteredOverride: true,
} as RealtimeStreamRegistration,
],
eventTypes: [eventType],
bindings: bindings(),
}),
).toThrow("stream registration is invalid");
expect(() =>
createTestRealtimeRegistry({
eventTypeMutator: (candidate) => ({
...candidate,
mapperId: "MissingMapper",
}),
}),
).toThrow("event type registration is invalid");
});
it("closes state-bearing, ephemeral and recovery contradictions", () => {
expect(() =>
createTestRealtimeRegistry({
recovery: {
mode: "SNAPSHOT_ONLY",
snapshotOperationId: "GET_REFERENCE_REALTIME_SNAPSHOT",
checkpointCodecId: "ReferenceRealtimeCheckpoint",
barrier: "NONE",
},
delivery: "INVALIDATION_HINT",
stateBearing: true,
}),
).toThrow("recovery contract is contradictory");
expect(() =>
createTestRealtimeRegistry({
recovery: {
mode: "SESSION_REBUILD",
rebuildInputId: "referenceRealtimeRebuild",
},
delivery: "EPHEMERAL",
stateBearing: true,
}),
).toThrow("recovery contract is contradictory");
expect(() =>
createTestRealtimeRegistry({
recovery: {
mode: "SESSION_REBUILD",
rebuildInputId: "referenceRealtimeRebuild",
},
delivery: "INVALIDATION_HINT",
stateBearing: false,
}),
).toThrow("recovery contract is contradictory");
const registry = createTestRealtimeRegistry({
recovery: {
mode: "SESSION_REBUILD",
rebuildInputId: "referenceRealtimeRebuild",
},
delivery: "EPHEMERAL",
stateBearing: false,
});
expect(registry.findStream(STREAM_ID)?.recovery.mode).toBe(
"SESSION_REBUILD",
);
});
it("allows only reductions of implementation ceilings", () => {
expect(() =>
createTestRealtimeRegistry({
limits: {
...TEST_LIMITS,
maxQueueEvents: REALTIME_HARD_LIMITS.maxQueueEvents + 1,
},
}),
).toThrow("exceed implementation ceilings");
expect(() =>
createTestRealtimeRegistry({
limits: {
...TEST_LIMITS,
maxQueueBytes: TEST_LIMITS.maxEventBytes - 1,
},
}),
).toThrow("cannot hold one event");
const strictLimits: RealtimeLimits = {
...TEST_LIMITS,
maxQueueEvents: 1,
maxDedupeEntries: 1,
};
expect(
createTestRealtimeRegistry({ limits: strictLimits }).findStream(
STREAM_ID,
)?.limits,
).toMatchObject({
maxQueueEvents: 1,
maxDedupeEntries: 1,
});
});
it("issues only bounded closed registry IDs", () => {
expect(defineStreamRegistrationId("VALID_STREAM")).toBe(
"VALID_STREAM",
);
expect(() => defineStreamRegistrationId("arbitrary-channel")).toThrow(
"stream ID is invalid",
);
expect(() => defineEventTypeId("X")).toThrow(
"event type ID is invalid",
);
});
});
function bindings() {
return {
schemaCodecs: TEST_SCHEMA_CODECS,
mappers: TEST_MAPPERS,
apiOperations: TEST_API_OPERATIONS,
endpointIds: [ENDPOINT_ID],
effectProfileIds: [EFFECT_PROFILE_ID],
killSwitchIds: [KILL_SWITCH_ID],
rebuildInputIds: ["referenceRealtimeRebuild"],
} as const;
}
+168
View File
@@ -0,0 +1,168 @@
import { describe, expect, it } from "vitest";
import {
isRealtimeResult,
realtimeFailure,
realtimeSuccess,
snapshotRealtimeResult,
} from "../../../src/adapters/realtime/result.ts";
function isString(value: unknown): value is string {
return typeof value === "string";
}
describe("realtime result boundary", () => {
it("uses an immutable one-shot snapshot instead of rereading a mutable result", () => {
const source = { ok: true, value: "captured" };
const captured = snapshotRealtimeResult(source, (value): value is string => {
source.value = "changed-during-validation";
return value === "captured";
});
source.value = "changed-after-validation";
expect(captured).toEqual(
realtimeSuccess("captured"),
);
expect(Object.isFrozen(captured)).toBe(true);
expect(isRealtimeResult(source, isString)).toBe(false);
});
it("canonicalizes failure fields before the source can change", () => {
const source = {
ok: false,
error: {
kind: "FORBIDDEN",
operation: "RECOVER",
retryable: false,
},
};
const captured = snapshotRealtimeResult(
source,
(_value): _value is never => false,
);
source.error.kind = "PROVIDER_UNAVAILABLE";
source.error.operation = "CONNECT";
source.error.retryable = true;
expect(captured).toEqual(
realtimeFailure("FORBIDDEN", "RECOVER", false),
);
expect(
captured && !captured.ok
? Object.isFrozen(captured.error)
: false,
).toBe(true);
});
it("rejects accessors without invoking them", () => {
let outerReads = 0;
const outerAccessor = Object.defineProperties(
{},
{
ok: {
enumerable: true,
get() {
outerReads += 1;
return true;
},
},
value: {
enumerable: true,
value: "safe",
},
},
);
let nestedReads = 0;
const nestedAccessor = Object.defineProperties(
{},
{
kind: {
enumerable: true,
get() {
nestedReads += 1;
return "FORBIDDEN";
},
},
operation: {
enumerable: true,
value: "RECOVER",
},
retryable: {
enumerable: true,
value: false,
},
},
);
expect(
snapshotRealtimeResult(outerAccessor, isString),
).toBeNull();
expect(
snapshotRealtimeResult(
{ ok: false, error: nestedAccessor },
(_value): _value is never => false,
),
).toBeNull();
expect(outerReads).toBe(0);
expect(nestedReads).toBe(0);
});
it("consumes proxy fields only from one descriptor snapshot", () => {
const propertyReads: PropertyKey[] = [];
const descriptorReads = new Map<PropertyKey, number>();
const source = new Proxy(
{ ok: true, value: "descriptor-value" },
{
get(_target, key) {
propertyReads.push(key);
return key === "value" ? "get-trap-value" : false;
},
getOwnPropertyDescriptor(target, key) {
descriptorReads.set(
key,
(descriptorReads.get(key) ?? 0) + 1,
);
return Reflect.getOwnPropertyDescriptor(target, key);
},
},
);
expect(snapshotRealtimeResult(source, isString)).toEqual(
realtimeSuccess("descriptor-value"),
);
expect(propertyReads).toEqual([]);
expect(descriptorReads).toEqual(
new Map<PropertyKey, number>([
["ok", 1],
["value", 1],
]),
);
});
it("fails closed when a proxy is revoked", () => {
const revocable = Proxy.revocable(
{ ok: true, value: "safe" },
{},
);
revocable.revoke();
expect(
snapshotRealtimeResult(revocable.proxy, isString),
).toBeNull();
expect(isRealtimeResult(revocable.proxy, isString)).toBe(false);
});
it.each([
{ ok: true, value: "safe", extra: true },
Object.assign(
Object.create({ inherited: true }) as Record<string, unknown>,
{ ok: true, value: "safe" },
),
Object.assign(
{ ok: true, value: "safe" },
{ [Symbol("hidden")]: true },
),
])("rejects extra, inherited and symbol-key shapes", (source) => {
expect(snapshotRealtimeResult(source, isString)).toBeNull();
});
});
+188
View File
@@ -0,0 +1,188 @@
import { describe, expect, it } from "vitest";
import {
createIncrementalSseParser,
type SseParserItem,
} from "../../../src/adapters/realtime/sse/sse-parser.ts";
const encoder = new TextEncoder();
function pushText(
parser: ReturnType<typeof createIncrementalSseParser>,
text: string,
): readonly SseParserItem[] {
const result = parser.push(encoder.encode(text));
if (!result.ok) throw new Error(result.error.kind);
return result.value;
}
describe("incremental SSE parser", () => {
it("handles BOM, chunk boundaries, CR/LF/CRLF, comments and multi-line data", () => {
const parser = createIncrementalSseParser();
const chunks = [
"\uFEFF: ready\r",
"\nretry: 2500\revent: resource.updated\n",
"id: cursor-1\r\ndata: first\rdata:second\n\n",
];
const items = chunks.flatMap((chunk) => pushText(parser, chunk));
expect(items).toEqual([
{ kind: "COMMENT" },
{ kind: "RETRY", retryMs: 2_500 },
{
kind: "EVENT",
eventType: "resource.updated",
data: "first\nsecond",
id: "cursor-1",
hasExplicitId: true,
},
]);
expect(parser.finish()).toEqual({
ok: true,
value: {
items: [],
incompleteEventDiscarded: false,
},
});
});
it("preserves standard inherited ID state while marking direct IDs", () => {
const parser = createIncrementalSseParser();
const items = pushText(
parser,
"id: cursor-a\ndata: one\n\ndata: two\n\ndata:\n\n",
);
expect(items).toEqual([
{
kind: "EVENT",
eventType: "message",
data: "one",
id: "cursor-a",
hasExplicitId: true,
},
{
kind: "EVENT",
eventType: "message",
data: "two",
id: "cursor-a",
hasExplicitId: false,
},
{
kind: "EVENT",
eventType: "message",
data: "",
id: "cursor-a",
hasExplicitId: false,
},
]);
});
it("ignores invalid ID and retry fields without inventing a cursor", () => {
const parser = createIncrementalSseParser();
expect(
pushText(
parser,
"id: invalid\u0000cursor\nretry: 99999\ndata: value\n\n",
),
).toEqual([
{
kind: "EVENT",
eventType: "message",
data: "value",
id: null,
hasExplicitId: false,
},
]);
});
it("discards an event that was not terminated by a blank line", () => {
const parser = createIncrementalSseParser();
expect(pushText(parser, "data: incomplete\n")).toEqual([]);
expect(parser.finish()).toEqual({
ok: true,
value: {
items: [],
incompleteEventDiscarded: true,
},
});
expect(parser.push(encoder.encode("data: late\n\n"))).toEqual({
ok: false,
error: {
kind: "CLOSED",
operation: "DECODE",
retryable: false,
},
});
});
it("fails closed on malformed UTF-8 and parser ceilings", () => {
const malformed = createIncrementalSseParser();
expect(
malformed.push(new Uint8Array([0xc3, 0x28])),
).toEqual({
ok: false,
error: {
kind: "MALFORMED_EVENT",
operation: "DECODE",
retryable: false,
},
});
const longLine = createIncrementalSseParser({
maxLineBytes: 4,
maxEventBytes: 8,
maxIncompleteBufferBytes: 8,
maxRetryMs: 100,
});
expect(longLine.push(encoder.encode("data:"))).toEqual({
ok: false,
error: {
kind: "EVENT_TOO_LARGE",
operation: "DECODE",
retryable: false,
},
});
const largeEvent = createIncrementalSseParser({
maxLineBytes: 16,
maxEventBytes: 8,
maxIncompleteBufferBytes: 16,
maxRetryMs: 100,
});
expect(largeEvent.push(encoder.encode("data:abc\n"))).toEqual({
ok: false,
error: {
kind: "EVENT_TOO_LARGE",
operation: "DECODE",
retryable: false,
},
});
const excessiveBatch = createIncrementalSseParser({
maxItemsPerChunk: 2,
});
expect(excessiveBatch.push(encoder.encode(":\n:\n:\n"))).toEqual({
ok: false,
error: {
kind: "QUEUE_OVERFLOW",
operation: "DECODE",
retryable: false,
},
});
const oversizedChunk = createIncrementalSseParser({
maxChunkBytes: 65_536,
});
expect(
oversizedChunk.push(new Uint8Array(65_537)),
).toEqual({
ok: false,
error: {
kind: "EVENT_TOO_LARGE",
operation: "DECODE",
retryable: false,
},
});
});
});
@@ -0,0 +1,975 @@
import { describe, expect, it, vi } from "vitest";
import type {
ExternalRealtimeEventContext,
RealtimeEventAuthority,
RealtimeObservation,
RealtimeRecoveryCommit,
RealtimeRecoveryRequest,
} from "../../../src/application/ports/realtime/event-authority.ts";
import type { RealtimeResult } from "../../../src/application/ports/realtime/shared.ts";
import {
createRealtimeStreamCoordinator,
} from "../../../src/adapters/realtime/stream-coordinator.ts";
import {
realtimeFailure,
realtimeSuccess,
} from "../../../src/adapters/realtime/result.ts";
import type { ValidatedRealtimeEventDto } from "../../../src/adapters/realtime/event-codec.ts";
import type { RealtimePolicyRegistry } from "../../../src/contracts/realtime-streams.ts";
import {
STREAM_ID,
TEST_LIMITS,
TEST_MAPPER,
TEST_MAPPERS,
createTestRealtimeCodec,
createTestRealtimeRegistry,
realtimeEventJson,
type EventOverrides,
} from "./fixture.ts";
describe("transport-independent realtime stream coordinator", () => {
it("applies one stream sequentially and commits each cursor after its effect", async () => {
const first = deferred<RealtimeResult<void>>();
const applied: string[] = [];
const harness = createHarness({
async apply(_profile, value) {
const selected = (value as Readonly<{ value: string }>).value;
applied.push(selected);
return selected === "first"
? first.promise
: realtimeSuccess(undefined);
},
});
await harness.initialize();
const firstResult = harness.coordinator.accept(
harness.event({
eventId: "event-00000001",
sequence: "1",
resumeCursor: "cursor-00000001",
payload: { value: "first" },
}),
);
const secondResult = harness.coordinator.accept(
harness.event({
eventId: "event-00000002",
sequence: "2",
resumeCursor: "cursor-00000002",
payload: { value: "second" },
}),
);
await vi.waitFor(() => expect(applied).toEqual(["first"]));
expect(harness.coordinator.getResumeState(STREAM_ID)).toMatchObject({
lastAppliedSequence: "0",
resumeCursor: "cursor-snapshot-0",
});
first.resolve(realtimeSuccess(undefined));
await expect(firstResult).resolves.toMatchObject({
ok: true,
value: { outcome: "APPLIED" },
});
await expect(secondResult).resolves.toMatchObject({
ok: true,
value: { outcome: "APPLIED" },
});
expect(applied).toEqual(["first", "second"]);
expect(harness.coordinator.getResumeState(STREAM_ID)).toEqual({
recoveryMode: "CURSOR",
streamEpoch: "stream-epoch-0001",
lastAppliedSequence: "2",
resumeCursor: "cursor-00000002",
});
});
it("drops exact duplicates and stale events without replaying effects", async () => {
const harness = createHarness();
await harness.initialize();
const event = harness.event();
await harness.coordinator.accept(event);
await expect(harness.coordinator.accept(event)).resolves.toEqual({
ok: true,
value: {
outcome: "DROPPED",
reason: "DUPLICATE_EVENT",
},
});
await expect(
harness.coordinator.accept(
harness.event({
eventId: "event-stale-0001",
sequence: "0",
resumeCursor: "cursor-stale-0001",
}),
),
).resolves.toEqual({
ok: true,
value: {
outcome: "DROPPED",
reason: "STALE_EVENT",
},
});
expect(harness.effects).toHaveBeenCalledTimes(1);
expect(harness.coordinator.inspect(STREAM_ID).dedupeEntries).toBe(1);
});
it("recovers without applying a conflicting ID, sequence gap or epoch", async () => {
for (const [overrides, expectedReason] of [
[
{
eventId: "event-00000001",
sequence: "1",
payload: { value: "conflict" },
},
"EVENT_CONFLICT",
],
[
{
eventId: "event-gap-000001",
sequence: "3",
resumeCursor: "cursor-gap-000001",
},
"SEQUENCE_GAP",
],
[
{
eventId: "event-epoch-0001",
sequence: "2",
streamEpoch: "stream-epoch-0002",
resumeCursor: "cursor-epoch-0001",
},
"STREAM_EPOCH_CHANGED",
],
] as const) {
const harness = createHarness();
await harness.initialize();
await harness.coordinator.accept(harness.event());
await expect(
harness.coordinator.accept(harness.event(overrides)),
).resolves.toMatchObject({
ok: true,
value: {
outcome: "RECOVERED",
reason: expectedReason,
},
});
expect(harness.recoveryReasons).toEqual([
"INITIALIZE",
expectedReason,
]);
expect(harness.effects).toHaveBeenCalledTimes(1);
}
});
it("does not apply or advance when mapping fails", async () => {
const harness = createHarness({
mappers: {
ReferenceRealtimeMapper: {
...TEST_MAPPER,
map: () => ({
ok: false as const,
code: "MAPPING_INVARIANT_REJECTED" as const,
}),
},
},
});
await harness.initialize();
await expect(
harness.coordinator.accept(harness.event()),
).resolves.toMatchObject({
ok: true,
value: {
outcome: "RECOVERED",
reason: "MAPPING_CONTRACT_VIOLATION",
},
});
expect(harness.effects).not.toHaveBeenCalled();
expect(harness.recoveryReasons).toEqual([
"INITIALIZE",
"MAPPING_CONTRACT_VIOLATION",
]);
});
it("leaves the prior checkpoint when an effect and its recovery fail", async () => {
let recoveryCount = 0;
const harness = createHarness({
apply: async () => realtimeFailure("APPLY_FAILED", "APPLY"),
recover: async () => {
recoveryCount += 1;
return recoveryCount === 1
? realtimeSuccess(snapshotCommit("0"))
: realtimeFailure("PROVIDER_UNAVAILABLE", "RECOVER");
},
});
await harness.initialize();
await expect(
harness.coordinator.accept(harness.event()),
).resolves.toMatchObject({
ok: false,
error: {
kind: "PROVIDER_UNAVAILABLE",
operation: "RECOVER",
},
});
expect(harness.coordinator.getResumeState(STREAM_ID)).toMatchObject({
lastAppliedSequence: "0",
resumeCursor: "cursor-snapshot-0",
});
expect(harness.coordinator.inspect(STREAM_ID).freshness).toBe(
"UNKNOWN",
);
await harness.coordinator.accept(
harness.event({
eventId: "event-00000002",
sequence: "2",
resumeCursor: "cursor-00000002",
}),
);
expect(harness.effects).toHaveBeenCalledTimes(1);
});
it("rejects malformed authority results without advancing a checkpoint", async () => {
let recoveryCount = 0;
const harness = createHarness({
apply: async () =>
({
ok: true,
value: "not-void",
}) as unknown as RealtimeResult<void>,
recover: async () => {
recoveryCount += 1;
return recoveryCount === 1
? realtimeSuccess(snapshotCommit("0"))
: ({
ok: false,
error: {
kind: "FORBIDDEN",
operation: "APPLY",
retryable: false,
},
} as RealtimeResult<RealtimeRecoveryCommit>);
},
});
await harness.initialize();
await expect(
harness.coordinator.accept(harness.event()),
).resolves.toEqual(
realtimeFailure("PROTOCOL_MISMATCH", "RECOVER"),
);
expect(harness.coordinator.getResumeState(STREAM_ID)).toMatchObject({
lastAppliedSequence: "0",
});
expect(harness.coordinator.inspect(STREAM_ID).freshness).toBe(
"UNKNOWN",
);
});
it("rejects recovery accessors without invoking them", async () => {
let checkpointReads = 0;
const accessorCommit = Object.defineProperties(
{},
{
checkpoint: {
enumerable: true,
get() {
checkpointReads += 1;
return snapshotCommit("0");
},
},
kind: {
enumerable: true,
value: "SNAPSHOT_RESET",
},
},
) as RealtimeRecoveryCommit;
const harness = createHarness({
recover: async () => realtimeSuccess(accessorCommit),
});
await expect(
harness.coordinator.recover(STREAM_ID, "INITIALIZE"),
).resolves.toEqual(
realtimeFailure("PROTOCOL_MISMATCH", "RECOVER"),
);
expect(checkpointReads).toBe(0);
expect(harness.coordinator.getResumeState(STREAM_ID)).toBeNull();
});
it("commits only the one-shot descriptor snapshot of a recovery proxy", async () => {
const descriptorCommit = snapshotCommit("0");
const propertyReads: PropertyKey[] = [];
const proxyCommit = new Proxy(descriptorCommit, {
get(_target, key) {
propertyReads.push(key);
if (key === "kind") return "SESSION_REBUILD";
if (key === "checkpoint") return snapshotCommit("99");
return undefined;
},
});
const harness = createHarness({
recover: async () => realtimeSuccess(proxyCommit),
});
const recovered = await harness.coordinator.recover(
STREAM_ID,
"INITIALIZE",
);
expect(recovered).toMatchObject({
ok: true,
value: {
lastAppliedSequence: "0",
resumeCursor: "cursor-snapshot-0",
},
});
expect(propertyReads).toEqual([]);
});
it.each([
Object.freeze({
...snapshotCommit("0"),
unexpected: true,
}),
Object.assign(
Object.create({ inherited: true }) as Record<string, unknown>,
snapshotCommit("0"),
),
])(
"rejects extra and inherited recovery commit shapes",
async (commit) => {
const harness = createHarness({
recover: async () =>
realtimeSuccess(commit as RealtimeRecoveryCommit),
});
await expect(
harness.coordinator.recover(STREAM_ID, "INITIALIZE"),
).resolves.toEqual(
realtimeFailure("PROTOCOL_MISMATCH", "RECOVER"),
);
expect(harness.coordinator.getResumeState(STREAM_ID)).toBeNull();
},
);
it("expires effect and recovery commit leases when callbacks settle", async () => {
let effectContext: ExternalRealtimeEventContext | undefined;
let recoveryRequest: RealtimeRecoveryRequest | undefined;
const harness = createHarness({
async apply(_profile, _value, context) {
effectContext = context;
expect(context.isCurrent()).toBe(true);
return realtimeSuccess(undefined);
},
async recover(request) {
recoveryRequest = request;
expect(request.isCurrent()).toBe(true);
return realtimeSuccess(snapshotCommit("0"));
},
});
await harness.initialize();
expect(recoveryRequest?.isCurrent()).toBe(false);
await harness.coordinator.accept(harness.event());
expect(effectContext?.isCurrent()).toBe(false);
});
it("fences an in-flight successful effect before cursor commit", async () => {
const pending = deferred<RealtimeResult<void>>();
let effectContext: ExternalRealtimeEventContext | undefined;
const harness = createHarness({
apply: async (_profile, _value, context) => {
effectContext = context;
return pending.promise;
},
});
await harness.initialize();
const accepted = harness.coordinator.accept(harness.event());
await vi.waitFor(() =>
expect(harness.effects).toHaveBeenCalledOnce(),
);
harness.setCurrent(false);
expect(effectContext?.isCurrent()).toBe(false);
pending.resolve(realtimeSuccess(undefined));
await expect(accepted).resolves.toEqual({
ok: true,
value: {
outcome: "DROPPED",
reason: "SCOPE_FENCED",
},
});
expect(harness.coordinator.getResumeState(STREAM_ID)).toBeNull();
expect(harness.coordinator.inspect(STREAM_ID)).toMatchObject({
freshness: "UNKNOWN",
dedupeEntries: 0,
queuedEvents: 0,
awaitingTransportBarrier: false,
});
});
it("aborts and quiesces the active effect before queue-overflow recovery", async () => {
const pending = deferred<RealtimeResult<void>>();
let capturedSignal: AbortSignal | undefined;
const registry = createTestRealtimeRegistry({
limits: {
...TEST_LIMITS,
maxQueueEvents: 1,
},
});
const harness = createHarness({
registry,
async apply(_profile, _value, _context, signal) {
capturedSignal = signal;
return pending.promise;
},
});
await harness.initialize();
const first = harness.coordinator.accept(harness.event());
await vi.waitFor(() =>
expect(harness.effects).toHaveBeenCalledOnce(),
);
const overflow = harness.coordinator.accept(
harness.event({
eventId: "event-00000002",
sequence: "2",
resumeCursor: "cursor-00000002",
}),
);
expect(capturedSignal?.aborted).toBe(true);
expect(harness.recoveryReasons).toEqual(["INITIALIZE"]);
pending.resolve(realtimeSuccess(undefined));
await expect(first).resolves.toMatchObject({
ok: true,
value: { outcome: "DROPPED" },
});
await expect(overflow).resolves.toMatchObject({
ok: true,
value: {
outcome: "RECOVERED",
reason: "QUEUE_OVERFLOW",
},
});
expect(harness.recoveryReasons).toEqual([
"INITIALIZE",
"QUEUE_OVERFLOW",
]);
});
it("treats a current scope mismatch as a protocol violation, not a stale callback", async () => {
const harness = createHarness();
await harness.initialize();
await expect(
harness.coordinator.accept(
harness.event({ scopeBinding: "other-scope-binding" }),
),
).resolves.toMatchObject({
ok: true,
value: {
outcome: "RECOVERED",
reason: "SCOPE_PROTOCOL_VIOLATION",
},
});
expect(harness.effects).not.toHaveBeenCalled();
expect(harness.recoveryReasons).toEqual([
"INITIALIZE",
"SCOPE_PROTOCOL_VIOLATION",
]);
});
it("never claims CURRENT for a SNAPSHOT_ONLY stream without a barrier", async () => {
const registry = createTestRealtimeRegistry({
recovery: {
mode: "SNAPSHOT_ONLY",
snapshotOperationId: "GET_REFERENCE_REALTIME_SNAPSHOT",
checkpointCodecId: "ReferenceRealtimeCheckpoint",
barrier: "NONE",
},
delivery: "INVALIDATION_HINT",
stateBearing: false,
});
const harness = createHarness({
registry,
recover: async () =>
realtimeSuccess({
kind: "SNAPSHOT_RESET",
checkpoint: {
recoveryMode: "SNAPSHOT_ONLY",
streamEpoch: "stream-epoch-0001",
lastAppliedSequence: "0",
resumeCursor: null,
snapshotRevision: "snapshot-revision-0",
},
}),
});
await harness.initialize();
expect(harness.coordinator.inspect(STREAM_ID).freshness).toBe("STALE");
await expect(
harness.coordinator.accept(
harness.event({
recoveryMode: "SNAPSHOT_ONLY",
resumeCursor: null,
}),
),
).resolves.toMatchObject({
ok: true,
value: { outcome: "APPLIED" },
});
expect(harness.coordinator.inspect(STREAM_ID).freshness).toBe("STALE");
});
it("requires an exact transport barrier before promoting a recovered stream to CURRENT", async () => {
const harness = createHarness();
const recovered = await harness.coordinator.recover(
STREAM_ID,
"INITIALIZE",
);
expect(recovered.ok).toBe(true);
if (!recovered.ok) return;
await Promise.resolve();
expect(harness.coordinator.inspect(STREAM_ID)).toMatchObject({
freshness: "STALE",
awaitingTransportBarrier: true,
});
expect(
harness.coordinator.confirmTransportBarrier(
STREAM_ID,
Object.freeze({
...recovered.value,
}) as typeof recovered.value,
),
).toMatchObject({
ok: false,
error: { kind: "PROTOCOL_MISMATCH" },
});
const publicResumeState =
harness.coordinator.getResumeState(STREAM_ID);
expect(publicResumeState).not.toBe(recovered.value);
expect(
harness.coordinator.confirmTransportBarrier(
STREAM_ID,
publicResumeState as typeof recovered.value,
),
).toMatchObject({
ok: false,
error: { kind: "PROTOCOL_MISMATCH" },
});
expect(
harness.coordinator.confirmTransportBarrier(
STREAM_ID,
recovered.value,
),
).toEqual(realtimeSuccess(undefined));
expect(harness.coordinator.inspect(STREAM_ID)).toMatchObject({
freshness: "CURRENT",
awaitingTransportBarrier: false,
});
});
it("marks a successful invalidation hint stale until authoritative refresh", async () => {
const registry = createTestRealtimeRegistry({
recovery: {
mode: "SNAPSHOT_ONLY",
snapshotOperationId: "GET_REFERENCE_REALTIME_SNAPSHOT",
checkpointCodecId: "ReferenceRealtimeCheckpoint",
barrier: "CONNECT_BUFFER",
},
delivery: "INVALIDATION_HINT",
stateBearing: false,
});
const harness = createHarness({
registry,
recover: async () =>
realtimeSuccess({
kind: "SNAPSHOT_RESET",
checkpoint: {
recoveryMode: "SNAPSHOT_ONLY",
streamEpoch: "stream-epoch-0001",
lastAppliedSequence: "0",
resumeCursor: null,
snapshotRevision: "snapshot-revision-0",
},
}),
});
await harness.initialize();
expect(harness.coordinator.inspect(STREAM_ID).freshness).toBe(
"CURRENT",
);
await expect(
harness.coordinator.accept(
harness.event({
recoveryMode: "SNAPSHOT_ONLY",
resumeCursor: null,
}),
),
).resolves.toMatchObject({
ok: true,
value: { outcome: "APPLIED" },
});
expect(harness.coordinator.inspect(STREAM_ID).freshness).toBe("STALE");
});
it("recovers instead of evicting and continuing after dedupe capacity", async () => {
const harness = createHarness({
registry: createTestRealtimeRegistry({
limits: {
...TEST_LIMITS,
maxDedupeEntries: 1,
},
}),
});
await harness.initialize();
await harness.coordinator.accept(harness.event());
await expect(
harness.coordinator.accept(
harness.event({
eventId: "event-00000002",
sequence: "2",
resumeCursor: "cursor-00000002",
}),
),
).resolves.toMatchObject({
ok: true,
value: {
outcome: "RECOVERED",
reason: "DEDUPE_OVERFLOW",
},
});
expect(harness.effects).toHaveBeenCalledTimes(1);
expect(harness.coordinator.inspect(STREAM_ID).dedupeEntries).toBe(0);
});
it("rejects a regressing same-epoch recovery checkpoint atomically", async () => {
let checkpoint = "5";
const harness = createHarness({
recover: async () => realtimeSuccess(snapshotCommit(checkpoint)),
});
await harness.initialize();
checkpoint = "4";
await expect(
harness.coordinator.recover(STREAM_ID, "CURSOR_EXPIRED"),
).resolves.toMatchObject({
ok: false,
error: { kind: "PROTOCOL_MISMATCH" },
});
expect(harness.coordinator.getResumeState(STREAM_ID)).toMatchObject({
lastAppliedSequence: "5",
});
});
it("coalesces recovery, closes idempotently and rejects forged DTOs", async () => {
const pending = deferred<RealtimeResult<RealtimeRecoveryCommit>>();
const recover = vi.fn(async () => pending.promise);
const harness = createHarness({ recover });
const first = harness.coordinator.recover(STREAM_ID, "INITIALIZE");
const second = harness.coordinator.recover(STREAM_ID, "SEQUENCE_GAP");
await vi.waitFor(() => expect(recover).toHaveBeenCalledOnce());
pending.resolve(realtimeSuccess(snapshotCommit("0")));
await expect(first).resolves.toMatchObject({ ok: true });
await expect(second).resolves.toMatchObject({ ok: true });
harness.coordinator.close();
harness.coordinator.close();
expect(harness.coordinator.getResumeState(STREAM_ID)).toBeNull();
await expect(
harness.coordinator.accept(
Object.freeze({}) as ValidatedRealtimeEventDto,
),
).resolves.toMatchObject({
ok: false,
error: { kind: "MALFORMED_EVENT" },
});
await expect(
harness.coordinator.accept(harness.event()),
).resolves.toEqual({
ok: true,
value: { outcome: "DROPPED", reason: "CLOSED" },
});
});
it("returns the exact shared checkpoint to an event waiting on active recovery", async () => {
const pending = deferred<RealtimeResult<RealtimeRecoveryCommit>>();
const recover = vi.fn(async () => pending.promise);
const harness = createHarness({ recover });
const recovering = harness.coordinator.recover(
STREAM_ID,
"INITIALIZE",
);
await vi.waitFor(() => expect(recover).toHaveBeenCalledOnce());
const accepting = harness.coordinator.accept(harness.event());
pending.resolve(realtimeSuccess(snapshotCommit("0")));
const recovered = await recovering;
const accepted = await accepting;
expect(recovered.ok).toBe(true);
expect(accepted.ok).toBe(true);
if (!recovered.ok || !accepted.ok) return;
expect(accepted.value).toMatchObject({
outcome: "RECOVERED",
reason: "INITIALIZE",
});
if (accepted.value.outcome !== "RECOVERED") return;
expect(accepted.value.resumeState).toBe(recovered.value);
});
it("aborts only an event waiter without cancelling shared recovery", async () => {
const pending = deferred<RealtimeResult<RealtimeRecoveryCommit>>();
let recoverySignal: AbortSignal | undefined;
const harness = createHarness({
recover: async (request) => {
recoverySignal = request.signal;
return pending.promise;
},
});
const recovering = harness.coordinator.recover(
STREAM_ID,
"INITIALIZE",
);
await vi.waitFor(() => expect(recoverySignal).toBeDefined());
const controller = new AbortController();
const accepting = harness.coordinator.accept(
harness.event(),
controller.signal,
);
controller.abort();
await expect(accepting).resolves.toEqual(
realtimeFailure("ABORTED", "RECEIVE"),
);
expect(recoverySignal?.aborted).toBe(false);
pending.resolve(realtimeSuccess(snapshotCommit("0")));
await expect(recovering).resolves.toMatchObject({ ok: true });
});
it("aborts the active recovery authority when closed", async () => {
let recoverySignal: AbortSignal | undefined;
const harness = createHarness({
recover: async (request) => {
recoverySignal = request.signal;
return new Promise((resolve) => {
request.signal.addEventListener(
"abort",
() => resolve(realtimeFailure("ABORTED", "RECOVER")),
{ once: true },
);
});
},
});
const recovering = harness.coordinator.recover(
STREAM_ID,
"INITIALIZE",
);
await vi.waitFor(() => expect(recoverySignal).toBeDefined());
harness.coordinator.close();
expect(recoverySignal?.aborted).toBe(true);
await expect(recovering).resolves.toMatchObject({
ok: false,
error: { kind: "CLOSED", operation: "RECOVER" },
});
});
it("does not commit a non-cooperative recovery after caller cancellation", async () => {
const pending = deferred<RealtimeResult<RealtimeRecoveryCommit>>();
let recoveryRequest: RealtimeRecoveryRequest | undefined;
const harness = createHarness({
recover: async (request) => {
recoveryRequest = request;
return pending.promise;
},
});
const controller = new AbortController();
const recovering = harness.coordinator.recover(
STREAM_ID,
"INITIALIZE",
controller.signal,
);
await vi.waitFor(() => expect(recoveryRequest).toBeDefined());
controller.abort();
expect(recoveryRequest?.isCurrent()).toBe(false);
pending.resolve(realtimeSuccess(snapshotCommit("0")));
await expect(recovering).resolves.toEqual(
realtimeFailure("ABORTED", "RECOVER"),
);
expect(recoveryRequest?.signal.aborted).toBe(true);
expect(recoveryRequest?.isCurrent()).toBe(false);
expect(harness.coordinator.getResumeState(STREAM_ID)).toBeNull();
expect(harness.coordinator.inspect(STREAM_ID).freshness).toBe(
"UNKNOWN",
);
});
it("emits only closed redacted observations and ignores sink failure", async () => {
const observations: RealtimeObservation[] = [];
const harness = createHarness({
observe(observation) {
observations.push(observation);
throw new Error("diagnostic sink unavailable");
},
});
await harness.initialize();
await harness.coordinator.accept(
harness.event({
eventId: "sensitive-event-id",
resumeCursor: "sensitive-cursor",
payload: { value: "sensitive-payload" },
}),
);
const serialized = JSON.stringify(observations);
expect(serialized).not.toContain("sensitive-event-id");
expect(serialized).not.toContain("sensitive-cursor");
expect(serialized).not.toContain("sensitive-payload");
expect(serialized).not.toContain("scope-binding-0001");
expect(observations).toEqual(
expect.arrayContaining([
expect.objectContaining({
operation: "RECOVER",
outcome: "RECOVERED",
reason: "INITIALIZE",
}),
expect.objectContaining({
operation: "APPLY",
outcome: "APPLIED",
}),
]),
);
});
});
type HarnessOptions = Readonly<{
registry?: RealtimePolicyRegistry;
mappers?: typeof TEST_MAPPERS | Readonly<Record<string, typeof TEST_MAPPER>>;
apply?: RealtimeEventAuthority["effects"]["apply"];
recover?: (
request: RealtimeRecoveryRequest,
) => Promise<RealtimeResult<RealtimeRecoveryCommit>>;
observe?: (observation: RealtimeObservation) => void;
}>;
function createHarness(options: HarnessOptions = {}) {
const registry = options.registry ?? createTestRealtimeRegistry();
const codec = createTestRealtimeCodec(registry);
let current = true;
const recoveryReasons: string[] = [];
let defaultRecoveryCount = 0;
const effects = vi.fn(
options.apply ??
(async () => realtimeSuccess(undefined)),
);
const recover = vi.fn(
options.recover ??
(async (request: RealtimeRecoveryRequest) => {
recoveryReasons.push(request.reason);
defaultRecoveryCount += 1;
return realtimeSuccess(
snapshotCommit(
"0",
`stream-epoch-${String(defaultRecoveryCount).padStart(4, "0")}`,
),
);
}),
);
const authority: RealtimeEventAuthority = {
effects: { apply: effects },
recovery: {
async recover(request) {
if (options.recover) recoveryReasons.push(request.reason);
return recover(request);
},
},
};
const coordinator = createRealtimeStreamCoordinator({
registry,
mappers: options.mappers ?? TEST_MAPPERS,
authority,
scope: {
generation: 7,
scopeBinding: "scope-binding-0001",
isCurrent: () => current,
},
now: () => 10_000,
observe: options.observe,
});
return {
coordinator,
effects,
recoveryReasons,
event(overrides: EventOverrides = {}) {
const decoded = codec.decode(realtimeEventJson(overrides));
if (!decoded.ok) {
throw new Error(`test event failed: ${decoded.error.kind}`);
}
return decoded.value;
},
async initialize() {
const result = await coordinator.recover(
STREAM_ID,
"INITIALIZE",
);
await Promise.resolve();
if (
result.ok &&
coordinator.inspect(STREAM_ID).awaitingTransportBarrier
) {
const confirmed = coordinator.confirmTransportBarrier(
STREAM_ID,
result.value,
);
if (!confirmed.ok) {
throw new Error("test transport barrier failed");
}
}
return result;
},
setCurrent(value: boolean) {
current = value;
},
};
}
function snapshotCommit(
sequence: string,
streamEpoch = "stream-epoch-0001",
): RealtimeRecoveryCommit {
return Object.freeze({
kind: "SNAPSHOT_RESET",
checkpoint: Object.freeze({
recoveryMode: "CURSOR",
streamEpoch,
lastAppliedSequence: sequence,
resumeCursor: `cursor-snapshot-${sequence}`,
snapshotRevision: `snapshot-revision-${sequence}`,
}),
});
}
function deferred<Value>() {
let resolve!: (value: Value) => void;
const promise = new Promise<Value>((next) => {
resolve = next;
});
return { promise, resolve };
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,266 @@
import { describe, expect, it } from "vitest";
import {
REALTIME_WEBSOCKET_PROTOCOL,
decodeWebSocketServerFrame,
encodeWebSocketClientFrame,
nextUnsignedSequence,
type WebSocketAdvertisedLimits,
type WebSocketSubscribeFrame,
} from "../../../src/adapters/realtime/websocket/websocket-protocol.ts";
const LIMITS: WebSocketAdvertisedLimits = Object.freeze({
maxFrameBytes: 65_536,
maxSubscriptions: 32,
maxInboundQueueCount: 256,
maxInboundQueueBytes: 4 * 1_024 * 1_024,
maxOutboundQueueCount: 128,
maxOutboundQueueBytes: 256 * 1_024,
maxBufferedAmountBytes: 256 * 1_024,
maxEventsPerSecond: 128,
});
function encode(value: unknown): string {
return JSON.stringify(value);
}
describe("realtime WebSocket protocol", () => {
it("decodes and freezes an exact WELCOME frame", () => {
const result = decodeWebSocketServerFrame(
encode({
type: "WELCOME",
protocol: REALTIME_WEBSOCKET_PROTOCOL,
connectionId: "connection.0001",
heartbeatMs: 15_000,
heartbeatAckTimeoutMs: 5_000,
limits: LIMITS,
}),
65_536,
);
expect(result).toMatchObject({
ok: true,
value: {
type: "WELCOME",
limits: { maxSubscriptions: 32 },
},
});
if (!result.ok) throw new Error("Expected WELCOME to decode.");
if (result.value.type !== "WELCOME") {
throw new Error("Expected the WELCOME discriminant.");
}
expect(Object.isFrozen(result.value)).toBe(true);
expect(Object.isFrozen(result.value.limits)).toBe(true);
});
it("rejects extra keys, unknown frames, wrong versions and binary data", () => {
const welcome = {
type: "WELCOME",
protocol: REALTIME_WEBSOCKET_PROTOCOL,
connectionId: "connection.0001",
heartbeatMs: 15_000,
heartbeatAckTimeoutMs: 5_000,
limits: LIMITS,
};
expect(
decodeWebSocketServerFrame(
encode({ ...welcome, credential: "must-not-cross" }),
65_536,
),
).toEqual({
ok: false,
error: { code: "MALFORMED_FRAME" },
});
expect(
decodeWebSocketServerFrame(
encode({ ...welcome, protocol: "realtime.v2" }),
65_536,
),
).toEqual({
ok: false,
error: { code: "PROTOCOL_MISMATCH" },
});
expect(
decodeWebSocketServerFrame(
encode({
type: "COMMAND",
protocol: REALTIME_WEBSOCKET_PROTOCOL,
}),
65_536,
),
).toEqual({
ok: false,
error: { code: "UNKNOWN_FRAME" },
});
expect(
decodeWebSocketServerFrame(new Uint8Array([1, 2, 3]), 65_536),
).toEqual({
ok: false,
error: { code: "BINARY_FRAME" },
});
const duplicateTopLevel = encode(welcome).replace(
'{"type":"WELCOME",',
'{"\\u0074ype":"WELCOME","type":"WELCOME",',
);
expect(
decodeWebSocketServerFrame(duplicateTopLevel, 65_536),
).toEqual({
ok: false,
error: { code: "MALFORMED_FRAME" },
});
const duplicateNested = encode({
type: "EVENT",
protocol: REALTIME_WEBSOCKET_PROTOCOL,
subscriptionId: "subscription.0001",
envelope: { streamId: "orders.v1" },
}).replace(
'"streamId":"orders.v1"',
'"streamId":"orders.v1","\\u0073treamId":"shadowed"',
);
expect(
decodeWebSocketServerFrame(duplicateNested, 65_536),
).toEqual({
ok: false,
error: { code: "MALFORMED_FRAME" },
});
});
it("enforces frame bytes and canonical uint64 sequences", () => {
expect(
decodeWebSocketServerFrame(
encode({
type: "SUBSCRIBED",
protocol: REALTIME_WEBSOCKET_PROTOCOL,
subscriptionId: "subscription.0001",
streamEpoch: "stream-epoch.0001",
acceptedCursor: "cursor.0001",
nextExpectedSequence: "01",
}),
65_536,
),
).toEqual({
ok: false,
error: { code: "MALFORMED_FRAME" },
});
expect(
decodeWebSocketServerFrame(
encode({
type: "HEARTBEAT_ACK",
protocol: REALTIME_WEBSOCKET_PROTOCOL,
nonce: "nonce.0001",
}),
8,
),
).toEqual({
ok: false,
error: { code: "FRAME_TOO_LARGE" },
});
expect(nextUnsignedSequence("0")).toBe("1");
expect(nextUnsignedSequence("18446744073709551614")).toBe(
"18446744073709551615",
);
expect(nextUnsignedSequence("18446744073709551615")).toBeNull();
expect(nextUnsignedSequence("01")).toBeNull();
});
it("decodes only an exact UNSUBSCRIBED acknowledgement", () => {
const acknowledgement = {
type: "UNSUBSCRIBED",
protocol: REALTIME_WEBSOCKET_PROTOCOL,
subscriptionId: "subscription.0001",
};
const decoded = decodeWebSocketServerFrame(
encode(acknowledgement),
65_536,
);
expect(decoded).toMatchObject({
ok: true,
value: acknowledgement,
});
if (!decoded.ok) {
throw new Error("Expected UNSUBSCRIBED to decode.");
}
expect(Object.isFrozen(decoded.value)).toBe(true);
expect(
decodeWebSocketServerFrame(
encode({ ...acknowledgement, released: true }),
65_536,
),
).toEqual({
ok: false,
error: { code: "MALFORMED_FRAME" },
});
expect(
decodeWebSocketServerFrame(
encode({ ...acknowledgement, subscriptionId: "" }),
65_536,
),
).toEqual({
ok: false,
error: { code: "MALFORMED_FRAME" },
});
});
it("rejects deeply nested or structurally excessive event envelopes without recursion", () => {
let nested: unknown = "leaf";
for (let depth = 0; depth < 40; depth += 1) {
nested = [nested];
}
const event = (envelope: unknown) =>
encode({
type: "EVENT",
protocol: REALTIME_WEBSOCKET_PROTOCOL,
subscriptionId: "subscription.0001",
envelope,
});
expect(
decodeWebSocketServerFrame(
event({ payload: nested }),
65_536,
),
).toEqual({
ok: false,
error: { code: "MALFORMED_FRAME" },
});
expect(
decodeWebSocketServerFrame(
event({
payload: Array.from({ length: 4_097 }, () => ({})),
}),
65_536,
),
).toEqual({
ok: false,
error: { code: "MALFORMED_FRAME" },
});
});
it("encodes only closed client frames without leaking arbitrary commands", () => {
const frame: WebSocketSubscribeFrame = {
type: "SUBSCRIBE",
protocol: REALTIME_WEBSOCKET_PROTOCOL,
subscriptionId: "subscription.0001",
streamId: "orders.v1",
cursor: "cursor.0001",
scopeBinding: "scope-binding.0001",
};
const result = encodeWebSocketClientFrame(frame, 65_536);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("Expected SUBSCRIBE to encode.");
expect(JSON.parse(result.value)).toEqual(frame);
expect(
encodeWebSocketClientFrame(
{ ...frame, payload: "arbitrary" } as WebSocketSubscribeFrame,
65_536,
),
).toEqual({
ok: false,
error: { code: "MALFORMED_FRAME" },
});
});
});
+1 -1
View File
@@ -6,7 +6,7 @@ import {
registrySnapshotDigest,
validateBreakingEvidence,
verifyRegistryBaselineApproval,
} from "../../scripts/lib/registry-compatibility.mjs";
} from "../../scripts/lib/registry-compatibility.ts";
function snapshot(
rows: Readonly<Record<string, Readonly<Record<string, unknown>>>>,
@@ -1,20 +1,19 @@
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
type RegistryDefinition = Readonly<{
registryId: string;
owner: string;
requiredFields: string[];
fieldTypes: Record<string, string>;
}>;
describe("registry governance manifest", () => {
it("declares ten typed, single-owner executable registries", async () => {
const governance = JSON.parse(
await readFile("config/contracts/registry-governance.json", "utf8"),
);
const registries =
/** @type {Array<{
* registryId: string,
* owner: string,
* requiredFields: string[],
* fieldTypes: Record<string, string>
* }>} */ (
governance.registries
);
const registries = governance.registries as RegistryDefinition[];
expect(governance.registries).toHaveLength(10);
expect(new Set(registries.map((entry) => entry.registryId)).size).toBe(
10,
@@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
import {
compareReleaseToRuntime,
RELEASE_TOKEN_REGISTRY,
} from "../../src/contracts/release-tokens.js";
} from "../../src/contracts/release-tokens.ts";
describe("release coherence", () => {
it("owns all eight release tokens and keeps builtAt diagnostic-only", () => {
+14 -3
View File
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { buildRequestTarget } from "../../src/adapters/http/request-builder.js";
import type { ApiOperation } from "../../src/contracts/api-operations.js";
import { buildRequestTarget } from "../../src/adapters/http/request-builder.ts";
import type { ApiOperation } from "../../src/contracts/api-operations.ts";
const operation: ApiOperation = {
method: "GET",
@@ -34,7 +34,7 @@ describe("deterministic HTTP request target", () => {
expect(result.success).toBe(true);
if (!result.success) return;
expect(result.url.href).toBe(
"https://api.test/api/resources/folder%2Fitem?cursor=next+page&limit=20&tags=beta&tags=alpha",
"https://api.test/base/api/resources/folder%2Fitem?cursor=next+page&limit=20&tags=beta&tags=alpha",
);
});
@@ -50,5 +50,16 @@ describe("deterministic HTTP request target", () => {
{ nested: { secret: true } },
),
).toEqual({ success: false, code: "SEARCH_PARAMETER_INVALID" });
expect(
buildRequestTarget(
"https://api.test",
operation,
{ resourceId: "one", unexpected: "two" },
{},
),
).toEqual({ success: false, code: "PATH_PARAMETER_UNEXPECTED" });
expect(
buildRequestTarget("http://api.test", operation, { resourceId: "one" }),
).toEqual({ success: false, code: "BASE_URL_INVALID" });
});
});
+66
View File
@@ -0,0 +1,66 @@
import { describe, expect, it } from "vitest";
import {
createRestProviderProfile,
resolveRestSecurityProfiles,
validateRestProfileBindings,
} from "../../src/contracts/rest-profiles.ts";
import { REFERENCE_FEATURE_CONTRACT } from "../../src/features/reference-feature/contracts/reference-feature-contract.ts";
describe("REST provider/auth/CSRF profiles", () => {
it("preserves the provider prefix and rejects unsafe endpoint forms", () => {
expect(
createRestProviderProfile(
"PRIMARY_API",
"https://api.test/base/",
["omit"],
),
).toMatchObject({
providerId: "PRIMARY_API",
baseUrl: "https://api.test/base/",
redirect: "error",
referrerPolicy: "no-referrer",
});
expect(() =>
createRestProviderProfile(
"PRIMARY_API",
"https://user:password@api.test/base/",
),
).toThrow("Invalid REST provider profile");
expect(() =>
createRestProviderProfile("PRIMARY_API", "http://api.test/base/"),
).toThrow("Invalid REST provider profile");
});
it("resolves bearer auth to omit credentials and no CSRF", () => {
const operation =
REFERENCE_FEATURE_CONTRACT.apiOperations.CREATE_REFERENCE_RESOURCE;
const resolved = resolveRestSecurityProfiles(
operation,
createRestProviderProfile("PRIMARY_API", "https://api.test", ["omit"]),
);
expect(resolved).toMatchObject({
auth: {
transport: "BEARER_HEADER",
credentials: "omit",
allowedCredentialHeaders: ["authorization"],
},
csrf: { mode: "NONE" },
});
});
it("validates every installed reference profile binding as a set", () => {
expect(
validateRestProfileBindings(
REFERENCE_FEATURE_CONTRACT.apiOperations,
{ PRIMARY_API: ["omit"] },
),
).toBe(true);
expect(() =>
validateRestProfileBindings(
REFERENCE_FEATURE_CONTRACT.apiOperations,
{},
),
).toThrow("Unregistered REST provider binding");
});
});
@@ -0,0 +1,238 @@
import { describe, expect, it } from "vitest";
import type { ResumableUploadCheckpoint } from "../../src/application/ports/browser-transfer/resumable-upload.ts";
import { RESUMABLE_UPLOAD_PROTOCOL } from "../../src/application/ports/browser-transfer/resumable-upload.ts";
import {
createIndexedDbResumableUploadCheckpointRuntime,
createIndexedDbResumableUploadCheckpointStore,
uploadCheckpointDatabaseName,
} from "../../src/adapters/browser-transfer/resumable-upload/indexeddb-checkpoint-store.ts";
import { MemoryIndexedDbFactory } from "../helpers/memory-indexeddb.ts";
const scope = Object.freeze({
authorityToken: "authority_token_01",
namespaceToken: "namespace_token_01",
partitionToken: "partition_token_01",
});
function checkpoint(
revision: number,
overrides: Partial<ResumableUploadCheckpoint> = {},
): ResumableUploadCheckpoint {
return {
schemaVersion: 1,
protocol: RESUMABLE_UPLOAD_PROTOCOL,
revision,
state: "ACTIVE",
uploadKey: "upload_key_01",
requestBindingSha256: "a".repeat(64),
fingerprint: {
algorithm: "SHA-256-PARTS-V1",
digestHex: "b".repeat(64),
byteLength: 4,
partSizeBytes: 4,
partCount: 1,
},
sessionId: "session_01",
sessionExpiresAtEpochMs: 5_000,
sessionMaxConcurrency: 1,
acceptedParts: [],
updatedAtEpochMs: 1_000,
...overrides,
};
}
function deletingFactory(
memory: MemoryIndexedDbFactory,
mode: "SUCCESS" | "BLOCKED",
): IDBFactory {
return {
open: memory.factory.open.bind(memory.factory),
cmp: memory.factory.cmp.bind(memory.factory),
deleteDatabase: () => {
const request = {
result: undefined,
error: null,
transaction: null,
source: null,
readyState: "pending",
onsuccess: null,
onerror: null,
onblocked: null,
onupgradeneeded: null,
addEventListener() {},
removeEventListener() {},
dispatchEvent: () => true,
} as unknown as IDBOpenDBRequest;
queueMicrotask(() => {
if (mode === "SUCCESS") {
request.onsuccess?.(new Event("success"));
} else {
request.onblocked?.({
oldVersion: 1,
newVersion: null,
} as IDBVersionChangeEvent);
}
});
return request;
},
databases: async () => [],
} as IDBFactory;
}
describe("IndexedDB resumable upload checkpoint", () => {
it("length-prefixes scope tuples so delimiter placement cannot collide", () => {
const first = uploadCheckpointDatabaseName({
authorityToken: "aaaaaaaa-bbbbbbbb",
namespaceToken: "cccccccc",
partitionToken: "dddddddd",
});
const second = uploadCheckpointDatabaseName({
authorityToken: "aaaaaaaa",
namespaceToken: "bbbbbbbb-cccccccc",
partitionToken: "dddddddd",
});
expect(first).not.toBe(second);
expect(first).toContain("17:aaaaaaaa-bbbbbbbb");
expect(second).toContain("8:aaaaaaaa");
});
it("commits CAS only at transaction completion and rejects stale revisions", async () => {
const memory = new MemoryIndexedDbFactory();
const store = createIndexedDbResumableUploadCheckpointStore({
scope,
factory: memory.factory,
});
const first = checkpoint(1);
expect(
await store.compareAndSwap({
expectedRevision: null,
checkpoint: first,
}),
).toEqual({ ok: true, value: first });
expect(await store.read(first.uploadKey)).toEqual({
ok: true,
value: first,
});
memory.failNextWriteCommit(
new DOMException("commit failed", "UnknownError"),
);
const failed = await store.compareAndSwap({
expectedRevision: 1,
checkpoint: checkpoint(2, { state: "ABORT_PENDING" }),
});
expect(failed).toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
expect(await store.read(first.uploadKey)).toEqual({
ok: true,
value: first,
});
expect(
await store.compareAndSwap({
expectedRevision: 2,
checkpoint: checkpoint(3),
}),
).toMatchObject({
ok: false,
error: { code: "CONFLICT", recovery: "RECONCILE" },
});
});
it("rejects unknown persisted fields so URLs and credentials cannot enter a checkpoint", async () => {
const memory = new MemoryIndexedDbFactory();
const store = createIndexedDbResumableUploadCheckpointStore({
scope,
factory: memory.factory,
});
const smuggled = {
...checkpoint(1),
signedUrl: "https://object.invalid/secret?signature=value",
} as unknown as ResumableUploadCheckpoint;
expect(
await store.compareAndSwap({
expectedRevision: null,
checkpoint: smuggled,
}),
).toMatchObject({
ok: false,
error: { code: "INVALID_INPUT" },
});
expect(await store.read("upload_key_01")).toEqual({
ok: true,
value: null,
});
});
it("closes the bound partition before successful lifecycle deletion", async () => {
const memory = new MemoryIndexedDbFactory();
const runtime = createIndexedDbResumableUploadCheckpointRuntime({
scope,
factory: deletingFactory(memory, "SUCCESS"),
blockedTimeoutMs: 10,
});
expect(
await runtime.store.compareAndSwap({
expectedRevision: null,
checkpoint: checkpoint(1),
}),
).toMatchObject({ ok: true });
expect(await runtime.admin.deletePartition()).toEqual({
ok: true,
value: { state: "DELETED" },
});
expect(await runtime.store.read("upload_key_01")).toMatchObject({
ok: false,
error: { code: "UNAVAILABLE", recovery: "RESUME" },
});
});
it("bounds a partition deletion blocked by another browser context", async () => {
const memory = new MemoryIndexedDbFactory();
const runtime = createIndexedDbResumableUploadCheckpointRuntime({
scope,
factory: deletingFactory(memory, "BLOCKED"),
blockedTimeoutMs: 1,
});
expect(await runtime.admin.deletePartition()).toMatchObject({
ok: false,
error: {
code: "BLOCKED",
retryable: true,
recovery: "RELOAD_OTHER_CONTEXTS",
},
});
});
it("never reports a false abort after irreversible deleteDatabase dispatch", async () => {
const memory = new MemoryIndexedDbFactory();
const runtime = createIndexedDbResumableUploadCheckpointRuntime({
scope,
factory: deletingFactory(memory, "SUCCESS"),
});
const controller = new AbortController();
const deletion = runtime.admin.deletePartition(controller.signal);
controller.abort();
expect(await deletion).toEqual({
ok: true,
value: { state: "DELETED" },
});
const preAborted = new AbortController();
preAborted.abort();
const second = createIndexedDbResumableUploadCheckpointRuntime({
scope: { ...scope, partitionToken: "partition_token_02" },
factory: deletingFactory(new MemoryIndexedDbFactory(), "SUCCESS"),
});
expect(
await second.admin.deletePartition(preAborted.signal),
).toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
});
});
@@ -0,0 +1,447 @@
import { describe, expect, it, vi } from "vitest";
import {
createResumableUploadFetchJsonTransport,
type ResumableUploadFetchTransportDependencies,
} from "../../src/adapters/browser-transfer/resumable-upload/fetch-json-transport.ts";
import type {
ResumableUploadControlOperation,
ResumableUploadJsonTransport,
} from "../../src/adapters/browser-transfer/resumable-upload/http-control-plane-adapter.ts";
import { createResumableUploadWebLock } from "../../src/adapters/browser-transfer/resumable-upload/upload-mutation-lock.ts";
const API_ORIGIN = "https://api.example";
const ENDPOINTS = Object.freeze({
CREATE_SESSION: `${API_ORIGIN}/uploads/create`,
GET_STATUS: `${API_ORIGIN}/uploads/status`,
COMPLETE: `${API_ORIGIN}/uploads/complete`,
ABORT: `${API_ORIGIN}/uploads/abort`,
});
const activeSignal = new AbortController().signal;
function responseAt(
url: string,
body: BodyInit | null,
init: ResponseInit,
): Response {
const response = new Response(body, init);
Object.defineProperty(response, "url", {
configurable: true,
value: url,
});
return response;
}
function jsonResponseAt(
url: string,
value: unknown,
status = 200,
headers: Readonly<Record<string, string>> = {},
): Response {
const body = JSON.stringify(value);
return responseAt(url, body, {
status,
headers: {
"content-type": "application/json; charset=utf-8",
"content-length": String(new TextEncoder().encode(body).byteLength),
...headers,
},
});
}
function createTransport(
fetcher: typeof fetch,
overrides: Partial<ResumableUploadFetchTransportDependencies> = {},
): ResumableUploadJsonTransport {
return createResumableUploadFetchJsonTransport({
endpoints: ENDPOINTS,
allowedOrigins: [API_ORIGIN],
credentials: "same-origin",
fetcher,
timeoutMs: 100,
maxRequestBytes: 4_096,
maxResponseBytes: 4_096,
maxRetryAfterMs: 3_000,
...overrides,
});
}
describe("resumable upload fetch transport", () => {
it("uses a closed operation map and fixed production fetch policy", async () => {
let receivedUrl = "";
let receivedInit: RequestInit | undefined;
const fetcher = vi.fn(
async (
input: RequestInfo | URL,
init?: RequestInit,
): Promise<Response> => {
receivedUrl = String(input);
receivedInit = init;
return jsonResponseAt(ENDPOINTS.GET_STATUS, { state: "ok" });
},
) as unknown as typeof fetch;
const transport = createTransport(fetcher, {
requestHeaders: [{ name: "x-runtime-version", value: "v1" }],
});
const result = await transport.execute({
operation: "GET_STATUS",
body: { sessionId: "session_01" },
signal: activeSignal,
});
expect(result).toEqual({
ok: true,
value: { state: "ok" },
});
expect(receivedUrl).toBe(ENDPOINTS.GET_STATUS);
expect(receivedInit).toMatchObject({
method: "POST",
credentials: "same-origin",
redirect: "error",
referrerPolicy: "no-referrer",
cache: "no-store",
mode: "cors",
});
const headers = new Headers(receivedInit?.headers);
expect(headers.get("accept")).toBe("application/json");
expect(headers.get("content-type")).toBe(
"application/json; charset=utf-8",
);
expect(headers.get("x-runtime-version")).toBe("v1");
});
it("rejects a runtime operation outside the allowlist before fetch", async () => {
const fetcher = vi.fn();
const transport = createTransport(
fetcher as unknown as typeof fetch,
);
const execute = transport.execute as (
input: Readonly<{
operation: string;
body: Readonly<Record<string, unknown>>;
signal: AbortSignal;
}>,
) => ReturnType<ResumableUploadJsonTransport["execute"]>;
const result = await execute({
operation: "DELETE_EVERYTHING",
body: {},
signal: activeSignal,
});
expect(result).toMatchObject({
ok: false,
error: { code: "INVALID_INPUT" },
});
expect(fetcher).not.toHaveBeenCalled();
});
it("keeps timeout active while a response body is stalled and cancels it", async () => {
const cancel = vi.fn();
const stalled = new ReadableStream<Uint8Array>({
cancel,
});
const fetcher = vi.fn(async () =>
responseAt(ENDPOINTS.GET_STATUS, stalled, {
status: 200,
headers: { "content-type": "application/json" },
}),
) as unknown as typeof fetch;
const transport = createTransport(fetcher, { timeoutMs: 5 });
const result = await transport.execute({
operation: "GET_STATUS",
body: {},
signal: activeSignal,
});
await Promise.resolve();
expect(result).toMatchObject({
ok: false,
error: {
code: "UNAVAILABLE",
retryable: true,
recovery: "RESUME",
},
});
expect(cancel).toHaveBeenCalledTimes(1);
});
it("propagates parent abort during a stalled body and cancels promptly", async () => {
const cancel = vi.fn();
const stalled = new ReadableStream<Uint8Array>({
cancel,
});
const fetcher = vi.fn(async () =>
responseAt(ENDPOINTS.GET_STATUS, stalled, {
status: 200,
headers: { "content-type": "application/json" },
}),
) as unknown as typeof fetch;
const transport = createTransport(fetcher, { timeoutMs: 1_000 });
const controller = new AbortController();
const pending = transport.execute({
operation: "GET_STATUS",
body: {},
signal: controller.signal,
});
await Promise.resolve();
controller.abort();
const result = await pending;
await Promise.resolve();
expect(result).toMatchObject({
ok: false,
error: { code: "ABORTED", retryable: false },
});
expect(cancel).toHaveBeenCalledTimes(1);
});
it("requires Content-Length to match the bytes actually consumed", async () => {
const body = JSON.stringify({ state: "ok" });
const fetcher = vi.fn(async () =>
responseAt(ENDPOINTS.GET_STATUS, body, {
status: 200,
headers: {
"content-type": "application/json",
"content-length": String(body.length + 1),
},
}),
) as unknown as typeof fetch;
const result = await createTransport(fetcher).execute({
operation: "GET_STATUS",
body: {},
signal: activeSignal,
});
expect(result).toMatchObject({
ok: false,
error: {
code: "INTEGRITY_FAILED",
recovery: "RECONCILE",
},
});
});
it("bounds Retry-After and cancels an unconsumed error body", async () => {
const cancel = vi.fn();
const body = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new Uint8Array([1]));
},
cancel,
});
const fetcher = vi.fn(async () =>
responseAt(ENDPOINTS.GET_STATUS, body, {
status: 429,
headers: { "retry-after": "2" },
}),
) as unknown as typeof fetch;
const result = await createTransport(fetcher).execute({
operation: "GET_STATUS",
body: {},
signal: activeSignal,
});
await Promise.resolve();
expect(result).toMatchObject({
ok: false,
error: {
code: "UNAVAILABLE",
retryable: true,
retryAfterMs: 2_000,
},
});
expect(cancel).toHaveBeenCalledTimes(1);
});
it.each([500, 599])(
"classifies HTTP %s as retryable; runtime attempt limits remain authoritative",
async (status) => {
const fetcher = vi.fn(async () =>
jsonResponseAt(
ENDPOINTS.GET_STATUS,
{ error: "closed" },
status,
),
) as unknown as typeof fetch;
const result = await createTransport(fetcher).execute({
operation: "GET_STATUS",
body: {},
signal: activeSignal,
});
expect(result).toMatchObject({
ok: false,
error: {
code: "UNAVAILABLE",
retryable: true,
recovery: "RESUME",
},
});
},
);
it("cancels bodies rejected by URL and content-type policy", async () => {
const urlCancel = vi.fn();
const typeCancel = vi.fn();
const responses = [
responseAt(
`${API_ORIGIN}/unexpected`,
new ReadableStream<Uint8Array>({ cancel: urlCancel }),
{
status: 200,
headers: { "content-type": "application/json" },
},
),
responseAt(
ENDPOINTS.GET_STATUS,
new ReadableStream<Uint8Array>({ cancel: typeCancel }),
{
status: 200,
headers: { "content-type": "text/html" },
},
),
];
const fetcher = vi.fn(async () => responses.shift()!) as unknown as typeof fetch;
const transport = createTransport(fetcher);
const wrongUrl = await transport.execute({
operation: "GET_STATUS",
body: {},
signal: activeSignal,
});
const wrongType = await transport.execute({
operation: "GET_STATUS",
body: {},
signal: activeSignal,
});
await Promise.resolve();
expect(wrongUrl).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(wrongType).toMatchObject({
ok: false,
error: { code: "CORRUPT_DATA" },
});
expect(urlCancel).toHaveBeenCalledTimes(1);
expect(typeCancel).toHaveBeenCalledTimes(1);
});
it.each([
"authorization",
"proxy-authorization",
"set-cookie",
"connection",
"transfer-encoding",
])("rejects composition headers that can alter authority: %s", (name) => {
const fetcher = vi.fn() as unknown as typeof fetch;
expect(() =>
createTransport(fetcher, {
requestHeaders: [{ name, value: "forbidden" }],
}),
).toThrow(TypeError);
});
it("rejects unknown expected-success operation keys", () => {
const fetcher = vi.fn() as unknown as typeof fetch;
expect(() =>
createTransport(fetcher, {
expectedSuccessStatuses: {
DELETE_EVERYTHING: 204,
} as unknown as Partial<
Readonly<Record<ResumableUploadControlOperation, number>>
>,
}),
).toThrow(TypeError);
});
});
describe("resumable upload Web Lock", () => {
it("uses one exclusive opaque lock name and serializes mutations", async () => {
const requests: Array<
Readonly<{
name: string;
options: Readonly<{
mode: "exclusive";
signal?: AbortSignal;
}>;
}>
> = [];
let queue = Promise.resolve();
const manager = {
request<Value>(
name: string,
options: Readonly<{
mode: "exclusive";
signal?: AbortSignal;
}>,
callback: (lock: unknown) => Promise<Value>,
): Promise<Value> {
requests.push({ name, options });
const result = queue.then(async () => await callback({ name }));
queue = result.then(
() => undefined,
() => undefined,
);
return result;
},
} as unknown as LockManager;
const lock = createResumableUploadWebLock(
manager,
"upload-runtime-v1",
);
const events: string[] = [];
let releaseFirst!: () => void;
const gate = new Promise<void>((resolve) => {
releaseFirst = resolve;
});
const first = lock.run(
"upload_key_lock",
activeSignal,
async () => {
events.push("first:start");
await gate;
events.push("first:end");
return 1;
},
);
const second = lock.run(
"upload_key_lock",
activeSignal,
async () => {
events.push("second:start");
return 2;
},
);
await Promise.resolve();
await Promise.resolve();
expect(events).toEqual(["first:start"]);
releaseFirst();
await expect(Promise.all([first, second])).resolves.toEqual([1, 2]);
expect(events).toEqual([
"first:start",
"first:end",
"second:start",
]);
expect(requests).toEqual([
{
name: "upload-runtime-v1:upload_key_lock",
options: { mode: "exclusive", signal: activeSignal },
},
{
name: "upload-runtime-v1:upload_key_lock",
options: { mode: "exclusive", signal: activeSignal },
},
]);
});
});
@@ -0,0 +1,511 @@
import { describe, expect, it, vi } from "vitest";
import type {
PresignedUploadPartCapabilityProvider,
} from "../../src/application/ports/browser-transfer/presigned-transfer.ts";
import type {
ResumableUploadCheckpoint,
ResumableUploadCheckpointStore,
UploadFileFingerprint,
UploadPartReceipt,
UploadSession,
} from "../../src/application/ports/browser-transfer/resumable-upload.ts";
import { RESUMABLE_UPLOAD_PROTOCOL } from "../../src/application/ports/browser-transfer/resumable-upload.ts";
import type { BrowserDataResult } from "../../src/application/ports/browser-file-storage/shared.ts";
import {
browserDataFailure,
browserDataSuccess,
} from "../../src/adapters/browser-file-storage/result.ts";
import {
createPresignedCapabilityVault,
createSingleUsePresignedReplayGuard,
} from "../../src/adapters/browser-transfer/presigned/presigned-capability-vault.ts";
import { createPresignedCapabilityHttpProvider } from "../../src/adapters/browser-transfer/presigned/presigned-capability-http-provider.ts";
import { createPresignedTransferExecutor } from "../../src/adapters/browser-transfer/presigned/presigned-transfer-executor.ts";
import { createResumableUploadFetchJsonTransport } from "../../src/adapters/browser-transfer/resumable-upload/fetch-json-transport.ts";
import {
createResumableUploadHttpControlPlane,
type ResumableUploadJsonTransport,
} from "../../src/adapters/browser-transfer/resumable-upload/http-control-plane-adapter.ts";
import { createPresignedUploadPartExecutor } from "../../src/adapters/browser-transfer/resumable-upload/presigned-upload-part-executor.ts";
import { createResumableUploadRuntime } from "../../src/adapters/browser-transfer/resumable-upload/resumable-upload-runtime.ts";
import type { UploadMutationLock } from "../../src/adapters/browser-transfer/resumable-upload/upload-mutation-lock.ts";
const NOW = 1_000_000;
const API_ORIGIN = "https://api.example";
const OBJECT_ORIGIN = "https://objects.example";
const CAPABILITY_ENDPOINT = `${API_ORIGIN}/capabilities`;
const CONTROL_ENDPOINTS = Object.freeze({
CREATE_SESSION: `${API_ORIGIN}/uploads/create`,
GET_STATUS: `${API_ORIGIN}/uploads/status`,
COMPLETE: `${API_ORIGIN}/uploads/complete`,
ABORT: `${API_ORIGIN}/uploads/abort`,
});
const CHECKSUM_HEADER = "x-checksum-sha256";
const POLICY_HEADER = "x-policy-version";
const activeSignal = new AbortController().signal;
const noContentionLock: UploadMutationLock = Object.freeze({
async run<Value>(
_uploadKey: string,
_signal: AbortSignal,
task: () => Promise<Value>,
): Promise<Value> {
return await task();
},
});
class MemoryCheckpointStore implements ResumableUploadCheckpointStore {
readonly rows = new Map<string, ResumableUploadCheckpoint>();
async read(
uploadKey: string,
): Promise<BrowserDataResult<ResumableUploadCheckpoint | null>> {
return browserDataSuccess(
structuredClone(this.rows.get(uploadKey) ?? null),
);
}
async compareAndSwap(
input: Parameters<
ResumableUploadCheckpointStore["compareAndSwap"]
>[0],
): Promise<BrowserDataResult<ResumableUploadCheckpoint>> {
const current = this.rows.get(input.checkpoint.uploadKey);
if (
(input.expectedRevision === null && current) ||
(input.expectedRevision !== null &&
current?.revision !== input.expectedRevision)
) {
return browserDataFailure("CONFLICT", "UPLOAD_RECONCILE", {
recovery: "RECONCILE",
});
}
const snapshot = structuredClone(input.checkpoint);
this.rows.set(snapshot.uploadKey, snapshot);
return browserDataSuccess(snapshot);
}
async remove(
input: Parameters<
ResumableUploadCheckpointStore["remove"]
>[0],
): Promise<BrowserDataResult<void>> {
const current = this.rows.get(input.uploadKey);
if (current?.revision !== input.expectedRevision) {
return browserDataFailure("CONFLICT", "UPLOAD_RECONCILE", {
recovery: "RECONCILE",
});
}
this.rows.delete(input.uploadKey);
return browserDataSuccess(undefined);
}
close(): void {}
}
function responseAt(
url: string,
body: BodyInit | null,
init: ResponseInit,
): Response {
const response = new Response(body, init);
Object.defineProperty(response, "url", {
configurable: true,
value: url,
});
return response;
}
function jsonResponseAt(
url: string,
value: unknown,
status = 200,
): Response {
const body = JSON.stringify(value);
return responseAt(url, body, {
status,
headers: {
"content-type": "application/json; charset=utf-8",
"content-length": String(new TextEncoder().encode(body).byteLength),
},
});
}
function rangeSource(bytes: Uint8Array) {
return Object.freeze({
kind: "RANGE_READER" as const,
reader: Object.freeze({
byteLength: bytes.byteLength,
async readRange(input: Readonly<{
offset: number;
length: number;
signal: AbortSignal;
}>) {
return input.signal.aborted
? browserDataFailure("ABORTED", "FILE_READ")
: browserDataSuccess(
bytes.slice(
input.offset,
input.offset + input.length,
),
);
},
}),
});
}
describe("resumable upload HTTP control plane", () => {
it("rejects unknown response fields so URLs cannot cross the DTO boundary", async () => {
const fingerprint: UploadFileFingerprint = Object.freeze({
algorithm: "SHA-256-PARTS-V1",
digestHex: "a".repeat(64),
byteLength: 4,
partSizeBytes: 4,
partCount: 1,
});
const transport: ResumableUploadJsonTransport = {
async execute() {
return browserDataSuccess({
protocol: RESUMABLE_UPLOAD_PROTOCOL,
sessionId: "session_01",
requestBindingSha256: "b".repeat(64),
fingerprint,
partSizeBytes: 4,
partCount: 1,
maxConcurrency: 1,
expiresAtEpochMs: NOW + 10_000,
signedUrl: "https://objects.example/secret?signature=leak",
});
},
};
const partCapabilities: PresignedUploadPartCapabilityProvider = {
issueUploadPart: vi.fn(),
};
const control = createResumableUploadHttpControlPlane({
transport,
partCapabilities,
});
const result = await control.createSession({
protocol: RESUMABLE_UPLOAD_PROTOCOL,
uploadKey: "upload_key_strict",
purpose: "attachment",
mediaType: "application/octet-stream",
requestBindingSha256: "b".repeat(64),
fingerprint,
requestedPartSizeBytes: 4,
requestedMaxConcurrency: 1,
idempotencyKey: "upload-create-idempotency-01",
signal: activeSignal,
});
expect(result).toMatchObject({
ok: false,
error: {
code: "CORRUPT_DATA",
operation: "UPLOAD_SESSION",
recovery: "RECONCILE",
},
});
expect(JSON.stringify(result)).not.toContain("signature=leak");
});
it("runs create, list-parts, session-bound capability PUT and ordered complete through actual fetch adapters", async () => {
const checkpoints = new MemoryCheckpointStore();
const accepted = new Map<number, UploadPartReceipt>();
const issuedBindings: Array<Readonly<Record<string, unknown>>> = [];
const objectPutOptions: RequestInit[] = [];
const completedParts: UploadPartReceipt[][] = [];
const partByHref = new Map<
string,
Readonly<{
partNumber: number;
offset: number;
byteLength: number;
checksumSha256: string;
}>
>();
let session: UploadSession | null = null;
let completed = false;
let capabilitySequence = 0;
const fetcher = vi.fn(
async (
input: RequestInfo | URL,
init?: RequestInit,
): Promise<Response> => {
const url = String(input);
if (url === CAPABILITY_ENDPOINT) {
const request = JSON.parse(String(init?.body)) as Readonly<{
method: string;
binding: Readonly<{
kind: string;
sessionId: string;
requestBindingSha256: string;
uploadBindingSha256: string;
partNumber: number;
offset: number;
idempotencyKey: string;
}>;
mediaType: string;
byteLength: number;
expectedSha256: string;
}>;
issuedBindings.push(
Object.freeze({ ...request.binding }),
);
capabilitySequence += 1;
const path =
`/multipart/${request.binding.sessionId}` +
`/part-${request.binding.partNumber}`;
const href =
`${OBJECT_ORIGIN}${path}` +
`?signature=opaque-${capabilitySequence}`;
partByHref.set(
href,
Object.freeze({
partNumber: request.binding.partNumber,
offset: request.binding.offset,
byteLength: request.byteLength,
checksumSha256: request.expectedSha256,
}),
);
return jsonResponseAt(CAPABILITY_ENDPOINT, {
capabilityReceipt: `capability-upload-${capabilitySequence}`,
method: "PUT",
binding: request.binding,
href,
origin: OBJECT_ORIGIN,
path,
allowedQueryParameters: ["signature"],
requestHeaders: [
{
name: "content-type",
value: request.mediaType,
},
{
name: CHECKSUM_HEADER,
value: request.expectedSha256,
},
],
requiredResponseHeaders: [
{ name: POLICY_HEADER, value: "v1" },
],
digestRequestHeader: CHECKSUM_HEADER,
digestResponseHeader: null,
receiptResponseHeader: "etag",
expectedStatus: 200,
expectedResponseByteLength: 0,
mediaType: request.mediaType,
byteLength: request.byteLength,
maxBytes: request.byteLength,
expectedSha256: request.expectedSha256,
expiresAtEpochMs: NOW + 30_000,
singleUse: true,
});
}
const part = partByHref.get(url);
if (part) {
objectPutOptions.push(init ?? {});
const receiptToken = `etag-part-${part.partNumber}`;
accepted.set(
part.partNumber,
Object.freeze({
...part,
receiptToken,
}),
);
return responseAt(url, null, {
status: 200,
headers: {
"content-length": "0",
[POLICY_HEADER]: "v1",
etag: receiptToken,
},
});
}
const request = JSON.parse(String(init?.body)) as Record<
string,
unknown
>;
if (url === CONTROL_ENDPOINTS.CREATE_SESSION) {
const fingerprint =
request.fingerprint as UploadFileFingerprint;
session = Object.freeze({
protocol: RESUMABLE_UPLOAD_PROTOCOL,
sessionId: "session_integration_01",
requestBindingSha256: String(
request.requestBindingSha256,
),
fingerprint,
partSizeBytes: Number(
request.requestedPartSizeBytes,
),
partCount: fingerprint.partCount,
maxConcurrency: 2,
expiresAtEpochMs: NOW + 60_000,
});
return jsonResponseAt(url, session, 201);
}
if (url === CONTROL_ENDPOINTS.GET_STATUS && session) {
return completed
? jsonResponseAt(url, {
state: "QUARANTINED",
session,
resourceId: "resource_integration_01",
})
: jsonResponseAt(url, {
state: "ACTIVE",
session,
acceptedParts: [...accepted.values()].sort(
(left, right) =>
left.partNumber - right.partNumber,
),
});
}
if (url === CONTROL_ENDPOINTS.COMPLETE && session) {
completedParts.push(
request.orderedParts as UploadPartReceipt[],
);
completed = true;
return jsonResponseAt(url, {
state: "QUARANTINED",
protocol: RESUMABLE_UPLOAD_PROTOCOL,
sessionId: session.sessionId,
requestBindingSha256:
session.requestBindingSha256,
fingerprint: session.fingerprint,
resourceId: "resource_integration_01",
});
}
if (url === CONTROL_ENDPOINTS.ABORT) {
return jsonResponseAt(url, { state: "ABORTED" });
}
throw new TypeError("Unexpected test endpoint.");
},
) as unknown as typeof fetch;
const vault = createPresignedCapabilityVault({
maxActiveCapabilities: 16,
now: () => NOW,
});
const capabilityProvider =
createPresignedCapabilityHttpProvider({
endpoint: CAPABILITY_ENDPOINT,
vault,
allowedDataOrigins: [OBJECT_ORIGIN],
allowedDataPathPrefixes: ["/multipart/"],
allowedQueryParameters: ["signature"],
allowedRequestHeaders: [
"content-type",
CHECKSUM_HEADER,
],
allowedResponseHeaders: [POLICY_HEADER, "etag"],
hardMaxTransferBytes: 64,
hardMaxUploadResponseBytes: 16,
maxCapabilityTtlMs: 60_000,
minimumRemainingLifetimeMs: 100,
timeoutMs: 5_000,
fetcher,
now: () => NOW,
});
const presignedExecutor = createPresignedTransferExecutor({
vault,
replayGuard: createSingleUsePresignedReplayGuard(),
hardMaxTransferBytes: 64,
hardMaxChunkBytes: 4,
hardMaxUploadResponseBytes: 16,
minimumRemainingLifetimeMs: 100,
timeoutMs: 5_000,
fetcher,
now: () => NOW,
});
const transport = createResumableUploadFetchJsonTransport({
endpoints: CONTROL_ENDPOINTS,
allowedOrigins: [API_ORIGIN],
credentials: "same-origin",
fetcher,
timeoutMs: 5_000,
maxRequestBytes: 64 * 1024,
maxResponseBytes: 64 * 1024,
});
const controlPlane = createResumableUploadHttpControlPlane({
transport,
partCapabilities: capabilityProvider,
});
const runtime = createResumableUploadRuntime({
controlPlane,
partExecutor: createPresignedUploadPartExecutor(
presignedExecutor.uploadParts,
() => NOW,
),
checkpoints,
mutationLock: noContentionLock,
crypto,
policy: {
partSizeBytes: 4,
maxFileBytes: 64,
maxPartCount: 16,
maxConcurrency: 2,
maxInFlightBytes: 32,
partBufferCopyFactor: 4,
maxSourceChunkBytes: 4,
maxRetries: 1,
retryBaseDelayMs: 1,
retryMaxDelayMs: 10,
maxRetryAfterMs: 100,
capabilityRefreshSkewMs: 100,
maxSessionLifetimeMs: 120_000,
providerAttemptTimeoutMs: 5_000,
},
now: () => NOW,
random: () => 0,
sleep: async () => {},
});
const result = await runtime.upload({
uploadKey: "upload_key_integration",
purpose: "attachment",
mediaType: "application/octet-stream",
source: rangeSource(
new Uint8Array([1, 2, 3, 4, 5, 6]),
),
signal: activeSignal,
});
expect(result).toMatchObject({
ok: true,
value: {
state: "QUARANTINED",
resourceId: "resource_integration_01",
byteLength: 6,
replayed: false,
},
});
expect(issuedBindings).toHaveLength(2);
expect(
issuedBindings.every(
(binding) =>
binding.protocol === RESUMABLE_UPLOAD_PROTOCOL &&
binding.sessionId === "session_integration_01" &&
binding.requestBindingSha256 ===
session?.requestBindingSha256,
),
).toBe(true);
expect(objectPutOptions).toHaveLength(2);
expect(
objectPutOptions.every(
(options) =>
options.method === "PUT" &&
options.credentials === "omit" &&
options.redirect === "error",
),
).toBe(true);
expect(
completedParts[0]?.map((part) => part.partNumber),
).toEqual([1, 2]);
expect(checkpoints.rows.size).toBe(0);
});
});
File diff suppressed because it is too large Load Diff
@@ -5,7 +5,7 @@ import {
parseRetryAfter,
retryDelay,
shouldRetry,
} from "../../src/adapters/http/retry-policy.js";
} from "../../src/adapters/http/retry-policy.ts";
describe("HTTP retry policy", () => {
it("uses capped exponential full jitter", () => {
+3 -3
View File
@@ -3,9 +3,9 @@ import { describe, expect, it } from "vitest";
import {
ROUTE_REGISTRY,
ROUTE_RUNTIME_CONTRACT,
} from "../../src/features/installed-feature-contracts.js";
import { ROUTE_RUNTIME } from "../../src/features/installed-feature-runtimes.js";
import { parseRouteInput } from "../../src/presentation/routes/route-codecs.js";
} from "../../src/features/installed-feature-contracts.ts";
import { ROUTE_RUNTIME } from "../../src/features/installed-feature-runtimes.tsx";
import { parseRouteInput } from "../../src/presentation/routes/route-codecs.ts";
describe("typed installed route catalog", () => {
it("keeps contract and executable runtime contributions complete", () => {
@@ -2,10 +2,24 @@ import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
type RunbookSpecification = Readonly<{
triggerKinds: string[];
containment: string;
window: string;
escalation: string[];
recoveryEvidence: string[];
negativeFixture: string;
gateId: string;
}>;
type RunbookDocument = Readonly<{
runbooks: Record<string, RunbookSpecification>;
}>;
describe("operational runbook contract", () => {
const document = JSON.parse(
readFileSync("config/runbooks/runbooks.json", "utf8"),
);
) as RunbookDocument;
it("defines all five runbooks with four machine-checkable contract axes", () => {
expect(Object.keys(document.runbooks)).toEqual([
@@ -3,10 +3,13 @@ import { describe, expect, it, vi } from "vitest";
import {
createRuntimeAdapters,
createRuntimeHttpClient,
} from "../../src/bootstrap/runtime-adapters.js";
import { TEST_HTTP_CONTRACT } from "../helpers/http-contract-fixture.js";
} from "../../src/bootstrap/runtime-adapters.ts";
import { TEST_HTTP_CONTRACT } from "../helpers/http-contract-fixture.ts";
const runtime = {
type Runtime = Parameters<typeof createRuntimeAdapters>[0]["runtime"];
type Release = Parameters<typeof createRuntimeAdapters>[0]["release"];
const runtime: Runtime = {
config: {
APP_ENV: "local",
API_BASE_URL: "http://localhost:8080",
@@ -15,9 +18,18 @@ const runtime = {
REQUEST_TIMEOUT_MS: 4321,
MAX_RETRY_ATTEMPTS: 0,
RELEASE_MANIFEST_URL: "/release-manifest.json",
CONFIG_SCHEMA_VERSION: "1",
API_CONTRACT_VERSION: "1",
},
build: {
buildId: "build-a",
commitSha: "abc123",
routerBasePath: "/",
runtimeConfigUrl: "/config.json",
},
validationDurationMs: 0,
};
const release = /** @type {const} */ ({
const release: Release = {
schemaVersion: 1,
appVersion: "0.1.0",
buildId: "build-a",
@@ -28,15 +40,12 @@ const release = /** @type {const} */ ({
releaseId: "release-a",
builtAt: "2026-07-25T00:00:00Z",
routeChunks: { "route-home": "assets/home.js" },
});
};
describe("runtime adapter composition", () => {
it("constructs the local demo seam and infrastructure adapters", async () => {
const adapters = await createRuntimeAdapters({
runtime:
/** @type {Parameters<typeof createRuntimeAdapters>[0]["runtime"]} */ (
runtime
),
runtime,
release,
host: {},
});
@@ -46,19 +55,53 @@ describe("runtime adapter composition", () => {
releaseId: "release-a",
});
expect(adapters.infrastructure.queryClient).toBeDefined();
expect(adapters.infrastructure.queryInvalidation).toBeDefined();
expect(
adapters.infrastructure.crossContextInvalidationStatus(),
).toBe("DEGRADED_LOCAL_ONLY");
expect(adapters.outputPorts.diagnostics.record).toEqual(expect.any(Function));
expect(adapters.outputPorts.telemetry.emit).toEqual(expect.any(Function));
expect(adapters.outputPorts.telemetry.pendingCount()).toBe(0);
expect(adapters).not.toHaveProperty("http");
expect(adapters).not.toHaveProperty("storage");
adapters.infrastructure.dispose();
});
it("does not fail boot when Web Storage capability getters throw", async () => {
const host: Record<string, unknown> = {};
Object.defineProperties(host, {
localStorage: {
get() {
throw new DOMException("denied", "SecurityError");
},
},
sessionStorage: {
get() {
throw new DOMException("denied", "SecurityError");
},
},
});
const adapters = await createRuntimeAdapters({
runtime,
release,
host,
});
expect(adapters.infrastructure.queryClient).toBeDefined();
expect(adapters.outputPorts.preferences.read("COLOR_SCHEME")).toEqual({
ok: true,
value: undefined,
});
adapters.infrastructure.dispose();
});
it("fails closed when an external auth owner was not installed", async () => {
const adapters = await createRuntimeAdapters({
runtime:
/** @type {Parameters<typeof createRuntimeAdapters>[0]["runtime"]} */ ({
config: { ...runtime.config, AUTH_MODE: "external" },
}),
runtime: {
...runtime,
config: { ...runtime.config, AUTH_MODE: "external" },
},
release,
host: {},
});
@@ -74,10 +117,7 @@ describe("runtime adapter composition", () => {
};
const fetcher = vi.fn(async () => Response.json(activeRelease));
const adapters = await createRuntimeAdapters({
runtime:
/** @type {Parameters<typeof createRuntimeAdapters>[0]["runtime"]} */ (
runtime
),
runtime,
release,
fetcher,
host: {},
@@ -94,10 +134,9 @@ describe("runtime adapter composition", () => {
});
it("injects runtime timeout and max-attempt policy into HTTP execution", async () => {
const scheduled =
/** @type {Array<{callback: () => void, milliseconds: number}>} */ ([]);
const scheduled: Array<{ callback: () => void; milliseconds: number }> = [];
const scheduler = {
setTimeout: vi.fn((callback, milliseconds) => {
setTimeout: vi.fn((callback: () => void, milliseconds: number) => {
scheduled.push({ callback, milliseconds });
return scheduled.length;
}),
@@ -115,23 +154,14 @@ describe("runtime adapter composition", () => {
);
const authSession =
(await createRuntimeAdapters({
runtime:
/** @type {Parameters<typeof createRuntimeAdapters>[0]["runtime"]} */ (
runtime
),
runtime,
release,
host: {},
})).outputPorts.session;
const client = createRuntimeHttpClient(
{
runtime:
/** @type {Parameters<typeof createRuntimeHttpClient>[0]["runtime"]} */ (
runtime
),
authSession:
/** @type {import("../../src/application/ports/auth-session-port.js").AuthSessionPort} */ (
authSession
),
runtime,
authSession,
fetcher,
clock: { now: () => 0, sleep: async () => {} },
scheduler,
@@ -0,0 +1,52 @@
import { describe, expect, it, vi } from "vitest";
import { createServerStateScopeRuntime } from "../../src/adapters/query-cache/server-state-scope-runtime.ts";
describe("server-state session generation runtime", () => {
it("fences the old generation before reset and publishes the new scope after reset", async () => {
let sessionListener: () => void = () => {};
let completeReset: () => void = () => {};
const reset = new Promise<void>((resolve) => {
completeReset = resolve;
});
const resetLocal = vi.fn(() => reset);
let tokenSequence = 0;
const runtime = createServerStateScopeRuntime({
session: {
subscribe(listener) {
sessionListener = listener;
return () => {};
},
},
queryInvalidation: {
invalidate: async () => {},
beginMutation: () => ({ release: async () => {} }),
resetLocal,
dispose() {},
},
tokenFactory: () => `scope-token-${String(tokenSequence++).padStart(8, "0")}`,
});
const changed = vi.fn();
runtime.subscribe(changed);
const before = runtime.getSnapshot();
const identity = before.identities.intern({ id: "private" });
identity.acquire();
sessionListener();
expect(before.isCurrent()).toBe(false);
expect(runtime.getSnapshot()).toBe(before);
expect(changed).not.toHaveBeenCalled();
await vi.waitFor(() => expect(resetLocal).toHaveBeenCalledOnce());
completeReset();
await vi.waitFor(() => expect(changed).toHaveBeenCalledOnce());
const after = runtime.getSnapshot();
expect(after.generation).toBe(before.generation + 1);
expect(after.fingerprint).not.toBe(before.fingerprint);
expect(after.isCurrent()).toBe(true);
expect(before.identities.inspect().closed).toBe(true);
runtime.dispose();
expect(after.identities.inspect().closed).toBe(true);
});
});
+204
View File
@@ -0,0 +1,204 @@
import { describe, expect, it, vi } from "vitest";
import { createStorageDurabilityAdapter } from "../../src/adapters/browser-file-storage/storage-manager-adapter.ts";
describe("storage manager adapter", () => {
it("classifies rough origin pressure without treating it as a reservation", async () => {
const adapter = createStorageDurabilityAdapter({
estimate: async () => ({ usage: 75, quota: 100 }),
persisted: async () => false,
persist: async () => false,
});
expect(await adapter.inspect()).toEqual({
ok: true,
value: {
usageBytes: 75,
quotaBytes: 100,
persisted: false,
pressure: "PRESSURE",
},
});
});
it("snapshots pressure thresholds when the adapter is composed", async () => {
const policy = {
pressureRatio: 0.7,
criticalRatio: 0.85,
};
const adapter = createStorageDurabilityAdapter(
{
estimate: async () => ({ usage: 50, quota: 100 }),
},
policy,
);
policy.pressureRatio = 0.1;
policy.criticalRatio = 0.2;
await expect(adapter.inspect()).resolves.toMatchObject({
ok: true,
value: { pressure: "NORMAL" },
});
});
it("captures StorageManager methods when the adapter is composed", async () => {
const manager = {
estimate: async () => ({ usage: 10, quota: 100 }),
persisted: async () => false,
};
const adapter = createStorageDurabilityAdapter(manager);
manager.estimate = async () => ({ usage: 100, quota: 100 });
manager.persisted = async () => true;
await expect(adapter.inspect()).resolves.toEqual({
ok: true,
value: {
usageBytes: 10,
quotaBytes: 100,
persisted: false,
pressure: "NORMAL",
},
});
});
it("settles an aborted inspection while native reads remain pending", async () => {
const never = new Promise<never>(() => undefined);
const estimate = vi.fn(() => never);
const persisted = vi.fn(() => never);
const adapter = createStorageDurabilityAdapter({
estimate,
persisted,
});
const controller = new AbortController();
const inspection = adapter.inspect(controller.signal);
controller.abort();
await expect(inspection).resolves.toMatchObject({
ok: false,
error: { code: "ABORTED", operation: "STORAGE_ESTIMATE" },
});
expect(estimate).toHaveBeenCalledOnce();
expect(persisted).toHaveBeenCalledOnce();
});
it("consumes a late native rejection when abort wins during invocation", async () => {
const controller = new AbortController();
let rejectEstimate: ((reason: unknown) => void) | undefined;
const estimateResult = new Promise<StorageEstimate>(
(_resolve, reject) => {
rejectEstimate = reject;
},
);
const unhandled: unknown[] = [];
const onUnhandled = (reason: unknown): void => {
unhandled.push(reason);
};
process.on("unhandledRejection", onUnhandled);
try {
const adapter = createStorageDurabilityAdapter({
estimate: () => {
controller.abort();
return estimateResult;
},
});
await expect(adapter.inspect(controller.signal)).resolves.toMatchObject({
ok: false,
error: { code: "ABORTED", operation: "STORAGE_ESTIMATE" },
});
rejectEstimate?.(new Error("late estimate rejection"));
await new Promise<void>((resolve) => {
setTimeout(resolve, 0);
});
expect(unhandled).toEqual([]);
} finally {
process.off("unhandledRejection", onUnhandled);
}
});
it("requests persistence only through the explicit user-initiated port", async () => {
const persist = vi.fn(async () => true);
const adapter = createStorageDurabilityAdapter({
estimate: async () => ({}),
persist,
}, undefined, { isActive: true });
expect(
await adapter.requestPersistence({
reason: "PROTECT_UNSYNCED_USER_DATA",
userInitiated: true,
}),
).toEqual({ ok: true, value: "GRANTED" });
expect(persist).toHaveBeenCalledOnce();
});
it("requires real transient user activation before invoking persist", async () => {
const persist = vi.fn(async () => true);
const adapter = createStorageDurabilityAdapter(
{ estimate: async () => ({}), persist },
undefined,
{ isActive: false },
);
await expect(
adapter.requestPersistence({
reason: "PROTECT_UNSYNCED_USER_DATA",
userInitiated: true,
}),
).resolves.toMatchObject({
ok: false,
error: { code: "PERMISSION_DENIED" },
});
expect(persist).not.toHaveBeenCalled();
});
it("reports irreversible persist truth when abort wins after invocation", async () => {
const controller = new AbortController();
const adapter = createStorageDurabilityAdapter(
{
estimate: async () => ({}),
persist: async () => {
controller.abort();
return true;
},
},
undefined,
{ isActive: true },
);
await expect(
adapter.requestPersistence({
reason: "PROTECT_UNSYNCED_USER_DATA",
userInitiated: true,
signal: controller.signal,
}),
).resolves.toEqual({ ok: true, value: "GRANTED" });
});
it("keeps unsupported persistence state distinct from false", async () => {
const adapter = createStorageDurabilityAdapter({
estimate: async () => ({ usage: 1, quota: 10 }),
});
await expect(adapter.inspect()).resolves.toMatchObject({
ok: true,
value: { persisted: null },
});
});
it("returns unsupported rather than inventing durable memory storage", async () => {
const adapter = createStorageDurabilityAdapter(undefined);
expect(await adapter.inspect()).toMatchObject({
ok: false,
error: {
code: "UNSUPPORTED",
recovery: "ONLINE_ONLY",
},
});
});
});
-96
View File
@@ -1,96 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { createBrowserStorageAdapter } from "../../src/adapters/storage/browser-storage-adapter.js";
import {
STORAGE_REGISTRY,
buildPhysicalKey,
defineStorageKey,
} from "../../src/contracts/storage-keys.js";
/**
* @param {{quota?: boolean}} [options]
* @returns {Storage}
*/
function createStorage({ quota = false } = {}) {
const values = /** @type {Map<string, string>} */ (new Map());
return {
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
if (quota) throw new DOMException("full", "QuotaExceededError");
values.set(key, value);
},
removeItem: (key) => values.delete(key),
clear: () => values.clear(),
key: () => null,
get length() {
return values.size;
},
};
}
describe("storage registry", () => {
it("builds namespace and schema-versioned physical keys", () => {
expect(buildPhysicalKey("preference", 2, "theme")).toBe(
"ca-frontend:preference:v2:theme",
);
expect(STORAGE_REGISTRY.COLOR_SCHEME.physicalKey).toContain(":v1:");
});
it("rejects token or secret persistence registrations", () => {
expect(() =>
defineStorageKey({
logicalName: "TOKEN",
scope: "auth",
name: "token",
backend: "localStorage",
classification: "sensitive-forbidden",
schemaVersion: 1,
ttl: null,
migration: "discard",
quotaFallback: "feature-disable",
}),
).toThrow("Sensitive client storage registration is forbidden");
});
it("round-trips public preferences through the adapter", () => {
const localStorage = createStorage();
const adapter = createBrowserStorageAdapter({ localStorage });
expect(adapter.write("COLOR_SCHEME", "dark")).toEqual({ ok: true });
expect(adapter.read("COLOR_SCHEME")).toEqual({ ok: true, value: "dark" });
});
it("falls back to memory when preference storage quota is exceeded", () => {
const localStorage = createStorage({ quota: true });
const record = vi.fn();
const adapter = createBrowserStorageAdapter({
localStorage,
diagnostics: { record },
});
expect(adapter.write("COLOR_SCHEME", "dark")).toMatchObject({
ok: false,
fallback: "memory",
error: { kind: "STORAGE_QUOTA_EXCEEDED" },
});
expect(adapter.read("COLOR_SCHEME")).toEqual({ ok: true, value: "dark" });
expect(record).toHaveBeenCalledOnce();
expect(record).toHaveBeenCalledWith({
level: "warn",
eventId: "storage.operation.failed",
context: {
operation: "write:COLOR_SCHEME",
error_kind: "STORAGE_QUOTA_EXCEEDED",
},
});
expect(JSON.stringify(record.mock.calls)).not.toContain("dark");
});
it("discards data from a previous schema version", () => {
const localStorage = createStorage();
localStorage.setItem(
STORAGE_REGISTRY.COLOR_SCHEME.physicalKey,
JSON.stringify({ schemaVersion: 0, value: "dark" }),
);
const adapter = createBrowserStorageAdapter({ localStorage });
expect(adapter.read("COLOR_SCHEME")).toEqual({ ok: true, value: undefined });
});
});
+508
View File
@@ -0,0 +1,508 @@
import { describe, expect, it, vi } from "vitest";
import { createBrowserStorageAdapter } from "../../src/adapters/storage/browser-storage-adapter.ts";
import {
STORAGE_REGISTRY,
buildPhysicalKey,
defineStorageKey,
getStorageDefinition,
isStorageValueAllowed,
type StorageDefinition,
type StorageKeyInput,
} from "../../src/contracts/storage-keys.ts";
type StorageFailureMode = "none" | "quota" | "security";
function createStorage(options: {
initial?: Readonly<Record<string, string>>;
writeFailure?: StorageFailureMode;
readFailure?: StorageFailureMode;
removeFailure?: StorageFailureMode;
} = {}) {
const values = new Map<string, string>(Object.entries(options.initial ?? {}));
const state = {
writeFailure: options.writeFailure ?? "none",
readFailure: options.readFailure ?? "none",
removeFailure: options.removeFailure ?? "none",
};
const getItem = vi.fn((key: string) => {
throwFor(state.readFailure);
return values.get(key) ?? null;
});
const setItem = vi.fn((key: string, value: string) => {
throwFor(state.writeFailure);
values.set(key, value);
});
const removeItem = vi.fn((key: string) => {
throwFor(state.removeFailure);
values.delete(key);
});
const storage: Storage = {
getItem,
setItem,
removeItem,
clear: () => values.clear(),
key: (index) => [...values.keys()][index] ?? null,
get length() {
return values.size;
},
};
return { storage, values, state, getItem, setItem, removeItem };
}
function throwFor(mode: StorageFailureMode): void {
if (mode === "quota") {
throw new DOMException("private quota detail", "QuotaExceededError");
}
if (mode === "security") {
throw new DOMException("private security detail", "SecurityError");
}
}
function serializedEnvelope(
value: unknown,
overrides: Readonly<{
schemaVersion?: number;
expiresAt?: number | null;
}> = {},
): string {
return JSON.stringify({
schemaVersion: overrides.schemaVersion ?? 1,
expiresAt: overrides.expiresAt ?? null,
value,
});
}
function definitionResolver(
extra: Readonly<Record<string, StorageDefinition>>,
) {
return (logicalName: string): StorageDefinition =>
extra[logicalName] ?? getStorageDefinition(logicalName);
}
describe("storage registry", () => {
it("builds namespace and schema-versioned physical keys", () => {
expect(buildPhysicalKey("preference", 2, "theme")).toBe(
"ca-frontend:preference:v2:theme",
);
expect(STORAGE_REGISTRY.COLOR_SCHEME.physicalKey).toContain(":v1:");
});
it("rejects token or secret persistence registrations", () => {
expect(() =>
defineStorageKey({
logicalName: "TOKEN",
scope: "auth",
name: "token",
backend: "localStorage",
classification: "sensitive-forbidden",
schemaVersion: 1,
valueCodec: "none",
ttl: null,
migration: "discard",
quotaFallback: "feature-disable",
}),
).toThrow("Sensitive client storage registration is forbidden");
});
it("enforces the registry-owned typed value codec contract", () => {
const colorScheme = STORAGE_REGISTRY.COLOR_SCHEME;
expect(
["system", "light", "dark"].every((value) =>
isStorageValueAllowed(colorScheme, value),
),
).toBe(true);
expect(
[
"",
"LIGHT",
"private-custom-theme",
1,
null,
{ mode: "dark" },
].some((value) => isStorageValueAllowed(colorScheme, value)),
).toBe(false);
const opaqueString = STORAGE_REGISTRY.CHUNK_RELOAD_GUARD;
expect(isStorageValueAllowed(opaqueString, "a")).toBe(true);
expect(isStorageValueAllowed(opaqueString, "x".repeat(2_048))).toBe(true);
expect(isStorageValueAllowed(opaqueString, "")).toBe(false);
expect(isStorageValueAllowed(opaqueString, "x".repeat(2_049))).toBe(false);
expect(isStorageValueAllowed(opaqueString, { value: "opaque" })).toBe(false);
expect(
isStorageValueAllowed(
STORAGE_REGISTRY.QUERY_PERSISTENCE,
"must-never-persist",
),
).toBe(false);
});
it("rejects unregistered codecs and executable migration policies", () => {
const definition = {
logicalName: "UNSAFE_POLICY",
scope: "preference",
name: "unsafe-policy",
backend: "localStorage",
classification: "public-preference",
schemaVersion: 1,
valueCodec: "unregistered-codec",
ttl: null,
migration: "discard",
quotaFallback: "no-persist",
} as unknown as StorageKeyInput;
expect(() => defineStorageKey(definition)).toThrow(
"Unknown client storage value codec",
);
expect(() =>
defineStorageKey({
...definition,
valueCodec: "opaque-string-v1",
migration: (() => "unsafe") as unknown as "discard",
}),
).toThrow("Unsupported client storage migration policy");
});
it("round-trips public preferences through the adapter", () => {
const { storage: localStorage } = createStorage();
const adapter = createBrowserStorageAdapter({ localStorage });
expect(adapter.write("COLOR_SCHEME", "dark")).toEqual({ ok: true });
expect(adapter.read("COLOR_SCHEME")).toEqual({ ok: true, value: "dark" });
});
it("keeps local and session registry entries on their declared backends", () => {
const local = createStorage();
const session = createStorage();
const adapter = createBrowserStorageAdapter({
localStorage: local.storage,
sessionStorage: session.storage,
});
expect(adapter.write("COLOR_SCHEME", "light")).toEqual({ ok: true });
expect(adapter.write("CHUNK_RELOAD_GUARD", "release-a->release-b")).toEqual({
ok: true,
});
expect(
local.values.has(STORAGE_REGISTRY.COLOR_SCHEME.physicalKey),
).toBe(true);
expect(
local.values.has(STORAGE_REGISTRY.CHUNK_RELOAD_GUARD.physicalKey),
).toBe(false);
expect(
session.values.has(STORAGE_REGISTRY.CHUNK_RELOAD_GUARD.physicalKey),
).toBe(true);
expect(adapter.read("CHUNK_RELOAD_GUARD")).toEqual({
ok: true,
value: "release-a->release-b",
});
});
it("falls back to memory when preference storage quota is exceeded", () => {
const { storage: localStorage } = createStorage({
writeFailure: "quota",
});
const record = vi.fn();
const adapter = createBrowserStorageAdapter({
localStorage,
diagnostics: { record },
});
expect(adapter.write("COLOR_SCHEME", "dark")).toMatchObject({
ok: false,
fallback: "memory",
error: { kind: "STORAGE_QUOTA_EXCEEDED" },
});
expect(adapter.read("COLOR_SCHEME")).toEqual({ ok: true, value: "dark" });
expect(record).toHaveBeenCalledOnce();
expect(record).toHaveBeenCalledWith({
level: "warn",
eventId: "storage.operation.failed",
context: {
operation: "write:COLOR_SCHEME",
error_kind: "STORAGE_QUOTA_EXCEEDED",
},
});
expect(JSON.stringify(record.mock.calls)).not.toContain("dark");
expect(JSON.stringify(record.mock.calls)).not.toContain(
"private quota detail",
);
});
it("discards data from a previous schema version", () => {
const { storage: localStorage, values, removeItem } = createStorage({
initial: {
[STORAGE_REGISTRY.COLOR_SCHEME.physicalKey]: serializedEnvelope(
"dark",
{ schemaVersion: 2 },
),
},
});
const adapter = createBrowserStorageAdapter({ localStorage });
expect(adapter.read("COLOR_SCHEME")).toEqual({ ok: true, value: undefined });
expect(removeItem).toHaveBeenCalledOnce();
expect(values.has(STORAGE_REGISTRY.COLOR_SCHEME.physicalKey)).toBe(false);
});
it("rejects values outside the selected key codec before persistence", () => {
const { storage: localStorage, setItem } = createStorage();
const adapter = createBrowserStorageAdapter({ localStorage });
expect(adapter.write("COLOR_SCHEME", { mode: "dark" })).toMatchObject({
ok: false,
error: {
kind: "STORAGE_UNAVAILABLE",
code: "COLOR_SCHEME_WRITE_VALUE_REJECTED",
},
});
expect(setItem).not.toHaveBeenCalled();
});
it.each([
["malformed JSON", "{private malformed value"],
[
"invalid envelope",
JSON.stringify({
schemaVersion: 1,
expiresAt: null,
value: "dark",
unexpected: "private",
}),
],
["oversized record", "x".repeat(512)],
])(
"removes a %s once and suppresses it when native cleanup fails",
(_label, raw) => {
const physicalKey = STORAGE_REGISTRY.COLOR_SCHEME.physicalKey;
const { storage, getItem, removeItem } = createStorage({
initial: { [physicalKey]: raw },
removeFailure: "security",
});
const record = vi.fn();
const adapter = createBrowserStorageAdapter({
localStorage: storage,
diagnostics: { record },
maxSerializedBytes: 128,
});
expect(adapter.read("COLOR_SCHEME")).toEqual({
ok: true,
value: undefined,
});
expect(adapter.read("COLOR_SCHEME")).toEqual({
ok: true,
value: undefined,
});
expect(getItem).toHaveBeenCalledOnce();
expect(removeItem).toHaveBeenCalledOnce();
expect(JSON.stringify(record.mock.calls)).not.toMatch(
/private|malformed|unexpected/,
);
},
);
it("removes expired persistent records and does not return their value", () => {
const expiring = defineStorageKey({
logicalName: "EXPIRING_PREFERENCE",
scope: "preference",
name: "expiring",
backend: "localStorage",
classification: "public-preference",
schemaVersion: 1,
valueCodec: "opaque-string-v1",
ttl: 10,
migration: "discard",
quotaFallback: "memory",
});
const { storage, removeItem } = createStorage({
initial: {
[expiring.physicalKey]: serializedEnvelope("private-expired", {
expiresAt: 99,
}),
},
});
const adapter = createBrowserStorageAdapter({
localStorage: storage,
now: () => 100,
resolveDefinition: definitionResolver({
EXPIRING_PREFERENCE: expiring,
}),
});
expect(adapter.read("EXPIRING_PREFERENCE")).toEqual({
ok: true,
value: undefined,
});
expect(removeItem).toHaveBeenCalledWith(expiring.physicalKey);
});
it("prioritizes a failed-write memory envelope over stale persistent data and expires it", () => {
let timestamp = 100;
const expiring = defineStorageKey({
logicalName: "EXPIRING_PREFERENCE",
scope: "preference",
name: "expiring",
backend: "localStorage",
classification: "public-preference",
schemaVersion: 1,
valueCodec: "opaque-string-v1",
ttl: 10,
migration: "discard",
quotaFallback: "memory",
});
const { storage, state } = createStorage({
initial: {
[expiring.physicalKey]: serializedEnvelope("stale", {
expiresAt: 1_000,
}),
},
writeFailure: "quota",
});
const adapter = createBrowserStorageAdapter({
localStorage: storage,
now: () => timestamp,
resolveDefinition: definitionResolver({
EXPIRING_PREFERENCE: expiring,
}),
});
expect(adapter.write("EXPIRING_PREFERENCE", "fresh")).toMatchObject({
ok: false,
fallback: "memory",
});
expect(adapter.read("EXPIRING_PREFERENCE")).toEqual({
ok: true,
value: "fresh",
});
state.writeFailure = "none";
timestamp = 111;
expect(adapter.read("EXPIRING_PREFERENCE")).toEqual({
ok: true,
value: undefined,
});
});
it("clears a failed-write overlay after a later persistent write succeeds", () => {
const physicalKey = STORAGE_REGISTRY.COLOR_SCHEME.physicalKey;
const { storage, state, values } = createStorage({
writeFailure: "security",
});
const adapter = createBrowserStorageAdapter({ localStorage: storage });
expect(adapter.write("COLOR_SCHEME", "dark")).toMatchObject({
ok: false,
fallback: "memory",
error: { kind: "STORAGE_UNAVAILABLE" },
});
expect(adapter.read("COLOR_SCHEME")).toEqual({
ok: true,
value: "dark",
});
state.writeFailure = "none";
expect(adapter.write("COLOR_SCHEME", "light")).toEqual({ ok: true });
values.set(physicalKey, serializedEnvelope("system"));
expect(adapter.read("COLOR_SCHEME")).toEqual({
ok: true,
value: "system",
});
});
it("purges the memory overlay even when persistent remove throws", () => {
const { storage, state } = createStorage({
writeFailure: "quota",
removeFailure: "security",
});
const adapter = createBrowserStorageAdapter({ localStorage: storage });
expect(adapter.write("COLOR_SCHEME", "dark")).toMatchObject({
fallback: "memory",
});
state.writeFailure = "none";
expect(adapter.remove("COLOR_SCHEME")).toMatchObject({
ok: false,
error: { kind: "STORAGE_UNAVAILABLE" },
});
expect(adapter.read("COLOR_SCHEME")).toEqual({
ok: true,
value: undefined,
});
});
it("normalizes SecurityError reads and never exposes native details", () => {
const { storage } = createStorage({ readFailure: "security" });
const record = vi.fn();
const adapter = createBrowserStorageAdapter({
localStorage: storage,
diagnostics: { record },
});
const result = adapter.read("COLOR_SCHEME");
expect(result).toMatchObject({
ok: false,
error: {
kind: "STORAGE_UNAVAILABLE",
code: "COLOR_SCHEME_READ_UNAVAILABLE",
},
});
expect(JSON.stringify({ result, calls: record.mock.calls })).not.toContain(
"private security detail",
);
});
it.each([
["cyclic", (() => {
const value: { self?: unknown; secret: string } = {
secret: "private-cyclic",
};
value.self = value;
return value;
})()],
["BigInt", { secret: "private-bigint", value: 1n }],
["exotic", new Date(0)],
])("rejects %s values before calling the backend", (_label, value) => {
const { storage, setItem } = createStorage();
const record = vi.fn();
const adapter = createBrowserStorageAdapter({
localStorage: storage,
diagnostics: { record },
});
const result = adapter.write("COLOR_SCHEME", value);
expect(result).toMatchObject({
ok: false,
error: {
kind: "STORAGE_UNAVAILABLE",
code: "COLOR_SCHEME_WRITE_VALUE_REJECTED",
},
});
expect(setItem).not.toHaveBeenCalled();
expect(JSON.stringify({ result, calls: record.mock.calls })).not.toMatch(
/private-cyclic|private-bigint/,
);
});
it("rejects oversized serialized values before persistence and redacts them", () => {
const secret = `private-oversize-${"x".repeat(512)}`;
const { storage, setItem } = createStorage();
const record = vi.fn();
const adapter = createBrowserStorageAdapter({
sessionStorage: storage,
diagnostics: { record },
maxSerializedBytes: 128,
});
const result = adapter.write("CHUNK_RELOAD_GUARD", secret);
expect(result).toMatchObject({
ok: false,
error: {
kind: "STORAGE_UNAVAILABLE",
code: "CHUNK_RELOAD_GUARD_WRITE_SIZE_LIMIT_EXCEEDED",
},
});
expect(setItem).not.toHaveBeenCalled();
expect(JSON.stringify({ result, calls: record.mock.calls })).not.toContain(
secret,
);
});
});
+1 -1
View File
@@ -7,7 +7,7 @@ import {
supplyChainDigest,
validateDependencyReview,
validateLicensePolicy,
} from "../../scripts/lib/supply-chain.mjs";
} from "../../scripts/lib/supply-chain.ts";
const integrity = `sha512-${Buffer.alloc(64, 7).toString("base64")}`;
const dependency = {
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import { systemClock } from "../../src/adapters/platform/system-clock.js";
import { systemClock } from "../../src/adapters/platform/system-clock.ts";
describe("systemClock", () => {
it("resolves after the requested duration", async () => {
@@ -0,0 +1,236 @@
import { QueryClient } from "@tanstack/react-query";
import { describe, expect, it, vi } from "vitest";
import type {
BrowserCrossContextInvalidation,
CrossContextInvalidationDelivery,
} from "../../src/adapters/cross-context-invalidation/index.ts";
import { createTanStackCacheCoordinator } from "../../src/adapters/query-cache/tanstack-cache-coordinator.ts";
import { defineQueryInvalidationTopic } from "../../src/contracts/query-invalidation.ts";
const TOPIC_A = defineQueryInvalidationTopic("qinv.topic-a");
const TOPIC_B = defineQueryInvalidationTopic("qinv.topic-b");
function queryRegistry() {
return Object.freeze({
A: Object.freeze({
namespace: Object.freeze(["resource-a", 1] as const),
invalidationTopic: TOPIC_A,
crossContext: "invalidate-only" as const,
version: 1,
persistence: "disabled" as const,
}),
B: Object.freeze({
namespace: Object.freeze(["resource-b", 1] as const),
invalidationTopic: TOPIC_B,
crossContext: "invalidate-only" as const,
version: 1,
persistence: "disabled" as const,
}),
});
}
function crossContextHarness() {
let listener:
| ((delivery: CrossContextInvalidationDelivery) => void)
| undefined;
const publish = vi.fn(() => ({
ok: true as const,
transport: "BROADCAST" as const,
}));
const close = vi.fn();
const transport: BrowserCrossContextInvalidation = {
getStatus: () => "ACTIVE_BROADCAST",
publish,
subscribe(next) {
listener = next;
return () => {
listener = undefined;
};
},
close,
};
return Object.freeze({
transport,
publish,
close,
deliver(topic: string, ordering: "NEXT" | "GAP" = "NEXT") {
listener?.({
ordering,
transport: "BROADCAST",
event: {
protocolVersion: 1,
eventId: `event-${topic}-${ordering}`,
sourceId: "remote-source",
sourceEpoch: "remote-epoch",
sequence: 1,
cacheEpoch: "cache-epoch",
topic,
topicVersion: 1,
emittedAt: 1,
expiresAt: 60_001,
},
});
},
});
}
function createClient(): QueryClient {
return new QueryClient({
defaultOptions: {
queries: { retry: false, staleTime: Infinity, gcTime: Infinity },
mutations: { retry: false },
},
});
}
describe("TanStack cross-context cache coordinator", () => {
it("maps a local opaque topic to one namespace and publishes no query key", async () => {
const client = createClient();
const harness = crossContextHarness();
client.setQueryData(["resource-a", 1, "list"], ["a"]);
client.setQueryData(["resource-b", 1, "list"], ["b"]);
const coordinator = createTanStackCacheCoordinator({
queryClient: client,
queryRegistry: queryRegistry(),
crossContext: harness.transport,
});
await coordinator.invalidate([TOPIC_A]);
expect(
client.getQueryState(["resource-a", 1, "list"])?.isInvalidated,
).toBe(true);
expect(
client.getQueryState(["resource-b", 1, "list"])?.isInvalidated,
).toBe(false);
expect(harness.publish).toHaveBeenCalledWith({
topic: TOPIC_A,
topicVersion: 1,
});
expect(JSON.stringify(harness.publish.mock.calls)).not.toContain(
"resource-a",
);
});
it("applies a remote hint without publishing an echo", async () => {
const client = createClient();
const harness = crossContextHarness();
client.setQueryData(["resource-a", 1, "detail", "opaque"], {
value: true,
});
const coordinator = createTanStackCacheCoordinator({
queryClient: client,
queryRegistry: queryRegistry(),
crossContext: harness.transport,
});
harness.deliver(TOPIC_A);
await vi.waitFor(() =>
expect(
client.getQueryState([
"resource-a",
1,
"detail",
"opaque",
])?.isInvalidated,
).toBe(true),
);
expect(harness.publish).not.toHaveBeenCalled();
coordinator.dispose();
expect(harness.close).toHaveBeenCalledOnce();
});
it("coalesces remote hints while a local mutation lease is held", async () => {
const client = createClient();
const harness = crossContextHarness();
client.setQueryData(["resource-a", 1, "list"], ["a"]);
const invalidate = vi.spyOn(client, "invalidateQueries");
const coordinator = createTanStackCacheCoordinator({
queryClient: client,
queryRegistry: queryRegistry(),
crossContext: harness.transport,
});
const lease = coordinator.beginMutation([TOPIC_A]);
harness.deliver(TOPIC_A);
harness.deliver(TOPIC_A);
await Promise.resolve();
expect(invalidate).not.toHaveBeenCalled();
await lease.release();
expect(invalidate).toHaveBeenCalledTimes(1);
expect(harness.publish).not.toHaveBeenCalled();
});
it("reconciles every registered namespace when a source sequence has a gap", async () => {
const client = createClient();
const harness = crossContextHarness();
client.setQueryData(["resource-a", 1, "list"], ["a"]);
client.setQueryData(["resource-b", 1, "list"], ["b"]);
const coordinator = createTanStackCacheCoordinator({
queryClient: client,
queryRegistry: queryRegistry(),
crossContext: harness.transport,
});
harness.deliver(TOPIC_A, "GAP");
await vi.waitFor(() => {
expect(
client.getQueryState(["resource-a", 1, "list"])?.isInvalidated,
).toBe(true);
expect(
client.getQueryState(["resource-b", 1, "list"])?.isInvalidated,
).toBe(true);
});
});
it("fences remote delivery until a local reset has cancelled and cleared the cache", async () => {
const client = createClient();
const harness = crossContextHarness();
client.setQueryData(["resource-a", 1, "list"], ["private-old-scope"]);
let finishCancellation: (() => void) | undefined;
const cancelQueries = vi
.spyOn(client, "cancelQueries")
.mockImplementation(
() =>
new Promise<void>((resolve) => {
finishCancellation = resolve;
}),
);
const invalidate = vi.spyOn(client, "invalidateQueries");
const coordinator = createTanStackCacheCoordinator({
queryClient: client,
queryRegistry: queryRegistry(),
crossContext: harness.transport,
});
const reset = coordinator.resetLocal();
await vi.waitFor(() => expect(cancelQueries).toHaveBeenCalledOnce());
harness.deliver(TOPIC_A);
await Promise.resolve();
expect(invalidate).not.toHaveBeenCalled();
finishCancellation?.();
await reset;
expect(client.getQueryData(["resource-a", 1, "list"])).toBeUndefined();
await vi.waitFor(() => expect(invalidate).toHaveBeenCalledOnce());
expect(harness.publish).not.toHaveBeenCalled();
});
it("rejects an unregistered topic before opening a mutation lease", () => {
const coordinator = createTanStackCacheCoordinator({
queryClient: createClient(),
queryRegistry: queryRegistry(),
});
expect(() =>
coordinator.beginMutation([
defineQueryInvalidationTopic("unknown-topic"),
]),
).toThrow(
"Unregistered query invalidation topic",
);
});
});
@@ -3,11 +3,11 @@ import { describe, expect, it, vi } from "vitest";
import {
createTelemetryAdapter,
safeTraceparent,
} from "../../src/adapters/telemetry/best-effort-telemetry.js";
} from "../../src/adapters/telemetry/best-effort-telemetry.ts";
import {
TELEMETRY_REGISTRY,
projectTelemetryEvent,
} from "../../src/contracts/telemetry.js";
} from "../../src/contracts/telemetry.ts";
const validAttributes = {
error_kind: "SERVER_FAILURE",
@@ -67,12 +67,12 @@ describe("telemetry registry and redaction", () => {
describe("best-effort telemetry adapter", () => {
it("bounds the queue using oldest-drop without blocking callers", () => {
const scheduled = /** @type {Array<() => void>} */ ([]);
const scheduled: Array<() => void> = [];
const adapter = createTelemetryAdapter({
enabled: true,
endpoint: "https://telemetry.test/events",
maxQueue: 2,
schedule: (callback) => scheduled.push(callback),
schedule: (callback: () => void) => scheduled.push(callback),
fetcher: vi.fn(),
});
@@ -111,13 +111,70 @@ describe("best-effort telemetry adapter", () => {
expect(adapter.pendingCount()).toBe(0);
});
it("reschedules events whose flush callback runs during an active delivery", async () => {
const scheduled: Array<() => void> = [];
const deliveredRouteIds: unknown[][] = [];
let releaseFirstDelivery: () => void = () => {};
const firstDelivery = new Promise<void>((resolve) => {
releaseFirstDelivery = resolve;
});
let deliveryCount = 0;
const fetcher = vi.fn(async (_endpoint: RequestInfo | URL, init?: RequestInit) => {
deliveryCount += 1;
const body = JSON.parse(String(init?.body));
deliveredRouteIds.push(
(body.events as Array<{ attributes: { route_id: unknown } }>).map(
(event) => event.attributes.route_id,
),
);
if (deliveryCount === 1) {
await firstDelivery;
}
return new Response(null, { status: 204 });
});
const adapter = createTelemetryAdapter({
enabled: true,
endpoint: "https://telemetry.test/events",
schedule: (callback: () => void) => scheduled.push(callback),
fetcher,
});
const runScheduled = () => {
const callback = scheduled.shift();
if (!callback) throw new Error("Expected a scheduled telemetry flush");
callback();
};
adapter.emit("api.request.failed", {
...validAttributes,
route_id: "FIRST_ROUTE",
});
runScheduled();
await vi.waitFor(() => expect(fetcher).toHaveBeenCalledOnce());
adapter.emit("api.request.failed", {
...validAttributes,
route_id: "SECOND_ROUTE",
});
runScheduled();
expect(fetcher).toHaveBeenCalledOnce();
expect(adapter.pendingCount()).toBe(1);
releaseFirstDelivery();
await vi.waitFor(() => expect(scheduled).toHaveLength(1));
runScheduled();
await vi.waitFor(() => expect(fetcher).toHaveBeenCalledTimes(2));
expect(adapter.pendingCount()).toBe(0);
expect(deliveredRouteIds).toEqual([["FIRST_ROUTE"], ["SECOND_ROUTE"]]);
});
it("never serializes circular or unbounded event context", () => {
const adapter = createTelemetryAdapter({
enabled: true,
endpoint: "https://telemetry.test/events",
schedule: () => {},
});
const circular = {};
const circular: Record<string, unknown> = {};
circular.self = circular;
const hostile = new Proxy(
{},
@@ -0,0 +1,117 @@
import { describe, expect, it, vi } from "vitest";
import {
createBrowserUploadCancellationChannel,
type UploadCancellationBroadcastFacade,
} from "../../src/adapters/browser-transfer/resumable-upload/upload-cancellation-channel.ts";
type MessageListener = (
event: Readonly<{ data: unknown }>,
) => void;
function createBroadcastHarness() {
const listeners: Array<Set<MessageListener>> = [];
const closes: Array<ReturnType<typeof vi.fn>> = [];
function createChannel(): UploadCancellationBroadcastFacade {
const ownIndex = listeners.length;
const ownListeners = new Set<MessageListener>();
const close = vi.fn();
listeners.push(ownListeners);
closes.push(close);
return {
postMessage(message) {
for (const [index, peerListeners] of listeners.entries()) {
if (index === ownIndex) continue;
for (const listener of [...peerListeners]) {
listener({ data: structuredClone(message) });
}
}
},
addEventListener(_type, listener) {
ownListeners.add(listener);
},
removeEventListener(_type, listener) {
ownListeners.delete(listener);
},
close,
};
}
return {
createChannel,
emit(index: number, data: unknown) {
for (const listener of [...(listeners[index] ?? [])]) {
listener({ data });
}
},
listenerCount(index: number) {
return listeners[index]?.size ?? 0;
},
closes,
};
}
describe("browser upload cancellation channel", () => {
it("delivers only the closed v1 wire message to another context", () => {
const harness = createBroadcastHarness();
const first = createBrowserUploadCancellationChannel({
channelName: "product-upload-cancel-v1",
createChannel: harness.createChannel,
});
const second = createBrowserUploadCancellationChannel({
channelName: "product-upload-cancel-v1",
createChannel: harness.createChannel,
});
expect(first).toBeDefined();
expect(second).toBeDefined();
if (!first || !second) return;
const received = vi.fn();
const release = second.subscribe(received);
expect(first.publish("upload_key_cross_context")).toBe(true);
expect(received).toHaveBeenCalledOnce();
expect(received).toHaveBeenCalledWith(
"upload_key_cross_context",
);
for (const malformed of [
null,
{
protocol: "RESUMABLE_UPLOAD_CANCEL_V0",
uploadKey: "upload_key_cross_context",
},
{
protocol: "RESUMABLE_UPLOAD_CANCEL_V1",
uploadKey: "short",
},
{
protocol: "RESUMABLE_UPLOAD_CANCEL_V1",
uploadKey: "upload_key_cross_context",
extra: true,
},
]) {
harness.emit(1, malformed);
}
expect(received).toHaveBeenCalledTimes(1);
release();
second.close();
expect(harness.listenerCount(1)).toBe(0);
expect(harness.closes[1]).toHaveBeenCalledOnce();
expect(second.publish("upload_key_cross_context")).toBe(false);
first.close();
});
it("fails closed when the host is unsupported and rejects invalid namespaces", () => {
expect(
createBrowserUploadCancellationChannel({ host: {} }),
).toBeUndefined();
expect(() =>
createBrowserUploadCancellationChannel({
channelName: "invalid channel name",
host: {},
}),
).toThrow(/channel name/u);
});
});
+255
View File
@@ -0,0 +1,255 @@
import { describe, expect, it } from "vitest";
import {
WEB_PUSH_PROTOCOLS,
webPushSuccess,
} from "../../src/contracts/web-push.ts";
import {
clickDataFromHint,
decodeNotificationClickData,
decodeWebPushHint,
} from "../../src/adapters/web-push/push-codec.ts";
import {
createAssociationNotificationTag,
createWebPushNotificationRegistry,
} from "../../src/adapters/web-push/notification-registry.ts";
import {
WEB_PUSH_REGISTRATION_OPERATIONS,
createWebPushRegistrationGateway,
type WebPushRegistrationExecutor,
} from "../../src/adapters/web-push/push-registration-gateway.ts";
const now = Date.parse("2026-07-28T00:00:00.000Z");
function hint(overrides: Readonly<Record<string, unknown>> = {}) {
return {
protocol: WEB_PUSH_PROTOCOLS.hint,
notificationType: "INBOX_ACTIVITY",
notificationId: "notification_01",
associationEpoch: "association_01",
releaseEpoch: "release_01",
issuedAt: "2026-07-27T23:59:00.000Z",
expiresAt: "2026-07-28T01:00:00.000Z",
routeIntent: "OPEN_INBOX",
...overrides,
};
}
function encoded(value: unknown): Uint8Array {
return new TextEncoder().encode(JSON.stringify(value));
}
describe("Web Push contracts", () => {
it("decodes an exact bounded hint and persists only typed click data", () => {
const decoded = decodeWebPushHint(encoded(hint()), now);
expect(decoded).toEqual({ ok: true, value: hint() });
if (!decoded.ok) throw new Error("expected a decoded hint");
const click = clickDataFromHint(decoded.value);
expect(decodeNotificationClickData(click, now)).toEqual({
ok: true,
value: click,
});
expect(click).not.toHaveProperty("notificationType");
expect(click).not.toHaveProperty("issuedAt");
});
it("fails closed for declarative, unknown, expired, and oversized payloads", () => {
expect(
decodeWebPushHint(encoded({ web_push: 8030 }), now),
).toMatchObject({
ok: false,
error: { code: "DECLARATIVE_PUSH_FORBIDDEN" },
});
expect(
decodeWebPushHint(encoded(hint({ unexpected: true })), now),
).toMatchObject({
ok: false,
error: { code: "CONTRACT_REJECTED" },
});
expect(
decodeWebPushHint(
encoded(
hint({
issuedAt: "2026-07-27T22:00:00.000Z",
expiresAt: "2026-07-27T23:00:00.000Z",
}),
),
now,
),
).toMatchObject({
ok: false,
error: { code: "EXPIRED" },
});
expect(
decodeWebPushHint(new Uint8Array(3 * 1024 + 1), now),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(
decodeWebPushHint(
encoded(
hint({
issuedAt: "2026-07-28T00:06:00.000Z",
expiresAt: "2026-07-28T01:00:00.000Z",
}),
),
now,
),
).toMatchObject({
ok: false,
error: { code: "CONTRACT_REJECTED" },
});
expect(
decodeWebPushHint(
encoded(
hint({
issuedAt: "2026-07-28T00:00:00.000Z",
expiresAt: "2026-07-29T00:00:00.001Z",
}),
),
now,
),
).toMatchObject({
ok: false,
error: { code: "CONTRACT_REJECTED" },
});
const duplicateNotificationId = JSON.stringify(hint()).replace(
'"notificationId":"notification_01"',
'"notificationId":"notification_old","notificationId":"notification_01"',
);
expect(
decodeWebPushHint(
new TextEncoder().encode(duplicateNotificationId),
now,
),
).toMatchObject({
ok: false,
error: { code: "CONTRACT_REJECTED" },
});
});
it("uses an injected closed notification and route registry", async () => {
const registry = createWebPushNotificationRegistry([
{
notificationType: "INBOX_ACTIVITY",
routeIntent: "OPEN_INBOX",
title: "새 알림이 있습니다",
body: "앱을 열어 최신 내용을 확인하세요.",
path: "/inbox",
},
]);
expect(
registry.resolve("INBOX_ACTIVITY", "OPEN_INBOX"),
).toMatchObject({ path: "/inbox" });
expect(registry.resolve("UNKNOWN", "OPEN_INBOX")).toBeNull();
expect(registry.routePath("OPEN_INBOX")).toBe("/inbox");
const tag = await createAssociationNotificationTag(
"association_01",
"INBOX_ACTIVITY",
async () => new Uint8Array(32).fill(7).buffer,
);
expect(tag).toMatch(/^ca-push-v1-[0-9a-f]{24}-inbox_activity$/u);
expect(tag).not.toContain("association_01");
});
it("binds backend calls to fixed operations and strict response codecs", async () => {
const calls: Array<Readonly<{ operationId: string; body: unknown }>> = [];
const executor: WebPushRegistrationExecutor = {
async execute(input) {
calls.push(input);
switch (input.operationId) {
case WEB_PUSH_REGISTRATION_OPERATIONS.register:
return webPushSuccess({
protocol: WEB_PUSH_PROTOCOLS.registration,
associationEpoch: "association_01",
sessionBindingEpoch: "session_01",
});
case WEB_PUSH_REGISTRATION_OPERATIONS.reconcile:
return webPushSuccess({
protocol: WEB_PUSH_PROTOCOLS.reconciliation,
state: "ABSENT",
});
case WEB_PUSH_REGISTRATION_OPERATIONS.revoke:
return webPushSuccess({
protocol: WEB_PUSH_PROTOCOLS.revoke,
state: "ALREADY_GONE",
});
}
},
};
const gateway = createWebPushRegistrationGateway(executor);
const p256dh = new Uint8Array(65);
p256dh[0] = 4;
const material = {
endpoint: "https://push.example.test/subscription/opaque",
p256dh: Buffer.from(p256dh).toString("base64url"),
auth: Buffer.from(new Uint8Array(16)).toString("base64url"),
expirationTime: null,
};
const authority = {
fenceGeneration: "fence_01",
sessionBindingEpoch: "session_01",
releaseEpoch: "release_01",
};
expect(
await gateway.register({
material,
authority,
idempotencyKey: "idempotency_01",
}),
).toEqual({
ok: true,
value: {
associationEpoch: "association_01",
sessionBindingEpoch: "session_01",
},
});
expect(await gateway.reconcile({ material, authority })).toEqual({
ok: true,
value: { state: "ABSENT" },
});
expect(
await gateway.revoke({ associationEpoch: "association_01" }),
).toEqual({
ok: true,
value: { state: "ALREADY_GONE" },
});
expect(calls.map((call) => call.operationId)).toEqual([
WEB_PUSH_REGISTRATION_OPERATIONS.register,
WEB_PUSH_REGISTRATION_OPERATIONS.reconcile,
WEB_PUSH_REGISTRATION_OPERATIONS.revoke,
]);
expect(
await gateway.register({
material: {
...material,
p256dh: Buffer.from(new Uint8Array(65)).toString(
"base64url",
),
},
authority,
idempotencyKey: "idempotency_02",
}),
).toMatchObject({
ok: false,
error: { code: "INVALID_INPUT" },
});
const throwing = createWebPushRegistrationGateway({
async execute() {
throw new Error("native transport detail must not escape");
},
});
expect(
await throwing.revoke({
associationEpoch: "association_01",
}),
).toMatchObject({
ok: false,
error: { code: "NATIVE_FAILURE" },
});
});
});
+350
View File
@@ -0,0 +1,350 @@
import { describe, expect, it } from "vitest";
import {
WEB_PUSH_PROTOCOLS,
} from "../../src/contracts/web-push.ts";
import {
createPushAssociationFenceStore,
} from "../../src/adapters/web-push/push-association-fence-store.ts";
import {
createFakePushControlStoreDependencies,
} from "../helpers/fake-push-control-repository.ts";
const firstAuthority = Object.freeze({
fenceGeneration: "fence_01",
sessionBindingEpoch: "session_01",
releaseEpoch: "release_01",
});
const nextAuthority = Object.freeze({
fenceGeneration: "fence_02",
sessionBindingEpoch: "session_02",
releaseEpoch: "release_01",
});
function manualScheduler() {
let sequence = 0;
const callbacks = new Map<number, () => void>();
return {
scheduler: {
setTimeout(callback: () => void) {
sequence += 1;
callbacks.set(sequence, callback);
return sequence;
},
clearTimeout(handle: unknown) {
if (typeof handle === "number") callbacks.delete(handle);
},
},
expireAll() {
for (const callback of [...callbacks.values()]) callback();
},
};
}
describe("Web Push durable control fence", () => {
it("CASes UNASSOCIATED to ACTIVE and prevents tombstone resurrection", async () => {
const store = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const prepared = await store.prepare({
authority: firstAuthority,
updatedAt: "2026-07-28T00:00:00.000Z",
});
expect(prepared).toMatchObject({
ok: true,
value: {
revision: 1,
control: {
association: { state: "UNASSOCIATED" },
},
},
});
if (!prepared.ok) throw new Error("expected prepared control");
expect(prepared.value.control).not.toHaveProperty("revision");
const active = await store.activate({
expectedRevision: prepared.value.revision,
authority: firstAuthority,
associationEpoch: "association_01",
updatedAt: "2026-07-28T00:00:01.000Z",
});
expect(active).toMatchObject({
ok: true,
value: {
revision: 2,
control: {
association: {
state: "ACTIVE",
associationEpoch: "association_01",
},
},
},
});
if (!active.ok) throw new Error("expected active control");
const revoked = await store.markRevoked({
expectedRevision: active.value.revision,
authority: firstAuthority,
updatedAt: "2026-07-28T00:00:02.000Z",
});
expect(revoked).toMatchObject({
ok: true,
value: {
control: { association: { state: "REVOKED" } },
},
});
if (!revoked.ok) throw new Error("expected revoked control");
expect(
await store.activate({
expectedRevision: revoked.value.revision,
authority: firstAuthority,
associationEpoch: "association_01",
updatedAt: "2026-07-28T00:00:03.000Z",
}),
).toMatchObject({
ok: false,
error: { code: "TOMBSTONE_CONFLICT" },
});
expect(
await store.activate({
expectedRevision: revoked.value.revision,
authority: firstAuthority,
associationEpoch: "association_02",
updatedAt: "2026-07-28T00:00:04.000Z",
}),
).toMatchObject({
ok: true,
value: {
control: {
association: {
state: "ACTIVE",
associationEpoch: "association_02",
},
},
},
});
});
it("lets logout generation rotation win an in-flight registration CAS", async () => {
const dependencies = createFakePushControlStoreDependencies();
const store = createPushAssociationFenceStore(dependencies);
const prepared = await store.prepare({
authority: firstAuthority,
updatedAt: "2026-07-28T00:00:00.000Z",
});
if (!prepared.ok) throw new Error("expected prepared control");
const paused = dependencies.repository.pauseNextWrite(
(control) => control.association.state === "ACTIVE",
);
const lateActivation = store.activate({
expectedRevision: prepared.value.revision,
authority: firstAuthority,
associationEpoch: "association_late",
updatedAt: "2026-07-28T00:00:02.000Z",
});
await paused.reached;
const fenced = await store.rotateAndRevoke({
expectedRevision: prepared.value.revision,
previousAuthority: firstAuthority,
nextAuthority,
updatedAt: "2026-07-28T00:00:01.000Z",
});
expect(fenced).toMatchObject({
ok: true,
value: {
revision: 2,
control: {
fenceGeneration: "fence_02",
association: { state: "UNASSOCIATED" },
},
},
});
paused.release();
expect(await lateActivation).toMatchObject({
ok: false,
error: { code: "STALE_REVISION" },
});
expect(await store.read()).toMatchObject({
ok: true,
value: {
revision: 2,
control: {
fenceGeneration: "fence_02",
association: { state: "UNASSOCIATED" },
},
},
});
});
it("purges only the captured revoked association and cannot delete a newer owner", async () => {
const dependencies = createFakePushControlStoreDependencies();
const store = createPushAssociationFenceStore(dependencies);
const prepared = await store.prepare({
authority: firstAuthority,
updatedAt: "2026-07-28T00:00:00.000Z",
});
if (!prepared.ok) throw new Error("expected prepared control");
const active = await store.activate({
expectedRevision: prepared.value.revision,
authority: firstAuthority,
associationEpoch: "association_old",
updatedAt: "2026-07-28T00:00:01.000Z",
});
if (!active.ok) throw new Error("expected active control");
const revoked = await store.markRevoked({
expectedRevision: active.value.revision,
authority: firstAuthority,
updatedAt: "2026-07-28T00:00:02.000Z",
});
if (!revoked.ok) throw new Error("expected revoked control");
expect(
await store.purge({
expectedRevision: revoked.value.revision,
authority: firstAuthority,
associationEpoch: "association_wrong",
}),
).toMatchObject({
ok: false,
error: { code: "ASSOCIATION_MISMATCH" },
});
const paused = dependencies.repository.pauseNextRemove();
const latePurge = store.purge({
expectedRevision: revoked.value.revision,
authority: firstAuthority,
associationEpoch: "association_old",
});
await paused.reached;
const newer = await store.activate({
expectedRevision: revoked.value.revision,
authority: firstAuthority,
associationEpoch: "association_new",
updatedAt: "2026-07-28T00:00:03.000Z",
});
expect(newer).toMatchObject({
ok: true,
value: {
control: {
association: {
state: "ACTIVE",
associationEpoch: "association_new",
},
},
},
});
paused.release();
await expect(latePurge).resolves.toMatchObject({
ok: false,
error: { code: "STALE_REVISION" },
});
expect(await store.read()).toMatchObject({
ok: true,
value: {
control: {
association: {
state: "ACTIVE",
associationEpoch: "association_new",
},
},
},
});
});
it("removes the exact revoked tombstone with revision CAS", async () => {
const store = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const prepared = await store.prepare({
authority: firstAuthority,
updatedAt: "2026-07-28T00:00:00.000Z",
});
if (!prepared.ok) throw new Error("expected prepared control");
const active = await store.activate({
expectedRevision: prepared.value.revision,
authority: firstAuthority,
associationEpoch: "association_01",
updatedAt: "2026-07-28T00:00:01.000Z",
});
if (!active.ok) throw new Error("expected active control");
const revoked = await store.markRevoked({
expectedRevision: active.value.revision,
authority: firstAuthority,
updatedAt: "2026-07-28T00:00:02.000Z",
});
if (!revoked.ok) throw new Error("expected revoked control");
expect(
await store.purge({
expectedRevision: revoked.value.revision,
authority: firstAuthority,
associationEpoch: "association_01",
}),
).toEqual({ ok: true, value: undefined });
expect(await store.read()).toEqual({ ok: true, value: null });
});
it("fails closed when the injected repository returns a polluted record", async () => {
const dependencies = createFakePushControlStoreDependencies();
dependencies.repository.seedRaw(
{
protocol: WEB_PUSH_PROTOCOLS.control,
...firstAuthority,
updatedAt: "2026-07-28T00:00:00.000Z",
association: { state: "UNASSOCIATED" },
endpoint: "https://must-not-persist.invalid",
},
7,
);
const store = createPushAssociationFenceStore(dependencies);
expect(await store.read()).toMatchObject({
ok: false,
error: { code: "CONTROL_CORRUPT" },
});
});
it("aborts a late repository CAS at the hard operation deadline", async () => {
const dependencies = createFakePushControlStoreDependencies();
const clock = manualScheduler();
const store = createPushAssociationFenceStore({
...dependencies,
operationDeadlineMs: 1,
scheduler: clock.scheduler,
});
const prepared = await store.prepare({
authority: firstAuthority,
updatedAt: "2026-07-28T00:00:00.000Z",
});
if (!prepared.ok) throw new Error("expected prepared control");
const paused = dependencies.repository.pauseNextWrite(
(control) => control.association.state === "ACTIVE",
);
const activation = store.activate({
expectedRevision: prepared.value.revision,
authority: firstAuthority,
associationEpoch: "association_late",
updatedAt: "2026-07-28T00:00:01.000Z",
});
await paused.reached;
clock.expireAll();
expect(await activation).toMatchObject({
ok: false,
error: { code: "DEADLINE_EXCEEDED" },
});
paused.release();
expect(await store.read()).toMatchObject({
ok: true,
value: {
revision: 1,
control: { association: { state: "UNASSOCIATED" } },
},
});
});
});
@@ -0,0 +1,86 @@
import { describe, expect, it, vi } from "vitest";
import {
withAbortableDeadline,
type TimeoutScheduler,
} from "../../src/adapters/web-push/runtime-support.ts";
import {
webPushSuccess,
} from "../../src/contracts/web-push.ts";
describe("Web Push runtime deadlines", () => {
it("maps a scheduler setup failure without starting the task", async () => {
const task = vi.fn(async () => webPushSuccess(undefined));
const scheduler: TimeoutScheduler = {
setTimeout() {
throw new Error("scheduler unavailable");
},
clearTimeout: vi.fn(),
};
await expect(
withAbortableDeadline(task, {
deadlineMs: 10,
operation: "PUSH_HANDLE",
scheduler,
}),
).resolves.toMatchObject({
ok: false,
error: {
code: "NATIVE_FAILURE",
operation: "PUSH_HANDLE",
},
});
expect(task).not.toHaveBeenCalled();
});
it("does not let timer cleanup replace a settled result", async () => {
const scheduler: TimeoutScheduler = {
setTimeout: () => Object.freeze({ handle: true }),
clearTimeout() {
throw new Error("cleanup unavailable");
},
};
await expect(
withAbortableDeadline(
async () => webPushSuccess("settled"),
{
deadlineMs: 10,
operation: "PUSH_HANDLE",
scheduler,
},
),
).resolves.toEqual(webPushSuccess("settled"));
});
it("rechecks caller cancellation after listener registration", async () => {
let abortReads = 0;
const signal = {
get aborted() {
abortReads += 1;
return abortReads >= 2;
},
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
} as unknown as AbortSignal;
const task = vi.fn(async () => webPushSuccess(undefined));
const scheduler: TimeoutScheduler = {
setTimeout: () => Object.freeze({ handle: true }),
clearTimeout: vi.fn(),
};
await expect(
withAbortableDeadline(task, {
deadlineMs: 10,
operation: "PUSH_HANDLE",
scheduler,
signal,
}),
).resolves.toMatchObject({
ok: false,
error: { code: "ABORTED", operation: "PUSH_HANDLE" },
});
expect(task).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,830 @@
import { describe, expect, it, vi } from "vitest";
import { WEB_PUSH_PROTOCOLS, webPushSuccess } from "../../src/contracts/web-push.ts";
import { createPushAssociationFenceStore } from "../../src/adapters/web-push/push-association-fence-store.ts";
import {
createWebPushSubscriptionAdapter,
type WindowPushSubscriptionFacade,
} from "../../src/adapters/web-push/push-subscription-adapter.ts";
import type { WebPushRegistrationGateway } from "../../src/adapters/web-push/push-registration-gateway.ts";
import type { TimeoutScheduler } from "../../src/adapters/web-push/runtime-support.ts";
import {
createFakePushControlStoreDependencies,
} from "../helpers/fake-push-control-repository.ts";
const now = Date.parse("2026-07-28T00:00:00.000Z");
const authority = Object.freeze({
fenceGeneration: "fence_01",
sessionBindingEpoch: "session_01",
releaseEpoch: "release_01",
});
const nextAuthority = Object.freeze({
fenceGeneration: "fence_02",
sessionBindingEpoch: "session_02",
releaseEpoch: "release_01",
});
function bytes(length: number, first?: number): Uint8Array<ArrayBuffer> {
const value = new Uint8Array(new ArrayBuffer(length));
value.fill(7);
if (first !== undefined) value[0] = first;
return value;
}
const applicationServerKey = bytes(65, 4);
const vapidPublicKey = Buffer.from(applicationServerKey).toString("base64url");
function subscription(
unsubscribe = vi.fn(async () => true),
): WindowPushSubscriptionFacade {
return {
endpoint: "https://push.example.test/subscription/opaque",
expirationTime: null,
options: {
applicationServerKey: applicationServerKey.slice().buffer,
},
getKey(name) {
return name === "p256dh"
? bytes(65).buffer
: bytes(16).buffer;
},
unsubscribe,
};
}
function gateway(
overrides: Partial<WebPushRegistrationGateway> = {},
): WebPushRegistrationGateway {
return {
register: async () =>
webPushSuccess({
associationEpoch: "association_01",
sessionBindingEpoch: "session_01",
}),
reconcile: async () =>
webPushSuccess({
state: "ACTIVE",
associationEpoch: "association_01",
sessionBindingEpoch: "session_01",
}),
revoke: async () => webPushSuccess({ state: "REVOKED" }),
...overrides,
};
}
async function activeFenceStore() {
const store = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const prepared = await store.prepare({
authority,
updatedAt: "2026-07-28T00:00:00.000Z",
});
if (!prepared.ok) throw new Error("expected prepared control");
const active = await store.activate({
expectedRevision: prepared.value.revision,
authority,
associationEpoch: "association_01",
updatedAt: "2026-07-28T00:00:01.000Z",
});
if (!active.ok) throw new Error("expected active control");
return store;
}
function manualScheduler(): Readonly<{
scheduler: TimeoutScheduler;
expireAll(): void;
}> {
let sequence = 0;
const callbacks = new Map<number, () => void>();
return {
scheduler: {
setTimeout(callback) {
sequence += 1;
callbacks.set(sequence, callback);
return sequence;
},
clearTimeout(handle) {
if (typeof handle === "number") callbacks.delete(handle);
},
},
expireAll() {
for (const callback of [...callbacks.values()]) callback();
},
};
}
describe("Web Push window subscription adapter", () => {
it("requests permission only from an explicit action, registers, fences, and inspects READY", async () => {
const fenceStore = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const native = subscription();
let current: WindowPushSubscriptionFacade | null = null;
const subscribe = vi.fn(async (input) => {
expect(input.userVisibleOnly).toBe(true);
expect([...input.applicationServerKey]).toEqual([
...applicationServerKey,
]);
current = native;
return native;
});
let permission: "default" | "granted" = "default";
const requestPermission = vi.fn(async () => {
permission = "granted";
return permission;
});
const register = vi.fn(gateway().register);
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => true,
permission: {
permission: () => permission,
requestPermission,
},
registration: {
active: true,
pushManager: {
getSubscription: async () => current,
subscribe,
},
getNotifications: async () => [],
},
fenceStore,
gateway: gateway({ register }),
vapidPublicKey,
now: () => now,
idempotencyKeyFactory: () => "idempotency_01",
});
expect(
await adapter.enable({
authority,
signal: new AbortController().signal,
}),
).toEqual({ ok: true, value: { state: "PUSH_READY" } });
expect(requestPermission).toHaveBeenCalledOnce();
expect(subscribe).toHaveBeenCalledOnce();
expect(register).toHaveBeenCalledOnce();
expect(
await adapter.inspect({ authority }),
).toEqual({ ok: true, value: { state: "PUSH_READY" } });
const control = await fenceStore.read();
expect(control).toMatchObject({
ok: true,
value: {
control: {
association: {
state: "ACTIVE",
associationEpoch: "association_01",
},
},
},
});
});
it("keeps denied and absent-user-activation flows side-effect free", async () => {
const fenceStore = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const subscribe = vi.fn(async () => subscription());
const register = vi.fn(gateway().register);
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => false,
permission: {
permission: () => "default",
requestPermission: async () => "denied",
},
registration: {
active: true,
pushManager: {
getSubscription: async () => null,
subscribe,
},
getNotifications: async () => [],
},
fenceStore,
gateway: gateway({ register }),
vapidPublicKey,
now: () => now,
});
expect(
await adapter.enable({
authority,
signal: new AbortController().signal,
}),
).toMatchObject({
ok: false,
error: { code: "PERMISSION_DENIED" },
});
expect(subscribe).not.toHaveBeenCalled();
expect(register).not.toHaveBeenCalled();
expect(await fenceStore.read()).toEqual({ ok: true, value: null });
});
it("compensates a stale backend session binding without activating local authority", async () => {
const fenceStore = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const unsubscribe = vi.fn(async () => true);
const native = subscription(unsubscribe);
const revoke = vi.fn(gateway().revoke);
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => true,
permission: {
permission: () => "granted",
requestPermission: async () => "granted",
},
registration: {
active: true,
pushManager: {
getSubscription: async () => native,
subscribe: async () => native,
},
getNotifications: async () => [],
},
fenceStore,
gateway: gateway({
register: async () =>
webPushSuccess({
associationEpoch: "association_stale",
sessionBindingEpoch: "session_old",
}),
revoke,
}),
vapidPublicKey,
now: () => now,
idempotencyKeyFactory: () => "idempotency_01",
});
expect(
await adapter.enable({
authority,
signal: new AbortController().signal,
}),
).toEqual({
ok: true,
value: {
state: "PUSH_UNAVAILABLE",
reason: "SESSION_AUTHORITY_CHANGED",
},
});
expect(revoke).toHaveBeenCalledWith({
associationEpoch: "association_stale",
signal: expect.any(AbortSignal),
});
expect(unsubscribe).toHaveBeenCalledOnce();
expect(await fenceStore.read()).toMatchObject({
ok: true,
value: {
control: { association: { state: "UNASSOCIATED" } },
},
});
});
it("commits the logout fence first, then revokes backend/native state and closes owned notifications", async () => {
const fenceStore = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const prepared = await fenceStore.prepare({
authority,
updatedAt: "2026-07-28T00:00:00.000Z",
});
if (!prepared.ok) throw new Error("expected prepared control");
await fenceStore.activate({
expectedRevision: prepared.value.revision,
authority,
associationEpoch: "association_01",
updatedAt: "2026-07-28T00:00:01.000Z",
});
const unsubscribe = vi.fn(async () => true);
const native = subscription(unsubscribe);
const notificationClose = vi.fn();
const revoke = vi.fn(async () => {
const control = await fenceStore.read();
expect(control).toMatchObject({
ok: true,
value: {
control: {
fenceGeneration: "fence_02",
association: { state: "REVOKED" },
},
},
});
return webPushSuccess({ state: "REVOKED" as const });
});
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => true,
permission: {
permission: () => "granted",
requestPermission: async () => "granted",
},
registration: {
active: true,
pushManager: {
getSubscription: async () => native,
subscribe: async () => native,
},
getNotifications: async () => [
{
data: {
protocol: WEB_PUSH_PROTOCOLS.click,
notificationId: "notification_01",
routeIntent: "OPEN_INBOX",
associationEpoch: "association_01",
releaseEpoch: "release_01",
expiresAt: "2026-07-28T01:00:00.000Z",
},
close: notificationClose,
},
],
},
fenceStore,
gateway: gateway({ revoke }),
vapidPublicKey,
now: () => now,
});
expect(
await adapter.revoke({
previousAuthority: authority,
nextAuthority,
}),
).toEqual({
ok: true,
value: { state: "PUSH_UNAVAILABLE", reason: "REVOKED" },
});
expect(revoke).toHaveBeenCalledWith({
associationEpoch: "association_01",
signal: expect.any(AbortSignal),
});
expect(unsubscribe).toHaveBeenCalledOnce();
expect(notificationClose).toHaveBeenCalledOnce();
});
it("does not clean the current native subscription for a stale revoke authority", async () => {
const fenceStore = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const prepared = await fenceStore.prepare({
authority: nextAuthority,
updatedAt: "2026-07-28T00:00:00.000Z",
});
if (!prepared.ok) throw new Error("expected prepared control");
const activated = await fenceStore.activate({
expectedRevision: prepared.value.revision,
authority: nextAuthority,
associationEpoch: "association_new",
updatedAt: "2026-07-28T00:00:01.000Z",
});
if (!activated.ok) throw new Error("expected active control");
const unsubscribe = vi.fn(async () => true);
const getSubscription = vi.fn(async () =>
subscription(unsubscribe),
);
const notificationClose = vi.fn();
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => true,
permission: {
permission: () => "granted",
requestPermission: async () => "granted",
},
registration: {
active: true,
pushManager: {
getSubscription,
subscribe: async () => subscription(unsubscribe),
},
getNotifications: async () => [
{
data: {
protocol: WEB_PUSH_PROTOCOLS.click,
notificationId: "notification_new",
routeIntent: "OPEN_INBOX",
associationEpoch: "association_new",
releaseEpoch: "release_01",
expiresAt: "2026-07-28T01:00:00.000Z",
},
close: notificationClose,
},
],
},
fenceStore,
gateway: gateway(),
vapidPublicKey,
now: () => now,
});
await expect(
adapter.revoke({
previousAuthority: authority,
nextAuthority,
}),
).resolves.toEqual({
ok: true,
value: {
state: "PUSH_UNAVAILABLE",
reason: "LOCAL_FENCE_UNSAFE",
},
});
expect(getSubscription).not.toHaveBeenCalled();
expect(unsubscribe).not.toHaveBeenCalled();
expect(notificationClose).not.toHaveBeenCalled();
});
it("does not clean a captured old subscription after a newer association commits", async () => {
const fenceStore = await activeFenceStore();
const unsubscribe = vi.fn(async () => true);
const native = subscription(unsubscribe);
const notificationClose = vi.fn();
const revoke = vi.fn(async () => {
const current = await fenceStore.read();
if (!current.ok || !current.value) {
throw new Error("expected rotated control");
}
const activated = await fenceStore.activate({
expectedRevision: current.value.revision,
authority: nextAuthority,
associationEpoch: "association_new",
updatedAt: "2026-07-28T00:00:02.000Z",
});
if (!activated.ok) throw new Error("expected new association");
return webPushSuccess({ state: "REVOKED" as const });
});
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => true,
permission: {
permission: () => "granted",
requestPermission: async () => "granted",
},
registration: {
active: true,
pushManager: {
getSubscription: async () => native,
subscribe: async () => native,
},
getNotifications: async () => [
{
data: {
protocol: WEB_PUSH_PROTOCOLS.click,
notificationId: "notification_old",
routeIntent: "OPEN_INBOX",
associationEpoch: "association_01",
releaseEpoch: "release_01",
expiresAt: "2026-07-28T01:00:00.000Z",
},
close: notificationClose,
},
],
},
fenceStore,
gateway: gateway({ revoke }),
vapidPublicKey,
now: () => now,
});
await expect(
adapter.revoke({
previousAuthority: authority,
nextAuthority,
}),
).resolves.toEqual({
ok: true,
value: {
state: "PUSH_UNAVAILABLE",
reason: "NATIVE_UNSUBSCRIBE_AMBIGUOUS",
},
});
expect(unsubscribe).not.toHaveBeenCalled();
expect(notificationClose).not.toHaveBeenCalled();
await expect(fenceStore.read()).resolves.toMatchObject({
ok: true,
value: {
control: {
association: {
state: "ACTIVE",
associationEpoch: "association_new",
},
},
},
});
});
it("fences an ACTIVE association when notification permission drifts", async () => {
const fenceStore = await activeFenceStore();
const unsubscribe = vi.fn(async () => true);
const notificationClose = vi.fn();
const revoke = vi.fn(async () =>
webPushSuccess({ state: "REVOKED" as const }),
);
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => false,
permission: {
permission: () => "denied",
requestPermission: async () => "denied",
},
registration: {
active: true,
pushManager: {
getSubscription: async () => subscription(unsubscribe),
subscribe: async () => subscription(unsubscribe),
},
getNotifications: async () => [
{
data: {
protocol: WEB_PUSH_PROTOCOLS.click,
notificationId: "notification_expired",
routeIntent: "OPEN_INBOX",
associationEpoch: "association_01",
releaseEpoch: "release_01",
expiresAt: "2026-07-27T00:00:00.000Z",
},
close: notificationClose,
},
],
},
fenceStore,
gateway: gateway({ revoke }),
vapidPublicKey,
now: () => now,
});
expect(await adapter.reconcile({ authority })).toEqual({
ok: true,
value: { state: "PUSH_DENIED" },
});
expect(await fenceStore.read()).toMatchObject({
ok: true,
value: {
control: {
association: {
state: "REVOKED",
associationEpoch: "association_01",
},
},
},
});
expect(revoke).toHaveBeenCalledOnce();
expect(unsubscribe).toHaveBeenCalledOnce();
expect(notificationClose).toHaveBeenCalledOnce();
});
it("revokes local, backend, and native state on VAPID key drift", async () => {
const fenceStore = await activeFenceStore();
const unsubscribe = vi.fn(async () => true);
const native = subscription(unsubscribe);
const wrongApplicationServerKey = applicationServerKey.slice();
wrongApplicationServerKey[1] = 99;
const mismatched: WindowPushSubscriptionFacade = {
...native,
options: {
applicationServerKey: wrongApplicationServerKey.buffer,
},
};
const revoke = vi.fn(async () =>
webPushSuccess({ state: "REVOKED" as const }),
);
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => true,
permission: {
permission: () => "granted",
requestPermission: async () => "granted",
},
registration: {
active: true,
pushManager: {
getSubscription: async () => mismatched,
subscribe: async () => mismatched,
},
getNotifications: async () => [],
},
fenceStore,
gateway: gateway({ revoke }),
vapidPublicKey,
now: () => now,
});
expect(await adapter.reconcile({ authority })).toEqual({
ok: true,
value: {
state: "PUSH_UNAVAILABLE",
reason: "SUBSCRIPTION_KEY_MISMATCH",
},
});
expect(await fenceStore.read()).toMatchObject({
ok: true,
value: {
control: {
association: {
state: "REVOKED",
associationEpoch: "association_01",
},
},
},
});
expect(revoke).toHaveBeenCalledOnce();
expect(unsubscribe).toHaveBeenCalledOnce();
});
it("bounds a non-cooperative native subscription inspection", async () => {
const fenceStore = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const clock = manualScheduler();
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => true,
permission: {
permission: () => "granted",
requestPermission: async () => "granted",
},
registration: {
active: true,
pushManager: {
getSubscription: () =>
new Promise<WindowPushSubscriptionFacade | null>(() => {}),
subscribe: async () => subscription(),
},
getNotifications: async () => [],
},
fenceStore,
gateway: gateway(),
vapidPublicKey,
nativeOperationDeadlineMs: 1,
scheduler: clock.scheduler,
});
const inspection = adapter.inspect({ authority });
await Promise.resolve();
clock.expireAll();
expect(await inspection).toMatchObject({
ok: false,
error: { code: "DEADLINE_EXCEEDED" },
});
});
it("aborts a non-cooperative native operation on dispose", async () => {
const fenceStore = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => true,
permission: {
permission: () => "granted",
requestPermission: async () => "granted",
},
registration: {
active: true,
pushManager: {
getSubscription: () =>
new Promise<WindowPushSubscriptionFacade | null>(() => {}),
subscribe: async () => subscription(),
},
getNotifications: async () => [],
},
fenceStore,
gateway: gateway(),
vapidPublicKey,
});
const inspection = adapter.inspect({ authority });
await Promise.resolve();
adapter.dispose();
expect(await inspection).toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
});
it("maps a throwing browser facade and releases the exclusive lease", async () => {
const fenceStore = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => true,
permission: {
permission() {
throw new Error("permission provider unavailable");
},
requestPermission: async () => "granted",
},
registration: {
active: true,
pushManager: {
getSubscription: async () => null,
subscribe: async () => subscription(),
},
getNotifications: async () => [],
},
fenceStore,
gateway: gateway(),
vapidPublicKey,
});
for (let attempt = 0; attempt < 2; attempt += 1) {
await expect(
adapter.inspect({ authority }),
).resolves.toMatchObject({
ok: false,
error: {
code: "NATIVE_FAILURE",
operation: "SUBSCRIPTION_INSPECT",
},
});
}
});
it("bounds a non-cooperative backend register and revokes its late commit", async () => {
const fenceStore = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const clock = manualScheduler();
let signalRegisterStarted: (() => void) | undefined;
const registerStarted = new Promise<void>((resolve) => {
signalRegisterStarted = resolve;
});
let finishRegister:
| ((
result: Awaited<
ReturnType<WebPushRegistrationGateway["register"]>
>,
) => void)
| undefined;
const lateRegister = new Promise<
Awaited<ReturnType<WebPushRegistrationGateway["register"]>>
>((resolve) => {
finishRegister = resolve;
});
const revoke = vi.fn(async () =>
webPushSuccess({ state: "REVOKED" as const }),
);
const adapter = createWebPushSubscriptionAdapter({
secureContext: true,
userActivationIsActive: () => true,
permission: {
permission: () => "granted",
requestPermission: async () => "granted",
},
registration: {
active: true,
pushManager: {
getSubscription: async () => subscription(),
subscribe: async () => subscription(),
},
getNotifications: async () => [],
},
fenceStore,
gateway: gateway({
register: async () => {
signalRegisterStarted?.();
return await lateRegister;
},
revoke,
}),
vapidPublicKey,
backendOperationDeadlineMs: 1,
scheduler: clock.scheduler,
idempotencyKeyFactory: () => "idempotency_01",
});
const enabling = adapter.enable({
authority,
signal: new AbortController().signal,
});
await registerStarted;
clock.expireAll();
expect(await enabling).toMatchObject({
ok: false,
error: { code: "DEADLINE_EXCEEDED" },
});
finishRegister?.(
webPushSuccess({
associationEpoch: "association_late",
sessionBindingEpoch: "session_01",
}),
);
for (let turn = 0; turn < 8; turn += 1) {
await Promise.resolve();
}
expect(revoke).toHaveBeenCalledWith({
associationEpoch: "association_late",
signal: expect.any(AbortSignal),
});
expect(await fenceStore.read()).toMatchObject({
ok: true,
value: {
control: { association: { state: "UNASSOCIATED" } },
},
});
});
});
+617
View File
@@ -0,0 +1,617 @@
import { describe, expect, it, vi } from "vitest";
import {
WEB_PUSH_PROTOCOLS,
type NotificationClickDataV1,
} from "../../src/contracts/web-push.ts";
import { createPushAssociationFenceStore } from "../../src/adapters/web-push/push-association-fence-store.ts";
import { createWebPushNotificationRegistry } from "../../src/adapters/web-push/notification-registry.ts";
import { createPushEventAdapter } from "../../src/adapters/web-push/inbound/push-event-adapter.ts";
import { createNotificationClickAdapter } from "../../src/adapters/web-push/inbound/notification-click-adapter.ts";
import { createWebPushServiceWorkerRuntime } from "../../src/adapters/web-push/service-worker-runtime.ts";
import type { TimeoutScheduler } from "../../src/adapters/web-push/runtime-support.ts";
import {
createFakePushControlStoreDependencies,
} from "../helpers/fake-push-control-repository.ts";
const now = Date.parse("2026-07-28T00:00:00.000Z");
const authority = Object.freeze({
fenceGeneration: "fence_01",
sessionBindingEpoch: "session_01",
releaseEpoch: "release_01",
});
const nextAuthority = Object.freeze({
fenceGeneration: "fence_02",
sessionBindingEpoch: "session_02",
releaseEpoch: "release_01",
});
const registry = createWebPushNotificationRegistry([
{
notificationType: "INBOX_ACTIVITY",
routeIntent: "OPEN_INBOX",
title: "새 알림이 있습니다",
body: "앱을 열어 최신 내용을 확인하세요.",
path: "/inbox",
},
]);
async function activeFence() {
const store = createPushAssociationFenceStore(
createFakePushControlStoreDependencies(),
);
const prepared = await store.prepare({
authority,
updatedAt: "2026-07-28T00:00:00.000Z",
});
if (!prepared.ok) throw new Error("expected prepared push control");
const active = await store.activate({
expectedRevision: prepared.value.revision,
authority,
associationEpoch: "association_01",
updatedAt: "2026-07-28T00:00:01.000Z",
});
if (!active.ok) throw new Error("expected active push control");
return store;
}
function hint(overrides: Readonly<Record<string, unknown>> = {}) {
return {
protocol: WEB_PUSH_PROTOCOLS.hint,
notificationType: "INBOX_ACTIVITY",
notificationId: "notification_01",
associationEpoch: "association_01",
releaseEpoch: "release_01",
issuedAt: "2026-07-27T23:59:00.000Z",
expiresAt: "2026-07-28T01:00:00.000Z",
routeIntent: "OPEN_INBOX",
...overrides,
};
}
function clickData(): NotificationClickDataV1 {
return {
protocol: WEB_PUSH_PROTOCOLS.click,
notificationId: "notification_01",
routeIntent: "OPEN_INBOX",
associationEpoch: "association_01",
releaseEpoch: "release_01",
expiresAt: "2026-07-28T01:00:00.000Z",
};
}
function manualScheduler(): Readonly<{
scheduler: TimeoutScheduler;
delays: readonly number[];
expireAll(): void;
}> {
let sequence = 0;
const callbacks = new Map<number, () => void>();
const delays: number[] = [];
return {
scheduler: {
setTimeout(callback, milliseconds) {
sequence += 1;
delays.push(milliseconds);
callbacks.set(sequence, callback);
return sequence;
},
clearTimeout(handle) {
if (typeof handle === "number") callbacks.delete(handle);
},
},
expireAll() {
for (const callback of [...callbacks.values()]) callback();
},
delays,
};
}
describe("Web Push worker runtime", () => {
it("waits for strict hint handling and shows only registry-owned safe copy", async () => {
const store = await activeFence();
const shown: Array<Readonly<{ title: string; options: unknown }>> = [];
let waited: Promise<void> | null = null;
const adapter = createPushEventAdapter({
fenceStore: store,
registry,
now: () => now,
tagDigest: async () => new Uint8Array(32).fill(9).buffer,
notifications: {
async showNotification(title, options) {
shown.push({ title, options });
},
},
});
const bytes = new TextEncoder().encode(JSON.stringify(hint()));
const result = await adapter.handle({
data: { arrayBuffer: () => bytes.slice().buffer },
waitUntil(task) {
waited = task;
},
});
await waited;
expect(result).toEqual({ ok: true, value: undefined });
expect(shown).toHaveLength(1);
expect(shown[0]).toMatchObject({
title: "새 알림이 있습니다",
options: {
body: "앱을 열어 최신 내용을 확인하세요.",
data: {
protocol: WEB_PUSH_PROTOCOLS.click,
notificationId: "notification_01",
},
},
});
expect(
(shown[0]?.options as { tag: string }).tag,
).not.toContain("association_01");
});
it("drops mismatched associations before notification rendering", async () => {
const store = await activeFence();
const showNotification = vi.fn(async () => {});
const adapter = createPushEventAdapter({
fenceStore: store,
registry,
now: () => now,
tagDigest: async () => new Uint8Array(32).buffer,
notifications: { showNotification },
});
const bytes = new TextEncoder().encode(
JSON.stringify(hint({ associationEpoch: "association_old" })),
);
const result = await adapter.handle({
data: { arrayBuffer: () => bytes.slice().buffer },
waitUntil() {},
});
expect(result).toMatchObject({
ok: false,
error: { code: "ASSOCIATION_MISMATCH" },
});
expect(showNotification).not.toHaveBeenCalled();
});
it("aborts late worker work at the handler deadline before notification display", async () => {
const store = await activeFence();
const clock = manualScheduler();
const showNotification = vi.fn(async () => {});
let signalDigestStarted: (() => void) | undefined;
const digestStarted = new Promise<void>((resolve) => {
signalDigestStarted = resolve;
});
let finishDigest: ((value: ArrayBuffer) => void) | undefined;
const digest = new Promise<ArrayBuffer>((resolve) => {
finishDigest = resolve;
});
const adapter = createPushEventAdapter({
fenceStore: store,
registry,
now: () => now,
handlerDeadlineMs: 1,
scheduler: clock.scheduler,
tagDigest: async () => {
signalDigestStarted?.();
return await digest;
},
notifications: { showNotification },
});
const bytes = new TextEncoder().encode(JSON.stringify(hint()));
const handling = adapter.handle({
data: { arrayBuffer: () => bytes.slice().buffer },
waitUntil() {},
});
await digestStarted;
clock.expireAll();
expect(await handling).toMatchObject({
ok: false,
error: { code: "DEADLINE_EXCEEDED" },
});
finishDigest?.(new Uint8Array(32).buffer);
await Promise.resolve();
await Promise.resolve();
expect(showNotification).not.toHaveBeenCalled();
});
it("rechecks the durable generation after async push work", async () => {
const store = await activeFence();
const showNotification = vi.fn(async () => {});
const adapter = createPushEventAdapter({
fenceStore: store,
registry,
now: () => now,
notifications: { showNotification },
tagDigest: async () => {
const current = await store.read();
if (!current.ok || !current.value) {
throw new Error("expected active control");
}
const rotated = await store.rotateAndRevoke({
expectedRevision: current.value.revision,
previousAuthority: authority,
nextAuthority,
updatedAt: "2026-07-28T00:00:02.000Z",
});
if (!rotated.ok) throw new Error("expected rotated control");
return new Uint8Array(32).buffer;
},
});
const bytes = new TextEncoder().encode(JSON.stringify(hint()));
expect(
await adapter.handle({
data: { arrayBuffer: () => bytes.slice().buffer },
waitUntil() {},
}),
).toMatchObject({
ok: false,
error: { code: "ASSOCIATION_MISMATCH" },
});
expect(showNotification).not.toHaveBeenCalled();
});
it("revalidates persisted click authority and uses a same-origin handoff", async () => {
const store = await activeFence();
const messages: unknown[] = [];
const focus = vi.fn(async () => {});
const openWindow = vi.fn(async () => null);
const close = vi.fn();
let waited: Promise<void> | null = null;
const adapter = createNotificationClickAdapter({
fenceStore: store,
registry,
origin: "https://app.example.test",
now: () => now,
clients: {
async matchControlledWindowClients() {
return [
{
url: "https://app.example.test/current",
focus,
postMessage(message) {
messages.push(message);
},
},
];
},
openWindow,
},
});
const result = await adapter.handle({
notification: { data: clickData(), close },
waitUntil(task) {
waited = task;
},
});
await waited;
expect(result).toEqual({ ok: true, value: undefined });
expect(close).toHaveBeenCalledOnce();
expect(focus).toHaveBeenCalledOnce();
expect(openWindow).not.toHaveBeenCalled();
expect(messages).toEqual([
expect.objectContaining({
protocol: WEB_PUSH_PROTOCOLS.clickHandoff,
routeIntent: "OPEN_INBOX",
path: "/inbox",
}),
]);
});
it("rechecks the durable generation after client enumeration", async () => {
const store = await activeFence();
const postMessage = vi.fn();
const focus = vi.fn(async () => {});
const adapter = createNotificationClickAdapter({
fenceStore: store,
registry,
origin: "https://app.example.test",
now: () => now,
clients: {
async matchControlledWindowClients() {
const current = await store.read();
if (!current.ok || !current.value) {
throw new Error("expected active control");
}
const rotated = await store.rotateAndRevoke({
expectedRevision: current.value.revision,
previousAuthority: authority,
nextAuthority,
updatedAt: "2026-07-28T00:00:02.000Z",
});
if (!rotated.ok) throw new Error("expected rotated control");
return [
{
url: "https://app.example.test/",
focus,
postMessage,
},
];
},
openWindow: async () => null,
},
});
expect(
await adapter.handle({
notification: { data: clickData(), close() {} },
waitUntil() {},
}),
).toMatchObject({
ok: false,
error: { code: "ASSOCIATION_MISMATCH" },
});
expect(postMessage).not.toHaveBeenCalled();
expect(focus).not.toHaveBeenCalled();
});
it("aborts in-flight push work when the worker runtime is disposed", async () => {
const store = await activeFence();
const listeners = new Map<string, (event: unknown) => void>();
const showNotification = vi.fn(async () => {});
let signalDigestStarted: (() => void) | undefined;
const digestStarted = new Promise<void>((resolve) => {
signalDigestStarted = resolve;
});
let finishDigest: ((value: ArrayBuffer) => void) | undefined;
const digest = new Promise<ArrayBuffer>((resolve) => {
finishDigest = resolve;
});
const runtime = createWebPushServiceWorkerRuntime({
fenceStore: store,
registry,
now: () => now,
tagDigest: async () => {
signalDigestStarted?.();
return await digest;
},
host: {
origin: "https://app.example.test",
registration: { showNotification },
clients: {
matchAll: async () => [],
openWindow: async () => null,
},
addEventListener(type, listener) {
listeners.set(type, listener);
},
removeEventListener(type, listener) {
if (listeners.get(type) === listener) listeners.delete(type);
},
},
});
let waited: Promise<void> | null = null;
const bytes = new TextEncoder().encode(JSON.stringify(hint()));
listeners.get("push")?.({
data: { arrayBuffer: () => bytes.slice().buffer },
waitUntil(task: Promise<void>) {
waited = task;
},
});
await digestStarted;
runtime.dispose();
await waited;
finishDigest?.(new Uint8Array(32).buffer);
for (let turn = 0; turn < 4; turn += 1) {
await Promise.resolve();
}
expect(showNotification).not.toHaveBeenCalled();
expect(listeners.size).toBe(0);
});
it("bounds a non-cooperative subscription-change handoff at ten seconds", async () => {
const store = await activeFence();
const listeners = new Map<string, (event: unknown) => void>();
const clock = manualScheduler();
const observations: unknown[] = [];
const runtime = createWebPushServiceWorkerRuntime({
fenceStore: store,
registry,
scheduler: clock.scheduler,
observer: {
record(observation) {
observations.push(observation);
},
},
host: {
origin: "https://app.example.test",
registration: { showNotification: async () => {} },
clients: {
matchAll: () => new Promise<readonly unknown[]>(() => {}),
openWindow: async () => null,
},
addEventListener(type, listener) {
listeners.set(type, listener);
},
removeEventListener(type, listener) {
if (listeners.get(type) === listener) listeners.delete(type);
},
},
});
let waited: Promise<void> | null = null;
listeners.get("pushsubscriptionchange")?.({
waitUntil(task: Promise<void>) {
waited = task;
},
});
await Promise.resolve();
expect(clock.delays).toContain(10_000);
clock.expireAll();
await waited;
expect(observations).toContainEqual({
event: "web_push_subscription_rotated",
outcome: "DEGRADED",
reason: "DEADLINE_EXCEEDED",
});
runtime.dispose();
});
it("aborts subscription-change handoff on dispose and contains waitUntil throws", async () => {
const store = await activeFence();
const listeners = new Map<string, (event: unknown) => void>();
const observations: unknown[] = [];
const runtime = createWebPushServiceWorkerRuntime({
fenceStore: store,
registry,
observer: {
record(observation) {
observations.push(observation);
},
},
host: {
origin: "https://app.example.test",
registration: { showNotification: async () => {} },
clients: {
matchAll: () => new Promise<readonly unknown[]>(() => {}),
openWindow: async () => null,
},
addEventListener(type, listener) {
listeners.set(type, listener);
},
removeEventListener(type, listener) {
if (listeners.get(type) === listener) listeners.delete(type);
},
},
});
let waited: Promise<void> | null = null;
listeners.get("pushsubscriptionchange")?.({
waitUntil(task: Promise<void>) {
waited = task;
},
});
await Promise.resolve();
runtime.dispose();
await waited;
expect(observations).toContainEqual({
event: "web_push_subscription_rotated",
outcome: "DEGRADED",
reason: "ABORTED",
});
const throwingStore = await activeFence();
const throwingListeners = new Map<
string,
(event: unknown) => void
>();
const throwingObservations: unknown[] = [];
let signalThrowingObservation: (() => void) | undefined;
const throwingObservation = new Promise<void>((resolve) => {
signalThrowingObservation = resolve;
});
const throwingRuntime = createWebPushServiceWorkerRuntime({
fenceStore: throwingStore,
registry,
observer: {
record(observation) {
throwingObservations.push(observation);
signalThrowingObservation?.();
},
},
host: {
origin: "https://app.example.test",
registration: { showNotification: async () => {} },
clients: {
matchAll: async () => [],
openWindow: async () => null,
},
addEventListener(type, listener) {
throwingListeners.set(type, listener);
},
removeEventListener(type, listener) {
if (throwingListeners.get(type) === listener) {
throwingListeners.delete(type);
}
},
},
});
expect(() =>
throwingListeners.get("pushsubscriptionchange")?.({
waitUntil() {
throw new Error("waitUntil unavailable");
},
}),
).not.toThrow();
await throwingObservation;
expect(throwingObservations).toContainEqual({
event: "web_push_subscription_rotated",
outcome: "DEGRADED",
reason: "ABORTED",
});
throwingRuntime.dispose();
});
it("installs no handlers until the uncomposed factory is called and removes all on dispose", async () => {
const store = await activeFence();
const listeners = new Map<string, (event: unknown) => void>();
const messages: unknown[] = [];
const matchPolicies: boolean[] = [];
const client = {
url: "https://app.example.test/",
focus: async () => {},
postMessage(message: unknown) {
messages.push(message);
},
};
expect(listeners.size).toBe(0);
const runtime = createWebPushServiceWorkerRuntime({
fenceStore: store,
registry,
now: () => now,
host: {
origin: "https://app.example.test",
registration: {
async showNotification() {},
},
clients: {
async matchAll(input) {
matchPolicies.push(input.includeUncontrolled);
return [client];
},
async openWindow() {
return client;
},
},
addEventListener(type, listener) {
listeners.set(type, listener);
},
removeEventListener(type, listener) {
if (listeners.get(type) === listener) listeners.delete(type);
},
},
});
expect([...listeners.keys()].sort()).toEqual([
"notificationclick",
"push",
"pushsubscriptionchange",
]);
let clickWait: Promise<void> | null = null;
listeners.get("notificationclick")?.({
notification: { data: clickData(), close() {} },
waitUntil(task: Promise<void>) {
clickWait = task;
},
});
await clickWait;
let subscriptionWait: Promise<void> | null = null;
listeners.get("pushsubscriptionchange")?.({
waitUntil(task: Promise<void>) {
subscriptionWait = task;
},
});
await subscriptionWait;
expect(matchPolicies).toEqual([false, true]);
expect(messages).toEqual([
expect.objectContaining({
protocol: WEB_PUSH_PROTOCOLS.clickHandoff,
}),
{ protocol: WEB_PUSH_PROTOCOLS.reconcileRequired },
]);
runtime.dispose();
expect(listeners.size).toBe(0);
});
});