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"; import { diffRegistrySnapshots, validateBreakingEvidence, verifyRegistryBaselineApproval, } from "../../scripts/lib/registry-compatibility.ts"; type RegistryDefinition = Readonly<{ registryId: string; owner: string; requiredFields: string[]; fieldTypes: Record; path: string; exportName: string; rowsPath?: string; rowKeyFields?: string[]; uniqueFields?: string[]; uniqueFieldSets?: string[][]; snapshotProjection?: { singletonRowKey: string; canonicalArrayKeyFields: Record; }; consumers?: Array<{ path: string; token: string }>; breakingFields?: string[]; }>; describe("registry governance manifest", () => { 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(11); expect(new Set(registries.map((entry) => entry.registryId)).size).toBe( 11, ); expect(registries.every((entry) => entry.owner)).toBe(true); expect( registries.every( (entry) => Array.isArray(entry.requiredFields) && entry.requiredFields.length > 0 && entry.fieldTypes && Object.keys(entry.fieldTypes).length > 0, ), ).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", ], ], snapshotProjection: { singletonRowKey: "invalidation-graph", canonicalArrayKeyFields: { topics: ["$value"], namespaces: ["namespaceId", "namespaceVersion"], edges: [ "topicId", "namespace.namespaceId", "namespace.namespaceVersion", ], }, }, consumers: [ { path: "src/bootstrap/runtime-adapters.ts", token: "indexInvalidationRegistry(INVALIDATION_REGISTRY)", }, ], breakingFields: ["topics", "namespaces", "edges"], }); 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>; }>; }; expect( report.registries.find( (registry) => registry.registryId === "FIXTURE-INVALIDATION-EDGES", )?.rows, ).toEqual({ "invalidation-graph": { topics: ["orders", "qinv.fixture.changed"], namespaces: [ { namespaceId: "orders", namespaceVersion: 1 }, { namespaceId: "qinv.fixture.changed", namespaceVersion: 1 }, ], edges: [ { topicId: "orders", namespace: { namespaceId: "qinv.fixture.changed", namespaceVersion: 1, }, }, { topicId: "qinv.fixture.changed", namespace: { namespaceId: "orders", namespaceVersion: 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("requires explicit evidence for post-baseline graph and version drift", async () => { const directory = await mkdtemp( path.join(tmpdir(), "registry-governance-tamper-"), ); 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; contract: Record; rows: Record>; }>; }; const baseline = { schemaVersion: 2, registries: report.registries }; expect( baseline.registries.find( (registry) => registry.registryId === "FIXTURE-INVALIDATION-EDGES", )?.rows["invalidation-graph"], ).toBeDefined(); const tamperCases = [ { name: "topic", registryId: "FIXTURE-INVALIDATION-EDGES", field: "topics", apply(snapshot: typeof baseline) { const graph = snapshot.registries.find( (registry) => registry.registryId === this.registryId, )?.rows["invalidation-graph"]; (graph?.topics as string[])[0] = "tampered-topic"; }, }, { name: "namespace", registryId: "FIXTURE-INVALIDATION-EDGES", field: "namespaces", apply(snapshot: typeof baseline) { const graph = snapshot.registries.find( (registry) => registry.registryId === this.registryId, )?.rows["invalidation-graph"]; const namespaces = graph?.namespaces as Array<{ namespaceId: string; }>; namespaces[0]!.namespaceId = "tampered-namespace"; }, }, { name: "edge", registryId: "FIXTURE-INVALIDATION-EDGES", field: "edges", apply(snapshot: typeof baseline) { const graph = snapshot.registries.find( (registry) => registry.registryId === this.registryId, )?.rows["invalidation-graph"]; const edges = graph?.edges as Array<{ topicId: string }>; edges[0]!.topicId = "tampered-edge"; }, }, { name: "version", registryId: "FIXTURE-INVALIDATION-VERSIONS", field: "topicVersion", apply(snapshot: typeof baseline) { const versions = snapshot.registries.find( (registry) => registry.registryId === this.registryId, )?.rows; const firstVersion = versions?.[Object.keys(versions)[0] ?? ""]; if (!firstVersion) throw new Error("version fixture row missing"); firstVersion.topicVersion = 2; }, }, ]; for (const tamperCase of tamperCases) { const current = structuredClone(baseline); tamperCase.apply(current); const diff = diffRegistrySnapshots(baseline, current); expect(diff.impact, tamperCase.name).toBe("breaking"); expect( diff.changes.some((change) => change.kind === "registry-added"), tamperCase.name, ).toBe(false); const breakingChanges = diff.changes.filter( (change) => change.impact === "breaking", ); expect(breakingChanges, tamperCase.name).toEqual( expect.arrayContaining([ expect.objectContaining({ registryId: tamperCase.registryId, field: tamperCase.field, }), ]), ); expect( validateBreakingEvidence(diff, { changes: [] }).passed, tamperCase.name, ).toBe(false); expect( validateBreakingEvidence(diff, { changes: breakingChanges.map((change) => ({ changeId: change.changeId, versionBump: "2", migration: `migrate ${tamperCase.name}`, compatibilityWindow: "one release", rollback: `restore ${tamperCase.name} baseline`, owner: "fixture-owner", })), }).passed, tamperCase.name, ).toBe(true); } } finally { await rm(directory, { recursive: true, force: true }); } }); it("pins the full invalidation graph and versions in the approved repository baseline", async () => { const baseline = JSON.parse( await readFile("config/contracts/registry-baseline.json", "utf8"), ) as { schemaVersion: number; registries: Array<{ registryId: string; rows: Record>; }>; }; const approval = JSON.parse( await readFile( "config/contracts/registry-baseline.approval.json", "utf8", ), ) as Record; expect(verifyRegistryBaselineApproval(baseline, approval).passed).toBe(true); expect(baseline.registries).toHaveLength(11); const graph = baseline.registries.find( (registry) => registry.registryId === "FE-REG-QUERY-INVALIDATION", ); const versions = baseline.registries.find( (registry) => registry.registryId === "FE-REG-QUERY-INVALIDATION-TOPIC-VERSION", ); expect(graph?.rows["invalidation-graph"]).toMatchObject({ topics: expect.any(Array), namespaces: expect.any(Array), edges: expect.any(Array), }); expect(versions?.rows).toBeDefined(); expect(approval).toMatchObject({ owner: "tech-log-frontend", reason: "Install approved TechLog Public and Studio route contract", }); }); 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 }); } }); });