import { randomUUID } from "node:crypto" import { mkdir, open, readFile, readdir, lstat } from "node:fs/promises" import path from "node:path" import { Pool, type PoolClient } from "pg" import { env } from "../config.js" import { fingerprint, mcpStateDirectory, privateSecretFingerprint } from "../modules/mcp/security.js" import { analyzeMigration, MIGRATION_MANIFEST, migrationManifestDigest, validateMigrationFileSet } from "./migration-manifest.js" import { captureMigrationSchema, compareMigrationSchemas, type CapturedMigrationSchema } from "./migration-schema-inventory.js" const maximum = 32 * 1048576, domain = "database-controlled-schema-reference-v1" const filename = () => path.join(mcpStateDirectory(), "database-controlled-schema-reference-v1.json") export class ControlledSchemaReferenceProvisioningError extends Error { readonly automaticRetry = false constructor(readonly referenceDatabase: string, readonly phase: string, readonly publicationAttempted: boolean, options: ErrorOptions) { super("Controlled reference provisioning unconfirmed; inspect the exact owned database before manual cleanup", options) } } const scope = { applicationNamespaces: ["public"], excludedControlNamespaces: ["mmapp_control"] } const refuse = (reason: string): never => { throw new Error("Controlled schema reference refused: " + reason) } const sha = (value: unknown) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value) const identifier = (value: string) => '"' + value.replaceAll('"', '""') + '"' const literal = (value: string) => "'" + value.replaceAll("'", "''") + "'" interface Envelope { format: "controlled-schema-reference-v1"; manifestSha256: string; prefixLength: number; profileSha256: string; schema: CapturedMigrationSchema; canonicalSha256: string; signature: string; } const profile = (schema: CapturedMigrationSchema) => fingerprint({ serverVersion: schema.serverVersion, scope: schema.scope, database: schema.sections.database, namespaces: schema.sections.namespaces }) /** Fixed path and fixed issuer only. Supplied JSON or a digest never becomes a * reference. This proves provenance of a generated DDL reference, not data. */ export async function readControlledSchemaReference(): Promise { let handle try { handle = await open(filename(), "r") } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return null; throw error } let text: string try { const stat = await handle.stat() if (!stat.isFile() || stat.size < 1 || stat.size > maximum) return refuse("artifact bound") const bytes = Buffer.alloc(stat.size + 1); let length = 0 while (length < bytes.length) { const read = await handle.read(bytes, length, bytes.length - length, length); if (!read.bytesRead) break; length += read.bytesRead } if (length !== stat.size) return refuse("artifact changed while reading") text = new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length)) } finally { await handle.close() } const value = JSON.parse(text) as Envelope if (!value || Object.getPrototypeOf(value) !== Object.prototype || Object.keys(value).sort().join(" ") !== "canonicalSha256 format manifestSha256 prefixLength profileSha256 schema signature" || value.format !== "controlled-schema-reference-v1" || value.manifestSha256 !== migrationManifestDigest() || value.prefixLength !== MIGRATION_MANIFEST.length || !sha(value.profileSha256) || !sha(value.canonicalSha256) || !sha(value.signature)) return refuse("artifact envelope") compareMigrationSchemas(value.schema, value.schema) if (JSON.stringify(value.schema.scope) !== JSON.stringify(scope) || value.profileSha256 !== profile(value.schema)) return refuse("artifact profile") const { signature, canonicalSha256, ...payload } = value if (fingerprint(payload) !== canonicalSha256 || signature !== await privateSecretFingerprint(canonicalSha256, domain)) return refuse("artifact issuer proof") return structuredClone(value.schema) } async function snapshot(client: PoolClient): Promise { await client.query("BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY") try { await client.query("SET LOCAL search_path=pg_catalog") await client.query("SET LOCAL TimeZone='UTC'") await client.query("SET LOCAL DateStyle='ISO, YMD'") await client.query("SET LOCAL statement_timeout='5s'") const result = await captureMigrationSchema(client, { scope }) await client.query("ROLLBACK"); return result } catch (error) { await client.query("ROLLBACK").catch(() => {}); throw error } } const databaseProfileSql = `SELECT current_user AS role,pg_catalog.current_setting('server_version_num') AS version, pg_catalog.pg_encoding_to_char(d.encoding) AS encoding,d.datcollate AS collate,d.datctype AS ctype,d.datlocprovider::text AS provider, d.datlocale AS locale,d.daticurules AS rules,d.datcollversion AS collation_version,ts.spcname AS tablespace, d.datacl IS NULL AS default_acl,pg_catalog.shobj_description(d.oid,'pg_database') IS NULL AS no_comment, pg_catalog.pg_get_userbyid(d.datdba)=current_user AS owned_by_current FROM pg_catalog.pg_database d JOIN pg_catalog.pg_tablespace ts ON ts.oid=d.dattablespace WHERE d.datname=current_database()` /** Separate explicit provisioning workflow. Creates ONLY its own fresh UUID * database from template0, executes fixed pinned source there, closes it and * drops exactly that verified database BEFORE publishing an exclusive artifact. * No application data, application schema or control rows are changed. * This is never called by startup, restore, imports or a public MCP tool. */ export async function provisionControlledSchemaReference() { try { await lstat(filename()); return refuse("artifact already exists") } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error } const directory = new URL("../../drizzle/", import.meta.url) const entries = (await readdir(directory, { withFileTypes: true })).filter(entry => entry.name.toLowerCase().endsWith(".sql")) if (entries.some(entry => !entry.isFile())) return refuse("non-file migration source") validateMigrationFileSet(entries.map(entry => entry.name)) const files = await Promise.all(MIGRATION_MANIFEST.map(async pin => { const bytes = await readFile(new URL(pin.file, directory)); analyzeMigration(pin.file, bytes, pin); return { pin, sql: new TextDecoder("utf-8", { fatal: true }).decode(bytes) } })) const { pool } = await import("./index.js"), control = await pool.connect() const name = "mm_schema_reference_" + randomUUID().replaceAll("-", "") if (!/^mm_schema_reference_[a-f0-9]{32}$/.test(name)) return refuse("owned reference name") let reference: Pool | undefined, captured: CapturedMigrationSchema | undefined let phase = "profile", publicationAttempted = false try { await control.query("SET standard_conforming_strings=on") await control.query("SET statement_timeout='120s'") const rows = (await control.query(databaseProfileSql)).rows if (rows.length !== 1) return refuse("database profile missing") const meta = rows[0] if (!/^18\d{4}$/.test(meta.version) || !["b", "c", "i"].includes(meta.provider) || !meta.default_acl || !meta.no_comment || !meta.owned_by_current || [meta.role, meta.encoding, meta.collate, meta.ctype, meta.tablespace].some(value => typeof value !== "string" || !value || value.length > 256 || /[\x00-\x1f\x7f]/.test(value)) || [meta.locale, meta.rules, meta.collation_version].some(value => value !== null && (typeof value !== "string" || value.length > 4096 || /[\x00-\x1f\x7f]/.test(value)))) return refuse("unsupported database profile") const provider = { b: "builtin", c: "libc", i: "icu" }[meta.provider as "b" | "c" | "i"] let sql = `CREATE DATABASE ${identifier(name)} TEMPLATE template0 OWNER ${identifier(meta.role)} ENCODING ${literal(meta.encoding)} LOCALE_PROVIDER ${provider} LC_COLLATE ${literal(meta.collate)} LC_CTYPE ${literal(meta.ctype)} TABLESPACE ${identifier(meta.tablespace)}` if (meta.provider === "b" || meta.provider === "i") { if (!meta.locale) return refuse("provider locale missing"); sql += ` ${meta.provider === "b" ? "BUILTIN_LOCALE" : "ICU_LOCALE"} ${literal(meta.locale)}` } if (meta.provider === "i" && meta.rules !== null) sql += " ICU_RULES " + literal(meta.rules) phase = "create-unknown" await control.query(sql); phase = "created" const url = new URL(env.DATABASE_URL); url.pathname = "/" + name reference = new Pool({ connectionString: url.toString(), max: 1, connectionTimeoutMillis: 8000, statement_timeout: 120000 }) const client = await reference.connect() let clean = false try { if (fingerprint((await client.query(databaseProfileSql)).rows[0]) !== fingerprint(meta)) return refuse("generated database profile mismatch") await client.query("SET search_path=public,pg_catalog") await client.query("CREATE SCHEMA mmapp_control") for (const { pin, sql } of files) { if (pin.transaction === "owner-transaction") await client.query("BEGIN") const executed = await client.query(sql) if (!pin.selfMarker) await client.query("INSERT INTO public.schema_migrations(id) VALUES($1)", [pin.file.slice(0, -4)]) const committed = pin.transaction === "owner-transaction" ? await client.query("COMMIT") : Array.isArray(executed) ? executed.at(-1) : executed if (committed?.command !== "COMMIT") return refuse("reference file COMMIT not acknowledged") } const markers = (await client.query(`SELECT id FROM public.schema_migrations ORDER BY id LIMIT ${MIGRATION_MANIFEST.length + 1}`)).rows if (fingerprint(markers) !== fingerprint(MIGRATION_MANIFEST.map(pin => ({ id: pin.file.slice(0, -4) })))) return refuse("reference markers mismatch") captured = await snapshot(client); clean = true } finally { client.release(!clean) } await reference.end(); reference = undefined const owned = (await control.query("SELECT datname,pg_catalog.pg_get_userbyid(datdba)=current_user AS owned FROM pg_catalog.pg_database WHERE datname=$1", [name])).rows if (owned.length !== 1 || owned[0].datname !== name || owned[0].owned !== true) return refuse("reference cleanup ownership changed") phase = "drop-unknown" await control.query("DROP DATABASE " + identifier(name)); phase = "dropped" const payload = { format: "controlled-schema-reference-v1" as const, manifestSha256: migrationManifestDigest(), prefixLength: MIGRATION_MANIFEST.length, profileSha256: profile(captured), schema: captured } const canonicalSha256 = fingerprint(payload), signature = await privateSecretFingerprint(canonicalSha256, domain) const bytes = Buffer.from(JSON.stringify({ ...payload, canonicalSha256, signature }) + "\n") if (bytes.length > maximum) return refuse("generated artifact too large") await mkdir(mcpStateDirectory(), { recursive: true, mode: 0o700 }) publicationAttempted = true const output = await open(filename(), "wx", 0o600) try { await output.writeFile(bytes); await output.sync() } finally { await output.close() } return Object.freeze({ manifestSha256: payload.manifestSha256, logicalSchemaSha256: captured.sha256, referenceDatabaseDropped: true, readyAuthorized: false }) } catch (cause) { throw new ControlledSchemaReferenceProvisioningError(name, phase, publicationAttempted, { cause }) } finally { if (reference) await reference.end().catch(() => {}) // A failed or uncertain CREATE/DROP is never retried or force-dropped. The // exact owned UUID may remain for an operator; no artifact is published. control.release(true) } }