fix: classify DEV DB and provider blockers

fix: classify DEV DB and provider blockers
This commit is contained in:
Lyon
2026-05-22 23:38:11 +08:00
committed by GitHub
23 changed files with 1108 additions and 101 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");
}
+299 -13
View File
@@ -15,6 +15,12 @@ const publicHost = "74.48.78.17";
const publicPort = 16667;
const namespace = "hwlab-dev";
const tcpPorts = [16667, 7000, 7402];
const CODE_AGENT_PROVIDER_SECRET_CONTRACT = Object.freeze({
sourceIssue: "pikasTech/HWLAB#143",
env: "OPENAI_API_KEY",
secretName: "hwlab-code-agent-provider",
secretKey: "openai-api-key"
});
const runtimeServices = [
{ serviceId: "hwlab-cloud-api", port: 6667 },
{ serviceId: "hwlab-router", port: 7401 },
@@ -69,11 +75,24 @@ export async function runDevEdgeHealthSmoke(argv) {
};
edgeHealth.runtimeDbReadiness = inspectRuntimeDbReadiness(edgeHealth);
Object.assign(edgeHealth, classify(edgeHealth));
edgeHealth.providerSecretReadiness = inspectProviderSecretReadiness(edgeHealth);
edgeHealth.parallelBlockers = classifyParallelBlockers(edgeHealth);
if (edgeHealth.status === "pass" && edgeHealth.parallelBlockers.length > 0) {
Object.assign(edgeHealth, classifyParallelBlockerStatus(edgeHealth.parallelBlockers));
}
edgeHealth.milestoneImpact = classifyMilestoneImpact(edgeHealth);
const report = await createDevGateReport(edgeHealth);
await maybeWriteReport(report, args);
return { report, exitCode: edgeHealth.status === "pass" ? 0 : 2 };
}
export const __testHooks = Object.freeze({
classifyMilestoneImpact,
classifyParallelBlockers,
inspectProviderSecretReadiness,
summarizeRuntimeDbReadiness
});
function parseArgs(argv) {
const args = {
live: false,
@@ -106,8 +125,7 @@ function nowIso() {
async function createDevGateReport(edgeHealth) {
const commitId = await gitCommitId();
const status = edgeHealth.status === "pass" ? "pass" : edgeHealth.status === "not_run" ? "not_run" : "blocked";
const blockerType = status === "blocked" ? blockerTypeFor(edgeHealth.classification) : null;
const blockerScope = edgeHealth.diagnosis?.likelyLayer ?? edgeHealth.classification;
const blockers = reportBlockers(edgeHealth, status);
return {
$schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json",
@@ -140,6 +158,7 @@ async function createDevGateReport(edgeHealth) {
"node scripts/validate-dev-gate-report.mjs",
"node --check scripts/dev-edge-health-smoke.mjs",
"node --check scripts/src/dev-edge-health-smoke-lib.mjs",
"node --test scripts/src/dev-edge-health-smoke-lib.test.mjs",
"node scripts/dev-edge-health-smoke.mjs --live --write-report"
],
localSmoke: {
@@ -172,16 +191,7 @@ async function createDevGateReport(edgeHealth) {
],
summary: edgeHealth.blocker ?? "DEV edge health preconditions are satisfied."
},
blockers: blockerType
? [
{
type: blockerType,
scope: blockerScope,
status: "open",
summary: edgeHealth.blocker
}
]
: [],
blockers,
edgeHealth
};
}
@@ -204,6 +214,34 @@ function blockerTypeFor(classification) {
return "network_blocker";
}
function reportBlockers(edgeHealth, status) {
if (status !== "blocked") {
return [];
}
const parallelBlockers = Array.isArray(edgeHealth.parallelBlockers) ? edgeHealth.parallelBlockers : [];
if (parallelBlockers.length > 0) {
return parallelBlockers.map(({ type, scope, status: blockerStatus, summary, classification, sourceIssue, impact }) => ({
type,
scope,
status: blockerStatus,
summary,
classification,
sourceIssue,
impact
}));
}
return [
{
type: blockerTypeFor(edgeHealth.classification),
scope: edgeHealth.diagnosis?.likelyLayer ?? edgeHealth.classification,
status: "open",
summary: edgeHealth.blocker
}
];
}
function requireDevEndpoint() {
const endpoint = new URL(DEV_ENDPOINT);
assert.equal(endpoint.protocol, "http:", "DEV endpoint must use http");
@@ -246,7 +284,8 @@ async function inspectContracts() {
serviceCount: deploy.services?.length ?? 0,
edgeProxy: deploy.services?.find((service) => service.serviceId === "hwlab-edge-proxy") ?? null,
tunnelClient: deploy.services?.find((service) => service.serviceId === "hwlab-tunnel-client") ?? null,
cloudApiDb: inspectCloudApiDbContract(deploy)
cloudApiDb: inspectCloudApiDbContract(deploy),
codeAgentProvider: inspectCodeAgentProviderContract(deploy)
}
};
}
@@ -322,6 +361,38 @@ function inspectCloudApiDbContract(deploy) {
};
}
function inspectCodeAgentProviderContract(deploy) {
const env = deploy.services?.find((service) => service.serviceId === "hwlab-cloud-api")?.env ?? {};
const expectedRef = `secretRef:${CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretName}/${CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretKey}`;
const secretRefPresent = env[CODE_AGENT_PROVIDER_SECRET_CONTRACT.env] === expectedRef;
return {
status: secretRefPresent ? "degraded" : "blocked",
sourceIssue: CODE_AGENT_PROVIDER_SECRET_CONTRACT.sourceIssue,
requiredEnv: [
{
name: CODE_AGENT_PROVIDER_SECRET_CONTRACT.env,
present: Boolean(env[CODE_AGENT_PROVIDER_SECRET_CONTRACT.env]),
redacted: true,
source: "k8s-secret-ref"
}
],
secretRef: {
env: CODE_AGENT_PROVIDER_SECRET_CONTRACT.env,
secretName: CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretName,
secretKey: CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretKey,
present: secretRefPresent,
envInjected: false,
secretPresent: "not_observed_by_manifest",
secretKeyPresent: secretRefPresent ? "declared" : "not_observed_by_manifest",
secretValueRead: false,
redacted: true
},
secretMaterialRead: false,
valuesRedacted: true,
note: "Manifest-only Code Agent provider Secret reference; this is not runtime Secret injection or provider connectivity evidence."
};
}
function notRunDbReadiness(blockerMessage) {
return {
status: "not_run",
@@ -330,6 +401,7 @@ function notRunDbReadiness(blockerMessage) {
secret: unknownDbSecretPresence("not_observed"),
connectionAttempted: false,
connectionResult: "not_run",
connectionClassification: "not_run",
liveConnected: false,
valuesRedacted: true,
secretMaterialRead: false,
@@ -359,6 +431,7 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
secret: secretPresence,
connectionAttempted: false,
connectionResult: "health_db_missing",
connectionClassification: "health_db_missing",
liveConnected: false,
valuesRedacted: true,
secretMaterialRead: false,
@@ -374,6 +447,8 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
);
const connectionAttempted = Boolean(db.connectionAttempted ?? db.connectionChecked);
const connectionResult = db.connectionResult ?? db.connection?.result ?? (connectionAttempted ? "unknown" : "not_attempted");
const connectionClassification =
db.connection?.classification ?? classificationForRuntimeDb(db, connectionAttempted, connectionResult);
const liveConnected = Boolean(db.liveConnected ?? db.connected ?? db.ready);
const blockerMessage = liveConnected ? null : db.blocker ?? blockerForRuntimeDb(db, connectionAttempted, connectionResult);
const status = liveConnected ? "pass" : "blocked";
@@ -393,6 +468,7 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
},
connectionAttempted,
connectionResult,
connectionClassification,
liveConnected,
valuesRedacted: db.redaction?.valuesRedacted !== false && db.safety?.valuesRedacted === true,
secretMaterialRead: false,
@@ -404,6 +480,7 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
missingEnv: Array.isArray(db.missingEnv) ? db.missingEnv : [],
connectionAttempted,
connectionResult,
classification: connectionClassification,
liveConnected,
endpointRedacted: db.connection?.endpointRedacted !== false,
valueRedacted: true
@@ -412,6 +489,34 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
};
}
function classificationForRuntimeDb(db, connectionAttempted, connectionResult) {
if (typeof db?.connectionClassification === "string") {
return db.connectionClassification;
}
if (typeof db?.classification === "string") {
return db.classification;
}
if (db?.configReady !== true) {
return "missing_runtime_env";
}
if (!connectionAttempted) {
return "configured_no_live_attempt";
}
if (connectionResult === "dns_error") {
return "dns_resolution_failed";
}
if (connectionResult === "refused") {
return "tcp_refused";
}
if (connectionResult === "timeout") {
return "tcp_timeout";
}
if (connectionResult === "network_unreachable") {
return "network_unreachable";
}
return connectionResult === "connected" ? "tcp_connected" : "tcp_error";
}
function blockerForRuntimeDb(db, connectionAttempted, connectionResult) {
if (db.configReady !== true) {
const missing = Array.isArray(db.missingEnv) && db.missingEnv.length > 0 ? db.missingEnv.join(", ") : "required DB env";
@@ -436,6 +541,18 @@ function unknownDbSecretPresence(state) {
};
}
function unknownProviderSecretPresence(state) {
return {
observable: false,
secretName: CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretName,
secretKey: CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretKey,
secretPresent: state,
secretKeyPresent: state,
secretValueRead: false,
redacted: true
};
}
async function inspectKubernetes() {
const hasKubectl = await commandExists("kubectl");
const result = {
@@ -446,6 +563,7 @@ async function inspectKubernetes() {
endpoints: [],
pods: [],
dbSecret: unknownDbSecretPresence("not_observed"),
codeAgentProviderSecret: unknownProviderSecretPresence("not_observed"),
notes: []
};
if (!hasKubectl) {
@@ -464,6 +582,7 @@ async function inspectKubernetes() {
"-n", namespace, "get", "pods", "-l", "hwlab.pikastech.local/profile=dev", "-o", "json"
]);
await collectDbSecretPresence(result);
await collectCodeAgentProviderSecretPresence(result);
return result;
}
@@ -551,6 +670,48 @@ async function collectDbSecretPresence(result) {
}
}
async function collectCodeAgentProviderSecretPresence(result) {
const ref = CODE_AGENT_PROVIDER_SECRET_CONTRACT;
const base = {
observable: true,
sourceIssue: ref.sourceIssue,
env: ref.env,
secretName: ref.secretName,
secretKey: ref.secretKey,
secretPresent: false,
secretKeyPresent: false,
secretValueRead: false,
redacted: true
};
const exists = await runCommand("kubectl", ["-n", namespace, "get", "secret", ref.secretName, "-o", "name"], {
timeoutMs: 5000
});
if (exists.exitCode !== 0) {
result.codeAgentProviderSecret = {
...base,
command: "kubectl get secret -o name",
error: exists.stderr.trim() || "secret not observed"
};
result.notes.push(`kubectl Code Agent provider Secret presence probe failed: ${exists.stderr.trim()}`);
return;
}
const keyPresence = await runCommand("kubectl", ["-n", namespace, "describe", "secret", ref.secretName], {
timeoutMs: 5000
});
const keyLinePattern = new RegExp(`^\\s*${escapeRegExp(ref.secretKey)}:\\s+\\d+\\s+bytes\\s*$`, "mu");
result.codeAgentProviderSecret = {
...base,
secretPresent: true,
secretKeyPresent: keyPresence.exitCode === 0 && keyLinePattern.test(keyPresence.stdout),
command: "kubectl describe secret <name> (key-presence-only)",
error: keyPresence.exitCode === 0 ? null : keyPresence.stderr.trim()
};
if (keyPresence.exitCode !== 0) {
result.notes.push(`kubectl Code Agent provider Secret key presence probe failed: ${keyPresence.stderr.trim()}`);
}
}
async function inspectClusterDns() {
const results = [];
for (const service of runtimeServices) {
@@ -615,6 +776,131 @@ function classifyReachableHealth(json, runtimeDb = null) {
return { status: "pass", classification: "none", blocker: null };
}
function inspectProviderSecretReadiness(edgeHealth) {
const secret = edgeHealth.kubernetes?.codeAgentProviderSecret ?? unknownProviderSecretPresence("not_observed");
const manifest = edgeHealth.contracts?.deploy?.codeAgentProvider ?? null;
const manifestRefPresent = manifest?.secretRef?.present === true;
const secretPresent = secret.secretPresent === true;
const secretKeyPresent = secret.secretKeyPresent === true;
const status = secret.observable && (!secretPresent || !secretKeyPresent) ? "blocked" : "not_proven";
const classification = status === "blocked" ? "provider_secret_missing" : "provider_secret_presence_not_proven";
return {
status,
sourceIssue: CODE_AGENT_PROVIDER_SECRET_CONTRACT.sourceIssue,
provider: "openai-responses",
requiredEnv: CODE_AGENT_PROVIDER_SECRET_CONTRACT.env,
manifestRefPresent,
secret: {
...secret,
secretValueRead: false,
redacted: true
},
providerCallAttempted: false,
providerConnected: false,
secretMaterialRead: false,
valuesRedacted: true,
classification,
blocker: status === "blocked"
? `Code Agent provider Secret ${CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretName}/${CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretKey} is not present as key-presence evidence; #143 real provider-backed chat remains blocked`
: "Code Agent provider Secret presence was not proven by this edge-health run",
evidence: [
{
source: secret.observable ? "kubernetes-secret-presence" : "runtime-not-observable",
manifestRefPresent,
secretPresent,
secretKeyPresent,
secretValueRead: false,
redacted: true
}
]
};
}
function classifyParallelBlockers(edgeHealth) {
const blockers = [];
const dbReadiness = edgeHealth.runtimeDbReadiness;
if (dbReadiness?.status === "blocked") {
blockers.push({
id: "cloud-api-db-live",
type: "runtime_blocker",
scope: "cloud-api-db",
status: "open",
sourceIssue: "pikasTech/HWLAB#49",
classification: dbReadiness.connectionClassification ?? dbReadiness.connectionResult,
summary: dbReadiness.blocker,
impact: {
blocks: ["DB live readiness", "M4 live agent scheduling precondition", "M5 DEV-LIVE acceptance"],
separateFrom: ["pikasTech/HWLAB#143 provider Secret", "M3 hardware trusted-loop proof"]
}
});
}
const provider = edgeHealth.providerSecretReadiness;
if (provider?.status === "blocked") {
blockers.push({
id: "code-agent-provider-secret",
type: "agent_blocker",
scope: "code-agent-provider-secret",
status: "open",
sourceIssue: CODE_AGENT_PROVIDER_SECRET_CONTRACT.sourceIssue,
classification: provider.classification,
summary: provider.blocker,
impact: {
blocks: ["#143 provider-backed Code Agent chat", "M4 provider-backed agent execution", "M5 DEV-LIVE acceptance"],
separateFrom: ["pikasTech/HWLAB#49 DB live readiness", "M3 hardware trusted-loop proof"]
}
});
}
return blockers;
}
function classifyParallelBlockerStatus(parallelBlockers) {
return blocker(
"parallel_runtime_blocker",
parallelBlockers.map((item) => item.summary).join("; "),
{
likelyLayer: "parallel-dev-blockers",
confidence: "high",
likelyCause: "one or more independent DEV blockers remain open after route health passed",
evidence: parallelBlockers.map(({ id, scope, classification, sourceIssue }) => ({
id,
scope,
classification,
sourceIssue
})),
notProven: ["DB-backed runtime readiness", "provider-backed Code Agent readiness", "M3/M4/M5 DEV-LIVE acceptance"],
nextTask: "Close each independent blocker with redacted evidence, then rerun the read-only edge smoke."
}
);
}
function classifyMilestoneImpact(edgeHealth) {
const dbBlocked = edgeHealth.runtimeDbReadiness?.status === "blocked";
const providerBlocked = edgeHealth.providerSecretReadiness?.status === "blocked";
return {
M3: {
status: "separate_blocker",
summary: "M3 still requires real DEV hardware trusted-loop operation/trace/audit/evidence; DB DNS failure and #143 provider Secret absence do not prove or clear M3."
},
M4: {
status: dbBlocked || providerBlocked ? "blocked" : "not_proven",
blockedBy: [
...(dbBlocked ? ["cloud-api-db-live"] : []),
...(providerBlocked ? ["code-agent-provider-secret"] : [])
],
summary: "M4 live agent-loop remains blocked until DB live readiness and provider-backed agent prerequisites are independently green."
},
M5: {
status: dbBlocked || providerBlocked ? "blocked" : "not_proven",
blockedBy: [
...(dbBlocked ? ["cloud-api-db-live"] : []),
...(providerBlocked ? ["code-agent-provider-secret"] : [])
],
summary: "M5 DEV-LIVE acceptance cannot be promoted from route health, env presence, or provider manifest refs while DB live and #143 provider Secret blockers remain open."
}
};
}
function classifyRefusedPort(publicEdgePort, frpsControl, tunnelHealth) {
if (frpsControl?.status === "error" && frpsControl.code === "ECONNREFUSED") {
const allPortsRefused = tunnelHealth?.status === "error" && tunnelHealth.code === "ECONNREFUSED";
@@ -0,0 +1,111 @@
import assert from "node:assert/strict";
import test from "node:test";
import { __testHooks } from "./dev-edge-health-smoke-lib.mjs";
test("runtime DB readiness preserves dns resolution classification", () => {
const readiness = __testHooks.summarizeRuntimeDbReadiness(
{
configReady: true,
liveConnected: false,
liveDbEvidence: false,
connectionAttempted: true,
connectionResult: "dns_error",
fields: [
{ name: "HWLAB_CLOUD_DB_URL", present: true },
{ name: "HWLAB_CLOUD_DB_SSL_MODE", present: true }
],
secretRefs: [
{
env: "HWLAB_CLOUD_DB_URL",
secretName: "hwlab-cloud-api-dev-db",
secretKey: "database-url",
present: true,
envInjected: true
}
],
connection: {
attempted: true,
result: "dns_error",
classification: "dns_resolution_failed",
endpointRedacted: true,
valueRedacted: true
},
redaction: { valuesRedacted: true },
safety: { valuesRedacted: true, liveDbEvidence: false },
blocker: "DB endpoint DNS resolution failed from the cloud-api runtime"
},
{
observable: true,
secretName: "hwlab-cloud-api-dev-db",
secretKey: "database-url",
secretPresent: true,
secretKeyPresent: true,
secretValueRead: false,
redacted: true
}
);
assert.equal(readiness.status, "blocked");
assert.equal(readiness.connectionResult, "dns_error");
assert.equal(readiness.connectionClassification, "dns_resolution_failed");
assert.equal(readiness.liveConnected, false);
assert.equal(readiness.liveDbEvidence, false);
assert.equal(readiness.secretMaterialRead, false);
assert.equal(readiness.evidence[0].classification, "dns_resolution_failed");
});
test("parallel blockers keep DB DNS and provider Secret gaps independent", () => {
const edgeHealth = {
runtimeDbReadiness: {
status: "blocked",
connectionResult: "dns_error",
connectionClassification: "dns_resolution_failed",
blocker: "DB endpoint DNS resolution failed from the cloud-api runtime"
},
providerSecretReadiness: {
status: "blocked",
classification: "provider_secret_missing",
blocker: "Code Agent provider Secret hwlab-code-agent-provider/openai-api-key is not present as key-presence evidence"
}
};
const blockers = __testHooks.classifyParallelBlockers(edgeHealth);
assert.deepEqual(
blockers.map((blocker) => blocker.scope),
["cloud-api-db", "code-agent-provider-secret"]
);
assert.equal(blockers[0].classification, "dns_resolution_failed");
assert.equal(blockers[0].sourceIssue, "pikasTech/HWLAB#49");
assert.equal(blockers[1].classification, "provider_secret_missing");
assert.equal(blockers[1].sourceIssue, "pikasTech/HWLAB#143");
assert.ok(blockers[0].impact.separateFrom.includes("pikasTech/HWLAB#143 provider Secret"));
assert.ok(blockers[1].impact.separateFrom.includes("pikasTech/HWLAB#49 DB live readiness"));
});
test("provider Secret blocker remains open even if DB is not blocked", () => {
const blockers = __testHooks.classifyParallelBlockers({
runtimeDbReadiness: { status: "pass" },
providerSecretReadiness: {
status: "blocked",
classification: "provider_secret_missing",
blocker: "Code Agent provider Secret hwlab-code-agent-provider/openai-api-key is not present as key-presence evidence"
}
});
assert.equal(blockers.length, 1);
assert.equal(blockers[0].scope, "code-agent-provider-secret");
assert.equal(blockers[0].type, "agent_blocker");
});
test("milestone impact does not promote DB or provider evidence into M3/M4/M5", () => {
const impact = __testHooks.classifyMilestoneImpact({
runtimeDbReadiness: { status: "blocked" },
providerSecretReadiness: { status: "blocked" }
});
assert.equal(impact.M3.status, "separate_blocker");
assert.deepEqual(impact.M4.blockedBy, ["cloud-api-db-live", "code-agent-provider-secret"]);
assert.deepEqual(impact.M5.blockedBy, ["cloud-api-db-live", "code-agent-provider-secret"]);
assert.match(impact.M5.summary, /cannot be promoted/u);
});
+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);
+67
View File
@@ -3,6 +3,10 @@ import assert from "node:assert/strict";
import { readdir, readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
DEV_DB_ENV_CONTRACT,
parseDbUrlContract
} from "../internal/cloud/db-contract.mjs";
import {
ENVIRONMENT_DEV,
SERVICE_IDS,
@@ -53,6 +57,14 @@ async function parseDeployManifest(relativePath) {
return doc;
}
async function parseK8sList(relativePath) {
const raw = await readFile(path.join(repoRoot, relativePath), "utf8");
const doc = JSON.parse(raw);
assert.equal(doc.kind, "List", `${relativePath} kind`);
assert.ok(Array.isArray(doc.items), `${relativePath} items`);
return doc;
}
function assertUnique(name, values) {
assert.equal(new Set(values).size, values.length, `${name} must be unique`);
}
@@ -122,6 +134,8 @@ function assertEnvelopeValidation() {
const schemas = await parseJSONSchemaFiles("protocol/schemas");
const deploySchemas = await parseJSONSchemaFiles("deploy");
const deployManifest = await parseDeployManifest("deploy/deploy.json");
const k8sServices = await parseK8sList("deploy/k8s/base/services.yaml");
const k8sWorkloads = await parseK8sList("deploy/k8s/base/workloads.yaml");
const docsByName = new Map([...schemas, ...deploySchemas].map((item) => [path.basename(item.relativePath), item.doc]));
assert.ok(schemas.length >= 13, "expected protocol schemas");
@@ -137,6 +151,7 @@ assert.equal(deployServicesById.has("hwlab-agent-worker"), true);
assert.equal(deployServicesById.has("hwlab-agent-skills"), true);
const cloudApi = deployServicesById.get("hwlab-cloud-api");
const cloudApiWorkloadEnv = getWorkloadEnv(k8sWorkloads, "hwlab-cloud-api");
assert.equal(deployManifest.health.path, "/health/live", "deploy health source path");
assert.equal(deployManifest.publicEndpoints.frontend.url, "http://74.48.78.17:16666", "deploy frontend endpoint");
assert.equal(deployManifest.publicEndpoints.api.url, "http://74.48.78.17:16667", "deploy api endpoint");
@@ -153,8 +168,60 @@ assert.equal(cloudApi.env.HWLAB_IMAGE_TAG, deployManifest.commitId.slice(0, 7),
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_URL, "secretRef:hwlab-cloud-api-dev-db/database-url", "cloud-api DB URL must be a Secret reference placeholder");
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_SSL_MODE, "require", "cloud-api DB SSL mode");
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_CONTRACT, "dev-redacted-presence-only", "cloud-api DB contract marker");
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_SERVICE_NAME, DEV_DB_ENV_CONTRACT.dns.serviceName, "cloud-api DB service name");
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE, DEV_DB_ENV_CONTRACT.dns.namespace, "cloud-api DB service namespace");
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_HOST, DEV_DB_ENV_CONTRACT.dns.host, "cloud-api DB stable host");
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_PORT, String(DEV_DB_ENV_CONTRACT.dns.port), "cloud-api DB stable port");
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_DB_SERVICE_NAME?.value, DEV_DB_ENV_CONTRACT.dns.serviceName, "cloud-api workload DB service name");
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_DB_SERVICE_NAMESPACE?.value, DEV_DB_ENV_CONTRACT.dns.namespace, "cloud-api workload DB service namespace");
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_DB_HOST?.value, DEV_DB_ENV_CONTRACT.dns.host, "cloud-api workload DB host");
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_DB_PORT?.value, String(DEV_DB_ENV_CONTRACT.dns.port), "cloud-api workload DB port");
assertCloudApiDbDnsContract(k8sServices, cloudApi.env);
assert.equal(cloudApi.env.HWLAB_CODE_AGENT_PROVIDER, "openai", "cloud-api Code Agent provider");
assert.equal(cloudApi.env.HWLAB_CODE_AGENT_MODEL, "gpt-5.5", "cloud-api Code Agent model");
assert.equal(cloudApi.env.OPENAI_API_KEY, "secretRef:hwlab-code-agent-provider/openai-api-key", "cloud-api Code Agent OpenAI key must be a Secret reference placeholder");
console.log(`validated ${schemas.length + deploySchemas.length} JSON files and ${SERVICE_IDS.length} service ids`);
function listItems(document) {
return document?.kind === "List" ? document.items ?? [] : [document].filter(Boolean);
}
function getWorkloadEnv(workloads, serviceId) {
for (const item of listItems(workloads)) {
const itemServiceId =
item?.metadata?.labels?.["hwlab.pikastech.local/service-id"] ||
item?.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/service-id"] ||
item?.metadata?.name;
if (itemServiceId !== serviceId) {
continue;
}
const container = item?.spec?.template?.spec?.containers?.find((entry) => entry.name === serviceId);
return Object.fromEntries((container?.env ?? []).map((entry) => [entry.name, entry]));
}
return {};
}
function assertCloudApiDbDnsContract(services, env) {
const dns = DEV_DB_ENV_CONTRACT.dns;
const service = listItems(services).find((item) => item?.metadata?.name === dns.serviceName);
assert.ok(service, `deploy/k8s/base/services.yaml must define Service/${dns.serviceName}`);
assert.equal(service.metadata.namespace, dns.namespace, "cloud-api DB Service namespace");
assert.equal(service.kind, "Service", "cloud-api DB Service kind");
assert.equal(service.spec?.type, "ClusterIP", "cloud-api DB Service type");
assert.equal(service.spec?.selector, undefined, "cloud-api DB Service must be selectorless until DB endpoints are provisioned");
assert.equal(service.metadata?.labels?.["hwlab.pikastech.local/source-contract"], "db-dns", "cloud-api DB Service source-contract label");
const port = service.spec?.ports?.find((entry) => entry.name === dns.portName);
assert.ok(port, `Service/${dns.serviceName} must expose ${dns.portName}`);
assert.equal(port.port, dns.port, "cloud-api DB Service port");
assert.equal(port.targetPort, dns.portName, "cloud-api DB Service targetPort");
const expectedHost = `${dns.serviceName}.${dns.namespace}.svc.cluster.local`;
assert.equal(dns.host, expectedHost, "DB DNS contract host matches service namespace");
assert.equal(env.HWLAB_CLOUD_DB_HOST, expectedHost, "cloud-api DB host env matches Service DNS");
assert.equal(env.HWLAB_CLOUD_DB_SERVICE_NAME, service.metadata.name, "cloud-api DB service env matches Service name");
assert.equal(env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE, service.metadata.namespace, "cloud-api DB namespace env matches Service namespace");
assert.equal(env.HWLAB_CLOUD_DB_PORT, String(port.port), "cloud-api DB port env matches Service port");
const secretUrl = parseDbUrlContract(`postgres://${env.HWLAB_CLOUD_DB_HOST}:${env.HWLAB_CLOUD_DB_PORT}/hwlab`);
assert.equal(secretUrl.ok, true, "cloud-api DB stable host/port must form a supported postgres URL");
}
+27 -2
View File
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { DEV_DB_ENV_CONTRACT } from "../internal/cloud/db-contract.mjs";
import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../internal/protocol/index.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@@ -13,6 +14,7 @@ const forbiddenRuntimeSubstitutes = Object.freeze([
"provider-gateway",
"microservice-proxy"
]);
const dbDnsComponent = "cloud-api-db";
async function readText(relativePath) {
return readFile(path.join(repoRoot, relativePath), "utf8");
@@ -130,6 +132,24 @@ function assertMetadataServiceId(resource, expectedServiceId, label) {
assert.equal(labels["app.kubernetes.io/name"], expectedServiceId, `${label} app name label`);
}
function assertDbDnsService(service) {
const dns = DEV_DB_ENV_CONTRACT.dns;
assert.equal(service.metadata?.name, dns.serviceName, "DB DNS Service name");
assertDevNamespace(service.metadata?.namespace, "DB DNS Service namespace");
assert.equal(service.metadata?.labels?.["app.kubernetes.io/name"], dbDnsComponent, "DB DNS app label");
assert.equal(service.metadata?.labels?.["hwlab.pikastech.local/component"], dbDnsComponent, "DB DNS component label");
assert.equal(service.metadata?.labels?.["hwlab.pikastech.local/source-contract"], "db-dns", "DB DNS source-contract label");
assert.equal(service.metadata?.labels?.[serviceIdLabel], undefined, "DB DNS Service is not a frozen HWLAB runtime service-id");
assert.equal(service.spec?.type, "ClusterIP", "DB DNS Service type");
assert.equal(service.spec?.selector, undefined, "DB DNS Service stays selectorless until endpoints are provisioned");
const ports = service.spec?.ports;
assertArray(ports, "DB DNS Service ports");
assert.equal(ports.length, 1, "DB DNS Service exposes one port");
assert.equal(ports[0].name, dns.portName, "DB DNS Service port name");
assert.equal(ports[0].port, dns.port, "DB DNS Service port");
assert.equal(ports[0].targetPort, dns.portName, "DB DNS Service targetPort");
}
function assertTemplateServiceId(workload, expectedServiceId, label) {
const templateLabels = workload.spec?.template?.metadata?.labels;
assertObject(templateLabels, `${label}.spec.template.metadata.labels`);
@@ -291,11 +311,16 @@ function assertServices(services, guard) {
assert.equal(services.kind, "List", "services kind");
assertArray(services.items, "services items");
const serviceIds = services.items.map((item) => item.metadata?.labels?.[serviceIdLabel]);
const runtimeServices = services.items.filter((item) => item.metadata?.name !== dbDnsComponent);
const serviceIds = runtimeServices.map((item) => item.metadata?.labels?.[serviceIdLabel]);
assertUnique(serviceIds, "k8s service ids");
assertSameMembers(serviceIds, guard.requiredK3sSkeleton.requiredServiceServiceIds, "k8s service ids");
for (const service of services.items) {
const dbDnsService = services.items.find((item) => item.metadata?.name === dbDnsComponent);
assert.ok(dbDnsService, "k8s services must include selectorless cloud-api-db DNS Service");
assertDbDnsService(dbDnsService);
for (const service of runtimeServices) {
const serviceId = service.metadata?.labels?.[serviceIdLabel];
assertServiceId(serviceId, `k8s service ${service.metadata?.name}`);
assertMetadataServiceId(service, serviceId, `k8s service ${serviceId}`);