/* * SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-19-p0-projector-resume; PJ2026-010403 API契约 draft-2026-06-20-p0-error-diagnostics; PJ2026-010401 Web工作台 draft-2026-06-20-p0-error-diagnostics; PJ2026-01060505 Workbench Performance draft-2026-06-20-p0-error-diagnostics * 职责: Cloud API REST/SSE route dispatcher。Workbench 新资源路由应委托 read model / compat wrapper,不在 dispatcher 内写业务事实。 */ import { createServer } from "node:http"; import { randomUUID } from "node:crypto"; import { copyFileSync, existsSync, lstatSync, mkdirSync, symlinkSync } from "node:fs"; import path from "node:path"; import { buildMetadataFromEnv, 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, createResultEnvelope, handleJsonRpcRequest } from "./json-rpc.ts"; import { describeCodeAgentAvailability, handleCodeAgentChat } from "./code-agent-chat.ts"; import { createDurableCodeAgentTraceStore, defaultCodeAgentTraceStore } from "./code-agent-trace-store.ts"; import { createCodeAgentSessionRegistry } from "./code-agent-session-registry.ts"; import { codeAgentSessionLifecycleSummary } from "./code-agent-session-lifecycle.ts"; import { createCodexStdioSessionManager } from "./codex-stdio-session.ts"; import { buildGateDiagnosticsRows } from "./gate-diagnostics.ts"; import { applyRuntimeDbReadinessLayers, buildDbRuntimeReadiness } from "./db-contract.ts"; import { buildCloudApiReadiness } from "./health-contract.ts"; import { M3_IO_CONTROL_ROUTE, M3_STATUS_ROUTE, describeM3IoControl, describeM3IoControlLive, describeM3StatusLive, handleM3IoControl } from "./m3-io-control.ts"; import { createConfiguredCloudRuntimeStore } from "../db/runtime-store.ts"; import { createAccessController } from "./access-control.ts"; import { createGatewayDemoRegistry } from "./gateway-demo-registry.ts"; import { buildLiveBuildsPayload } from "./server-rest-payloads.ts"; import { createWebPerformanceStore, handleWebPerformanceIngestHttp, handleWebPerformanceMetricsHttp, handleWebPerformanceSummaryHttp } from "./web-performance.ts"; import { createBackendPerformanceStore, withBackendPerformanceContext } from "./backend-performance.ts"; import { createCodeAgentChatResultStore, handleCodeAgentCancelHttp, handleCodeAgentChatHttp, handleCodeAgentChatResultHttp, handleCodeAgentInspectHttp, handleCodeAgentSessionsHttp, handleCodeAgentSteerHttp, handleCodeAgentTurnHttp, handleCodeAgentTraceHttp } from "./server-code-agent-http.ts"; import { drainWorkbenchRealtimeConnections, handleWorkbenchReadModelHttp, handleWorkbenchRealtimeHttp } from "./server-workbench-http.ts"; import { handleWorkbenchDebugFakeSseHttp } from "./workbench-debug-fake-sse.ts"; import { handleWorkbenchKafkaSseDebugHttp } from "./workbench-kafka-sse-debug.ts"; import { handleWorkbenchLaunchHttp } from "./server-workbench-launch-http.ts"; import { startWorkbenchEmptySessionGc } from "./workbench-empty-session-gc.ts"; import { startHwlabKafkaEventBridge } from "./kafka-event-bridge.ts"; import { handleM3IoControlHttp } from "./server-m3-http.ts"; import { handleSkillsHttp } from "./server-skills-http.ts"; import { emitHttpServerRequestSpan, httpRequestOtelTraceContext, newOtelSpanId } from "./otel-trace.ts"; import { decorateErrorPayloadForHttp, logErrorEnvelope } from "./error-envelope.ts"; import { handleProviderProfileCatalogHttp, handleProviderProfilesHttp } from "./provider-profile-management.ts"; import { handleAdminSecretsHttp } from "./admin-secrets-readmodel.ts"; import { configureSkillRuntime, ensureCodexSkillsAggregationSync } from "./skills-store.ts"; import { createHwpodNodeWsRegistry } from "./hwpod-node-ws-registry.ts"; import { handleHwlabNodeDownloadHttp, handleHwlabNodeUpdateHttp, handleHwpodNodeOpsHttp, handleHwpodSpecDiscoveryHttp } from "./server-hwpod-http.ts"; import { handleCaseRunHttp } from "./server-caserun-http.ts"; import { handleProjectManagementProxyHttp } from "./project-management-proxy.ts"; import { HWPOD_NODE_OPS, HWPOD_NODE_OPS_CONTRACT_VERSION } from "../../tools/src/hwpod-node-ops-contract.ts"; const DEFAULT_BODY_LIMIT_BYTES = 1024 * 1024; const HWLAB_WEB_SESSION_COOKIE = "hwlab_session"; function runtimeEnvironment(env = process.env) { const value = env.HWLAB_GITOPS_PROFILE || env.HWLAB_ENVIRONMENT || ENVIRONMENT_DEV; return typeof value === "string" && value.trim() ? value.trim() : ENVIRONMENT_DEV; } async function observeHttpRoutePhase(request, options, phase, operation, attributes = {}) { const startedAtMs = Date.now(); try { const value = await operation(); emitHttpRoutePhaseSpan(request, options, phase, startedAtMs, Date.now(), "ok", null, attributes); return value; } catch (error) { emitHttpRoutePhaseSpan(request, options, phase, startedAtMs, Date.now(), "error", error, attributes); throw error; } } function createHttpRoutePhaseObserver(request, options, namespace) { return (phase, operation, attributes = {}) => observeHttpRoutePhase(request, options, `${namespace}.${phase}`, operation, attributes); } function emitHttpRoutePhaseSpan(request, options, phase, startTimeMs, endTimeMs, outcome, error, attributes = {}) { const context = request?.hwlabHttpRequestContext; if (!context?.traceId) return; const errorMessage = error ? String(error?.message ?? error).slice(0, 300) : null; void emitHttpServerRequestSpan(context, options?.env ?? process.env, { name: `${String(context.method ?? "GET").toUpperCase()} ${String(context.route ?? "/")} ${phase}`, method: context.method, route: context.route, parentSpanId: context.spanId, spanId: newOtelSpanId(), startTimeMs, endTimeMs, statusCode: outcome === "error" ? 500 : 200, status: outcome === "error" ? "error" : "ok", statusMessage: errorMessage, errorCode: error ? phaseErrorCode(error) : null, attributes: { "hwlab.http.stage": "http.server.phase", "hwlab.http.phase": phase, "hwlab.http.phase.outcome": outcome, ...attributes } }).catch(() => undefined); } function phaseErrorCode(error) { return String(error?.code ?? error?.name ?? "phase_error").slice(0, 120); } export function createCloudApiServer(options = {}) { const env = options.env ?? process.env; ensureCodeAgentRuntimeBase(env); const sessionRegistry = options.sessionRegistry || createCodeAgentSessionRegistry(); const runtimeStore = options.runtimeStore || createConfiguredCloudRuntimeStore({ ...options, env }); const traceStore = createDurableCodeAgentTraceStore({ traceStore: options.traceStore || defaultCodeAgentTraceStore, traceEventStore: runtimeStore }); const codexStdioManager = options.codexStdioManager || createCodexStdioSessionManager({ traceStore }); const codeAgentChatResults = options.codeAgentChatResults || createCodeAgentChatResultStore({ maxResults: parsePositiveInteger(env.HWLAB_CODE_AGENT_CHAT_RESULT_CACHE_SIZE, 256) }); const webPerformanceStore = options.webPerformanceStore || createWebPerformanceStore({ env }); const backendPerformanceStore = options.backendPerformanceStore || createBackendPerformanceStore({ env }); const gatewayRegistry = options.gatewayRegistry || createGatewayDemoRegistry({ staleMs: parsePositiveInteger(env.HWLAB_GATEWAY_DEMO_STALE_MS, 30000), dispatchTimeoutMs: parsePositiveInteger(env.HWLAB_GATEWAY_DISPATCH_TIMEOUT_MS, 120000) }); const hwpodNodeWsRegistry = options.hwpodNodeWsRegistry || createHwpodNodeWsRegistry({ ...options, env }); const accessController = options.accessController || createAccessController({ ...options, env, runtimeStore, gatewayRegistry, traceStore, codeAgentChatResults }); if (typeof accessController.configureCodeAgentWorkspaceContext === "function") { accessController.configureCodeAgentWorkspaceContext({ env, fetchImpl: options.fetchImpl, traceStore, codeAgentChatResults }); } const workbenchEmptySessionGc = startWorkbenchEmptySessionGc({ env, accessController, logger: options.logger ?? console }); const kafkaEventBridge = options.kafkaEventBridge ?? startHwlabKafkaEventBridge({ env, logger: options.logger ?? console, runtimeStore, kafkaFactory: options.kafkaFactory }); const server = createServer(async (request, response) => { const requestContext = buildHttpRequestContext(request); attachHttpRequestContext(request, response, requestContext); const backendPerformance = backendPerformanceStore.beginHttpRequest(request, response); let otelHttpSpanEmitted = false; const emitHttpSpan = (closedBy) => { if (otelHttpSpanEmitted) return; otelHttpSpanEmitted = true; const httpCtx = request.hwlabHttpRequestContext; if (!httpCtx?.traceId) return; if (isOtelNoisyProbeRoute(httpCtx.route)) return; const lastError = httpCtx.lastHttpError; void emitHttpServerRequestSpan(httpCtx, env, { startTimeMs: httpCtx.startedAtMs, endTimeMs: Date.now(), statusCode: Number(response.statusCode ?? backendPerformance.statusCode ?? 0), method: backendPerformance.method || httpCtx.method, route: backendPerformance.route || httpCtx.route, attributes: { "http.closed_by": closedBy, ...(httpCtx.otelAttributes ?? {}), "http.response.body.size": Number(backendPerformance.responseBytes ?? 0), "hwlab.backend.phase_count": Array.isArray(backendPerformance.phases) ? backendPerformance.phases.length : 0 }, errorCode: lastError?.code, errorCategory: lastError?.category, errorLayer: lastError?.layer, statusMessage: lastError?.code }).catch(() => undefined); }; response.once("finish", () => emitHttpSpan("finish")); response.once("close", () => emitHttpSpan("close")); await withBackendPerformanceContext(backendPerformance, async () => { try { await routeRequest(request, response, { ...options, env, runtimeStore, kafkaEventBridge, gatewayRegistry, hwpodNodeWsRegistry, accessController, sessionRegistry, traceStore, codexStdioManager, codeAgentChatResults, webPerformanceStore, backendPerformanceStore, backendPerformance, emitHttpRoutePhaseSpan }); } catch (error) { if (error?.alreadySent || response.headersSent || response.writableEnded) return; backendPerformance.recordPhase({ phase: "route_exception", durationMs: 0, outcome: "error" }); sendJson(response, 500, { error: { code: "internal_error", message: "hwlab-cloud-api request handling failed", reason: error.message } }); } }); }); server.on("close", () => { workbenchEmptySessionGc.stop(); void kafkaEventBridge.stop(); }); server.hwlabStartupReady = kafkaEventBridge?.ready ?? Promise.resolve(); server.hwlabAbortStartup = async () => { workbenchEmptySessionGc.stop(); await kafkaEventBridge.stop(); }; return server; } function buildHttpRequestContext(request) { const otelContext = httpRequestOtelTraceContext({ traceparent: getHeader(request, "traceparent"), traceId: getHeader(request, "x-hwlab-otel-trace-id") }); return { requestId: safeRequestId(getHeader(request, "x-request-id")) || `req_${randomUUID()}`, startedAtMs: Date.now(), traceId: otelContext.traceId, parentSpanId: otelContext.parentSpanId, spanId: otelContext.spanId, traceparent: otelContext.traceparent, method: String(request.method || "GET").toUpperCase(), route: String(request.url || "/").split("?")[0] || "/", valuesPrinted: false }; } function isOtelNoisyProbeRoute(route) { const path = String(route ?? "/").split("?")[0] || "/"; return path === "/metrics" || path.startsWith("/health"); } function attachHttpRequestContext(request, response, context) { request.hwlabHttpRequestContext = context; response.hwlabHttpRequestContext = context; if (response.headersSent || response.writableEnded) return; response.setHeader("traceparent", context.traceparent); response.setHeader("x-hwlab-otel-trace-id", context.traceId); response.setHeader("x-request-id", context.requestId); } export function ensureCodeAgentRuntimeBase(env = process.env) { const codeAgentAdapter = String(env.HWLAB_CODE_AGENT_ADAPTER ?? env.HWLAB_CODE_AGENT_PROVIDER ?? "").trim().toLowerCase(); const useCodexStdioRuntime = !codeAgentAdapter || codeAgentAdapter === "codex-stdio"; const skillRuntime = configureSkillRuntime(env); if (!useCodexStdioRuntime) return; 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 ||= "danger-full-access"; 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; 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); ensurePreinstalledHwpodSpec({ workspace }); ensureCodexSkillsAggregationSync(skillRuntime); } catch { // Runtime health reports the concrete missing/writable blocker. } } function ensurePreinstalledHwpodSpec({ workspace }) { const source = path.resolve(process.cwd(), ".hwlab/hwpod-spec.yaml"); const target = path.join(workspace, ".hwlab/hwpod-spec.yaml"); const sourceMeta = path.resolve(process.cwd(), ".hwlab/hwpod-spec.meta.json"); const targetMeta = path.join(workspace, ".hwlab/hwpod-spec.meta.json"); if (!existsSync(source)) return; mkdirSync(path.dirname(target), { recursive: true }); if (!existsSync(target)) copyFileSync(source, target); if (existsSync(sourceMeta) && !existsSync(targetMeta)) copyFileSync(sourceMeta, targetMeta); } export async function buildHealthPayload(options = {}) { const serviceId = CLOUD_API_SERVICE_ID; const env = options.env ?? process.env; const healthMode = options.healthMode === "live" ? "live" : "readiness"; const liveProbe = healthMode === "live"; ensureCodeAgentRuntimeBase(env); const metadata = buildMetadataFromEnv(env, { serviceId, fallbackImageRepository: "ghcr.io/pikastech/hwlab-cloud-api" }); const runtimeStore = options.runtimeStore ?? createConfiguredCloudRuntimeStore({ ...options, env }); const dbProbe = await buildDbRuntimeReadiness(env, liveProbe ? { ...(options.dbProbe ?? {}), probe: false } : options.dbProbe); const codeAgent = await describeCodeAgentAvailability(env, options); const runtime = liveProbe ? runtimeReadinessSnapshot(runtimeStore) : await runtimeReadiness(runtimeStore); const kafkaProjector = await kafkaProjectorHealth(options.kafkaEventBridge, { liveProbe }); const db = applyRuntimeDbReadinessLayers(dbProbe, runtime); const readiness = buildCloudApiReadiness({ db, codeAgent, runtime, kafkaProjector }); return { serviceId, environment: runtimeEnvironment(env), healthMode, 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, runtimeIdentity: metadata.runtime, endpoint: env.HWLAB_PUBLIC_ENDPOINT || DEV_ENDPOINT, observedAt: new Date().toISOString(), db, codeAgent, runtime, kafkaProjector }; } async function kafkaProjectorHealth(projector, { liveProbe = false } = {}) { if (!projector?.started) return { started: false, reason: projector?.reason ?? "disabled", valuesRedacted: true }; const identity = { capabilities: projector.capabilities ?? null, directPublishGroupId: projector.directPublishGroupId ?? null, projectorGroupId: projector.projectorGroupId ?? null, hwlabEventGroupId: projector.hwlabEventGroupId ?? null, agentRunTopic: projector.agentRunTopic, hwlabTopic: projector.hwlabTopic }; if (liveProbe) return { started: true, ...identity, ...(projector.startupStatus?.() ?? { status: "initializing" }), statusSource: "startup-state", valuesRedacted: true }; try { return { started: true, ...identity, ...(await projector.status()), valuesRedacted: true }; } catch (error) { return { started: true, status: "blocked", errorCode: error?.code ?? "hwlab_kafka_projector_status_failed", message: error?.message ?? "Kafka projector status query failed.", valuesRedacted: true }; } } function runtimeReadinessSnapshot(runtimeStore) { if (runtimeStore && typeof runtimeStore.summary === "function") { try { const summary = runtimeStore.summary(); return { ...summary, readinessFresh: false, readinessSource: "cached-summary" }; } catch (error) { return { adapter: "unknown", durable: false, ready: false, status: "degraded", blocker: "runtime_readiness_snapshot_failed", reason: error instanceof Error ? error.message : "Runtime readiness snapshot failed", readinessFresh: false, readinessSource: "cached-summary", valuesRedacted: true, endpointRedacted: true }; } } return { adapter: "unknown", durable: false, ready: false, status: "degraded", blocker: "runtime_readiness_snapshot_unavailable", reason: "Runtime store does not expose a cached summary for /health/live", readinessFresh: false, readinessSource: "cached-summary", valuesRedacted: true, endpointRedacted: true }; } async function routeRequest(request, response, options) { const url = new URL(request.url || "/", "http://hwlab-cloud-api.local"); if (request.method === "GET" && url.pathname === "/health/live") { sendJson(response, 200, await buildHealthPayload({ ...options, healthMode: "live" })); return; } if (request.method === "GET" && url.pathname === "/health") { sendJson(response, 200, await buildHealthPayload({ ...options, healthMode: "readiness" })); return; } if (request.method === "GET" && url.pathname === "/live") { sendJson(response, 200, { serviceId: CLOUD_API_SERVICE_ID, status: "live" }); return; } if (url.pathname === "/auth" || url.pathname.startsWith("/auth/")) { await options.accessController.handleAuthRoute(request, response, url); 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) { const navId = navIdForRestPath(url.pathname, request.method || "GET"); if (navId && typeof options.accessController?.requireNavAccess === "function" && !(await options.accessController.requireNavAccess(request, response, navId))) return; if (url.pathname === "/v1/web-performance" && request.method === "POST") { await handleWebPerformanceIngestHttp(request, response, { store: options.webPerformanceStore, env: options.env }); return; } if (url.pathname === "/v1/web-performance/metrics" && request.method === "GET") { handleWebPerformanceMetricsHttp(request, response, { store: options.webPerformanceStore, extraMetricsText: () => options.backendPerformanceStore.metricsText() }); return; } if (url.pathname === "/v1/web-performance/summary" && request.method === "GET") { handleWebPerformanceSummaryHttp(request, response, { store: options.webPerformanceStore, backendPerformanceStore: options.backendPerformanceStore }); return; } if ((url.pathname === "/v1/usage/summary" || url.pathname === "/v1/billing/summary") && request.method === "GET") { await handleUsageSummaryHttp(request, response, url, options); return; } if (url.pathname === "/v1/redeem" && request.method === "POST") { await handleRedeemHttp(request, response, options); return; } if (url.pathname === "/v1/subscription/summary" && request.method === "GET") { await handleSubscriptionSummaryHttp(request, response, options); return; } if (url.pathname === "/v1/payments/summary" && request.method === "GET") { await handlePaymentSummaryHttp(request, response, options); return; } if (url.pathname === "/v1/admin/billing/summary" && request.method === "GET") { await handleAdminBillingSummaryHttp(request, response, url, options); return; } if (url.pathname === "/v1/admin/billing/plans" && request.method === "GET") { await handleAdminBillingPlansHttp(request, response, options); return; } if (url.pathname === "/v1/admin/billing/usage" && request.method === "GET") { await handleAdminBillingUsageHttp(request, response, url, options); return; } if (url.pathname === "/v1/admin/billing/usage/export" && request.method === "GET") { await handleAdminBillingUsageExportHttp(request, response, url, options); return; } if (url.pathname === "/v1/admin/billing/ledger" && request.method === "GET") { await handleAdminBillingLedgerHttp(request, response, url, options); return; } if (url.pathname === "/v1/admin/billing/ledger/export" && request.method === "GET") { await handleAdminBillingLedgerExportHttp(request, response, url, options); return; } if (url.pathname === "/v1/admin/billing/redeem-codes" && (request.method === "GET" || request.method === "POST")) { await handleAdminBillingRedeemCodesHttp(request, response, url, options); return; } const adminBillingRedeemCodeMatch = url.pathname.match(/^\/v1\/admin\/billing\/redeem-codes\/([^/]+)$/u); if (adminBillingRedeemCodeMatch && request.method === "PATCH") { await handleAdminBillingRedeemCodeHttp(request, response, decodeURIComponent(adminBillingRedeemCodeMatch[1]), options); return; } if (url.pathname === "/v1/admin/billing/redeem-redemptions" && request.method === "GET") { await handleAdminBillingRedeemRedemptionsHttp(request, response, url, options); return; } if (url.pathname === "/v1/admin/billing/users" && (request.method === "GET" || request.method === "POST")) { await handleAdminBillingUsersHttp(request, response, url, options); return; } const adminBillingUserMatch = url.pathname.match(/^\/v1\/admin\/billing\/users\/([^/]+)$/u); if (adminBillingUserMatch && (request.method === "GET" || request.method === "PATCH")) { await handleAdminBillingUserHttp(request, response, url, decodeURIComponent(adminBillingUserMatch[1]), options); return; } const adminBillingUserCreditsMatch = url.pathname.match(/^\/v1\/admin\/billing\/users\/([^/]+)\/credits\/adjust$/u); if (adminBillingUserCreditsMatch && request.method === "POST") { await handleAdminBillingCreditAdjustHttp(request, response, decodeURIComponent(adminBillingUserCreditsMatch[1]), options); return; } const adminBillingUserStatusMatch = url.pathname.match(/^\/v1\/admin\/billing\/users\/([^/]+)\/status$/u); if (adminBillingUserStatusMatch && request.method === "PATCH") { await handleAdminBillingUserStatusHttp(request, response, decodeURIComponent(adminBillingUserStatusMatch[1]), options); return; } if (request.method === "GET" && url.pathname === "/v1") { const dbProbeOptions = { ...(options.dbProbe ?? {}), timeoutMs: 500 }; const [dbProbe, codeAgent, runtime] = await Promise.all([ observeHttpRoutePhase(request, options, "v1.db_readiness", () => buildDbRuntimeReadiness(options.env ?? process.env, dbProbeOptions)), observeHttpRoutePhase(request, options, "v1.code_agent_availability", () => describeCodeAgentAvailability(options.env ?? process.env, options)), observeHttpRoutePhase(request, options, "v1.runtime_readiness", () => runtimeReadinessSnapshot(options.runtimeStore)) ]); const db = applyRuntimeDbReadinessLayers(dbProbe, runtime); const kafkaProjector = await kafkaProjectorHealth(options.kafkaEventBridge, { liveProbe: false }); const readiness = buildCloudApiReadiness({ db, codeAgent, runtime, kafkaProjector }); 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, caserun: { route: "/v1/caserun", casesRoute: "/v1/caserun/cases", runsRoute: "/v1/caserun/runs", contractVersion: "hwlab-caserun-web-v1", mode: "compile-only", caseAuthority: "builtin-or-configured-case-repo" }, hwpod: { route: "/v1/hwpod-node-ops", specDiscoveryRoute: "/v1/hwpod/specs", contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION, specAuthority: "workspace-or-registry", compiler: "hwpod-compiler-cli", apiRole: "node-ops-forwarder", nodeRole: "thin-hwpod-node-executor", supportedOps: Array.from(HWPOD_NODE_OPS) }, projectManagement: { route: "/v1/project-management", serviceId: "hwlab-project-management", contractVersion: "project-management-v1", sourceOfTruth: "mdtodo-markdown-files" }, hwlabNode: { updateRoute: "/v1/hwlab-node/update", downloadRoute: "/v1/hwlab-node/download/hwlab-node.py", diagnosticsOp: "node.diagnostics", updateContractVersion: "hwlab-node-update-v1", defaultUpdateIntervalSeconds: 300 }, m3IoControl: describeM3IoControl(options) }); return; } if (url.pathname === "/v1/hwlab-node/update") { handleHwlabNodeUpdateHttp(request, response, url, options); return; } if (url.pathname === "/v1/hwlab-node/download/hwlab-node.py") { handleHwlabNodeDownloadHttp(request, response, options); return; } if (url.pathname === "/v1/auth/session" || url.pathname === "/v1/auth/login" || url.pathname === "/v1/auth/logout" || url.pathname === "/v1/auth/register") { const authUrl = new URL(url.href); authUrl.pathname = url.pathname.replace(/^\/v1\/auth/u, "/auth"); await options.accessController.handleAuthRoute(request, response, authUrl); return; } if (url.pathname === "/v1/skills" || url.pathname.startsWith("/v1/skills/")) { await handleSkillsHttp(request, response, url, options); return; } if (url.pathname === "/v1/users/me") { await options.accessController.handleUserRoute(request, response, url); return; } if (url.pathname === "/v1/project-management" || url.pathname.startsWith("/v1/project-management/")) { await handleProjectManagementProxyHttp(request, response, url, options); return; } if (url.pathname === "/v1/users/me/profile" && request.method === "PATCH") { await handleUserBillingProfileHttp(request, response, options); return; } if (url.pathname === "/v1/users/me/password" && request.method === "POST") { await handleUserBillingPasswordHttp(request, response, options); return; } if (url.pathname === "/v1/api-keys" || url.pathname === "/v1/api-keys/default" || url.pathname.startsWith("/v1/api-keys/")) { if (await maybeHandleUserBillingApiKeysHttp(request, response, url, options)) return; await options.accessController.handleUserRoute(request, response, url); return; } if (url.pathname === "/v1/internal/workbench/drain") { if (request.method !== "POST") { sendJson(response, 405, { ok: false, error: { code: "method_not_allowed", message: "POST required." } }); return; } if (!isAuthorizedWorkbenchDrainRequest(request, options.env ?? process.env)) { sendJson(response, 403, { ok: false, error: { code: "workbench_drain_forbidden", message: "Workbench drain endpoint requires pod-local preStop authorization." } }); return; } const result = await drainWorkbenchRealtimeConnections({ reason: "k8s_prestop", signal: "SIGTERM", timeoutMs: parsePositiveInteger((options.env ?? process.env).HWLAB_WORKBENCH_SSE_DRAIN_TIMEOUT_MS, 2500), env: options.env ?? process.env }); sendJson(response, 200, { ok: true, route: "/v1/internal/workbench/drain", result }); return; } if (url.pathname === "/v1/workbench/events" || url.pathname === "/v1/workbench/projection-events") { await handleWorkbenchRealtimeHttp(request, response, url, options); return; } if (url.pathname === "/v1/workbench/launches") { await handleWorkbenchLaunchHttp(request, response, options); return; } if (url.pathname === "/v1/workbench/debug/fake-sse" || url.pathname.startsWith("/v1/workbench/debug/fake-sse/")) { await handleWorkbenchDebugFakeSseHttp(request, response, url, options); return; } if (url.pathname === "/v1/workbench/debug/kafka-sse" || url.pathname.startsWith("/v1/workbench/debug/kafka-sse/")) { await handleWorkbenchKafkaSseDebugHttp(request, response, url, options); return; } if (url.pathname === "/v1/workbench/sync" || url.pathname === "/v1/workbench/sessions" || url.pathname.startsWith("/v1/workbench/sessions/") || url.pathname.startsWith("/v1/workbench/turns/") || url.pathname.startsWith("/v1/workbench/traces/")) { await handleWorkbenchReadModelHttp(request, response, url, options); return; } if (url.pathname === "/v1/access/status" || url.pathname === "/v1/setup/status") { await options.accessController.handleAccessStatusRoute(request, response, url); return; } if (url.pathname === "/v1/setup/first-admin") { await options.accessController.handleSetupRoute(request, response, url); return; } if (url.pathname === "/v1/admin/provider-profiles" || url.pathname.startsWith("/v1/admin/provider-profiles/")) { await handleProviderProfilesHttp(request, response, url, options); return; } if (url.pathname === "/v1/admin/secrets" || url.pathname.startsWith("/v1/admin/secrets/")) { await handleAdminSecretsHttp(request, response, url, options); return; } if (url.pathname === "/v1/provider-profiles" && request.method === "GET") { await handleProviderProfileCatalogHttp(request, response, options); return; } if (url.pathname.startsWith("/v1/admin/")) { await options.accessController.handleAdminRoute(request, response, url); return; } if (url.pathname === "/v1/hwpod-node-ops") { await handleHwpodNodeOpsHttp(request, response, options); return; } if (url.pathname === "/v1/caserun" || url.pathname.startsWith("/v1/caserun/")) { await handleCaseRunHttp(request, response, url, options); return; } if (request.method === "GET" && url.pathname === "/v1/hwpod/specs") { await handleHwpodSpecDiscoveryHttp(request, response, url, options); return; } if (request.method === "GET" && url.pathname === "/v1/hwpod-node/ws") { sendJson(response, 426, { ok: false, status: "upgrade_required", route: "/v1/hwpod-node/ws", websocket: options.hwpodNodeWsRegistry.describe() }); 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, observeLiveBuildPhase: createHttpRoutePhaseObserver(request, options, "live_builds") }, { buildHealthPayload, runtimeEnvironment })); 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") { const nextOptions = await codeAgentOptions(request, response, options); if (!nextOptions) return; await handleCodeAgentChatHttp(request, response, nextOptions); return; } if (url.pathname === "/v1/agent/sessions" || url.pathname.startsWith("/v1/agent/sessions/")) { const nextOptions = await codeAgentOptions(request, response, options, { required: true }); if (!nextOptions) return; await handleCodeAgentSessionsHttp(request, response, url, nextOptions); return; } if (request.method === "GET" && url.pathname === "/v1/agent/chat/inspect") { const nextOptions = await codeAgentOptions(request, response, options); if (!nextOptions) return; await handleCodeAgentInspectHttp(request, response, url, nextOptions); return; } if (request.method === "GET" && url.pathname.startsWith("/v1/agent/chat/result/")) { const nextOptions = await codeAgentOptions(request, response, options); if (!nextOptions) return; await handleCodeAgentChatResultHttp(request, response, url, nextOptions); return; } if (request.method === "GET" && url.pathname.startsWith("/v1/agent/turns/")) { const nextOptions = await codeAgentOptions(request, response, options); if (!nextOptions) return; await handleCodeAgentTurnHttp(request, response, url, nextOptions); return; } if (request.method === "GET" && url.pathname.startsWith("/v1/agent/traces/")) { const nextOptions = await codeAgentOptions(request, response, options); if (!nextOptions) return; await handleCodeAgentTraceHttp(request, response, url, nextOptions); return; } if (request.method === "POST" && url.pathname === "/v1/agent/chat/cancel") { const nextOptions = await codeAgentOptions(request, response, options); if (!nextOptions) return; await handleCodeAgentCancelHttp(request, response, nextOptions); return; } if (request.method === "POST" && url.pathname === "/v1/agent/chat/steer") { const nextOptions = await codeAgentOptions(request, response, options); if (!nextOptions) return; await handleCodeAgentSteerHttp(request, response, nextOptions); 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: runtimeEnvironment(options.env ?? process.env) } }, { adapter: "rest", runtimeStore: options.runtimeStore, env: options.env, dbProbe: options.dbProbe, m3IoRequestJson: options.m3IoRequestJson, gatewayRegistry: options.gatewayRegistry } ); sendJson(response, statusForRpcResponse(rpcResponse), rpcResponse); } function navIdForRestPath(pathname, method = "GET") { const verb = String(method || "GET").toUpperCase(); if (pathname === "/v1/usage/summary") return "user.usage"; if (pathname === "/v1/billing/summary" || pathname === "/v1/redeem" || pathname === "/v1/subscription/summary" || pathname === "/v1/payments/summary") return "user.billing"; if (pathname.startsWith("/v1/admin/billing/")) return "admin.billing"; if (pathname === "/v1/skills" || pathname.startsWith("/v1/skills/")) return "system.skills"; if (pathname === "/v1/project-management" || pathname.startsWith("/v1/project-management/")) return "project.mdtodo"; if (pathname === "/v1/api-keys" || pathname === "/v1/api-keys/default" || pathname.startsWith("/v1/api-keys/")) return "user.apiKeys"; if (pathname === "/v1/users/me/profile" || pathname === "/v1/users/me/password") return "system.settings"; if (pathname === "/v1/workbench/debug/fake-sse" || pathname.startsWith("/v1/workbench/debug/fake-sse/") || pathname === "/v1/workbench/debug/kafka-sse" || pathname.startsWith("/v1/workbench/debug/kafka-sse/")) return "workbench.debug"; if (pathname === "/v1/workbench/events" || pathname === "/v1/workbench/projection-events" || pathname === "/v1/workbench/sync" || pathname === "/v1/workbench/launches" || pathname === "/v1/workbench/sessions" || pathname.startsWith("/v1/workbench/sessions/") || pathname.startsWith("/v1/workbench/turns/") || pathname.startsWith("/v1/workbench/traces/")) return "workbench.code"; if (pathname === "/v1/agent/chat" || pathname === "/v1/agent/sessions" || pathname.startsWith("/v1/agent/sessions/") || pathname === "/v1/agent/chat/inspect" || pathname.startsWith("/v1/agent/chat/result/") || pathname.startsWith("/v1/agent/turns/") || pathname.startsWith("/v1/agent/traces/") || pathname === "/v1/agent/chat/cancel" || pathname === "/v1/agent/chat/steer") return "workbench.code"; if (pathname === "/v1/admin/provider-profiles" || pathname.startsWith("/v1/admin/provider-profiles/")) return "admin.providerProfiles"; if (pathname === "/v1/admin/secrets" || pathname.startsWith("/v1/admin/secrets/")) return "admin.secrets"; if (pathname === "/v1/provider-profiles" && verb === "GET") return "workbench.code"; if (pathname.startsWith("/v1/admin/access")) return "admin.access"; if (pathname.startsWith("/v1/admin/users")) return "admin.users"; if (pathname.startsWith("/v1/admin/")) return "admin.access"; if (pathname === "/v1/hwpod-node-ops" || pathname === "/v1/caserun" || pathname.startsWith("/v1/caserun/") || pathname === "/v1/hwpod/specs") return "admin.hwpodGroups"; if (pathname === "/v1/web-performance/metrics" || pathname === "/v1/web-performance/summary" || pathname === "/v1/live-builds") return "system.performance"; if (pathname === "/v1/diagnostics/gate") return "system.gate"; return ""; } async function codeAgentOptions(request, response, options, authOptions = {}) { const auth = await options.accessController.authenticate(request, { required: authOptions.required ?? options.accessController.required }); if (!auth.ok) { sendJson(response, auth.status, auth); return null; } return { ...options, actor: auth.actor ?? null, authSession: auth.session ?? null, authApiKey: auth.apiKey ?? null, actorApiKeySecret: auth.apiKey?.displaySecret ?? null, userBillingAuth: auth.userBilling ?? null, userBillingToken: userBillingTokenFromAuth(auth), userBillingClient: options.userBillingClient ?? options.accessController?.userBilling ?? null }; } async function handleUsageSummaryHttp(request, response, url, options) { const route = url.pathname === "/v1/billing/summary" ? "/v1/billing/summary" : "/v1/usage/summary"; const nextOptions = await codeAgentOptions(request, response, options, { required: true }); if (!nextOptions) return; const client = nextOptions.userBillingClient; const token = nextOptions.userBillingToken; if (nextOptions.userBillingAuth?.active !== true || !token) { sendRestError(request, response, 403, "user_billing_auth_required", "usage summary requires a user-billing session or API key", { operation: `GET ${route}`, target: { type: "route", id: route }, result: "rejected" }); return; } if (!client?.configured || typeof client.billingSummary !== "function") { sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing summary is not configured", { operation: `GET ${route}`, target: { type: "service", id: "hwlab-user-billing" } }); return; } const limit = parsePositiveInteger(url.searchParams.get("limit"), 20); const result = await client.billingSummary(token, { limit: Math.min(limit, 100) }); if (!result.ok) { sendRestError(request, response, result.status ?? 502, result.error?.code ?? "user_billing_summary_failed", result.error?.message ?? "user-billing summary request failed", { operation: `GET ${route}`, target: { type: "service", id: "hwlab-user-billing" }, reason: result.error?.code, result: "failed" }); return; } sendJson(response, 200, { ...(result.body ?? {}), proxy: { serviceId: CLOUD_API_SERVICE_ID, route, source: "hwlab-user-billing", valuesRedacted: true } }); } async function handleAdminBillingSummaryHttp(request, response, url, options) { const auth = await options.accessController.authenticate(request, { required: true }); if (!auth.ok) { sendJson(response, auth.status, auth); return; } if (auth.actor?.role !== "admin") { sendRestError(request, response, 403, "admin_required", "Only admin users can read billing summaries", { operation: "GET /v1/admin/billing/summary", target: { type: "route", id: "/v1/admin/billing/summary" }, result: "rejected" }); return; } const client = options.userBillingClient ?? options.accessController?.userBilling ?? null; if (!client?.configured || typeof client.adminBillingSummary !== "function") { sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin summary is not configured", { operation: "GET /v1/admin/billing/summary", target: { type: "service", id: "hwlab-user-billing" } }); return; } const limit = parsePositiveInteger(url.searchParams.get("limit"), 50); const result = await client.adminBillingSummary({ limit: Math.min(limit, 100) }); if (!result.ok) { sendRestError(request, response, result.status ?? 502, result.error?.code ?? "user_billing_admin_summary_failed", result.error?.message ?? "user-billing admin summary request failed", { operation: "GET /v1/admin/billing/summary", target: { type: "service", id: "hwlab-user-billing" }, reason: result.error?.code, result: "failed" }); return; } sendJson(response, 200, { ...(result.body ?? {}), actor: adminActorSummary(auth.actor), proxy: { serviceId: CLOUD_API_SERVICE_ID, route: "/v1/admin/billing/summary", source: "hwlab-user-billing", valuesRedacted: true } }); } async function requireAdminBillingClient(request, response, options, operation) { const auth = await options.accessController.authenticate(request, { required: true }); if (!auth.ok) { sendJson(response, auth.status, auth); return null; } if (auth.actor?.role !== "admin") { sendRestError(request, response, 403, "admin_required", "Only admin users can call user billing admin APIs", { operation, target: { type: "route", id: operation }, result: "rejected" }); return null; } const client = options.userBillingClient ?? options.accessController?.userBilling ?? null; if (!client?.configured) { sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin API is not configured", { operation, target: { type: "service", id: "hwlab-user-billing" } }); return null; } return { auth, client }; } async function requireUserBillingClient(request, response, options, operation) { const nextOptions = await codeAgentOptions(request, response, options, { required: true }); if (!nextOptions) return null; const client = nextOptions.userBillingClient; const token = nextOptions.userBillingToken; if (nextOptions.userBillingAuth?.active !== true || !token) { sendRestError(request, response, 403, "user_billing_auth_required", "user-billing session or API key is required", { operation, target: { type: "route", id: operation }, result: "rejected" }); return null; } if (!client?.configured) { sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing API is not configured", { operation, target: { type: "service", id: "hwlab-user-billing" } }); return null; } return { auth: nextOptions, client, token }; } async function handleRedeemHttp(request, response, options) { const context = await requireUserBillingClient(request, response, options, "POST /v1/redeem"); if (!context) return; const { auth, client, token } = context; if (typeof client.redeem !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing redeem API is not configured", { operation: "POST /v1/redeem", target: { type: "service", id: "hwlab-user-billing" } }); } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) return sendJson(response, 400, parsed.error); const result = await client.redeem(token, parsed.value); sendUserBillingProxyResult({ request, response, result, operation: "POST /v1/redeem", route: "/v1/redeem", actor: adminActorSummary(auth.actor) }); } async function handleSubscriptionSummaryHttp(request, response, options) { const context = await requireUserBillingClient(request, response, options, "GET /v1/subscription/summary"); if (!context) return; const { auth, client, token } = context; if (typeof client.subscriptionSummary !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing subscription API is not configured", { operation: "GET /v1/subscription/summary", target: { type: "service", id: "hwlab-user-billing" } }); } const result = await client.subscriptionSummary(token); sendUserBillingProxyResult({ request, response, result, operation: "GET /v1/subscription/summary", route: "/v1/subscription/summary", actor: adminActorSummary(auth.actor) }); } async function handlePaymentSummaryHttp(request, response, options) { const context = await requireUserBillingClient(request, response, options, "GET /v1/payments/summary"); if (!context) return; const { auth, client, token } = context; if (typeof client.paymentSummary !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing payment API is not configured", { operation: "GET /v1/payments/summary", target: { type: "service", id: "hwlab-user-billing" } }); } const result = await client.paymentSummary(token); sendUserBillingProxyResult({ request, response, result, operation: "GET /v1/payments/summary", route: "/v1/payments/summary", actor: adminActorSummary(auth.actor) }); } async function handleAdminBillingUsersHttp(request, response, url, options) { const context = await requireAdminBillingClient(request, response, options, `${request.method} /v1/admin/billing/users`); if (!context) return; const { auth, client } = context; let result; if (request.method === "GET") { if (typeof client.adminUsers !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin users API is not configured", { operation: "GET /v1/admin/billing/users", target: { type: "service", id: "hwlab-user-billing" } }); } result = await client.adminUsers({ page: parsePositiveInteger(url.searchParams.get("page"), 1), pageSize: Math.min(parsePositiveInteger(url.searchParams.get("pageSize") || url.searchParams.get("limit"), 50), 100), search: url.searchParams.get("search") ?? "", status: url.searchParams.get("status") ?? "", role: url.searchParams.get("role") ?? "" }); } else { if (typeof client.createAdminUser !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin user create API is not configured", { operation: "POST /v1/admin/billing/users", target: { type: "service", id: "hwlab-user-billing" } }); } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) return sendJson(response, 400, parsed.error); result = await client.createAdminUser(parsed.value); } sendUserBillingProxyResult({ request, response, result, operation: `${request.method} /v1/admin/billing/users`, route: "/v1/admin/billing/users", actor: adminActorSummary(auth.actor) }); } async function handleAdminBillingPlansHttp(request, response, options) { const context = await requireAdminBillingClient(request, response, options, "GET /v1/admin/billing/plans"); if (!context) return; const { auth, client } = context; if (typeof client.adminBillingPlans !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin plans API is not configured", { operation: "GET /v1/admin/billing/plans", target: { type: "service", id: "hwlab-user-billing" } }); } const result = await client.adminBillingPlans(); sendUserBillingProxyResult({ request, response, result, operation: "GET /v1/admin/billing/plans", route: "/v1/admin/billing/plans", actor: adminActorSummary(auth.actor) }); } async function handleAdminBillingUsageHttp(request, response, url, options) { const context = await requireAdminBillingClient(request, response, options, "GET /v1/admin/billing/usage"); if (!context) return; const { auth, client } = context; if (typeof client.adminUsage !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin usage API is not configured", { operation: "GET /v1/admin/billing/usage", target: { type: "service", id: "hwlab-user-billing" } }); } const result = await client.adminUsage(adminAuditParams(url)); sendUserBillingProxyResult({ request, response, result, operation: "GET /v1/admin/billing/usage", route: "/v1/admin/billing/usage", actor: adminActorSummary(auth.actor) }); } async function handleAdminBillingUsageExportHttp(request, response, url, options) { const context = await requireAdminBillingClient(request, response, options, "GET /v1/admin/billing/usage/export"); if (!context) return; const { client } = context; if (typeof client.adminUsageExport !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin usage export API is not configured", { operation: "GET /v1/admin/billing/usage/export", target: { type: "service", id: "hwlab-user-billing" } }); } const result = await client.adminUsageExport(adminAuditParams(url)); sendUserBillingCsvProxyResult({ request, response, result, operation: "GET /v1/admin/billing/usage/export", filename: "hwlab-admin-usage.csv" }); } async function handleAdminBillingLedgerHttp(request, response, url, options) { const context = await requireAdminBillingClient(request, response, options, "GET /v1/admin/billing/ledger"); if (!context) return; const { auth, client } = context; if (typeof client.adminLedger !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin ledger API is not configured", { operation: "GET /v1/admin/billing/ledger", target: { type: "service", id: "hwlab-user-billing" } }); } const result = await client.adminLedger(adminAuditParams(url)); sendUserBillingProxyResult({ request, response, result, operation: "GET /v1/admin/billing/ledger", route: "/v1/admin/billing/ledger", actor: adminActorSummary(auth.actor) }); } async function handleAdminBillingLedgerExportHttp(request, response, url, options) { const context = await requireAdminBillingClient(request, response, options, "GET /v1/admin/billing/ledger/export"); if (!context) return; const { client } = context; if (typeof client.adminLedgerExport !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin ledger export API is not configured", { operation: "GET /v1/admin/billing/ledger/export", target: { type: "service", id: "hwlab-user-billing" } }); } const result = await client.adminLedgerExport(adminAuditParams(url)); sendUserBillingCsvProxyResult({ request, response, result, operation: "GET /v1/admin/billing/ledger/export", filename: "hwlab-admin-ledger.csv" }); } async function handleAdminBillingRedeemCodesHttp(request, response, url, options) { const context = await requireAdminBillingClient(request, response, options, `${request.method} /v1/admin/billing/redeem-codes`); if (!context) return; const { auth, client } = context; let result; if (request.method === "GET") { if (typeof client.adminRedeemCodes !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin redeem code API is not configured", { operation: "GET /v1/admin/billing/redeem-codes", target: { type: "service", id: "hwlab-user-billing" } }); } result = await client.adminRedeemCodes(adminRedeemParams(url)); } else { if (typeof client.createAdminRedeemCode !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin redeem code create API is not configured", { operation: "POST /v1/admin/billing/redeem-codes", target: { type: "service", id: "hwlab-user-billing" } }); } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) return sendJson(response, 400, parsed.error); result = await client.createAdminRedeemCode({ ...parsed.value, createdByUserId: String(auth.actor?.id ?? ""), createdByUsername: String(auth.actor?.username ?? ""), metadata: { ...(parsed.value?.metadata && typeof parsed.value.metadata === "object" && !Array.isArray(parsed.value.metadata) ? parsed.value.metadata : {}), operator: adminActorSummary(auth.actor) } }); } sendUserBillingProxyResult({ request, response, result, operation: `${request.method} /v1/admin/billing/redeem-codes`, route: "/v1/admin/billing/redeem-codes", actor: adminActorSummary(auth.actor) }); } async function handleAdminBillingRedeemCodeHttp(request, response, codeId, options) { const context = await requireAdminBillingClient(request, response, options, "PATCH /v1/admin/billing/redeem-codes/{codeId}"); if (!context) return; const { auth, client } = context; if (typeof client.updateAdminRedeemCode !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin redeem code update API is not configured", { operation: "PATCH /v1/admin/billing/redeem-codes/{codeId}", target: { type: "service", id: "hwlab-user-billing" } }); } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) return sendJson(response, 400, parsed.error); const result = await client.updateAdminRedeemCode(codeId, parsed.value); sendUserBillingProxyResult({ request, response, result, operation: "PATCH /v1/admin/billing/redeem-codes/{codeId}", route: "/v1/admin/billing/redeem-codes/{codeId}", actor: adminActorSummary(auth.actor) }); } async function handleAdminBillingRedeemRedemptionsHttp(request, response, url, options) { const context = await requireAdminBillingClient(request, response, options, "GET /v1/admin/billing/redeem-redemptions"); if (!context) return; const { auth, client } = context; if (typeof client.adminRedeemRedemptions !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin redeem redemptions API is not configured", { operation: "GET /v1/admin/billing/redeem-redemptions", target: { type: "service", id: "hwlab-user-billing" } }); } const result = await client.adminRedeemRedemptions(adminRedeemParams(url)); sendUserBillingProxyResult({ request, response, result, operation: "GET /v1/admin/billing/redeem-redemptions", route: "/v1/admin/billing/redeem-redemptions", actor: adminActorSummary(auth.actor) }); } async function handleAdminBillingUserHttp(request, response, url, userId, options) { const context = await requireAdminBillingClient(request, response, options, `${request.method} /v1/admin/billing/users/{userId}`); if (!context) return; const { auth, client } = context; let result; if (request.method === "GET") { if (typeof client.adminUser !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin user detail API is not configured", { operation: "GET /v1/admin/billing/users/{userId}", target: { type: "service", id: "hwlab-user-billing" } }); } result = await client.adminUser(userId); } else { if (typeof client.updateAdminUser !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin user update API is not configured", { operation: "PATCH /v1/admin/billing/users/{userId}", target: { type: "service", id: "hwlab-user-billing" } }); } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) return sendJson(response, 400, parsed.error); result = await client.updateAdminUser(userId, parsed.value); } sendUserBillingProxyResult({ request, response, result, operation: `${request.method} /v1/admin/billing/users/{userId}`, route: "/v1/admin/billing/users/{userId}", actor: adminActorSummary(auth.actor) }); } async function handleAdminBillingCreditAdjustHttp(request, response, userId, options) { const context = await requireAdminBillingClient(request, response, options, "POST /v1/admin/billing/users/{userId}/credits/adjust"); if (!context) return; const { auth, client } = context; if (typeof client.adjustAdminCredits !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin credit adjust API is not configured", { operation: "POST /v1/admin/billing/users/{userId}/credits/adjust", target: { type: "service", id: "hwlab-user-billing" } }); } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) return sendJson(response, 400, parsed.error); const result = await client.adjustAdminCredits({ ...parsed.value, userId, metadata: { ...(parsed.value?.metadata && typeof parsed.value.metadata === "object" && !Array.isArray(parsed.value.metadata) ? parsed.value.metadata : {}), operator: adminActorSummary(auth.actor) } }); sendUserBillingProxyResult({ request, response, result, operation: "POST /v1/admin/billing/users/{userId}/credits/adjust", route: "/v1/admin/billing/users/{userId}/credits/adjust", actor: adminActorSummary(auth.actor) }); } async function handleAdminBillingUserStatusHttp(request, response, userId, options) { const auth = await options.accessController.authenticate(request, { required: true }); if (!auth.ok) { sendJson(response, auth.status, auth); return; } if (auth.actor?.role !== "admin") { sendRestError(request, response, 403, "admin_required", "Only admin users can update user status", { operation: "PATCH /v1/admin/billing/users/{userId}/status", target: { type: "route", id: "/v1/admin/billing/users/{userId}/status" }, result: "rejected" }); return; } const client = options.userBillingClient ?? options.accessController?.userBilling ?? null; if (!client?.configured || typeof client.updateAdminUserStatus !== "function") { sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing admin user status API is not configured", { operation: "PATCH /v1/admin/billing/users/{userId}/status", target: { type: "service", id: "hwlab-user-billing" } }); return; } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) { sendJson(response, 400, parsed.error); return; } const status = String(parsed.value?.status ?? "").trim().toLowerCase(); if (!["active", "disabled", "pending"].includes(status)) { sendRestError(request, response, 400, "invalid_status", "status must be active, disabled or pending", { operation: "PATCH /v1/admin/billing/users/{userId}/status", target: { type: "user", id: userId }, result: "rejected" }); return; } const result = await client.updateAdminUserStatus(userId, status); if (!result.ok) { sendRestError(request, response, result.status ?? 502, result.error?.code ?? "user_billing_admin_user_status_failed", result.error?.message ?? "user-billing admin user status request failed", { operation: "PATCH /v1/admin/billing/users/{userId}/status", target: { type: "service", id: "hwlab-user-billing" }, reason: result.error?.code, result: "failed" }); return; } sendJson(response, 200, { ...(result.body ?? {}), actor: adminActorSummary(auth.actor), proxy: { serviceId: CLOUD_API_SERVICE_ID, route: "/v1/admin/billing/users/{userId}/status", source: "hwlab-user-billing", valuesRedacted: true }, valuesRedacted: true }); } async function maybeHandleUserBillingApiKeysHttp(request, response, url, options) { const client = options.userBillingClient ?? options.accessController?.userBilling ?? null; if (!client?.configured) return false; const auth = await options.accessController.authenticate(request, { required: true }); if (!auth.ok || !String(auth.authMethod ?? "").startsWith("user-billing")) return false; const token = userBillingTokenFromAuth(auth); if (!token) return false; if (url.pathname === "/v1/api-keys/default") { sendRestError(request, response, 404, "user_billing_default_key_unsupported", "user-billing API keys do not support legacy default-key regeneration", { operation: `${request.method} /v1/api-keys/default`, target: { type: "route", id: "/v1/api-keys/default" }, result: "rejected" }); return true; } let result = null; const path = url.pathname; if (request.method === "GET" && path === "/v1/api-keys" && typeof client.listApiKeys === "function") { result = await client.listApiKeys(token); } else if (request.method === "POST" && path === "/v1/api-keys" && typeof client.createApiKey === "function") { const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) { sendJson(response, 400, parsed.error); return true; } result = await client.createApiKey(token, parsed.value); } else { const match = path.match(/^\/v1\/api-keys\/([^/]+)$/u); if (match && request.method === "PATCH" && typeof client.updateApiKey === "function") { const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) { sendJson(response, 400, parsed.error); return true; } result = await client.updateApiKey(token, decodeURIComponent(match[1]), parsed.value); } else if (match && request.method === "DELETE" && typeof client.revokeApiKey === "function") { result = await client.revokeApiKey(token, decodeURIComponent(match[1])); } } if (!result) return false; sendUserBillingProxyResult({ request, response, result, operation: `${request.method} /v1/api-keys`, route: path, actor: adminActorSummary(auth.actor) }); return true; } async function handleUserBillingProfileHttp(request, response, options) { const client = options.userBillingClient ?? options.accessController?.userBilling ?? null; const auth = await options.accessController.authenticate(request, { required: true }); if (!auth.ok) return sendJson(response, auth.status, auth); const token = userBillingTokenFromAuth(auth); if (!String(auth.authMethod ?? "").startsWith("user-billing")) { return sendRestError(request, response, 403, "user_billing_auth_required", "profile update requires a user-billing bearer token", { operation: "PATCH /v1/users/me/profile", target: { type: "route", id: "/v1/users/me/profile" } }); } if (!token || !client?.configured || typeof client.updateProfile !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing profile API is not configured", { operation: "PATCH /v1/users/me/profile", target: { type: "service", id: "hwlab-user-billing" } }); } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) return sendJson(response, 400, parsed.error); const result = await client.updateProfile(token, parsed.value); sendUserBillingProxyResult({ request, response, result, operation: "PATCH /v1/users/me/profile", route: "/v1/users/me/profile", actor: adminActorSummary(auth.actor) }); } async function handleUserBillingPasswordHttp(request, response, options) { const client = options.userBillingClient ?? options.accessController?.userBilling ?? null; const auth = await options.accessController.authenticate(request, { required: true }); if (!auth.ok) return sendJson(response, auth.status, auth); const token = userBillingTokenFromAuth(auth); if (auth.authMethod !== "user-billing-session") { return sendRestError(request, response, 403, "user_billing_session_required", "password update requires a user-billing session token", { operation: "POST /v1/users/me/password", target: { type: "route", id: "/v1/users/me/password" } }); } if (!token || !client?.configured || typeof client.changePassword !== "function") { return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing password API is not configured", { operation: "POST /v1/users/me/password", target: { type: "service", id: "hwlab-user-billing" } }); } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) return sendJson(response, 400, parsed.error); const result = await client.changePassword(token, parsed.value); sendUserBillingProxyResult({ request, response, result, operation: "POST /v1/users/me/password", route: "/v1/users/me/password", actor: adminActorSummary(auth.actor) }); } function sendUserBillingProxyResult({ request, response, result, operation, route, actor = null }) { if (!result?.ok) { sendRestError(request, response, result?.status ?? 502, result?.error?.code ?? "user_billing_request_failed", result?.error?.message ?? "user-billing request failed", { operation, target: { type: "service", id: "hwlab-user-billing" }, reason: result?.error?.code, result: "failed" }); return; } sendJson(response, result.status ?? 200, { ...(result.body ?? {}), actor, proxy: { serviceId: CLOUD_API_SERVICE_ID, route, source: "hwlab-user-billing", valuesRedacted: true }, valuesRedacted: true }); } function sendUserBillingCsvProxyResult({ request, response, result, operation, filename }) { if (!result?.ok) { sendRestError(request, response, result?.status ?? 502, result?.error?.code ?? "user_billing_request_failed", result?.error?.message ?? "user-billing export request failed", { operation, target: { type: "service", id: "hwlab-user-billing" }, reason: result?.error?.code, result: "failed" }); return; } response.writeHead(result.status ?? 200, { "content-type": "text/csv; charset=utf-8", "cache-control": "no-store", "content-disposition": `attachment; filename="${filename}"` }); response.end(String(result.body ?? "")); } function adminAuditParams(url) { const params = {}; for (const key of ["page", "pageSize", "limit", "userId", "search", "serviceId", "resourceType", "apiKeyId", "status", "kind", "source", "idempotencyKey", "from", "to"]) { const value = String(url.searchParams.get(key) ?? "").trim(); if (value) params[key] = value; } return params; } function adminRedeemParams(url) { const params = {}; for (const key of ["limit", "status", "userId", "codeId"]) { const value = String(url.searchParams.get(key) ?? "").trim(); if (value) params[key] = value; } return params; } function adminActorSummary(actor) { return actor ? { id: String(actor.id ?? ""), username: String(actor.username ?? ""), displayName: String(actor.displayName ?? ""), role: String(actor.role ?? ""), status: String(actor.status ?? ""), valuesRedacted: true } : null; } function bearerTokenFromRequest(request) { const header = String(getHeader(request, "authorization") ?? "").trim(); return header.toLowerCase().startsWith("bearer ") ? header.slice(7).trim() : ""; } function userBillingTokenFromRequest(request) { const bearer = bearerTokenFromRequest(request); if (bearer) return bearer; const cookieToken = cookieValueFromRequest(request, HWLAB_WEB_SESSION_COOKIE); return cookieToken.startsWith("hws_") ? cookieToken : ""; } function userBillingTokenFromAuth(auth) { const token = String(auth?.userBilling?.token ?? "").trim(); return token ? token : ""; } function cookieValueFromRequest(request, name) { const target = String(name ?? ""); if (!target) return ""; for (const part of String(getHeader(request, "cookie") ?? "").split(";")) { const trimmed = part.trim(); const index = trimmed.indexOf("="); if (index <= 0) continue; if (trimmed.slice(0, index) !== target) continue; try { return decodeURIComponent(trimmed.slice(index + 1)); } catch { return trimmed.slice(index + 1); } } return ""; } async function readJsonObject(request, limitBytes) { const body = await readBody(request, limitBytes); try { const value = body ? JSON.parse(body) : {}; if (!value || typeof value !== "object" || Array.isArray(value)) { return { ok: false, error: { accepted: false, error: { code: "invalid_params", message: "body must be a JSON object" } } }; } return { ok: true, value }; } catch (error) { return { ok: false, error: { accepted: false, error: { code: "parse_error", message: "Invalid JSON body", reason: error.message } } }; } } 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: runtimeEnvironment(env) }; 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. } } function sendRestError(request, response, statusCode, code, message, context = {}) { const requestId = request.hwlabHttpRequestContext?.requestId || getHeader(request, "x-request-id") || "req_unassigned"; const environment = context.environment || runtimeEnvironment(context.env ?? process.env); 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, 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) { if (response.headersSent || response.writableEnded) return false; const payload = decorateErrorPayloadForHttp(body, { response, statusCode }); logErrorEnvelope(payload, { statusCode }); response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" }); response.end(`${JSON.stringify(payload)}\n`); return true; } function getHeader(request, name) { const value = request.headers[name.toLowerCase()]; if (Array.isArray(value)) { return value[0]; } return value; } function safeRequestId(value) { const text = String(value ?? "").trim(); return /^[A-Za-z0-9_.:-]{3,180}$/u.test(text) ? text : null; } function firstHeaderValue(request, name) { return getHeader(request, name); } function truthyFlag(value) { return /^(?:1|true|yes|on|enabled)$/iu.test(String(value ?? "").trim()); } 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 safeOpaqueId(value) { const text = String(value ?? "").trim(); return /^[A-Za-z0-9_.:-]{3,180}$/u.test(text) ? text : null; } function isAuthorizedWorkbenchDrainRequest(request, env = process.env) { const expected = String(env.HOSTNAME ?? "").trim(); if (!expected) return false; return getHeader(request, "x-hwlab-drain-hostname") === expected; } function parsePositiveInteger(value, fallback) { const parsed = Number.parseInt(value ?? "", 10); return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; } function positiveInteger(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(); }