refactor: 프론트엔드 리펙토링

This commit is contained in:
donghyeon-ka
2026-09-18 22:05:42 +09:00
parent 5cc41467ae
commit ec7f20e2ee
100 changed files with 6005 additions and 2867 deletions
@@ -18,13 +18,13 @@ function databaseName(_prefix: string): string {
return `ca-idb-v1:a${token.slice(0, 31)}.n${token.slice(1)}.p${token.split("").reverse().join("").slice(0, 31)}`;
}
async function installLifecycleRuntime(
async function startLifecycleRuntime(
page: Page,
name: string,
targetVersion: 1 | 2,
blockedTimeoutMs = 1_000,
) {
return await page.evaluate(
): Promise<void> {
await page.evaluate(
async ({
currentDatabaseName,
currentTargetVersion,
@@ -173,8 +173,12 @@ async function installLifecycleRuntime(
__indexedDbLifecycleRuntime?: unknown;
}
).__indexedDbLifecycleRuntime = runtime;
const opened = await runtime.open();
return { opened, status: runtime.getStatus() };
const opening = runtime.open();
(
globalThis as unknown as {
__indexedDbLifecycleOpening?: Promise<unknown>;
}
).__indexedDbLifecycleOpening = opening;
},
{
currentDatabaseName: name,
@@ -184,6 +188,35 @@ async function installLifecycleRuntime(
);
}
async function settleLifecycleRuntime(page: Page): Promise<unknown> {
return await page.evaluate(async () => {
const state = globalThis as unknown as {
__indexedDbLifecycleRuntime?: {
getStatus(): unknown;
};
__indexedDbLifecycleOpening?: Promise<unknown>;
};
if (!state.__indexedDbLifecycleRuntime || !state.__indexedDbLifecycleOpening) {
throw new Error("IndexedDB lifecycle runtime is not installed.");
}
const opened = await state.__indexedDbLifecycleOpening;
return {
opened,
status: state.__indexedDbLifecycleRuntime.getStatus(),
};
});
}
async function installLifecycleRuntime(
page: Page,
name: string,
targetVersion: 1 | 2,
blockedTimeoutMs = 1_000,
): Promise<unknown> {
await startLifecycleRuntime(page, name, targetVersion, blockedTimeoutMs);
return await settleLifecycleRuntime(page);
}
async function lifecycleStatus(page: Page): Promise<unknown> {
return await page.evaluate(() => {
const runtime = (
@@ -977,12 +1010,16 @@ test("fails a blocked v2 upgrade closed, then closes its late connection after t
);
expect(blocker).toBe("OPEN");
const blocked = await installLifecycleRuntime(
page,
name,
2,
25,
);
await startLifecycleRuntime(page, name, 2, 500);
await expect
.poll(async () => await lifecycleStatus(page))
.toEqual({
kind: "BLOCKED",
currentVersion: 1,
targetVersion: 2,
});
const blocked = await settleLifecycleRuntime(page);
expect(blocked).toMatchObject({
opened: {
ok: false,
@@ -992,9 +1029,8 @@ test("fails a blocked v2 upgrade closed, then closes its late connection after t
},
},
status: {
kind: "BLOCKED",
currentVersion: 1,
targetVersion: 2,
kind: "CLOSED",
reason: "NOT_OPENED",
},
});
@@ -0,0 +1,244 @@
import type {
IndexedDbRuntimeDependencies,
} from "../../src/adapters/storage/indexeddb/indexeddb-types.ts";
import type { LocalDraft } from "../../src/features/local-draft-feature/domain/local-draft.ts";
import {
expect,
test,
} from "../support/browser/strict-browser-test.ts";
test("composes the local-draft feature over the real browser IndexedDB runtime", async ({
page,
}) => {
await page.route("**/favicon.ico", (route) =>
route.fulfill({ status: 204 }),
);
await page.goto("/config.json");
const result = await page.evaluate(async () => {
const indexedDbModulePath =
"/src/adapters/storage/indexeddb/index.ts";
const contributionModulePath =
"/src/features/feature-adapter-contribution.ts";
const localDraftModulePath =
"/src/features/local-draft-feature/adapters/create-local-draft-feature-input.ts";
const {
createIndexedDbRuntime,
indexedDbPhysicalDatabaseName,
} = (await import(
/* @vite-ignore */ indexedDbModulePath
)) as typeof import("../../src/adapters/storage/indexeddb/index.ts");
const {
composeFeatureAdapterInputs,
createIndexedDbRepositoryProvider,
} = (await import(
/* @vite-ignore */ contributionModulePath
)) as typeof import("../../src/features/feature-adapter-contribution.ts");
const {
LOCAL_DRAFT_FEATURE_ADAPTER_CONTRIBUTION,
} = (await import(
/* @vite-ignore */ localDraftModulePath
)) as typeof import("../../src/features/local-draft-feature/adapters/create-local-draft-feature-input.ts");
const suffix = crypto.randomUUID().replaceAll("-", "");
const scope = Object.freeze({
authorityToken: `a${suffix.slice(0, 31)}`,
namespaceToken: `n${suffix.slice(1, 32)}`,
partitionToken: `p${[...suffix].reverse().join("").slice(0, 31)}`,
accountScope: "ORIGIN_SHARED" as const,
});
const storagePolicy = Object.freeze({
owner: "local-draft-feature",
namespace: "local-draft",
classification: "INTERNAL" as const,
authority: "LOCAL_FIRST" as const,
accountScope: "ORIGIN_SHARED" as const,
retention: Object.freeze({ kind: "EXPLICIT_DELETE" as const }),
softBudgetBytes: 1_000_000,
hardBudgetBytes: 2_000_000,
evictionPriority: "USER_AUTHORED" as const,
logoutAction: "KEEP_ORIGIN_SHARED" as const,
accountDeletionAction: "KEEP_ORIGIN_SHARED" as const,
pressureAction: "RETAIN" as const,
unavailableFallback: "READ_ONLY" as const,
});
const config: IndexedDbRuntimeDependencies<
LocalDraft,
LocalDraft,
never
> = {
scope,
storagePolicy,
schemaVersion: 1,
recordStore: "records",
governanceStore: "governance",
retentionStore: "retention",
retentionEligibilityIndex: "by-eligibility",
lifecycleMetadataStores: [],
idempotencyStore: "receipts",
idempotencyExpiryIndex: "by-expiry",
receiptRetentionMs: 60_000,
maxIdempotencyReceipts: 100,
migrations: [
{
id: "local-draft-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",
},
],
},
],
},
],
codec: {
currentVersion: 1,
encode: (value) => ({ ok: true, value }),
measureStoredBytes: (value) =>
new TextEncoder().encode(JSON.stringify(value)).byteLength,
decode: (version, value) => {
if (
version !== 1 ||
value === null ||
typeof value !== "object"
) {
return { ok: false };
}
const candidate = value as Partial<LocalDraft>;
return typeof candidate.draftId === "string" &&
typeof candidate.title === "string" &&
typeof candidate.body === "string"
? { ok: true, value: candidate as LocalDraft }
: { ok: false };
},
fingerprint: async (value) => {
const bytes = new TextEncoder().encode(
JSON.stringify([value.draftId, value.title, value.body]),
);
const digest = await crypto.subtle.digest("SHA-256", bytes);
return Array.from(
new Uint8Array(digest),
(byte) => byte.toString(16).padStart(2, "0"),
).join("");
},
},
queryPolicy: {
plan: () => ({ limit: 1 }),
},
authorizeLifecycle: () => ({
authorized: true,
proofToken: "localdraftauthorityproof_001",
}),
};
const runtime = createIndexedDbRuntime(config);
const databaseName = indexedDbPhysicalDatabaseName(scope);
const opened = await runtime.open();
try {
const indexedDb = createIndexedDbRepositoryProvider(
Object.freeze({ "local-draft": runtime }),
);
const composed = composeFeatureAdapterInputs(
Object.freeze([LOCAL_DRAFT_FEATURE_ADAPTER_CONTRIBUTION] as const),
Object.freeze(["local-draft"]),
Object.freeze({ indexedDb }),
);
const localDraft = composed["local-draft"];
if (!localDraft) {
throw new Error("local-draft feature input was not composed");
}
const draft = Object.freeze({
draftId: "draft-1",
title: "Architecture notes",
body: "Stored through the reusable IndexedDB capability.",
});
const saved = await localDraft.saveDraft({
draft,
expectedRevision: null,
idempotencyKey: "local-draft-create-1",
});
const found = await localDraft.findDraft(draft.draftId);
const removed = await localDraft.removeDraft({
draftId: draft.draftId,
expectedRevision: saved.ok ? saved.value.revision : 1,
idempotencyKey: "local-draft-remove-1",
});
const afterRemove = await localDraft.findDraft(draft.draftId);
return {
opened,
saved,
found,
removed,
afterRemove,
composedFeatureIds: Object.keys(composed),
runtimeStatus: runtime.getStatus(),
};
} finally {
runtime.close();
await new Promise<void>((resolve, reject) => {
const request = indexedDB.deleteDatabase(databaseName);
request.onsuccess = () => resolve();
request.onerror = () =>
reject(request.error ?? new Error("IndexedDB cleanup failed"));
request.onblocked = () =>
reject(new Error("IndexedDB cleanup was blocked"));
});
}
});
expect(result.opened).toEqual({ ok: true, value: undefined });
expect(result.composedFeatureIds).toEqual(["local-draft"]);
expect(result.saved).toEqual({ ok: true, value: { revision: 1 } });
expect(result.found).toMatchObject({
ok: true,
value: {
draft: {
draftId: "draft-1",
title: "Architecture notes",
body: "Stored through the reusable IndexedDB capability.",
},
revision: 1,
},
});
expect(result.removed).toEqual({ ok: true, value: undefined });
expect(result.afterRemove).toEqual({ ok: true, value: null });
expect(result.runtimeStatus).toMatchObject({
kind: "READY",
schemaVersion: 1,
});
});
@@ -1,29 +1,48 @@
import { expect, test } from "../support/browser/strict-browser-test.ts";
test("executes the storage durability adapter against the real browser StorageManager", async ({
test("executes the storage durability adapter against the real browser StorageManager capability", async ({
page,
}) => {
await page.goto("/");
const result = await page.evaluate(async () => {
const observation = await page.evaluate(async () => {
const modulePath =
"/src/adapters/browser-file-storage/storage-manager-adapter.ts";
const { createStorageDurabilityAdapter } = await import(
/* @vite-ignore */ modulePath
);
const hasStorageManager = navigator.storage !== undefined;
const adapter = createStorageDurabilityAdapter(navigator.storage);
return adapter.inspect();
return {
hasStorageManager,
result: await adapter.inspect(),
};
});
expect(result.ok).toBe(true);
if (result.ok) {
if (!observation.hasStorageManager) {
expect(observation.result).toEqual({
ok: false,
error: {
code: "UNSUPPORTED",
operation: "STORAGE_ESTIMATE",
retryable: false,
recovery: "ONLINE_ONLY",
},
});
return;
}
expect(observation.result.ok).toBe(true);
if (observation.result.ok) {
expect(["UNKNOWN", "NORMAL", "PRESSURE", "CRITICAL"]).toContain(
result.value.pressure,
observation.result.value.pressure,
);
expect(
result.value.usageBytes === null || result.value.usageBytes >= 0,
observation.result.value.usageBytes === null ||
observation.result.value.usageBytes >= 0,
).toBe(true);
expect(
result.value.quotaBytes === null || result.value.quotaBytes >= 0,
observation.result.value.quotaBytes === null ||
observation.result.value.quotaBytes >= 0,
).toBe(true);
}
});
+3 -1
View File
@@ -71,7 +71,9 @@ describe("generic application router", () => {
await screen.findByRole("heading", { name: "UI 구성요소", level: 1 }),
).toBeVisible();
expect(window.location.pathname).toBe("/examples/ui");
expect(document.title).toBe("UI 구성요소 · Frontend Skeleton");
await waitFor(() =>
expect(document.title).toBe("UI 구성요소 · Frontend Skeleton"),
);
await waitFor(() =>
expect(
screen.getByRole("heading", { name: "UI 구성요소", level: 1 }),
@@ -0,0 +1,122 @@
import { describe, expect, it } from "vitest";
import type {
IndexedDbRepositoryPort,
} from "../../../src/application/ports/browser-file-storage/index.ts";
import type {
InstalledHttpOperationExecutor,
} from "../../../src/adapters/http/index.ts";
import {
composeFeatureAdapterInputs,
defineFeatureAdapterContribution,
type IndexedDbRepositoryProvider,
} from "../../../src/features/feature-adapter-contribution.ts";
import {
LOCAL_DRAFT_FEATURE_ADAPTER_CONTRIBUTION,
} from "../../../src/features/local-draft-feature/adapters/create-local-draft-feature-input.ts";
import type { LocalDraft } from "../../../src/features/local-draft-feature/domain/local-draft.ts";
type TestHttpFeatureInput = Readonly<{
ping(): Promise<"pong">;
}>;
declare module "../../../src/application/ports/in/application-api.ts" {
interface ApplicationFeatureInputs {
"test-http-feature": TestHttpFeatureInput;
}
}
const TEST_HTTP_FEATURE_ADAPTER_CONTRIBUTION =
defineFeatureAdapterContribution({
featureId: "test-http-feature",
needs: ["http"] as const,
createInput() {
return Object.freeze({
featureId: "test-http-feature" as const,
input: Object.freeze({
async ping() {
return "pong" as const;
},
}),
});
},
});
function localDraftRepository() {
let stored: Readonly<{ value: LocalDraft; revision: number }> | null = null;
const repository = Object.freeze({
async compareAndSwap(input: Readonly<{ value: LocalDraft }>) {
stored = Object.freeze({ value: input.value, revision: 1 });
return Object.freeze({
ok: true as const,
value: Object.freeze({ revision: 1 }),
});
},
async read() {
return Object.freeze({ ok: true as const, value: stored });
},
async remove() {
stored = null;
return Object.freeze({
ok: true as const,
value: Object.freeze({ revision: 2 }),
});
},
}) as unknown as IndexedDbRepositoryPort<LocalDraft, never>;
return repository;
}
describe("feature adapter composition", () => {
it("composes HTTP-only and IndexedDB-only concrete contributions together", async () => {
const repository = localDraftRepository();
const get = ((repositoryId: string) => {
expect(repositoryId).toBe("local-draft");
return repository;
}) as unknown as IndexedDbRepositoryProvider["get"];
const indexedDb = Object.freeze({ get }) satisfies IndexedDbRepositoryProvider;
const http = Object.freeze({
execute: async () => {
throw new Error("HTTP should not execute during composition");
},
}) as unknown as InstalledHttpOperationExecutor;
const inputs = composeFeatureAdapterInputs(
Object.freeze([
TEST_HTTP_FEATURE_ADAPTER_CONTRIBUTION,
LOCAL_DRAFT_FEATURE_ADAPTER_CONTRIBUTION,
] as const),
Object.freeze(["test-http-feature", "local-draft"]),
Object.freeze({ http, indexedDb }),
);
expect(Object.keys(inputs).sort()).toEqual([
"local-draft",
"test-http-feature",
]);
await expect(inputs["test-http-feature"]?.ping()).resolves.toBe("pong");
const localDraft = inputs["local-draft"];
if (!localDraft) throw new Error("local-draft input was not composed");
const draft = Object.freeze({
draftId: "draft-1",
title: "Local draft",
body: "Body",
});
await expect(
localDraft.saveDraft({
draft,
expectedRevision: null,
idempotencyKey: "save-draft-1",
}),
).resolves.toEqual({ ok: true, value: { revision: 1 } });
await expect(localDraft.findDraft("draft-1")).resolves.toEqual({
ok: true,
value: { draft, revision: 1 },
});
});
});
@@ -3,57 +3,129 @@ import { describe, expect, it, vi } from "vitest";
import {
createFeatureHttpBinding,
defineFeatureHttpOperation,
type HttpExecutionOutcome,
type InstalledHttpOperationExecutor,
} from "../../../src/adapters/http/index.ts";
import {
mappingFailure,
mappingSuccess,
} from "../../../src/contracts/boundary-mapper.ts";
import type {
InstalledHttpContract,
RuntimeValidator,
} from "../../../src/contracts/external-contract-runtime.ts";
type ResourceInput = Readonly<{ resourceId: string }>;
type ResourceWire = Readonly<{ id: string; title: string }>;
type ResourceProblem = Readonly<{ code?: string }>;
type Resource = Readonly<{ id: string; title: string }>;
const OPERATIONS = Object.freeze({
LOAD_RESOURCE: defineFeatureHttpOperation<
Readonly<{ resourceId: string }>,
Resource
>({
function validator<T>(schemaId: string): RuntimeValidator<T> {
return Object.freeze({
schemaId,
safeParse(value: unknown) {
return Object.freeze({
success: true as const,
data: value as T,
});
},
});
}
const LOAD_RESOURCE_CONTRACT = Object.freeze({
contract: Object.freeze({
operationId: "LOAD_RESOURCE",
method: "GET" as const,
pathTemplate: "/resources/{resourceId}",
inputValidator: validator<ResourceInput>("ResourceInput"),
outputValidator: validator<ResourceWire>("ResourceWire"),
problemValidator: validator<ResourceProblem>("ResourceProblem"),
acceptedStatuses: Object.freeze([200]),
emptyBodyStatuses: Object.freeze([]),
retrySemantics: "SAFE" as const,
requestBody: "NONE" as const,
responseBody: "REQUIRED_JSON" as const,
commandRecovery: null,
commandEffect: null,
projectRequest(input: ResourceInput) {
return Object.freeze({
pathValues: Object.freeze({ resourceId: input.resourceId }),
queryEntries: Object.freeze([]),
body: null,
});
},
}),
frontend: Object.freeze({
policyId: "TEST_LOAD_RESOURCE",
requestByteLimit: 0,
responseByteLimit: 8_192,
totalDeadlineMs: 1_000,
retryBudget: 0 as const,
authProfileId: "TEST",
diagnosticsOperation: "test.load-resource",
}),
}) satisfies InstalledHttpContract<ResourceInput, ResourceWire, ResourceProblem>;
const OPERATIONS = Object.freeze({
LOAD_RESOURCE: defineFeatureHttpOperation({
contract: LOAD_RESOURCE_CONTRACT,
routeId: "RESOURCE_DETAIL",
mapSuccess(value) {
if (
!value ||
typeof value !== "object" ||
typeof (value as Record<string, unknown>).id !== "string" ||
typeof (value as Record<string, unknown>).title !== "string"
) {
if (value.title.length === 0) {
return mappingFailure("MAPPING_INVARIANT_REJECTED");
}
const candidate = value as Readonly<{ id: string; title: string }>;
return mappingSuccess(
Object.freeze({ id: candidate.id, title: candidate.title }),
Object.freeze({ id: value.id, title: value.title }),
);
},
mapProblem(problem, metadata) {
if (metadata.status !== 409) return undefined;
return Object.freeze({
kind: "CONFLICT" as const,
code: problem.code ?? "RESOURCE_CONFLICT",
});
},
}),
} as const);
function scriptedExecutor<WireOutput, Problem>(
outcome: HttpExecutionOutcome<WireOutput, Problem>,
) {
const calls: Array<
Readonly<{
contract: unknown;
input: unknown;
context: unknown;
}>
> = [];
const implementation = async (
contract: unknown,
input: unknown,
context: unknown,
) => {
calls.push(Object.freeze({ contract, input, context }));
return outcome;
};
const execute = vi.fn(implementation) as unknown as
InstalledHttpOperationExecutor["execute"];
return {
executor: Object.freeze({ execute }) satisfies InstalledHttpOperationExecutor,
calls,
execute,
};
}
describe("feature HTTP binding", () => {
it("keeps typed feature input while platform owns route/context execution", async () => {
const execute = vi.fn<InstalledHttpOperationExecutor["execute"]>(
async (_operationId, input, context) => {
expect(input).toEqual({ resourceId: "resource-1" });
expect(context.routeId).toBe("RESOURCE_DETAIL");
return Object.freeze({
kind: "SUCCESS" as const,
value: Object.freeze({ id: "resource-1", title: "Reference" }),
metadata: Object.freeze({ status: 200 }),
effect: "NOT_APPLICABLE" as const,
});
},
);
const binding = createFeatureHttpBinding(
Object.freeze({ execute }),
OPERATIONS,
it("keeps contract-derived input while platform owns route/context execution", async () => {
const scripted = scriptedExecutor<ResourceWire, ResourceProblem>(
Object.freeze({
kind: "SUCCESS" as const,
value: Object.freeze({ id: "resource-1", title: "Reference" }),
metadata: Object.freeze({ status: 200 }),
effect: "NOT_APPLICABLE" as const,
}),
);
const binding = createFeatureHttpBinding(scripted.executor, OPERATIONS);
const result = await binding.execute("LOAD_RESOURCE", {
resourceId: "resource-1",
@@ -63,23 +135,27 @@ describe("feature HTTP binding", () => {
ok: true,
value: { id: "resource-1", title: "Reference" },
});
expect(execute).toHaveBeenCalledTimes(1);
expect(scripted.calls).toEqual([
{
contract: LOAD_RESOURCE_CONTRACT,
input: { resourceId: "resource-1" },
context: { routeId: "RESOURCE_DETAIL" },
},
]);
});
it("normalizes transport failure before it crosses the feature gateway", async () => {
const executor: InstalledHttpOperationExecutor = Object.freeze({
async execute() {
return Object.freeze({
kind: "TRANSPORT_FAILURE" as const,
failure: Object.freeze({
kind: "TIMEOUT" as const,
retryable: true,
}),
effect: "NOT_STARTED" as const,
});
},
});
const binding = createFeatureHttpBinding(executor, OPERATIONS);
const scripted = scriptedExecutor<ResourceWire, ResourceProblem>(
Object.freeze({
kind: "TRANSPORT_FAILURE" as const,
failure: Object.freeze({
kind: "TIMEOUT" as const,
retryable: true,
}),
effect: "NOT_STARTED" as const,
}),
);
const binding = createFeatureHttpBinding(scripted.executor, OPERATIONS);
const result = await binding.execute("LOAD_RESOURCE", {
resourceId: "resource-1",
@@ -92,18 +168,16 @@ describe("feature HTTP binding", () => {
expect(result.error.effect).toBe("NOT_STARTED");
});
it("turns feature mapper rejection into the shared mapping failure", async () => {
const executor: InstalledHttpOperationExecutor = Object.freeze({
async execute() {
return Object.freeze({
kind: "SUCCESS" as const,
value: Object.freeze({ unexpected: true }),
metadata: Object.freeze({ status: 200 }),
effect: "NOT_APPLICABLE" as const,
});
},
});
const binding = createFeatureHttpBinding(executor, OPERATIONS);
it("turns a domain mapper rejection into the shared mapping failure", async () => {
const scripted = scriptedExecutor<ResourceWire, ResourceProblem>(
Object.freeze({
kind: "SUCCESS" as const,
value: Object.freeze({ id: "resource-1", title: "" }),
metadata: Object.freeze({ status: 200 }),
effect: "NOT_APPLICABLE" as const,
}),
);
const binding = createFeatureHttpBinding(scripted.executor, OPERATIONS);
const result = await binding.execute("LOAD_RESOURCE", {
resourceId: "resource-1",
@@ -114,4 +188,29 @@ describe("feature HTTP binding", () => {
expect(result.error.kind).toBe("MAPPING_CONTRACT_VIOLATION");
expect(result.error.code).toBe("MAPPING_INVARIANT_REJECTED");
});
it("lets the feature interpret a typed business problem", async () => {
const scripted = scriptedExecutor<ResourceWire, ResourceProblem>(
Object.freeze({
kind: "PROBLEM" as const,
problem: Object.freeze({ code: "RESOURCE_NAME_EXISTS" }),
metadata: Object.freeze({ status: 409 }),
effect: "NOT_APPLIED" as const,
}),
);
const binding = createFeatureHttpBinding(scripted.executor, OPERATIONS);
const result = await binding.execute("LOAD_RESOURCE", {
resourceId: "resource-1",
});
expect(result).toMatchObject({
ok: false,
error: {
kind: "CONFLICT",
code: "RESOURCE_NAME_EXISTS",
httpStatus: 409,
},
});
});
});
@@ -32,7 +32,7 @@ describe("reference feature boundary contracts", () => {
effect: "MAYBE_APPLIED" as const,
}));
const installed = createReferenceFeatureInstalledInput({
contractOperations: { execute },
http: { execute },
});
await expect(
@@ -3,7 +3,6 @@ import { describe, expect, it, vi } from "vitest";
import { createContractHttpExecutor } from "../../../src/adapters/http/index.ts";
import { createHttpObservationProjector } from "../../../src/bootstrap/runtime-adapters.ts";
import { createReferenceFeatureInstalledInput } from "../../../src/features/reference-feature/adapters/create-reference-feature-input.ts";
import { REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION } from "../../../src/features/reference-feature/contracts/reference-feature-contract-contribution.ts";
function scopeSnapshot() {
return Object.freeze({
@@ -34,17 +33,9 @@ describe("reference feature HTTP diagnostics", () => {
telemetry: { emit: vi.fn() },
}),
});
const operations = new Map(
REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION.http.map((operation) => [
operation.contract.operationId,
operation,
]),
);
const installed = createReferenceFeatureInstalledInput({
contractOperations: Object.freeze({
async execute(operationId, input, context) {
const operation = operations.get(operationId);
if (!operation) throw new Error("Unregistered reference operation");
http: Object.freeze({
async execute(operation, input, context) {
return contractHttp.execute(operation, input, {
routeId: context.routeId,
scope: scopeSnapshot(),
@@ -0,0 +1,135 @@
import { describe, expect, it } from "vitest";
import {
createFeatureHttpBinding,
type InstalledHttpOperationExecutor,
} from "../../../src/adapters/http/index.ts";
import {
createReferenceHttpGateway,
REFERENCE_HTTP_OPERATIONS,
} from "../../../src/features/reference-feature/adapters/reference-http-gateway.ts";
function executorReturning(
outcome: Readonly<Record<string, unknown>>,
): InstalledHttpOperationExecutor {
const execute = (async () => outcome) as unknown as
InstalledHttpOperationExecutor["execute"];
return Object.freeze({ execute });
}
describe("reference HTTP business problem mapping", () => {
it("maps a typed 409 reference problem to the feature-owned business code", async () => {
const executor = executorReturning(
Object.freeze({
kind: "PROBLEM",
problem: Object.freeze({
type: "https://example.test/problems/reference-conflict",
title: "Reference conflict",
status: 409,
code: "REFERENCE_NAME_ALREADY_EXISTS",
}),
metadata: Object.freeze({ status: 409 }),
effect: "NOT_APPLIED",
}),
);
const gateway = createReferenceHttpGateway(
createFeatureHttpBinding(executor, REFERENCE_HTTP_OPERATIONS),
);
await expect(
gateway.create({ name: "duplicate" }),
).resolves.toMatchObject({
ok: false,
error: {
kind: "CONFLICT",
code: "REFERENCE_NAME_ALREADY_EXISTS",
httpStatus: 409,
},
});
});
it.each([
{
status: 409,
code: undefined,
expectedKind: "CONFLICT",
expectedCode: "REFERENCE_RESOURCE_CONFLICT",
},
{
status: 422,
code: "REFERENCE_NAME_REJECTED",
expectedKind: "VALIDATION_REJECTED",
expectedCode: "REFERENCE_NAME_REJECTED",
},
{
status: 422,
code: undefined,
expectedKind: "VALIDATION_REJECTED",
expectedCode: "REFERENCE_RESOURCE_REJECTED",
},
{
status: 400,
code: "REFERENCE_BAD_REQUEST",
expectedKind: "UNKNOWN_CLIENT_FAILURE",
expectedCode: "CONTRACT_PROBLEM",
},
] as const)(
"projects create problem status $status through the feature-owned mapper/fallback",
async ({ status, code, expectedKind, expectedCode }) => {
const executor = executorReturning(
Object.freeze({
kind: "PROBLEM",
problem: Object.freeze({
type: "https://example.test/problems/reference-" + status,
title: "Reference request rejected",
status,
...(code === undefined ? {} : { code }),
}),
metadata: Object.freeze({ status }),
effect: "NOT_APPLIED",
}),
);
const gateway = createReferenceHttpGateway(
createFeatureHttpBinding(executor, REFERENCE_HTTP_OPERATIONS),
);
await expect(
gateway.create({ name: "rejected" }),
).resolves.toMatchObject({
ok: false,
error: {
kind: expectedKind,
code: expectedCode,
httpStatus: status,
},
});
},
);
it("keeps the platform status fallback when the operation has no problem mapper", async () => {
const executor = executorReturning(
Object.freeze({
kind: "PROBLEM",
problem: Object.freeze({
type: "https://example.test/problems/not-found",
title: "Not found",
status: 404,
}),
metadata: Object.freeze({ status: 404 }),
effect: "NOT_APPLIED",
}),
);
const gateway = createReferenceHttpGateway(
createFeatureHttpBinding(executor, REFERENCE_HTTP_OPERATIONS),
);
await expect(gateway.get("missing")).resolves.toMatchObject({
ok: false,
error: {
kind: "NOT_FOUND",
code: "CONTRACT_PROBLEM",
httpStatus: 404,
},
});
});
});
@@ -1,5 +1,8 @@
import { describe, expect, it } from "vitest";
import {
type InstalledHttpOperationExecutor,
} from "../../../src/adapters/http/index.ts";
import { createReferenceFeatureInstalledInput } from "../../../src/features/reference-feature/adapters/create-reference-feature-input.ts";
/**
@@ -14,22 +17,31 @@ import { createReferenceFeatureInstalledInput } from "../../../src/features/refe
describe("reference feature installed operation executor", () => {
it("preserves the feature route id through the installed operation executor", async () => {
const seen: Array<Readonly<Record<string, unknown>>> = [];
const implementation = async (
contract: Readonly<{ contract: Readonly<{ operationId: string }> }>,
_input: unknown,
context: Readonly<Record<string, unknown>>,
) => {
seen.push(Object.freeze({ ...context }));
const value =
contract.contract.operationId === "LIST_REFERENCE_RESOURCES"
? []
: {
id: "resource-1",
name: "Resource",
};
return Object.freeze({
kind: "SUCCESS" as const,
value,
metadata: Object.freeze({ status: 200 }),
effect: "NOT_APPLICABLE" as const,
});
};
const execute = implementation as unknown as
InstalledHttpOperationExecutor["execute"];
const installed = createReferenceFeatureInstalledInput({
contractOperations: Object.freeze({
async execute(
_operationId: string,
_input: unknown,
context?: Readonly<Record<string, unknown>>,
) {
seen.push(Object.freeze({ ...(context ?? {}) }));
return Object.freeze({
kind: "SUCCESS" as const,
value: [],
metadata: Object.freeze({ status: 200 }),
effect: "NOT_APPLICABLE" as const,
});
},
}),
http: Object.freeze({ execute }),
});
await installed.input.listResources({ limit: 20 });
+1 -1
View File
@@ -35,7 +35,7 @@
"src/application/create-application.ts": {
"lines": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }, "statements": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }, "functions": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }, "branches": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }
},
"src/application/policies/compatibility.ts": {
"src/contracts/compatibility.ts": {
"lines": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }, "statements": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }, "functions": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }, "branches": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }
},
"src/application/policies/performance-budgets.ts": {
@@ -0,0 +1,33 @@
import type { InstalledHttpOperationExecutor } from "../../../src/adapters/http/index.ts";
import { composeFeatureAdapterInputs } from "../../../src/features/feature-adapter-contribution.ts";
declare module "../../../src/application/ports/in/application-api.ts" {
interface ApplicationFeatureInputs {
"direct-compose-probe": Readonly<{
ping(): "pong";
}>;
}
}
const malformedRawContribution = Object.freeze({
featureId: "direct-compose-probe" as const,
needs: ["http"] as const,
createInput() {
return Object.freeze({
featureId: "direct-compose-probe" as const,
input: Object.freeze({
ping() {
return "pong" as const;
},
}),
});
},
});
composeFeatureAdapterInputs(
Object.freeze([malformedRawContribution] as const),
Object.freeze(["direct-compose-probe"]),
Object.freeze({
http: null as unknown as InstalledHttpOperationExecutor,
}),
);
@@ -0,0 +1,13 @@
import { defineFeatureAdapterContribution } from "../../../src/features/feature-adapter-contribution.ts";
import { createReferenceFeatureInstalledInput } from "../../../src/features/reference-feature/adapters/create-reference-feature-input.ts";
defineFeatureAdapterContribution({
featureId: "reference-feature",
needs: ["http"] as const,
createInput(context) {
context.indexedDb;
return createReferenceFeatureInstalledInput({
http: context.http,
});
},
});
@@ -0,0 +1,15 @@
import { defineFeatureAdapterContribution } from "../../../src/features/feature-adapter-contribution.ts";
import type {} from "../../../src/features/reference-feature/application/reference-feature-api.ts";
defineFeatureAdapterContribution({
featureId: "reference-feature",
needs: ["http"] as const,
createInput() {
return {
featureId: "reference-feature" as const,
input: {
definitelyNotReferenceFeatureInput: true,
},
};
},
});
+19
View File
@@ -0,0 +1,19 @@
import {
createFeatureHttpBinding,
defineFeatureHttpOperation,
type InstalledHttpOperationExecutor,
} from "../../../src/adapters/http/index.ts";
import { GET_REFERENCE_RESOURCE_CONTRACT } from "../../../src/features/reference-feature/contracts/reference-feature-contract-contribution.ts";
import { mapReferenceResourcePayload } from "../../../src/features/reference-feature/contracts/reference-mapper.ts";
declare const executor: InstalledHttpOperationExecutor;
const operations = Object.freeze({
TOTALLY_WRONG_OPERATION_ID: defineFeatureHttpOperation({
contract: GET_REFERENCE_RESOURCE_CONTRACT,
routeId: "REFERENCE_RESOURCE_DETAIL",
mapSuccess: mapReferenceResourcePayload,
}),
});
createFeatureHttpBinding(executor, operations);
@@ -0,0 +1,7 @@
import type { ReferenceHttpBinding } from "../../../src/features/reference-feature/adapters/reference-http-gateway.ts";
declare const http: ReferenceHttpBinding;
http.execute("GET_REFERENCE_RESOURCE", {
name: "wrong-command-for-get",
});
+15
View File
@@ -0,0 +1,15 @@
import { defineFeatureHttpOperationForRoutes } from "../../../src/adapters/http/index.ts";
import {
type ReferenceFeatureRouteId,
} from "../../../src/features/reference-feature/adapters/reference-http-gateway.ts";
import { GET_REFERENCE_RESOURCE_CONTRACT } from "../../../src/features/reference-feature/contracts/reference-feature-contract-contribution.ts";
import { mapReferenceResourcePayload } from "../../../src/features/reference-feature/contracts/reference-mapper.ts";
const defineReferenceOperation =
defineFeatureHttpOperationForRoutes<ReferenceFeatureRouteId>();
defineReferenceOperation({
contract: GET_REFERENCE_RESOURCE_CONTRACT,
routeId: "TOTALLY_WRONG_ROUTE",
mapSuccess: mapReferenceResourcePayload,
});
+13
View File
@@ -0,0 +1,13 @@
import { defineFeatureHttpOperation } from "../../../src/adapters/http/index.ts";
import { GET_REFERENCE_RESOURCE_CONTRACT } from "../../../src/features/reference-feature/contracts/reference-feature-contract-contribution.ts";
defineFeatureHttpOperation({
contract: GET_REFERENCE_RESOURCE_CONTRACT,
routeId: "REFERENCE_RESOURCE_DETAIL",
mapSuccess(value: Readonly<{ unrelatedWireField: number }>) {
return {
ok: true as const,
value,
};
},
});
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-application-input.ts"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-application-output.ts"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-async-overlay.ts"
]
}
+15
View File
@@ -0,0 +1,15 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"types": [
"node",
"vite/client"
],
"isolatedModules": false
}
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-diagnostics-port.ts"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-direct-feature-composition.ts"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-failure-kind.ts"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-feature-capability-selection.ts"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-feature-contribution-input.ts"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-feature-input.ts"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-http-operation-id.ts"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-http-operation-input.ts"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-http-route-id.ts"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-http-wire-mapper.ts"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-icon-button.tsx"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-image-cdn-resolve-signal.ts"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-message-key.ts"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-message-params.ts"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-page-action.tsx"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-port-call.ts"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-port-implementation.ts"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-reference-operation.ts"
]
}
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-result-narrowing.ts"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.base.json",
"files": [
"./invalid-route-runtime.ts"
]
}
@@ -25,6 +25,13 @@ import { afterEach, describe, expect, it } from "vitest";
* longer. Raising the global default instead would hide a genuinely hung test.
*/
const PROCESS_HEAVY_TIMEOUT_MS = 30_000;
/**
* Test watchdog only. This is not a provider protocol deadline. Child-process
* scheduling can be delayed when the repository suite is under load, so the
* watchdog must not encode a one-second protocol requirement that does not
* exist in production.
*/
const PROCESS_HANDSHAKE_WATCHDOG_MS = 5_000;
const roots: string[] = [];
@@ -216,7 +223,7 @@ describe("provider guardian transaction protocol", () => {
}));
const ready = decodeProviderGuardianReady(
await within(readyPayload, 1_000, "guardian READY"),
await within(readyPayload, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian READY"),
nonce,
);
const raw = await lstat(rawPath);
@@ -231,7 +238,7 @@ describe("provider guardian transaction protocol", () => {
.toEqual({ dev: ready.sealedDev, ino: ready.sealedIno, mode: 0o600 });
child.stdin!.end();
await expect(within(completion, 1_000, "guardian abort")).resolves.toEqual({
await expect(within(completion, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian abort")).resolves.toEqual({
code: 125,
signal: null,
});
@@ -254,7 +261,7 @@ describe("provider guardian transaction protocol", () => {
if (input) child.stdin!.end(input);
else child.stdin!.end();
await expect(within(completion, 1_000, `${label} guardian EOF`)).resolves.toEqual({
await expect(within(completion, PROCESS_HANDSHAKE_WATCHDOG_MS, `${label} guardian EOF`)).resolves.toEqual({
code: expectedCode,
signal: null,
});
@@ -289,7 +296,7 @@ describe("provider guardian transaction protocol", () => {
await expect(within(
waitForChild(fixture.child),
1_000,
PROCESS_HANDSHAKE_WATCHDOG_MS,
`${boundKind} partial bootstrap exit`,
)).resolves.toEqual({ code: 126, signal: null });
const boundPath = boundKind === "raw" ? fixture.rawStagingPath : fixture.sealedTempPath;
@@ -320,7 +327,7 @@ describe("provider guardian transaction protocol", () => {
deadlineEpochMs: Date.now() + 2_000,
}));
await expect(within(completion, 1_000, "guardian parent startup death"))
await expect(within(completion, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian parent startup death"))
.resolves.toEqual({ code: 125, signal: null });
await expect(readdir(path.join(workspace, "provider-evidence/untrusted"))).resolves.toEqual([]);
await expect(readdir(path.join(workspace, "provider-evidence"))).resolves.toEqual(["untrusted"]);
@@ -348,7 +355,7 @@ describe("provider guardian transaction protocol", () => {
deadlineEpochMs: Date.now() + 3_000,
}));
const ready = decodeProviderGuardianReady(
await within(readyPayload, 1_000, "guardian READY"),
await within(readyPayload, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian READY"),
nonce,
);
const sealedTempPath = path.join(evidenceRoot, ready.sealedTempLeaf);
@@ -379,7 +386,7 @@ describe("provider guardian transaction protocol", () => {
sha256: createHash("sha256").update(bytes).digest("hex"),
}));
decodeProviderGuardianPublished(
await within(publishedPayload, 1_000, "guardian PUBLISHED"),
await within(publishedPayload, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian PUBLISHED"),
nonce,
{ dev: ready.sealedDev, ino: ready.sealedIno },
);
@@ -393,7 +400,7 @@ describe("provider guardian transaction protocol", () => {
await new Promise<void>((resolve) => setTimeout(resolve, 50));
expect(child.exitCode).toBeNull();
child.stdin!.end();
await expect(within(completion, 1_000, "guardian commit EOF")).resolves.toEqual({
await expect(within(completion, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian commit EOF")).resolves.toEqual({
code: 0,
signal: null,
});
@@ -456,7 +463,7 @@ describe("provider guardian transaction protocol", () => {
const transaction = await establishPublishedGuardian(workspace, Buffer.alloc(32, 0x31));
transaction.child.stdin!.end();
await expect(within(transaction.completion, 1_000, "published guardian EOF"))
await expect(within(transaction.completion, PROCESS_HANDSHAKE_WATCHDOG_MS, "published guardian EOF"))
.resolves.toEqual({ code: 125, signal: null });
await assertTransactionAbsent(transaction);
@@ -487,7 +494,7 @@ describe("provider guardian transaction protocol", () => {
transaction.child.stdin!.write(Buffer.from([0, 0, 0, 1, 0x7b]));
transaction.child.stdin!.end();
await expect(within(transaction.completion, 1_000, "guardian trailing frame"))
await expect(within(transaction.completion, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian trailing frame"))
.resolves.toEqual({ code: 126, signal: null });
await assertTransactionAbsent(transaction);
});
@@ -505,7 +512,7 @@ describe("provider guardian transaction protocol", () => {
});
process.kill(lease.pid, "SIGKILL");
await expect(within(lease.prematureExit, 1_000, "guardian hard death"))
await expect(within(lease.prematureExit, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian hard death"))
.resolves.toEqual(expect.objectContaining({ message: expect.stringMatching(/SIGKILL/u) }));
await lease.abort();
await expect(lstat(lease.rawPath)).rejects.toMatchObject({ code: "ENOENT" });
@@ -536,8 +543,8 @@ describe("provider guardian transaction protocol", () => {
});
process.kill(guardianPid, "SIGCONT");
await within(rawLink, 1_000, "pre-READY canonical raw link");
await expect(within(starting, 2_000, "pre-READY guardian rejection"))
await within(rawLink, PROCESS_HANDSHAKE_WATCHDOG_MS, "pre-READY canonical raw link");
await expect(within(starting, PROCESS_HANDSHAKE_WATCHDOG_MS, "pre-READY guardian rejection"))
.rejects.toThrow(/provider guardian/u);
const possibleTempLeaves = (await readdir(evidenceRoot)).filter((leaf) =>
leaf.startsWith(".vulnerability-report.json.guardian-")
@@ -587,7 +594,7 @@ describe("provider guardian transaction protocol", () => {
await canaryHandle.close();
process.kill(guardianPid, "SIGKILL");
await expect(within(starting, 2_000, "external-canary guardian rejection"))
await expect(within(starting, PROCESS_HANDSHAKE_WATCHDOG_MS, "external-canary guardian rejection"))
.rejects.toThrow(/provider guardian/u);
expect(await readFile(rawPath)).toEqual(canaryBytes);
expect(await lstat(rawPath)).toMatchObject({
@@ -664,14 +671,14 @@ describe("provider guardian transaction protocol", () => {
deadlineEpochMs: Date.now() + 3_000,
}));
const ready = decodeProviderGuardianReady(
await within(readyPayload, 1_000, "closed-stderr guardian READY"),
await within(readyPayload, PROCESS_HANDSHAKE_WATCHDOG_MS, "closed-stderr guardian READY"),
nonce,
);
child.stderr!.destroy();
await new Promise<void>((resolve) => child.stderr!.once("close", resolve));
child.stdin!.end();
await expect(within(completion, 1_000, "closed-stderr guardian exit"))
await expect(within(completion, PROCESS_HANDSHAKE_WATCHDOG_MS, "closed-stderr guardian exit"))
.resolves.toEqual({ code: 125, signal: null });
await expect(lstat(path.join(rawDirectory, "vulnerability-report.json")))
.rejects.toMatchObject({ code: "ENOENT" });
@@ -697,12 +704,12 @@ describe("provider guardian transaction protocol", () => {
deadlineEpochMs: Date.now() + 350,
}));
const ready = decodeProviderGuardianReady(
await within(readyPayload, 1_000, "deadline guardian READY"),
await within(readyPayload, PROCESS_HANDSHAKE_WATCHDOG_MS, "deadline guardian READY"),
nonce,
);
const sealedTempPath = path.join(evidenceRoot, ready.sealedTempLeaf);
await expect(within(completion, 1_000, "guardian deadline"))
await expect(within(completion, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian deadline"))
.resolves.toEqual({ code: null, signal: "SIGKILL" });
await expect(lstat(rawPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(lstat(sealedTempPath)).rejects.toMatchObject({ code: "ENOENT" });
@@ -874,7 +881,7 @@ async function establishPublishedGuardian(
deadlineEpochMs: Date.now() + 3_000,
}));
const ready = decodeProviderGuardianReady(
await within(readyPayload, 1_000, "guardian READY"),
await within(readyPayload, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian READY"),
nonce,
);
const sealedTempPath = path.join(evidenceRoot, ready.sealedTempLeaf);
@@ -896,7 +903,7 @@ async function establishPublishedGuardian(
sha256: createHash("sha256").update(bytes).digest("hex"),
}));
decodeProviderGuardianPublished(
await within(publishedPayload, 1_000, "guardian PUBLISHED"),
await within(publishedPayload, PROCESS_HANDSHAKE_WATCHDOG_MS, "guardian PUBLISHED"),
nonce,
{ dev: ready.sealedDev, ino: ready.sealedIno },
);
+47
View File
@@ -200,6 +200,53 @@ describe("bounded body reader", () => {
});
});
it("abandons a pending bounded read when the operation is aborted", async () => {
const controller = new AbortController();
const reader = {
read: vi.fn(() => new Promise<never>(() => {})),
cancel: vi.fn().mockRejectedValue(new Error("cancel ignored")),
releaseLock: vi.fn(),
};
const response = {
headers: new Headers(),
body: { getReader: () => reader },
} as unknown as Response;
const pending = readBoundedBytes(response, 8, controller.signal);
controller.abort();
await expect(pending).resolves.toEqual({
ok: false,
code: "RESPONSE_STREAM_FAILURE",
});
expect(reader.cancel).toHaveBeenCalledOnce();
expect(reader.releaseLock).toHaveBeenCalledOnce();
});
it("abandons a pending forbidden-body probe when the operation is aborted", async () => {
const controller = new AbortController();
const reader = {
read: vi.fn(() => new Promise<never>(() => {})),
cancel: vi.fn().mockRejectedValue(new Error("cancel ignored")),
releaseLock: vi.fn(),
};
const response = {
headers: new Headers(),
body: { getReader: () => reader },
} as unknown as Response;
const pending = probeForbiddenBody(response, controller.signal);
controller.abort();
await expect(pending).resolves.toEqual({
ok: false,
code: "RESPONSE_STREAM_FAILURE",
});
expect(reader.cancel).toHaveBeenCalledOnce();
expect(reader.releaseLock).toHaveBeenCalledOnce();
});
it("decodes valid JSON and distinguishes UTF-8 from JSON failures", () => {
expect(decodeJsonBytes(new TextEncoder().encode('{"ok":true}'))).toEqual({
ok: true,
+22 -16
View File
@@ -73,12 +73,17 @@ function labelledFileInput(): HTMLInputElement {
describe("browser file pickers", () => {
it("treats native input cancellation as a normal dismissed outcome", async () => {
const input = labelledFileInput();
Object.defineProperty(input, "showPicker", {
Object.defineProperty(input, "click", {
configurable: true,
value: vi.fn(() => {
queueMicrotask(() => input.dispatchEvent(new Event("cancel")));
}),
});
const baselineShowPicker = vi.fn();
Object.defineProperty(input, "showPicker", {
configurable: true,
value: baselineShowPicker,
});
const harness = createHarness();
const picker = new NativeInputFilePicker({
input,
@@ -90,6 +95,7 @@ describe("browser file pickers", () => {
ok: true,
value: { kind: "DISMISSED" },
});
expect(baselineShowPicker).not.toHaveBeenCalled();
});
it("resets the native input and supports same-file reselection", async () => {
@@ -102,12 +108,12 @@ describe("browser file pickers", () => {
configurable: true,
value: [selected],
});
const showPicker = vi.fn(() => {
const click = vi.fn(() => {
queueMicrotask(() => input.dispatchEvent(new Event("change")));
});
Object.defineProperty(input, "showPicker", {
Object.defineProperty(input, "click", {
configurable: true,
value: showPicker,
value: click,
});
let sequence = 0;
const { policies, vault } = createHarness(policyDefinition, {
@@ -130,14 +136,14 @@ describe("browser file pickers", () => {
ok: true,
value: { kind: "SELECTED" },
});
expect(showPicker).toHaveBeenCalledTimes(2);
expect(click).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", {
Object.defineProperty(input, "click", {
configurable: true,
value: vi.fn(),
});
@@ -186,7 +192,7 @@ describe("browser file pickers", () => {
it("honors AbortSignal while a native dialog is pending", async () => {
const input = labelledFileInput();
Object.defineProperty(input, "showPicker", {
Object.defineProperty(input, "click", {
configurable: true,
value: vi.fn(),
});
@@ -215,7 +221,7 @@ describe("browser file pickers", () => {
let fallback: (() => void) | undefined;
let delay: number | undefined;
const clearTimeout = vi.fn();
Object.defineProperty(input, "showPicker", {
Object.defineProperty(input, "click", {
configurable: true,
value: vi.fn(() => {
window.dispatchEvent(new Event("focus"));
@@ -261,7 +267,7 @@ describe("browser file pickers", () => {
const input = labelledFileInput();
let fallback: (() => void) | undefined;
const clearTimeout = vi.fn();
Object.defineProperty(input, "showPicker", {
Object.defineProperty(input, "click", {
configurable: true,
value: vi.fn(() => {
window.dispatchEvent(new Event("focus"));
@@ -316,14 +322,14 @@ describe("browser file pickers", () => {
setTimeout: originalSetTimeout,
clearTimeout: originalClearTimeout,
};
const originalShowPicker = vi.fn(() => {
const originalClick = vi.fn(() => {
focus?.(new Event("focus"));
});
const replacedShowPicker = vi.fn();
Object.defineProperty(input, "showPicker", {
const replacedClick = vi.fn();
Object.defineProperty(input, "click", {
configurable: true,
writable: true,
value: originalShowPicker,
value: originalClick,
});
const harness = createHarness();
const picker = new NativeInputFilePicker({
@@ -338,7 +344,7 @@ describe("browser file pickers", () => {
focusFallbackGraceMs: 0,
});
input.showPicker = replacedShowPicker;
input.click = replacedClick;
windowHost.addEventListener = vi.fn();
windowHost.removeEventListener = vi.fn();
scheduler.setTimeout = vi.fn();
@@ -348,8 +354,8 @@ describe("browser file pickers", () => {
ok: true,
value: { kind: "DISMISSED" },
});
expect(originalShowPicker).toHaveBeenCalledOnce();
expect(replacedShowPicker).not.toHaveBeenCalled();
expect(originalClick).toHaveBeenCalledOnce();
expect(replacedClick).not.toHaveBeenCalled();
expect(originalWindowAdd).toHaveBeenCalledOnce();
expect(originalWindowRemove).toHaveBeenCalledOnce();
expect(originalSetTimeout).toHaveBeenCalledOnce();
+1 -1
View File
@@ -178,7 +178,7 @@ describe("browser file runtime hard limits and disposal", () => {
it("aborts a pending native picker and prevents event resurrection", async () => {
const input = fileInput();
Object.defineProperty(input, "showPicker", {
Object.defineProperty(input, "click", {
configurable: true,
value: vi.fn(),
});
+3 -3
View File
@@ -189,9 +189,9 @@ describe("CI gate contract", () => {
),
);
expect(contract.jobs).toHaveLength(9);
expect(contract.commands).toHaveLength(84);
expect(contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0)).toBe(96);
expect(contract.commands.filter(({ expect }) => expect === "fail")).toHaveLength(23);
expect(contract.commands).toHaveLength(91);
expect(contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0)).toBe(103);
expect(contract.commands.filter(({ expect }) => expect === "fail")).toHaveLength(30);
expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(88);
expect(contract.artifacts).toHaveLength(109);
expect(contract.stages).toHaveLength(5);
+18 -16
View File
@@ -16,7 +16,7 @@ import {
} from "../../src/features/installed-feature-contracts.ts";
const COMPILED = Object.freeze([
Object.freeze({ featureId: "reference-feature" }),
Object.freeze({ featureId: "alpha-feature" }),
Object.freeze({ featureId: "billing" }),
]);
@@ -26,7 +26,7 @@ describe("build-time product selection", () => {
expect(
selectCompiledProductFeatures(COMPILED, declared).map((f) => f.featureId),
String(declared),
).toEqual(["reference-feature", "billing"]);
).toEqual(["alpha-feature", "billing"]);
}
});
@@ -35,10 +35,10 @@ describe("build-time product selection", () => {
selectCompiledProductFeatures(COMPILED, "billing").map((f) => f.featureId),
).toEqual(["billing"]);
expect(
selectCompiledProductFeatures(COMPILED, " billing , reference-feature ").map(
selectCompiledProductFeatures(COMPILED, " billing , alpha-feature ").map(
(f) => f.featureId,
),
).toEqual(["reference-feature", "billing"]);
).toEqual(["alpha-feature", "billing"]);
});
it("selects nothing only when asked explicitly", () => {
@@ -79,13 +79,13 @@ describe("build-time product selection", () => {
describe("runtime product feature resolution", () => {
it("reports active, disabled and not-installed distinctly", () => {
const statuses = resolveProductFeatures(
["reference-feature", "billing"],
["reference-feature"],
{ "reference-feature": "DISABLED" },
["alpha-feature", "billing"],
["alpha-feature"],
{ "alpha-feature": "DISABLED" },
);
expect(statuses).toEqual([
{ featureId: "alpha-feature", state: "DISABLED_BY_CONFIG" },
{ featureId: "billing", state: "NOT_INSTALLED" },
{ featureId: "reference-feature", state: "DISABLED_BY_CONFIG" },
]);
expect(activeProductFeatureIds(statuses)).toEqual([]);
});
@@ -103,11 +103,11 @@ describe("runtime product feature resolution", () => {
// A shared runtime document may cover several builds, so a stale key is
// inert rather than fatal.
const statuses = resolveProductFeatures(
["reference-feature"],
["reference-feature"],
["alpha-feature"],
["alpha-feature"],
{ analytics: "DISABLED" },
);
expect(activeProductFeatureIds(statuses)).toEqual(["reference-feature"]);
expect(activeProductFeatureIds(statuses)).toEqual(["alpha-feature"]);
});
it("leaves an installed feature active without an override", () => {
@@ -140,15 +140,15 @@ describe("runtime config carries the switch", () => {
expect(
runtimeConfigV2ArtifactSchema.parse({
...base,
FEATURE_OVERRIDES: { "reference-feature": "DISABLED" },
FEATURE_OVERRIDES: { "alpha-feature": "DISABLED" },
}).FEATURE_OVERRIDES,
).toEqual({ "reference-feature": "DISABLED" });
).toEqual({ "alpha-feature": "DISABLED" });
// There is no "ENABLED": the vocabulary itself is what makes the rule
// unbreakable, not a check somewhere downstream.
expect(
runtimeConfigV2ArtifactSchema.safeParse({
...base,
FEATURE_OVERRIDES: { "reference-feature": "ENABLED" },
FEATURE_OVERRIDES: { "alpha-feature": "ENABLED" },
}).success,
).toBe(false);
});
@@ -212,11 +212,13 @@ describe("route ownership", () => {
}
});
it("owns exactly the routes the registry received from features", () => {
it("owns exactly routes that exist in the composed registry", () => {
const owned = Object.keys(ROUTE_FEATURE_OWNER);
expect(owned.length).toBeGreaterThan(0);
for (const routeId of owned) {
expect(Object.keys(ROUTE_REGISTRY), routeId).toContain(routeId);
}
expect(
owned.every((routeId) => ROUTE_FEATURE_OWNER[routeId] !== undefined),
).toBe(true);
});
});
+137
View File
@@ -0,0 +1,137 @@
import type {
PublicCacheAsset,
PublicCacheReleaseManifest,
} from "../../src/application/ports/browser-file-storage/cache-storage-ports.ts";
import {
createDefaultPublicCachePolicy,
type PublicCacheRuntimePolicy,
} from "../../src/adapters/cache-storage/public-cache-policy.ts";
import {
computePublicCacheManifestDigestHex,
type PublicCacheMutationLock,
} from "../../src/adapters/cache-storage/public-response-cache-adapter.ts";
export class MemoryCache {
readonly responses: Array<Readonly<{
request: Request;
response: Response;
}>> = [];
async match(request: RequestInfo | URL): Promise<Response | undefined> {
const url =
request instanceof Request ? request.url : new URL(String(request)).href;
const nativeRequest =
request instanceof Request ? request : new Request(url);
return this.responses
.find(
(entry) =>
entry.request.url === url &&
varyMatches(entry.request, nativeRequest, entry.response),
)
?.response.clone();
}
async put(request: RequestInfo | URL, response: Response): Promise<void> {
const url =
request instanceof Request ? request.url : new URL(String(request)).href;
const nativeRequest =
request instanceof Request ? request.clone() : new Request(url);
const existing = this.responses.findIndex(
(entry) =>
entry.request.url === url &&
varyMatches(entry.request, nativeRequest, response),
);
const entry = {
request: nativeRequest,
response: response.clone(),
};
if (existing >= 0) this.responses.splice(existing, 1, entry);
else this.responses.push(entry);
}
}
function varyMatches(
storedRequest: Request,
incomingRequest: Request,
response: Response,
): boolean {
const vary = response.headers.get("vary");
if (!vary) return true;
return vary
.split(",")
.map((name) => name.trim().toLowerCase())
.every(
(name) =>
storedRequest.headers.get(name) ===
incomingRequest.headers.get(name),
);
}
export class MemoryCacheStorage {
readonly caches = new Map<string, MemoryCache>();
async open(name: string): Promise<Cache> {
let cache = this.caches.get(name);
if (!cache) {
cache = new MemoryCache();
this.caches.set(name, cache);
}
return cache as unknown as Cache;
}
async keys(): Promise<string[]> {
return [...this.caches.keys()];
}
async delete(name: string): Promise<boolean> {
return this.caches.delete(name);
}
}
export const immediateLock: PublicCacheMutationLock = {
async run(_signal, task) {
return await task();
},
};
export function deferred<Value>() {
let settle: ((value: Value) => void) | undefined;
const promise = new Promise<Value>((resolve) => {
settle = resolve;
});
return Object.freeze({
promise,
resolve(value: Value): void {
settle?.(value);
},
});
}
export async function digestHex(bytes: Uint8Array): Promise<string> {
const digest = await globalThis.crypto.subtle.digest(
"SHA-256",
Uint8Array.from(bytes),
);
return [...new Uint8Array(digest)]
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
}
export async function manifestFor(
releaseRegistryId: string,
assets: readonly PublicCacheAsset[],
policy: PublicCacheRuntimePolicy = createDefaultPublicCachePolicy(
"https://assets.example.test",
),
): Promise<PublicCacheReleaseManifest> {
return {
releaseRegistryId,
assets,
manifestDigestHex: await computePublicCacheManifestDigestHex(
globalThis.crypto,
releaseRegistryId,
assets,
policy,
),
};
}
@@ -0,0 +1,207 @@
import { describe, expect, it } from "vitest";
import type { PublicCacheAsset } from "../../src/application/ports/browser-file-storage/cache-storage-ports.ts";
import { createDefaultPublicCachePolicy } from "../../src/adapters/cache-storage/public-cache-policy.ts";
import { createPublicResponseCacheAdapter } from "../../src/adapters/cache-storage/public-response-cache-adapter.ts";
import {
MemoryCacheStorage,
digestHex,
immediateLock,
manifestFor,
} from "./public-response-cache-fixture.ts";
/**
* STO-RR-04 / STO-RR-05. A release cache that is currently serving traffic is
* the last thing a repair may destroy. A transient marker read failure is not
* evidence of damage, and a repair that has not yet fetched anything has not
* yet earned the right to delete what still works.
*/
describe("public response cache repair is failure-atomic", () => {
async function stagedRelease(releaseRegistryId: string) {
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const firstBytes = new Uint8Array([1, 1, 1, 1]);
const secondBytes = new Uint8Array([2, 2, 2, 2]);
const assets: readonly PublicCacheAsset[] = [
{
absoluteUrl: "https://assets.example.test/first.js",
expectedByteLength: firstBytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(firstBytes),
},
},
{
absoluteUrl: "https://assets.example.test/second.js",
expectedByteLength: secondBytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(secondBytes),
},
},
];
const bodies = new Map<string, Uint8Array>([
[assets[0]!.absoluteUrl, firstBytes],
[assets[1]!.absoluteUrl, secondBytes],
]);
const cacheStorage = new MemoryCacheStorage();
const fetchLog: string[] = [];
let failFrom: string | null = null;
const adapter = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: immediateLock,
policy,
fetcher: async (request: Request) => {
fetchLog.push(request.url);
if (failFrom !== null && request.url === failFrom) {
throw new TypeError("network is down");
}
const body = bodies.get(request.url);
if (!body) throw new TypeError(`unknown asset ${request.url}`);
return new Response(Uint8Array.from(body), {
headers: {
"cache-control": "public",
"content-type": "application/javascript",
},
});
},
});
const manifest = await manifestFor(releaseRegistryId, assets, policy);
expect(await adapter.admin.stageRelease(manifest)).toMatchObject({
ok: true,
});
expect(
await adapter.admin.activateRelease(
manifest.releaseRegistryId,
manifest.manifestDigestHex,
),
).toMatchObject({ ok: true });
const cacheName = [...cacheStorage.caches.keys()].find((name) =>
name.includes(releaseRegistryId),
);
if (!cacheName) throw new Error("staged cache missing");
return {
adapter,
assets,
cacheName,
cacheStorage,
fetchLog,
manifest,
setFailure(url: string | null) {
failFrom = url;
},
};
}
it("does not delete an active candidate when the marker read fails transiently", async () => {
const release = await stagedRelease("transient-marker");
const cache = release.cacheStorage.caches.get(release.cacheName)!;
const realMatch = cache.match.bind(cache);
let markerReads = 0;
const assetUrls = new Set(release.assets.map((asset) => asset.absoluteUrl));
cache.match = async (request: RequestInfo | URL) => {
const url =
request instanceof Request ? request.url : String(request);
if (!assetUrls.has(url)) {
markerReads += 1;
throw new DOMException("Storage is busy", "InvalidStateError");
}
return await realMatch(request);
};
const restaged = await release.adapter.admin.stageRelease(release.manifest);
expect(markerReads).toBeGreaterThan(0);
expect(restaged.ok).toBe(false);
expect(release.cacheStorage.caches.has(release.cacheName)).toBe(true);
cache.match = realMatch;
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[0]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
});
it("keeps every healthy asset when one repair fetch fails", async () => {
const release = await stagedRelease("partial-repair");
const cache = release.cacheStorage.caches.get(release.cacheName)!;
// Corrupt only the first asset's stored bytes.
const corrupted = cache.responses.findIndex(
(entry) => entry.request.url === release.assets[0]!.absoluteUrl,
);
expect(corrupted).toBeGreaterThanOrEqual(0);
cache.responses.splice(corrupted, 1);
release.setFailure(release.assets[0]!.absoluteUrl);
const restaged = await release.adapter.admin.stageRelease(release.manifest);
expect(restaged.ok).toBe(false);
// The cache still exists and the healthy asset is still served.
expect(release.cacheStorage.caches.has(release.cacheName)).toBe(true);
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[1]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
});
it("still removes a candidate this call created when staging fails", async () => {
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const bytes = new Uint8Array([7, 7, 7, 7]);
const asset: PublicCacheAsset = {
absoluteUrl: "https://assets.example.test/fresh.js",
expectedByteLength: bytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(bytes),
},
};
const cacheStorage = new MemoryCacheStorage();
const adapter = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: immediateLock,
policy,
fetcher: async () => {
throw new TypeError("network is down");
},
});
const manifest = await manifestFor("fresh-release", [asset], policy);
expect(await adapter.admin.stageRelease(manifest)).toMatchObject({
ok: false,
});
expect(await cacheStorage.keys()).toEqual([]);
});
it("repairs an evicted asset in place and keeps the release usable", async () => {
const release = await stagedRelease("in-place-repair");
const cache = release.cacheStorage.caches.get(release.cacheName)!;
const evicted = cache.responses.findIndex(
(entry) => entry.request.url === release.assets[1]!.absoluteUrl,
);
cache.responses.splice(evicted, 1);
expect(
await release.adapter.admin.stageRelease(release.manifest),
).toMatchObject({ ok: true });
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[1]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[0]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
});
});
+7 -321
View File
@@ -10,135 +10,17 @@ import {
type PublicCacheRuntimePolicy,
} from "../../src/adapters/cache-storage/public-cache-policy.ts";
import {
computePublicCacheManifestDigestHex,
createPublicResponseCacheAdapter,
type PublicCacheMutationLock,
} from "../../src/adapters/cache-storage/public-response-cache-adapter.ts";
class MemoryCache {
readonly responses: Array<Readonly<{
request: Request;
response: Response;
}>> = [];
async match(request: RequestInfo | URL): Promise<Response | undefined> {
const url =
request instanceof Request ? request.url : new URL(String(request)).href;
const nativeRequest =
request instanceof Request ? request : new Request(url);
return this.responses
.find(
(entry) =>
entry.request.url === url &&
varyMatches(entry.request, nativeRequest, entry.response),
)
?.response.clone();
}
async put(request: RequestInfo | URL, response: Response): Promise<void> {
const url =
request instanceof Request ? request.url : new URL(String(request)).href;
const nativeRequest =
request instanceof Request ? request.clone() : new Request(url);
const existing = this.responses.findIndex(
(entry) =>
entry.request.url === url &&
varyMatches(entry.request, nativeRequest, response),
);
const entry = {
request: nativeRequest,
response: response.clone(),
};
if (existing >= 0) this.responses.splice(existing, 1, entry);
else this.responses.push(entry);
}
}
function varyMatches(
storedRequest: Request,
incomingRequest: Request,
response: Response,
): boolean {
const vary = response.headers.get("vary");
if (!vary) return true;
return vary
.split(",")
.map((name) => name.trim().toLowerCase())
.every(
(name) =>
storedRequest.headers.get(name) ===
incomingRequest.headers.get(name),
);
}
class MemoryCacheStorage {
readonly caches = new Map<string, MemoryCache>();
async open(name: string): Promise<Cache> {
let cache = this.caches.get(name);
if (!cache) {
cache = new MemoryCache();
this.caches.set(name, cache);
}
return cache as unknown as Cache;
}
async keys(): Promise<string[]> {
return [...this.caches.keys()];
}
async delete(name: string): Promise<boolean> {
return this.caches.delete(name);
}
}
const immediateLock: PublicCacheMutationLock = {
async run(_signal, task) {
return await task();
},
};
function deferred<Value>() {
let settle: ((value: Value) => void) | undefined;
const promise = new Promise<Value>((resolve) => {
settle = resolve;
});
return Object.freeze({
promise,
resolve(value: Value): void {
settle?.(value);
},
});
}
async function digestHex(bytes: Uint8Array): Promise<string> {
const digest = await globalThis.crypto.subtle.digest(
"SHA-256",
Uint8Array.from(bytes),
);
return [...new Uint8Array(digest)]
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
}
async function manifestFor(
releaseRegistryId: string,
assets: readonly PublicCacheAsset[],
policy: PublicCacheRuntimePolicy = createDefaultPublicCachePolicy(
"https://assets.example.test",
),
): Promise<PublicCacheReleaseManifest> {
return {
releaseRegistryId,
assets,
manifestDigestHex: await computePublicCacheManifestDigestHex(
globalThis.crypto,
releaseRegistryId,
assets,
policy,
),
};
}
import {
MemoryCacheStorage,
deferred,
digestHex,
immediateLock,
manifestFor,
} from "./public-response-cache-fixture.ts";
describe("public response Cache Storage adapter", () => {
it("rejects a policy that enables variants but strips Vary", () => {
@@ -1513,199 +1395,3 @@ describe("public response Cache Storage adapter", () => {
expect(await cacheStorage.keys()).toEqual([]);
});
});
/**
* STO-RR-04 / STO-RR-05. A release cache that is currently serving traffic is
* the last thing a repair may destroy. A transient marker read failure is not
* evidence of damage, and a repair that has not yet fetched anything has not
* yet earned the right to delete what still works.
*/
describe("public response cache repair is failure-atomic", () => {
async function stagedRelease(releaseRegistryId: string) {
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const firstBytes = new Uint8Array([1, 1, 1, 1]);
const secondBytes = new Uint8Array([2, 2, 2, 2]);
const assets: readonly PublicCacheAsset[] = [
{
absoluteUrl: "https://assets.example.test/first.js",
expectedByteLength: firstBytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(firstBytes),
},
},
{
absoluteUrl: "https://assets.example.test/second.js",
expectedByteLength: secondBytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(secondBytes),
},
},
];
const bodies = new Map<string, Uint8Array>([
[assets[0]!.absoluteUrl, firstBytes],
[assets[1]!.absoluteUrl, secondBytes],
]);
const cacheStorage = new MemoryCacheStorage();
const fetchLog: string[] = [];
let failFrom: string | null = null;
const adapter = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: immediateLock,
policy,
fetcher: async (request: Request) => {
fetchLog.push(request.url);
if (failFrom !== null && request.url === failFrom) {
throw new TypeError("network is down");
}
const body = bodies.get(request.url);
if (!body) throw new TypeError(`unknown asset ${request.url}`);
return new Response(Uint8Array.from(body), {
headers: {
"cache-control": "public",
"content-type": "application/javascript",
},
});
},
});
const manifest = await manifestFor(releaseRegistryId, assets, policy);
expect(await adapter.admin.stageRelease(manifest)).toMatchObject({
ok: true,
});
expect(
await adapter.admin.activateRelease(
manifest.releaseRegistryId,
manifest.manifestDigestHex,
),
).toMatchObject({ ok: true });
const cacheName = [...cacheStorage.caches.keys()].find((name) =>
name.includes(releaseRegistryId),
);
if (!cacheName) throw new Error("staged cache missing");
return {
adapter,
assets,
cacheName,
cacheStorage,
fetchLog,
manifest,
setFailure(url: string | null) {
failFrom = url;
},
};
}
it("does not delete an active candidate when the marker read fails transiently", async () => {
const release = await stagedRelease("transient-marker");
const cache = release.cacheStorage.caches.get(release.cacheName)!;
const realMatch = cache.match.bind(cache);
let markerReads = 0;
const assetUrls = new Set(release.assets.map((asset) => asset.absoluteUrl));
cache.match = async (request: RequestInfo | URL) => {
const url =
request instanceof Request ? request.url : String(request);
if (!assetUrls.has(url)) {
markerReads += 1;
throw new DOMException("Storage is busy", "InvalidStateError");
}
return await realMatch(request);
};
const restaged = await release.adapter.admin.stageRelease(release.manifest);
expect(markerReads).toBeGreaterThan(0);
expect(restaged.ok).toBe(false);
expect(release.cacheStorage.caches.has(release.cacheName)).toBe(true);
cache.match = realMatch;
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[0]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
});
it("keeps every healthy asset when one repair fetch fails", async () => {
const release = await stagedRelease("partial-repair");
const cache = release.cacheStorage.caches.get(release.cacheName)!;
// Corrupt only the first asset's stored bytes.
const corrupted = cache.responses.findIndex(
(entry) => entry.request.url === release.assets[0]!.absoluteUrl,
);
expect(corrupted).toBeGreaterThanOrEqual(0);
cache.responses.splice(corrupted, 1);
release.setFailure(release.assets[0]!.absoluteUrl);
const restaged = await release.adapter.admin.stageRelease(release.manifest);
expect(restaged.ok).toBe(false);
// The cache still exists and the healthy asset is still served.
expect(release.cacheStorage.caches.has(release.cacheName)).toBe(true);
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[1]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
});
it("still removes a candidate this call created when staging fails", async () => {
const policy = createDefaultPublicCachePolicy(
"https://assets.example.test",
);
const bytes = new Uint8Array([7, 7, 7, 7]);
const asset: PublicCacheAsset = {
absoluteUrl: "https://assets.example.test/fresh.js",
expectedByteLength: bytes.byteLength,
expectedContentType: "application/javascript",
integrity: {
algorithm: "SHA-256",
digestHex: await digestHex(bytes),
},
};
const cacheStorage = new MemoryCacheStorage();
const adapter = createPublicResponseCacheAdapter({
cacheStorage: cacheStorage as unknown as CacheStorage,
crypto: globalThis.crypto,
mutationLock: immediateLock,
policy,
fetcher: async () => {
throw new TypeError("network is down");
},
});
const manifest = await manifestFor("fresh-release", [asset], policy);
expect(await adapter.admin.stageRelease(manifest)).toMatchObject({
ok: false,
});
expect(await cacheStorage.keys()).toEqual([]);
});
it("repairs an evicted asset in place and keeps the release usable", async () => {
const release = await stagedRelease("in-place-repair");
const cache = release.cacheStorage.caches.get(release.cacheName)!;
const evicted = cache.responses.findIndex(
(entry) => entry.request.url === release.assets[1]!.absoluteUrl,
);
cache.responses.splice(evicted, 1);
expect(
await release.adapter.admin.stageRelease(release.manifest),
).toMatchObject({ ok: true });
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[1]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
expect(
await release.adapter.responses.matchActiveExact({
absoluteUrl: release.assets[0]!.absoluteUrl,
}),
).toMatchObject({ ok: true });
});
});
@@ -0,0 +1,341 @@
import { vi } from "vitest";
import type {
ResumableUploadCheckpoint,
ResumableUploadCheckpointStore,
ResumableUploadControlPlane,
ResumableUploadSource,
UploadPartExecutor,
UploadPartReceipt,
UploadProviderResult,
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 { resolveResumableUploadRuntimePolicy } from "../../src/adapters/browser-transfer/resumable-upload/runtime-policy.ts";
import type {
UploadCancellationChannel,
UploadCancellationListener,
} from "../../src/adapters/browser-transfer/resumable-upload/upload-cancellation-channel.ts";
import type { UploadMutationLock } from "../../src/adapters/browser-transfer/resumable-upload/upload-mutation-lock.ts";
export type TestCapability = Readonly<{ id: string }>;
export const activeSignal = new AbortController().signal;
export const noContentionLock: UploadMutationLock = Object.freeze({
async run<Value>(
_uploadKey: string,
_signal: AbortSignal,
task: () => Promise<Value>,
): Promise<Value> {
return await task();
},
});
export function createSerialMutationLock(): UploadMutationLock {
let tail = Promise.resolve();
return Object.freeze({
run<Value>(
_uploadKey: string,
signal: AbortSignal,
task: () => Promise<Value>,
): Promise<Value> {
const result = tail.then(async () => {
if (signal.aborted) {
throw new DOMException(
"The operation was aborted.",
"AbortError",
);
}
return await task();
});
tail = result.then(
() => undefined,
() => undefined,
);
return result;
},
});
}
export function createMemoryCancellationPair(): readonly [
UploadCancellationChannel,
UploadCancellationChannel,
] {
const listeners = [
new Set<UploadCancellationListener>(),
new Set<UploadCancellationListener>(),
] as const;
const channels = listeners.map((ownListeners, ownIndex) => {
let closed = false;
return Object.freeze({
publish(uploadKey: string) {
if (closed) return false;
for (const [index, peerListeners] of listeners.entries()) {
if (index === ownIndex) continue;
for (const listener of [...peerListeners]) {
listener(uploadKey);
}
}
return true;
},
subscribe(listener: UploadCancellationListener) {
if (closed) throw new TypeError("closed");
ownListeners.add(listener);
return () => ownListeners.delete(listener);
},
close() {
closed = true;
ownListeners.clear();
},
});
});
return channels as unknown as readonly [
UploadCancellationChannel,
UploadCancellationChannel,
];
}
export class MemoryCheckpointStore implements ResumableUploadCheckpointStore {
readonly rows = new Map<string, ResumableUploadCheckpoint>();
closed = false;
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 {
this.closed = true;
}
}
export function rangeSource(bytes: Uint8Array): ResumableUploadSource {
return Object.freeze({
kind: "RANGE_READER" as const,
reader: Object.freeze({
byteLength: bytes.byteLength,
async readRange(input: Readonly<{
offset: number;
length: number;
signal: AbortSignal;
}>) {
if (input.signal.aborted) {
return browserDataFailure("ABORTED", "FILE_READ");
}
return browserDataSuccess(
bytes.slice(input.offset, input.offset + input.length),
);
},
}),
});
}
export function byteStreamSource(bytes: Uint8Array): ResumableUploadSource {
return Object.freeze({
kind: "FILE_BYTE_SOURCE" as const,
bytes: Object.freeze({
byteLength: bytes.byteLength,
async *stream(signal: AbortSignal) {
if (signal.aborted) {
yield browserDataFailure("ABORTED", "FILE_READ");
return;
}
yield browserDataSuccess(bytes.slice(0, 3));
yield browserDataSuccess(bytes.slice(3));
},
}),
});
}
export function runtimePolicy(
overrides: Partial<
Parameters<typeof resolveResumableUploadRuntimePolicy>[0]
> = {},
) {
return {
partSizeBytes: 4,
maxFileBytes: 100,
maxPartCount: 25,
maxConcurrency: 3,
maxInFlightBytes: 48,
partBufferCopyFactor: 4,
maxSourceChunkBytes: 8,
maxRetries: 2,
retryBaseDelayMs: 1,
retryMaxDelayMs: 10,
maxRetryAfterMs: 100,
capabilityRefreshSkewMs: 5,
maxSessionLifetimeMs: 10_000,
providerAttemptTimeoutMs: 100,
...overrides,
};
}
export type ControlHarness = Readonly<{
control: ResumableUploadControlPlane<TestCapability>;
accepted: Map<number, UploadPartReceipt>;
issued: ReturnType<typeof vi.fn>;
completedParts: UploadPartReceipt[][];
getSession(): UploadSession | null;
}>;
export function createControlHarness(options: Readonly<{
now?: number;
serverMaxConcurrency?: number;
sessionId?: (createIndex: number) => string;
statusParts?: (
session: UploadSession,
accepted: Map<number, UploadPartReceipt>,
) => readonly UploadPartReceipt[];
issueCapability?: (
input: Parameters<
ResumableUploadControlPlane<TestCapability>["issuePartCapability"]
>[0],
callIndex: number,
) => UploadProviderResult<Readonly<{
capability: TestCapability;
uploadBindingSha256: string;
expiresAtEpochMs: number;
}>>;
}> = {}): ControlHarness {
const now = options.now ?? 1_000;
const accepted = new Map<number, UploadPartReceipt>();
const completedParts: UploadPartReceipt[][] = [];
let session: UploadSession | null = null;
let createCount = 0;
let issueCount = 0;
const issued = vi.fn();
const control: ResumableUploadControlPlane<TestCapability> = {
async createSession(input) {
createCount += 1;
session = Object.freeze({
protocol: RESUMABLE_UPLOAD_PROTOCOL,
sessionId:
options.sessionId?.(createCount) ?? "session_01",
requestBindingSha256: input.requestBindingSha256,
fingerprint: input.fingerprint,
partSizeBytes: input.requestedPartSizeBytes,
partCount: input.fingerprint.partCount,
maxConcurrency: options.serverMaxConcurrency ?? 2,
expiresAtEpochMs: now + 5_000,
});
return browserDataSuccess(session);
},
async getStatus() {
if (!session) {
return browserDataFailure("NOT_FOUND", "UPLOAD_RECONCILE");
}
const parts =
options.statusParts?.(session, accepted) ??
[...accepted.values()].sort(
(left, right) => left.partNumber - right.partNumber,
);
return browserDataSuccess({
state: "ACTIVE",
session,
acceptedParts: parts,
});
},
async issuePartCapability(input) {
issueCount += 1;
issued(input);
return (
options.issueCapability?.(input, issueCount) ??
browserDataSuccess({
capability: Object.freeze({ id: `cap-${issueCount}` }),
uploadBindingSha256: input.uploadBindingSha256,
expiresAtEpochMs: now + 4_000,
})
);
},
async complete(input) {
completedParts.push([...input.orderedParts]);
return browserDataSuccess({
state: "QUARANTINED",
protocol: RESUMABLE_UPLOAD_PROTOCOL,
sessionId: input.sessionId,
requestBindingSha256: input.requestBindingSha256,
fingerprint: input.fingerprint,
resourceId: "resource_01",
});
},
async abort() {
return browserDataSuccess({ state: "ABORTED" });
},
};
return {
control,
accepted,
issued,
completedParts,
getSession: () => session,
};
}
export function executorFor(
harness: ControlHarness,
options: Readonly<{
delay?: () => Promise<void>;
onActive?: (active: number) => void;
}> = {},
): UploadPartExecutor<TestCapability> {
let active = 0;
return {
async uploadPart(input) {
active += 1;
options.onActive?.(active);
await options.delay?.();
active -= 1;
const receipt = Object.freeze({
...input.part,
receiptToken: `etag-part-${input.part.partNumber}`,
});
harness.accepted.set(input.part.partNumber, receipt);
return browserDataSuccess(receipt);
},
};
}
@@ -0,0 +1,188 @@
import { describe, expect, it, vi } from "vitest";
import { browserDataFailure } from "../../src/adapters/browser-file-storage/result.ts";
import { createResumableUploadRuntime } from "../../src/adapters/browser-transfer/resumable-upload/resumable-upload-runtime.ts";
import {
activeSignal,
byteStreamSource,
createControlHarness,
executorFor,
MemoryCheckpointStore,
noContentionLock,
runtimePolicy,
} from "./resumable-upload-runtime-fixture.ts";
/**
* TR-RR-06. A non-cooperative mutation lock or provider must not make teardown
* unbounded: `dispose()` bounds its drain and reports honestly when the runtime
* is still CLOSING, and an abort is admitted physical work it cannot step over.
*/
describe("TR-RR-06 bounded resumable teardown", () => {
it("reports an unproved drain instead of waiting forever", async () => {
const checkpoints = new MemoryCheckpointStore();
const harness = createControlHarness();
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness, { delay: async () => {} }),
checkpoints,
// A lock that never grants: dispose must still be bounded.
mutationLock: Object.freeze({
async run<Value>(): Promise<Value> {
return await new Promise<never>(() => {});
},
}),
crypto,
policy: runtimePolicy({ cleanupDeadlineMs: 20 }),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
void runtime.upload({
uploadKey: "upload_key_hung",
purpose: "attachment",
mediaType: "application/octet-stream",
source: byteStreamSource(new Uint8Array([1, 2, 3, 4, 5])),
signal: activeSignal,
});
await Promise.resolve();
await Promise.resolve();
const disposed = await runtime.dispose();
expect(disposed.ok).toBe(false);
// Still CLOSING: physical work the caller must not treat as finished.
expect(runtime.lifecycle()).toBe("CLOSING");
expect(checkpoints.closed).toBe(false);
});
it("closes once every admitted operation settles", async () => {
const checkpoints = new MemoryCheckpointStore();
const harness = createControlHarness();
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness, { delay: async () => {} }),
checkpoints,
mutationLock: noContentionLock,
crypto,
policy: runtimePolicy({ cleanupDeadlineMs: 200 }),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
const disposed = await runtime.dispose();
expect(disposed).toMatchObject({ ok: true });
expect(runtime.lifecycle()).toBe("CLOSED");
expect(checkpoints.closed).toBe(true);
});
});
/**
* TR-04. `dispose()` proved quiescence from the wrapper registry only. A
* provider that ignored its attempt deadline let the wrapper settle first and
* leave the set empty, so teardown reported a drained runtime — and closed the
* checkpoint store — while the provider was still running.
*/
describe("TR-04 teardown waits for raw provider work, not only its wrapper", () => {
it("refuses to report a drained runtime while a provider is still running", async () => {
const harness = createControlHarness();
const checkpoints = new MemoryCheckpointStore();
const closeStore = vi.spyOn(checkpoints, "close");
let releaseProvider: (() => void) | undefined;
// Ignores the attempt signal entirely and outlives its own deadline.
harness.control.createSession = () =>
new Promise((resolve) => {
releaseProvider = () =>
resolve(
browserDataFailure("UNAVAILABLE", "UPLOAD_SESSION", {
retryable: true,
recovery: "RESUME",
}),
);
});
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness),
checkpoints,
mutationLock: noContentionLock,
crypto,
policy: runtimePolicy({
providerAttemptTimeoutMs: 5,
cleanupDeadlineMs: 25,
maxRetries: 0,
}),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
const uploading = runtime.upload({
uploadKey: "upload_key_raw",
purpose: "attachment",
mediaType: "application/octet-stream",
source: byteStreamSource(new Uint8Array([1, 2, 3, 4, 5])),
signal: activeSignal,
});
await vi.waitFor(() => expect(releaseProvider).toBeDefined());
// The wrapper has already given up on the attempt.
await uploading;
const disposed = await runtime.dispose();
expect(disposed.ok).toBe(false);
if (!disposed.ok) {
expect(disposed.error.code).toBe("UNAVAILABLE");
expect(disposed.error.recovery).toBe("RESUME");
}
// The store stays open while something could still write a checkpoint.
expect(closeStore).not.toHaveBeenCalled();
releaseProvider?.();
});
it("reports a drained runtime once the raw provider settles", async () => {
const harness = createControlHarness();
const checkpoints = new MemoryCheckpointStore();
let releaseProvider: (() => void) | undefined;
harness.control.createSession = () =>
new Promise((resolve) => {
releaseProvider = () =>
resolve(
browserDataFailure("UNAVAILABLE", "UPLOAD_SESSION", {
retryable: true,
recovery: "RESUME",
}),
);
});
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness),
checkpoints,
mutationLock: noContentionLock,
crypto,
policy: runtimePolicy({
providerAttemptTimeoutMs: 5,
cleanupDeadlineMs: 1_000,
maxRetries: 0,
}),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
const uploading = runtime.upload({
uploadKey: "upload_key_raw_2",
purpose: "attachment",
mediaType: "application/octet-stream",
source: byteStreamSource(new Uint8Array([1, 2, 3, 4, 5])),
signal: activeSignal,
});
await vi.waitFor(() => expect(releaseProvider).toBeDefined());
await uploading;
const disposing = runtime.dispose();
releaseProvider?.();
await expect(disposing).resolves.toMatchObject({ ok: true });
expect(runtime.lifecycle()).toBe("CLOSED");
});
});
+13 -487
View File
@@ -32,319 +32,19 @@ import type {
} from "../../src/adapters/browser-transfer/resumable-upload/upload-cancellation-channel.ts";
import type { UploadMutationLock } from "../../src/adapters/browser-transfer/resumable-upload/upload-mutation-lock.ts";
type TestCapability = Readonly<{ id: string }>;
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();
},
});
function createSerialMutationLock(): UploadMutationLock {
let tail = Promise.resolve();
return Object.freeze({
run<Value>(
_uploadKey: string,
signal: AbortSignal,
task: () => Promise<Value>,
): Promise<Value> {
const result = tail.then(async () => {
if (signal.aborted) {
throw new DOMException(
"The operation was aborted.",
"AbortError",
);
}
return await task();
});
tail = result.then(
() => undefined,
() => undefined,
);
return result;
},
});
}
function createMemoryCancellationPair(): readonly [
UploadCancellationChannel,
UploadCancellationChannel,
] {
const listeners = [
new Set<UploadCancellationListener>(),
new Set<UploadCancellationListener>(),
] as const;
const channels = listeners.map((ownListeners, ownIndex) => {
let closed = false;
return Object.freeze({
publish(uploadKey: string) {
if (closed) return false;
for (const [index, peerListeners] of listeners.entries()) {
if (index === ownIndex) continue;
for (const listener of [...peerListeners]) {
listener(uploadKey);
}
}
return true;
},
subscribe(listener: UploadCancellationListener) {
if (closed) throw new TypeError("closed");
ownListeners.add(listener);
return () => ownListeners.delete(listener);
},
close() {
closed = true;
ownListeners.clear();
},
});
});
return channels as unknown as readonly [
UploadCancellationChannel,
UploadCancellationChannel,
];
}
class MemoryCheckpointStore implements ResumableUploadCheckpointStore {
readonly rows = new Map<string, ResumableUploadCheckpoint>();
closed = false;
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 {
this.closed = true;
}
}
function rangeSource(bytes: Uint8Array): ResumableUploadSource {
return Object.freeze({
kind: "RANGE_READER" as const,
reader: Object.freeze({
byteLength: bytes.byteLength,
async readRange(input: Readonly<{
offset: number;
length: number;
signal: AbortSignal;
}>) {
if (input.signal.aborted) {
return browserDataFailure("ABORTED", "FILE_READ");
}
return browserDataSuccess(
bytes.slice(input.offset, input.offset + input.length),
);
},
}),
});
}
function byteStreamSource(bytes: Uint8Array): ResumableUploadSource {
return Object.freeze({
kind: "FILE_BYTE_SOURCE" as const,
bytes: Object.freeze({
byteLength: bytes.byteLength,
async *stream(signal: AbortSignal) {
if (signal.aborted) {
yield browserDataFailure("ABORTED", "FILE_READ");
return;
}
yield browserDataSuccess(bytes.slice(0, 3));
yield browserDataSuccess(bytes.slice(3));
},
}),
});
}
function runtimePolicy(
overrides: Partial<
Parameters<typeof resolveResumableUploadRuntimePolicy>[0]
> = {},
) {
return {
partSizeBytes: 4,
maxFileBytes: 100,
maxPartCount: 25,
maxConcurrency: 3,
maxInFlightBytes: 48,
partBufferCopyFactor: 4,
maxSourceChunkBytes: 8,
maxRetries: 2,
retryBaseDelayMs: 1,
retryMaxDelayMs: 10,
maxRetryAfterMs: 100,
capabilityRefreshSkewMs: 5,
maxSessionLifetimeMs: 10_000,
providerAttemptTimeoutMs: 100,
...overrides,
};
}
type ControlHarness = Readonly<{
control: ResumableUploadControlPlane<TestCapability>;
accepted: Map<number, UploadPartReceipt>;
issued: ReturnType<typeof vi.fn>;
completedParts: UploadPartReceipt[][];
getSession(): UploadSession | null;
}>;
function createControlHarness(options: Readonly<{
now?: number;
serverMaxConcurrency?: number;
sessionId?: (createIndex: number) => string;
statusParts?: (
session: UploadSession,
accepted: Map<number, UploadPartReceipt>,
) => readonly UploadPartReceipt[];
issueCapability?: (
input: Parameters<
ResumableUploadControlPlane<TestCapability>["issuePartCapability"]
>[0],
callIndex: number,
) => UploadProviderResult<Readonly<{
capability: TestCapability;
uploadBindingSha256: string;
expiresAtEpochMs: number;
}>>;
}> = {}): ControlHarness {
const now = options.now ?? 1_000;
const accepted = new Map<number, UploadPartReceipt>();
const completedParts: UploadPartReceipt[][] = [];
let session: UploadSession | null = null;
let createCount = 0;
let issueCount = 0;
const issued = vi.fn();
const control: ResumableUploadControlPlane<TestCapability> = {
async createSession(input) {
createCount += 1;
session = Object.freeze({
protocol: RESUMABLE_UPLOAD_PROTOCOL,
sessionId:
options.sessionId?.(createCount) ?? "session_01",
requestBindingSha256: input.requestBindingSha256,
fingerprint: input.fingerprint,
partSizeBytes: input.requestedPartSizeBytes,
partCount: input.fingerprint.partCount,
maxConcurrency: options.serverMaxConcurrency ?? 2,
expiresAtEpochMs: now + 5_000,
});
return browserDataSuccess(session);
},
async getStatus() {
if (!session) {
return browserDataFailure("NOT_FOUND", "UPLOAD_RECONCILE");
}
const parts =
options.statusParts?.(session, accepted) ??
[...accepted.values()].sort(
(left, right) => left.partNumber - right.partNumber,
);
return browserDataSuccess({
state: "ACTIVE",
session,
acceptedParts: parts,
});
},
async issuePartCapability(input) {
issueCount += 1;
issued(input);
return (
options.issueCapability?.(input, issueCount) ??
browserDataSuccess({
capability: Object.freeze({ id: `cap-${issueCount}` }),
uploadBindingSha256: input.uploadBindingSha256,
expiresAtEpochMs: now + 4_000,
})
);
},
async complete(input) {
completedParts.push([...input.orderedParts]);
return browserDataSuccess({
state: "QUARANTINED",
protocol: RESUMABLE_UPLOAD_PROTOCOL,
sessionId: input.sessionId,
requestBindingSha256: input.requestBindingSha256,
fingerprint: input.fingerprint,
resourceId: "resource_01",
});
},
async abort() {
return browserDataSuccess({ state: "ABORTED" });
},
};
return {
control,
accepted,
issued,
completedParts,
getSession: () => session,
};
}
function executorFor(
harness: ControlHarness,
options: Readonly<{
delay?: () => Promise<void>;
onActive?: (active: number) => void;
}> = {},
): UploadPartExecutor<TestCapability> {
let active = 0;
return {
async uploadPart(input) {
active += 1;
options.onActive?.(active);
await options.delay?.();
active -= 1;
const receipt = Object.freeze({
...input.part,
receiptToken: `etag-part-${input.part.partNumber}`,
});
harness.accepted.set(input.part.partNumber, receipt);
return browserDataSuccess(receipt);
},
};
}
import {
activeSignal,
byteStreamSource,
createControlHarness,
createMemoryCancellationPair,
createSerialMutationLock,
executorFor,
MemoryCheckpointStore,
noContentionLock,
rangeSource,
runtimePolicy,
type TestCapability,
} from "./resumable-upload-runtime-fixture.ts";
describe("production resumable upload runtime", () => {
it("disposes through one drain that proves quiescence", async () => {
@@ -1280,177 +980,3 @@ describe("production resumable upload runtime", () => {
}
});
});
/**
* TR-RR-06. A non-cooperative mutation lock or provider must not make teardown
* unbounded: `dispose()` bounds its drain and reports honestly when the runtime
* is still CLOSING, and an abort is admitted physical work it cannot step over.
*/
describe("TR-RR-06 bounded resumable teardown", () => {
it("reports an unproved drain instead of waiting forever", async () => {
const checkpoints = new MemoryCheckpointStore();
const harness = createControlHarness();
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness, { delay: async () => {} }),
checkpoints,
// A lock that never grants: dispose must still be bounded.
mutationLock: Object.freeze({
async run<Value>(): Promise<Value> {
return await new Promise<never>(() => {});
},
}),
crypto,
policy: runtimePolicy({ cleanupDeadlineMs: 20 }),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
void runtime.upload({
uploadKey: "upload_key_hung",
purpose: "attachment",
mediaType: "application/octet-stream",
source: byteStreamSource(new Uint8Array([1, 2, 3, 4, 5])),
signal: activeSignal,
});
await Promise.resolve();
await Promise.resolve();
const disposed = await runtime.dispose();
expect(disposed.ok).toBe(false);
// Still CLOSING: physical work the caller must not treat as finished.
expect(runtime.lifecycle()).toBe("CLOSING");
expect(checkpoints.closed).toBe(false);
});
it("closes once every admitted operation settles", async () => {
const checkpoints = new MemoryCheckpointStore();
const harness = createControlHarness();
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness, { delay: async () => {} }),
checkpoints,
mutationLock: noContentionLock,
crypto,
policy: runtimePolicy({ cleanupDeadlineMs: 200 }),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
const disposed = await runtime.dispose();
expect(disposed).toMatchObject({ ok: true });
expect(runtime.lifecycle()).toBe("CLOSED");
expect(checkpoints.closed).toBe(true);
});
});
/**
* TR-04. `dispose()` proved quiescence from the wrapper registry only. A
* provider that ignored its attempt deadline let the wrapper settle first and
* leave the set empty, so teardown reported a drained runtime — and closed the
* checkpoint store — while the provider was still running.
*/
describe("TR-04 teardown waits for raw provider work, not only its wrapper", () => {
it("refuses to report a drained runtime while a provider is still running", async () => {
const harness = createControlHarness();
const checkpoints = new MemoryCheckpointStore();
const closeStore = vi.spyOn(checkpoints, "close");
let releaseProvider: (() => void) | undefined;
// Ignores the attempt signal entirely and outlives its own deadline.
harness.control.createSession = () =>
new Promise((resolve) => {
releaseProvider = () =>
resolve(
browserDataFailure("UNAVAILABLE", "UPLOAD_SESSION", {
retryable: true,
recovery: "RESUME",
}),
);
});
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness),
checkpoints,
mutationLock: noContentionLock,
crypto,
policy: runtimePolicy({
providerAttemptTimeoutMs: 5,
cleanupDeadlineMs: 25,
maxRetries: 0,
}),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
const uploading = runtime.upload({
uploadKey: "upload_key_raw",
purpose: "attachment",
mediaType: "application/octet-stream",
source: byteStreamSource(new Uint8Array([1, 2, 3, 4, 5])),
signal: activeSignal,
});
await vi.waitFor(() => expect(releaseProvider).toBeDefined());
// The wrapper has already given up on the attempt.
await uploading;
const disposed = await runtime.dispose();
expect(disposed.ok).toBe(false);
if (!disposed.ok) {
expect(disposed.error.code).toBe("UNAVAILABLE");
expect(disposed.error.recovery).toBe("RESUME");
}
// The store stays open while something could still write a checkpoint.
expect(closeStore).not.toHaveBeenCalled();
releaseProvider?.();
});
it("reports a drained runtime once the raw provider settles", async () => {
const harness = createControlHarness();
const checkpoints = new MemoryCheckpointStore();
let releaseProvider: (() => void) | undefined;
harness.control.createSession = () =>
new Promise((resolve) => {
releaseProvider = () =>
resolve(
browserDataFailure("UNAVAILABLE", "UPLOAD_SESSION", {
retryable: true,
recovery: "RESUME",
}),
);
});
const runtime = createResumableUploadRuntime({
controlPlane: harness.control,
partExecutor: executorFor(harness),
checkpoints,
mutationLock: noContentionLock,
crypto,
policy: runtimePolicy({
providerAttemptTimeoutMs: 5,
cleanupDeadlineMs: 1_000,
maxRetries: 0,
}),
now: () => 1_000,
random: () => 0,
sleep: async () => {},
});
const uploading = runtime.upload({
uploadKey: "upload_key_raw_2",
purpose: "attachment",
mediaType: "application/octet-stream",
source: byteStreamSource(new Uint8Array([1, 2, 3, 4, 5])),
signal: activeSignal,
});
await vi.waitFor(() => expect(releaseProvider).toBeDefined());
await uploading;
const disposing = runtime.dispose();
releaseProvider?.();
await expect(disposing).resolves.toMatchObject({ ok: true });
expect(runtime.lifecycle()).toBe("CLOSED");
});
});
+541
View File
@@ -0,0 +1,541 @@
import {
createHash,
generateKeyPairSync,
sign,
type KeyObject,
} from "node:crypto";
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { localEvidenceAssessmentArtifactSchema } from "../../scripts/contracts/release-artifacts.ts";
import { PROMOTED_FILE_NAMES } from "../../scripts/contracts/promotion-artifacts.ts";
import {
providerEvidenceSignaturePayload,
trustPolicySha256,
} from "../../scripts/lib/provider-evidence.ts";
import {
LOCAL_EVIDENCE_ASSESSMENT_PATH,
distSha256,
type ReleaseCandidateManifest,
} from "../../scripts/lib/release-candidate.ts";
import { supplyChainDigest } from "../../scripts/lib/supply-chain.ts";
export const PROCESS_HEAVY_TIMEOUT_MS = 30_000;
export const digest = (value: string): string =>
createHash("sha256").update(value).digest("hex");
export const digestBytes = (value: Buffer): string =>
createHash("sha256").update(value).digest("hex");
export function passingAssessment(): any {
return {
schemaVersion: 1 as const,
artifactType: "local-evidence-assessment" as const,
generatedAt: "2026-08-02T00:00:00.000Z",
status: "PASS" as const,
verifier: {
id: "clean-architecture-frontend-template/local-evidence-verifier",
version: "1",
sourceSha256: digest("verifier source"),
},
source: {
revision: "a".repeat(40),
sourceSetSha256: digest("source set"),
},
candidate: {
distSha256: digest("dist"),
lockfileSha256: digest("lockfile"),
sbomSha256: digest("sbom"),
},
secretScan: {
policySha256: digest("secret policy"),
sarifSha256: digest("secret sarif"),
scanInputSha256: digest("secret scan input"),
},
policyInputs: [
{
path: "config/security/dependency-policy.json",
bytes: 3,
sha256: digest("{}\n"),
},
],
evidenceInputs: [
{ path: "pnpm-lock.yaml", bytes: 9, sha256: digest("lockfile\n") },
],
checks: {
release: "PASS" as const,
supplyChain: "PASS" as const,
dependencyPolicy: "PASS" as const,
licensePolicy: "PASS" as const,
vulnerabilityPolicy: "PASS" as const,
secretScan: "PASS" as const,
},
failures: [] as string[],
};
}
export function providerExpectedContext() {
return {
run: { id: "run-42", attempt: 1 },
source: { revision: "b".repeat(40), sourceSetSha256: digest("provider source") },
candidate: {
archiveSha256: digest("archive"),
bundleSha256: digest("bundle"),
distSha256: digest("provider dist"),
lockfileSha256: digest("provider lockfile"),
},
secretScanAttestation: {
status: "PASS" as const,
localEvidenceAssessmentSha256: digest("provider assessment"),
sourceSetSha256: digest("provider source"),
policySha256: digest("provider secret policy"),
sarifSha256: digest("provider secret sarif"),
scanInputSha256: digest("provider secret input"),
},
} as const;
}
export function privatePromotionFiles() {
return PROMOTED_FILE_NAMES.map((name) => {
const bytes = Buffer.from(`${name}\n`);
return { name, bytes, sha256: digestBytes(bytes) };
});
}
export function syntheticSignedPromotionBundle() {
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const archiveBytes = Buffer.from("synthetic signed replay archive\n");
const run = { id: "signed-run", attempt: 1 } as const;
const source = {
revision: "a".repeat(40),
sourceSetSha256: digest("synthetic-source-set"),
} as const;
const candidate = {
archiveSha256: digestBytes(archiveBytes),
bundleSha256: digest("synthetic-bundle"),
distSha256: digest("synthetic-dist"),
lockfileSha256: digest("synthetic-lock"),
} as const;
const vulnerability = signedProviderV2(
{
schemaVersion: 2,
evidenceType: "vulnerability-report",
provider: "synthetic-vulnerability-provider",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...run, invocationNonce: "1".repeat(64) },
source,
candidate,
secretScanAttestation: {
status: "PASS",
localEvidenceAssessmentSha256: digest("synthetic-assessment"),
sourceSetSha256: source.sourceSetSha256,
policySha256: digest("synthetic-policy"),
sarifSha256: digest("synthetic-sarif"),
scanInputSha256: digest("synthetic-scan-input"),
},
findings: [],
},
"synthetic-vulnerability",
vulnerabilityKeys.publicKey,
vulnerabilityKeys.privateKey,
);
const provenance = signedProviderV2(
{
schemaVersion: 2,
evidenceType: "provenance-attestation",
provider: "synthetic-provenance-provider",
signer: "synthetic-signer",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...run, invocationNonce: "2".repeat(64) },
source,
candidate,
subject: { name: "dist", digest: { sha256: candidate.distSha256 } },
},
"synthetic-provenance",
provenanceKeys.publicKey,
provenanceKeys.privateKey,
);
const vulnerabilityBytes = Buffer.from(`${JSON.stringify(vulnerability)}\n`);
const provenanceBytes = Buffer.from(`${JSON.stringify(provenance)}\n`);
const vulnerabilityTrust = trust("synthetic-vulnerability", vulnerabilityKeys.publicKey);
const provenanceTrust = trust("synthetic-provenance", provenanceKeys.publicKey);
const providerEvidence = {
vulnerabilityReportSha256: digestBytes(vulnerabilityBytes),
provenanceAttestationSha256: digestBytes(provenanceBytes),
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
vulnerabilityKeyId: vulnerabilityTrust.keyId,
vulnerabilityKeyFingerprint: vulnerabilityTrust.publicKeyFingerprint,
provenanceKeyId: provenanceTrust.keyId,
provenanceKeyFingerprint: provenanceTrust.publicKeyFingerprint,
secretScanAttestation: vulnerability.secretScanAttestation,
};
const common = {
schemaVersion: 3,
verifiedAt: "2026-08-02T01:00:00.000Z",
status: "PASS",
verifier: {
id: "clean-architecture-frontend-template/promotion-verifier",
version: "3",
},
run,
source,
candidate,
providerEvidence,
trustPolicySha256: trustPolicySha256({ vulnerabilityTrust, provenanceTrust }),
failures: [],
};
const providerBytes = Buffer.from(
`${JSON.stringify({
...common,
artifactType: "provider-verification",
vulnerabilityStatus: "PASS",
provenanceAttestationStatus: "PASS",
}, null, 2)}\n`,
);
const promotionBytes = Buffer.from(
`${JSON.stringify({
...common,
artifactType: "promotion-verification",
localEvidenceStatus: "PASS",
localEvidenceAssessmentSha256: digest("synthetic-assessment"),
providerVerificationSha256: digestBytes(providerBytes),
}, null, 2)}\n`,
);
return {
files: {
"release-candidate.tar.gz": archiveBytes,
"vulnerability-report.json": vulnerabilityBytes,
"provenance-attestation.json": provenanceBytes,
"provider-verification.json": providerBytes,
"promotion-verification.json": promotionBytes,
},
verification: {
vulnerabilityTrust,
provenanceTrust,
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
expected: {
run,
sourceRevision: source.revision,
sourceSetSha256: source.sourceSetSha256,
archiveSha256: candidate.archiveSha256,
bundleSha256: candidate.bundleSha256,
distSha256: candidate.distSha256,
lockfileSha256: candidate.lockfileSha256,
},
},
vulnerabilityPem: vulnerabilityKeys.publicKey.export({ type: "spki", format: "pem" }),
provenancePem: provenanceKeys.publicKey.export({ type: "spki", format: "pem" }),
};
}
export function fingerprint(publicKey: KeyObject): string {
return `sha256:${createHash("sha256")
.update(publicKey.export({ type: "spki", format: "der" }))
.digest("hex")}`;
}
export function trust(keyId: string, publicKey: KeyObject) {
return { keyId, publicKey, publicKeyFingerprint: fingerprint(publicKey) };
}
export function signedProviderV2(
unsigned: Record<string, unknown>,
keyId: string,
publicKey: KeyObject,
privateKey: KeyObject,
fingerprintOverride?: string,
): Record<string, any> {
const { signature: existingSignature, ...payload } = unsigned;
const value = {
...payload,
signature: {
algorithm: "Ed25519" as const,
keyId,
publicKeyFingerprint:
fingerprintOverride ??
(existingSignature && typeof existingSignature === "object" &&
"publicKeyFingerprint" in existingSignature
? String(existingSignature.publicKeyFingerprint)
: fingerprint(publicKey)),
value: "",
},
};
value.signature.value = sign(
null,
providerEvidenceSignaturePayload(value),
privateKey,
).toString("base64");
return value;
}
export function providerUnsigned(
kind: "vulnerability" | "provenance",
expected: ReturnType<typeof providerExpectedContext>,
): Record<string, any> {
const common = {
source: expected.source,
candidate: expected.candidate,
schemaVersion: 2,
evidenceType:
kind === "vulnerability"
? "vulnerability-report"
: "provenance-attestation",
provider: `fixture-${kind}`,
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: {
...expected.run,
invocationNonce: kind === "vulnerability" ? "1".repeat(64) : "2".repeat(64),
},
};
return kind === "vulnerability"
? {
...common,
secretScanAttestation: expected.secretScanAttestation,
findings: [],
}
: {
...common,
signer: "fixture-workload",
subject: {
name: "dist",
digest: { sha256: expected.candidate.distSha256 },
},
};
}
export async function createArchivedAssessmentFixture(): Promise<{
root: string;
manifest: ReleaseCandidateManifest;
assessmentSha256: string;
}> {
const root = await mkdtemp(path.join(tmpdir(), "archived-assessment-"));
const sourceRevision = "a".repeat(40);
const sourceSetSha256 = digest("source set");
const releaseManifestBytes = Buffer.from(
`${JSON.stringify({
schemaVersion: 1,
appVersion: "1.0.0",
buildId: "build-1",
commitSha: sourceRevision,
configSchemaVersion: "1",
apiContractVersion: "1",
assetManifestHash: digest("vite manifest"),
releaseId: "release-1",
builtAt: "2026-08-02T00:00:00.000Z",
routeChunks: { home: "assets/home.js" },
})}\n`,
);
const distInputs = [
{ path: "dist/app.js", bytes: Buffer.byteLength("app\n"), sha256: digest("app\n"), gzipBytes: 0 },
{
path: "dist/release-manifest.json",
bytes: releaseManifestBytes.byteLength,
sha256: digestBytes(releaseManifestBytes),
gzipBytes: 0,
},
];
const candidateDist = distSha256(distInputs);
const sbomBytes = Buffer.from(
`${JSON.stringify({
bomFormat: "CycloneDX",
specVersion: "1.6",
serialNumber: "urn:uuid:00000000-0000-4000-8000-000000000001",
version: 1,
metadata: {
component: { type: "application", name: "fixture", version: "1.0.0" },
properties: [],
},
components: [],
dependencies: [],
})}\n`,
);
const sbomSha256 = digestBytes(sbomBytes);
const lockfileBytes = Buffer.from("lockfile\n");
const lockfileDigest = digestBytes(lockfileBytes);
const buildManifest = {
schemaVersion: 1,
buildId: "build-1",
commitSha: sourceRevision,
releaseId: "release-1",
moduleInventoryHash: digest("module inventory"),
generatedAt: "2026-08-02T00:00:00.000Z",
buildContext: {
nodeVersion: "v24.0.0",
packageManagerVersion: "11.0.0",
runnerImage: "linux-x64",
sourceDateEpoch: "1785638400",
},
outputs: {
directory: "dist",
viteManifest: "dist/.vite/manifest.json",
moduleInventory: "artifacts/quality/vite-module-inventory.json",
routeChunks: { home: "assets/home.js" },
runtimeConfigSchema: "dist/runtime-config.schema.json",
},
};
const provenance = {
_type: "https://in-toto.io/Statement/v1",
subject: [{ name: "dist", digest: { sha256: candidateDist } }],
predicateType: "https://slsa.dev/provenance/v1",
predicate: {
buildDefinition: {
buildType: "https://vite.dev/build/v1",
externalParameters: {},
internalParameters: {},
resolvedDependencies: [
{ uri: "pnpm-lock.yaml", digest: { sha256: lockfileDigest } },
],
},
runDetails: {
builder: { id: "fixture-builder" },
metadata: { invocationId: "LOCAL_UNSIGNED" },
},
materials: { lockfileSha256: lockfileDigest, sourceSetSha256, sbomSha256 },
},
};
const supplyVerification = {
schemaVersion: 1,
localStatus: "PASS",
promotionStatus: "FAIL_UNVERIFIED",
lockfileSha256: lockfileDigest,
sourceSetSha256,
distSha256: candidateDist,
sbomSha256,
dependencyDiff: { added: [], removed: [], changed: [], upgrades: [] },
highRiskReview: [],
vulnerabilityStatus: "FAIL_UNVERIFIED",
provenanceAttestationStatus: "FAIL_UNVERIFIED",
failures: [],
};
const members = new Map<string, Buffer>([
["dist/app.js", Buffer.from("app\n")],
["dist/release-manifest.json", releaseManifestBytes],
["pnpm-lock.yaml", lockfileBytes],
["artifacts/release/build-manifest.json", Buffer.from(`${JSON.stringify(buildManifest)}\n`)],
["artifacts/release/provenance.json", Buffer.from(`${JSON.stringify(provenance)}\n`)],
[
"artifacts/security/supply-chain-verification.json",
Buffer.from(`${JSON.stringify(supplyVerification)}\n`),
],
["artifacts/release/sbom.cdx.json", sbomBytes],
]);
const evidenceInputs = [...members.entries()]
.map(([memberPath, bytes]) => ({
path: memberPath,
bytes: bytes.byteLength,
sha256: digestBytes(bytes),
}))
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
const policyPaths = [
"config/security/dependency-baseline.approval.json",
"config/security/dependency-baseline.json",
"config/security/dependency-change-evidence.json",
"config/security/dependency-policy.json",
"config/security/secret-scan-policy.json",
"config/security/vulnerability-exceptions.json",
"config/security/vulnerability-policy.json",
"schemas/artifacts/build-manifest.schema.json",
"schemas/artifacts/dependency-inventory.schema.json",
"schemas/artifacts/supply-chain-verification.schema.json",
"scripts/contracts/release-artifacts.ts",
"scripts/create-release-candidate.ts",
"scripts/generate-supply-chain.ts",
"scripts/lib/build-manifest-outputs.ts",
"scripts/lib/json-schema.ts",
"scripts/lib/local-policy-evidence.ts",
"scripts/lib/local-release-evidence.ts",
"scripts/lib/release-candidate.ts",
"scripts/lib/release-input-evidence.ts",
"scripts/lib/release-runtime-coherence.ts",
"scripts/lib/repository-file-inventory.ts",
"scripts/lib/secret-scan-evaluator.ts",
"scripts/lib/secret-scan-policy.ts",
"scripts/lib/secret-scan.ts",
"scripts/lib/supply-chain.ts",
"scripts/lib/validated-json-artifact.ts",
"src/contracts/release-artifacts.ts",
"src/features/installed-contract-contributions.ts",
"src/features/installed-feature-contracts.ts",
];
const sbomRow = evidenceInputs.find(
({ path: memberPath }) => memberPath === "artifacts/release/sbom.cdx.json",
)!;
const policyInputs = policyPaths.map((policyPath) => ({
path: policyPath,
bytes: 2,
sha256: digest(`policy:${policyPath}`),
}));
const verifierPaths = new Set([
"scripts/contracts/release-artifacts.ts",
"scripts/create-release-candidate.ts",
"scripts/generate-supply-chain.ts",
"scripts/lib/build-manifest-outputs.ts",
"scripts/lib/json-schema.ts",
"scripts/lib/local-policy-evidence.ts",
"scripts/lib/local-release-evidence.ts",
"scripts/lib/release-candidate.ts",
"scripts/lib/release-input-evidence.ts",
"scripts/lib/release-runtime-coherence.ts",
"scripts/lib/repository-file-inventory.ts",
"scripts/lib/secret-scan-evaluator.ts",
"scripts/lib/secret-scan-policy.ts",
"scripts/lib/secret-scan.ts",
"scripts/lib/supply-chain.ts",
"scripts/lib/validated-json-artifact.ts",
"src/contracts/release-artifacts.ts",
"src/features/installed-contract-contributions.ts",
"src/features/installed-feature-contracts.ts",
]);
const assessment = localEvidenceAssessmentArtifactSchema.parse({
...passingAssessment(),
verifier: {
id: "clean-architecture-frontend-template/local-evidence-verifier",
version: "1",
sourceSha256: supplyChainDigest(
policyInputs.filter(({ path: policyPath }) => verifierPaths.has(policyPath)),
),
},
source: { revision: sourceRevision, sourceSetSha256 },
candidate: {
distSha256: candidateDist,
lockfileSha256: evidenceInputs.find(({ path: memberPath }) => memberPath === "pnpm-lock.yaml")!
.sha256,
sbomSha256: sbomRow.sha256,
},
policyInputs,
evidenceInputs,
});
const assessmentBytes = Buffer.from(`${JSON.stringify(assessment)}\n`);
members.set(LOCAL_EVIDENCE_ASSESSMENT_PATH, assessmentBytes);
for (const [memberPath, bytes] of members) {
await mkdir(path.dirname(path.join(root, memberPath)), { recursive: true });
await writeFile(path.join(root, memberPath), bytes);
}
const files = [...members.entries()]
.map(([memberPath, bytes]) => ({
path: memberPath,
bytes: bytes.byteLength,
sha256: digestBytes(bytes),
}))
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
const manifest: ReleaseCandidateManifest = {
schemaVersion: 1,
distSha256: assessment.candidate.distSha256,
lockfileSha256: assessment.candidate.lockfileSha256,
bundleSha256: supplyChainDigest(files),
files,
};
await mkdir(path.join(root, "artifacts/release"), { recursive: true });
await writeFile(
path.join(root, "artifacts/release/release-candidate.json"),
`${JSON.stringify(manifest)}\n`,
);
return { root, manifest, assessmentSha256: digestBytes(assessmentBytes) };
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,370 @@
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import {
chmod,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
rename,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { describe, expect, it } from "vitest";
import { PROMOTED_FILE_NAMES } from "../../scripts/contracts/promotion-artifacts.ts";
import { publishPrivatePromotionStaging } from "../../scripts/lib/promotion-stager.ts";
import { verifyExactPromotionBundle } from "../../scripts/lib/exact-promotion-bundle.ts";
import {
PROCESS_HEAVY_TIMEOUT_MS,
privatePromotionFiles,
syntheticSignedPromotionBundle,
} from "./security-followup-fixture.ts";
describe("security private promotion staging contracts", () => {
it("forces exact private staging modes in an isolated child with umask 077", async () => {
const root = await mkdtemp(path.join(tmpdir(), "promotion-umask-"));
try {
const stagerUrl = pathToFileURL(
path.join(process.cwd(), "scripts/lib/promotion-stager.ts"),
).href;
const contractsUrl = pathToFileURL(
path.join(process.cwd(), "scripts/contracts/promotion-artifacts.ts"),
).href;
const childPath = path.join(root, "umask-child.mjs");
const resultPath = path.join(root, "result.json");
await writeFile(resultPath, "{}\n", { mode: 0o600 });
await writeFile(
childPath,
[
`import { lstat, writeFile } from "node:fs/promises";`,
`import path from "node:path";`,
`import { createHash } from "node:crypto";`,
`import { cleanupFinalizedPromotion, publishPrivatePromotionStaging } from ${JSON.stringify(stagerUrl)};`,
`import { PROMOTED_FILE_NAMES } from ${JSON.stringify(contractsUrl)};`,
`process.umask(Number.parseInt(process.argv[2], 8));`,
`const runnerTempRoot = process.argv[3];`,
`const files = PROMOTED_FILE_NAMES.map((name) => { const bytes = Buffer.from(name); return { name, bytes, sha256: createHash("sha256").update(bytes).digest("hex") }; });`,
`const finalized = await publishPrivatePromotionStaging(runnerTempRoot, { id: "umask", attempt: 1 }, files, () => Buffer.alloc(16, 1));`,
`const directoryMode = (await lstat(finalized.stagingRoot)).mode & 0o777;`,
`const fileModes = await Promise.all(PROMOTED_FILE_NAMES.map(async (name) => (await lstat(path.join(finalized.stagingRoot, name))).mode & 0o777));`,
`await cleanupFinalizedPromotion({ runnerTempRoot, stagingRoot: finalized.stagingRoot, cleanupToken: finalized.cleanupToken, runnerTempIdentity: finalized.runnerTempIdentity, stagingIdentity: finalized.stagingIdentity });`,
`await writeFile(process.argv[4], JSON.stringify({ directoryMode, fileModes }));`,
].join("\n"),
);
const child = spawnSync(process.execPath, [childPath, "077", root, resultPath], {
cwd: root,
encoding: "utf8",
timeout: 30_000,
});
expect(child.status, `${child.stdout}\n${child.stderr}`).toBe(0);
expect(JSON.parse(await readFile(resultPath, "utf8"))).toEqual({
directoryMode: 0o700,
fileModes: [0o400, 0o400, 0o400, 0o400, 0o400],
});
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("rejects a staged file unlinked and recreated after its original write", async () => {
const root = await mkdtemp(path.join(tmpdir(), "promotion-seal-recreate-"));
const files = privatePromotionFiles();
const token = `promotion-seal-1-${"11".repeat(16)}`;
try {
await expect(
publishPrivatePromotionStaging(
root,
{ id: "seal", attempt: 1 },
files,
() => Buffer.alloc(16, 0x11),
undefined,
async (name) => {
if (name !== PROMOTED_FILE_NAMES.at(-1)) return;
const first = path.join(root, token, PROMOTED_FILE_NAMES[0]);
await rm(first);
await writeFile(first, "replacement bytes\n", { mode: 0o400 });
},
),
).rejects.toThrow(/staged.*digest|inode|seal/u);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("rejects staged mode drift before returning the upload root", async () => {
const root = await mkdtemp(path.join(tmpdir(), "promotion-seal-mode-"));
const files = privatePromotionFiles();
const token = `promotion-seal-1-${"12".repeat(16)}`;
try {
await expect(
publishPrivatePromotionStaging(
root,
{ id: "seal", attempt: 1 },
files,
() => Buffer.alloc(16, 0x12),
undefined,
async (name) => {
if (name === PROMOTED_FILE_NAMES.at(-1)) {
await chmod(path.join(root, token, PROMOTED_FILE_NAMES[0]), 0o600);
}
},
),
).rejects.toThrow(/mode.*0400|staged.*mode|seal/u);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("rejects staging leaf replacement between mkdir and descriptor open", async () => {
const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-replace-"));
const files = privatePromotionFiles();
const token = `promotion-preopen-1-${"13".repeat(16)}`;
const displaced = path.join(root, `${token}-displaced`);
const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]);
try {
await expect(
publishPrivatePromotionStaging(
root,
{ id: "preopen", attempt: 1 },
files,
() => Buffer.alloc(16, 0x13),
undefined,
undefined,
undefined,
async (stagingRoot) => {
await rename(stagingRoot, displaced);
await mkdir(stagingRoot, { mode: 0o700 });
await writeFile(replacementCanary, "external replacement canary\n");
},
),
).rejects.toThrow(/staging leaf.*changed|mkdir.*open|identity/u);
await expect(readFile(replacementCanary, "utf8")).resolves.toBe(
"external replacement canary\n",
);
await expect(readdir(displaced)).resolves.toEqual([]);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("does not scan a crowded parent to recover an unverified pre-open leaf", async () => {
const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-bounded-"));
const files = privatePromotionFiles();
const token = `promotion-preopen-bound-1-${"14".repeat(16)}`;
const displaced = path.join(root, `${token}-displaced`);
const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]);
try {
for (let offset = 0; offset < 4_097; offset += 128) {
await Promise.all(
Array.from(
{ length: Math.min(128, 4_097 - offset) },
(_, index) =>
mkdir(
path.join(
root,
`noise-${String(offset + index).padStart(4, "0")}`,
),
),
),
);
}
let failure: unknown;
try {
await publishPrivatePromotionStaging(
root,
{ id: "preopen-bound", attempt: 1 },
files,
() => Buffer.alloc(16, 0x14),
undefined,
undefined,
undefined,
async (stagingRoot) => {
await rename(stagingRoot, displaced);
await mkdir(stagingRoot, { mode: 0o700 });
await writeFile(replacementCanary, "external replacement canary\n");
},
);
} catch (error) {
failure = error;
}
expect(failure).toBeInstanceOf(Error);
expect(failure).not.toBeInstanceOf(AggregateError);
expect((failure as Error).message).toMatch(/staging leaf.*identity/i);
await expect(readdir(displaced)).resolves.toEqual([]);
await expect(readFile(replacementCanary, "utf8")).resolves.toBe(
"external replacement canary\n",
);
} finally {
await rm(root, { recursive: true, force: true });
}
}, 20_000);
it("leaves a non-empty moved original untouched after pre-open mismatch", async () => {
const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-nonempty-"));
const files = privatePromotionFiles();
const token = `promotion-preopen-nonempty-1-${"15".repeat(16)}`;
const displaced = path.join(root, `${token}-displaced`);
const ownedResidual = path.join(displaced, "owned-residual");
const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]);
try {
let failure: unknown;
try {
await publishPrivatePromotionStaging(
root,
{ id: "preopen-nonempty", attempt: 1 },
files,
() => Buffer.alloc(16, 0x15),
undefined,
undefined,
undefined,
async (stagingRoot) => {
await rename(stagingRoot, displaced);
await writeFile(ownedResidual, "owned residual\n");
await mkdir(stagingRoot, { mode: 0o700 });
await writeFile(replacementCanary, "external replacement canary\n");
},
);
} catch (error) {
failure = error;
}
expect(failure).toBeInstanceOf(Error);
expect(failure).not.toBeInstanceOf(AggregateError);
expect((failure as Error).message).toMatch(/staging leaf.*identity/i);
await expect(readFile(ownedResidual, "utf8")).resolves.toBe(
"owned residual\n",
);
await expect(readFile(replacementCanary, "utf8")).resolves.toBe(
"external replacement canary\n",
);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("does not search outside the parent for a moved unverified original", async () => {
const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-missing-"));
const outside = await mkdtemp(path.join(tmpdir(), "promotion-preopen-moved-"));
const files = privatePromotionFiles();
const token = `promotion-preopen-missing-1-${"16".repeat(16)}`;
const displaced = path.join(outside, token);
const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]);
try {
let failure: unknown;
try {
await publishPrivatePromotionStaging(
root,
{ id: "preopen-missing", attempt: 1 },
files,
() => Buffer.alloc(16, 0x16),
undefined,
undefined,
undefined,
async (stagingRoot) => {
await rename(stagingRoot, displaced);
await mkdir(stagingRoot, { mode: 0o700 });
await writeFile(replacementCanary, "external replacement canary\n");
},
);
} catch (error) {
failure = error;
}
expect(failure).toBeInstanceOf(Error);
expect(failure).not.toBeInstanceOf(AggregateError);
expect((failure as Error).message).toMatch(/staging leaf.*identity/i);
await expect(lstat(displaced)).resolves.toEqual(
expect.objectContaining({ dev: expect.any(Number), ino: expect.any(Number) }),
);
await expect(readFile(replacementCanary, "utf8")).resolves.toBe(
"external replacement canary\n",
);
} finally {
await rm(root, { recursive: true, force: true });
await rm(outside, { recursive: true, force: true });
}
});
it("rejects a fresh signed exact-five bundle replayed under a different expected run", async () => {
const fixture = syntheticSignedPromotionBundle();
await expect(
verifyExactPromotionBundle(fixture.files, {
...fixture.verification,
expected: {
...fixture.verification.expected,
run: { id: "different-run", attempt: 1 },
},
}),
).rejects.toThrow(/external expected run.*mismatch|expected promotion run/u);
});
it("requires every external expected identity variable at the exact promotion CLI", async () => {
const fixture = syntheticSignedPromotionBundle();
const root = await mkdtemp(path.join(tmpdir(), "promotion-replay-cli-"));
const bundleRoot = path.join(root, "bundle");
try {
await mkdir(bundleRoot);
for (const [name, bytes] of Object.entries(fixture.files)) {
await writeFile(path.join(bundleRoot, name), bytes);
}
await writeFile(path.join(root, "vulnerability.pem"), fixture.vulnerabilityPem);
await writeFile(path.join(root, "provenance.pem"), fixture.provenancePem);
const cliPath = path.join(process.cwd(), "scripts/verify-exact-promotion-bundle.ts");
const baseEnvironment: NodeJS.ProcessEnv = {
...process.env,
PROMOTION_BUNDLE_ROOT: bundleRoot,
VULNERABILITY_PUBLIC_KEY_PATH: "vulnerability.pem",
VULNERABILITY_KEY_ID: "synthetic-vulnerability",
PROVENANCE_PUBLIC_KEY_PATH: "provenance.pem",
PROVENANCE_KEY_ID: "synthetic-provenance",
EXPECTED_PROMOTION_RUN_ID: fixture.verification.expected.run.id,
EXPECTED_PROMOTION_RUN_ATTEMPT: String(
fixture.verification.expected.run.attempt,
),
EXPECTED_PROMOTION_SOURCE_REVISION:
fixture.verification.expected.sourceRevision,
EXPECTED_PROMOTION_ARCHIVE_SHA256:
fixture.verification.expected.archiveSha256,
};
const requiredExpected = [
"EXPECTED_PROMOTION_RUN_ID",
"EXPECTED_PROMOTION_RUN_ATTEMPT",
"EXPECTED_PROMOTION_SOURCE_REVISION",
"EXPECTED_PROMOTION_ARCHIVE_SHA256",
] as const;
for (const missing of requiredExpected) {
const environment = { ...baseEnvironment };
delete environment[missing];
const result = spawnSync(process.execPath, [cliPath], {
cwd: root,
encoding: "utf8",
env: environment,
});
expect(result.status, missing).not.toBe(0);
expect(result.stderr, missing).toContain(
`exact promotion verification environment is missing ${missing}`,
);
}
for (const [name, value, diagnostic] of [
["EXPECTED_PROMOTION_RUN_ID", "different-run", /external expected run.*mismatch/u],
["EXPECTED_PROMOTION_RUN_ATTEMPT", "2", /external expected run.*mismatch/u],
["EXPECTED_PROMOTION_SOURCE_REVISION", "f".repeat(40), /external expected source revision.*mismatch/u],
["EXPECTED_PROMOTION_ARCHIVE_SHA256", "0".repeat(64), /external expected archive digest.*mismatch/u],
] as const) {
const result = spawnSync(process.execPath, [cliPath], {
cwd: root,
encoding: "utf8",
env: { ...baseEnvironment, [name]: value },
});
expect(result.status, name).not.toBe(0);
expect(result.stderr, name).toMatch(diagnostic);
}
} finally {
await rm(root, { recursive: true, force: true });
}
}, PROCESS_HEAVY_TIMEOUT_MS);
});
@@ -0,0 +1,708 @@
import { generateKeyPairSync } from "node:crypto";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { EventEmitter } from "node:events";
import { describe, expect, it } from "vitest";
import {
evaluatePromotionEvidence,
providerPublicKeyFingerprint,
validateProviderEvidence,
} from "../../scripts/lib/provider-evidence.ts";
import { readProviderTrust } from "../../scripts/lib/provider-trust.ts";
import { superviseProviderEvidence } from "../../scripts/lib/provider-supervisor.ts";
import { runProviderProcess } from "../../scripts/lib/provider-process-runner.ts";
import { runStageVerifiedPromotionCli } from "../../scripts/lib/stage-verified-promotion-cli.ts";
import type { ReleaseCandidateManifest } from "../../scripts/lib/release-candidate.ts";
import {
digest,
providerExpectedContext,
providerUnsigned,
signedProviderV2,
trust,
} from "./security-followup-fixture.ts";
describe("security provider evidence contracts", () => {
it("accepts signed provider v2 evidence only for the exact run, source, archive, and nonce", () => {
const now = Date.parse("2026-08-02T01:00:00.000Z");
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const expected = providerExpectedContext();
const vulnerability = signedProviderV2(
{
source: expected.source,
candidate: expected.candidate,
schemaVersion: 2,
evidenceType: "vulnerability-report",
provider: "fixture-vulnerability",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...expected.run, invocationNonce: "1".repeat(64) },
secretScanAttestation: expected.secretScanAttestation,
findings: [],
},
"vulnerability-key",
vulnerabilityKeys.publicKey,
vulnerabilityKeys.privateKey,
);
const provenance = signedProviderV2(
{
source: expected.source,
candidate: expected.candidate,
schemaVersion: 2,
evidenceType: "provenance-attestation",
provider: "fixture-provenance",
signer: "fixture-workload",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...expected.run, invocationNonce: "2".repeat(64) },
subject: { name: "dist", digest: { sha256: expected.candidate.distSha256 } },
},
"provenance-key",
provenanceKeys.publicKey,
provenanceKeys.privateKey,
);
const result = evaluatePromotionEvidence({
expected: {
...expected,
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
},
localStatus: "PASS",
vulnerabilityReport: vulnerability,
provenanceAttestation: provenance,
vulnerabilityTrust: trust("vulnerability-key", vulnerabilityKeys.publicKey),
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
nowEpochMs: () => now,
});
expect(result).toEqual({
status: "PASS",
vulnerabilityStatus: "PASS",
provenanceAttestationStatus: "PASS",
failures: [],
});
const replayed = evaluatePromotionEvidence({
expected: {
...expected,
run: { id: expected.run.id, attempt: 2 },
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
},
localStatus: "PASS",
vulnerabilityReport: vulnerability,
provenanceAttestation: provenance,
vulnerabilityTrust: trust("vulnerability-key", vulnerabilityKeys.publicKey),
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
nowEpochMs: () => now,
});
expect(replayed.status).toBe("FAIL_UNVERIFIED");
expect(replayed.failures).toEqual(
expect.arrayContaining([
"vulnerability report run identity mismatch",
"provenance attestation run identity mismatch",
]),
);
});
it("rejects a signed vulnerability PASS when the captured SARIF attestation differs", () => {
const keys = generateKeyPairSync("ed25519");
const expected = providerExpectedContext();
const secretScanAttestation = {
status: "PASS" as const,
localEvidenceAssessmentSha256: digest("assessment"),
sourceSetSha256: expected.source.sourceSetSha256,
policySha256: digest("secret policy"),
sarifSha256: digest("real sarif"),
scanInputSha256: digest("scan input"),
};
const report = signedProviderV2(
{
schemaVersion: 2,
evidenceType: "vulnerability-report",
provider: "fixture-vulnerability",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...expected.run, invocationNonce: "1".repeat(64) },
source: expected.source,
candidate: expected.candidate,
secretScanAttestation,
findings: [],
},
"vulnerability-key",
keys.publicKey,
keys.privateKey,
);
const validated = validateProviderEvidence({
kind: "vulnerability",
value: report,
expected: {
...expected,
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
secretScanAttestation: {
...secretScanAttestation,
sarifSha256: digest("forged empty sarif"),
},
},
trust: trust("vulnerability-key", keys.publicKey),
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
});
expect(validated.status).toBe("FAIL_UNVERIFIED");
expect(validated.failures).toContain(
"vulnerability report secret scan attestation mismatch",
);
const forged = structuredClone(report);
forged.secretScanAttestation.sarifSha256 = digest("forged empty sarif");
const forgedValidation = validateProviderEvidence({
kind: "vulnerability",
value: forged,
expected: {
...expected,
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
secretScanAttestation: forged.secretScanAttestation,
},
trust: trust("vulnerability-key", keys.publicKey),
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
});
expect(forgedValidation.failures).toContain(
"vulnerability report signature verification failed",
);
const provenanceKeys = generateKeyPairSync("ed25519");
const provenance = signedProviderV2(
{
schemaVersion: 2,
evidenceType: "provenance-attestation",
provider: "fixture-provenance",
signer: "fixture-workload",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...expected.run, invocationNonce: "2".repeat(64) },
source: expected.source,
candidate: expected.candidate,
subject: { name: "dist", digest: { sha256: expected.candidate.distSha256 } },
},
"provenance-key",
provenanceKeys.publicKey,
provenanceKeys.privateKey,
);
const evaluated = evaluatePromotionEvidence({
expected: {
...expected,
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
secretScanAttestation: {
...secretScanAttestation,
sarifSha256: digest("forged empty sarif"),
},
},
localStatus: "PASS",
vulnerabilityReport: report,
provenanceAttestation: provenance,
vulnerabilityTrust: trust("vulnerability-key", keys.publicKey),
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
});
expect(evaluated.vulnerabilityStatus).toBe("FAIL_UNVERIFIED");
expect(evaluated.failures).toContain(
"vulnerability report secret scan attestation mismatch",
);
});
it.each(["vulnerability", "provenance"] as const)(
"rejects correctly re-signed %s v2 context/time/replay drift",
(kind) => {
const now = Date.parse("2026-08-02T01:00:00.000Z");
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const expected = providerExpectedContext();
const baseVulnerability = providerUnsigned("vulnerability", expected);
const baseProvenance = providerUnsigned("provenance", expected);
const validVulnerability = signedProviderV2(
baseVulnerability,
"vulnerability-key",
vulnerabilityKeys.publicKey,
vulnerabilityKeys.privateKey,
);
const validProvenance = signedProviderV2(
baseProvenance,
"provenance-key",
provenanceKeys.publicKey,
provenanceKeys.privateKey,
);
const rawCases: Array<readonly [
string,
(value: Record<string, any>) => Record<string, any>,
RegExp,
]> = [
["schema v1", (value) => ({ ...value, schemaVersion: 1 }), /missing or invalid/u],
[
"evidence type",
(value) => ({
...value,
evidenceType:
kind === "vulnerability"
? "provenance-attestation"
: "vulnerability-report",
}),
/missing or invalid/u,
],
...(["archiveSha256", "bundleSha256", "distSha256", "lockfileSha256"] as const).map(
(field) => [
`candidate ${field}`,
(value: Record<string, any>) => ({
...value,
candidate: { ...value.candidate, [field]: "f".repeat(64) },
...(kind === "provenance" && field === "distSha256"
? {
subject: {
name: "dist",
digest: { sha256: "f".repeat(64) },
},
}
: {}),
}),
/candidate identity|subject dist/u,
] as const,
),
[
"different archive with same dist and lockfile",
(value) => ({
...value,
candidate: { ...value.candidate, archiveSha256: "e".repeat(64) },
}),
/candidate identity/u,
],
[
"source revision",
(value) => ({ ...value, source: { ...value.source, revision: "c".repeat(40) } }),
/source identity/u,
],
[
"source set",
(value) => ({ ...value, source: { ...value.source, sourceSetSha256: "c".repeat(64) } }),
/source identity/u,
],
[
"run id",
(value) => ({ ...value, run: { ...value.run, id: "other-run" } }),
/run identity/u,
],
[
"run attempt replay",
(value) => ({ ...value, run: { ...value.run, attempt: 2 } }),
/run identity/u,
],
[
"different nonce",
(value) => ({ ...value, run: { ...value.run, invocationNonce: "3".repeat(64) } }),
/invocation nonce/u,
],
[
"missing nonce",
(value) => {
const run = { ...value.run };
delete run.invocationNonce;
return { ...value, run };
},
/missing or invalid/u,
],
[
"uppercase nonce",
(value) => ({ ...value, run: { ...value.run, invocationNonce: "A".repeat(64) } }),
/missing or invalid/u,
],
[
"short nonce",
(value) => ({ ...value, run: { ...value.run, invocationNonce: "1".repeat(62) } }),
/missing or invalid/u,
],
[
"issued future boundary",
(value) => ({ ...value, issuedAt: "2026-08-02T01:05:00.001Z" }),
/future skew/u,
],
[
"expiry equality",
(value) => ({ ...value, expiresAt: "2026-08-02T01:00:00.000Z" }),
/expired/u,
],
[
"expiry past",
(value) => ({ ...value, expiresAt: "2026-08-02T00:59:59.999Z" }),
/expired/u,
],
[
"zero lifetime",
(value) => ({
...value,
issuedAt: "2026-08-02T01:01:00.000Z",
expiresAt: "2026-08-02T01:01:00.000Z",
}),
/not positive/u,
],
[
"negative lifetime",
(value) => ({
...value,
issuedAt: "2026-08-02T01:02:00.000Z",
expiresAt: "2026-08-02T01:01:59.999Z",
}),
/not positive/u,
],
[
"lifetime above two hours",
(value) => ({
...value,
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T03:00:00.001Z",
}),
/exceeds two hours/u,
],
[
"wrong fingerprint",
(value) => ({
...value,
signature: {
...value.signature,
publicKeyFingerprint: `sha256:${"d".repeat(64)}`,
},
}),
/trust identity/u,
],
];
const cases = rawCases.map(([name, mutate, failure]) => ({
name,
mutate,
failure,
}));
for (const testCase of cases) {
const base = kind === "vulnerability" ? baseVulnerability : baseProvenance;
const mutated = testCase.mutate(structuredClone(base));
const resigned = signedProviderV2(
mutated,
kind === "vulnerability" ? "vulnerability-key" : "provenance-key",
kind === "vulnerability" ? vulnerabilityKeys.publicKey : provenanceKeys.publicKey,
kind === "vulnerability" ? vulnerabilityKeys.privateKey : provenanceKeys.privateKey,
"signature" in mutated && mutated.signature?.publicKeyFingerprint
? mutated.signature.publicKeyFingerprint
: undefined,
);
const result = evaluatePromotionEvidence({
expected: {
...expected,
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
},
localStatus: "PASS",
vulnerabilityReport:
kind === "vulnerability" ? resigned : validVulnerability,
provenanceAttestation:
kind === "provenance" ? resigned : validProvenance,
vulnerabilityTrust: trust("vulnerability-key", vulnerabilityKeys.publicKey),
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
nowEpochMs: () => now,
});
expect(result.status, testCase.name).toBe("FAIL_UNVERIFIED");
expect(result.failures.join("\n"), testCase.name).toMatch(testCase.failure);
}
},
);
it("canonicalizes provider fingerprints from DER SPKI across PEM wrapping and rejects Ed448", async () => {
const root = await mkdtemp(path.join(tmpdir(), "provider-fingerprint-"));
try {
const ed25519 = generateKeyPairSync("ed25519").publicKey;
const pem = ed25519.export({ type: "spki", format: "pem" }).toString();
const body = pem.replace(/-----[^-]+-----|\s/gu, "");
const wrapped = (width: number) =>
`-----BEGIN PUBLIC KEY-----\n${body.match(new RegExp(`.{1,${width}}`, "gu"))!.join("\n")}\n-----END PUBLIC KEY-----\n`;
await writeFile(path.join(root, "a.pem"), wrapped(64));
await writeFile(path.join(root, "b.pem"), wrapped(32));
const first = await readProviderTrust(root, "a.pem", "fixture-key");
const second = await readProviderTrust(root, "b.pem", "fixture-key");
expect(first?.publicKeyFingerprint).toBe(providerPublicKeyFingerprint(ed25519));
expect(second?.publicKeyFingerprint).toBe(first?.publicKeyFingerprint);
const ed448 = generateKeyPairSync("ed448").publicKey;
await writeFile(root + "/ed448.pem", ed448.export({ type: "spki", format: "pem" }));
await expect(readProviderTrust(root, "ed448.pem", "fixture-key")).resolves.toBeNull();
expect(() => providerPublicKeyFingerprint(ed448)).toThrow(/must be Ed25519/u);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("captures the downloaded archive pathname exactly once in the provider supervisor", async () => {
const keys = generateKeyPairSync("ed25519");
const expected = providerExpectedContext();
let captureCount = 0;
let receivedEnvironment: Readonly<Record<string, string>> | undefined;
const manifest: ReleaseCandidateManifest = {
schemaVersion: 1,
distSha256: expected.candidate.distSha256,
lockfileSha256: expected.candidate.lockfileSha256,
bundleSha256: expected.candidate.bundleSha256,
files: [{ path: "pnpm-lock.yaml", bytes: 1, sha256: expected.candidate.lockfileSha256 }],
};
const result = await superviseProviderEvidence(
{
kind: "vulnerability",
archivePath: "/downloads/candidate.tar.gz",
expectedArchiveSha256: expected.candidate.archiveSha256,
expectedRun: {
id: expected.run.id,
attempt: expected.run.attempt,
sourceRevision: expected.source.revision,
},
trust: trust("vulnerability-key", keys.publicKey),
executeProvider: async ({ environment }) => {
receivedEnvironment = environment;
},
captureReport: async () => Buffer.from("{}\n"),
},
{
captureArchive: async (input) => {
captureCount += 1;
expect(input).toEqual({
archivePath: "/downloads/candidate.tar.gz",
expectedSha256: expected.candidate.archiveSha256,
});
return {
bytes: Buffer.from("captured archive"),
archiveSha256: expected.candidate.archiveSha256,
};
},
withVerifiedCandidate: (async (input: any) =>
input.verify({ extractionRoot: "/captured/extraction", manifest })) as any,
verifyLocalEvidence: async () => ({
status: "PASS",
identity: {
sourceRevision: expected.source.revision,
sourceSetSha256: expected.source.sourceSetSha256,
assessmentSha256: digest("assessment"),
secretScan: {
policySha256: digest("provider secret policy"),
sarifSha256: digest("provider secret sarif"),
scanInputSha256: digest("provider secret input"),
},
},
failures: [],
}),
validateUpload: (async (input: any) => {
expect("archivePath" in input).toBe(false);
return { sealed: true };
}) as any,
randomBytes: () => Buffer.alloc(32, 0x11),
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
},
);
expect(captureCount).toBe(1);
expect(receivedEnvironment).toEqual(
expect.objectContaining({
PROVIDER_EVIDENCE_SCHEMA_VERSION: "2",
PROVIDER_INVOCATION_NONCE: "11".repeat(32),
PROVIDER_ISSUED_AT: "2026-08-02T01:00:00.000Z",
PROVIDER_EXPIRES_AT: "2026-08-02T02:00:00.000Z",
CI_RUN_ID: expected.run.id,
CI_RUN_ATTEMPT: "1",
SOURCE_REVISION: expected.source.revision,
CANDIDATE_ARCHIVE_SHA256: expected.candidate.archiveSha256,
}),
);
expect(result.evidence).toEqual({ sealed: true });
});
it("samples provider freshness after report capture instead of reusing issuance time", async () => {
const keys = generateKeyPairSync("ed25519");
const expected = providerExpectedContext();
const manifest: ReleaseCandidateManifest = {
schemaVersion: 1,
distSha256: expected.candidate.distSha256,
lockfileSha256: expected.candidate.lockfileSha256,
bundleSha256: expected.candidate.bundleSha256,
files: [{ path: "pnpm-lock.yaml", bytes: 1, sha256: expected.candidate.lockfileSha256 }],
};
const issuedSample = Date.parse("2026-08-02T01:00:00.000Z");
const validationSample = Date.parse("2026-08-02T02:00:00.001Z");
const samples = [issuedSample, validationSample];
let issuedAt = "";
await expect(
superviseProviderEvidence(
{
kind: "vulnerability",
archivePath: "/downloads/candidate.tar.gz",
expectedArchiveSha256: expected.candidate.archiveSha256,
expectedRun: {
id: expected.run.id,
attempt: expected.run.attempt,
sourceRevision: expected.source.revision,
},
trust: trust("vulnerability-key", keys.publicKey),
executeProvider: async ({ environment }) => {
issuedAt = environment.PROVIDER_ISSUED_AT!;
},
captureReport: async () => Buffer.from("{}\n"),
},
{
captureArchive: async () => ({
bytes: Buffer.from("captured archive"),
archiveSha256: expected.candidate.archiveSha256,
}),
withVerifiedCandidate: (async (input: any) =>
input.verify({ extractionRoot: "/captured/extraction", manifest })) as any,
verifyLocalEvidence: async () => ({
status: "PASS",
identity: {
sourceRevision: expected.source.revision,
sourceSetSha256: expected.source.sourceSetSha256,
assessmentSha256: digest("assessment"),
secretScan: {
policySha256: digest("provider secret policy"),
sarifSha256: digest("provider secret sarif"),
scanInputSha256: digest("provider secret input"),
},
},
failures: [],
}),
validateUpload: (async (input: any) => {
expect(input.nowEpochMs()).toBe(validationSample);
throw new Error("provider report expired during execution");
}) as any,
randomBytes: () => Buffer.alloc(32, 0x33),
nowEpochMs: () => samples.shift()!,
},
),
).rejects.toThrow(/expired during execution/u);
expect(issuedAt).toBe("2026-08-02T01:00:00.000Z");
});
it("kills a timed-out provider but settles only after the child closes", async () => {
const child = new EventEmitter() as EventEmitter & {
kill(signal: NodeJS.Signals): boolean;
};
let killedWith: NodeJS.Signals | undefined;
child.kill = (signal) => {
killedWith = signal;
return true;
};
let fireTimeout: (() => void) | undefined;
let settled = false;
const running = runProviderProcess(
{ executable: "/usr/bin/bwrap", arguments: [], environment: {}, timeoutMs: 1 },
{
spawnChild: () => child as any,
setTimer: (callback) => {
fireTimeout = callback;
return 1 as any;
},
clearTimer: () => undefined,
},
).finally(() => {
settled = true;
});
fireTimeout?.();
await Promise.resolve();
expect(killedWith).toBe("SIGKILL");
expect(settled).toBe(false);
child.emit("close", null, "SIGKILL");
await expect(running).rejects.toThrow(/timed out/u);
expect(settled).toBe(true);
});
it("captures process-group kill errors, attempts child fallback, and settles after close", async () => {
const child = new EventEmitter() as EventEmitter & {
pid: number;
kill(signal: NodeJS.Signals): boolean;
};
child.pid = 12_346;
let fallbackSignal: NodeJS.Signals | undefined;
child.kill = (signal) => {
fallbackSignal = signal;
return true;
};
let fireTimeout: (() => void) | undefined;
const running = runProviderProcess(
{ executable: "/usr/bin/bwrap", arguments: [], environment: {}, timeoutMs: 1 },
{
spawnChild: () => child as any,
setTimer: (callback) => {
fireTimeout = callback;
return 1 as any;
},
clearTimer: () => undefined,
killProcessGroup: () => {
throw Object.assign(new Error("group kill denied"), { code: "EPERM" });
},
},
);
expect(() => fireTimeout?.()).not.toThrow();
expect(fallbackSignal).toBe("SIGKILL");
child.emit("close", null, "SIGKILL");
await expect(running).rejects.toThrow(/timed out.*kill failed.*close/u);
});
it.each(["open failure", "partial write failure"])(
"cleans finalized staging from memory when GITHUB_OUTPUT has a %s",
async (failureKind) => {
const finalized = {
stagingRoot: "/runner/promotion-run-1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
cleanupToken: "promotion-run-1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
runnerTempIdentity: { dev: 10, ino: 20 },
stagingIdentity: { dev: 30, ino: 40 },
files: [],
} as const;
let cleanupInput: unknown;
let appendCalls = 0;
const environment = {
CANDIDATE_ARCHIVE_PATH: "candidate.tar.gz",
CANDIDATE_ARCHIVE_SHA256: "a".repeat(64),
VULNERABILITY_REPORT_PATH: "vulnerability.json",
PROVENANCE_ATTESTATION_PATH: "provenance.json",
VULNERABILITY_PUBLIC_KEY_PATH: "vulnerability.pem",
VULNERABILITY_KEY_ID: "vulnerability-key",
PROVENANCE_PUBLIC_KEY_PATH: "provenance.pem",
PROVENANCE_KEY_ID: "provenance-key",
CI_RUN_ID: "run",
CI_RUN_ATTEMPT: "1",
VITE_COMMIT_SHA: "b".repeat(40),
VULNERABILITY_INVOCATION_NONCE: "c".repeat(64),
PROVENANCE_INVOCATION_NONCE: "d".repeat(64),
RUNNER_TEMP: "/runner",
GITHUB_OUTPUT: "/runner/github-output",
};
await expect(
runStageVerifiedPromotionCli(environment, {
cwd: () => "/workspace",
finalize: async () => finalized as any,
appendOutput: async () => {
appendCalls += 1;
if (failureKind === "partial write failure") {
// The output sink accepted an unspecified prefix before rejecting.
}
throw new Error(failureKind);
},
cleanup: async (input) => {
cleanupInput = input;
},
writeStdout: () => undefined,
}),
).rejects.toThrow(new RegExp(failureKind, "u"));
expect(appendCalls).toBe(1);
expect(cleanupInput).toEqual({
runnerTempRoot: "/runner",
stagingRoot: finalized.stagingRoot,
cleanupToken: finalized.cleanupToken,
runnerTempIdentity: finalized.runnerTempIdentity,
stagingIdentity: finalized.stagingIdentity,
});
},
);
});
@@ -182,8 +182,8 @@ describe("selective Task 3 contract closure", () => {
it.skipIf(isReducedCiContractRun())("accepts only the canonical exact-count authority and rejects orphan retention", async () => {
const canonical = await loadCiGateContract(process.cwd());
expect(canonical.gates).toHaveLength(27);
expect(canonical.commands).toHaveLength(84);
expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(96);
expect(canonical.commands).toHaveLength(91);
expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(103);
expect(canonical.artifacts).toHaveLength(109);
expect(canonical.stages).toHaveLength(5);
expect(canonical.retention.classes).toHaveLength(5);