Files
pikasTech-HWLAB/scripts/src/dev-edge-health-smoke-lib.mjs
T
2026-06-08 22:19:30 +08:00

1208 lines
44 KiB
JavaScript

import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import net from "node:net";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
DEV_DB_ENV_CONTRACT,
summarizeDbContract
} from "../../internal/cloud/db-contract.ts";
import {
DEV_CODE_AGENT_PROVIDER_CONTRACT,
codeAgentSecretRefPlaceholder
} from "../../internal/cloud/code-agent-contract.ts";
import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs";
import { DEV_ENDPOINT, ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
import { readStructuredFile } from "./structured-config.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const defaultReportPath = tempReportPath("dev-edge-health.json");
const publicHost = "74.48.78.17";
const publicPort = 16667;
const namespace = "hwlab-dev";
const d601KubeconfigPath = "/etc/rancher/k3s/k3s.yaml";
const tcpPorts = [16667, 7000, 7402];
const CODE_AGENT_PROVIDER_SECRET_CONTRACT = Object.freeze({
sourceIssue: "pikasTech/HWLAB#143",
env: DEV_CODE_AGENT_PROVIDER_CONTRACT.secretRefs[0].env,
secretName: DEV_CODE_AGENT_PROVIDER_CONTRACT.secretRefs[0].secretName,
secretKey: DEV_CODE_AGENT_PROVIDER_CONTRACT.secretRefs[0].secretKey
});
const runtimeServices = [
{ serviceId: "hwlab-cloud-api", port: 6667 },
{ serviceId: "hwlab-router", port: 7401 },
{ serviceId: "hwlab-tunnel-client", port: 7402 },
{ serviceId: "hwlab-edge-proxy", port: 6667 }
];
function nativeKubectlArgs(args) {
return [`KUBECONFIG=${d601KubeconfigPath}`, "kubectl", ...args];
}
function nativeKubectlCommand(args) {
return nativeKubectlArgs(args).join(" ");
}
function runKubectl(args, options) {
return runCommand("env", nativeKubectlArgs(args), options);
}
export async function runDevEdgeHealthSmoke(argv) {
const args = parseArgs(argv);
requireDevEndpoint();
if (!args.live) {
const edgeHealth = {
generatedAt: nowIso(),
mode: "contract-only",
endpoint: DEV_ENDPOINT,
status: "not_run",
classification: "not_run",
blocker: "live network probes require --live",
contracts: await inspectContracts(),
runtimeDbReadiness: notRunDbReadiness("live network probes require --live")
};
const report = await createDevGateReport(edgeHealth);
await maybeWriteReport(report, args);
return { report, exitCode: 0 };
}
const [contracts, publicTcp, publicHttp, kubernetes, clusterDns] = await Promise.all([
inspectContracts(),
Promise.all(tcpPorts.map((port) => probeTcp({ host: publicHost, port }))),
Promise.all([fetchJson(`${DEV_ENDPOINT}/health`), fetchJson(`${DEV_ENDPOINT}/health/live`)]),
inspectKubernetes(),
inspectClusterDns()
]);
const edgeHealth = {
generatedAt: nowIso(),
mode: "live-read-only",
endpoint: DEV_ENDPOINT,
safety: {
environment: ENVIRONMENT_DEV,
prodTouched: false,
secretsRead: false,
restarts: false,
unideskRuntimeSubstitute: false
},
contracts,
publicTcp,
publicHttp,
kubernetes,
clusterDns
};
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,
writeReport: false,
reportPath: defaultReportPath
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--live") {
args.live = true;
} else if (arg === "--report") {
index += 1;
assert.ok(argv[index], "--report requires a path");
args.reportPath = argv[index];
args.writeReport = true;
} else {
throw new Error(`unknown argument ${arg}`);
}
}
return args;
}
function nowIso() {
return new Date().toISOString();
}
async function createDevGateReport(edgeHealth) {
const commitId = await gitCommitId();
const status = edgeHealth.status === "pass" ? "pass" : edgeHealth.status === "not_run" ? "not_run" : "blocked";
const blockers = reportBlockers(edgeHealth, status);
return {
$schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json",
$id: "https://hwlab.pikastech.local/dev-gate/dev-edge-health.json",
reportVersion: "v1",
issue: "pikasTech/HWLAB#36",
taskId: "dev-edge-health",
commitId,
acceptanceLevel: "dev_edge_health",
devOnly: true,
prodDisabled: true,
reportLifecycle: activeReportLifecycle("Current DEV edge-health report; deprecated public 6666/6667 endpoints are not valid active evidence."),
status,
generatedAt: edgeHealth.generatedAt,
namespace,
endpoint: DEV_ENDPOINT,
sourceContract: {
status: "pass",
documents: [
"docs/reference/dev-runtime-boundary.md",
"docs/reference/spec-v02-documentation-governance.md",
"docs/reference/node-gitops-cicd.md",
"deploy/frp/README.md",
"deploy/master-edge/README.md"
],
summary: "DEV edge health is pinned to the frozen HWLAB endpoint, frp reverse link, master edge proxy, and hwlab-dev services."
},
validationCommands: [
"node --check scripts/validate-dev-gate-report.mjs",
"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 --report ${defaultReportPath}`
],
localSmoke: {
status: "pass",
commands: [],
evidence: [
"npm run check includes internal/cloud/server-health.test.ts and validates /health plus /health/live."
],
summary: "Local cloud-api health checks pass and include service, commit, and image evidence fields."
},
dryRun: {
status: "not_run",
commands: [
"node scripts/run-bun.mjs tools/hwlab-cli/bin/hwlab-cli.ts client workbench summary --base-url http://74.48.78.17:19666"
],
evidence: [
"This edge-health task used the dedicated read-only smoke instead of the MVP e2e dry-run."
],
summary: "MVP dry-run remains the generic gate command; edge health uses a narrower route smoke."
},
devPreconditions: {
status,
requirements: [
"http://74.48.78.17:16667/health returns HWLAB DEV JSON with service, commit, and image evidence",
"frps control on 74.48.78.17:7000 accepts the D601 frp client",
"hwlab-dev hwlab-edge-proxy, hwlab-tunnel-client, hwlab-router, and hwlab-cloud-api are observable",
"No PROD endpoint, secret read, restart, or UniDesk runtime substitute is used"
],
summary: edgeHealth.blocker ?? "DEV edge health preconditions are satisfied."
},
blockers,
edgeHealth
};
}
async function gitCommitId() {
const result = await runCommand("git", ["rev-parse", "--short=12", "HEAD"], { timeoutMs: 5000 });
return result.exitCode === 0 ? result.stdout.trim() : "unknown";
}
function blockerTypeFor(classification) {
if (classification === "app_health_blocker") {
return "runtime_blocker";
}
if (classification === "k3s_service_blocker") {
return "runtime_blocker";
}
if (classification === "not_run") {
return "observability_blocker";
}
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");
assert.equal(endpoint.hostname, publicHost, "DEV endpoint host");
assert.equal(Number(endpoint.port), publicPort, "DEV endpoint port");
}
async function inspectContracts() {
const frpc = await readText("deploy/frp/frpc.dev.toml");
const frps = await readText("deploy/frp/frps.dev.toml");
const deploy = await readJson("deploy/deploy.yaml");
const master = await readJson("deploy/master-edge/health-contract.json");
const frpsUsesVhostEdgePort = /vhostHTTPPort\s*=\s*(16667|6667)/u.test(frps);
return {
devEndpoint: DEV_ENDPOINT,
frp: {
frpcTargetsEdgeProxy:
/localIP\s*=\s*"hwlab-edge-proxy\.hwlab-dev\.svc\.cluster\.local"/u.test(frpc) &&
/localPort\s*=\s*6667/u.test(frpc) &&
/remotePort\s*=\s*16667/u.test(frpc),
frpcTargetsCloudWeb:
/localIP\s*=\s*"hwlab-cloud-web\.hwlab-dev\.svc\.cluster\.local"/u.test(frpc) &&
/localPort\s*=\s*8080/u.test(frpc) &&
/remotePort\s*=\s*16666/u.test(frpc),
frpsTcpRemotePort16667: /start\s*=\s*16667[\s\S]*end\s*=\s*16667/u.test(frps),
frpsTcpRemotePort16666: /start\s*=\s*16666[\s\S]*end\s*=\s*16666/u.test(frps),
frpsUsesVhostEdgePort,
configRisk: frpsUsesVhostEdgePort ? "port_conflict" : "none"
},
masterEdge: {
endpoint: master.endpoint,
reverseLinkMode: master.reverseLink?.mode,
reverseLinkClient: master.reverseLink?.client,
publicPort: master.reverseLink?.publicPort
},
deploy: {
environment: deploy.environment,
namespace: deploy.namespace,
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),
codeAgentProvider: inspectCodeAgentProviderContract(deploy)
}
};
}
function inspectCloudApiDbContract(deploy) {
const env = deploy.services?.find((service) => service.serviceId === "hwlab-cloud-api")?.env ?? {};
const configReady = Boolean(env.HWLAB_CLOUD_DB_URL && env.HWLAB_CLOUD_DB_SSL_MODE);
const connection = {
attempted: false,
networkAttempted: false,
result: "manifest_only_not_attempted",
classification: "manifest_only",
endpointSource: DEV_DB_ENV_CONTRACT.endpointAuthority.source,
probeType: "tcp-connect",
endpointRedacted: true,
valueRedacted: true,
timeoutMs: null,
durationMs: 0,
errorCode: null
};
const db = {
status: configReady ? "degraded" : "blocked",
ready: false,
configReady,
connected: false,
liveConnected: false,
connectionChecked: false,
connectionAttempted: false,
connectionResult: connection.result,
endpointSource: DEV_DB_ENV_CONTRACT.endpointAuthority.source,
endpoint: {
authoritative: {
source: DEV_DB_ENV_CONTRACT.endpointAuthority.source,
env: DEV_DB_ENV_CONTRACT.endpointAuthority.env,
requiredForReadiness: true,
usedForProbe: true,
endpointRedacted: true,
valueRedacted: true
}
},
optionalPublicDnsAlias: {
source: DEV_DB_ENV_CONTRACT.dns.source,
serviceName: DEV_DB_ENV_CONTRACT.dns.serviceName,
namespace: DEV_DB_ENV_CONTRACT.dns.namespace,
port: DEV_DB_ENV_CONTRACT.dns.port,
portName: DEV_DB_ENV_CONTRACT.dns.portName,
requiredForReadiness: false,
usedForProbe: false,
envPresent: false,
readyGate: "not_readiness_authority"
},
fields: [
{
name: "HWLAB_CLOUD_DB_URL",
present: Boolean(env.HWLAB_CLOUD_DB_URL),
redacted: true,
source: "k8s-secret-ref"
},
{
name: "HWLAB_CLOUD_DB_SSL_MODE",
present: Boolean(env.HWLAB_CLOUD_DB_SSL_MODE),
redacted: false,
source: "runtime-env"
}
],
missingEnv: ["HWLAB_CLOUD_DB_URL", "HWLAB_CLOUD_DB_SSL_MODE"].filter((name) => !env[name]),
secretRefs: [
{
env: "HWLAB_CLOUD_DB_URL",
secretName: "hwlab-cloud-api-dev-db",
secretKey: "database-url",
present: env.HWLAB_CLOUD_DB_URL === "secretRef:hwlab-cloud-api-dev-db/database-url",
envInjected: false,
secretPresent: "not_observed_by_manifest",
secretKeyPresent: "declared",
redacted: true
}
],
connection,
safety: {
secretsRead: false,
valuesRedacted: true,
liveDbEvidence: false
},
readinessLayers: Object.fromEntries(DEV_DB_ENV_CONTRACT.readinessLayers.map((layer) => [layer, { status: "not_proven" }])),
blocker: configReady
? "Manifest declares DB env/Secret references but does not prove runtime env injection or live DB connection"
: "Manifest is missing DB env/Secret references"
};
return {
...summarizeDbContract(db),
manifestConfigReady: configReady,
envInjected: false,
secretMaterialRead: false,
valuesRedacted: true,
note: "Manifest-only DB env placeholder; this is not runtime env injection or live DB evidence."
};
}
function inspectCodeAgentProviderContract(deploy) {
const env = deploy.services?.find((service) => service.serviceId === "hwlab-cloud-api")?.env ?? {};
const expectedRef = codeAgentSecretRefPlaceholder();
const secretRefPresent = env[CODE_AGENT_PROVIDER_SECRET_CONTRACT.env] === expectedRef;
const egressBaseUrl = env.HWLAB_CODE_AGENT_OPENAI_BASE_URL;
const egressReady = egressBaseUrl === DEV_CODE_AGENT_PROVIDER_CONTRACT.egress.defaultBaseUrl;
const directPublicOpenAi = egressBaseUrl === DEV_CODE_AGENT_PROVIDER_CONTRACT.egress.forbiddenDirectBaseUrl;
return {
status: secretRefPresent && egressReady ? "degraded" : "blocked",
sourceIssue: CODE_AGENT_PROVIDER_SECRET_CONTRACT.sourceIssue,
provider: DEV_CODE_AGENT_PROVIDER_CONTRACT.runtimeProvider,
requiredEnv: [
{
name: CODE_AGENT_PROVIDER_SECRET_CONTRACT.env,
present: Boolean(env[CODE_AGENT_PROVIDER_SECRET_CONTRACT.env]),
redacted: true,
source: "k8s-secret-ref"
},
{
name: DEV_CODE_AGENT_PROVIDER_CONTRACT.egress.env,
present: Boolean(egressBaseUrl),
redacted: false,
source: "runtime-env"
}
],
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
},
egress: {
env: DEV_CODE_AGENT_PROVIDER_CONTRACT.egress.env,
target: DEV_CODE_AGENT_PROVIDER_CONTRACT.egress.target,
present: Boolean(egressBaseUrl),
ready: egressReady,
directPublicOpenAi,
valueRedacted: true
},
secretMaterialRead: false,
valuesRedacted: true,
note: "Manifest-only Code Agent provider Secret reference and DEV egress/base-url contract; this is not runtime Secret injection or provider connectivity evidence."
};
}
function notRunDbReadiness(blockerMessage) {
return {
status: "not_run",
configReady: false,
envInjected: false,
secret: unknownDbSecretPresence("not_observed"),
connectionAttempted: false,
connectionResult: "not_run",
connectionClassification: "not_run",
liveConnected: false,
valuesRedacted: true,
secretMaterialRead: false,
liveDbEvidence: false,
blocker: blockerMessage,
evidence: []
};
}
function inspectRuntimeDbReadiness(edgeHealth) {
const db = selectRuntimeDb(edgeHealth.publicHttp);
return summarizeRuntimeDbReadiness(db, edgeHealth.kubernetes?.dbSecret ?? unknownDbSecretPresence("not_observed"));
}
function selectRuntimeDb(publicHttp) {
const live = publicHttp.find((probe) => probe.url.endsWith("/health/live") && probe.ok && probe.json?.db);
const health = publicHttp.find((probe) => probe.url.endsWith("/health") && probe.ok && probe.json?.db);
return live?.json?.db ?? health?.json?.db ?? null;
}
function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresence("not_observed")) {
if (!db) {
return {
status: "blocked",
configReady: false,
envInjected: false,
secret: secretPresence,
connectionAttempted: false,
connectionResult: "health_db_missing",
connectionClassification: "health_db_missing",
liveConnected: false,
valuesRedacted: true,
secretMaterialRead: false,
liveDbEvidence: false,
blocker: "cloud-api health is reachable but does not expose redacted DB readiness fields",
evidence: []
};
}
const requiredEnv = Array.isArray(db.fields) ? db.fields : [];
const envInjected = DEV_DB_ENV_CONTRACT.requiredEnv.every((name) =>
requiredEnv.some((field) => field.name === name && field.present === true)
);
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 endpointSource =
db.endpointSource ?? db.connection?.endpointSource ?? db.endpoint?.authoritative?.source ?? DEV_DB_ENV_CONTRACT.endpointAuthority.source;
const optionalPublicDnsAlias = db.optionalPublicDnsAlias ?? db.endpoint?.optionalPublicDnsAlias ?? {
source: DEV_DB_ENV_CONTRACT.dns.source,
serviceName: DEV_DB_ENV_CONTRACT.dns.serviceName,
namespace: DEV_DB_ENV_CONTRACT.dns.namespace,
port: DEV_DB_ENV_CONTRACT.dns.port,
portName: DEV_DB_ENV_CONTRACT.dns.portName,
requiredForReadiness: false,
usedForProbe: false,
envPresent: false,
readyGate: "not_readiness_authority"
};
const liveConnected = Boolean(db.liveConnected ?? db.connected ?? db.ready);
const authorityReady = endpointSource === DEV_DB_ENV_CONTRACT.endpointAuthority.source;
const blockerMessage = liveConnected && authorityReady
? null
: db.blocker ?? (authorityReady ? blockerForRuntimeDb(db, connectionAttempted, connectionResult) : "DB readiness did not use the Secret URL host authority");
const status = liveConnected && authorityReady ? "pass" : "blocked";
const secretRef = Array.isArray(db.secretRefs) ? db.secretRefs[0] : null;
return {
status,
configReady: Boolean(db.configReady),
envInjected,
secret: {
...secretPresence,
secretName: secretRef?.secretName ?? DEV_DB_ENV_CONTRACT.secretRefs[0].secretName,
secretKey: secretRef?.secretKey ?? DEV_DB_ENV_CONTRACT.secretRefs[0].secretKey,
secretRefPresent: Boolean(secretRef?.present),
envInjected: Boolean(secretRef?.envInjected ?? secretRef?.present),
redacted: true
},
connectionAttempted,
connectionResult,
connectionClassification,
endpointSource,
optionalPublicDnsAlias: {
source: optionalPublicDnsAlias.source ?? DEV_DB_ENV_CONTRACT.dns.source,
serviceName: optionalPublicDnsAlias.serviceName ?? DEV_DB_ENV_CONTRACT.dns.serviceName,
namespace: optionalPublicDnsAlias.namespace ?? DEV_DB_ENV_CONTRACT.dns.namespace,
port: optionalPublicDnsAlias.port ?? DEV_DB_ENV_CONTRACT.dns.port,
portName: optionalPublicDnsAlias.portName ?? DEV_DB_ENV_CONTRACT.dns.portName,
requiredForReadiness: optionalPublicDnsAlias.requiredForReadiness === true,
usedForProbe: optionalPublicDnsAlias.usedForProbe === true,
envPresent: optionalPublicDnsAlias.envPresent === true,
readyGate: optionalPublicDnsAlias.readyGate ?? "not_readiness_authority"
},
liveConnected,
valuesRedacted: db.redaction?.valuesRedacted !== false && db.safety?.valuesRedacted === true,
secretMaterialRead: false,
liveDbEvidence: Boolean(db.liveDbEvidence ?? db.safety?.liveDbEvidence),
blocker: blockerMessage,
evidence: [
{
configReady: Boolean(db.configReady),
missingEnv: Array.isArray(db.missingEnv) ? db.missingEnv : [],
connectionAttempted,
connectionResult,
classification: connectionClassification,
endpointSource,
liveConnected,
endpointRedacted: db.connection?.endpointRedacted !== false,
valueRedacted: true
}
]
};
}
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";
return `cloud-api DB runtime env is not ready; missing ${missing}`;
}
if (!connectionAttempted) {
return "cloud-api DB env is injected, but runtime health has not attempted a live DB connection";
}
return `cloud-api DB live connection is blocked; connectionResult=${connectionResult}`;
}
function unknownDbSecretPresence(state) {
const ref = DEV_DB_ENV_CONTRACT.secretRefs[0];
return {
observable: false,
secretName: ref.secretName,
secretKey: ref.secretKey,
secretPresent: state,
secretKeyPresent: state,
secretValueRead: false,
redacted: true
};
}
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 = {
observable: hasKubectl,
namespace,
kubeconfig: d601KubeconfigPath,
context: null,
nodeNames: [],
services: [],
endpoints: [],
pods: [],
dbSecret: unknownDbSecretPresence("not_observed"),
codeAgentProviderSecret: unknownProviderSecretPresence("not_observed"),
notes: []
};
if (!hasKubectl) {
result.notes.push("kubectl is not installed in this runner");
return result;
}
await collectKubectlContext(result);
await collectKubectlNodeIdentity(result);
await collectKubectlJson(result, "services", [
"-n", namespace, "get", "svc", "hwlab-cloud-api", "hwlab-edge-proxy", "hwlab-tunnel-client", "hwlab-router", "-o", "json"
]);
await collectKubectlJson(result, "endpoints", [
"-n", namespace, "get", "endpoints", "hwlab-cloud-api", "hwlab-edge-proxy", "hwlab-tunnel-client", "hwlab-router", "-o", "json"
]);
await collectKubectlJson(result, "pods", [
"-n", namespace, "get", "pods", "-l", "hwlab.pikastech.local/profile=dev", "-o", "json"
]);
await collectDbSecretPresence(result);
await collectCodeAgentProviderSecretPresence(result);
return result;
}
async function collectKubectlContext(result) {
const context = await runKubectl(["config", "current-context"], { timeoutMs: 5000 });
result.context = {
exitCode: context.exitCode,
value: context.exitCode === 0 ? context.stdout.trim() : null,
command: nativeKubectlCommand(["config", "current-context"]),
stderr: context.stderr.trim()
};
}
async function collectKubectlNodeIdentity(result) {
const args = ["get", "nodes", "-o", "jsonpath={.items[*].metadata.name}"];
const nodes = await runKubectl(args, { timeoutMs: 5000 });
if (nodes.exitCode !== 0) {
result.notes.push(`kubectl node identity probe failed: ${nodes.stderr.trim()}`);
return;
}
result.nodeNames = nodes.stdout.trim().split(/\s+/u).filter(Boolean);
if (!result.nodeNames.includes("d601")) {
result.notes.push(`kubectl node identity did not include d601: ${result.nodeNames.join(",") || "none"}`);
}
}
async function collectKubectlJson(result, kind, args) {
const probe = await runKubectl(args, { timeoutMs: 10000 });
if (probe.exitCode !== 0) {
result.notes.push(`kubectl ${kind} probe failed: ${probe.stderr.trim()}`);
return;
}
const doc = JSON.parse(probe.stdout);
if (kind === "services") {
result.services = doc.items.map((item) => ({
name: item.metadata.name,
type: item.spec.type,
clusterIP: item.spec.clusterIP,
ports: item.spec.ports?.map(({ name, port, targetPort }) => ({ name, port, targetPort })) ?? [],
selector: item.spec.selector ?? {}
}));
} else if (kind === "endpoints") {
result.endpoints = doc.items.map((item) => ({
name: item.metadata.name,
readyAddresses: (item.subsets ?? []).flatMap((subset) => subset.addresses ?? []).map((address) => address.ip),
notReadyAddresses: (item.subsets ?? []).flatMap((subset) => subset.notReadyAddresses ?? []).map((address) => address.ip),
ports: (item.subsets ?? []).flatMap((subset) => subset.ports ?? []).map(({ name, port }) => ({ name, port }))
}));
} else if (kind === "pods") {
result.pods = doc.items.map((item) => ({
name: item.metadata.name,
phase: item.status.phase,
serviceId: item.metadata.labels?.["hwlab.pikastech.local/service-id"] ?? null,
containers: item.status.containerStatuses?.map(({ name, ready, restartCount, image, imageID }) => ({
name, ready, restartCount, image, imageID
})) ?? []
}));
}
}
async function collectDbSecretPresence(result) {
const ref = DEV_DB_ENV_CONTRACT.secretRefs[0];
const base = {
observable: true,
secretName: ref.secretName,
secretKey: ref.secretKey,
secretPresent: false,
secretKeyPresent: false,
secretValueRead: false,
redacted: true
};
const exists = await runKubectl(["-n", namespace, "get", "secret", ref.secretName, "-o", "name"], {
timeoutMs: 5000
});
if (exists.exitCode !== 0) {
result.dbSecret = {
...base,
command: nativeKubectlCommand(["-n", namespace, "get", "secret", ref.secretName, "-o", "name"]),
error: exists.stderr.trim() || "secret not observed"
};
result.notes.push(`kubectl DB Secret presence probe failed: ${exists.stderr.trim()}`);
return;
}
const keyPresence = await runKubectl(["-n", namespace, "describe", "secret", ref.secretName], {
timeoutMs: 5000
});
const keyLinePattern = new RegExp(`^\\s*${escapeRegExp(ref.secretKey)}:\\s+\\d+\\s+bytes\\s*$`, "mu");
result.dbSecret = {
...base,
secretPresent: true,
secretKeyPresent: keyPresence.exitCode === 0 && keyLinePattern.test(keyPresence.stdout),
command: `${nativeKubectlCommand(["-n", namespace, "describe", "secret", ref.secretName])} (key-presence-only)`,
error: keyPresence.exitCode === 0 ? null : keyPresence.stderr.trim()
};
if (keyPresence.exitCode !== 0) {
result.notes.push(`kubectl DB Secret key presence probe failed: ${keyPresence.stderr.trim()}`);
}
}
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 runKubectl(["-n", namespace, "get", "secret", ref.secretName, "-o", "name"], {
timeoutMs: 5000
});
if (exists.exitCode !== 0) {
result.codeAgentProviderSecret = {
...base,
command: nativeKubectlCommand(["-n", namespace, "get", "secret", ref.secretName, "-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 runKubectl(["-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: `${nativeKubectlCommand(["-n", namespace, "describe", "secret", ref.secretName])} (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) {
const host = `${service.serviceId}.${namespace}.svc.cluster.local`;
const dns = await runCommand("getent", ["hosts", host], { timeoutMs: 3000 });
results.push({
serviceId: service.serviceId,
host,
port: service.port,
resolved: dns.exitCode === 0,
output: dns.stdout.trim()
});
}
return results;
}
function classify(report) {
const publicEdgePort = report.publicTcp.find((probe) => probe.port === publicPort);
const frpsControl = report.publicTcp.find((probe) => probe.port === 7000);
const tunnelHealth = report.publicTcp.find((probe) => probe.port === 7402);
const publicHealth = report.publicHttp.find((probe) => probe.url.endsWith("/health"));
if (publicHealth?.ok && publicHealth.json?.serviceId && publicHealth.json?.environment === ENVIRONMENT_DEV) {
return classifyReachableHealth(publicHealth.json, selectRuntimeDb(report.publicHttp));
}
if (publicEdgePort?.status === "error" && publicEdgePort.code === "ECONNREFUSED") {
return classifyRefusedPort(publicEdgePort, frpsControl, tunnelHealth);
}
if (publicEdgePort?.status === "timeout") {
return blocker("dns_port_firewall_blocker", "public 16667 timed out before any HTTP response");
}
if (publicHealth && !publicHealth.ok && publicHealth.status) {
return blocker("app_health_blocker", `public /health returned HTTP ${publicHealth.status}`);
}
if (report.kubernetes.observable && report.kubernetes.services.length === 0) {
return blocker("k3s_service_blocker", "kubectl is available but HWLAB DEV services were not observed");
}
return blocker("dns_port_firewall_blocker", publicHealth?.error?.message ?? "public DEV health did not return a usable response");
}
function classifyReachableHealth(json, runtimeDb = null) {
const missingEvidence = [];
if (!json.commit) missingEvidence.push("commit");
if (!json.image) missingEvidence.push("image");
if (!json.service && !json.serviceId) missingEvidence.push("service");
if (missingEvidence.length > 0) {
return blocker("app_health_blocker", `public /health is reachable but missing ${missingEvidence.join(", ")} evidence`);
}
const dbReadiness = summarizeRuntimeDbReadiness(runtimeDb ?? json.db);
if (dbReadiness.status !== "pass") {
return blocker("app_health_blocker", dbReadiness.blocker, {
likelyLayer: "cloud-api-db",
confidence: dbReadiness.configReady ? "high" : "medium",
likelyCause: dbReadiness.blocker,
evidence: dbReadiness.evidence,
notProven: dbReadiness.connectionAttempted
? ["authenticated SQL query readiness"]
: ["live DB endpoint reachability", "authenticated SQL query readiness"],
nextTask: "Deploy cloud-api DB runtime readiness probe and/or repair DEV DB connectivity, then rerun the read-only health smoke without reading or printing the DB secret value."
});
}
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 Secret URL host readiness 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";
const diagnosis = {
likelyLayer: "master-edge/frps",
confidence: "high",
likelyCause: allPortsRefused
? "public 16667, frps control 7000, and tunnel health 7402 all refuse TCP connections; the master-edge frps listener or its ports are most likely down or closed"
: "public 16667 and frps control 7000 both refuse TCP connections; the master-edge frps listener or its ports are most likely down or closed",
evidence: [
summarizeTcpProbe(publicEdgePort),
summarizeTcpProbe(frpsControl),
summarizeTcpProbe(tunnelHealth)
],
notProven: [
"k3s service readiness",
"frpc connectivity",
"edge proxy deployment"
],
nextTask: "Restore or reopen frps on 74.48.78.17, then rerun the read-only edge smoke."
};
return blocker("frp_blocker", diagnosis.likelyCause, diagnosis);
}
if (tunnelHealth?.status === "error" && tunnelHealth.code === "ECONNREFUSED") {
return blocker("frp_blocker", "public 16667 refuses TCP and tunnel health 7402 is not reachable", {
likelyLayer: "d601/frpc",
confidence: "medium",
likelyCause: "public 16667 and tunnel health 7402 both refuse TCP connections; the D601 frp client or its remote port is the likely break",
evidence: [
summarizeTcpProbe(publicEdgePort),
summarizeTcpProbe(tunnelHealth)
],
notProven: [
"master-edge frps reachability",
"k3s service readiness"
],
nextTask: "Restore the D601 tunnel client or its remote port, then rerun the read-only edge smoke."
});
}
return blocker("edge_proxy_blocker", "public 16667 refuses TCP connections", {
likelyLayer: "master-edge/edge-proxy",
confidence: "medium",
likelyCause: "public 16667 refuses TCP while frps control 7000 stays reachable, so the public edge proxy or its listener is the likely break",
evidence: [
summarizeTcpProbe(publicEdgePort),
summarizeTcpProbe(frpsControl)
],
notProven: [
"k3s service readiness"
],
nextTask: "Repair the edge proxy or its public listener, then rerun the read-only edge smoke."
});
}
function blocker(classification, message, diagnosis = null) {
return {
status: "blocker",
classification,
blocker: message,
...(diagnosis ? { diagnosis } : {})
};
}
function summarizeTcpProbe(probe) {
if (!probe) {
return null;
}
return {
host: probe.host,
port: probe.port,
status: probe.status,
code: probe.code ?? null,
message: probe.message ?? null
};
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
}
async function commandExists(command) {
const result = await runCommand("command", ["-v", command], { timeoutMs: 3000 });
return result.exitCode === 0;
}
function runCommand(command, args, { timeoutMs = 10000 } = {}) {
return new Promise((resolve) => {
const startedAt = Date.now();
const child = spawn(command, args, {
cwd: repoRoot,
shell: command === "command",
stdio: ["ignore", "pipe", "pipe"]
});
let stdout = "";
let stderr = "";
let settled = false;
const timeout = setTimeout(() => {
child.kill("SIGTERM");
finish(124, `${stderr}\ncommand timed out after ${timeoutMs}ms`.trim());
}, timeoutMs);
function finish(exitCode, finalStderr = stderr) {
if (settled) return;
settled = true;
clearTimeout(timeout);
resolve({
command: [command, ...args].join(" "),
exitCode,
stdout,
stderr: finalStderr,
durationMs: Date.now() - startedAt
});
}
child.stdout.on("data", (chunk) => {
stdout += chunk.toString();
});
child.stderr.on("data", (chunk) => {
stderr += chunk.toString();
});
child.on("error", (error) => finish(127, error.message));
child.on("close", (exitCode) => finish(exitCode));
});
}
function probeTcp({ host, port, timeoutMs = 5000 }) {
return new Promise((resolve) => {
const startedAt = Date.now();
const socket = net.connect({ host, port, timeout: timeoutMs });
socket.on("connect", () => {
resolve({ host, port, status: "connected", durationMs: Date.now() - startedAt });
socket.destroy();
});
socket.on("timeout", () => {
resolve({ host, port, status: "timeout", durationMs: Date.now() - startedAt });
socket.destroy();
});
socket.on("error", (error) => {
resolve({ host, port, status: "error", code: error.code, message: error.message, durationMs: Date.now() - startedAt });
});
});
}
async function fetchJson(url, { timeoutMs = 8000 } = {}) {
const startedAt = Date.now();
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
const text = await response.text();
return {
url,
ok: response.ok,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
json: parseJsonOrNull(text),
bodyPreview: text.slice(0, 500),
durationMs: Date.now() - startedAt
};
} catch (error) {
return {
url,
ok: false,
error: { name: error.name, message: error.message, code: error.code },
durationMs: Date.now() - startedAt
};
} finally {
clearTimeout(timeout);
}
}
function parseJsonOrNull(text) {
try {
return text ? JSON.parse(text) : null;
} catch {
return null;
}
}
async function readJson(relativePath) {
return readStructuredFile(repoRoot, relativePath);
}
async function readText(relativePath) {
return readFile(path.join(repoRoot, relativePath), "utf8");
}
async function maybeWriteReport(report, args) {
if (!args.writeReport) {
return;
}
const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, args.reportPath, "--report");
await mkdir(path.dirname(absoluteReportPath), { recursive: true });
await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`);
}