Files
pikasTech-HWLAB/scripts/src/deploy-contract-plan.mjs
T
2026-05-23 04:56:21 +00:00

608 lines
29 KiB
JavaScript

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_CODE_AGENT_PROVIDER_CONTRACT,
codeAgentSecretRefPlaceholder
} from "../../internal/cloud/code-agent-contract.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const expectedPorts = Object.freeze({ frontend: 16666, api: 16667 });
const legacyPublicPorts = new Set([6666, 6667]);
const duplicateMappingAllowed = new Set(["hwlab-box-simu", "hwlab-gateway-simu"]);
const tomlBareKeyPattern = /^[A-Za-z0-9_-]+$/u;
function parseArgs(argv) {
const args = { check: false, pretty: false };
for (const arg of argv) {
if (arg === "--check") {
args.check = true;
} else if (arg === "--pretty") {
args.pretty = true;
} else {
throw new Error(`unknown argument: ${arg}`);
}
}
return args;
}
async function readJson(relativePath) {
return JSON.parse(await readFile(path.join(repoRoot, relativePath), "utf8"));
}
function stripTomlComment(line) {
let inString = false;
let escaped = false;
for (let index = 0; index < line.length; index += 1) {
const char = line[index];
if (inString) {
if (escaped) {
escaped = false;
} else if (char === "\\") {
escaped = true;
} else if (char === "\"") {
inString = false;
}
} else if (char === "\"") {
inString = true;
} else if (char === "#") {
return line.slice(0, index);
}
}
return line;
}
function parseTomlValue(value, relativePath, lineNumber) {
if (value.startsWith("\"")) {
try {
const parsed = JSON.parse(value);
if (typeof parsed !== "string") {
throw new Error("not a string");
}
return parsed;
} catch {
throw new Error(`${relativePath}:${lineNumber}: invalid TOML basic string`);
}
}
if (value === "true") return true;
if (value === "false") return false;
if (/^[+-]?\d(?:_?\d)*$/u.test(value)) {
return Number.parseInt(value.replaceAll("_", ""), 10);
}
throw new Error(`${relativePath}:${lineNumber}: unsupported TOML value ${JSON.stringify(value)}`);
}
// Keep this parser scoped to the FRP TOML subset used in this repo so the dry-run
// contract check does not depend on Python 3.11's tomllib being present.
export function parseFrpTomlText(text, relativePath = "FRP TOML") {
const document = {};
let current = document;
for (const [lineIndex, rawLine] of text.split(/\r?\n/u).entries()) {
const lineNumber = lineIndex + 1;
const line = stripTomlComment(rawLine).trim();
if (line === "") continue;
const arrayTableMatch = line.match(/^\[\[([A-Za-z0-9_-]+)\]\]$/u);
if (arrayTableMatch) {
const name = arrayTableMatch[1];
if (!Array.isArray(document[name])) {
if (Object.hasOwn(document, name)) {
throw new Error(`${relativePath}:${lineNumber}: cannot redefine scalar ${name} as array table`);
}
document[name] = [];
}
current = {};
document[name].push(current);
continue;
}
if (line.startsWith("[") || line.includes("]")) {
throw new Error(`${relativePath}:${lineNumber}: only root keys and array-of-table headers are supported`);
}
const assignmentMatch = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/u);
if (!assignmentMatch) {
throw new Error(`${relativePath}:${lineNumber}: expected key = value`);
}
const [, key, valueSource] = assignmentMatch;
if (!tomlBareKeyPattern.test(key)) {
throw new Error(`${relativePath}:${lineNumber}: unsupported TOML key ${JSON.stringify(key)}`);
}
if (Object.hasOwn(current, key)) {
throw new Error(`${relativePath}:${lineNumber}: duplicate TOML key ${key}`);
}
current[key] = parseTomlValue(valueSource.trim(), relativePath, lineNumber);
}
return document;
}
async function parseToml(relativePath) {
return parseFrpTomlText(await readFile(path.join(repoRoot, relativePath), "utf8"), relativePath);
}
function addDiagnostic(ctx, code, jsonPath, message, { expected, actual } = {}) {
ctx.diagnostics.push({
code,
path: jsonPath,
message,
...(expected === undefined ? {} : { expected }),
...(actual === undefined ? {} : { actual })
});
}
function expect(ctx, condition, code, jsonPath, message, details) {
ctx.checks += 1;
if (!condition) {
addDiagnostic(ctx, code, jsonPath, message, details);
}
}
function expectEqual(ctx, actual, expected, jsonPath, message) {
expect(ctx, Object.is(actual, expected), "contract_mismatch", jsonPath, message, { expected, actual });
}
function flagLegacyPublicPort(ctx, jsonPath, actual, expected, label) {
if (legacyPublicPorts.has(Number(actual))) {
addDiagnostic(ctx, "legacy_public_endpoint_drift", jsonPath, `${label} uses legacy public port ${actual}`, {
expected,
actual
});
return;
}
expectEqual(ctx, Number(actual), expected, jsonPath, `${label} public port`);
}
function listItems(doc) {
return doc?.kind === "List" ? doc.items ?? [] : [doc].filter(Boolean);
}
function mapByServiceId(items) {
return new Map(
items.map((item) => [
item?.metadata?.labels?.["hwlab.pikastech.local/service-id"] ?? item?.metadata?.name,
item
])
);
}
function mapByName(items) {
return new Map(items.map((item) => [item?.metadata?.name, item]));
}
function parseEndpoint(ctx, id, endpoint) {
const expectedPort = expectedPorts[id];
expect(ctx, Boolean(endpoint), "missing_source", `$.publicEndpoints.${id}`, `${id} public endpoint is required`);
if (!endpoint) return null;
let urlPort = null;
try {
const url = new URL(endpoint.url);
urlPort = Number(url.port || (url.protocol === "http:" ? 80 : 443));
expectEqual(ctx, url.protocol.replace(":", ""), endpoint.protocol, `$.publicEndpoints.${id}.url`, `${id} URL protocol`);
expectEqual(ctx, url.hostname, endpoint.host, `$.publicEndpoints.${id}.url`, `${id} URL host`);
} catch (error) {
addDiagnostic(ctx, "invalid_url", `$.publicEndpoints.${id}.url`, `${id} public endpoint URL is invalid`, {
actual: endpoint.url
});
}
expectEqual(ctx, endpoint.protocol, "http", `$.publicEndpoints.${id}.protocol`, `${id} public endpoint protocol`);
expectEqual(ctx, endpoint.host, "74.48.78.17", `$.publicEndpoints.${id}.host`, `${id} public endpoint host`);
flagLegacyPublicPort(ctx, `$.publicEndpoints.${id}.port`, endpoint.port, expectedPort, `${id} endpoint`);
if (urlPort !== null) {
flagLegacyPublicPort(ctx, `$.publicEndpoints.${id}.url`, urlPort, expectedPort, `${id} endpoint URL`);
}
return { endpointId: id, ...endpoint };
}
function validateSource(ctx, manifest) {
expectEqual(ctx, manifest.environment, "dev", "$.environment", "manifest environment");
expectEqual(ctx, manifest.profiles?.prod?.enabled, false, "$.profiles.prod.enabled", "PROD profile must stay disabled");
expectEqual(ctx, manifest.health?.method, "GET", "$.health.method", "health method");
expectEqual(ctx, manifest.health?.path, "/health/live", "$.health.path", "health path");
expectEqual(ctx, manifest.health?.responseFormat, "json", "$.health.responseFormat", "health response format");
const services = manifest.services ?? [];
const servicesById = new Map(services.map((service) => [service.serviceId, service]));
for (const service of services) {
expectEqual(ctx, service.healthPath, manifest.health?.path, `$.services.${service.serviceId}.healthPath`, `${service.serviceId} health path`);
}
const endpoints = {
frontend: parseEndpoint(ctx, "frontend", manifest.publicEndpoints?.frontend),
api: parseEndpoint(ctx, "api", manifest.publicEndpoints?.api)
};
for (const [id, endpoint] of Object.entries(endpoints)) {
expect(ctx, servicesById.has(endpoint?.serviceId), "unknown_service", `$.publicEndpoints.${id}.serviceId`, `${id} public endpoint service must exist`);
}
expectEqual(ctx, manifest.endpoint, endpoints.api?.url, "$.endpoint", "legacy manifest endpoint alias must point to API endpoint");
expectEqual(ctx, manifest.profiles?.dev?.endpoint, endpoints.api?.url, "$.profiles.dev.endpoint", "DEV profile endpoint");
const cloudApi = servicesById.get("hwlab-cloud-api");
const tunnelClient = servicesById.get("hwlab-tunnel-client");
const cli = servicesById.get("hwlab-cli");
const patchPanel = servicesById.get("hwlab-patch-panel");
expectEqual(ctx, cloudApi?.env?.HWLAB_PUBLIC_ENDPOINT, endpoints.api?.url, "$.services.hwlab-cloud-api.env.HWLAB_PUBLIC_ENDPOINT", "cloud API public endpoint env");
validateCloudApiDbSource(ctx, cloudApi?.env ?? {});
validateCloudApiCodeAgentSource(ctx, cloudApi?.env ?? {});
validateM3PatchPanelSource(ctx, patchPanel);
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");
const proxies = manifest.frp?.proxies ?? [];
const proxyNames = new Set();
for (const [index, proxy] of proxies.entries()) {
const basePath = `$.frp.proxies[${index}]`;
expect(ctx, !proxyNames.has(proxy.name), "duplicate_source", `${basePath}.name`, `duplicate frp proxy ${proxy.name}`);
proxyNames.add(proxy.name);
expectEqual(ctx, proxy.type, "tcp", `${basePath}.type`, `${proxy.name} proxy type`);
expectEqual(ctx, proxy.localHost, `${proxy.localServiceId}.${manifest.namespace}.svc.cluster.local`, `${basePath}.localHost`, `${proxy.name} local host`);
if (proxy.endpointId) {
const endpoint = endpoints[proxy.endpointId];
expectEqual(ctx, endpoint?.frpProxy, proxy.name, `${basePath}.name`, `${proxy.endpointId} endpoint frp proxy`);
flagLegacyPublicPort(ctx, `${basePath}.remotePort`, proxy.remotePort, endpoint?.port, `${proxy.name} remote`);
}
}
const serviceMappings = manifest.k3s?.serviceMappings ?? [];
const mappingIds = new Set();
const mappingNames = new Set();
for (const [index, mapping] of serviceMappings.entries()) {
const basePath = `$.k3s.serviceMappings[${index}]`;
expect(ctx, servicesById.has(mapping.serviceId), "unknown_service", `${basePath}.serviceId`, `${mapping.serviceId} must exist in services`);
expect(ctx, !mappingNames.has(mapping.name), "duplicate_source", `${basePath}.name`, `duplicate k3s mapping name ${mapping.name}`);
mappingNames.add(mapping.name);
expect(
ctx,
!mappingIds.has(mapping.serviceId) || duplicateMappingAllowed.has(mapping.serviceId),
"duplicate_source",
`${basePath}.serviceId`,
`duplicate k3s mapping ${mapping.serviceId}`
);
mappingIds.add(mapping.serviceId);
expectEqual(ctx, mapping.namespace, manifest.namespace, `${basePath}.namespace`, `${mapping.serviceId} namespace`);
expectEqual(ctx, mapping.targetPort, "http", `${basePath}.targetPort`, `${mapping.serviceId} targetPort`);
}
return { manifest, endpoints, servicesById, proxies, serviceMappings, healthPath: manifest.health?.path };
}
function requiredM3Route() {
return {
fromResourceId: "res_boxsimu_1",
fromPort: "DO1",
patchPanelServiceId: "hwlab-patch-panel",
toResourceId: "res_boxsimu_2",
toPort: "DI1"
};
}
function parseJsonEnv(ctx, value, jsonPath, label) {
try {
return JSON.parse(String(value ?? ""));
} catch (error) {
addDiagnostic(ctx, "invalid_json", jsonPath, `${label} must be JSON`, {
actual: error instanceof Error ? error.message : String(error)
});
return null;
}
}
function validateM3PatchPanelSource(ctx, patchPanel) {
const route = requiredM3Route();
expectEqual(ctx, patchPanel?.m3Route?.fromResourceId, route.fromResourceId, "$.services.hwlab-patch-panel.m3Route.fromResourceId", "patch panel first-class M3 source resource");
expectEqual(ctx, patchPanel?.m3Route?.fromPort, route.fromPort, "$.services.hwlab-patch-panel.m3Route.fromPort", "patch panel first-class M3 source port");
expectEqual(ctx, patchPanel?.m3Route?.patchPanelServiceId, route.patchPanelServiceId, "$.services.hwlab-patch-panel.m3Route.patchPanelServiceId", "patch panel first-class M3 route owner");
expectEqual(ctx, patchPanel?.m3Route?.toResourceId, route.toResourceId, "$.services.hwlab-patch-panel.m3Route.toResourceId", "patch panel first-class M3 target resource");
expectEqual(ctx, patchPanel?.m3Route?.toPort, route.toPort, "$.services.hwlab-patch-panel.m3Route.toPort", "patch panel first-class M3 target port");
const endpointMap = parseJsonEnv(
ctx,
patchPanel?.env?.HWLAB_PATCH_PANEL_ENDPOINT_MAP,
"$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_ENDPOINT_MAP",
"patch panel endpoint map"
);
expectEqual(
ctx,
endpointMap?.res_boxsimu_2,
"http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201",
"$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_ENDPOINT_MAP.res_boxsimu_2",
"patch panel M3 target endpoint"
);
const wiring = parseJsonEnv(
ctx,
patchPanel?.env?.HWLAB_PATCH_PANEL_WIRING_CONFIG,
"$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG",
"patch panel wiring config"
);
const connection = wiring?.connections?.[0];
expectEqual(ctx, wiring?.status, "active", "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.status", "patch panel M3 wiring status");
expectEqual(
ctx,
wiring?.constraints?.propagation,
"patch-panel-only",
"$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.constraints.propagation",
"patch panel M3 wiring propagation"
);
expectEqual(
ctx,
wiring?.constraints?.localLoopbackSubstituteAllowed,
false,
"$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.constraints.localLoopbackSubstituteAllowed",
"patch panel M3 wiring forbids loopback substitute"
);
expectEqual(ctx, connection?.from?.resourceId, route.fromResourceId, "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.connections[0].from.resourceId", "patch panel M3 source resource");
expectEqual(ctx, connection?.from?.port, route.fromPort, "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.connections[0].from.port", "patch panel M3 source port");
expectEqual(ctx, connection?.to?.resourceId, route.toResourceId, "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.connections[0].to.resourceId", "patch panel M3 target resource");
expectEqual(ctx, connection?.to?.port, route.toPort, "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.connections[0].to.port", "patch panel M3 target port");
expectEqual(ctx, connection?.mode, "exclusive", "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.connections[0].mode", "patch panel M3 route mode");
}
function validateCloudApiDbSource(ctx, env) {
const alias = DEV_DB_ENV_CONTRACT.dns;
expectEqual(ctx, env.HWLAB_CLOUD_DB_URL, "secretRef:hwlab-cloud-api-dev-db/database-url", "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_URL", "cloud API DB URL Secret reference");
expectEqual(
ctx,
env.HWLAB_CLOUD_DB_SSL_MODE,
DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE,
"$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SSL_MODE",
"cloud API DEV DB SSL mode"
);
expectEqual(ctx, env.HWLAB_CLOUD_DB_CONTRACT, "dev-redacted-presence-only", "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_CONTRACT", "cloud API DB redacted contract marker");
expectEqual(ctx, DEV_DB_ENV_CONTRACT.endpointAuthority.source, "secret-url-host", "internal/cloud/db-contract.mjs.endpointAuthority.source", "cloud API DB runtime authority source");
expectEqual(ctx, DEV_DB_ENV_CONTRACT.endpointAuthority.env, "HWLAB_CLOUD_DB_URL", "internal/cloud/db-contract.mjs.endpointAuthority.env", "cloud API DB runtime authority env");
expectEqual(ctx, alias.requiredForReadiness, false, "internal/cloud/db-contract.mjs.dns.requiredForReadiness", "cloud API DB DNS alias readiness requirement");
expectEqual(ctx, alias.usedForProbe, false, "internal/cloud/db-contract.mjs.dns.usedForProbe", "cloud API DB DNS alias probe source");
for (const name of [
"HWLAB_CLOUD_DB_SERVICE_NAME",
"HWLAB_CLOUD_DB_SERVICE_NAMESPACE",
"HWLAB_CLOUD_DB_HOST",
"HWLAB_CLOUD_DB_PORT"
]) {
expectEqual(ctx, env[name], undefined, `$.services.hwlab-cloud-api.env.${name}`, `${name} is optional and not a readiness hard requirement`);
}
}
function validateCloudApiCodeAgentSource(ctx, env) {
const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT;
expectEqual(ctx, env.HWLAB_CODE_AGENT_PROVIDER, contract.provider, "$.services.hwlab-cloud-api.env.HWLAB_CODE_AGENT_PROVIDER", "cloud API Code Agent provider");
expectEqual(ctx, env.HWLAB_CODE_AGENT_MODEL, contract.model, "$.services.hwlab-cloud-api.env.HWLAB_CODE_AGENT_MODEL", "cloud API Code Agent model");
expectEqual(
ctx,
env.HWLAB_CODE_AGENT_OPENAI_BASE_URL,
contract.egress.defaultBaseUrl,
"$.services.hwlab-cloud-api.env.HWLAB_CODE_AGENT_OPENAI_BASE_URL",
"cloud API Code Agent DEV egress/proxy base URL"
);
expect(
ctx,
env.HWLAB_CODE_AGENT_OPENAI_BASE_URL !== contract.egress.forbiddenDirectBaseUrl,
"forbidden_direct_egress",
"$.services.hwlab-cloud-api.env.HWLAB_CODE_AGENT_OPENAI_BASE_URL",
"cloud API Code Agent must not point directly at public api.openai.com",
{ expected: contract.egress.target, actual: env.HWLAB_CODE_AGENT_OPENAI_BASE_URL }
);
expectEqual(ctx, env.OPENAI_API_KEY, codeAgentSecretRefPlaceholder(), "$.services.hwlab-cloud-api.env.OPENAI_API_KEY", "cloud API Code Agent OpenAI Secret reference");
}
function renderPlan(source) {
const proxyPorts = [...new Set(source.proxies.map((proxy) => Number(proxy.remotePort)))].sort((a, b) => a - b);
return {
publicEndpoints: Object.fromEntries(
Object.entries(source.endpoints).map(([id, endpoint]) => [id, {
url: endpoint?.url,
serviceId: endpoint?.serviceId,
port: endpoint?.port,
frpProxy: endpoint?.frpProxy
}])
),
frpc: {
serverAddr: source.manifest.frp?.server?.address,
serverPort: source.manifest.frp?.server?.bindPort,
proxies: source.proxies.map((proxy) => ({
name: proxy.name,
type: proxy.type,
localIP: proxy.localHost,
localPort: proxy.localPort,
remotePort: proxy.remotePort
}))
},
frps: {
bindPort: source.manifest.frp?.server?.bindPort,
allowPorts: proxyPorts.map((port) => ({ start: port, end: port }))
},
k3sServices: source.serviceMappings.map((mapping) => ({
name: mapping.name,
namespace: mapping.namespace,
serviceId: mapping.serviceId,
port: mapping.port,
targetPort: mapping.targetPort
})),
health: {
method: source.manifest.health?.method,
path: source.healthPath,
endpoint: source.endpoints.api?.url
}
};
}
function validateFrpArtifacts(ctx, source, rendered, frpc, frps) {
expectEqual(ctx, frpc.serverAddr, rendered.frpc.serverAddr, "deploy/frp/frpc.dev.toml.serverAddr", "frpc server address");
expectEqual(ctx, frpc.serverPort, rendered.frpc.serverPort, "deploy/frp/frpc.dev.toml.serverPort", "frpc server port");
const actualProxies = new Map((frpc.proxies ?? []).map((proxy) => [proxy.name, proxy]));
for (const proxy of source.proxies) {
const actual = actualProxies.get(proxy.name);
expect(ctx, Boolean(actual), "missing_rendered_artifact", `deploy/frp/frpc.dev.toml.${proxy.name}`, `${proxy.name} proxy exists`);
if (!actual) continue;
expectEqual(ctx, actual.type, proxy.type, `deploy/frp/frpc.dev.toml.${proxy.name}.type`, `${proxy.name} type`);
expectEqual(ctx, actual.localIP, proxy.localHost, `deploy/frp/frpc.dev.toml.${proxy.name}.localIP`, `${proxy.name} local IP`);
expectEqual(ctx, actual.localPort, proxy.localPort, `deploy/frp/frpc.dev.toml.${proxy.name}.localPort`, `${proxy.name} local port`);
if (proxy.endpointId) {
flagLegacyPublicPort(ctx, `deploy/frp/frpc.dev.toml.${proxy.name}.remotePort`, actual.remotePort, source.endpoints[proxy.endpointId]?.port, `${proxy.name} rendered remote`);
} else {
expectEqual(ctx, actual.remotePort, proxy.remotePort, `deploy/frp/frpc.dev.toml.${proxy.name}.remotePort`, `${proxy.name} remote port`);
}
}
expectEqual(ctx, frps.bindPort, rendered.frps.bindPort, "deploy/frp/frps.dev.toml.bindPort", "frps bind port");
for (const allow of rendered.frps.allowPorts) {
const match = (frps.allowPorts ?? []).some((actual) => actual.start === allow.start && actual.end === allow.end);
expect(ctx, match, "missing_rendered_artifact", `deploy/frp/frps.dev.toml.allowPorts.${allow.start}`, `frps allows remote port ${allow.start}`);
}
for (const [index, allow] of (frps.allowPorts ?? []).entries()) {
for (const legacyPort of legacyPublicPorts) {
if (allow.start <= legacyPort && allow.end >= legacyPort) {
addDiagnostic(ctx, "legacy_public_endpoint_drift", `deploy/frp/frps.dev.toml.allowPorts[${index}]`, `frps allows legacy public port ${legacyPort}`, {
expected: [expectedPorts.frontend, expectedPorts.api],
actual: { start: allow.start, end: allow.end }
});
}
}
}
}
function validateK3sArtifacts(ctx, source, rendered, services, workloads, healthContract, masterEdge) {
const serviceItems = mapByName(listItems(services));
const workloadItems = mapByServiceId(listItems(workloads));
for (const expected of rendered.k3sServices) {
const actual = serviceItems.get(expected.name);
expect(ctx, Boolean(actual), "missing_rendered_artifact", `deploy/k8s/base/services.yaml.${expected.serviceId}`, `${expected.serviceId} k3s service exists`);
if (actual) {
const port = (actual.spec?.ports ?? []).find((entry) => entry.name === "http") ?? actual.spec?.ports?.[0];
expectEqual(ctx, actual.metadata?.name, expected.name, `deploy/k8s/base/services.yaml.${expected.serviceId}.metadata.name`, `${expected.serviceId} service name`);
expectEqual(ctx, actual.metadata?.namespace, expected.namespace, `deploy/k8s/base/services.yaml.${expected.serviceId}.metadata.namespace`, `${expected.serviceId} service namespace`);
expectEqual(ctx, port?.port, expected.port, `deploy/k8s/base/services.yaml.${expected.serviceId}.spec.ports.http.port`, `${expected.serviceId} service port`);
expectEqual(ctx, port?.targetPort, expected.targetPort, `deploy/k8s/base/services.yaml.${expected.serviceId}.spec.ports.http.targetPort`, `${expected.serviceId} service targetPort`);
}
const container = workloadItems.get(expected.serviceId)?.spec?.template?.spec?.containers?.[0];
for (const probeName of ["readinessProbe", "livenessProbe"]) {
if (container?.[probeName]?.httpGet?.path) {
expectEqual(ctx, container[probeName].httpGet.path, source.healthPath, `deploy/k8s/base/workloads.yaml.${expected.serviceId}.${probeName}`, `${expected.serviceId} ${probeName} path`);
}
}
}
validateCloudApiDbOptionalAliasArtifacts(ctx, services, workloads);
validateCloudApiCodeAgentArtifacts(ctx, 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");
flagLegacyPublicPort(ctx, "deploy/master-edge/health-contract.json.reverseLink.webPublicPort", masterEdge.reverseLink?.webPublicPort, expectedPorts.frontend, "master edge frontend");
expectEqual(ctx, masterEdge.prodAcceptance, false, "deploy/master-edge/health-contract.json.prodAcceptance", "master edge PROD acceptance");
}
function validateCloudApiDbOptionalAliasArtifacts(ctx, services, workloads) {
const alias = DEV_DB_ENV_CONTRACT.dns;
const service = listItems(services).find((item) => item?.metadata?.name === alias.serviceName);
expectEqual(ctx, service, undefined, "deploy/k8s/base/services.yaml.cloud-api-db", "cloud-api-db remains optional until Service/Endpoint ownership and rollout contract exist");
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_SSL_MODE"),
DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE,
"deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SSL_MODE",
"cloud API workload DEV DB SSL mode"
);
for (const name of [
"HWLAB_CLOUD_DB_SERVICE_NAME",
"HWLAB_CLOUD_DB_SERVICE_NAMESPACE",
"HWLAB_CLOUD_DB_HOST",
"HWLAB_CLOUD_DB_PORT"
]) {
expectEqual(ctx, env.get(name), undefined, `deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.${name}`, `${name} is optional and not injected as readiness authority`);
}
}
function validateCloudApiCodeAgentArtifacts(ctx, workloads) {
const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT;
const secretRef = contract.secretRefs[0];
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]));
expectEqual(ctx, env.get("HWLAB_CODE_AGENT_PROVIDER")?.value, contract.provider, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CODE_AGENT_PROVIDER", "cloud API workload Code Agent provider");
expectEqual(ctx, env.get("HWLAB_CODE_AGENT_MODEL")?.value, contract.model, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CODE_AGENT_MODEL", "cloud API workload Code Agent model");
expectEqual(
ctx,
env.get("HWLAB_CODE_AGENT_OPENAI_BASE_URL")?.value,
contract.egress.defaultBaseUrl,
"deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CODE_AGENT_OPENAI_BASE_URL",
"cloud API workload Code Agent DEV egress/proxy base URL"
);
expect(
ctx,
env.get("HWLAB_CODE_AGENT_OPENAI_BASE_URL")?.value !== contract.egress.forbiddenDirectBaseUrl,
"forbidden_direct_egress",
"deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CODE_AGENT_OPENAI_BASE_URL",
"cloud API workload Code Agent must not point directly at public api.openai.com",
{ expected: contract.egress.target, actual: env.get("HWLAB_CODE_AGENT_OPENAI_BASE_URL")?.value }
);
expectEqual(ctx, env.get(secretRef.env)?.valueFrom?.secretKeyRef?.name, secretRef.secretName, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.OPENAI_API_KEY.secretKeyRef.name", "cloud API workload Code Agent Secret name");
expectEqual(ctx, env.get(secretRef.env)?.valueFrom?.secretKeyRef?.key, secretRef.secretKey, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.OPENAI_API_KEY.secretKeyRef.key", "cloud API workload Code Agent Secret key");
}
async function buildPlan() {
const ctx = { checks: 0, diagnostics: [] };
const [manifest, services, workloads, healthContract, masterEdge, frpc, frps] = await Promise.all([
readJson("deploy/deploy.json"),
readJson("deploy/k8s/base/services.yaml"),
readJson("deploy/k8s/base/workloads.yaml"),
readJson("deploy/k8s/dev/health-contract.yaml"),
readJson("deploy/master-edge/health-contract.json"),
parseToml("deploy/frp/frpc.dev.toml"),
parseToml("deploy/frp/frps.dev.toml")
]);
const source = validateSource(ctx, manifest);
const rendered = renderPlan(source);
validateFrpArtifacts(ctx, source, rendered, frpc, frps);
validateK3sArtifacts(ctx, source, rendered, services, workloads, healthContract, masterEdge);
const drift = ctx.diagnostics.filter((item) => item.code === "legacy_public_endpoint_drift");
return {
kind: "hwlab-deploy-contract-plan",
mode: "dry-run",
status: ctx.diagnostics.length === 0 ? "pass" : "blocked",
source: "deploy/deploy.json",
dryRunOnly: true,
safety: {
frpApply: false,
kubectlApply: false,
prod: false,
restart: false
},
summary: {
publicEndpoints: Object.keys(rendered.publicEndpoints).length,
frpProxies: rendered.frpc.proxies.length,
k3sServices: rendered.k3sServices.length,
healthPath: rendered.health.path,
legacyPublicEndpointDrift: drift.length,
checks: ctx.checks
},
rendered,
diagnostics: ctx.diagnostics,
nextIntegration: ["hwlab edge apply", "deploy apply"]
};
}
export async function runDeployContractPlanCli(argv = process.argv.slice(2)) {
const args = parseArgs(argv);
const plan = await buildPlan();
process.stdout.write(`${JSON.stringify(plan, null, args.pretty ? 2 : 0)}\n`);
if (args.check && plan.status !== "pass") {
process.exitCode = 1;
}
}
export function writeDeployContractPlanError(error) {
process.stdout.write(`${JSON.stringify({
kind: "hwlab-deploy-contract-plan",
mode: "dry-run",
status: "error",
error: error instanceof Error ? error.message : String(error),
safety: { frpApply: false, kubectlApply: false, prod: false, restart: false }
})}\n`);
process.exitCode = 1;
}