fix: add dev DB DNS contract

This commit is contained in:
Code Queue Review
2026-05-22 15:32:57 +00:00
parent 01c4e1b533
commit 3049546cfd
19 changed files with 407 additions and 29 deletions
+72 -7
View File
@@ -339,7 +339,7 @@ function blockerHint(blocker) {
return "Restore the public DEV health path and confirm it identifies HWLAB dev, not a substitute runtime.";
}
if (blocker.scope === "dev-health-db" || blocker.scope === "cloud-api-db" || blocker.scope === "cloud-api-db-config") {
return "Configure and verify the DEV cloud-api DB env readiness through health output, without reading secret values.";
return "Configure and verify the DEV cloud-api DB env and stable DNS readiness through health output, without reading secret values.";
}
return blocker.summary;
}
@@ -543,7 +543,7 @@ function validateK8s(devKustomization, namespaceDoc, workloads, services, health
};
}
async function checkCloudApiDb(deploy, workloads, blockers) {
async function checkCloudApiDb(deploy, workloads, services, blockers) {
const deployEnv = deploy?.services?.find((service) => service.serviceId === "hwlab-cloud-api")?.env ?? {};
const envNames = new Set(Object.keys(deployEnv));
const workloadEnv = new Map();
@@ -586,16 +586,22 @@ async function checkCloudApiDb(deploy, workloads, blockers) {
const missingSecretRefs = requiredEnv
.filter((env) => env.secretRef && !env.secretRef.present)
.map((env) => `${env.secretRef.secretName}/${env.secretRef.secretKey}`);
if (missingManifest.length > 0 || missingK8s.length > 0 || missingSecretRefs.length > 0) {
const dnsContract = inspectCloudApiDbDnsContract(deployEnv, workloadEnv, services);
const missingDnsContract = dnsContract.missing;
if (missingManifest.length > 0 || missingK8s.length > 0 || missingSecretRefs.length > 0 || missingDnsContract.length > 0) {
addBlocker(
blockers,
"runtime_blocker",
"cloud-api-db-config",
`cloud-api DEV DB env contract is incomplete; missing ${[...missingManifest, ...missingK8s, ...missingSecretRefs].join(", ")}`
`cloud-api DEV DB env/DNS contract is incomplete; missing ${[...missingManifest, ...missingK8s, ...missingSecretRefs, ...missingDnsContract].join(", ")}`
);
}
const configReady = missingManifest.length === 0 && missingK8s.length === 0 && missingSecretRefs.length === 0;
const configReady =
missingManifest.length === 0 &&
missingK8s.length === 0 &&
missingSecretRefs.length === 0 &&
missingDnsContract.length === 0;
const health = {
status: configReady ? "degraded" : "blocked",
ready: false,
@@ -628,12 +634,71 @@ async function checkCloudApiDb(deploy, workloads, blockers) {
missingManifest,
missingK8s,
missingSecretRefs,
missingDnsContract,
dns: dnsContract,
runtimeHealth: summarizeDbContract(health),
secretMaterialRead: false,
valuesRedacted: true,
liveDbEvidence: false,
fixtureEvidence: false,
note: "This check records DB env/Secret reference presence only. It is not live DB evidence."
note: "This check records DB env, Secret reference, and stable DNS Service presence only. It is not live DB evidence."
};
}
function inspectCloudApiDbDnsContract(deployEnv, workloadEnv, services) {
const dns = DEV_DB_ENV_CONTRACT.dns;
const expectedHost = `${dns.serviceName}.${dns.namespace}.svc.cluster.local`;
const service = listItems(services).find((item) => item?.metadata?.name === dns.serviceName) ?? null;
const port = service?.spec?.ports?.find((entry) => entry.name === dns.portName) ?? null;
const actual = {
deployServiceName: deployEnv.HWLAB_CLOUD_DB_SERVICE_NAME,
deployServiceNamespace: deployEnv.HWLAB_CLOUD_DB_SERVICE_NAMESPACE,
deployHost: deployEnv.HWLAB_CLOUD_DB_HOST,
deployPort: deployEnv.HWLAB_CLOUD_DB_PORT,
workloadServiceName: workloadEnv.get("HWLAB_CLOUD_DB_SERVICE_NAME")?.value,
workloadServiceNamespace: workloadEnv.get("HWLAB_CLOUD_DB_SERVICE_NAMESPACE")?.value,
workloadHost: workloadEnv.get("HWLAB_CLOUD_DB_HOST")?.value,
workloadPort: workloadEnv.get("HWLAB_CLOUD_DB_PORT")?.value,
serviceName: service?.metadata?.name,
serviceNamespace: service?.metadata?.namespace,
serviceType: service?.spec?.type,
serviceSelector: service?.spec?.selector ?? null,
portName: port?.name,
port: port?.port,
targetPort: port?.targetPort
};
const checks = [
["deployEnv.HWLAB_CLOUD_DB_SERVICE_NAME", actual.deployServiceName, dns.serviceName],
["deployEnv.HWLAB_CLOUD_DB_SERVICE_NAMESPACE", actual.deployServiceNamespace, dns.namespace],
["deployEnv.HWLAB_CLOUD_DB_HOST", actual.deployHost, expectedHost],
["deployEnv.HWLAB_CLOUD_DB_PORT", actual.deployPort, String(dns.port)],
["workloadEnv.HWLAB_CLOUD_DB_SERVICE_NAME", actual.workloadServiceName, dns.serviceName],
["workloadEnv.HWLAB_CLOUD_DB_SERVICE_NAMESPACE", actual.workloadServiceNamespace, dns.namespace],
["workloadEnv.HWLAB_CLOUD_DB_HOST", actual.workloadHost, expectedHost],
["workloadEnv.HWLAB_CLOUD_DB_PORT", actual.workloadPort, String(dns.port)],
["Service.metadata.name", actual.serviceName, dns.serviceName],
["Service.metadata.namespace", actual.serviceNamespace, dns.namespace],
["Service.spec.type", actual.serviceType, "ClusterIP"],
["Service.spec.selector", actual.serviceSelector, null],
["Service.port.name", actual.portName, dns.portName],
["Service.port.port", actual.port, dns.port],
["Service.port.targetPort", actual.targetPort, dns.portName]
];
const missing = checks
.filter(([, actualValue, expectedValue]) => !Object.is(actualValue, expectedValue))
.map(([name, actualValue, expectedValue]) => `${name} expected ${expectedValue} got ${actualValue ?? "missing"}`);
return {
ready: missing.length === 0,
host: expectedHost,
serviceName: dns.serviceName,
namespace: dns.namespace,
port: dns.port,
selectorless: service ? service.spec?.selector === undefined : false,
secretMaterialRead: false,
liveDbEvidence: false,
missing,
actual
};
}
@@ -937,7 +1002,7 @@ export async function runDevDeployApply(argv, io = {}) {
const artifactEvidence = validateDeployAndCatalog(deploy, catalog, commitId, blockers);
const k8sManifest = validateK8s(devKustomization, namespaceDoc, workloads, services, healthContract, deploy, blockers);
const cloudApiDb = await checkCloudApiDb(deploy, workloads, blockers);
const cloudApiDb = await checkCloudApiDb(deploy, workloads, services, blockers);
if (!(await readText("internal/cloud/server.mjs")).includes('url.pathname === "/health/live"')) {
addBlocker(blockers, "contract_blocker", "cloud-api-health-path", "cloud-api does not implement the k8s /health/live path");
}