fix: harden invalidation registry governance

This commit is contained in:
DongHyeonka
2026-08-01 23:59:11 +09:00
parent 73a50426d6
commit 0eb23875cb
15 changed files with 906 additions and 147 deletions
@@ -0,0 +1,141 @@
import { describe, expect, it, vi } from "vitest";
import { createRuntimeAdapters } from "../../../src/bootstrap/runtime-adapters.ts";
import { createRuntimeIdentityRegistry } from "../../../src/contracts/query-keys.ts";
import { bindQuery } from "../../../src/contracts/server-state.ts";
import type { CacheScopeSnapshot } from "../../../src/contracts/server-state-scope.ts";
import {
REFERENCE_FEATURE_ID,
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
} from "../../../src/features/reference-feature/contracts/reference-feature-contract.ts";
type Runtime = Parameters<typeof createRuntimeAdapters>[0]["runtime"];
type Release = Parameters<typeof createRuntimeAdapters>[0]["release"];
const runtime: Runtime = {
config: {
APP_ENV: "local",
API_BASE_URL: "http://localhost:8080",
TELEMETRY_ENABLED: false,
AUTH_MODE: "demo",
REQUEST_TIMEOUT_MS: 4321,
MAX_RETRY_ATTEMPTS: 0,
RELEASE_MANIFEST_URL: "/release-manifest.json",
CONFIG_SCHEMA_VERSION: "2.0",
CAPABILITY_OVERRIDES: {
REALTIME: "DEFAULT",
WEB_WORKER: "DEFAULT",
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
},
},
configSchema: "V2",
build: {
buildId: "build-a",
commitSha: "abc123",
routerBasePath: "/",
runtimeConfigUrl: "/config.json",
},
validationDurationMs: 0,
};
const release: Release = {
schemaVersion: 2,
appVersion: "0.1.0",
buildId: "build-a",
commitSha: "abc123",
configSchemaVersion: "2.0",
contractSet: {
setAlgorithm: "CA_CONTRACT_SET_V1",
setDigest: `sha256:${"0".repeat(64)}`,
packages: [],
},
assetManifestHash: "hash-a",
releaseId: "release-a",
builtAt: "2026-07-25T00:00:00Z",
routeChunks: { "route-home": "assets/home.js" },
};
function referenceBoundQueryKey() {
const scope: CacheScopeSnapshot = {
generation: 1,
fingerprint: "runtime-scope-fingerprint-0001",
identities: createRuntimeIdentityRegistry({
tokenFactory: () => "runtime-identity-token-0001",
}),
signal: new AbortController().signal,
isCurrent: () => true,
};
return bindQuery(
{
definitionId: "reference-resource-runtime-test-v1",
definitionVersion: 1,
owner: REFERENCE_FEATURE_ID,
namespace: "reference-resource",
namespaceVersion: 1,
operationId: "GET_REFERENCE_RESOURCE",
profileId: "DETAIL_STANDARD",
measureResult: () => ({ itemCount: 1, estimatedBytes: 8 }),
execute: async () => ({ ok: true as const, value: "reference-1" }),
},
"reference-1",
scope,
).queryKey;
}
describe("reference feature runtime composition", () => {
it("invalidates a real bound query through the installed production graph", async () => {
const adapters = await createRuntimeAdapters({ runtime, release, host: {} });
const queryKey = referenceBoundQueryKey();
adapters.infrastructure.queryClient.setQueryData(queryKey, {
resourceId: "reference-1",
});
await adapters.infrastructure.queryInvalidation.invalidate([
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
]);
expect(
adapters.infrastructure.queryClient.getQueryState(queryKey)?.isInvalidated,
).toBe(true);
adapters.infrastructure.dispose();
});
it("executes installed feature HTTP through the composed contract registry", async () => {
const fetcher = vi.fn(async () =>
Response.json([{ id: "reference-1", name: "Direct contract payload" }]),
);
const adapters = await createRuntimeAdapters({
runtime,
release,
host: {},
fetcher,
});
await adapters.outputPorts.session.beginSignIn();
await vi.waitFor(() =>
expect(adapters.infrastructure.serverStateScope.getPhase()).toBe("READY"),
);
await expect(
adapters.featureInputs[REFERENCE_FEATURE_ID].listResources({ limit: 20 }),
).resolves.toEqual({
ok: true,
value: [
{
resourceId: "reference-1",
title: "Direct contract payload",
createdAt: null,
},
],
});
expect(fetcher).toHaveBeenCalledWith(
"http://localhost:8080/api/reference-resources?limit=20",
expect.objectContaining({
method: "GET",
redirect: "error",
cache: "no-store",
}),
);
adapters.infrastructure.dispose();
});
});
@@ -0,0 +1,40 @@
{
"schemaVersion": 2,
"sourceDirectories": [],
"registries": [
{
"registryId": "FIXTURE-DUPLICATE-INVALIDATION-EDGES",
"path": "tests/fixtures/registry/invalidation/registries.ts",
"exportName": "DUPLICATE_INVALIDATION_REGISTRY",
"rowsPath": "edges",
"rowKeyFields": [
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion"
],
"owner": "fixture",
"requiredFields": [
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion"
],
"fieldTypes": {
"topicId": "string",
"namespace.namespaceId": "string",
"namespace.namespaceVersion": "integer"
},
"uniqueFieldSets": [
[
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion"
]
],
"breakingFields": [
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion"
]
}
]
}
+54
View File
@@ -0,0 +1,54 @@
{
"schemaVersion": 2,
"sourceDirectories": [],
"registries": [
{
"registryId": "FIXTURE-INVALIDATION-EDGES",
"path": "tests/fixtures/registry/invalidation/registries.ts",
"exportName": "INVALIDATION_REGISTRY",
"rowsPath": "edges",
"rowKeyFields": [
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion"
],
"owner": "fixture",
"requiredFields": [
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion"
],
"fieldTypes": {
"topicId": "string",
"namespace.namespaceId": "string",
"namespace.namespaceVersion": "integer"
},
"uniqueFieldSets": [
[
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion"
]
],
"breakingFields": [
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion"
]
},
{
"registryId": "FIXTURE-INVALIDATION-VERSIONS",
"path": "tests/fixtures/registry/invalidation/registries.ts",
"exportName": "INVALIDATION_TOPIC_VERSIONS",
"rowKeyFields": ["topicId"],
"owner": "fixture",
"requiredFields": ["topicId", "topicVersion"],
"fieldTypes": {
"topicId": "string",
"topicVersion": "integer"
},
"uniqueFields": ["topicId"],
"breakingFields": ["topicId", "topicVersion"]
}
]
}
+49
View File
@@ -0,0 +1,49 @@
const namespace = Object.freeze({
namespaceId: "orders",
namespaceVersion: 1,
});
const reverseNamespace = Object.freeze({
namespaceId: "qinv.fixture.changed",
namespaceVersion: 1,
});
export const INVALIDATION_REGISTRY = Object.freeze({
topics: Object.freeze(["qinv.fixture.changed", "orders"]),
namespaces: Object.freeze([namespace, reverseNamespace]),
edges: Object.freeze([
Object.freeze({
topicId: "qinv.fixture.changed",
namespace,
}),
Object.freeze({
topicId: "orders",
namespace: reverseNamespace,
}),
]),
});
export const INVALIDATION_TOPIC_VERSIONS = Object.freeze([
Object.freeze({
topicId: "qinv.fixture.changed",
topicVersion: 1,
}),
Object.freeze({
topicId: "orders",
topicVersion: 1,
}),
]);
export const DUPLICATE_INVALIDATION_REGISTRY = Object.freeze({
topics: INVALIDATION_REGISTRY.topics,
namespaces: INVALIDATION_REGISTRY.namespaces,
edges: Object.freeze([
INVALIDATION_REGISTRY.edges[0],
Object.freeze({
topicId: "qinv.fixture.changed",
namespace: Object.freeze({
namespaceId: "orders",
namespaceVersion: 1,
}),
}),
]),
});
+220
View File
@@ -0,0 +1,220 @@
import { z } from "zod";
import type {
CommandEffectDescriptor,
InstalledContractContribution,
InstalledHttpContract,
RuntimeValidator,
} from "../../src/contracts/external-contract-runtime.ts";
function zodValidator<T>(
schemaId: string,
schema: z.ZodType<T>,
): RuntimeValidator<T> {
return Object.freeze({
schemaId,
safeParse(value: unknown) {
const result = schema.safeParse(value);
if (result.success) {
return Object.freeze({ success: true as const, data: result.data });
}
return Object.freeze({
success: false as const,
issues: Object.freeze(
result.error.issues.map((issue) =>
Object.freeze({
path: Object.freeze(
issue.path.map((segment) =>
typeof segment === "number" ? segment : String(segment),
),
),
code: String(issue.code),
}),
),
),
});
},
});
}
const entitySchema = z
.object({
id: z.string().min(1),
name: z.string().min(1),
})
.strip();
const problemSchema = z
.object({
type: z.string().min(1),
title: z.string().min(1),
status: z.int().min(100).max(599),
})
.strip();
type TestProblem = z.output<typeof problemSchema>;
const problemValidator = zodValidator("TestProblem", problemSchema);
const readPolicy = Object.freeze({
policyId: "TEST_READ_V1",
requestByteLimit: 0,
responseByteLimit: 32_768,
totalDeadlineMs: 10_000,
retryBudget: 2 as const,
authProfileId: "TEST_AUTH",
diagnosticsOperation: "test.read",
});
export const TEST_LIST_HTTP_CONTRACT: InstalledHttpContract<
Readonly<{ limit: number }>,
readonly z.output<typeof entitySchema>[],
TestProblem
> = Object.freeze({
contract: Object.freeze({
operationId: "TEST_LIST_ENTITIES",
method: "GET" as const,
pathTemplate: "/api/test-entities",
inputValidator: zodValidator(
"TestEntityListQuery",
z
.object({
limit: z.coerce.number().int().min(1).max(100).default(20),
})
.strict(),
),
outputValidator: zodValidator(
"TestEntityListPayload",
z.array(entitySchema).max(100),
),
problemValidator,
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: Readonly<{ limit: number }>) {
return Object.freeze({
pathValues: Object.freeze({}),
queryEntries: Object.freeze([
Object.freeze(["limit", String(input.limit)] as const),
]),
body: null,
});
},
}),
frontend: readPolicy,
});
export const TEST_DETAIL_HTTP_CONTRACT: InstalledHttpContract<
Readonly<{ entityId: string }>,
z.output<typeof entitySchema>,
TestProblem
> = Object.freeze({
contract: Object.freeze({
operationId: "TEST_GET_ENTITY",
method: "GET" as const,
pathTemplate: "/api/test-entities/{entityId}",
inputValidator: zodValidator(
"TestEntityParams",
z.object({ entityId: z.string().min(1) }).strict(),
),
outputValidator: zodValidator("TestEntityPayload", entitySchema),
problemValidator,
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: Readonly<{ entityId: string }>) {
return Object.freeze({
pathValues: Object.freeze({ entityId: input.entityId }),
queryEntries: Object.freeze([]),
body: null,
});
},
}),
frontend: Object.freeze({
...readPolicy,
policyId: "TEST_DETAIL_V1",
diagnosticsOperation: "test.detail",
}),
});
const commandEffect: CommandEffectDescriptor<TestProblem> = Object.freeze({
successEffect: "APPLIED_CONFIRMED" as const,
classifyProblem({
status,
}: Readonly<{ status: number; problem: TestProblem }>) {
return status === 400 ? "NOT_APPLIED" : "MAYBE_APPLIED";
},
});
export const TEST_CREATE_HTTP_CONTRACT: InstalledHttpContract<
Readonly<{ name: string }>,
z.output<typeof entitySchema>,
TestProblem
> = Object.freeze({
contract: Object.freeze({
operationId: "TEST_CREATE_ENTITY",
method: "POST" as const,
pathTemplate: "/api/test-entities",
inputValidator: zodValidator(
"TestCreateEntityCommand",
z.object({ name: z.string().trim().min(1) }).strict(),
),
outputValidator: zodValidator("TestEntityPayload", entitySchema),
problemValidator,
acceptedStatuses: Object.freeze([201]),
emptyBodyStatuses: Object.freeze([]),
retrySemantics: "KEYED" as const,
requestBody: "JSON" as const,
responseBody: "REQUIRED_JSON" as const,
commandRecovery: Object.freeze({
mode: "IDEMPOTENCY_REPLAY" as const,
operationIdentityField: "idempotencyKey",
}),
commandEffect,
projectRequest(input: Readonly<{ name: string }>) {
return Object.freeze({
pathValues: Object.freeze({}),
queryEntries: Object.freeze([]),
body: Object.freeze({ ...input }),
});
},
}),
frontend: Object.freeze({
policyId: "TEST_CREATE_V1",
requestByteLimit: 32_768,
responseByteLimit: 32_768,
totalDeadlineMs: 10_000,
retryBudget: 0 as const,
authProfileId: "TEST_AUTH",
diagnosticsOperation: "test.create",
}),
});
export const TEST_CONTRACT_CONTRIBUTION: InstalledContractContribution =
Object.freeze({
contributionId: "test-http-v1",
featureId: "test-feature",
source: Object.freeze({
kind: "EXTERNAL_PACKAGE" as const,
package: Object.freeze({
packageId: "@test/contracts",
version: "1.0.0",
digest: `sha256:${"a".repeat(64)}`,
runtimeProtocolVersion: 1 as const,
sourceRevision: "abcdef1",
}),
}),
http: Object.freeze([
TEST_LIST_HTTP_CONTRACT,
TEST_DETAIL_HTTP_CONTRACT,
TEST_CREATE_HTTP_CONTRACT,
]) as readonly InstalledHttpContract<unknown, unknown, unknown>[],
events: Object.freeze([]),
});
+10 -10
View File
@@ -5,14 +5,14 @@ import {
composeContractContributions,
type InstalledContractContribution,
} from "../../src/contracts/external-contract-runtime.ts";
import { REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION } from "../../src/features/reference-feature/contracts/reference-feature-contract-contribution.ts";
import { TEST_CONTRACT_CONTRIBUTION } from "../helpers/external-contract-fixture.ts";
function contribution(
contributionId: string,
http: InstalledContractContribution["http"],
): InstalledContractContribution & Readonly<{ contributionId: string }> {
return Object.freeze({
...REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION,
...TEST_CONTRACT_CONTRIBUTION,
contributionId,
http: Object.freeze([...http]),
});
@@ -30,10 +30,10 @@ function captureContributionError(operation: () => unknown): ContractContributio
describe("external contract contribution composition", () => {
it("allows one feature to install multiple uniquely identified contributions", () => {
const operations = REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION.http;
const operations = TEST_CONTRACT_CONTRIBUTION.http;
const composed = composeContractContributions([
contribution("reference-read-contracts", operations.slice(0, 2)),
contribution("reference-command-contracts", operations.slice(2)),
contribution("test-read-contracts", operations.slice(0, 2)),
contribution("test-command-contracts", operations.slice(2)),
]);
expect(composed.contributions).toHaveLength(2);
@@ -42,12 +42,12 @@ describe("external contract contribution composition", () => {
it("rejects duplicate contribution identities even across different feature entries", () => {
const first = contribution(
"reference-contracts",
REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION.http.slice(0, 1),
"test-contracts",
TEST_CONTRACT_CONTRIBUTION.http.slice(0, 1),
);
const second = contribution(
"reference-contracts",
REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION.http.slice(1),
"test-contracts",
TEST_CONTRACT_CONTRIBUTION.http.slice(1),
);
const error = captureContributionError(() =>
@@ -69,7 +69,7 @@ describe("external contract contribution composition", () => {
});
it("rejects method and body vocabulary outside the runtime protocol", () => {
const installed = REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION.http[0]!;
const installed = TEST_CONTRACT_CONTRIBUTION.http[0]!;
const malformedOperation = {
...installed,
contract: { ...installed.contract, method: "TRACE" },
+8 -15
View File
@@ -2,15 +2,13 @@ import { describe, expect, it, vi } from "vitest";
import { createContractHttpExecutor } from "../../src/adapters/http/http-execution-v3.ts";
import type { InstalledHttpContract } from "../../src/contracts/external-contract-runtime.ts";
import { COMPOSED_CONTRACT_CONTRIBUTIONS } from "../../src/features/installed-contract-contributions.ts";
import {
TEST_CREATE_HTTP_CONTRACT,
TEST_LIST_HTTP_CONTRACT,
} from "../helpers/external-contract-fixture.ts";
const installed = (() => {
const candidate = COMPOSED_CONTRACT_CONTRIBUTIONS.httpByOperationId.get(
"LIST_REFERENCE_RESOURCES",
);
if (!candidate) throw new Error("reference list contract is not installed");
return candidate;
})();
const installed: InstalledHttpContract<unknown, unknown, unknown> =
TEST_LIST_HTTP_CONTRACT;
const scope = Object.freeze({
generation: 1,
@@ -20,13 +18,8 @@ const scope = Object.freeze({
isCurrent: () => true,
});
const createInstalled = (() => {
const candidate = COMPOSED_CONTRACT_CONTRIBUTIONS.httpByOperationId.get(
"CREATE_REFERENCE_RESOURCE",
);
if (!candidate) throw new Error("reference create contract is not installed");
return candidate;
})();
const createInstalled: InstalledHttpContract<unknown, unknown, unknown> =
TEST_CREATE_HTTP_CONTRACT;
function operation(
overrides: Readonly<{
@@ -106,6 +106,31 @@ describe("query invalidation registry", () => {
]);
});
it("snapshots a caller-owned namespace identity before indexing it", () => {
const mutableNamespace = {
namespaceId: "orders",
namespaceVersion: 1,
};
const index = indexInvalidationRegistry({
topics: [topic],
namespaces: [mutableNamespace],
edges: [{ topicId: topic, namespace: mutableNamespace }],
});
mutableNamespace.namespaceId = "mutated-orders";
mutableNamespace.namespaceVersion = 2;
const indexedNamespace = index.namespacesForTopic.get(topic)?.[0];
expect(indexedNamespace).toEqual({
namespaceId: "orders",
namespaceVersion: 1,
});
expect(Object.isFrozen(indexedNamespace)).toBe(true);
expect(
indexedNamespace && createQueryInvalidationPrefix(indexedNamespace),
).toEqual(["query", 2, "orders", 1]);
});
it("rejects duplicate namespace identities even when they are separate objects", () => {
const duplicate = defineQueryNamespaceIdentity("orders", 1);
+142 -4
View File
@@ -1,4 +1,7 @@
import { readFile } from "node:fs/promises";
import { spawnSync } from "node:child_process";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
type RegistryDefinition = Readonly<{
@@ -6,17 +9,25 @@ type RegistryDefinition = Readonly<{
owner: string;
requiredFields: string[];
fieldTypes: Record<string, string>;
path: string;
exportName: string;
rowsPath?: string;
rowKeyFields?: string[];
uniqueFields?: string[];
uniqueFieldSets?: string[][];
consumers?: Array<{ path: string; token: string }>;
breakingFields?: string[];
}>;
describe("registry governance manifest", () => {
it("declares nine typed, single-owner executable registries", async () => {
it("declares eleven typed, single-owner executable registries", async () => {
const governance = JSON.parse(
await readFile("config/contracts/registry-governance.json", "utf8"),
);
const registries = governance.registries as RegistryDefinition[];
expect(governance.registries).toHaveLength(9);
expect(governance.registries).toHaveLength(11);
expect(new Set(registries.map((entry) => entry.registryId)).size).toBe(
9,
11,
);
expect(registries.every((entry) => entry.owner)).toBe(true);
expect(
@@ -29,4 +40,131 @@ describe("registry governance manifest", () => {
),
).toBe(true);
});
it("governs the installed invalidation graph and wire versions at their real exports", async () => {
const governance = JSON.parse(
await readFile("config/contracts/registry-governance.json", "utf8"),
) as { registries: RegistryDefinition[] };
const byId = new Map(
governance.registries.map((registry) => [registry.registryId, registry]),
);
expect(byId.get("FE-REG-QUERY-INVALIDATION")).toMatchObject({
path: "src/features/installed-feature-contracts.ts",
exportName: "INVALIDATION_REGISTRY",
rowsPath: "edges",
rowKeyFields: [
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion",
],
uniqueFieldSets: [
[
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion",
],
],
consumers: [
{
path: "src/bootstrap/runtime-adapters.ts",
token: "indexInvalidationRegistry(INVALIDATION_REGISTRY)",
},
],
breakingFields: [
"topicId",
"namespace.namespaceId",
"namespace.namespaceVersion",
],
});
expect(byId.get("FE-REG-QUERY-INVALIDATION-TOPIC-VERSION")).toMatchObject({
path: "src/features/installed-feature-contracts.ts",
exportName: "INVALIDATION_TOPIC_VERSIONS",
rowKeyFields: ["topicId"],
uniqueFields: ["topicId"],
consumers: [
{
path: "src/bootstrap/runtime-adapters.ts",
token: "indexInvalidationTopicVersions(",
},
],
breakingFields: ["topicId", "topicVersion"],
});
});
it("projects graph and array exports into deterministic governed rows", async () => {
const directory = await mkdtemp(
path.join(tmpdir(), "registry-governance-projection-"),
);
const artifact = path.join(directory, "registries.json");
try {
const result = spawnSync(
process.execPath,
[
"scripts/check-registries.ts",
"--governance",
"tests/fixtures/registry/invalidation/governance.json",
"--artifact",
artifact,
"--no-baseline",
],
{ encoding: "utf8" },
);
expect(result.status, result.stderr).toBe(0);
const report = JSON.parse(await readFile(artifact, "utf8")) as {
registries: Array<{ registryId: string; rows: Record<string, unknown> }>;
};
expect(
report.registries.find(
(registry) => registry.registryId === "FIXTURE-INVALIDATION-EDGES",
)?.rows,
).toHaveProperty('["qinv.fixture.changed","orders",1]');
expect(
report.registries.find(
(registry) => registry.registryId === "FIXTURE-INVALIDATION-EDGES",
)?.rows,
).toHaveProperty('["orders","qinv.fixture.changed",1]');
expect(
report.registries.find(
(registry) => registry.registryId === "FIXTURE-INVALIDATION-VERSIONS",
)?.rows,
).toHaveProperty('["qinv.fixture.changed"]');
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("rejects duplicate composite invalidation edges after projection", async () => {
const directory = await mkdtemp(
path.join(tmpdir(), "registry-governance-duplicate-"),
);
const artifact = path.join(directory, "registries.json");
try {
const result = spawnSync(
process.execPath,
[
"scripts/check-registries.ts",
"--governance",
"tests/fixtures/registry/invalidation/duplicate-governance.json",
"--artifact",
artifact,
"--no-baseline",
],
{ encoding: "utf8" },
);
expect(result.status).toBe(1);
const report = JSON.parse(await readFile(artifact, "utf8")) as {
failures: string[];
};
expect(report.failures).toEqual([
expect.stringContaining(
"duplicates topicId+namespace.namespaceId+namespace.namespaceVersion",
),
]);
} finally {
await rm(directory, { recursive: true, force: true });
}
});
});
+4 -95
View File
@@ -4,14 +4,6 @@ import {
createRuntimeAdapters,
createRuntimeHttpClient,
} from "../../src/bootstrap/runtime-adapters.ts";
import { INVALIDATION_REGISTRY } from "../../src/features/installed-feature-contracts.ts";
import {
REFERENCE_FEATURE_ID,
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
} from "../../src/features/reference-feature/contracts/reference-feature-contract.ts";
import { createRuntimeIdentityRegistry } from "../../src/contracts/query-keys.ts";
import { bindQuery } from "../../src/contracts/server-state.ts";
import type { CacheScopeSnapshot } from "../../src/contracts/server-state-scope.ts";
import { TEST_HTTP_CONTRACT } from "../helpers/http-contract-fixture.ts";
type Runtime = Parameters<typeof createRuntimeAdapters>[0]["runtime"];
@@ -60,33 +52,6 @@ const release: Release = {
routeChunks: { "route-home": "assets/home.js" },
};
function referenceBoundQueryKey() {
const scope: CacheScopeSnapshot = {
generation: 1,
fingerprint: "runtime-scope-fingerprint-0001",
identities: createRuntimeIdentityRegistry({
tokenFactory: () => "runtime-identity-token-0001",
}),
signal: new AbortController().signal,
isCurrent: () => true,
};
return bindQuery(
{
definitionId: "reference-resource-runtime-test-v1",
definitionVersion: 1,
owner: REFERENCE_FEATURE_ID,
namespace: "reference-resource",
namespaceVersion: 1,
operationId: "GET_REFERENCE_RESOURCE",
profileId: "DETAIL_STANDARD",
measureResult: () => ({ itemCount: 1, estimatedBytes: 8 }),
execute: async () => ({ ok: true as const, value: "reference-1" }),
},
"reference-1",
scope,
).queryKey;
}
describe("runtime adapter composition", () => {
it("constructs the local demo seam and infrastructure adapters", async () => {
const adapters = await createRuntimeAdapters({
@@ -112,66 +77,11 @@ describe("runtime adapter composition", () => {
adapters.infrastructure.dispose();
});
it("invalidates a real bound query through the installed production graph", async () => {
const adapters = await createRuntimeAdapters({ runtime, release, host: {} });
const queryKey = referenceBoundQueryKey();
adapters.infrastructure.queryClient.setQueryData(queryKey, {
resourceId: "reference-1",
});
await adapters.infrastructure.queryInvalidation.invalidate([
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
]);
expect(
adapters.infrastructure.queryClient.getQueryState(queryKey)?.isInvalidated,
).toBe(true);
adapters.infrastructure.dispose();
});
it("executes installed feature HTTP through the composed contract registry", async () => {
const fetcher = vi.fn(async () =>
Response.json([{ id: "reference-1", name: "Direct contract payload" }]),
);
const adapters = await createRuntimeAdapters({
runtime,
release,
host: {},
fetcher,
});
await adapters.outputPorts.session.beginSignIn();
await vi.waitFor(() =>
expect(adapters.infrastructure.serverStateScope.getPhase()).toBe("READY"),
);
await expect(
adapters.featureInputs[REFERENCE_FEATURE_ID].listResources({ limit: 20 }),
).resolves.toEqual({
ok: true,
value: [
{
resourceId: "reference-1",
title: "Direct contract payload",
createdAt: null,
},
],
});
expect(fetcher).toHaveBeenCalledWith(
"http://localhost:8080/api/reference-resources?limit=20",
expect.objectContaining({
method: "GET",
redirect: "error",
cache: "no-store",
}),
);
adapters.infrastructure.dispose();
});
it("replaces the QueryClient and coordinator for each session generation", async () => {
const adapters = await createRuntimeAdapters({ runtime, release, host: {} });
const previousClient = adapters.infrastructure.queryClient;
const previousCoordinator = adapters.infrastructure.queryInvalidation;
const invalidatePrevious = vi.spyOn(previousClient, "invalidateQueries");
const clearPrevious = vi.spyOn(previousClient, "clear");
await adapters.outputPorts.session.beginSignIn();
@@ -182,10 +92,9 @@ describe("runtime adapter composition", () => {
previousCoordinator,
);
const topic = INVALIDATION_REGISTRY.topics[0];
if (!topic) throw new Error("expected an installed invalidation topic");
await previousCoordinator.invalidate([topic]);
expect(invalidatePrevious).not.toHaveBeenCalled();
const clearCallsAfterReplacement = clearPrevious.mock.calls.length;
await previousCoordinator.resetLocal();
expect(clearPrevious).toHaveBeenCalledTimes(clearCallsAfterReplacement);
adapters.infrastructure.dispose();
});