import { createServer } from "node:http"; import { randomUUID } from "node:crypto"; import { existsSync, lstatSync, mkdirSync, symlinkSync } from "node:fs"; import { readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { buildMetadataFromEnv, imageTagFromReference, normalizeIsoTimestamp } from "../build-metadata.mjs"; import { DEV_ENDPOINT, ENVIRONMENT_DEV, ERROR_CODES, SERVICE_IDS } from "../protocol/index.mjs"; import { AUDIT_FIELD_NAMES, CLOUD_API_SERVICE_ID, createAuditRecord } from "../audit/index.mjs"; import { SUPPORTED_RPC_METHODS, createErrorEnvelope, handleJsonRpcRequest } from "./json-rpc.mjs"; import { createCodeAgentErrorPayload, describeCodeAgentAvailability, handleCodeAgentChat } from "./code-agent-chat.mjs"; import { defaultCodeAgentTraceStore } from "./code-agent-trace-store.mjs"; import { createCodeAgentSessionRegistry } from "./code-agent-session-registry.mjs"; import { createCodexStdioSessionManager } from "./codex-stdio-session.mjs"; import { buildGateDiagnosticsRows } from "./gate-diagnostics.mjs"; import { applyRuntimeDbReadinessLayers, buildDbRuntimeReadiness } from "./db-contract.mjs"; import { buildCloudApiReadiness } from "./health-contract.mjs"; import { M3_IO_CONTROL_ROUTE, M3_STATUS_ROUTE, describeM3IoControl, describeM3IoControlLive, describeM3StatusLive, handleM3IoControl } from "./m3-io-control.mjs"; import { createConfiguredCloudRuntimeStore } from "../db/runtime-store.mjs"; import { createGatewayDemoRegistry } from "./gateway-demo-registry.mjs"; const DEFAULT_BODY_LIMIT_BYTES = 1024 * 1024; const LIVE_BUILD_TIMEOUT_MS = 900; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const LIVE_BUILD_METADATA_PATHS = Object.freeze({ deployManifest: "deploy/deploy.json", artifactCatalog: "deploy/artifact-catalog.dev.json" }); const LIVE_BUILD_SERVICE_DEFAULTS = Object.freeze({ "hwlab-cloud-api": Object.freeze({ urlEnv: null, defaultUrl: "http://127.0.0.1:6667" }), "hwlab-cloud-web": Object.freeze({ urlEnv: "HWLAB_CLOUD_WEB_SERVICE_URL", defaultUrl: "http://hwlab-cloud-web.hwlab-dev.svc.cluster.local:8080" }), "hwlab-agent-mgr": Object.freeze({ urlEnv: "HWLAB_AGENT_MGR_URL", defaultUrl: "http://hwlab-agent-mgr.hwlab-dev.svc.cluster.local:7410" }), "hwlab-agent-worker": Object.freeze({ urlEnv: "HWLAB_AGENT_WORKER_URL", defaultUrl: "http://hwlab-agent-worker.hwlab-dev.svc.cluster.local:7411" }), "hwlab-gateway": Object.freeze({ urlEnv: "HWLAB_GATEWAY_URL", defaultUrl: "http://hwlab-gateway.hwlab-dev.svc.cluster.local:7001" }), "hwlab-gateway-simu": Object.freeze({ urlEnv: "HWLAB_GATEWAY_SIMU_URL", defaultUrl: "http://hwlab-gateway-simu.hwlab-dev.svc.cluster.local:7101" }), "hwlab-box-simu": Object.freeze({ urlEnv: "HWLAB_BOX_SIMU_URL", defaultUrl: "http://hwlab-box-simu.hwlab-dev.svc.cluster.local:7201" }), "hwlab-patch-panel": Object.freeze({ urlEnv: "HWLAB_PATCH_PANEL_URL", defaultUrl: "http://hwlab-patch-panel.hwlab-dev.svc.cluster.local:7301" }), "hwlab-router": Object.freeze({ urlEnv: "HWLAB_ROUTER_URL", defaultUrl: "http://hwlab-router.hwlab-dev.svc.cluster.local:7401" }), "hwlab-tunnel-client": Object.freeze({ urlEnv: "HWLAB_TUNNEL_CLIENT_URL", defaultUrl: "http://hwlab-tunnel-client.hwlab-dev.svc.cluster.local:7402" }), "hwlab-edge-proxy": Object.freeze({ urlEnv: "HWLAB_EDGE_PROXY_URL", defaultUrl: "http://hwlab-edge-proxy.hwlab-dev.svc.cluster.local:6667", healthPath: "/health" }), "hwlab-cli": Object.freeze({ urlEnv: "HWLAB_CLI_URL", defaultUrl: "http://hwlab-cli.hwlab-dev.svc.cluster.local:7501" }), "hwlab-agent-skills": Object.freeze({ urlEnv: "HWLAB_AGENT_SKILLS_URL", defaultUrl: "http://hwlab-agent-skills.hwlab-dev.svc.cluster.local:7430" }) }); const LIVE_BUILD_EXTERNAL_COMPONENTS = Object.freeze([ Object.freeze({ serviceId: "hwlab-frpc", serviceName: "hwlab-frpc", kind: "external", externalImage: true, defaultImage: "127.0.0.1:5000/hwlab/frpc:v0.68.1", externalReason: "外部镜像或非 HWLAB 构建产物" }) ]); export function createCloudApiServer(options = {}) { const env = options.env ?? process.env; ensureCodeAgentRuntimeBase(env); const sessionRegistry = options.sessionRegistry || createCodeAgentSessionRegistry(); const traceStore = options.traceStore || defaultCodeAgentTraceStore; const codexStdioManager = options.codexStdioManager || createCodexStdioSessionManager({ traceStore }); const runtimeStore = options.runtimeStore || createConfiguredCloudRuntimeStore({ ...options, env }); const gatewayRegistry = options.gatewayRegistry || createGatewayDemoRegistry({ staleMs: parsePositiveInteger(env.HWLAB_GATEWAY_DEMO_STALE_MS, 30000), dispatchTimeoutMs: parsePositiveInteger(env.HWLAB_GATEWAY_DISPATCH_TIMEOUT_MS, 30000) }); return createServer(async (request, response) => { try { await routeRequest(request, response, { ...options, env, runtimeStore, gatewayRegistry, sessionRegistry, traceStore, codexStdioManager }); } catch (error) { sendJson(response, 500, { error: { code: "internal_error", message: "hwlab-cloud-api request handling failed", reason: error.message } }); } }); } export function ensureCodeAgentRuntimeBase(env = process.env) { const workspace = env.HWLAB_CODE_AGENT_CODEX_WORKSPACE || env.HWLAB_CODE_AGENT_WORKSPACE || "/workspace/hwlab"; const codexHome = env.CODEX_HOME || env.HWLAB_CODE_AGENT_CODEX_HOME || "/codex-home"; const appCodexCommand = "/app/node_modules/.bin/codex"; const localCodexCommand = path.join(process.cwd(), "node_modules", ".bin", "codex"); const codexCommand = existsSync(appCodexCommand) ? appCodexCommand : existsSync(localCodexCommand) ? localCodexCommand : appCodexCommand; env.HWLAB_CODE_AGENT_WORKSPACE ||= workspace; env.HWLAB_CODE_AGENT_CODEX_WORKSPACE ||= workspace; env.HWLAB_CODE_AGENT_CODEX_SANDBOX ||= "workspace-write"; env.HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED ||= "1"; env.HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR ||= "repo-owned"; env.CODEX_HOME ||= codexHome; env.HWLAB_CODE_AGENT_CODEX_COMMAND ||= codexCommand; env.HWLAB_CODE_AGENT_SKILLS_DIRS ||= "/app/skills:/root/.agents/skills:/home/ubuntu/.agents/skills"; for (const target of [path.dirname(workspace), codexHome]) { try { mkdirSync(target, { recursive: true }); } catch { // Runtime health reports the concrete missing/writable blocker. } } try { if (!existsSync(workspace)) { if (workspace === "/workspace/hwlab" && existsSync("/app")) { symlinkSync("/app", workspace, "dir"); } else { mkdirSync(workspace, { recursive: true }); } } lstatSync(workspace); } catch { // Runtime health reports the concrete missing/writable blocker. } } export async function buildHealthPayload(options = {}) { const serviceId = CLOUD_API_SERVICE_ID; const env = options.env ?? process.env; ensureCodeAgentRuntimeBase(env); const metadata = buildMetadataFromEnv(env, { serviceId, fallbackImageRepository: "ghcr.io/pikastech/hwlab-cloud-api" }); const dbProbe = await buildDbRuntimeReadiness(env, options.dbProbe); const codeAgent = await describeCodeAgentAvailability(env, options); const runtime = await runtimeReadiness(options.runtimeStore ?? createConfiguredCloudRuntimeStore({ ...options, env })); const db = applyRuntimeDbReadinessLayers(dbProbe, runtime); const readiness = buildCloudApiReadiness({ db, codeAgent, runtime }); return { serviceId, environment: ENVIRONMENT_DEV, status: readiness.status, ready: readiness.ready, revision: metadata.revision, readiness, blockers: readiness.blockers, blockerCodes: readiness.blockerCodes, service: { id: serviceId, role: "cloud-api", healthPath: "/health", livePath: "/health/live" }, commit: metadata.commit, image: metadata.image, build: metadata.build, endpoint: env.HWLAB_PUBLIC_ENDPOINT || DEV_ENDPOINT, observedAt: new Date().toISOString(), db, codeAgent, runtime }; } async function routeRequest(request, response, options) { const url = new URL(request.url || "/", "http://hwlab-cloud-api.local"); if (request.method === "GET" && (url.pathname === "/health" || url.pathname === "/health/live")) { sendJson(response, 200, await buildHealthPayload(options)); return; } if (request.method === "GET" && url.pathname === "/live") { sendJson(response, 200, { serviceId: CLOUD_API_SERVICE_ID, status: "live" }); return; } if (request.method === "POST" && (url.pathname === "/rpc" || url.pathname === "/json-rpc")) { await handleRpcHttpRequest(request, response, options); return; } if (url.pathname === "/v1" || url.pathname.startsWith("/v1/")) { await handleRestAdapter(request, response, url, options); return; } sendRestError(request, response, 404, "not_found", "Route is not part of the L1 cloud-api runtime", { operation: `${request.method || "GET"} ${url.pathname}`, target: { type: "route", id: url.pathname } }); } async function handleRpcHttpRequest(request, response, options) { const body = await readBody(request, options.bodyLimitBytes); let envelope; try { envelope = body ? JSON.parse(body) : {}; } catch (error) { sendJson( response, 400, createErrorEnvelope({ id: "req_unassigned", code: ERROR_CODES.parseError, message: "Invalid JSON body", data: { reason: error.message }, context: { operation: "json_rpc.parse", target: { type: "http_route", id: request.url || "/rpc" }, result: "rejected" } }) ); return; } const rpcResponse = await handleJsonRpcRequest(envelope, { adapter: "json-rpc", runtimeStore: options.runtimeStore, env: options.env, dbProbe: options.dbProbe, m3IoRequestJson: options.m3IoRequestJson, gatewayRegistry: options.gatewayRegistry }); sendJson(response, statusForRpcResponse(rpcResponse), rpcResponse); } async function handleRestAdapter(request, response, url, options) { if (request.method === "GET" && url.pathname === "/v1") { const dbProbe = await buildDbRuntimeReadiness(options.env ?? process.env, options.dbProbe); const codeAgent = await describeCodeAgentAvailability(options.env ?? process.env, options); const runtime = await runtimeReadiness(options.runtimeStore); const db = applyRuntimeDbReadinessLayers(dbProbe, runtime); const readiness = buildCloudApiReadiness({ db, codeAgent, runtime }); sendJson(response, 200, { serviceId: CLOUD_API_SERVICE_ID, adapter: "rest", status: readiness.status, ready: readiness.ready, rpcBridge: "POST /v1/rpc/{method}", codeAgent, methods: SUPPORTED_RPC_METHODS, auditFields: AUDIT_FIELD_NAMES, db, runtime, readiness, blockers: readiness.blockers, blockerCodes: readiness.blockerCodes, m3IoControl: describeM3IoControl(options) }); return; } if (request.method === "GET" && url.pathname === M3_IO_CONTROL_ROUTE) { sendJson(response, 200, await describeM3IoControlLive(options)); return; } if (request.method === "GET" && url.pathname === M3_STATUS_ROUTE) { sendJson(response, 200, await describeM3StatusLive(options)); return; } if (request.method === "GET" && url.pathname === "/v1/diagnostics/gate") { sendJson(response, 200, await buildGateDiagnosticsRows(options)); return; } if (request.method === "GET" && url.pathname === "/v1/live-builds") { sendJson(response, 200, await buildLiveBuildsPayload(options)); return; } if (request.method === "GET" && url.pathname === "/v1/gateway/sessions") { sendJson(response, 200, options.gatewayRegistry.describe()); return; } if (request.method === "POST" && url.pathname === "/v1/gateway/poll") { await handleGatewayPollHttp(request, response, options); return; } if (request.method === "POST" && url.pathname === "/v1/gateway/result") { await handleGatewayResultHttp(request, response, options); return; } if (request.method === "POST" && url.pathname === M3_IO_CONTROL_ROUTE) { await handleM3IoControlHttp(request, response, options); return; } if (request.method === "POST" && url.pathname === "/v1/agent/chat") { await handleCodeAgentChatHttp(request, response, options); return; } if (request.method === "POST" && url.pathname === "/v1/agent/chat/cancel") { await handleCodeAgentCancelHttp(request, response, options); return; } if (request.method === "GET" && url.pathname.startsWith("/v1/agent/chat/trace/")) { await handleCodeAgentTraceHttp(request, response, url, options); return; } if (request.method !== "POST" || !url.pathname.startsWith("/v1/rpc/")) { sendRestError(request, response, 404, "not_found", "REST route is not implemented in the L1 cloud-api runtime", { operation: `${request.method || "GET"} ${url.pathname}`, target: { type: "route", id: url.pathname } }); return; } const method = decodeURIComponent(url.pathname.slice("/v1/rpc/".length)); const body = await readBody(request, options.bodyLimitBytes); let params = {}; try { params = body ? JSON.parse(body) : {}; } catch (error) { sendRestError(request, response, 400, "parse_error", "Invalid JSON body", { operation: method, target: { type: "rpc_method", id: method }, result: "rejected", reason: error.message }); return; } if (!params || typeof params !== "object" || Array.isArray(params)) { sendRestError(request, response, 400, "invalid_params", "REST RPC bridge body must be a JSON object", { operation: method, target: { type: "rpc_method", id: method }, result: "rejected" }); return; } const rpcResponse = await handleJsonRpcRequest( { jsonrpc: "2.0", id: getHeader(request, "x-request-id") || `req_${randomUUID()}`, method, params, meta: { traceId: getHeader(request, "x-trace-id") || `trc_${randomUUID()}`, actorId: getHeader(request, "x-actor-id"), serviceId: getHeader(request, "x-source-service-id") || CLOUD_API_SERVICE_ID, environment: ENVIRONMENT_DEV } }, { adapter: "rest", runtimeStore: options.runtimeStore, env: options.env, dbProbe: options.dbProbe, m3IoRequestJson: options.m3IoRequestJson, gatewayRegistry: options.gatewayRegistry } ); sendJson(response, statusForRpcResponse(rpcResponse), rpcResponse); } async function buildLiveBuildsPayload(options = {}) { const env = options.env ?? process.env; const metadata = await loadLiveBuildMetadata(options); const inventory = liveBuildServiceInventory(metadata); const services = await Promise.all( inventory.map((service) => observeLiveBuildService(service, { ...options, env })) ); const hwlabServices = services.filter((service) => service.kind === "hwlab"); const latest = [...hwlabServices] .filter((service) => service.build.createdAt) .sort((left, right) => Date.parse(right.build.createdAt) - Date.parse(left.build.createdAt))[0] ?? null; return { serviceId: CLOUD_API_SERVICE_ID, environment: ENVIRONMENT_DEV, status: "ok", contractVersion: "live-builds-v1", observedAt: new Date().toISOString(), source: { kind: "live-health+deploy-artifact-catalog", route: "/v1/live-builds", healthPath: "/health/live", metadataPaths: LIVE_BUILD_METADATA_PATHS, desiredStateSource: metadata.deployManifest.status === "ok" ? LIVE_BUILD_METADATA_PATHS.deployManifest : "unavailable", artifactCatalogSource: metadata.artifactCatalog.status === "ok" ? LIVE_BUILD_METADATA_PATHS.artifactCatalog : "unavailable", note: "Each row prefers current live service health build metadata and falls back only to deploy/artifact catalog metadata when the live tag/revision matches; observedAt is not used as build time." }, latest, counts: { total: services.length, hwlab: hwlabServices.length, withBuildTime: hwlabServices.filter((service) => Boolean(service.build.createdAt)).length, unavailable: hwlabServices.filter((service) => service.build.createdAt === null).length, external: services.filter((service) => service.kind === "external").length }, services }; } async function observeLiveBuildService(service, options) { if (service.externalImage) { return externalLiveBuildRecord(service, { healthUrl: null, httpStatus: null, imageReference: service.defaultImage ?? "unknown", reason: "外部镜像或非 HWLAB 构建产物" }); } const metadataRecords = liveBuildRecordsFromMetadata(service); const metadataRecord = metadataRecords[0] ?? null; if (service.serviceId === CLOUD_API_SERVICE_ID) { const health = await buildHealthPayload(options); return liveBuildRecordFromHealth(service, { ok: true, status: 200, url: "/health/live", payload: health }, metadataRecords); } const baseUrl = String(options.env?.[service.urlEnv] || service.defaultUrl || "").replace(/\/+$/u, ""); if (!baseUrl) { return metadataRecord ? metadataRecordWithHealthGap(metadataRecord, { service, healthUrl: null, httpStatus: null, reason: `${service.serviceId} 没有配置 live health URL` }) : unavailableLiveBuildRecord(service, { reasonCode: "service_url_unavailable", reason: `${service.serviceId} 没有配置 live health URL` }); } const healthPath = service.healthPath ?? "/health/live"; return liveBuildRecordFromHealth( service, await fetchServiceHealth(`${baseUrl}${healthPath}`, options.fetchImpl ?? fetch), metadataRecords ); } async function fetchServiceHealth(url, fetchImpl) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), LIVE_BUILD_TIMEOUT_MS); try { const response = await fetchImpl(url, { headers: { Accept: "application/json" }, signal: controller.signal }); const text = await response.text(); let payload = null; try { payload = text ? JSON.parse(text) : null; } catch { return { ok: false, status: response.status, url, reasonCode: "invalid_json", reason: `${url} returned non-JSON health payload` }; } return { ok: response.ok, status: response.status, url, payload, reasonCode: response.ok ? null : "http_error", reason: response.ok ? null : `${url} returned HTTP ${response.status}` }; } catch (error) { const timedOut = error?.name === "AbortError"; return { ok: false, status: null, url, reasonCode: timedOut ? "timeout" : "request_failed", reason: timedOut ? `${url} timed out after ${LIVE_BUILD_TIMEOUT_MS}ms` : error.message }; } finally { clearTimeout(timeout); } } function liveBuildRecordFromHealth(service, result, metadataRecords = []) { const candidates = Array.isArray(metadataRecords) ? metadataRecords : [metadataRecords].filter(Boolean); const metadataRecord = candidates[0] ?? null; if (!result.ok) { const reason = healthUnavailableReason(service, result.reason ?? `${service.serviceId} health unavailable`); return metadataRecord ? metadataRecordWithHealthGap(metadataRecord, { service, healthUrl: result.url, httpStatus: result.status, reason }) : unavailableLiveBuildRecord(service, { healthUrl: result.url, httpStatus: result.status, reasonCode: result.reasonCode ?? "health_unavailable", reason }); } const payload = result.payload ?? {}; const payloadServiceId = payload.serviceId ?? service.serviceId; if (!String(payloadServiceId).startsWith("hwlab-")) { return externalLiveBuildRecord(service, { healthUrl: result.url, httpStatus: result.status, imageReference: payload.image?.reference ?? payload.image ?? "unknown", imageTag: payload.image?.tag ?? null, imageDigest: payload.image?.digest ?? "unknown", commitId: payload.commit?.id ?? payload.revision ?? payload.version ?? "unknown", commitSource: payload.commit?.source ?? "external-health", revision: payload.revision ?? payload.commit?.id ?? payload.version ?? "unknown", reason: "外部镜像或非 HWLAB 构建产物" }); } const imageReference = payload.image?.reference ?? payload.image ?? "unknown"; const commitId = payload.commit?.id ?? payload.revision ?? "unknown"; const liveIdentity = liveBuildIdentityFromHealth({ payload, imageReference, commitId }); const createdAt = normalizeHealthBuildTime(payload); if (createdAt) { return liveBuildHealthPayloadRecord(service, result, { payload, imageReference, commitId, createdAt, metadataSource: payload.build?.metadataSource ?? "health:build.createdAt", unavailableReason: null }); } const matched = selectMatchingMetadataRecord(liveIdentity, candidates); if (matched.record?.build?.createdAt) { return mergeLiveHealthOntoMetadataRecord(matched.record, { match: matched.match, liveIdentity, service, result, payload, imageReference, commitId }); } const mismatch = matched.match ?? liveBuildMetadataMatch(liveIdentity, metadataRecord); const unavailableReason = matched.record ? "构建时间不可用:当前 live 镜像未提供 buildCreatedAt" : metadataRecord ? `构建时间不可用:live tag 与 ${controlledMetadataLabel(metadataRecord)} 不匹配` : "构建时间不可用:当前 live 镜像未提供 buildCreatedAt"; return liveBuildHealthPayloadRecord(service, result, { payload, imageReference, commitId, createdAt: null, metadataSource: matched.record ? "live-health:matched-controlled-metadata-without-build-time" : metadataRecord ? "live-health:controlled-metadata-mismatch" : "live-health:build-created-at-missing", unavailableReason, metadataMismatch: mismatch, unavailableDetail: matched.record ? `live tag ${liveIdentity.imageTag} / revision ${liveIdentity.revision} 已匹配 ${controlledMetadataLabel(matched.record)},但该 metadata 没有 buildCreatedAt(metadata tag ${matched.record.image.tag},metadata revision ${matched.record.revision},来源 ${matched.record.build.metadataSource})` : metadataRecord ? `未使用可能过期的构建时间:live tag ${liveIdentity.imageTag},live revision ${liveIdentity.revision},metadata tag ${metadataRecord.image.tag},metadata revision ${metadataRecord.revision},来源 ${metadataRecord.build.metadataSource}` : "health payload 缺少 build.createdAt / image.createdAt / buildCreatedAt,deploy/artifact catalog 也没有可匹配的 buildCreatedAt" }); } function liveBuildHealthPayloadRecord(service, result, { payload, imageReference, commitId, createdAt, metadataSource, unavailableReason, metadataMismatch = null, unavailableDetail = null }) { return { serviceId: service.serviceId, name: service.serviceName, kind: "hwlab", status: createdAt ? "ok" : "build_time_unavailable", healthUrl: result.url, httpStatus: result.status, desiredState: desiredStateSummary(service), build: { createdAt, source: payload.build?.source ?? payload.commit?.source ?? "health-payload", metadataSource, unavailableReason, unavailableDetail, metadataMismatch }, image: { reference: imageReference, tag: payload.image?.tag ?? imageTagFromReference(imageReference) ?? "unknown", digest: payload.image?.digest ?? "unknown" }, commit: { id: commitId, source: payload.commit?.source ?? "health-payload" }, revision: payload.revision ?? commitId }; } function metadataRecordWithHealthGap(metadataRecord, { service, healthUrl, httpStatus, reason }) { const metadataTag = metadataRecord.image?.tag ?? "unknown"; const metadataRevision = metadataRecord.revision ?? metadataRecord.commit?.id ?? "unknown"; return { ...metadataRecord, healthUrl, httpStatus, status: "build_time_unavailable", build: { ...metadataRecord.build, createdAt: null, metadataSource: "live-health-unavailable", unavailableReason: `构建时间不可用:当前 live health 不可用,无法确认 ${service.serviceId} live tag/revision 是否匹配 deploy/artifact catalog metadata,未使用可能过期的构建时间(metadata tag ${metadataTag},metadata revision ${metadataRevision});${reason}` } }; } function mergeLiveHealthOntoMetadataRecord(metadataRecord, { match, liveIdentity, service, result, payload, imageReference, commitId }) { return { ...metadataRecord, serviceId: service.serviceId, name: service.serviceName, kind: "hwlab", status: "ok", healthUrl: result.url, httpStatus: result.status, build: { ...metadataRecord.build, metadataSource: metadataRecord.build.metadataSource, unavailableReason: null, liveMetadataMatch: match, liveHealthMissingReason: `health payload 缺少 build.createdAt / image.createdAt / buildCreatedAt,已使用与 live tag/revision 匹配的 ${controlledMetadataLabel(metadataRecord)}(live tag ${liveIdentity.imageTag})` }, image: { reference: imageReference !== "unknown" ? imageReference : metadataRecord.image.reference, tag: payload.image?.tag ?? imageTagFromReference(imageReference) ?? metadataRecord.image.tag, digest: payload.image?.digest ?? metadataRecord.image.digest }, commit: { id: commitId !== "unknown" ? commitId : metadataRecord.commit.id, source: payload.commit?.source ?? metadataRecord.commit.source }, revision: payload.revision ?? (commitId !== "unknown" ? commitId : metadataRecord.revision) }; } function liveBuildIdentityFromHealth({ payload, imageReference, commitId }) { return { imageReference, imageTag: payload.image?.tag ?? imageTagFromReference(imageReference) ?? "unknown", commitId, revision: payload.revision ?? commitId, digest: payload.image?.digest ?? "unknown" }; } function selectMatchingMetadataRecord(liveIdentity, metadataRecords) { let firstMatched = null; for (const record of metadataRecords) { const match = liveBuildMetadataMatch(liveIdentity, record); if (!match.matched) continue; if (record.build?.createdAt) return { record, match }; firstMatched ??= { record, match }; } if (firstMatched) return firstMatched; return { record: null, match: metadataRecords.length ? liveBuildMetadataMatch(liveIdentity, metadataRecords[0]) : null }; } function liveBuildMetadataMatch(liveIdentity, metadataRecord) { if (!metadataRecord) { return { matched: false, matchedValues: [], live: liveBuildIdentitySummary(liveIdentity), metadata: null }; } const liveValues = uniqueStrings([ liveIdentity.imageTag, imageTagFromReference(liveIdentity.imageReference), liveIdentity.revision, liveIdentity.commitId, liveIdentity.digest ]).filter((value) => value !== "unknown"); const metadataValues = uniqueStrings([ metadataRecord.image?.tag, imageTagFromReference(metadataRecord.image?.reference), metadataRecord.revision, metadataRecord.commit?.id, metadataRecord.image?.digest ]).filter((value) => value !== "unknown" && value !== "not_published"); const matchedValues = liveValues.filter((liveValue) => metadataValues.some((metadataValue) => liveBuildIdentityEquivalent(liveValue, metadataValue)) ); return { matched: matchedValues.length > 0, matchedValues, live: liveBuildIdentitySummary(liveIdentity), metadata: { imageTag: metadataRecord.image?.tag ?? "unknown", revision: metadataRecord.revision ?? "unknown", commitId: metadataRecord.commit?.id ?? "unknown", digest: metadataRecord.image?.digest ?? "unknown" } }; } function liveBuildIdentitySummary(identity) { return { imageTag: identity?.imageTag ?? "unknown", revision: identity?.revision ?? "unknown", commitId: identity?.commitId ?? "unknown", digest: identity?.digest ?? "unknown" }; } function liveBuildIdentityEquivalent(left, right) { const a = String(left ?? "").trim(); const b = String(right ?? "").trim(); if (!a || !b || a === "unknown" || b === "unknown") return false; if (a === b) return true; if (a.startsWith("sha256:") || b.startsWith("sha256:")) return false; return a.length >= 7 && b.length >= 7 && (a.startsWith(b) || b.startsWith(a)); } function liveBuildRecordsFromMetadata(service) { return [ liveBuildRecordFromMetadataSource(service, "artifact"), liveBuildRecordFromMetadataSource(service, "deploy") ].filter(Boolean); } function liveBuildRecordFromMetadataSource(service, sourceKind) { const source = sourceKind === "artifact" ? service.artifact : service.deploy; if (!source) return null; const createdAt = normalizeIsoTimestamp( sourceKind === "deploy" ? source.env?.HWLAB_BUILD_CREATED_AT : source.buildCreatedAt ); const imageReference = sourceKind === "deploy" ? source.env?.HWLAB_IMAGE ?? source.image ?? "unknown" : source.image ?? service.deploy?.image ?? "unknown"; const commitId = sourceKind === "artifact" ? source.commitId ?? imageTagFromReference(imageReference) ?? "unknown" : source.env?.HWLAB_COMMIT_ID ?? imageTagFromReference(imageReference) ?? "unknown"; const digest = sourceKind === "deploy" ? source.env?.HWLAB_IMAGE_DIGEST ?? "unknown" : source.digest ?? "unknown"; const buildSource = sourceKind === "deploy" ? source.env?.HWLAB_BUILD_SOURCE ?? "deploy-desired-state-env" : source.buildSource ?? "artifact-catalog-metadata"; const imageTag = sourceKind === "deploy" ? source.env?.HWLAB_IMAGE_TAG ?? imageTagFromReference(imageReference) ?? "unknown" : source.imageTag ?? imageTagFromReference(imageReference) ?? "unknown"; const revision = sourceKind === "deploy" ? source.env?.HWLAB_REVISION ?? commitId : commitId; return { serviceId: service.serviceId, name: service.serviceName, kind: "hwlab", status: createdAt ? "ok" : "build_time_unavailable", healthUrl: null, httpStatus: null, desiredState: desiredStateSummary(service), build: { createdAt, source: buildSource, metadataSource: artifactMetadataSource(sourceKind), unavailableReason: createdAt ? null : `构建时间不可用:${controlledMetadataLabel({ build: { metadataSource: artifactMetadataSource(sourceKind) } })} 缺少 buildCreatedAt` }, image: { reference: imageReference, tag: imageTag, digest }, commit: { id: commitId, source: sourceKind === "artifact" ? "artifact-catalog" : "deploy-desired-state" }, revision }; } function desiredStateSummary(service) { return { namespace: service.deploy?.namespace ?? service.artifact?.namespace ?? "hwlab-dev", profile: service.deploy?.profile ?? service.artifact?.profile ?? ENVIRONMENT_DEV, replicas: Number.isFinite(Number(service.deploy?.replicas)) ? Number(service.deploy.replicas) : null, healthPath: service.healthPath ?? service.deploy?.healthPath ?? service.artifact?.healthPath ?? "/health/live", source: service.deploy ? LIVE_BUILD_METADATA_PATHS.deployManifest : "service-defaults" }; } function artifactMetadataSource(sourceKind) { if (sourceKind === "artifact") return "artifact-catalog:buildCreatedAt"; return "deploy-env:HWLAB_BUILD_CREATED_AT"; } function controlledMetadataLabel(record) { const source = String(record?.build?.metadataSource ?? ""); if (source.includes("artifact-catalog")) return "catalog metadata"; if (source.includes("deploy-env")) return "deploy metadata"; return "controlled metadata"; } function unavailableLiveBuildRecord(service, { healthUrl = null, httpStatus = null, reasonCode, reason }) { return { serviceId: service.serviceId, name: service.serviceName, kind: "hwlab", status: "unavailable", healthUrl, httpStatus, desiredState: desiredStateSummary(service), build: { createdAt: null, metadataSource: "unavailable", unavailableReason: `构建时间不可用:${reason}` }, image: { reference: "unknown", tag: "unknown", digest: "unknown" }, commit: { id: "unknown", source: "unavailable" }, revision: "unknown", error: { code: reasonCode, message: reason } }; } function externalLiveBuildRecord(service, { healthUrl = null, httpStatus = null, imageReference = "unknown", imageTag = null, imageDigest = "unknown", commitId = "unknown", commitSource = "external-image", revision = commitId, reason }) { return { serviceId: service.serviceId, name: service.serviceName, kind: "external", status: "external", healthUrl, httpStatus, build: { createdAt: null, metadataSource: "external-image", unavailableReason: reason }, image: { reference: imageReference, tag: imageTag ?? imageTagFromReference(imageReference) ?? "unknown", digest: imageDigest }, commit: { id: commitId, source: commitSource }, revision }; } function healthUnavailableReason(service, reason) { if (Number(service.deploy?.replicas) === 0) { return `${service.serviceId} 当前 hwlab-dev desired replicas=0,无可读取的常驻 /health/live:${reason}`; } if (service.activation === "manual") { return `${service.serviceId} 是手动激活服务,当前 live health 不可用:${reason}`; } return reason; } function normalizeHealthBuildTime(payload) { return normalizeIsoTimestamp( payload?.build?.createdAt ?? payload?.image?.createdAt ?? payload?.buildCreatedAt ?? payload?.metadata?.buildCreatedAt ?? null ); } async function loadLiveBuildMetadata(options = {}) { if (options.liveBuildMetadata) return normalizeLiveBuildMetadata(options.liveBuildMetadata); const root = options.repoRoot ?? process.env.HWLAB_REPO_ROOT ?? repoRoot; const [deployManifest, artifactCatalog] = await Promise.all([ readJsonMetadata(root, LIVE_BUILD_METADATA_PATHS.deployManifest), readJsonMetadata(root, LIVE_BUILD_METADATA_PATHS.artifactCatalog) ]); return normalizeLiveBuildMetadata({ deployManifest, artifactCatalog }); } function normalizeLiveBuildMetadata(metadata) { return { deployManifest: normalizeMetadataFile(metadata.deployManifest), artifactCatalog: normalizeMetadataFile(metadata.artifactCatalog) }; } async function readJsonMetadata(root, relativePath) { try { const payload = JSON.parse(await readFile(path.join(root, relativePath), "utf8")); return { status: "ok", path: relativePath, payload }; } catch (error) { return { status: "unavailable", path: relativePath, error: error.message, payload: null }; } } function normalizeMetadataFile(file) { if (file?.status === "ok" || file?.status === "unavailable") { return { status: file.status, path: file.path ?? null, error: file.error ?? null, payload: file.payload ?? null }; } return { status: file ? "ok" : "unavailable", path: null, error: null, payload: file ?? null }; } function liveBuildServiceInventory(metadata) { const deployServices = Array.isArray(metadata.deployManifest.payload?.services) ? metadata.deployManifest.payload.services : []; const deployByServiceId = new Map(deployServices.map((service) => [service.serviceId, service])); const artifactByServiceId = new Map( (Array.isArray(metadata.artifactCatalog.payload?.services) ? metadata.artifactCatalog.payload.services : []) .map((service) => [service.serviceId, service]) ); const serviceIds = uniqueStrings([ ...SERVICE_IDS, ...deployServices.map((service) => service.serviceId), ...artifactByServiceId.keys() ]).filter((serviceId) => String(serviceId).startsWith("hwlab-")); return [ ...serviceIds.map((serviceId) => { const deploy = deployByServiceId.get(serviceId) ?? null; const defaults = LIVE_BUILD_SERVICE_DEFAULTS[serviceId] ?? {}; const healthPath = deploy?.healthPath ?? defaults.healthPath ?? "/health/live"; return Object.freeze({ serviceId, serviceName: deploy?.name ?? serviceId, urlEnv: defaults.urlEnv ?? serviceUrlEnvName(serviceId), defaultUrl: defaults.defaultUrl ?? serviceDefaultUrl(serviceId), healthPath, activation: deploy?.replicas === 0 ? "zero-replica" : defaults.activation, deploy, artifact: artifactByServiceId.get(serviceId) ?? null }); }), ...LIVE_BUILD_EXTERNAL_COMPONENTS ]; } function uniqueStrings(values) { return [...new Set(values.map((value) => String(value ?? "").trim()).filter(Boolean))]; } function serviceUrlEnvName(serviceId) { return `${serviceId.replace(/^hwlab-/u, "HWLAB_").replace(/-/gu, "_").toUpperCase()}_URL`; } function serviceDefaultUrl(serviceId) { const port = { "hwlab-cloud-api": 6667, "hwlab-cloud-web": 8080, "hwlab-agent-mgr": 7410, "hwlab-agent-worker": 7411, "hwlab-gateway": 7001, "hwlab-gateway-simu": 7101, "hwlab-box-simu": 7201, "hwlab-patch-panel": 7301, "hwlab-router": 7401, "hwlab-tunnel-client": 7402, "hwlab-edge-proxy": 6667, "hwlab-cli": 7501, "hwlab-agent-skills": 7430 }[serviceId] ?? 8080; return `http://${serviceId}.hwlab-dev.svc.cluster.local:${port}`; } async function handleGatewayPollHttp(request, response, options) { const body = await readBody(request, options.bodyLimitBytes); let params = {}; try { params = body ? JSON.parse(body) : {}; } catch (error) { sendJson(response, 400, { accepted: false, error: { code: "parse_error", message: "Invalid JSON body", reason: error.message } }); return; } if (!params || typeof params !== "object" || Array.isArray(params)) { sendJson(response, 400, { accepted: false, error: { code: "invalid_params", message: "gateway poll body must be a JSON object" } }); return; } const session = options.gatewayRegistry.updateSession(params); await persistGatewayDemoSession({ runtimeStore: options.runtimeStore, session, request, env: options.env }); const outbound = options.gatewayRegistry.nextRequest(session.gatewaySessionId); sendJson(response, 200, { accepted: true, serviceId: CLOUD_API_SERVICE_ID, mode: "gateway-outbound-poll-demo", gatewayId: session.gatewayId, gatewaySessionId: session.gatewaySessionId, queueDepth: session.queue.length, request: outbound, type: outbound ? "request" : "noop", observedAt: new Date().toISOString() }); } async function handleGatewayResultHttp(request, response, options) { const body = await readBody(request, options.bodyLimitBytes); let params = {}; try { params = body ? JSON.parse(body) : {}; } catch (error) { sendJson(response, 400, { accepted: false, error: { code: "parse_error", message: "Invalid JSON body", reason: error.message } }); return; } const result = options.gatewayRegistry.complete(params); sendJson(response, result.accepted ? 200 : 404, { ...result, serviceId: CLOUD_API_SERVICE_ID, mode: "gateway-outbound-poll-demo", observedAt: new Date().toISOString() }); } async function persistGatewayDemoSession({ runtimeStore, session, request, env = process.env }) { if (!runtimeStore) return; const refreshMs = parsePositiveInteger(env.HWLAB_GATEWAY_REGISTRATION_REFRESH_MS, 60000); if (session.runtimeRegisteredEpochMs && Date.now() - session.runtimeRegisteredEpochMs < refreshMs) { return; } const meta = { traceId: getHeader(request, "x-trace-id") || `trc_gateway_poll_${randomUUID()}`, actorId: `svc_${session.serviceId}`, serviceId: CLOUD_API_SERVICE_ID, environment: ENVIRONMENT_DEV }; try { await runtimeStore.registerGatewaySession?.({ projectId: session.projectId, gatewaySessionId: session.gatewaySessionId, serviceId: "hwlab-gateway", gatewayId: session.gatewayId, endpoint: session.endpoint, status: "connected", labels: { demoOutboundPoll: "true", os: String(session.system?.platform ?? "unknown") } }, meta); await runtimeStore.registerBoxResource?.({ projectId: session.projectId, gatewaySessionId: session.gatewaySessionId, resourceId: session.resourceId, boxId: session.boxId, resourceType: "simulator_endpoint", state: "available", metadata: { serviceId: "hwlab-gateway", demoShellHost: true } }, meta); if (session.capabilities.length > 0) { await runtimeStore.reportBoxCapabilities?.({ capabilities: session.capabilities.map((capability) => ({ ...capability, resourceId: capability.resourceId ?? session.resourceId, projectId: capability.projectId ?? session.projectId })) }, meta); } session.runtimeRegisteredAt = new Date().toISOString(); session.runtimeRegisteredEpochMs = Date.now(); } catch { // Demo polling must keep the gateway online even when durable runtime writes are not ready. } } async function handleCodeAgentChatHttp(request, response, options) { const body = await readBody(request, options.bodyLimitBytes); let params = {}; try { params = body ? JSON.parse(body) : {}; } catch (error) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: "parse_error", message: "Invalid JSON body", reason: error.message, traceId: getHeader(request, "x-trace-id") || "trc_unassigned", model: options.env?.HWLAB_CODE_AGENT_MODEL || options.env?.OPENAI_MODEL || process.env.HWLAB_CODE_AGENT_MODEL || process.env.OPENAI_MODEL || "unknown", layer: "api", retryable: true }) }); return; } if (!params || typeof params !== "object" || Array.isArray(params)) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: "invalid_params", message: "Code Agent chat body must be a JSON object", traceId: getHeader(request, "x-trace-id") || "trc_unassigned", model: options.env?.HWLAB_CODE_AGENT_MODEL || options.env?.OPENAI_MODEL || process.env.HWLAB_CODE_AGENT_MODEL || process.env.OPENAI_MODEL || "unknown", layer: "api", retryable: true }) }); return; } const payload = await handleCodeAgentChat( { ...params, traceId: getHeader(request, "x-trace-id") || params.traceId }, { callProvider: options.callCodeAgentProvider, timeoutMs: options.codeAgentTimeoutMs, hardTimeoutMs: options.codeAgentHardTimeoutMs, env: options.env, workspace: options.workspace, skillsDirs: options.skillsDirs, skillsDirsExact: options.skillsDirsExact, skillsDiscoveryDelayMs: options.skillsDiscoveryDelayMs, sessionRegistry: options.sessionRegistry, m3IoSkillRequestJson: options.m3IoSkillRequestJson ?? createCodeAgentM3HwlabApiRequestJson(options), codexStdioManager: options.codexStdioManager, traceStore: options.traceStore } ); sendJson(response, payload.status === "failed" && payload.error?.code === "invalid_params" ? 400 : 200, payload); } async function handleCodeAgentCancelHttp(request, response, options) { const body = await readBody(request, options.bodyLimitBytes); let params = {}; try { params = body ? JSON.parse(body) : {}; } catch (error) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: "parse_error", message: "Invalid JSON body", reason: error.message, traceId: getHeader(request, "x-trace-id") || "trc_unassigned", layer: "api", retryable: true }) }); return; } if (!params || typeof params !== "object" || Array.isArray(params)) { sendJson(response, 400, { ...createCodeAgentErrorPayload({ code: "invalid_params", message: "Code Agent cancel body must be a JSON object", traceId: getHeader(request, "x-trace-id") || "trc_unassigned", layer: "api", retryable: true }) }); return; } const traceId = safeTraceId(getHeader(request, "x-trace-id") || params.traceId); const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; const snapshot = traceId ? traceStore.snapshot(traceId) : null; const sessionId = safeSessionId(params.sessionId) || safeSessionId(snapshot?.sessionId); const conversationId = safeConversationId(params.conversationId); const manager = options.codexStdioManager; if (!traceId) { sendJson(response, 400, cancelBlockedPayload({ code: "cancel_trace_missing", message: "traceId is required to cancel the current Code Agent request.", traceId: "trc_unassigned", conversationId, sessionId })); return; } if (!sessionId || !manager || typeof manager.get !== "function" || typeof manager.cancel !== "function") { traceStore.append(traceId, { type: "cancel", status: "blocked", label: "cancel:not_cancelable", errorCode: "cancel_session_missing", message: "Cancel request did not include a bound Codex stdio sessionId.", waitingFor: "session-binding" }); sendJson(response, 409, cancelBlockedPayload({ code: "cancel_session_missing", message: "当前请求尚未暴露可取消的 Codex stdio sessionId;页面不能只隐藏 UI,已保留输入和 trace。", traceId, conversationId, sessionId: null, runnerTrace: traceStore.snapshot(traceId) })); return; } const currentSession = manager.get(sessionId, { conversationId }) ?? null; if (!currentSession || !["busy", "creating"].includes(currentSession.status)) { traceStore.append(traceId, { type: "cancel", status: "blocked", label: "cancel:not_in_flight", errorCode: "cancel_not_in_flight", message: `Session ${sessionId} is not an in-flight Codex stdio request.`, sessionId, sessionStatus: currentSession?.status ?? "missing" }); sendJson(response, 409, cancelBlockedPayload({ code: currentSession ? "cancel_not_in_flight" : "cancel_session_not_found", message: currentSession ? `当前 session 状态为 ${currentSession.status},没有可取消的 in-flight Codex stdio 请求。` : `没有找到 sessionId=${sessionId} 的 Codex stdio session。`, traceId, conversationId, sessionId, session: currentSession, runnerTrace: traceStore.snapshot(traceId) })); return; } const canceledSession = manager.cancel(sessionId, { traceId, conversationId, reason: "user_cancel" }); traceStore.append(traceId, { type: "cancel", status: "canceled", label: "cancel:canceled", message: "User canceled the current Codex stdio request.", sessionId, sessionStatus: canceledSession?.status ?? "canceled", waitingFor: "user-retry", terminal: true }); const runnerTrace = traceStore.snapshot(traceId); sendJson(response, 200, { accepted: true, canceled: true, status: "canceled", conversationId: conversationId ?? canceledSession?.conversationId ?? null, sessionId, traceId, session: canceledSession, runnerTrace, lastTraceEvent: runnerTrace.lastEvent, retryable: true, userMessage: "当前 Codex stdio 请求已取消;输入、sessionId、traceId 和最后 trace event 已保留,可重试上一条消息。" }); } function cancelBlockedPayload({ code, message, traceId, conversationId = null, sessionId = null, session = null, runnerTrace = null }) { return { accepted: false, canceled: false, status: "failed", conversationId, sessionId, traceId, session, runnerTrace, lastTraceEvent: runnerTrace?.lastEvent ?? null, error: { code, layer: "session", category: "cancel_blocked", retryable: true, userMessage: message, message, traceId, route: "/v1/agent/chat/cancel", toolName: "codex-stdio.cancel" }, blocker: { code, layer: "session", category: "cancel_blocked", retryable: true, summary: message, userMessage: message, traceId, route: "/v1/agent/chat/cancel", toolName: "codex-stdio.cancel" } }; } async function handleCodeAgentTraceHttp(request, response, url, options) { const parts = url.pathname.split("/").filter(Boolean); const traceId = decodeURIComponent(parts[4] ?? ""); const traceStore = options.traceStore ?? defaultCodeAgentTraceStore; if (!/^trc_[A-Za-z0-9_.:-]+$/u.test(traceId)) { sendJson(response, 400, { error: { code: "invalid_trace_id", message: "traceId must start with trc_ and contain only safe identifier characters" } }); return; } if (parts[5] === "stream") { sendTraceSse(response, traceStore, traceId, options); return; } sendJson(response, 200, traceStore.snapshot(traceId)); } function sendTraceSse(response, traceStore, traceId, options = {}) { response.writeHead(200, { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-store", connection: "keep-alive", "x-accel-buffering": "no" }); const writeEvent = (eventName, payload) => { response.write(`event: ${eventName}\n`); response.write(`data: ${JSON.stringify(payload)}\n\n`); }; writeEvent("snapshot", traceStore.snapshot(traceId)); const unsubscribe = traceStore.subscribe(traceId, (event, snapshot) => { writeEvent("runnerTrace", { event, snapshot }); }); const heartbeatMs = parsePositiveInteger(options.env?.HWLAB_CODE_AGENT_TRACE_HEARTBEAT_MS, 15000); const heartbeat = setInterval(() => { writeEvent("heartbeat", traceStore.snapshot(traceId)); }, heartbeatMs); response.on("close", () => { clearInterval(heartbeat); unsubscribe(); }); } function createCodeAgentM3HwlabApiRequestJson(options = {}) { return async (targetUrl, request = {}) => { const url = new URL(targetUrl); const route = url.pathname; if (route === M3_IO_CONTROL_ROUTE && request.method === "POST") { return { ok: true, status: 200, body: await handleM3IoControl(request.body ?? {}, { runtimeStore: options.runtimeStore, now: options.now, env: options.env, requestJson: options.m3IoRequestJson }) }; } if (route === M3_STATUS_ROUTE && request.method === "GET") { return { ok: true, status: 200, body: await describeM3StatusLive(options) }; } return { ok: false, status: 404, body: { error: { code: "not_found", message: `Unsupported Code Agent M3 HWLAB API route ${route}` } }, error: `Unsupported Code Agent M3 HWLAB API route ${route}` }; }; } async function handleM3IoControlHttp(request, response, options) { const body = await readBody(request, options.bodyLimitBytes); let params = {}; try { params = body ? JSON.parse(body) : {}; } catch (error) { sendJson(response, 400, { serviceId: CLOUD_API_SERVICE_ID, contractVersion: "m3-io-control-v1", route: M3_IO_CONTROL_ROUTE, method: "POST", status: "blocked", accepted: false, traceId: getHeader(request, "x-trace-id") || "trc_unassigned", operationId: null, auditId: null, evidenceId: null, audit: { auditId: null, status: "not_written" }, evidence: { evidenceId: null, status: "blocked", sourceKind: "BLOCKED" }, readback: null, error: { code: "parse_error", message: "Invalid JSON body", reason: error.message } }); return; } if (!params || typeof params !== "object" || Array.isArray(params)) { sendJson(response, 400, { serviceId: CLOUD_API_SERVICE_ID, contractVersion: "m3-io-control-v1", route: M3_IO_CONTROL_ROUTE, method: "POST", status: "blocked", accepted: false, traceId: getHeader(request, "x-trace-id") || "trc_unassigned", operationId: null, auditId: null, evidenceId: null, audit: { auditId: null, status: "not_written" }, evidence: { evidenceId: null, status: "blocked", sourceKind: "BLOCKED" }, readback: null, error: { code: "invalid_params", message: "M3 IO control body must be a JSON object" } }); return; } try { const payload = await handleM3IoControl( { ...params, traceId: getHeader(request, "x-trace-id") || params.traceId, requestId: getHeader(request, "x-request-id") || params.requestId, actorId: getHeader(request, "x-actor-id") || params.actorId }, { runtimeStore: options.runtimeStore, env: options.env, requestJson: options.m3IoRequestJson, traceId: getHeader(request, "x-trace-id"), requestId: getHeader(request, "x-request-id"), actorId: getHeader(request, "x-actor-id") } ); sendJson(response, payload.httpStatus ?? 200, payload); } catch (error) { const protocolCode = Number.isInteger(error?.code) ? error.code : ERROR_CODES.internalError; sendJson(response, protocolCode === ERROR_CODES.invalidParams ? 400 : 200, { serviceId: CLOUD_API_SERVICE_ID, contractVersion: "m3-io-control-v1", route: M3_IO_CONTROL_ROUTE, method: "POST", status: "blocked", accepted: false, traceId: getHeader(request, "x-trace-id") || params.traceId || "trc_unassigned", operationId: null, auditId: null, evidenceId: null, audit: { auditId: null, status: "not_written" }, evidence: { evidenceId: null, status: "blocked", sourceKind: "BLOCKED" }, readback: null, error: { code: protocolCode, message: error.message, data: error.data ?? {} } }); } } function sendRestError(request, response, statusCode, code, message, context = {}) { const requestId = getHeader(request, "x-request-id") || "req_unassigned"; sendJson(response, statusCode, { error: { code, message, reason: context.reason, audit: createAuditRecord({ requestId, actor: { type: "user", id: getHeader(request, "x-actor-id") || `system_${CLOUD_API_SERVICE_ID}` }, source: { serviceId: CLOUD_API_SERVICE_ID, environment: ENVIRONMENT_DEV, traceId: getHeader(request, "x-trace-id"), adapter: "rest" }, operation: context.operation || `${request.method || "GET"} ${request.url || "/"}`, target: context.target || { type: "route", id: request.url || "/" }, result: context.result || "failed" }) } }); } function statusForRpcResponse(rpcResponse) { if (!rpcResponse.error) { return 200; } if (rpcResponse.error.code === ERROR_CODES.invalidRequest || rpcResponse.error.code === ERROR_CODES.parseError) { return 400; } if (rpcResponse.error.code === ERROR_CODES.methodNotFound) { return 404; } return 200; } function readBody(request, limitBytes = DEFAULT_BODY_LIMIT_BYTES) { const limit = Number.isInteger(limitBytes) && limitBytes > 0 ? limitBytes : DEFAULT_BODY_LIMIT_BYTES; return new Promise((resolve, reject) => { let body = ""; request.setEncoding("utf8"); request.on("data", (chunk) => { body += chunk; if (Buffer.byteLength(body, "utf8") > limit) { request.destroy(new Error(`request body exceeds ${limit} bytes`)); } }); request.on("end", () => resolve(body)); request.on("error", reject); }); } function sendJson(response, statusCode, body) { response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" }); response.end(`${JSON.stringify(body)}\n`); } function getHeader(request, name) { const value = request.headers[name.toLowerCase()]; if (Array.isArray(value)) { return value[0]; } return value; } function safeTraceId(value) { const text = String(value ?? "").trim(); return /^trc_[A-Za-z0-9_.:-]+$/u.test(text) ? text : null; } function safeSessionId(value) { const text = String(value ?? "").trim(); return /^ses_[A-Za-z0-9_.:-]+$/u.test(text) ? text : null; } function safeConversationId(value) { const text = String(value ?? "").trim(); return /^cnv_[A-Za-z0-9_.:-]+$/u.test(text) ? text : null; } function parsePositiveInteger(value, fallback) { const parsed = Number.parseInt(value ?? "", 10); return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; } async function runtimeReadiness(runtimeStore) { if (typeof runtimeStore?.readiness === "function") { return runtimeStore.readiness(); } return runtimeStore.summary(); }