import { createHash } from "node:crypto"; import { mkdir, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs"; import { PostgresCloudRuntimeStore, RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED, RUNTIME_DURABLE_ADAPTER_DRIVER_MISSING, RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED, RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED, RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED, RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED, buildPostgresPoolConfig } from "../../internal/db/runtime-store.mjs"; import { ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const defaultReportPath = "reports/dev-gate/dev-runtime-provisioning-report.json"; const issue = "pikasTech/HWLAB#311"; export const DEV_DB_ADMIN_SECRET_REF = Object.freeze({ env: "HWLAB_CLOUD_DB_ADMIN_URL", secretName: "hwlab-cloud-api-dev-db-admin", secretKey: "admin-url" }); const appSecretRef = DEV_DB_ENV_CONTRACT.secretRefs[0]; const systemDatabases = new Set(["postgres", "template0", "template1"]); export async function runDevRuntimeProvisioningCli(argv = process.argv.slice(2), options = {}) { const args = parseArgs(argv); if (args.help) { process.stdout.write(`${usage()}\n`); return 0; } const report = await buildDevRuntimeProvisioningReport(args, options); if (args.writeReport) { await writeReport(report, args.reportPath, options.repoRoot ?? repoRoot); } const printable = args.pretty || args.writeReport ? report : summarizeReport(report); process.stdout.write(`${JSON.stringify(printable, null, 2)}\n`); if (report.safetyRefusal || (args.failOnBlocked && report.conclusion.status !== "ready")) { return 1; } return 0; } export async function buildDevRuntimeProvisioningReport(args = {}, options = {}) { const parsed = normalizeArgs(args); const env = options.env ?? process.env; const now = options.now ?? (() => new Date().toISOString()); const target = parseProvisioningTarget(env.HWLAB_CLOUD_DB_URL); const admin = summarizeAdminEnv(env); const report = { issue, mode: parsed.mode, generatedAt: now(), target: { environment: ENVIRONMENT_DEV, namespace: DEV_DB_ENV_CONTRACT.dns.namespace, prodAllowed: false, dbUrlEnv: appSecretRef.env, dbUrlSecretRef: appSecretRef, adminUrlEnv: DEV_DB_ADMIN_SECRET_REF.env, adminUrlSecretRef: DEV_DB_ADMIN_SECRET_REF, dbEndpointAuthority: DEV_DB_ENV_CONTRACT.endpointAuthority.source }, db: summarizeDbEnv(env, target, admin), applyBoundary: buildApplyBoundary(parsed), safety: buildSafety(parsed), actions: { liveDbReadAttempted: false, liveDbWriteAttempted: false, adminInspectionAttempted: false, adminWriteAttempted: false, appRuntimeReadinessAttempted: false, roleCreated: false, roleExisted: false, rolePasswordSynchronized: false, databaseCreated: false, databaseExisted: false, databaseConnectGranted: false, schemaPrivilegesGranted: false, readinessVerified: false }, provisioning: { targetRole: emptyObjectState(target.roleNamePresent), targetDatabase: emptyObjectState(target.databaseNamePresent), privileges: { checked: false, ready: false, databaseConnectGranted: false, schemaPrivilegesGranted: false }, ready: false }, runtime: null, gates: sourceOnlyGates(target), downstreamRuntimeGates: null, blockers: [], safetyRefusal: false }; validateSourceContract(report, target, parsed); if (parsed.mode === "check" || (parsed.mode === "dry-run" && !parsed.allowLiveDbRead)) { return finalizeReport(report); } if (parsed.mode === "dry-run") { if (!parsed.confirmDev) { addSafetyRefusal(report, "DEV DB provisioning live dry-run requires --confirm-dev."); return finalizeReport(report); } if (!target.ok) { return finalizeReport(report); } await verifyRuntimeReadiness(report, env, options); if (admin.present || options.adminClient) { await inspectAdminProvisioning(report, env, target, options); } else { inferProvisioningFromRuntime(report); if (!report.provisioning.ready) { addBlocker(report, "runtime_blocker", "runtime-provisioning-admin", "Role/database inspection requires the DEV admin SecretRef env when runtime readiness is not already green.", { adminUrlEnv: DEV_DB_ADMIN_SECRET_REF.env, adminUrlSecretRef: DEV_DB_ADMIN_SECRET_REF, valueRedacted: true, secretValuesPrinted: false }); } } addRuntimeProvisioningBlocker(report); return finalizeReport(report); } if (parsed.mode === "apply") { if (!parsed.confirmDev || !parsed.confirmedNonProduction) { addSafetyRefusal(report, "DEV DB provisioning apply requires --confirm-dev and --confirmed-non-production."); return finalizeReport(report); } if (!target.ok) { return finalizeReport(report); } if (!admin.present && !options.adminClient) { addBlocker(report, "runtime_blocker", "runtime-provisioning-admin", "DEV DB provisioning apply requires the admin DB URL from its SecretRef env.", { adminUrlEnv: DEV_DB_ADMIN_SECRET_REF.env, adminUrlSecretRef: DEV_DB_ADMIN_SECRET_REF, valueRedacted: true, secretValuesPrinted: false }); return finalizeReport(report); } await applyProvisioning(report, env, target, options); if (report.actions.adminWriteAttempted && report.blockers.length === 0) { await verifyRuntimeReadiness(report, env, options); inferProvisioningFromRuntime(report); addRuntimeProvisioningBlocker(report); } return finalizeReport(report); } addSafetyRefusal(report, `Unsupported DEV DB provisioning mode: ${parsed.mode}`); return finalizeReport(report); } export function parseArgs(argv = []) { const args = { mode: "check", allowLiveDbRead: false, confirmDev: false, confirmedNonProduction: false, writeReport: false, reportPath: defaultReportPath, pretty: false, failOnBlocked: false, help: false }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === "--check") args.mode = "check"; else if (arg === "--dry-run") args.mode = "dry-run"; else if (arg === "--apply") args.mode = "apply"; else if (arg === "--allow-live-db-read") args.allowLiveDbRead = true; else if (arg === "--confirm-dev") args.confirmDev = true; else if (arg === "--confirmed-non-production") args.confirmedNonProduction = true; else if (arg === "--write-report") args.writeReport = true; else if (arg === "--pretty") args.pretty = true; else if (arg === "--fail-on-blocked") args.failOnBlocked = true; else if (arg === "--help" || arg === "-h") args.help = true; else if (arg === "--report") { index += 1; args.reportPath = requireArgValue(argv[index], "--report"); } else if (arg.startsWith("--report=")) { args.reportPath = requireArgValue(arg.slice("--report=".length), "--report"); } else { throw new Error(`unknown argument: ${arg}`); } } return args; } export function parseProvisioningTarget(rawUrl) { const present = typeof rawUrl === "string" && rawUrl.trim().length > 0; if (!present) { return { ok: false, present: false, parseStatus: "missing", errorCode: "TARGET_DB_URL_MISSING", roleNamePresent: false, databaseNamePresent: false, passwordPresent: false }; } let url; try { url = new URL(rawUrl); } catch { return { ok: false, present: true, parseStatus: "invalid_url", errorCode: "TARGET_DB_URL_INVALID", roleNamePresent: false, databaseNamePresent: false, passwordPresent: false }; } const protocolReady = ["postgres:", "postgresql:"].includes(url.protocol); const roleName = decodeURIComponent(url.username || ""); const password = decodeURIComponent(url.password || ""); const databaseName = decodeURIComponent(url.pathname.replace(/^\/+/u, "")); const databaseAllowed = Boolean(databaseName) && !systemDatabases.has(databaseName.toLowerCase()); const ok = protocolReady && Boolean(roleName) && Boolean(password) && databaseAllowed; return { ok, present: true, parseStatus: ok ? "ready" : "blocked", errorCode: ok ? null : targetParseErrorCode({ protocolReady, roleName, password, databaseName, databaseAllowed }), roleNamePresent: Boolean(roleName), databaseNamePresent: Boolean(databaseName), databaseAllowed, passwordPresent: Boolean(password), endpointClass: DEV_DB_ENV_CONTRACT.endpointAuthority.source, roleName, databaseName, password }; } function normalizeArgs(args) { return { ...parseArgs([]), ...args }; } function targetParseErrorCode({ protocolReady, roleName, password, databaseName, databaseAllowed }) { if (!protocolReady) return "TARGET_DB_URL_PROTOCOL_BLOCKED"; if (!roleName) return "TARGET_DB_URL_ROLE_MISSING"; if (!password) return "TARGET_DB_URL_PASSWORD_MISSING"; if (!databaseName) return "TARGET_DB_URL_DATABASE_MISSING"; if (!databaseAllowed) return "TARGET_DB_URL_DATABASE_FORBIDDEN"; return "TARGET_DB_URL_BLOCKED"; } function summarizeAdminEnv(env) { return { present: typeof env?.[DEV_DB_ADMIN_SECRET_REF.env] === "string" && env[DEV_DB_ADMIN_SECRET_REF.env].trim().length > 0, env: DEV_DB_ADMIN_SECRET_REF.env, secretRef: DEV_DB_ADMIN_SECRET_REF, valueRedacted: true }; } function summarizeDbEnv(env, target, admin) { return { targetUrlPresent: target.present, targetUrlValueRedacted: true, targetEndpointRedacted: true, targetEndpointClass: target.endpointClass ?? DEV_DB_ENV_CONTRACT.endpointAuthority.source, targetParseStatus: target.parseStatus, targetErrorCode: target.errorCode, targetRoleNamePresent: target.roleNamePresent, targetDatabaseNamePresent: target.databaseNamePresent, targetDatabaseAllowed: target.databaseAllowed === true, targetPasswordPresent: target.passwordPresent, sslMode: env?.HWLAB_CLOUD_DB_SSL_MODE || DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE, sslModeSecret: false, adminUrlPresent: admin.present, adminUrlValueRedacted: true, adminEndpointRedacted: true, dbUrlSecretRef: appSecretRef, adminUrlSecretRef: DEV_DB_ADMIN_SECRET_REF, endpointAuthority: DEV_DB_ENV_CONTRACT.endpointAuthority.source }; } function validateSourceContract(report, target, args) { if (target.ok) return; if (args.mode === "check" && target.present === false) return; addBlocker(report, "runtime_blocker", "runtime-provisioning-target", "DEV DB provisioning requires a valid target DB URL from the cloud-api SecretRef env.", { env: appSecretRef.env, secretRef: appSecretRef, errorCode: target.errorCode, valueRedacted: true, endpointRedacted: true, secretValuesPrinted: false }); } async function verifyRuntimeReadiness(report, env, options) { report.actions.liveDbReadAttempted = true; report.actions.appRuntimeReadinessAttempted = true; const runtimeStore = new PostgresCloudRuntimeStore({ env, dbUrl: env?.HWLAB_CLOUD_DB_URL, sslMode: env?.HWLAB_CLOUD_DB_SSL_MODE, queryClient: options.queryClient, pgModuleLoader: options.pgModuleLoader }); let runtime; try { runtime = await runtimeStore.readiness(); } finally { if (!options.queryClient && typeof runtimeStore.pool?.end === "function") { await runtimeStore.pool.end(); } } report.runtime = summarizeRuntime(runtime); report.actions.readinessVerified = runtime.ready === true; report.downstreamRuntimeGates = report.runtime.gates; report.gates = { ...report.gates, ssl: normalizeGate(runtime.gates?.ssl), auth: normalizeGate(runtime.gates?.auth), schema: normalizeGate(runtime.gates?.schema), migration: normalizeGate(runtime.gates?.migration), durability: normalizeGate(runtime.gates?.durability) }; } async function inspectAdminProvisioning(report, env, target, options) { report.actions.liveDbReadAttempted = true; report.actions.adminInspectionAttempted = true; const { client, close } = await openAdminClient(env, options); try { const [role, database] = await Promise.all([ objectExists(client, "SELECT 1 AS present FROM pg_catalog.pg_roles WHERE rolname = $1 LIMIT 1", [target.roleName]), objectExists(client, "SELECT 1 AS present FROM pg_catalog.pg_database WHERE datname = $1 LIMIT 1", [target.databaseName]) ]); report.provisioning.targetRole = { checked: true, exists: role, created: false, ready: role, targetNamePresent: target.roleNamePresent }; report.provisioning.targetDatabase = { checked: true, exists: database, created: false, ready: database, targetNamePresent: target.databaseNamePresent }; report.actions.roleExisted = role; report.actions.databaseExisted = database; report.provisioning.ready = role && database && runtimeAuthSatisfied(report.runtime); if (!role) addMissingObjectBlocker(report, "role"); if (!database) addMissingObjectBlocker(report, "database"); } catch (error) { addClassifiedAdminBlocker(report, error, "DEV DB provisioning inspection could not verify the target role/database."); } finally { await close(); } } async function applyProvisioning(report, env, target, options) { report.actions.liveDbWriteAttempted = true; report.actions.adminWriteAttempted = true; let admin; try { admin = await openAdminClient(env, options); const client = admin.client; const roleExists = await objectExists(client, "SELECT 1 AS present FROM pg_catalog.pg_roles WHERE rolname = $1 LIMIT 1", [target.roleName]); const databaseExists = await objectExists(client, "SELECT 1 AS present FROM pg_catalog.pg_database WHERE datname = $1 LIMIT 1", [target.databaseName]); report.actions.roleExisted = roleExists; report.actions.databaseExisted = databaseExists; if (!roleExists) { await client.query(`CREATE ROLE ${quotePgIdent(target.roleName)} LOGIN PASSWORD ${quotePgLiteral(target.password)}`); report.actions.roleCreated = true; } else { await client.query(`ALTER ROLE ${quotePgIdent(target.roleName)} WITH LOGIN PASSWORD ${quotePgLiteral(target.password)}`); } report.actions.rolePasswordSynchronized = true; if (!databaseExists) { await client.query(`CREATE DATABASE ${quotePgIdent(target.databaseName)} OWNER ${quotePgIdent(target.roleName)}`); report.actions.databaseCreated = true; } await client.query(`GRANT CONNECT ON DATABASE ${quotePgIdent(target.databaseName)} TO ${quotePgIdent(target.roleName)}`); report.actions.databaseConnectGranted = true; } catch (error) { addClassifiedAdminBlocker(report, error, "DEV DB provisioning apply could not complete role/database provisioning."); return; } finally { if (admin) await admin.close(); } let targetAdmin; try { targetAdmin = await openAdminClient(env, { ...options, adminDbUrlOverride: withDatabaseInPostgresUrl(env[DEV_DB_ADMIN_SECRET_REF.env], target.databaseName) }); await targetAdmin.client.query(`GRANT USAGE, CREATE ON SCHEMA public TO ${quotePgIdent(target.roleName)}`); report.actions.schemaPrivilegesGranted = true; } catch (error) { addClassifiedAdminBlocker(report, error, "DEV DB provisioning apply could not grant target database schema privileges."); return; } finally { if (targetAdmin) await targetAdmin.close(); } report.provisioning.targetRole = { checked: true, exists: true, created: report.actions.roleCreated, ready: true, targetNamePresent: true }; report.provisioning.targetDatabase = { checked: true, exists: true, created: report.actions.databaseCreated, ready: true, targetNamePresent: true }; report.provisioning.privileges = { checked: true, ready: report.actions.databaseConnectGranted && report.actions.schemaPrivilegesGranted, databaseConnectGranted: report.actions.databaseConnectGranted, schemaPrivilegesGranted: report.actions.schemaPrivilegesGranted }; report.provisioning.ready = report.provisioning.privileges.ready; } async function openAdminClient(env, options) { if (options.adminClient) { return { client: options.adminClient, close: async () => {} }; } const pool = await createPgPool({ dbUrl: options.adminDbUrlOverride ?? env[DEV_DB_ADMIN_SECRET_REF.env], sslMode: env.HWLAB_CLOUD_DB_SSL_MODE, timeoutMs: env.HWLAB_CLOUD_DB_PROBE_TIMEOUT_MS, pgModuleLoader: options.pgModuleLoader }); return { client: pool, close: async () => { await pool.end(); } }; } async function createPgPool({ dbUrl, sslMode, timeoutMs, pgModuleLoader }) { let pg; try { pg = await (pgModuleLoader ? pgModuleLoader() : import("pg")); } catch (error) { if (error?.code === "ERR_MODULE_NOT_FOUND") { const driverError = new Error("Postgres provisioning requires the pg package"); driverError.code = "HWLAB_PG_DRIVER_MISSING"; throw driverError; } throw error; } const Pool = pg.Pool ?? pg.default?.Pool; if (typeof Pool !== "function") { const driverError = new Error("Postgres provisioning could not load pg.Pool"); driverError.code = "HWLAB_PG_DRIVER_MISSING"; throw driverError; } return new Pool(buildPostgresPoolConfig({ dbUrl, sslMode, timeoutMs })); } function inferProvisioningFromRuntime(report) { const runtimeReady = report.runtime?.ready === true && report.runtime?.durable === true; const authReady = runtimeAuthSatisfied(report.runtime); if (!runtimeReady && !authReady) return; report.provisioning.targetRole = { ...report.provisioning.targetRole, checked: report.provisioning.targetRole.checked || false, exists: true, ready: true, inferredFromRuntime: true }; report.provisioning.targetDatabase = { ...report.provisioning.targetDatabase, checked: report.provisioning.targetDatabase.checked || false, exists: true, ready: true, inferredFromRuntime: true }; report.provisioning.ready = true; } function addRuntimeProvisioningBlocker(report) { const blocker = report.runtime?.blocker; if (!blocker) return; if (blocker === RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED) { addBlocker(report, "runtime_blocker", "runtime-provisioning-ssl", "DEV DB provisioning reached the target runtime adapter, but SSL mode blocked connection.", runtimeEvidence(report.runtime)); } else if (blocker === RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED) { addBlocker(report, "runtime_blocker", "runtime-provisioning-auth", "DEV DB target role/database exists check did not prove application authentication readiness.", runtimeEvidence(report.runtime)); } else if (blocker === RUNTIME_DURABLE_ADAPTER_DRIVER_MISSING) { addBlocker(report, "runtime_blocker", "runtime-provisioning-driver", "Postgres driver is unavailable for DEV DB provisioning verification.", runtimeEvidence(report.runtime)); } } function addMissingObjectBlocker(report, kind) { const scope = kind === "role" ? "runtime-provisioning-role" : "runtime-provisioning-database"; addBlocker(report, "runtime_blocker", scope, `DEV DB target ${kind} is missing according to admin catalog inspection.`, { exists: false, created: false, valueRedacted: true, endpointRedacted: true, affectedRuntimeBlocker: RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED, secretValuesPrinted: false }); } function addClassifiedAdminBlocker(report, error, summary) { const classified = classifyProvisioningError(error); addBlocker(report, "runtime_blocker", classified.scope, summary, { blocker: classified.blocker, errorCode: classified.errorCode, valueRedacted: true, endpointRedacted: true, secretValuesPrinted: false }); } function classifyProvisioningError(error) { const errorCode = typeof error?.code === "string" ? error.code : "UNKNOWN"; if (errorCode === "HWLAB_PG_DRIVER_MISSING") { return { blocker: RUNTIME_DURABLE_ADAPTER_DRIVER_MISSING, scope: "runtime-provisioning-driver", errorCode }; } if (isSslError(error)) { return { blocker: RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED, scope: "runtime-provisioning-ssl", errorCode }; } if (["28P01", "28000", "42501"].includes(errorCode)) { return { blocker: RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED, scope: "runtime-provisioning-auth", errorCode }; } if (["3D000"].includes(errorCode)) { return { blocker: RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED, scope: "runtime-provisioning-database", errorCode }; } if (["42P01", "42703", "3F000"].includes(errorCode)) { return { blocker: RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED, scope: "runtime-provisioning-schema", errorCode }; } return { blocker: RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED, scope: "runtime-provisioning-query", errorCode }; } function runtimeEvidence(runtime) { return { blocker: runtime.blocker, queryResult: runtime.connection?.queryResult ?? "unknown", errorCode: runtime.connection?.errorCode ?? null, schemaReady: runtime.schema?.ready === true, migrationReady: runtime.migration?.ready === true, durableReady: runtime.ready === true && runtime.durable === true, secretValuesPrinted: false }; } async function objectExists(client, sql, params) { const result = await client.query(sql, params); return Array.isArray(result?.rows) && result.rows.length > 0; } function sourceOnlyGates(target) { const sourceReady = target.ok; const targetMissingInSourceOnly = target.present === false; return { ssl: notCheckedGate("source-only validation does not connect to Postgres"), auth: sourceReady || targetMissingInSourceOnly ? notCheckedGate("auth requires live target runtime verification") : blockedGate("runtime_provisioning_target_blocked"), role: sourceReady || targetMissingInSourceOnly ? notCheckedGate("role existence requires DEV admin catalog inspection") : blockedGate("runtime_provisioning_target_blocked"), database: sourceReady || targetMissingInSourceOnly ? notCheckedGate("database existence requires DEV admin catalog inspection") : blockedGate("runtime_provisioning_target_blocked"), schema: notCheckedGate("schema is verified by runtime migration/readiness after role/database provisioning"), migration: notCheckedGate("migration ledger is verified by dev-runtime-migration"), durability: notCheckedGate("durability is verified by post-provisioning runtime readiness and postflight") }; } function normalizeGate(gate = {}) { if (gate.ready === true || gate.status === "ready") return readyGate(); if (gate.status === "blocked") return blockedGate(gate.blocker ?? "runtime_durable_adapter_blocked"); return notCheckedGate(); } function readyGate(summary = "ready") { return { checked: true, ready: true, status: "ready", blocker: null, summary }; } function blockedGate(blocker, summary = "blocked") { return { checked: true, ready: false, status: "blocked", blocker, summary }; } function notCheckedGate(summary = "not checked") { return { checked: false, ready: false, status: "not_checked", blocker: null, summary }; } function emptyObjectState(targetNamePresent) { return { checked: false, exists: false, created: false, ready: false, targetNamePresent }; } function summarizeRuntime(runtime = {}) { return { adapter: runtime.adapter ?? "unknown", durable: Boolean(runtime.durable), durableRequested: Boolean(runtime.durableRequested), ready: runtime.ready === true, status: runtime.status ?? "unknown", blocker: runtime.blocker ?? null, liveRuntimeEvidence: Boolean(runtime.liveRuntimeEvidence), fixtureEvidence: Boolean(runtime.fixtureEvidence), connection: { queryAttempted: Boolean(runtime.connection?.queryAttempted), queryResult: runtime.connection?.queryResult ?? "unknown", endpointRedacted: true, valueRedacted: true, errorCode: runtime.connection?.errorCode ?? null }, schema: { checked: Boolean(runtime.schema?.checked), ready: Boolean(runtime.schema?.ready), missingTables: Array.isArray(runtime.schema?.missingTables) ? [...runtime.schema.missingTables] : [], missingColumns: Array.isArray(runtime.schema?.missingColumns) ? [...runtime.schema.missingColumns] : [] }, migration: { checked: Boolean(runtime.migration?.checked), ready: Boolean(runtime.migration?.ready), requiredMigrationId: runtime.migration?.requiredMigrationId ?? null, appliedMigrationId: runtime.migration?.appliedMigrationId ?? null, missing: runtime.migration?.missing !== false }, gates: runtime.gates ?? {} }; } function runtimeAuthSatisfied(runtime = {}) { if (runtime.ready === true && runtime.durable === true) return true; const blocker = runtime.blocker; return [ RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED, RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED, RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED ].includes(blocker) && runtime.connection?.queryAttempted === true; } function buildApplyBoundary(args) { return { defaultMode: "check", mode: args.mode, liveDbReads: args.mode === "apply" || (args.mode === "dry-run" && args.allowLiveDbRead), liveDbWrites: args.mode === "apply", requiresForLiveDbRead: [ "--confirm-dev", `${appSecretRef.env} from ${appSecretRef.secretName}/${appSecretRef.secretKey}`, `${DEV_DB_ADMIN_SECRET_REF.env} from ${DEV_DB_ADMIN_SECRET_REF.secretName}/${DEV_DB_ADMIN_SECRET_REF.secretKey} unless runtime readiness already proves auth` ], requiresForApply: [ "--apply", "--confirm-dev", "--confirmed-non-production", `${appSecretRef.env} from ${appSecretRef.secretName}/${appSecretRef.secretKey}`, `${DEV_DB_ADMIN_SECRET_REF.env} from ${DEV_DB_ADMIN_SECRET_REF.secretName}/${DEV_DB_ADMIN_SECRET_REF.secretKey}` ], writeScope: args.mode === "apply" ? [ "DEV Postgres target role named by the redacted cloud-api DB URL", "DEV Postgres target database named by the redacted cloud-api DB URL", "DEV target DB CONNECT and public schema CREATE/USAGE grants for that role" ] : [], noWriteScope: [ "Kubernetes Secret resources", "PROD", "service restarts", "schema migrations", "M3/M4/M5 acceptance" ], forbidden: [ "printing DB URLs, passwords, tokens, Secret values, or kubeconfig material", "manual psql or node stdin database writes", "mutating PROD" ] }; } function buildSafety(args) { return { devOnly: true, environment: ENVIRONMENT_DEV, targetNamespace: DEV_DB_ENV_CONTRACT.dns.namespace, prodAllowed: false, sourceStoresSecretValues: false, printsSecretValues: false, secretValuesPrinted: false, dbUrlValueRedacted: true, adminUrlValueRedacted: true, endpointRedacted: true, readsKubernetesSecrets: false, writesKubernetesSecrets: false, liveDbWrites: args.mode === "apply", fixtureEvidenceAllowed: false }; } function addBlocker(report, type, scope, summary, evidence = {}) { report.blockers.push({ type, scope, status: "open", summary, sourceIssue: scope.includes("auth") ? "pikasTech/HWLAB#307" : issue, evidence }); } function addSafetyRefusal(report, summary) { report.safetyRefusal = true; addBlocker(report, "safety_refusal", "runtime-provisioning-apply-boundary", summary, { devOnly: true, prodAllowed: false, secretValuesPrinted: false }); } function finalizeReport(report) { const openBlockers = report.blockers.filter((blocker) => blocker.status === "open"); report.provisioning.ready = report.provisioning.ready || ( report.provisioning.targetRole.ready === true && report.provisioning.targetDatabase.ready === true && (report.provisioning.privileges.ready === true || report.mode !== "apply") ); report.conclusion = { status: openBlockers.length === 0 ? "ready" : "blocked", summary: openBlockers.length === 0 ? conclusionReadySummary(report) : "DEV DB provisioning is blocked; see separated role/database/ssl/auth blockers.", blockerCount: openBlockers.length }; return report; } function conclusionReadySummary(report) { if (report.mode === "apply") { return "DEV DB role/database provisioning completed without printing secret values."; } if (report.mode === "dry-run" && report.actions.liveDbReadAttempted) { return "DEV DB role/database provisioning read-only verification passed without DB writes or secret output."; } return "DEV DB provisioning source contract is ready; no DB connection or live write was attempted."; } function summarizeReport(report) { return { issue: report.issue, mode: report.mode, conclusion: report.conclusion, db: report.db, actions: report.actions, provisioning: report.provisioning, gates: report.gates, downstreamRuntimeGates: report.downstreamRuntimeGates, blockers: report.blockers, safety: report.safety }; } function quotePgIdent(value) { return `"${String(value).replaceAll('"', '""')}"`; } function quotePgLiteral(value) { return `'${String(value).replaceAll("'", "''")}'`; } function withDatabaseInPostgresUrl(rawUrl, databaseName) { const url = new URL(rawUrl); url.pathname = `/${encodeURIComponent(databaseName)}`; return url.toString(); } function isSslError(error) { const code = typeof error?.code === "string" ? error.code : ""; if (code.startsWith("ERR_SSL") || code.startsWith("ERR_TLS")) return true; const message = typeof error?.message === "string" ? error.message.toLowerCase() : ""; return [ "does not support ssl", "ssl is not enabled", "ssl negotiation", "ssl off", "ssl required", "requires ssl", "no ssl encryption", "no encryption", "hostssl", "server requires ssl", "tls handshake" ].some((pattern) => message.includes(pattern)); } async function writeReport(report, reportPath, root) { const absolute = path.resolve(root, reportPath); await mkdir(path.dirname(absolute), { recursive: true }); await writeFile(absolute, `${JSON.stringify(report, null, 2)}\n`); } function requireArgValue(value, flag) { if (typeof value !== "string" || value.trim() === "") { throw new Error(`${flag} requires a value`); } return value; } function usage() { return [ "Usage: node scripts/dev-runtime-provisioning.mjs [--check|--dry-run|--apply]", "", "Default --check validates redacted DEV DB provisioning inputs only and never connects to DB.", "--dry-run is source-only unless --allow-live-db-read --confirm-dev is provided.", "--apply requires --confirm-dev --confirmed-non-production, HWLAB_CLOUD_DB_URL, and HWLAB_CLOUD_DB_ADMIN_URL.", "Reports never print DB URLs, passwords, tokens, Secret values, or kubeconfig material." ].join("\n"); } export function formatDevRuntimeProvisioningFailure(error) { const message = redactFailureText(error instanceof Error ? error.message : String(error)); return { issue, conclusion: { status: "blocked", summary: "DEV DB provisioning command failed before producing a report." }, error: message, safety: { devOnly: true, prodAllowed: false, secretValuesPrinted: false }, trace: createHash("sha256").update(message).digest("hex").slice(0, 12) }; } function redactFailureText(value) { return String(value ?? "") .replace(/postgres(?:ql)?:\/\/[^\s"'<>]+/giu, "[redacted-postgres-url]") .replace(/(password\s*[=:]\s*)[^\s,;]+/giu, "$1[redacted]") .replace(/(token\s*[=:]\s*)[^\s,;]+/giu, "$1[redacted]") .replace(/(kubeconfig\s*[=:]\s*)[^\s,;]+/giu, "$1[redacted]"); }