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"; type RegistryRow = Record; type RegistryRows = Record; type RegistryReference = Readonly<{ registryId: string; field: string; targetField: string; }>; type RegistryConsumer = Readonly<{ path: string; token: string }>; type RegistrySpecification = Readonly<{ registryId: string; owner: string; path: string; exportName: string; declaredRows?: unknown; requiredFields: readonly string[]; fieldTypes?: Readonly>; keyField?: string; uniqueFields?: readonly string[]; 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"), ); } 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}`); } if (!rows || typeof rows !== "object" || Array.isArray(rows)) { failures.push(`${specification.registryId} is not an object registry`); continue; } const registryRows = rows as RegistryRows; 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 (!(field in row)) { failures.push(`${specification.registryId}.${rowName} missing ${field}`); } } for (const [field, declaredType] of Object.entries( specification.fieldTypes ?? {}, )) { if ( field in row && !matchesDeclaredType(row[field], String(declaredType)) ) { failures.push( `${specification.registryId}.${rowName}.${field} expected ${declaredType}, received ${runtimeType(row[field])}`, ); } } if ( specification.keyField && row[specification.keyField] !== 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 value = row[field]; if (value === undefined) continue; 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 [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, row[field])) ) { failures.push( `${specification.registryId}.${rowName}.${field} has unknown value ${String(row[field])}`, ); } } } 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 ?? [], }); snapshots.push({ registryId: specification.registryId, owner: specification.owner, source: sourcePath ?? specification.path, rowCount: Object.keys(registryRows).length, contract, rows: canonicalizeRegistryValue(registryRows), }); } 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) => row[reference.targetField]) .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 = row[reference.field]; 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 = row[specification.consumerIdentityField]; 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, }; 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`, );