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
+39
View File
@@ -3,6 +3,7 @@ import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs";
const execFileAsync = promisify(execFile);
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
@@ -135,6 +136,7 @@ function validateSource(ctx, manifest) {
const tunnelClient = servicesById.get("hwlab-tunnel-client");
const cli = servicesById.get("hwlab-cli");
expectEqual(ctx, cloudApi?.env?.HWLAB_PUBLIC_ENDPOINT, endpoints.api?.url, "$.services.hwlab-cloud-api.env.HWLAB_PUBLIC_ENDPOINT", "cloud API public endpoint env");
validateCloudApiDbDnsSource(ctx, cloudApi?.env ?? {});
expectEqual(ctx, cli?.env?.HWLAB_CLI_ENDPOINT, endpoints.api?.url, "$.services.hwlab-cli.env.HWLAB_CLI_ENDPOINT", "CLI endpoint env");
expectEqual(ctx, tunnelClient?.env?.HWLAB_FRP_PUBLIC_PORT, String(expectedPorts.api), "$.services.hwlab-tunnel-client.env.HWLAB_FRP_PUBLIC_PORT", "tunnel API public port env");
expectEqual(ctx, tunnelClient?.env?.HWLAB_FRP_WEB_PUBLIC_PORT, String(expectedPorts.frontend), "$.services.hwlab-tunnel-client.env.HWLAB_FRP_WEB_PUBLIC_PORT", "tunnel frontend public port env");
@@ -168,6 +170,15 @@ function validateSource(ctx, manifest) {
return { manifest, endpoints, servicesById, proxies, serviceMappings, healthPath: manifest.health?.path };
}
function validateCloudApiDbDnsSource(ctx, env) {
const dns = DEV_DB_ENV_CONTRACT.dns;
const expectedHost = `${dns.serviceName}.${dns.namespace}.svc.cluster.local`;
expectEqual(ctx, env.HWLAB_CLOUD_DB_SERVICE_NAME, dns.serviceName, "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SERVICE_NAME", "cloud API DB service name");
expectEqual(ctx, env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE, dns.namespace, "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE", "cloud API DB service namespace");
expectEqual(ctx, env.HWLAB_CLOUD_DB_HOST, expectedHost, "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_HOST", "cloud API DB stable DNS host");
expectEqual(ctx, env.HWLAB_CLOUD_DB_PORT, String(dns.port), "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_PORT", "cloud API DB service port");
}
function renderPlan(source) {
const proxyPorts = [...new Set(source.proxies.map((proxy) => Number(proxy.remotePort)))].sort((a, b) => a - b);
return {
@@ -265,6 +276,8 @@ function validateK3sArtifacts(ctx, source, rendered, services, workloads, health
}
}
validateCloudApiDbDnsArtifacts(ctx, services, workloads);
expectEqual(ctx, healthContract.data?.endpoint, source.endpoints.api?.url, "deploy/k8s/dev/health-contract.yaml.data.endpoint", "health contract endpoint");
expectEqual(ctx, masterEdge.endpoint, source.endpoints.api?.url, "deploy/master-edge/health-contract.json.endpoint", "master edge endpoint");
flagLegacyPublicPort(ctx, "deploy/master-edge/health-contract.json.reverseLink.publicPort", masterEdge.reverseLink?.publicPort, expectedPorts.api, "master edge API");
@@ -272,6 +285,32 @@ function validateK3sArtifacts(ctx, source, rendered, services, workloads, health
expectEqual(ctx, masterEdge.prodAcceptance, false, "deploy/master-edge/health-contract.json.prodAcceptance", "master edge PROD acceptance");
}
function validateCloudApiDbDnsArtifacts(ctx, services, workloads) {
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);
expect(ctx, Boolean(service), "missing_rendered_artifact", "deploy/k8s/base/services.yaml.cloud-api-db", "cloud-api DB stable DNS Service exists");
if (service) {
const port = (service.spec?.ports ?? []).find((entry) => entry.name === dns.portName);
expectEqual(ctx, service.metadata?.namespace, dns.namespace, "deploy/k8s/base/services.yaml.cloud-api-db.metadata.namespace", "cloud-api DB Service namespace");
expectEqual(ctx, service.spec?.type, "ClusterIP", "deploy/k8s/base/services.yaml.cloud-api-db.spec.type", "cloud-api DB Service type");
expectEqual(ctx, service.spec?.selector, undefined, "deploy/k8s/base/services.yaml.cloud-api-db.spec.selector", "cloud-api DB Service is selectorless until endpoints are provisioned");
expectEqual(ctx, service.metadata?.labels?.["hwlab.pikastech.local/source-contract"], "db-dns", "deploy/k8s/base/services.yaml.cloud-api-db.metadata.labels.source-contract", "cloud-api DB Service source-contract label");
expect(ctx, Boolean(port), "missing_rendered_artifact", "deploy/k8s/base/services.yaml.cloud-api-db.spec.ports.postgres", "cloud-api DB Service postgres port exists");
if (port) {
expectEqual(ctx, port.port, dns.port, "deploy/k8s/base/services.yaml.cloud-api-db.spec.ports.postgres.port", "cloud-api DB Service port");
expectEqual(ctx, port.targetPort, dns.portName, "deploy/k8s/base/services.yaml.cloud-api-db.spec.ports.postgres.targetPort", "cloud-api DB Service targetPort");
}
}
const container = mapByServiceId(listItems(workloads)).get("hwlab-cloud-api")?.spec?.template?.spec?.containers?.[0];
const env = new Map((container?.env ?? []).map((entry) => [entry.name, entry.value]));
expectEqual(ctx, env.get("HWLAB_CLOUD_DB_SERVICE_NAME"), dns.serviceName, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SERVICE_NAME", "cloud API workload DB service name");
expectEqual(ctx, env.get("HWLAB_CLOUD_DB_SERVICE_NAMESPACE"), dns.namespace, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE", "cloud API workload DB service namespace");
expectEqual(ctx, env.get("HWLAB_CLOUD_DB_HOST"), expectedHost, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CLOUD_DB_HOST", "cloud API workload DB host");
expectEqual(ctx, env.get("HWLAB_CLOUD_DB_PORT"), String(dns.port), "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CLOUD_DB_PORT", "cloud API workload DB port");
}
async function buildPlan() {
const ctx = { checks: 0, diagnostics: [] };
const [manifest, services, workloads, healthContract, masterEdge, frpc, frps] = await Promise.all([
+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");
}
+74 -8
View File
@@ -805,8 +805,8 @@ function validateEdgeHealthReport(reporter, edgeReport) {
});
}
function validateCloudApiDbContract(reporter, deploy, workloads) {
const staticContract = inspectCloudApiDbStaticContract(deploy, workloads);
function validateCloudApiDbContract(reporter, deploy, workloads, services) {
const staticContract = inspectCloudApiDbStaticContract(deploy, workloads, services);
const healthContract = summarizeDbContract(buildDbHealthContract());
const evidence = [
{
@@ -822,14 +822,15 @@ function validateCloudApiDbContract(reporter, deploy, workloads) {
"cloud-api-db-env-contract",
"db",
"pass",
"cloud-api DEV DB manifest declares the required env names and redacted Secret reference.",
"cloud-api DEV DB manifest declares the required env names, redacted Secret reference, and stable k3s DB DNS Service.",
evidence
);
} else {
const missing = [
...staticContract.missingDeployEnv,
...staticContract.missingK8sEnv,
...staticContract.missingSecretRefs
...staticContract.missingSecretRefs,
...staticContract.missingDnsContract
];
reporter.check(
"cloud-api-db-env-contract",
@@ -842,7 +843,7 @@ function validateCloudApiDbContract(reporter, deploy, workloads) {
type: "contract_blocker",
scope: "cloud-api-db-env-contract",
summary: `cloud-api DEV DB contract is incomplete; missing ${missing.join(", ")}.`,
nextTask: "Repair deploy/deploy.json and deploy/k8s/base/workloads.yaml so they expose only the required DB env names and redacted Secret reference."
nextTask: "Repair deploy/deploy.json, deploy/k8s/base/workloads.yaml, and deploy/k8s/base/services.yaml so they expose the required DB env names, redacted Secret reference, and stable DB Service DNS."
});
}
@@ -872,7 +873,7 @@ function validateCloudApiDbContract(reporter, deploy, workloads) {
});
}
function inspectCloudApiDbStaticContract(deploy, workloads) {
function inspectCloudApiDbStaticContract(deploy, workloads, services) {
const cloudApi = deploy?.services?.find((service) => service.serviceId === "hwlab-cloud-api") ?? {};
const deployEnv = cloudApi.env ?? {};
const workloadEnv = getWorkloadEnvByServiceId(workloads, "hwlab-cloud-api");
@@ -909,15 +910,22 @@ function inspectCloudApiDbStaticContract(deploy, workloads) {
const missingSecretRefs = fields
.filter((field) => field.secretRef && !field.secretRef.present)
.map((field) => `${field.secretRef.secretName}/${field.secretRef.secretKey}`);
const dnsContract = inspectCloudApiDbDnsContract(deployEnv, workloadEnv, services);
return {
contractVersion: DEV_DB_ENV_CONTRACT.contractVersion,
environment: DEV_DB_ENV_CONTRACT.environment,
ready: missingDeployEnv.length === 0 && missingK8sEnv.length === 0 && missingSecretRefs.length === 0,
ready:
missingDeployEnv.length === 0 &&
missingK8sEnv.length === 0 &&
missingSecretRefs.length === 0 &&
dnsContract.ready,
requiredEnv: fields,
dns: dnsContract,
missingDeployEnv,
missingK8sEnv,
missingSecretRefs,
missingDnsContract: dnsContract.missing,
secretMaterialRead: false,
valuesRedacted: true,
liveDbEvidence: false,
@@ -925,6 +933,64 @@ function inspectCloudApiDbStaticContract(deploy, workloads) {
};
}
function inspectCloudApiDbDnsContract(deployEnv, workloadEnv, services) {
const dns = DEV_DB_ENV_CONTRACT.dns;
const expectedHost = `${dns.serviceName}.${dns.namespace}.svc.cluster.local`;
const service = listManifestItems(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,
portName: dns.portName,
selectorless: service ? service.spec?.selector === undefined : false,
secretMaterialRead: false,
liveDbEvidence: false,
missing,
actual
};
}
function getWorkloadEnvByServiceId(workloads, serviceId) {
const envByName = new Map();
for (const item of listManifestItems(workloads)) {
@@ -1174,7 +1240,7 @@ export async function runPreflight(argv) {
);
validateRegistryCapabilities(reporter, registryCapabilities);
validateCloudApiDbContract(reporter, deploy, contracts[3]);
validateCloudApiDbContract(reporter, deploy, contracts[3], contracts[4]);
validateArtifactPublishReport(reporter, optionalReports.artifactPublish, artifactIdentity, targetShortCommit, targetCommit, args.targetRef);
validateEdgeHealthReport(reporter, optionalReports.edgeHealth);
await validateEdgeContracts(reporter, masterEdge);