import net from "node:net"; import { ENVIRONMENT_DEV } from "../protocol/index.mjs"; export const DEV_DB_ENV_CONTRACT = Object.freeze({ contractVersion: "v1", environment: ENVIRONMENT_DEV, requiredEnv: Object.freeze([ "HWLAB_CLOUD_DB_URL", "HWLAB_CLOUD_DB_SSL_MODE" ]), secretRefs: Object.freeze([ Object.freeze({ env: "HWLAB_CLOUD_DB_URL", secretName: "hwlab-cloud-api-dev-db", secretKey: "database-url" }) ]), nonSecretDefaults: Object.freeze({ HWLAB_CLOUD_DB_SSL_MODE: "require" }), forbidden: Object.freeze({ prodAllowed: false, secretPlaintextAllowed: false, liveDbEvidenceFromFixtures: false }) }); const secretEnvNames = new Set(DEV_DB_ENV_CONTRACT.secretRefs.map((item) => item.env)); const supportedDbProtocols = new Set(["postgres:", "postgresql:"]); const defaultProbeTimeoutMs = 1200; export function buildDbHealthContract(env = process.env) { const fields = DEV_DB_ENV_CONTRACT.requiredEnv.map((name) => { const present = hasEnvValue(env, name); return { name, present, redacted: secretEnvNames.has(name), source: secretEnvNames.has(name) ? "k8s-secret-ref" : "runtime-env", required: true }; }); const missingEnv = fields.filter((field) => !field.present).map((field) => field.name); const configReady = missingEnv.length === 0; const connection = buildNotAttemptedConnection(configReady, missingEnv); const connected = false; const connectionChecked = false; const status = configReady ? "degraded" : "blocked"; return { contractVersion: DEV_DB_ENV_CONTRACT.contractVersion, environment: DEV_DB_ENV_CONTRACT.environment, connected, liveConnected: connected, connectionChecked, connectionAttempted: connection.attempted, connectionResult: connection.result, configReady, ready: connected, status, mode: configReady ? "configured_without_live_connection_attempt" : "not_configured", fields, missingEnv, secretRefs: DEV_DB_ENV_CONTRACT.secretRefs.map(({ env: envName, secretName, secretKey }) => ({ env: envName, secretName, secretKey, present: hasEnvValue(env, envName), envInjected: hasEnvValue(env, envName), secretPresent: "not_observed_by_runtime", secretKeyPresent: "not_observed_by_runtime", redacted: true })), connection, safety: { devOnly: true, prodAllowed: DEV_DB_ENV_CONTRACT.forbidden.prodAllowed, secretsRead: false, valuesRedacted: true, liveDbEvidence: false }, blocker: configReady ? "live DB connection has not been attempted by this health contract" : missingEnvBlocker(missingEnv), evidence: configReady ? "env_presence_only_no_live_db" : "env_contract_blocked" }; } export async function buildDbRuntimeReadiness(env = process.env, options = {}) { const base = buildDbHealthContract(env); if (!base.configReady) { return base; } if (probeDisabled(env, options)) { return { ...base, blocker: "live DB probe is disabled for this runtime", evidence: "env_presence_only_probe_disabled" }; } const connection = await probeDbConnection(env, options); return applyConnectionResult(base, connection); } export function buildDbEnvManifestPlaceholder() { return { requiredEnv: [...DEV_DB_ENV_CONTRACT.requiredEnv], secretRefs: DEV_DB_ENV_CONTRACT.secretRefs.map((item) => ({ ...item })), nonSecretDefaults: { ...DEV_DB_ENV_CONTRACT.nonSecretDefaults }, fixtureEvidence: { liveDbEvidence: false, summary: "This manifest contract records only env and Secret reference names. It is not live DB evidence." } }; } export function summarizeDbContract(db = buildDbHealthContract()) { return { status: db.status, ready: db.ready, configReady: db.configReady, connected: db.connected, liveConnected: db.liveConnected ?? db.connected, connectionChecked: db.connectionChecked, connectionAttempted: db.connectionAttempted ?? db.connectionChecked, connectionResult: db.connectionResult ?? db.connection?.result ?? "unknown", requiredEnv: db.fields.map((field) => ({ name: field.name, present: field.present, redacted: field.redacted, source: field.source })), missingEnv: [...db.missingEnv], secretRefs: db.secretRefs.map( ({ env: envName, secretName, secretKey, present, envInjected, secretPresent, secretKeyPresent, redacted }) => ({ env: envName, secretName, secretKey, present, envInjected: envInjected ?? present, secretPresent: secretPresent ?? "not_observed", secretKeyPresent: secretKeyPresent ?? "not_observed", redacted }) ), connection: summarizeConnection(db.connection), blocker: db.blocker ?? null, liveDbEvidence: db.safety.liveDbEvidence, fixtureEvidence: false }; } function hasEnvValue(env, name) { return typeof env?.[name] === "string" && env[name].trim().length > 0; } function probeDisabled(env, options) { return options.probe === false || env?.HWLAB_CLOUD_DB_PROBE_DISABLED === "1"; } function buildNotAttemptedConnection(configReady, missingEnv) { return { attempted: false, networkAttempted: false, result: configReady ? "not_attempted" : "not_attempted_missing_env", classification: configReady ? "configured_no_live_attempt" : "missing_runtime_env", probeType: "tcp-connect", endpointRedacted: true, valueRedacted: true, timeoutMs: null, durationMs: 0, errorCode: null, missingEnv: [...missingEnv] }; } async function probeDbConnection(env, options) { const timeoutMs = normalizeTimeoutMs(options.timeoutMs ?? env?.HWLAB_CLOUD_DB_PROBE_TIMEOUT_MS); const parsed = parseDbTarget(env?.HWLAB_CLOUD_DB_URL); if (!parsed.ok) { return { attempted: true, networkAttempted: false, result: parsed.result, classification: parsed.classification, probeType: "tcp-connect", endpointRedacted: true, valueRedacted: true, timeoutMs, durationMs: 0, errorCode: parsed.errorCode, missingEnv: [] }; } return tcpConnect({ host: parsed.host, port: parsed.port, timeoutMs }); } function parseDbTarget(rawUrl) { try { const url = new URL(rawUrl); if (!supportedDbProtocols.has(url.protocol)) { return { ok: false, result: "unsupported_protocol", classification: "db_url_unsupported_protocol", errorCode: "UNSUPPORTED_PROTOCOL" }; } if (!url.hostname) { return { ok: false, result: "invalid_url", classification: "db_url_missing_host", errorCode: "MISSING_HOST" }; } const port = url.port ? Number.parseInt(url.port, 10) : 5432; if (!Number.isInteger(port) || port <= 0 || port > 65535) { return { ok: false, result: "invalid_url", classification: "db_url_invalid_port", errorCode: "INVALID_PORT" }; } return { ok: true, host: url.hostname, port }; } catch { return { ok: false, result: "invalid_url", classification: "db_url_parse_error", errorCode: "INVALID_URL" }; } } function tcpConnect({ host, port, timeoutMs }) { return new Promise((resolve) => { const startedAt = Date.now(); const socket = net.connect({ host, port, timeout: timeoutMs }); let settled = false; function finish(result) { if (settled) return; settled = true; socket.destroy(); resolve({ attempted: true, networkAttempted: true, probeType: "tcp-connect", endpointRedacted: true, valueRedacted: true, timeoutMs, durationMs: Date.now() - startedAt, missingEnv: [], ...result }); } socket.on("connect", () => { finish({ result: "connected", classification: "tcp_connected", errorCode: null }); }); socket.on("timeout", () => { finish({ result: "timeout", classification: "tcp_timeout", errorCode: "ETIMEDOUT" }); }); socket.on("error", (error) => { finish(classifySocketError(error)); }); }); } function classifySocketError(error) { const code = typeof error?.code === "string" ? error.code : "UNKNOWN"; if (code === "ECONNREFUSED") { return { result: "refused", classification: "tcp_refused", errorCode: code }; } if (code === "ENOTFOUND" || code === "EAI_AGAIN") { return { result: "dns_error", classification: "dns_resolution_failed", errorCode: code }; } if (code === "ETIMEDOUT") { return { result: "timeout", classification: "tcp_timeout", errorCode: code }; } if (code === "EHOSTUNREACH" || code === "ENETUNREACH") { return { result: "network_unreachable", classification: "network_unreachable", errorCode: code }; } return { result: "error", classification: "tcp_error", errorCode: code }; } function applyConnectionResult(base, connection) { const liveConnected = connection.result === "connected"; return { ...base, connected: liveConnected, liveConnected, connectionChecked: connection.attempted, connectionAttempted: connection.attempted, connectionResult: connection.result, ready: liveConnected, status: liveConnected ? "ready" : "degraded", mode: liveConnected ? "live_connection_ready" : "live_connection_blocked", connection, safety: { ...base.safety, liveDbEvidence: connection.attempted, liveDbConnectedEvidence: liveConnected }, blocker: liveConnected ? null : blockerForConnection(connection), evidence: liveConnected ? "live_db_tcp_connection_ready" : "live_db_tcp_connection_blocked" }; } function blockerForConnection(connection) { if (connection.result === "invalid_url") { return "DB URL is injected but cannot be parsed as a supported postgres URL"; } if (connection.result === "unsupported_protocol") { return "DB URL is injected but does not use a supported postgres protocol"; } if (connection.result === "refused") { return "DB endpoint refused a TCP connection from the cloud-api runtime"; } if (connection.result === "timeout") { return "DB endpoint TCP connection timed out from the cloud-api runtime"; } if (connection.result === "dns_error") { return "DB endpoint DNS resolution failed from the cloud-api runtime"; } if (connection.result === "network_unreachable") { return "DB endpoint network is unreachable from the cloud-api runtime"; } return "DB endpoint TCP connection did not complete from the cloud-api runtime"; } function missingEnvBlocker(missingEnv) { return `DB runtime config is missing ${missingEnv.join(", ")}`; } function normalizeTimeoutMs(value) { const parsed = Number.parseInt(String(value ?? ""), 10); if (!Number.isInteger(parsed) || parsed <= 0) { return defaultProbeTimeoutMs; } return Math.min(parsed, 5000); } function summarizeConnection(connection) { if (!connection) { return { attempted: false, result: "unknown", endpointRedacted: true, valueRedacted: true }; } return { attempted: Boolean(connection.attempted), networkAttempted: Boolean(connection.networkAttempted), result: connection.result, classification: connection.classification, probeType: connection.probeType, endpointRedacted: true, valueRedacted: true, timeoutMs: connection.timeoutMs, durationMs: connection.durationMs, errorCode: connection.errorCode ?? null }; }