#!/usr/bin/env node import { spawnSync } from "node:child_process"; import { createHash } from "node:crypto"; import { mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import pg from "pg"; export const REPOSITORY_ROOT = path.resolve(import.meta.dirname, ".."); export const BASELINE_MANIFEST_PATH = path.join(REPOSITORY_ROOT, "prisma/baseline-legacy-manifest.json"); export const LEGACY_MIGRATION_ARCHIVE_PATH = path.join(REPOSITORY_ROOT, "prisma/baseline-legacy-migrations.tar.gz"); export const PRISMA_CLI_PATH = path.join(REPOSITORY_ROOT, "node_modules/prisma/build/index.js"); const IMPERSONATION_AUDIT_MIGRATION = "20260508140000_impersonation_audit"; const BOOKING_OVERLAP_MIGRATION = "20260717090000_ak_p16b_booking_overlap_constraint"; const TENANT_DELETION_MIGRATION = "20260721090000_ak_p21_tenant_deletion_capabilities"; const SUPPORT_RETENTION_MIGRATION = "20260721093000_ak_p21_support_retention_purge"; export const MIGRATION_TABLE_EXISTS_QUERY = ` SELECT to_regclass('public._prisma_migrations') IS NOT NULL AS "tableExists"; `; export const MIGRATION_HISTORY_QUERY = ` SELECT migration_name AS "name", checksum, finished_at AS "finishedAt", rolled_back_at AS "rolledBackAt" FROM public._prisma_migrations ORDER BY started_at, id; `; export const LEGACY_VARIANT_CATALOG_CHECKS = [ { id: "impersonation audit delete semantics", migrations: [IMPERSONATION_AUDIT_MIGRATION], query: ` SELECT (SELECT is_nullable = 'YES' FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'ImpersonationAudit' AND column_name = 'actorUserId') AND (SELECT is_nullable = 'YES' FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'ImpersonationAudit' AND column_name = 'targetUserId') AND (SELECT confdeltype = 'n' FROM pg_constraint WHERE conrelid = 'public."ImpersonationAudit"'::regclass AND conname = 'ImpersonationAudit_actorUserId_fkey') AND (SELECT confdeltype = 'n' FROM pg_constraint WHERE conrelid = 'public."ImpersonationAudit"'::regclass AND conname = 'ImpersonationAudit_targetUserId_fkey') AS "matches"; `, }, { id: "tenant-scoped booking overlap constraint", migrations: [BOOKING_OVERLAP_MIGRATION], query: ` SELECT COALESCE( (SELECT pg_get_constraintdef(oid, false) LIKE '%"tenantId" WITH =%' FROM pg_constraint WHERE conrelid = 'public."Appointment"'::regclass AND conname = 'appointment_no_overlap'), false ) AS "matches"; `, }, { id: "tenant deletion function ownership and ACLs", migrations: [TENANT_DELETION_MIGRATION], query: ` WITH expected(function_oid) AS ( VALUES ('public.hivegrid_purge_tenant_guarded_rows(text)'::regprocedure::oid), ('public.hivegrid_minimize_tenant_support_pii(text)'::regprocedure::oid) ) SELECT COUNT(*) = 2 AND BOOL_AND(pg_get_userbyid(proc.proowner) = 'hivegrid_rls_definer') AND BOOL_AND(has_function_privilege('hivegrid_worker', proc.oid, 'EXECUTE')) AND BOOL_AND(NOT has_function_privilege('hivegrid_app', proc.oid, 'EXECUTE')) AND BOOL_AND(NOT has_function_privilege('hivegrid_authentik_jit', proc.oid, 'EXECUTE')) AND BOOL_AND(NOT EXISTS ( SELECT 1 FROM aclexplode(COALESCE(proc.proacl, acldefault('f', proc.proowner))) AS acl WHERE acl.grantee = 0 AND acl.privilege_type = 'EXECUTE' )) AS "matches" FROM expected JOIN pg_proc AS proc ON proc.oid = expected.function_oid; `, }, { id: "support retention function ownership and ACLs", migrations: [SUPPORT_RETENTION_MIGRATION], query: ` SELECT pg_get_userbyid(proc.proowner) = 'hivegrid_rls_definer' AND has_function_privilege('hivegrid_worker', proc.oid, 'EXECUTE') AND NOT has_function_privilege('hivegrid_app', proc.oid, 'EXECUTE') AND NOT has_function_privilege('hivegrid_authentik_jit', proc.oid, 'EXECUTE') AND NOT EXISTS ( SELECT 1 FROM aclexplode(COALESCE(proc.proacl, acldefault('f', proc.proowner))) AS acl WHERE acl.grantee = 0 AND acl.privilege_type = 'EXECUTE' ) AS "matches" FROM pg_proc AS proc WHERE proc.oid = 'public.hivegrid_purge_tenant_retained_support(text)'::regprocedure; `, }, ]; export class BaselineAdoptionError extends Error { constructor(message, options) { super(message, options); this.name = "BaselineAdoptionError"; } } export function readBaselineManifest(manifestPath = BASELINE_MANIFEST_PATH) { return JSON.parse(readFileSync(manifestPath, "utf8")); } function assertManifest(manifest) { if ( manifest?.version !== 1 || typeof manifest.baselineMigration !== "string" || !/^[a-f0-9]{64}$/.test(manifest.baselineChecksum ?? "") || !Array.isArray(manifest.migrations) || manifest.legacyMigrationCount !== manifest.migrations.length ) { throw new BaselineAdoptionError("The Prisma baseline legacy manifest is malformed"); } const names = new Set(); for (const migration of manifest.migrations) { if (typeof migration?.name !== "string" || !/^[a-f0-9]{64}$/.test(migration.checksum ?? "") || names.has(migration.name)) { throw new BaselineAdoptionError("The Prisma baseline legacy manifest contains an invalid or duplicate migration"); } names.add(migration.name); } if (!Array.isArray(manifest.legacyChecksumVariants)) { throw new BaselineAdoptionError("The Prisma baseline legacy manifest is missing its reviewed checksum variants"); } const currentChecksums = new Map(manifest.migrations.map((migration) => [migration.name, migration.checksum])); const variantKeys = new Set(); for (const variant of manifest.legacyChecksumVariants) { const key = `${variant?.name}:${variant?.checksum}`; if ( !names.has(variant?.name) || !/^[a-f0-9]{64}$/.test(variant?.checksum ?? "") || !/^[a-f0-9]{40}$/.test(variant?.sourceCommit ?? "") || currentChecksums.get(variant.name) === variant.checksum || variantKeys.has(key) ) { throw new BaselineAdoptionError("The Prisma baseline legacy manifest contains an invalid checksum variant"); } variantKeys.add(key); } } function isSuccessful(row) { return row.finishedAt != null && row.rolledBackAt == null; } function isUnresolvedFailure(row) { return row.finishedAt == null && row.rolledBackAt == null; } function matchesReviewedChecksum(name, checksum, expectedChecksum, reviewedVariants) { return checksum === expectedChecksum || reviewedVariants.get(name)?.has(checksum); } export function classifyMigrationHistory({ tableExists, rows }, manifest) { assertManifest(manifest); if (!tableExists) return { mode: "fresh", detail: "migration table absent" }; if (!Array.isArray(rows)) throw new BaselineAdoptionError("Prisma migration history rows are unavailable"); if (rows.some(isUnresolvedFailure)) { throw new BaselineAdoptionError("Prisma migration history contains an unresolved failed migration"); } const successful = rows.filter(isSuccessful); const baselineRows = successful.filter((row) => row.name === manifest.baselineMigration); if (baselineRows.length > 0) { if (baselineRows.length !== 1 || baselineRows[0].checksum !== manifest.baselineChecksum) { throw new BaselineAdoptionError("The applied Prisma baseline does not match the committed baseline checksum"); } return { mode: "baseline", detail: "baseline already applied" }; } if (rows.length === 0) return { mode: "fresh", detail: "migration history empty" }; const expected = new Map(manifest.migrations.map((migration) => [migration.name, migration.checksum])); const reviewedVariants = new Map(); for (const variant of manifest.legacyChecksumVariants) { const checksums = reviewedVariants.get(variant.name) ?? new Set(); checksums.add(variant.checksum); reviewedVariants.set(variant.name, checksums); } const rowNames = new Set(rows.map((row) => row.name)); const unexpectedNames = [...rowNames].filter((name) => !expected.has(name)); if (unexpectedNames.length > 0) { throw new BaselineAdoptionError("Prisma migration history contains entries outside the reviewed legacy chain"); } const successfulByName = new Map(); for (const row of successful) { const entries = successfulByName.get(row.name) ?? []; entries.push(row); successfulByName.set(row.name, entries); } const variantNames = []; let successfulPrefixLength = 0; let gapFound = false; for (const [name, checksum] of expected) { const entries = successfulByName.get(name) ?? []; if (entries.length === 0) { gapFound = true; continue; } if (entries.length !== 1 || gapFound) { throw new BaselineAdoptionError("Prisma migration history is not an exact legacy prefix"); } if (!matchesReviewedChecksum(name, entries[0].checksum, checksum, reviewedVariants)) { throw new BaselineAdoptionError(`Prisma migration history checksum differs from the reviewed legacy chain (${name})`); } if (entries[0].checksum !== checksum) { variantNames.push(name); } successfulPrefixLength += 1; } if (successful.length !== successfulPrefixLength) { throw new BaselineAdoptionError("Prisma migration history contains duplicate successful legacy entries"); } if (successfulPrefixLength < manifest.migrations.length) { return { mode: "legacy-prefix", detail: "checksum-verified legacy prefix", variantNames, appliedMigrationCount: successfulPrefixLength, missingMigrations: manifest.migrations.slice(successfulPrefixLength).map((migration) => migration.name), }; } if (successful.length !== manifest.migrations.length) { throw new BaselineAdoptionError("Prisma migration history has an unexpected successful legacy migration count"); } return { mode: "legacy", detail: variantNames.length === 0 ? "complete reviewed legacy chain" : "complete reviewed legacy chain with historical checksums", variantNames, }; } function sha256(value) { return createHash("sha256").update(value).digest("hex"); } export function prepareLegacyMigrationArchive({ manifest, archivePath = LEGACY_MIGRATION_ARCHIVE_PATH, spawnImpl = spawnSync, tempRoot = os.tmpdir() } = {}) { assertManifest(manifest); if (!/^[a-f0-9]{64}$/.test(manifest.legacyArchiveChecksum ?? "") || sha256(readFileSync(archivePath)) !== manifest.legacyArchiveChecksum) { throw new BaselineAdoptionError("The Prisma legacy migration archive does not match the reviewed manifest"); } const temporaryDirectory = mkdtempSync(path.join(tempRoot, "hivegrid-legacy-catch-up-")); const cleanup = () => rmSync(temporaryDirectory, { recursive: true, force: true }); try { const extraction = spawnImpl("tar", ["-xzf", archivePath, "-C", temporaryDirectory], { cwd: REPOSITORY_ROOT, stdio: "inherit", }); if (extraction.error || extraction.status !== 0) { throw new BaselineAdoptionError("Could not extract the reviewed Prisma legacy migration archive", { cause: extraction.error, }); } const migrationsPath = path.join(temporaryDirectory, "prisma/migrations"); const migrationNames = readdirSync(migrationsPath, { withFileTypes: true }) .filter((entry) => entry.isDirectory()) .map((entry) => entry.name) .sort(); const expectedNames = manifest.migrations.map((migration) => migration.name); if (JSON.stringify(migrationNames) !== JSON.stringify(expectedNames)) { throw new BaselineAdoptionError("The Prisma legacy migration archive contains an unexpected migration set"); } for (const migration of manifest.migrations) { const sqlPath = path.join(migrationsPath, migration.name, "migration.sql"); if (sha256(readFileSync(sqlPath)) !== migration.checksum) { throw new BaselineAdoptionError(`The Prisma legacy migration archive checksum differs (${migration.name})`); } } return { migrationsPath, cleanup }; } catch (error) { cleanup(); throw error; } } export function deployMissingLegacyMigrations({ manifest, missingMigrations, env = process.env, prepareArchive = prepareLegacyMigrationArchive, spawnImpl = spawnSync, tempRoot = os.tmpdir(), } = {}) { const connectionString = env.DIRECT_URL?.trim(); if (!connectionString) throw new BaselineAdoptionError("DIRECT_URL is required for Prisma legacy catch-up"); const expectedMissing = manifest.migrations.slice(manifest.migrations.length - missingMigrations.length).map((migration) => migration.name); if (missingMigrations.length === 0 || JSON.stringify(missingMigrations) !== JSON.stringify(expectedMissing)) { throw new BaselineAdoptionError("Prisma legacy catch-up requires the exact missing manifest suffix"); } const prepared = prepareArchive({ manifest }); let configDirectory; try { configDirectory = mkdtempSync(path.join(tempRoot, "hivegrid-prisma-config-")); const configPath = path.join(configDirectory, "prisma.config.ts"); const prismaConfigUrl = pathToFileURL(path.join(REPOSITORY_ROOT, "node_modules/prisma/config.js")).href; writeFileSync( configPath, [ `import { defineConfig, env } from ${JSON.stringify(prismaConfigUrl)};`, "", "export default defineConfig({", ` schema: ${JSON.stringify(path.join(REPOSITORY_ROOT, "prisma/schema"))},`, ` migrations: { path: ${JSON.stringify(prepared.migrationsPath)} },`, ' datasource: { url: env("DIRECT_URL") },', "});", "", ].join("\n"), { encoding: "utf8", mode: 0o600 } ); const result = spawnImpl(process.execPath, [PRISMA_CLI_PATH, "migrate", "deploy", "--config", configPath], { cwd: REPOSITORY_ROOT, env, stdio: "inherit", }); if (result.error || result.status !== 0) { throw new BaselineAdoptionError("Prisma could not apply the missing reviewed legacy migration suffix", { cause: result.error, }); } } finally { if (configDirectory) rmSync(configDirectory, { recursive: true, force: true }); prepared.cleanup(); } } export function verifyPrismaSchemaParity({ env = process.env, spawnImpl = spawnSync } = {}) { const result = spawnImpl( process.execPath, [PRISMA_CLI_PATH, "migrate", "diff", "--from-config-datasource", "--to-schema", "prisma/schema", "--exit-code", "--config", "prisma.config.ts"], { cwd: REPOSITORY_ROOT, env, stdio: "inherit", } ); if (result.error || result.status === 1 || result.status == null) { throw new BaselineAdoptionError("Could not verify the live database schema after applying migrations", { cause: result.error, }); } if (result.status === 2) { throw new BaselineAdoptionError("Live database schema differs from the reviewed current Prisma schema after applying migrations"); } if (result.status !== 0) { throw new BaselineAdoptionError(`Live database schema verification returned unsupported exit status ${result.status}`); } } export async function verifyLegacyVariantCatalog({ variantNames, env = process.env, clientFactory = (connectionString) => new pg.Client({ connectionString, application_name: "hivegrid-prisma-baseline-variant-verification" }), } = {}) { const requiredChecks = LEGACY_VARIANT_CATALOG_CHECKS.filter((check) => check.migrations.some((migration) => variantNames.includes(migration))); if (requiredChecks.length === 0) return; const connectionString = env.DIRECT_URL?.trim(); if (!connectionString) throw new BaselineAdoptionError("DIRECT_URL is required for Prisma baseline variant verification"); const client = clientFactory(connectionString); try { await client.connect(); for (const check of requiredChecks) { const result = await client.query(check.query); if (result.rows[0]?.matches !== true) { throw new BaselineAdoptionError(`Live database does not satisfy the reviewed ${check.id} contract`); } } } catch (error) { if (error instanceof BaselineAdoptionError) throw error; throw new BaselineAdoptionError("Could not verify the live database catalog before adopting historical checksums", { cause: error, }); } finally { await client.end().catch(() => undefined); } } export async function inspectMigrationHistory({ env = process.env, clientFactory = (connectionString) => new pg.Client({ connectionString, application_name: "hivegrid-prisma-baseline-adoption" }), } = {}) { const connectionString = env.DIRECT_URL?.trim(); if (!connectionString) throw new BaselineAdoptionError("DIRECT_URL is required for Prisma baseline adoption"); const client = clientFactory(connectionString); try { await client.connect(); const tableState = await client.query(MIGRATION_TABLE_EXISTS_QUERY); const tableExists = tableState.rows[0]?.tableExists === true; if (!tableExists) return { tableExists: false, rows: [] }; const history = await client.query(MIGRATION_HISTORY_QUERY); return { tableExists: true, rows: history.rows }; } catch (error) { if (error instanceof BaselineAdoptionError) throw error; throw new BaselineAdoptionError("Could not inspect Prisma migration history; inspect restricted database logs", { cause: error }); } finally { await client.end().catch(() => undefined); } } export function resolveBaselineMigration({ manifest, env = process.env, spawnImpl = spawnSync } = {}) { const result = spawnImpl(process.execPath, [PRISMA_CLI_PATH, "migrate", "resolve", "--applied", manifest.baselineMigration, "--config", "prisma.config.ts"], { cwd: REPOSITORY_ROOT, env, stdio: "inherit", }); if (result.error || result.status !== 0) { throw new BaselineAdoptionError("Prisma could not record the reviewed baseline migration", { cause: result.error }); } } export async function adoptPrismaBaseline({ manifest = readBaselineManifest(), inspect = inspectMigrationHistory, deployMissing = deployMissingLegacyMigrations, verifyCatalog = verifyLegacyVariantCatalog, resolve = resolveBaselineMigration, stdout = process.stdout, } = {}) { let before = classifyMigrationHistory(await inspect(), manifest); if (before.mode === "fresh") { stdout.write("[baseline] fresh database; migrate deploy will apply the baseline\n"); return before; } if (before.mode === "baseline") { stdout.write("[baseline] committed baseline already recorded\n"); return before; } if (before.mode === "legacy-prefix") { stdout.write( `[baseline] applying ${before.missingMigrations.length} checksum-verified legacy migration(s) after ${before.appliedMigrationCount} applied entries\n` ); await deployMissing({ manifest, missingMigrations: before.missingMigrations }); before = classifyMigrationHistory(await inspect(), manifest); if (before.mode !== "legacy") { throw new BaselineAdoptionError("Prisma legacy catch-up did not produce the complete reviewed legacy chain"); } stdout.write("[baseline] legacy migration suffix applied and verified\n"); } if (before.variantNames.length > 0) { stdout.write(`[baseline] verifying ${before.variantNames.length} reviewed historical checksum variant catalog contract(s)\n`); await verifyCatalog({ variantNames: before.variantNames }); stdout.write("[baseline] security-sensitive historical catalog contracts verified\n"); } stdout.write(`[baseline] adopting ${manifest.baselineMigration} over the checksum-verified legacy chain\n`); await resolve({ manifest }); const after = classifyMigrationHistory(await inspect(), manifest); if (after.mode !== "baseline") { throw new BaselineAdoptionError("Prisma baseline adoption did not produce a verified baseline history entry"); } stdout.write("[baseline] adoption verified\n"); return { mode: "adopted", detail: before.detail }; } const isCli = process.argv[1] && path.join(import.meta.dirname, "adopt-prisma-baseline.mjs") === path.resolve(process.argv[1]); if (isCli) { try { const args = process.argv.slice(2); if (args.length === 0 || (args.length === 1 && args[0] === "--adopt")) { await adoptPrismaBaseline(); } else if (args.length === 1 && args[0] === "--verify-current-schema") { verifyPrismaSchemaParity(); process.stdout.write("[baseline] current live schema verified after migrations\n"); } else { throw new BaselineAdoptionError("Unsupported Prisma baseline adoption argument"); } } catch (error) { const message = error instanceof BaselineAdoptionError ? error.message : "Prisma baseline adoption failed without a safe diagnostic"; process.stderr.write(`FAIL ${message}\n`); process.exitCode = 1; } }