import { execFile } from "node:child_process"; 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"; import { DEV_CODE_AGENT_PROVIDER_CONTRACT, codeAgentSecretRefPlaceholder } from "../../internal/cloud/code-agent-contract.mjs"; const execFileAsync = promisify(execFile); 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"]); 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")); } async function parseToml(relativePath) { const code = [ "import json,sys,tomllib", "with open(sys.argv[1], 'rb') as handle:", " print(json.dumps(tomllib.load(handle), sort_keys=True))" ].join("\n"); const result = await execFileAsync("python3", ["-c", code, path.join(repoRoot, relativePath)]); return JSON.parse(result.stdout); } 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"); 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 ?? {}); 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 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; }