From 653dd27f5acdb32b3b68f27366e81744415aa732 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Tue, 9 Jun 2026 09:28:09 +0800 Subject: [PATCH] fix: report runtime lane db secret refs --- internal/cloud/db-contract.ts | 53 ++++++++++++++-------- internal/cloud/server-health.test.ts | 66 ++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 18 deletions(-) diff --git a/internal/cloud/db-contract.ts b/internal/cloud/db-contract.ts index 0c15ab0f..77cf6fc0 100644 --- a/internal/cloud/db-contract.ts +++ b/internal/cloud/db-contract.ts @@ -65,23 +65,29 @@ export const DEV_DB_ENV_CONTRACT = Object.freeze({ }) }); -export const V02_DB_ENV_CONTRACT = Object.freeze({ - ...DEV_DB_ENV_CONTRACT, - environment: "v02", - secretRefs: Object.freeze([ - Object.freeze({ - env: "HWLAB_CLOUD_DB_URL", - secretName: "hwlab-cloud-api-v02-db", - secretKey: "database-url" +export const V02_DB_ENV_CONTRACT = buildRuntimeLaneDbEnvContract("v02"); + +export const V03_DB_ENV_CONTRACT = buildRuntimeLaneDbEnvContract("v03"); + +function buildRuntimeLaneDbEnvContract(profile) { + return Object.freeze({ + ...DEV_DB_ENV_CONTRACT, + environment: profile, + secretRefs: Object.freeze([ + Object.freeze({ + env: "HWLAB_CLOUD_DB_URL", + secretName: `hwlab-cloud-api-${profile}-db`, + secretKey: "database-url" + }) + ]), + dns: Object.freeze({ + ...DEV_DB_ENV_CONTRACT.dns, + serviceName: `hwlab-${profile}-postgres`, + namespace: `hwlab-${profile}`, + host: `hwlab-${profile}-postgres.hwlab-${profile}.svc.cluster.local` }) - ]), - dns: Object.freeze({ - ...DEV_DB_ENV_CONTRACT.dns, - serviceName: "hwlab-v02-postgres", - namespace: "hwlab-v02", - host: "hwlab-v02-postgres.hwlab-v02.svc.cluster.local" - }) -}); + }); +} const supportedDbProtocols = new Set(["postgres:", "postgresql:"]); const defaultProbeTimeoutMs = 1200; @@ -269,11 +275,22 @@ function hasEnvValue(env, name) { export function dbEnvContractFor(env = process.env) { const value = env?.HWLAB_GITOPS_PROFILE || env?.HWLAB_ENVIRONMENT || ""; const profile = typeof value === "string" ? value.trim().toLowerCase() : ""; - return profile === "v02" ? V02_DB_ENV_CONTRACT : DEV_DB_ENV_CONTRACT; + return dbEnvContractForProfile(profile); } function dbEnvContractForDb(db = {}) { - return db?.environment === V02_DB_ENV_CONTRACT.environment ? V02_DB_ENV_CONTRACT : DEV_DB_ENV_CONTRACT; + return dbEnvContractForProfile(db?.environment); +} + +function dbEnvContractForProfile(profile) { + if (profile === V02_DB_ENV_CONTRACT.environment) return V02_DB_ENV_CONTRACT; + if (profile === V03_DB_ENV_CONTRACT.environment) return V03_DB_ENV_CONTRACT; + if (isRuntimeLaneProfile(profile)) return buildRuntimeLaneDbEnvContract(profile); + return DEV_DB_ENV_CONTRACT; +} + +function isRuntimeLaneProfile(profile) { + return typeof profile === "string" && /^v[0-9]{2,}$/u.test(profile); } function secretEnvNamesForContract(contract) { diff --git a/internal/cloud/server-health.test.ts b/internal/cloud/server-health.test.ts index 8823a616..28d4ffca 100644 --- a/internal/cloud/server-health.test.ts +++ b/internal/cloud/server-health.test.ts @@ -189,6 +189,72 @@ test("cloud api health reports the v02 DB contract under the v02 profile", async } }); +test("cloud api health reports the v03 DB contract under the v03 profile", async () => { + const originalEnv = { + HWLAB_ENVIRONMENT: process.env.HWLAB_ENVIRONMENT, + HWLAB_GITOPS_PROFILE: process.env.HWLAB_GITOPS_PROFILE, + HWLAB_CLOUD_DB_URL: process.env.HWLAB_CLOUD_DB_URL, + HWLAB_CLOUD_DB_SSL_MODE: process.env.HWLAB_CLOUD_DB_SSL_MODE, + HWLAB_CLOUD_DB_SERVICE_NAME: process.env.HWLAB_CLOUD_DB_SERVICE_NAME, + HWLAB_CLOUD_DB_SERVICE_NAMESPACE: process.env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE, + HWLAB_CLOUD_DB_HOST: process.env.HWLAB_CLOUD_DB_HOST, + HWLAB_CLOUD_DB_PORT: process.env.HWLAB_CLOUD_DB_PORT + }; + const fakeDb = createTcpServer((socket) => socket.end()); + await new Promise((resolve) => fakeDb.listen(0, "127.0.0.1", resolve)); + const dbPort = fakeDb.address().port; + process.env.HWLAB_ENVIRONMENT = "v03"; + process.env.HWLAB_GITOPS_PROFILE = "v03"; + process.env.HWLAB_CLOUD_DB_URL = `postgres://hwlab_test@127.0.0.1:${dbPort}/hwlab_v03`; + process.env.HWLAB_CLOUD_DB_SSL_MODE = "disable"; + delete process.env.HWLAB_CLOUD_DB_SERVICE_NAME; + delete process.env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE; + delete process.env.HWLAB_CLOUD_DB_HOST; + delete process.env.HWLAB_CLOUD_DB_PORT; + + const server = createCloudApiServer(); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + + try { + const { port } = server.address(); + const response = await fetch(`http://127.0.0.1:${port}/health/live`); + const payload = await response.json(); + const dbJson = JSON.stringify(payload.db); + assert.equal(payload.environment, "v03"); + assert.equal(payload.db.environment, "v03"); + assert.equal(payload.db.secretRefs[0].secretName, "hwlab-cloud-api-v03-db"); + assert.equal(payload.db.secretRefs[0].secretKey, "database-url"); + assert.equal(payload.db.secretRefs[0].redacted, true); + assert.equal(payload.db.optionalPublicDnsAlias.serviceName, "hwlab-v03-postgres"); + assert.equal(payload.db.optionalPublicDnsAlias.namespace, "hwlab-v03"); + assert.equal(payload.db.optionalPublicDnsAlias.requiredForReadiness, false); + assert.equal(payload.db.optionalPublicDnsAlias.usedForProbe, false); + assert.equal(payload.db.safety.environment, "v03"); + assert.equal(payload.db.safety.devOnly, false); + assert.equal(payload.db.redaction.secretMaterialRead, false); + assert.equal(dbJson.includes("hwlab-cloud-api-dev-db"), false); + assert.equal(dbJson.includes("hwlab-cloud-api-v02-db"), false); + assert.equal(dbJson.includes("hwlab-dev"), false); + assert.equal(dbJson.includes("hwlab-v02"), false); + assert.equal(dbJson.includes("127.0.0.1"), false); + assert.equal(dbJson.includes(String(dbPort)), false); + } finally { + for (const [name, value] of Object.entries(originalEnv)) { + if (value === undefined) { + delete process.env[name]; + } else { + process.env[name] = value; + } + } + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); + await new Promise((resolve, reject) => { + fakeDb.close((error) => (error ? reject(error) : resolve())); + }); + } +}); + test("cloud api health separates DB connected from durable runtime schema readiness", async () => { const originalUrl = process.env.HWLAB_CLOUD_DB_URL; const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE;