import { access, mkdir, readFile, readdir, writeFile, } from "node:fs/promises"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { canonicalizeRegistryValue, diffRegistrySnapshots, registrySnapshotDigest, validateBreakingEvidence, verifyRegistryBaselineApproval, } from "./lib/registry-compatibility.ts"; import { assertMatchesJsonSchema } from "./lib/json-schema.ts"; type RegistryRow = Record; type RegistryRows = Record; type RegistryReference = Readonly<{ registryId: string; field: string; targetField: string; }>; type RegistryConsumer = Readonly<{ path: string; token: string }>; type RegistrySnapshotProjection = Readonly<{ singletonRowKey: string; canonicalArrayKeyFields: Readonly>; }>; type RegistrySpecification = Readonly<{ registryId: string; owner: string; path: string; exportName: string; declaredRows?: unknown; rowsPath?: string; rowKeyFields?: readonly string[]; requiredFields: readonly string[]; fieldTypes?: Readonly>; keyField?: string; uniqueFields?: readonly string[]; uniqueFieldSets?: readonly (readonly string[])[]; snapshotProjection?: RegistrySnapshotProjection; allowedValues?: Readonly>; references?: readonly RegistryReference[]; breakingFields?: readonly string[]; consumers?: readonly RegistryConsumer[]; consumerIdentityField?: string; consumerDirectories?: readonly string[]; orphanExemptRows?: readonly string[]; }>; type RegistryGovernance = Readonly<{ registries: readonly RegistrySpecification[]; sourceDirectories?: readonly string[]; }>; type RegistrySnapshot = Record; type CompatibilitySummary = Readonly<{ impact: string; changes: readonly unknown[]; }>; function argumentValue( name: string, fallback: string | undefined, ): string | undefined { const index = process.argv.indexOf(name); return index >= 0 && process.argv[index + 1] ? process.argv[index + 1] : fallback; } const defaultGovernancePath = "config/contracts/registry-governance.json"; const governancePath = argumentValue("--governance", defaultGovernancePath) ?? defaultGovernancePath; const artifactPath = argumentValue("--artifact", "artifacts/quality/registries.json") ?? "artifacts/quality/registries.json"; const usesRepositoryBaseline = governancePath === defaultGovernancePath && !process.argv.includes("--no-baseline"); const baselinePath = argumentValue( "--baseline", usesRepositoryBaseline ? "config/contracts/registry-baseline.json" : undefined, ); const approvalPath = argumentValue( "--approval", usesRepositoryBaseline ? "config/contracts/registry-baseline.approval.json" : undefined, ); const evidencePath = argumentValue( "--compatibility-evidence", usesRepositoryBaseline ? "config/contracts/registry-change-evidence.json" : undefined, ); const governance = JSON.parse( await readFile(governancePath, "utf8"), ) as RegistryGovernance; const failures: string[] = []; const owners = new Map(); const snapshots: RegistrySnapshot[] = []; const rowsByRegistry = new Map(); const sourcesByRegistry = new Map(); const registryExtensions = [".ts", ".tsx", ".mts", ".cts"]; async function resolveRegistrySource( declaredPath: string, ): Promise { const extension = path.extname(declaredPath); const basePath = extension ? declaredPath.slice(0, -extension.length) : declaredPath; const candidates: string[] = []; for (const candidateExtension of registryExtensions) { const candidate = `${basePath}${candidateExtension}`; try { await access(candidate); candidates.push(candidate); } catch { // Continue through the supported TypeScript source extensions. } } if (candidates.length > 1) { failures.push( `ambiguous registry source ${declaredPath}: ${candidates.join(", ")}`, ); return null; } return candidates[0] ?? null; } function runtimeType(value: unknown): string { if (value === null) return "null"; if (Array.isArray(value)) return "array"; if (Number.isInteger(value)) return "integer"; return typeof value; } function matchesDeclaredType(value: unknown, declaration: string): boolean { const actual = runtimeType(value); return declaration .split("|") .some( (candidate) => candidate === actual || (candidate === "number" && actual === "integer"), ); } type FieldLookup = Readonly<{ found: boolean; value: unknown; }>; function lookupField(value: unknown, fieldPath: string): FieldLookup { let current = value; for (const segment of fieldPath.split(".")) { if ( !current || typeof current !== "object" || Array.isArray(current) || !Object.hasOwn(current, segment) ) { return Object.freeze({ found: false, value: undefined }); } current = (current as Record)[segment]; } return Object.freeze({ found: true, value: current }); } function projectRegistryRows( specification: RegistrySpecification, exportedValue: unknown, ): RegistryRows | null { const selected = specification.rowsPath ? lookupField(exportedValue, specification.rowsPath) : Object.freeze({ found: true, value: exportedValue }); if (!selected.found) { failures.push( `${specification.registryId} is missing rows path ${specification.rowsPath}`, ); return null; } if (!Array.isArray(selected.value)) { if ( !selected.value || typeof selected.value !== "object" ) { failures.push(`${specification.registryId} is not an object registry`); return null; } return selected.value as RegistryRows; } const projected: RegistryRows = {}; const keyOccurrences = new Map(); for (const [index, row] of selected.value.entries()) { if (!row || typeof row !== "object" || Array.isArray(row)) { projected[String(index)] = row as RegistryRow; continue; } const keyValues = (specification.rowKeyFields ?? []).map( (field) => lookupField(row, field).value, ); const baseKey = keyValues.length > 0 ? JSON.stringify(keyValues.map(canonicalizeRegistryValue)) : String(index); const occurrence = keyOccurrences.get(baseKey) ?? 0; keyOccurrences.set(baseKey, occurrence + 1); projected[occurrence === 0 ? baseKey : `${baseKey}#${occurrence + 1}`] = row as RegistryRow; } return projected; } function snapshotKeyField( value: unknown, fieldPath: string, ): FieldLookup { return fieldPath === "$value" ? Object.freeze({ found: true, value }) : lookupField(value, fieldPath); } function replaceProjectedField( row: RegistryRow, fieldPath: string, value: unknown, ): boolean { const segments = fieldPath.split("."); const finalSegment = segments.pop(); if (!finalSegment) return false; let current = row; for (const segment of segments) { const next = current[segment]; if (!next || typeof next !== "object" || Array.isArray(next)) { return false; } current = next as RegistryRow; } current[finalSegment] = value; return true; } function projectSnapshotRows( specification: RegistrySpecification, exportedValue: unknown, validatedRows: RegistryRows, ): RegistryRows | null { const projection = specification.snapshotProjection; if (!projection) return validatedRows; if ( !exportedValue || typeof exportedValue !== "object" || Array.isArray(exportedValue) ) { failures.push( `${specification.registryId} snapshot projection requires an object export`, ); return null; } const canonicalRow = canonicalizeRegistryValue(exportedValue) as RegistryRow; for (const [fieldPath, keyFields] of Object.entries( projection.canonicalArrayKeyFields, )) { const selected = lookupField(exportedValue, fieldPath); if (!selected.found || !Array.isArray(selected.value)) { failures.push( `${specification.registryId} snapshot field ${fieldPath} must be an array`, ); return null; } const keyedItems: Array<{ identity: string; value: unknown }> = []; const identities = new Set(); for (const [index, item] of selected.value.entries()) { const keyValues = keyFields.map((field) => snapshotKeyField(item, field)); const missingIndex = keyValues.findIndex((field) => !field.found); if (missingIndex >= 0) { failures.push( `${specification.registryId} snapshot field ${fieldPath}[${index}] is missing key ${keyFields[missingIndex]}`, ); return null; } const identity = JSON.stringify( keyValues.map((field) => canonicalizeRegistryValue(field.value)), ); if (identities.has(identity)) { failures.push( `${specification.registryId} snapshot field ${fieldPath} duplicates key ${identity}`, ); return null; } identities.add(identity); keyedItems.push({ identity, value: canonicalizeRegistryValue(item), }); } keyedItems.sort((left, right) => left.identity.localeCompare(right.identity), ); if ( !replaceProjectedField( canonicalRow, fieldPath, keyedItems.map((item) => item.value), ) ) { failures.push( `${specification.registryId} snapshot field ${fieldPath} cannot be projected`, ); return null; } } return Object.freeze({ [projection.singletonRowKey]: canonicalRow, }); } async function filesBelow(directory: string): Promise { try { const entries = await readdir(directory, { withFileTypes: true }); const groups = await Promise.all( entries.map((entry) => { const target = path.join(directory, entry.name); return entry.isDirectory() ? filesBelow(target) : [target]; }), ); return groups.flat().filter((file) => /\.(?:ts|tsx|mts|cts)$/.test(file), ); } catch { return []; } } for (const specification of governance.registries) { if (owners.has(specification.registryId)) { failures.push(`duplicate owner for ${specification.registryId}`); } owners.set(specification.registryId, specification.owner); let rows = specification.declaredRows; const sourcePath = await resolveRegistrySource(specification.path); try { if (!sourcePath) throw new Error("missing registry source"); const registryModule = (await import( `${pathToFileURL(path.resolve(sourcePath)).href}?registry-check=${Date.now()}` )) as Record; rows = registryModule[specification.exportName]; } catch { if (!rows) failures.push(`missing registry source ${specification.path}`); } const registryRows = projectRegistryRows(specification, rows); if (!registryRows) { continue; } rowsByRegistry.set(specification.registryId, registryRows); sourcesByRegistry.set( specification.registryId, sourcePath ?? specification.path, ); for (const [rowName, row] of Object.entries(registryRows)) { if (!row || typeof row !== "object" || Array.isArray(row)) { failures.push(`${specification.registryId}.${rowName} is not an object`); continue; } for (const field of specification.requiredFields) { if (!lookupField(row, field).found) { failures.push(`${specification.registryId}.${rowName} missing ${field}`); } } for (const [field, declaredType] of Object.entries( specification.fieldTypes ?? {}, )) { if ( lookupField(row, field).found && !matchesDeclaredType( lookupField(row, field).value, String(declaredType), ) ) { failures.push( `${specification.registryId}.${rowName}.${field} expected ${declaredType}, received ${runtimeType(lookupField(row, field).value)}`, ); } } if ( specification.keyField && lookupField(row, specification.keyField).value !== rowName ) { failures.push( `${specification.registryId}.${rowName}.${specification.keyField} must match its registry key`, ); } } for (const field of specification.uniqueFields ?? []) { const values = new Map(); for (const [rowName, row] of Object.entries(registryRows)) { if (!row || typeof row !== "object" || Array.isArray(row)) continue; const selected = lookupField(row, field); if (!selected.found) continue; const value = selected.value; const identity = JSON.stringify(canonicalizeRegistryValue(value)); if (values.has(identity)) { failures.push( `${specification.registryId}.${rowName} duplicates ${field}=${String(value)} from ${values.get(identity)}`, ); } else { values.set(identity, rowName); } } } for (const fields of specification.uniqueFieldSets ?? []) { const values = new Map(); for (const [rowName, row] of Object.entries(registryRows)) { if (!row || typeof row !== "object" || Array.isArray(row)) continue; const selected = fields.map((field) => lookupField(row, field)); if (selected.some((field) => !field.found)) continue; const identity = JSON.stringify( selected.map((field) => canonicalizeRegistryValue(field.value)), ); const previous = values.get(identity); if (previous) { failures.push( `${specification.registryId}.${rowName} duplicates ${fields.join("+")}=${identity} from ${previous}`, ); } else { values.set(identity, rowName); } } } for (const [field, allowed] of Object.entries( specification.allowedValues ?? {}, )) { for (const [rowName, row] of Object.entries(registryRows)) { if (!row || typeof row !== "object" || Array.isArray(row)) continue; if ( !allowed.some((value) => Object.is(value, lookupField(row, field).value), ) ) { failures.push( `${specification.registryId}.${rowName}.${field} has unknown value ${String(lookupField(row, field).value)}`, ); } } } const contract = Object.freeze({ requiredFields: specification.requiredFields, fieldTypes: specification.fieldTypes ?? {}, uniqueFields: specification.uniqueFields ?? [], allowedValues: specification.allowedValues ?? {}, references: specification.references ?? [], keyField: specification.keyField ?? null, breakingFields: specification.breakingFields ?? [], ...(specification.rowsPath ? { rowsPath: specification.rowsPath } : {}), ...(specification.rowKeyFields ? { rowKeyFields: specification.rowKeyFields } : {}), ...(specification.uniqueFieldSets ? { uniqueFieldSets: specification.uniqueFieldSets } : {}), ...(specification.snapshotProjection ? { snapshotProjection: specification.snapshotProjection } : {}), }); const snapshotRows = projectSnapshotRows(specification, rows, registryRows); if (!snapshotRows) continue; snapshots.push({ registryId: specification.registryId, owner: specification.owner, source: sourcePath ?? specification.path, rowCount: Object.keys(snapshotRows).length, contract, rows: canonicalizeRegistryValue(snapshotRows), }); } for (const specification of governance.registries) { const rows = rowsByRegistry.get(specification.registryId); if (!rows) continue; for (const reference of specification.references ?? []) { const targetRows = rowsByRegistry.get(reference.registryId); if (!targetRows) { failures.push( `${specification.registryId} references unknown registry ${reference.registryId}`, ); continue; } const targetValues = new Set( Object.values(targetRows) .filter((row) => row && typeof row === "object" && !Array.isArray(row)) .map((row) => lookupField(row, reference.targetField).value) .filter((value) => value !== undefined && value !== null), ); for (const [rowName, row] of Object.entries(rows)) { if (!row || typeof row !== "object" || Array.isArray(row)) continue; const value = lookupField(row, reference.field).value; if ( value !== undefined && value !== null && !targetValues.has(value) ) { failures.push( `${specification.registryId}.${rowName}.${reference.field} references unknown ${reference.registryId}.${reference.targetField}=${String(value)}`, ); } } } for (const consumer of specification.consumers ?? []) { try { const source = await readFile(consumer.path, "utf8"); if (!source.includes(consumer.token)) { failures.push( `${specification.registryId} consumer ${consumer.path} is missing ${consumer.token}`, ); } } catch { failures.push( `${specification.registryId} consumer source is missing: ${consumer.path}`, ); } } if (specification.consumerIdentityField) { const consumerFiles = ( await Promise.all( (specification.consumerDirectories ?? []).map(filesBelow), ) ).flat(); const sourcePath = sourcesByRegistry.get(specification.registryId); const consumerText = ( await Promise.all( consumerFiles .filter((file) => file !== sourcePath) .map((file) => readFile(file, "utf8")), ) ).join("\n"); const exemptions = new Set(specification.orphanExemptRows ?? []); for (const [rowName, row] of Object.entries(rows)) { if ( !row || typeof row !== "object" || Array.isArray(row) || exemptions.has(rowName) ) { continue; } const identity = lookupField( row, specification.consumerIdentityField, ).value; if ( (typeof identity !== "string" && typeof identity !== "number") || !consumerText.includes(String(identity)) ) { failures.push( `${specification.registryId}.${rowName} has no executable consumer for ${specification.consumerIdentityField}=${String(identity)}`, ); } } } } const sourceFiles = governance.sourceDirectories ?? [ "src/application", "src/presentation", "src/domain", ]; const adHocPatterns = [ { name: "direct fetch", expression: /\bfetch\s*\(/ }, { name: "direct localStorage", expression: /\blocalStorage\.(?:get|set|remove)Item/, }, { name: "direct import.meta.env", expression: /\bimport\.meta\.env\./ }, { name: "raw API path", expression: /["']\/api\// }, ]; for (const sourceDirectory of sourceFiles) { for (const file of await filesBelow(sourceDirectory)) { const content = await readFile(file, "utf8"); for (const pattern of adHocPatterns) { if (pattern.expression.test(content)) { failures.push(`ad-hoc ${pattern.name} in ${file}`); } } } } const currentSnapshot = canonicalizeRegistryValue({ schemaVersion: 2, registries: snapshots, }) as Readonly>; let baselineDigest: string | null = null; const currentDigest = registrySnapshotDigest(currentSnapshot); let compatibility: CompatibilitySummary = { impact: "not-evaluated", changes: [], }; if (baselinePath && approvalPath && evidencePath) { try { const baseline = JSON.parse( await readFile(baselinePath, "utf8"), ) as Record; const approval = JSON.parse( await readFile(approvalPath, "utf8"), ) as Record; const approvalResult = verifyRegistryBaselineApproval(baseline, approval); baselineDigest = approvalResult.actualDigest; if (!approvalResult.passed) { failures.push( `registry baseline approval digest mismatch: approved=${approvalResult.approvedDigest} actual=${approvalResult.actualDigest}`, ); } const registryDiff = diffRegistrySnapshots(baseline, currentSnapshot); compatibility = registryDiff; const evidence = JSON.parse( await readFile(evidencePath, "utf8"), ) as Record; const evidenceResult = validateBreakingEvidence(registryDiff, evidence); failures.push(...evidenceResult.failures); } catch (error) { failures.push( `registry compatibility evidence unavailable: ${ error instanceof Error ? error.name : "unknown" }`, ); } } const report = { schemaVersion: 2, generatedAt: new Date().toISOString(), baselineDigest, currentDigest, compatibility, failures, registries: snapshots, }; if (usesRepositoryBaseline && failures.length === 0) { try { assertMatchesJsonSchema( JSON.parse( await readFile( "schemas/artifacts/registry-snapshot.schema.json", "utf8", ), ), report, "registry snapshot", ); } catch { failures.push("registry snapshot JSON Schema mismatch"); } } await mkdir(path.dirname(artifactPath), { recursive: true }); await writeFile(artifactPath, `${JSON.stringify(report, null, 2)}\n`); if (failures.length > 0) { process.stderr.write(`Registry governance failed:\n${failures.join("\n")}\n`); process.exit(1); } process.stdout.write( `Registry governance: ${snapshots.length} registries PASS; compatibility=${compatibility.impact}\n`, );