Files
pikasTech-HWLAB/scripts/src/dev-runtime-provisioning.mjs
T

931 lines
32 KiB
JavaScript

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.ts";
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.ts";
import { ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const defaultReportPath = tempReportPath("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 === "--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");
args.writeReport = true;
} else if (arg.startsWith("--report=")) {
args.reportPath = requireArgValue(arg.slice("--report=".length), "--report");
args.writeReport = true;
} 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 = ensureNotRepoReportsPath(root, reportPath, "--report");
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]");
}