// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0. // SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-25-p0-web-probe-sentinel. // SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-26-p9-multi-web-probe-sentinel. // SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets. // Responsibility: YAML source-of-truth parsing for HWLAB node/lane Workbench observability. import { readFileSync } from "node:fs"; import { rootPath } from "./config"; export type HwlabRuntimeLane = string; export interface HwlabRuntimeNodeSpec { readonly id: string; readonly route: string; readonly kubeRoute: string; readonly sourceWorkspace: string; readonly gitopsRoot: string; readonly networkProfileId: string; readonly downloadProfileId: string; } export interface HwlabProxySpec { readonly http: string; readonly https: string; readonly all: string; readonly socks5?: string; readonly noProxy: readonly string[]; } export interface HwlabNetworkProfileSpec { readonly id: string; readonly proxy: HwlabProxySpec; readonly dockerBuildProxy: HwlabProxySpec; } export interface HwlabDownloadProfileSpec { readonly id: string; readonly git: { readonly proxyMode: "inherit" | "direct" | "none"; readonly retries: number; readonly timeoutSeconds: number; }; readonly npm: { readonly registry: string; readonly retries: number; readonly fetchTimeoutSeconds: number; }; readonly pip: { readonly indexUrl: string; readonly retries: number; readonly timeoutSeconds: number; }; readonly docker: { readonly registryMirrors: readonly string[]; readonly pullRetries: number; }; readonly curl: { readonly retries: number; readonly connectTimeoutSeconds: number; readonly maxTimeSeconds: number; }; } export interface HwlabRuntimeExternalPostgresComponentSpec { readonly secretName: string; readonly secretKey: string; readonly sourceRef: string; readonly envKey: string; readonly role: string; readonly authnKey?: string; readonly schema?: string; } export interface HwlabRuntimeExternalPostgresRuntimeAccessSpec { readonly routeName?: string; readonly endpointAddress: string; readonly port: number; } export interface HwlabRuntimeExternalPostgresSpec { readonly provider: string; readonly configRef: string; readonly serviceName: string; readonly endpointAddress: string; readonly port: number; readonly runtimeAccess?: HwlabRuntimeExternalPostgresRuntimeAccessSpec; readonly sslmode: "require"; readonly database: string; readonly cloudApi: HwlabRuntimeExternalPostgresComponentSpec; readonly openfga: HwlabRuntimeExternalPostgresComponentSpec; } export interface HwlabRuntimePostgresStoreSpec { readonly mode?: "local-k3s" | "platform-service"; readonly secretName?: string; readonly statefulSet?: string; readonly serviceName?: string; readonly adminUser?: string; readonly adminPasswordSourceRef?: string; readonly adminPasswordSourceKey?: string; readonly cloudApi?: HwlabRuntimePostgresStoreComponentSpec; readonly openfga?: HwlabRuntimePostgresStoreComponentSpec; readonly poolMax: number; readonly connectionTimeoutMs?: number; readonly queryRetryMaxAttempts?: number; readonly queryRetryInitialDelayMs?: number; readonly queryRetryMaxDelayMs?: number; } export interface HwlabRuntimePostgresStoreComponentSpec { readonly secretName: string; readonly secretKey: string; readonly database: string; readonly role: string; readonly authnKey?: string; readonly postgresPasswordKey?: string; } export interface HwlabRuntimeStoreSpec { readonly postgres?: HwlabRuntimePostgresStoreSpec; } export interface HwlabRuntimeWebProbeServiceOriginSpec { readonly mode: "k8s-service-cluster-ip"; readonly serviceName: string; readonly namespace: string; readonly port: number; readonly scheme: "http" | "https"; } export interface HwlabRuntimeWebProbePublicOriginSpec { readonly mode: "public"; readonly baseUrl?: string; } export type HwlabRuntimeWebProbeOriginSpec = HwlabRuntimeWebProbeServiceOriginSpec | HwlabRuntimeWebProbePublicOriginSpec; export interface HwlabRuntimeWebProbeSpec { readonly browserProxyMode?: "auto" | "direct"; readonly playwrightBrowsersPath?: string; readonly defaultOrigin?: HwlabRuntimeWebProbeOriginSpec; readonly authLogin?: HwlabRuntimeWebProbeAuthLoginSpec; readonly alertThresholds?: HwlabRuntimeWebProbeAlertThresholdsSpec; readonly projectManagement?: HwlabRuntimeWebProbeProjectManagementSpec; } export interface HwlabRuntimeWebProbeAuthLoginSpec { readonly maxAttempts: number; readonly requestTimeoutMs: number; readonly initialDelayMs: number; readonly maxDelayMs: number; } export type HwlabRuntimeWebProbeSentinelConfigRefKey = "runtime" | "scenarios" | "promptSet" | "reportViews" | "publicExposure" | "cicd" | "secrets"; export const HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS = ["runtime", "scenarios", "promptSet", "reportViews", "publicExposure", "cicd", "secrets"] as const satisfies readonly HwlabRuntimeWebProbeSentinelConfigRefKey[]; export interface HwlabRuntimeWebProbeSentinelSpec { readonly enabled: boolean; readonly configRefs: Record; } export interface HwlabRuntimeWebProbeSentinelRegistryItemSpec { readonly id: string; readonly enabled: boolean; readonly configRef: string; } export interface HwlabRuntimeWebProbeMonitorRootSpec { readonly enabled: boolean; readonly sentinelId: string; readonly publicBaseUrl: string; readonly routePrefix: "/"; readonly caddyManagedBlockOwner: string; } export interface HwlabRuntimeWebProbeAlertThresholdsSpec { readonly sameOriginApiSlowMs: number; readonly partialApiSlowMs: number; readonly longLivedStreamOpenSlowMs: number; readonly visibleLoadingSlowMs: number; readonly turnTimingSampleSlackSeconds: number; readonly turnElapsedSevereTimeoutSeconds: number; readonly uncommandedStateChangeCommandWindowMs: number; readonly scrollJumpCommandWindowMs: number; readonly scrollJumpFromY: number; readonly scrollJumpToY: number; readonly sessionRailFallbackRatio: number; } export interface HwlabRuntimeWebProbeProjectManagementSpec { readonly enabled: boolean; readonly targetPaths: readonly string[]; readonly readinessSelectors: readonly string[]; readonly naturalApiPathPrefixes: readonly string[]; readonly commandAllowlist: readonly string[]; readonly launchRoute: string; readonly slowApiBudgetMs: number; } export interface HwlabRuntimeBuildkitSpec { readonly sidecarImage: string; readonly sourceImage: string; } export interface HwlabRuntimeObservabilitySpec { readonly prometheusOperator: boolean; readonly traceExplorerUrlTemplate?: string; readonly metricsEndpoint?: HwlabRuntimeObservabilityMetricsEndpointSpec; readonly workbench?: HwlabRuntimeObservabilityWorkbenchSpec; readonly webProbe?: HwlabRuntimeObservabilityWebProbeSpec; readonly recordingRules: readonly HwlabRuntimeObservabilityRecordingRuleSpec[]; readonly warningAlerts: readonly HwlabRuntimeObservabilityWarningAlertSpec[]; } export interface HwlabRuntimeObservabilityWebProbeSpec { readonly sentinel?: HwlabRuntimeWebProbeSentinelSpec; readonly sentinels?: readonly HwlabRuntimeWebProbeSentinelRegistryItemSpec[]; readonly monitorRoot?: HwlabRuntimeWebProbeMonitorRootSpec; } export interface HwlabRuntimeObservabilityMetricsEndpointSpec { readonly serviceName: string; readonly containerName: string; readonly port: number; readonly scheme: "http"; readonly path: string; readonly scrapeMode: "pod-loopback"; readonly publicRawMetrics: "denied"; } export interface HwlabRuntimeObservabilityWorkbenchSpec { readonly enabled: boolean; readonly summaryPath: string; readonly lowSampleThreshold: number; readonly metricPrefixes: readonly string[]; readonly requiredSeries: readonly string[]; readonly backendLabelDenylist: readonly string[]; readonly maxUnknownEventLines: number; } export interface HwlabRuntimeObservabilityRecordingRuleSpec { readonly id: string; readonly metric: string; readonly sourceMetric: string; readonly quantile: number; readonly window: string; readonly minSamples: number; readonly groupBy: readonly string[]; readonly matchLabels: Record; } export interface HwlabRuntimeObservabilityWarningAlertSpec { readonly id: string; readonly ruleId: string; readonly severity: "warning"; readonly thresholdSeconds: number; readonly minSamples: number; readonly for: string; readonly matchLabels: Record; } export interface HwlabRuntimeImageRewriteSpec { readonly source: string; readonly target: string; } export interface HwlabRuntimeImageBuildSpec { readonly id: string; readonly kind: "moonbridge"; readonly target: string; readonly sourceRepo: string; readonly sourceRef: string; readonly builderImage: string; readonly goProxy: string; readonly dockerNetworkMode: "default" | "host"; } export interface HwlabRuntimePublicExposureFrpcProxySpec { readonly name: string; readonly localIP: string; readonly localPort: number; readonly remotePort: number; } export interface HwlabRuntimePublicExposureFrpcExtraProxySpec extends HwlabRuntimePublicExposureFrpcProxySpec { readonly id: string; readonly hostname?: string; readonly publicBaseUrl?: string; readonly cloudWebEnvName?: string; } export interface HwlabRuntimePublicExposureSpec { readonly enabled: boolean; readonly mode: "pk01-caddy-frp"; readonly publicBaseUrl: string; readonly hostname: string; readonly expectedA: string; readonly serverAddr: string; readonly serverPort: number; readonly tokenSourceRef: string; readonly tokenSourceKey: string; readonly secretName: string; readonly secretKey: string; readonly tokenKey: string; readonly caddyRoute: string; readonly caddyConfigPath: string; readonly caddyServiceName: string; readonly caddyEmail: string; readonly caddyTls: "auto" | "internal"; readonly responseHeaderTimeoutSeconds: number; readonly webProxy: HwlabRuntimePublicExposureFrpcProxySpec; readonly apiProxy: HwlabRuntimePublicExposureFrpcProxySpec; readonly extraProxies: readonly HwlabRuntimePublicExposureFrpcExtraProxySpec[]; } export interface HwlabRuntimeBootstrapAdminSpec { readonly username: string; readonly displayName: string; readonly usernameSourceRef?: string; readonly usernameSourceKey?: string; readonly usernameSourceLine?: number; readonly passwordSourceRef: string; readonly passwordSourceKey: string; readonly passwordSourceLine?: number; readonly passwordHashTransform: "hwlab-sha256"; readonly secretName: string; readonly secretKey: string; readonly rolloutDeployment: string; } export interface HwlabRuntimeCodeAgentProviderSpec { readonly secretName: string; readonly sourceRef: string; readonly openaiSourceKey?: string; readonly opencodeSourceKey?: string; } export interface HwlabRuntimeKafkaShadowProducerSpec { readonly enabled: boolean; readonly mode: "shadow-produce-only"; readonly consumeEnabled: false; readonly configRef: string; readonly bootstrapServers: string; readonly commandTopic: string; readonly clientId: string; } export interface HwlabRuntimeCodeAgentRuntimeSpec { readonly enabled: boolean; readonly adapter: string; readonly managerUrl: string; readonly apiKeySecretName: string; readonly apiKeySecretKey: string; readonly runnerNamespace: string; readonly secretNamespace: string; readonly repoUrlFrom: "runtimeGitReadUrl"; readonly providerIdFrom: "runtimeNodeId"; readonly defaultProviderProfile: string; readonly codexStdioSupervisor: "repo-owned"; readonly kafkaShadowProducer?: HwlabRuntimeKafkaShadowProducerSpec; } export interface HwlabRuntimeSourceWorkspaceSpec { readonly git?: HwlabRuntimeSourceWorkspaceGitSpec; readonly requiredCommands: readonly string[]; readonly requiredFiles: readonly string[]; readonly hostDependencies?: HwlabRuntimeSourceWorkspaceHostDependenciesSpec; readonly install: { readonly executor: "k3s-job" | "host"; readonly dependencyCommand: string; readonly browserCommand: string; readonly stateDir?: string; readonly timeoutSeconds: number; }; } export interface HwlabRuntimeSourceWorkspaceGitSpec { readonly remoteName: string; readonly remoteUrl: string; readonly identityTarget?: string; readonly identityId?: string; readonly proxyEnvPath?: string; readonly verifyRemote: boolean; readonly requireUpToDate: boolean; } export interface HwlabRuntimeSourceWorkspaceHostDependenciesSpec { readonly checkCommands: readonly string[]; readonly stateDir: string; readonly install: { readonly command: string; readonly timeoutSeconds: number; }; } export interface HwlabRuntimeLaneSpec { readonly lane: HwlabRuntimeLane; readonly nodeId: string; readonly nodeRoute: string; readonly nodeKubeRoute: string; readonly gitopsRoot: string; readonly minor: number; readonly version: string; readonly sourceBranch: string; readonly workspace: string; readonly cicdRepo: string; readonly cicdRepoLock: string; readonly app: string; readonly pipeline: string; readonly pipelineRunPrefix: string; readonly serviceAccountName: string; readonly controlPlaneFieldManager: string; readonly gitUrl: string; readonly gitReadUrl: string; readonly gitWriteUrl: string; readonly argoRepoUrl: string; readonly gitopsBranch: string; readonly catalogPath: string; readonly runtimePath: string; readonly runtimeNamespace: string; readonly runtimeRenderDir: string; readonly tektonDir: string; readonly argoApplicationFile: string; readonly registryPrefix: string; readonly baseImage: string; readonly baseImageSource?: string; readonly serviceIds: readonly string[]; readonly publicWebUrl: string; readonly publicApiUrl: string; readonly stepEnv: Record; readonly buildkit?: HwlabRuntimeBuildkitSpec; readonly bootstrapAdmin?: HwlabRuntimeBootstrapAdminSpec; readonly codeAgentProvider?: HwlabRuntimeCodeAgentProviderSpec; readonly codeAgentRuntime?: HwlabRuntimeCodeAgentRuntimeSpec; readonly sourceWorkspace?: HwlabRuntimeSourceWorkspaceSpec; readonly externalPostgres?: HwlabRuntimeExternalPostgresSpec; readonly runtimeStore?: HwlabRuntimeStoreSpec; readonly webProbe?: HwlabRuntimeWebProbeSpec; readonly publicExposure: HwlabRuntimePublicExposureSpec | null; readonly observability: HwlabRuntimeObservabilitySpec; readonly runtimeImageRewrites: readonly HwlabRuntimeImageRewriteSpec[]; readonly runtimeImageBuilds: readonly HwlabRuntimeImageBuildSpec[]; readonly networkProfileId: string; readonly downloadProfileId: string; readonly networkProfile: HwlabNetworkProfileSpec; readonly downloadProfile: HwlabDownloadProfileSpec; } export function hwlabRuntimeActiveExternalPostgres(spec: HwlabRuntimeLaneSpec): HwlabRuntimeExternalPostgresSpec | undefined { return spec.runtimeStore?.postgres?.mode === "platform-service" ? spec.externalPostgres : undefined; } export const HWLAB_NODE_LANE_CONFIG_PATH = "config/hwlab-node-lanes.yaml"; interface HwlabLaneConfig { readonly id: HwlabRuntimeLane; readonly node: string; readonly activeTarget?: string; readonly minor: number; readonly version: string; readonly sourceBranch: string; readonly workspace: string; readonly cicdRepo: string; readonly cicdRepoLock: string; readonly app: string; readonly pipeline: string; readonly pipelineRunPrefix: string; readonly serviceAccountName: string; readonly controlPlaneFieldManager: string; readonly git: { readonly url: string; readonly readUrl: string; readonly writeUrl: string }; readonly argo: { readonly repoURL?: string }; readonly gitopsBranch: string; readonly catalogPath: string; readonly runtime: { readonly path: string; readonly namespace: string; readonly renderDir: string }; readonly tektonDir: string; readonly argoApplicationFile: string; readonly registryPrefix: string; readonly baseImage: string; readonly baseImageSource?: string; readonly serviceIds: readonly string[]; readonly public: { readonly webUrl: string; readonly apiUrl: string }; readonly stepEnv: Record; readonly buildkit?: HwlabRuntimeBuildkitSpec; readonly bootstrapAdmin?: HwlabRuntimeBootstrapAdminSpec; readonly codeAgentProvider?: HwlabRuntimeCodeAgentProviderSpec; readonly codeAgentRuntime?: HwlabRuntimeCodeAgentRuntimeSpec; readonly sourceWorkspace?: HwlabRuntimeSourceWorkspaceSpec; readonly externalPostgres?: HwlabRuntimeExternalPostgresSpec; readonly runtimeStore?: HwlabRuntimeStoreSpec; readonly webProbe?: HwlabRuntimeWebProbeSpec; readonly publicExposure: HwlabRuntimePublicExposureSpec | null; readonly observability: HwlabRuntimeObservabilitySpec; readonly runtimeImageRewrites: readonly HwlabRuntimeImageRewriteSpec[]; readonly runtimeImageBuilds: readonly HwlabRuntimeImageBuildSpec[]; } interface HwlabNodeLaneConfig { readonly defaultTarget: { readonly node: string; readonly lane: HwlabRuntimeLane; }; readonly requiredNoProxy: readonly string[]; readonly nodes: Record; readonly lanes: Record; readonly laneTargets: Partial>>; readonly networkProfiles: Record; readonly downloadProfiles: Record; } function asRecord(value: unknown, path: string): Record { if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be an object`); return value as Record; } function stringField(obj: Record, key: string, path: string): string { const value = obj[key]; if (typeof value !== "string" || value.length === 0) throw new Error(`${path}.${key} must be a non-empty string`); return value; } function enumStringField(obj: Record, key: string, path: string, allowed: readonly T[]): T { const value = stringField(obj, key, path); if (!allowed.includes(value as T)) throw new Error(`${path}.${key} must be one of ${allowed.join(", ")}`); return value as T; } function numberField(obj: Record, key: string, path: string): number { const value = obj[key]; if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`${path}.${key} must be a positive integer`); return value; } function stringArrayField(obj: Record, key: string, path: string): string[] { const value = obj[key]; if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.length === 0)) { throw new Error(`${path}.${key} must be an array of non-empty strings`); } return [...value] as string[]; } function nonEmptyStringArrayField(obj: Record, key: string, path: string): string[] { const values = stringArrayField(obj, key, path); if (values.length === 0) throw new Error(`${path}.${key} must contain at least one string`); return values; } function optionalStringField(obj: Record, key: string, path: string): string | undefined { const value = obj[key]; if (value === undefined) return undefined; if (typeof value !== "string" || value.length === 0) throw new Error(`${path}.${key} must be a non-empty string`); return value; } function booleanField(obj: Record, key: string, path: string): boolean { const value = obj[key]; if (typeof value !== "boolean") throw new Error(`${path}.${key} must be a boolean`); return value; } function nonNegativeIntegerField(obj: Record, key: string, path: string): number { const value = obj[key]; if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throw new Error(`${path}.${key} must be a non-negative integer`); return value; } function positiveNumberField(obj: Record, key: string, path: string): number { const value = obj[key]; if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new Error(`${path}.${key} must be a positive number`); return value; } function boundedIntegerField(obj: Record, key: string, path: string, min: number, max: number): number { const value = obj[key]; if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) { throw new Error(`${path}.${key} must be an integer between ${min} and ${max}`); } return value; } function sortedRecordEntries(value: unknown, path: string): Array<[string, Record]> { return Object.entries(asRecord(value, path)).map(([key, item]) => [key, asRecord(item, `${path}.${key}`)]); } function unique(values: readonly string[]): string[] { return [...new Set(values)]; } function mergeOptionalRecord(base: unknown, override: unknown): Record | undefined { if (base === undefined && override === undefined) return undefined; const baseRecord = base === undefined ? {} : asRecord(base, "base"); const overrideRecord = override === undefined ? {} : asRecord(override, "override"); return { ...baseRecord, ...overrideRecord }; } function optionalStringRecord(value: unknown, path: string): Record { if (value === undefined) return {}; const raw = asRecord(value, path); return Object.fromEntries(Object.entries(raw).map(([key, item]) => { if (typeof item !== "string" || item.length === 0) throw new Error(`${path}.${key} must be a non-empty string`); return [key, item]; })); } function proxyConfig(raw: Record, id: string, key: "proxy" | "dockerBuildProxy", requiredNoProxy: readonly string[]): HwlabProxySpec { const proxy = asRecord(raw[key], `networkProfiles.${id}.${key}`); return { http: stringField(proxy, "http", `networkProfiles.${id}.${key}`), https: stringField(proxy, "https", `networkProfiles.${id}.${key}`), all: stringField(proxy, "all", `networkProfiles.${id}.${key}`), socks5: optionalStringField(proxy, "socks5", `networkProfiles.${id}.${key}`), noProxy: unique([...stringArrayField(proxy, "noProxy", `networkProfiles.${id}.${key}`), ...requiredNoProxy]), }; } function networkProfileConfig(id: string, raw: Record, requiredNoProxy: readonly string[]): HwlabNetworkProfileSpec { return { id, proxy: proxyConfig(raw, id, "proxy", requiredNoProxy), dockerBuildProxy: proxyConfig(raw, id, "dockerBuildProxy", requiredNoProxy), }; } function downloadProfileConfig(id: string, raw: Record): HwlabDownloadProfileSpec { const git = asRecord(raw.git, `downloadProfiles.${id}.git`); const npm = asRecord(raw.npm, `downloadProfiles.${id}.npm`); const pip = asRecord(raw.pip, `downloadProfiles.${id}.pip`); const docker = asRecord(raw.docker, `downloadProfiles.${id}.docker`); const curl = asRecord(raw.curl, `downloadProfiles.${id}.curl`); const proxyMode = stringField(git, "proxyMode", `downloadProfiles.${id}.git`); if (proxyMode !== "inherit" && proxyMode !== "direct" && proxyMode !== "none") { throw new Error(`downloadProfiles.${id}.git.proxyMode must be inherit, direct, or none`); } return { id, git: { proxyMode, retries: numberField(git, "retries", `downloadProfiles.${id}.git`), timeoutSeconds: numberField(git, "timeoutSeconds", `downloadProfiles.${id}.git`), }, npm: { registry: stringField(npm, "registry", `downloadProfiles.${id}.npm`), retries: numberField(npm, "retries", `downloadProfiles.${id}.npm`), fetchTimeoutSeconds: numberField(npm, "fetchTimeoutSeconds", `downloadProfiles.${id}.npm`), }, pip: { indexUrl: stringField(pip, "indexUrl", `downloadProfiles.${id}.pip`), retries: numberField(pip, "retries", `downloadProfiles.${id}.pip`), timeoutSeconds: numberField(pip, "timeoutSeconds", `downloadProfiles.${id}.pip`), }, docker: { registryMirrors: stringArrayField(docker, "registryMirrors", `downloadProfiles.${id}.docker`), pullRetries: numberField(docker, "pullRetries", `downloadProfiles.${id}.docker`), }, curl: { retries: numberField(curl, "retries", `downloadProfiles.${id}.curl`), connectTimeoutSeconds: numberField(curl, "connectTimeoutSeconds", `downloadProfiles.${id}.curl`), maxTimeSeconds: numberField(curl, "maxTimeSeconds", `downloadProfiles.${id}.curl`), }, }; } function nodeConfig(id: string, raw: Record): HwlabRuntimeNodeSpec { return { id, route: stringField(raw, "route", `nodes.${id}`), kubeRoute: stringField(raw, "kubeRoute", `nodes.${id}`), sourceWorkspace: stringField(raw, "sourceWorkspace", `nodes.${id}`), gitopsRoot: stringField(raw, "gitopsRoot", `nodes.${id}`), networkProfileId: stringField(raw, "networkProfile", `nodes.${id}`), downloadProfileId: stringField(raw, "downloadProfile", `nodes.${id}`), }; } function assertYamlDeclaredLaneId(id: string, path: string): asserts id is HwlabRuntimeLane { if (!/^[A-Za-z0-9._-]+$/u.test(id)) throw new Error(`${path} has an unsupported lane id; lane ids must be YAML-declared simple ids`); } function laneConfig(id: HwlabRuntimeLane, raw: Record): HwlabLaneConfig { const git = asRecord(raw.git, `lanes.${id}.git`); const argo = raw.argo === undefined ? {} : asRecord(raw.argo, `lanes.${id}.argo`); const runtime = asRecord(raw.runtime, `lanes.${id}.runtime`); const publicUrls = asRecord(raw.public, `lanes.${id}.public`); const minor = numberField(raw, "minor", `lanes.${id}`); const version = stringField(raw, "version", `lanes.${id}`); if (version !== `v0.${minor}`) throw new Error(`lanes.${id}.version must equal v0.${minor}`); return { id, node: stringField(raw, "node", `lanes.${id}`), activeTarget: optionalStringField(raw, "activeTarget", `lanes.${id}`), minor, version, sourceBranch: stringField(raw, "sourceBranch", `lanes.${id}`), workspace: stringField(raw, "workspace", `lanes.${id}`), cicdRepo: stringField(raw, "cicdRepo", `lanes.${id}`), cicdRepoLock: stringField(raw, "cicdRepoLock", `lanes.${id}`), app: stringField(raw, "app", `lanes.${id}`), pipeline: stringField(raw, "pipeline", `lanes.${id}`), pipelineRunPrefix: stringField(raw, "pipelineRunPrefix", `lanes.${id}`), serviceAccountName: stringField(raw, "serviceAccountName", `lanes.${id}`), controlPlaneFieldManager: stringField(raw, "controlPlaneFieldManager", `lanes.${id}`), git: { url: stringField(git, "url", `lanes.${id}.git`), readUrl: stringField(git, "readUrl", `lanes.${id}.git`), writeUrl: stringField(git, "writeUrl", `lanes.${id}.git`), }, argo: { repoURL: optionalStringField(argo, "repoURL", `lanes.${id}.argo`), }, gitopsBranch: stringField(raw, "gitopsBranch", `lanes.${id}`), catalogPath: stringField(raw, "catalogPath", `lanes.${id}`), runtime: { path: stringField(runtime, "path", `lanes.${id}.runtime`), namespace: stringField(runtime, "namespace", `lanes.${id}.runtime`), renderDir: stringField(runtime, "renderDir", `lanes.${id}.runtime`), }, tektonDir: stringField(raw, "tektonDir", `lanes.${id}`), argoApplicationFile: stringField(raw, "argoApplicationFile", `lanes.${id}`), registryPrefix: stringField(raw, "registryPrefix", `lanes.${id}`), baseImage: stringField(raw, "baseImage", `lanes.${id}`), baseImageSource: optionalStringField(raw, "baseImageSource", `lanes.${id}`), serviceIds: stringArrayField(raw, "serviceIds", `lanes.${id}`), public: { webUrl: stringField(publicUrls, "webUrl", `lanes.${id}.public`), apiUrl: stringField(publicUrls, "apiUrl", `lanes.${id}.public`), }, stepEnv: optionalStringRecord(raw.stepEnv, `lanes.${id}.stepEnv`), buildkit: buildkitConfig(raw.buildkit, `lanes.${id}.buildkit`), bootstrapAdmin: bootstrapAdminConfig(raw.bootstrapAdmin, `lanes.${id}.bootstrapAdmin`), codeAgentProvider: codeAgentProviderConfig(raw.codeAgentProvider, `lanes.${id}.codeAgentProvider`), codeAgentRuntime: codeAgentRuntimeConfig(raw.codeAgentRuntime, `lanes.${id}.codeAgentRuntime`), sourceWorkspace: sourceWorkspaceConfig(raw.sourceWorkspace, `lanes.${id}.sourceWorkspace`), externalPostgres: externalPostgresConfig(raw.externalPostgres, `lanes.${id}.externalPostgres`), runtimeStore: runtimeStoreConfig(raw.runtimeStore, `lanes.${id}.runtimeStore`), webProbe: webProbeConfig(raw.webProbe, `lanes.${id}.webProbe`), publicExposure: publicExposureConfig(raw.publicExposure, `lanes.${id}.publicExposure`), observability: observabilityConfig(raw.observability, `lanes.${id}.observability`), runtimeImageRewrites: runtimeImageRewritesConfig(raw.runtimeImageRewrites, `lanes.${id}.runtimeImageRewrites`), runtimeImageBuilds: runtimeImageBuildsConfig(raw.runtimeImageBuilds, `lanes.${id}.runtimeImageBuilds`), }; } function laneTargetConfig(id: HwlabRuntimeLane, nodeId: string, baseRaw: Record, targetRaw: Record): HwlabLaneConfig { const merged: Record = { ...baseRaw, ...targetRaw, node: nodeId, git: mergeOptionalRecord(baseRaw.git, targetRaw.git), argo: mergeOptionalRecord(baseRaw.argo, targetRaw.argo), runtime: mergeOptionalRecord(baseRaw.runtime, targetRaw.runtime), public: mergeOptionalRecord(baseRaw.public, targetRaw.public), stepEnv: mergeOptionalRecord(baseRaw.stepEnv, targetRaw.stepEnv) ?? {}, buildkit: mergeOptionalRecord(baseRaw.buildkit, targetRaw.buildkit), bootstrapAdmin: mergeOptionalRecord(baseRaw.bootstrapAdmin, targetRaw.bootstrapAdmin), codeAgentProvider: mergeOptionalRecord(baseRaw.codeAgentProvider, targetRaw.codeAgentProvider), codeAgentRuntime: mergeOptionalRecord(baseRaw.codeAgentRuntime, targetRaw.codeAgentRuntime), sourceWorkspace: mergeOptionalRecord(baseRaw.sourceWorkspace, targetRaw.sourceWorkspace), externalPostgres: mergeOptionalRecord(baseRaw.externalPostgres, targetRaw.externalPostgres), runtimeStore: mergeOptionalRecord(baseRaw.runtimeStore, targetRaw.runtimeStore), webProbe: mergeOptionalRecord(baseRaw.webProbe, targetRaw.webProbe), publicExposure: mergeOptionalRecord(baseRaw.publicExposure, targetRaw.publicExposure), observability: mergeOptionalRecord(baseRaw.observability, targetRaw.observability), }; delete merged.targets; delete merged.activeTarget; return laneConfig(id, merged); } function buildkitConfig(value: unknown, path: string): HwlabRuntimeBuildkitSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); return { sidecarImage: stringField(raw, "sidecarImage", path), sourceImage: stringField(raw, "sourceImage", path), }; } function sourceRefField(obj: Record, key: string, path: string): string { const value = stringField(obj, key, path); if (!/^[A-Za-z0-9_./-]+$/u.test(value)) throw new Error(`${path}.${key} has an unsupported format`); return value; } function secretKeyField(obj: Record, key: string, path: string): string { const value = stringField(obj, key, path); if (!/^[A-Za-z0-9_.-]+$/u.test(value)) throw new Error(`${path}.${key} has an unsupported format`); return value; } function bootstrapAdminConfig(value: unknown, path: string): HwlabRuntimeBootstrapAdminSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); const transform = stringField(raw, "passwordHashTransform", path); if (transform !== "hwlab-sha256") throw new Error(`${path}.passwordHashTransform must be hwlab-sha256`); const rollout = asRecord(raw.rollout, `${path}.rollout`); const usernameSourceRef = raw.usernameSourceRef === undefined ? undefined : sourceRefField(raw, "usernameSourceRef", path); const usernameSourceKey = raw.usernameSourceKey === undefined ? undefined : secretKeyField(raw, "usernameSourceKey", path); const usernameSourceLine = raw.usernameSourceLine === undefined ? undefined : numberField(raw, "usernameSourceLine", path); const passwordSourceLine = raw.passwordSourceLine === undefined ? undefined : numberField(raw, "passwordSourceLine", path); return { username: stringField(raw, "username", path), displayName: stringField(raw, "displayName", path), ...(usernameSourceRef === undefined ? {} : { usernameSourceRef }), ...(usernameSourceKey === undefined ? {} : { usernameSourceKey }), ...(usernameSourceLine === undefined ? {} : { usernameSourceLine }), passwordSourceRef: sourceRefField(raw, "passwordSourceRef", path), passwordSourceKey: secretKeyField(raw, "passwordSourceKey", path), ...(passwordSourceLine === undefined ? {} : { passwordSourceLine }), passwordHashTransform: transform, secretName: stringField(raw, "secretName", path), secretKey: secretKeyField(raw, "secretKey", path), rolloutDeployment: stringField(rollout, "deployment", `${path}.rollout`), }; } function codeAgentProviderConfig(value: unknown, path: string): HwlabRuntimeCodeAgentProviderSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); const openaiSourceKey = optionalStringField(raw, "openaiSourceKey", path); const opencodeSourceKey = optionalStringField(raw, "opencodeSourceKey", path); if (openaiSourceKey === undefined && opencodeSourceKey === undefined) { throw new Error(`${path} must declare at least one of openaiSourceKey or opencodeSourceKey`); } return { secretName: stringField(raw, "secretName", path), sourceRef: sourceRefField(raw, "sourceRef", path), ...(openaiSourceKey === undefined ? {} : { openaiSourceKey: secretKeyField(raw, "openaiSourceKey", path) }), ...(opencodeSourceKey === undefined ? {} : { opencodeSourceKey: secretKeyField(raw, "opencodeSourceKey", path) }), }; } function codeAgentRuntimeConfig(value: unknown, path: string): HwlabRuntimeCodeAgentRuntimeSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); const managerUrl = stringField(raw, "managerUrl", path); try { const parsed = new URL(managerUrl); if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("unsupported protocol"); } catch { throw new Error(`${path}.managerUrl must be an http(s) URL`); } return { enabled: booleanField(raw, "enabled", path), adapter: stringField(raw, "adapter", path), managerUrl, apiKeySecretName: stringField(raw, "apiKeySecretName", path), apiKeySecretKey: secretKeyField(raw, "apiKeySecretKey", path), runnerNamespace: stringField(raw, "runnerNamespace", path), secretNamespace: stringField(raw, "secretNamespace", path), repoUrlFrom: enumStringField(raw, "repoUrlFrom", path, ["runtimeGitReadUrl"]), providerIdFrom: enumStringField(raw, "providerIdFrom", path, ["runtimeNodeId"]), defaultProviderProfile: stringField(raw, "defaultProviderProfile", path), codexStdioSupervisor: enumStringField(raw, "codexStdioSupervisor", path, ["repo-owned"]), ...(raw.kafkaShadowProducer === undefined ? {} : { kafkaShadowProducer: codeAgentKafkaShadowProducerConfig(raw.kafkaShadowProducer, `${path}.kafkaShadowProducer`) }), }; } function codeAgentKafkaShadowProducerConfig(value: unknown, path: string): HwlabRuntimeKafkaShadowProducerSpec { const raw = asRecord(value, path); const mode = enumStringField(raw, "mode", path, ["shadow-produce-only"]); const consumeEnabled = booleanField(raw, "consumeEnabled", path); if (consumeEnabled !== false) throw new Error(`${path}.consumeEnabled must be false during shadow-produce-only stage`); return { enabled: booleanField(raw, "enabled", path), mode, consumeEnabled, configRef: stringField(raw, "configRef", path), bootstrapServers: stringField(raw, "bootstrapServers", path), commandTopic: stringField(raw, "commandTopic", path), clientId: stringField(raw, "clientId", path), }; } function commandNameField(value: string, path: string): string { if (!/^[A-Za-z0-9._+-]+$/u.test(value)) throw new Error(`${path} must be a simple command name`); return value; } function relativeWorkspacePathField(value: string, path: string): string { if (value.startsWith("/") || value.includes("\0") || value.split("/").some((part) => part === "..")) { throw new Error(`${path} must be a relative workspace path without ..`); } return value; } function absoluteHostPathField(value: string, path: string): string { const parts = value.slice(1).split("/"); if (!value.startsWith("/") || value.includes("\0") || parts.some((part) => part === ".." || part === "")) { throw new Error(`${path} must be an absolute host path without empty components or ..`); } return value; } function gitRemoteNameField(value: string, path: string): string { if (!/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error(`${path} must be a git remote name`); return value; } function sourceWorkspaceGitConfig(value: unknown, path: string): HwlabRuntimeSourceWorkspaceGitSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); return { remoteName: gitRemoteNameField(stringField(raw, "remoteName", path), `${path}.remoteName`), remoteUrl: stringField(raw, "remoteUrl", path), ...(raw.identityTarget === undefined ? {} : { identityTarget: stringField(raw, "identityTarget", path) }), ...(raw.identityId === undefined ? {} : { identityId: stringField(raw, "identityId", path) }), ...(raw.proxyEnvPath === undefined ? {} : { proxyEnvPath: absoluteHostPathField(stringField(raw, "proxyEnvPath", path), `${path}.proxyEnvPath`) }), verifyRemote: booleanField(raw, "verifyRemote", path), requireUpToDate: booleanField(raw, "requireUpToDate", path), }; } function sourceWorkspaceConfig(value: unknown, path: string): HwlabRuntimeSourceWorkspaceSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); const install = asRecord(raw.install, `${path}.install`); return { git: sourceWorkspaceGitConfig(raw.git, `${path}.git`), requiredCommands: stringArrayField(raw, "requiredCommands", path) .map((item, index) => commandNameField(item, `${path}.requiredCommands[${index}]`)), requiredFiles: stringArrayField(raw, "requiredFiles", path) .map((item, index) => relativeWorkspacePathField(item, `${path}.requiredFiles[${index}]`)), hostDependencies: sourceWorkspaceHostDependenciesConfig(raw.hostDependencies, `${path}.hostDependencies`), install: { executor: enumStringField(install, "executor", `${path}.install`, ["k3s-job", "host"]), dependencyCommand: stringField(install, "dependencyCommand", `${path}.install`), browserCommand: stringField(install, "browserCommand", `${path}.install`), ...(install.stateDir === undefined ? {} : { stateDir: absoluteHostPathField(stringField(install, "stateDir", `${path}.install`), `${path}.install.stateDir`) }), timeoutSeconds: numberField(install, "timeoutSeconds", `${path}.install`), }, }; } function sourceWorkspaceHostDependenciesConfig(value: unknown, path: string): HwlabRuntimeSourceWorkspaceHostDependenciesSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); const install = asRecord(raw.install, `${path}.install`); return { checkCommands: stringArrayField(raw, "checkCommands", path) .map((item, index) => commandNameField(item, `${path}.checkCommands[${index}]`)), stateDir: absoluteHostPathField(stringField(raw, "stateDir", path), `${path}.stateDir`), install: { command: stringField(install, "command", `${path}.install`), timeoutSeconds: numberField(install, "timeoutSeconds", `${path}.install`), }, }; } function externalPostgresComponentConfig(value: unknown, path: string): HwlabRuntimeExternalPostgresComponentSpec { const raw = asRecord(value, path); return { secretName: stringField(raw, "secretName", path), secretKey: stringField(raw, "secretKey", path), sourceRef: stringField(raw, "sourceRef", path), envKey: stringField(raw, "envKey", path), role: stringField(raw, "role", path), authnKey: optionalStringField(raw, "authnKey", path), schema: optionalStringField(raw, "schema", path), }; } function externalPostgresRuntimeAccessConfig(value: unknown, path: string): HwlabRuntimeExternalPostgresRuntimeAccessSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); return { routeName: optionalStringField(raw, "routeName", path), endpointAddress: stringField(raw, "endpointAddress", path), port: numberField(raw, "port", path), }; } function externalPostgresConfig(value: unknown, path: string): HwlabRuntimeExternalPostgresSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); const sslmode = stringField(raw, "sslmode", path); if (sslmode !== "require") throw new Error(`${path}.sslmode must be require`); return { provider: stringField(raw, "provider", path), configRef: stringField(raw, "configRef", path), serviceName: stringField(raw, "serviceName", path), endpointAddress: stringField(raw, "endpointAddress", path), port: numberField(raw, "port", path), ...(raw.runtimeAccess === undefined ? {} : { runtimeAccess: externalPostgresRuntimeAccessConfig(raw.runtimeAccess, `${path}.runtimeAccess`) }), sslmode, database: stringField(raw, "database", path), cloudApi: externalPostgresComponentConfig(raw.cloudApi, `${path}.cloudApi`), openfga: externalPostgresComponentConfig(raw.openfga, `${path}.openfga`), }; } function runtimeStoreConfig(value: unknown, path: string): HwlabRuntimeStoreSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); const postgres = raw.postgres === undefined ? undefined : asRecord(raw.postgres, `${path}.postgres`); return { ...(postgres === undefined ? {} : { postgres: { ...(postgres.mode === undefined ? {} : { mode: postgresModeField(postgres, `${path}.postgres`) }), ...(postgres.secretName === undefined ? {} : { secretName: stringField(postgres, "secretName", `${path}.postgres`) }), ...(postgres.statefulSet === undefined ? {} : { statefulSet: stringField(postgres, "statefulSet", `${path}.postgres`) }), ...(postgres.serviceName === undefined ? {} : { serviceName: stringField(postgres, "serviceName", `${path}.postgres`) }), ...(postgres.adminUser === undefined ? {} : { adminUser: stringField(postgres, "adminUser", `${path}.postgres`) }), ...(postgres.adminPasswordSourceRef === undefined ? {} : { adminPasswordSourceRef: stringField(postgres, "adminPasswordSourceRef", `${path}.postgres`) }), ...(postgres.adminPasswordSourceKey === undefined ? {} : { adminPasswordSourceKey: secretKeyField(postgres, "adminPasswordSourceKey", `${path}.postgres`) }), ...(postgres.cloudApi === undefined ? {} : { cloudApi: runtimeStorePostgresComponentConfig(postgres.cloudApi, `${path}.postgres.cloudApi`) }), ...(postgres.openfga === undefined ? {} : { openfga: runtimeStorePostgresComponentConfig(postgres.openfga, `${path}.postgres.openfga`) }), poolMax: numberField(postgres, "poolMax", `${path}.postgres`), ...(postgres.connectionTimeoutMs === undefined ? {} : { connectionTimeoutMs: numberField(postgres, "connectionTimeoutMs", `${path}.postgres`) }), ...(postgres.queryRetryMaxAttempts === undefined ? {} : { queryRetryMaxAttempts: numberField(postgres, "queryRetryMaxAttempts", `${path}.postgres`) }), ...(postgres.queryRetryInitialDelayMs === undefined ? {} : { queryRetryInitialDelayMs: numberField(postgres, "queryRetryInitialDelayMs", `${path}.postgres`) }), ...(postgres.queryRetryMaxDelayMs === undefined ? {} : { queryRetryMaxDelayMs: numberField(postgres, "queryRetryMaxDelayMs", `${path}.postgres`) }), }, }), }; } function postgresModeField(raw: Record, path: string): "local-k3s" | "platform-service" { const mode = stringField(raw, "mode", path); if (mode !== "local-k3s" && mode !== "platform-service") throw new Error(`${path}.mode must be local-k3s or platform-service`); return mode; } function runtimeStorePostgresComponentConfig(value: unknown, path: string): HwlabRuntimePostgresStoreComponentSpec { const raw = asRecord(value, path); return { secretName: stringField(raw, "secretName", path), secretKey: secretKeyField(raw, "secretKey", path), database: stringField(raw, "database", path), role: stringField(raw, "role", path), authnKey: optionalStringField(raw, "authnKey", path), postgresPasswordKey: optionalStringField(raw, "postgresPasswordKey", path), }; } function webProbeConfig(value: unknown, path: string): HwlabRuntimeWebProbeSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); const rawBrowserProxyMode = optionalStringField(raw, "browserProxyMode", path); let browserProxyMode: "auto" | "direct" | undefined; if (rawBrowserProxyMode !== undefined) { if (rawBrowserProxyMode !== "auto" && rawBrowserProxyMode !== "direct") { throw new Error(`${path}.browserProxyMode must be auto or direct`); } browserProxyMode = rawBrowserProxyMode; } const playwrightBrowsersPath = optionalStringField(raw, "playwrightBrowsersPath", path); return { ...(browserProxyMode === undefined ? {} : { browserProxyMode }), ...(playwrightBrowsersPath === undefined ? {} : { playwrightBrowsersPath }), ...(raw.defaultOrigin === undefined ? {} : { defaultOrigin: webProbeOriginConfig(raw.defaultOrigin, `${path}.defaultOrigin`) }), ...(raw.authLogin === undefined ? {} : { authLogin: webProbeAuthLoginConfig(raw.authLogin, `${path}.authLogin`) }), ...(raw.alertThresholds === undefined ? {} : { alertThresholds: webProbeAlertThresholdsConfig(raw.alertThresholds, `${path}.alertThresholds`) }), ...(raw.projectManagement === undefined ? {} : { projectManagement: webProbeProjectManagementConfig(raw.projectManagement, `${path}.projectManagement`) }), }; } function webProbeAuthLoginConfig(value: unknown, path: string): HwlabRuntimeWebProbeAuthLoginSpec { const raw = asRecord(value, path); const maxAttempts = positiveNumberField(raw, "maxAttempts", path); if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 20) throw new Error(`${path}.maxAttempts must be an integer between 1 and 20`); return { maxAttempts, requestTimeoutMs: boundedIntegerField(raw, "requestTimeoutMs", path, 1000, 120000), initialDelayMs: boundedIntegerField(raw, "initialDelayMs", path, 0, 60000), maxDelayMs: boundedIntegerField(raw, "maxDelayMs", path, 0, 120000), }; } function webProbeSentinelConfig(value: unknown, path: string): HwlabRuntimeWebProbeSentinelSpec { const raw = asRecord(value, path); const allowed = new Set(["enabled", "configRefs"]); for (const key of Object.keys(raw)) { if (!allowed.has(key)) throw new Error(`${path}.${key} is not allowed; root sentinel YAML may only contain enabled/configRefs`); } const refs = asRecord(raw.configRefs, `${path}.configRefs`); const allowedRefKeys = new Set(HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS); for (const key of Object.keys(refs)) { if (!allowedRefKeys.has(key as HwlabRuntimeWebProbeSentinelConfigRefKey)) throw new Error(`${path}.configRefs.${key} is not a supported sentinel configRef`); } const configRefs = Object.fromEntries(HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS.map((key) => { const ref = stringField(refs, key, `${path}.configRefs`); validateConfigRef(ref, `${path}.configRefs.${key}`); return [key, ref]; })) as Record; return { enabled: booleanField(raw, "enabled", path), configRefs, }; } function webProbeSentinelRegistryItemConfig(value: unknown, path: string): HwlabRuntimeWebProbeSentinelRegistryItemSpec { const raw = asRecord(value, path); const allowed = new Set(["id", "enabled", "configRef"]); for (const key of Object.keys(raw)) { if (!allowed.has(key)) throw new Error(`${path}.${key} is not allowed; sentinel registry items may only contain id/enabled/configRef`); } const id = stringField(raw, "id", path); if (!/^[a-z0-9][a-z0-9-]{1,80}$/u.test(id)) throw new Error(`${path}.id must be a stable lowercase sentinel id`); const configRef = stringField(raw, "configRef", path); validateConfigRef(configRef, `${path}.configRef`); return { id, enabled: booleanField(raw, "enabled", path), configRef, }; } function webProbeSentinelRegistryConfig(value: unknown, path: string): readonly HwlabRuntimeWebProbeSentinelRegistryItemSpec[] { if (!Array.isArray(value)) throw new Error(`${path} must be an array`); if (value.length === 0) throw new Error(`${path} must contain at least one sentinel`); const items = value.map((item, index) => webProbeSentinelRegistryItemConfig(item, `${path}[${index}]`)); const ids = new Set(); for (const item of items) { if (ids.has(item.id)) throw new Error(`${path} contains duplicate sentinel id ${item.id}`); ids.add(item.id); } return items; } function validateConfigRef(ref: string, path: string): void { const [file, fragment, extra] = ref.split("#"); if (extra !== undefined || file === undefined || fragment === undefined || file.length === 0 || fragment.length === 0) { throw new Error(`${path} must use path/to/file.yaml#object.path syntax`); } if (file.startsWith("/") || file.includes("\0") || file.includes("..") || !file.startsWith("config/") || !file.endsWith(".yaml")) { throw new Error(`${path} must reference a repo-relative config/*.yaml file without ..`); } if (!/^[A-Za-z0-9_.\-[\]${}]+$/u.test(fragment)) { throw new Error(`${path} has an unsupported YAML path fragment`); } } function webProbeProjectManagementConfig(value: unknown, path: string): HwlabRuntimeWebProbeProjectManagementSpec { const raw = asRecord(value, path); const targetPaths = nonEmptyStringArrayField(raw, "targetPaths", path); const readinessSelectors = nonEmptyStringArrayField(raw, "readinessSelectors", path); const naturalApiPathPrefixes = nonEmptyStringArrayField(raw, "naturalApiPathPrefixes", path); const commandAllowlist = nonEmptyStringArrayField(raw, "commandAllowlist", path); const launchRoute = stringField(raw, "launchRoute", path); if (!launchRoute.startsWith("/")) throw new Error(`${path}.launchRoute must be an absolute path`); for (const [field, values] of Object.entries({ targetPaths, naturalApiPathPrefixes })) { for (const item of values) { if (!item.startsWith("/")) throw new Error(`${path}.${field} entries must be absolute paths; got ${item}`); } } return { enabled: booleanField(raw, "enabled", path), targetPaths, readinessSelectors, naturalApiPathPrefixes, commandAllowlist, launchRoute, slowApiBudgetMs: positiveNumberField(raw, "slowApiBudgetMs", path), }; } function webProbeAlertThresholdsConfig(value: unknown, path: string): HwlabRuntimeWebProbeAlertThresholdsSpec { const raw = asRecord(value, path); const sessionRailFallbackRatio = positiveNumberField(raw, "sessionRailFallbackRatio", path); if (sessionRailFallbackRatio > 1) throw new Error(`${path}.sessionRailFallbackRatio must be <= 1`); return { sameOriginApiSlowMs: positiveNumberField(raw, "sameOriginApiSlowMs", path), partialApiSlowMs: positiveNumberField(raw, "partialApiSlowMs", path), longLivedStreamOpenSlowMs: positiveNumberField(raw, "longLivedStreamOpenSlowMs", path), visibleLoadingSlowMs: positiveNumberField(raw, "visibleLoadingSlowMs", path), turnTimingSampleSlackSeconds: positiveNumberField(raw, "turnTimingSampleSlackSeconds", path), turnElapsedSevereTimeoutSeconds: positiveNumberField(raw, "turnElapsedSevereTimeoutSeconds", path), uncommandedStateChangeCommandWindowMs: positiveNumberField(raw, "uncommandedStateChangeCommandWindowMs", path), scrollJumpCommandWindowMs: positiveNumberField(raw, "scrollJumpCommandWindowMs", path), scrollJumpFromY: positiveNumberField(raw, "scrollJumpFromY", path), scrollJumpToY: positiveNumberField(raw, "scrollJumpToY", path), sessionRailFallbackRatio, }; } function webProbeOriginConfig(value: unknown, path: string): HwlabRuntimeWebProbeOriginSpec { const raw = asRecord(value, path); const mode = stringField(raw, "mode", path); if (mode === "public") { return { mode, ...(raw.baseUrl === undefined ? {} : { baseUrl: stringField(raw, "baseUrl", path).replace(/\/+$/u, "") }), }; } if (mode !== "k8s-service-cluster-ip") throw new Error(`${path}.mode must be public or k8s-service-cluster-ip`); const scheme = stringField(raw, "scheme", path); if (scheme !== "http" && scheme !== "https") throw new Error(`${path}.scheme must be http or https`); return { mode, serviceName: stringField(raw, "serviceName", path), namespace: stringField(raw, "namespace", path), port: numberField(raw, "port", path), scheme, }; } function publicExposureProxyConfig(value: unknown, path: string): HwlabRuntimePublicExposureFrpcProxySpec { const raw = asRecord(value, path); return { name: stringField(raw, "name", path), localIP: stringField(raw, "localIP", path), localPort: numberField(raw, "localPort", path), remotePort: numberField(raw, "remotePort", path), }; } function publicExposureExtraProxyConfig(value: unknown, path: string): HwlabRuntimePublicExposureFrpcExtraProxySpec { const raw = asRecord(value, path); const publicBaseUrl = optionalStringField(raw, "publicBaseUrl", path)?.replace(/\/+$/u, ""); const hostname = optionalStringField(raw, "hostname", path); if (publicBaseUrl !== undefined) { validatePublicExposureOrigin(publicBaseUrl, `${path}.publicBaseUrl`); if (hostname !== undefined && new URL(publicBaseUrl).hostname !== hostname) throw new Error(`${path}.publicBaseUrl hostname must match ${path}.hostname`); } const cloudWebEnvName = optionalStringField(raw, "cloudWebEnvName", path); if (cloudWebEnvName !== undefined && !/^HWLAB_CLOUD_WEB_[A-Z0-9_]+$/u.test(cloudWebEnvName)) throw new Error(`${path}.cloudWebEnvName must be a HWLAB_CLOUD_WEB_* env name`); if (cloudWebEnvName !== undefined && publicBaseUrl === undefined) throw new Error(`${path}.cloudWebEnvName requires ${path}.publicBaseUrl`); return { id: stringField(raw, "id", path), ...publicExposureProxyConfig(value, path), ...(hostname === undefined ? {} : { hostname }), ...(publicBaseUrl === undefined ? {} : { publicBaseUrl }), ...(cloudWebEnvName === undefined ? {} : { cloudWebEnvName }), }; } function publicExposureExtraProxiesConfig(value: unknown, path: string): HwlabRuntimePublicExposureFrpcExtraProxySpec[] { if (value === undefined) return []; if (!Array.isArray(value)) throw new Error(`${path} must be an array`); return value.map((item, index) => publicExposureExtraProxyConfig(item, `${path}[${index}]`)); } function publicExposureConfig(value: unknown, path: string): HwlabRuntimePublicExposureSpec | null { if (value === undefined) return null; const raw = asRecord(value, path); const mode = stringField(raw, "mode", path); if (mode !== "pk01-caddy-frp") throw new Error(`${path}.mode must be pk01-caddy-frp`); const frpc = asRecord(raw.frpc, `${path}.frpc`); const caddy = asRecord(raw.caddy, `${path}.caddy`); const caddyTls = optionalStringField(caddy, "tls", `${path}.caddy`) ?? "auto"; if (caddyTls !== "auto" && caddyTls !== "internal") throw new Error(`${path}.caddy.tls must be auto or internal`); return { enabled: true, mode, publicBaseUrl: stringField(raw, "publicBaseUrl", path).replace(/\/+$/u, ""), hostname: stringField(raw, "hostname", path), expectedA: stringField(raw, "expectedA", path), serverAddr: stringField(frpc, "serverAddr", `${path}.frpc`), serverPort: numberField(frpc, "serverPort", `${path}.frpc`), tokenSourceRef: stringField(frpc, "tokenSourceRef", `${path}.frpc`), tokenSourceKey: stringField(frpc, "tokenSourceKey", `${path}.frpc`), secretName: stringField(frpc, "secretName", `${path}.frpc`), secretKey: optionalStringField(frpc, "secretKey", `${path}.frpc`) ?? "frpc.toml", tokenKey: optionalStringField(frpc, "tokenKey", `${path}.frpc`) ?? "token", caddyRoute: stringField(caddy, "route", `${path}.caddy`), caddyConfigPath: stringField(caddy, "configPath", `${path}.caddy`), caddyServiceName: stringField(caddy, "serviceName", `${path}.caddy`), caddyEmail: stringField(caddy, "email", `${path}.caddy`), caddyTls, responseHeaderTimeoutSeconds: numberField(caddy, "responseHeaderTimeoutSeconds", `${path}.caddy`), webProxy: publicExposureProxyConfig(frpc.webProxy, `${path}.frpc.webProxy`), apiProxy: publicExposureProxyConfig(frpc.apiProxy, `${path}.frpc.apiProxy`), extraProxies: publicExposureExtraProxiesConfig(frpc.extraProxies, `${path}.frpc.extraProxies`), }; } function validatePublicExposureOrigin(publicBaseUrl: string, path: string): void { try { const parsed = new URL(publicBaseUrl); if (parsed.protocol !== "https:") throw new Error("must use https"); if (parsed.pathname !== "/" || parsed.search.length > 0 || parsed.hash.length > 0) throw new Error("must point to the public origin root"); } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error(`${path} must be an https origin root URL: ${message}`); } } function observabilityConfig(value: unknown, path: string): HwlabRuntimeObservabilitySpec { const raw = asRecord(value, path); const recordingRules = observabilityRecordingRulesConfig(raw.recordingRules, `${path}.recordingRules`); const warningAlerts = observabilityWarningAlertsConfig(raw.warningAlerts, `${path}.warningAlerts`); const recordingRuleIds = new Set(recordingRules.map((rule) => rule.id)); for (const alert of warningAlerts) { if (!recordingRuleIds.has(alert.ruleId)) throw new Error(`${path}.warningAlerts.${alert.id}.ruleId must reference a recordingRules id`); } const traceExplorerUrlTemplate = optionalStringField(raw, "traceExplorerUrlTemplate", path); if (traceExplorerUrlTemplate !== undefined) validateTraceExplorerUrlTemplate(traceExplorerUrlTemplate, `${path}.traceExplorerUrlTemplate`); return { prometheusOperator: booleanField(raw, "prometheusOperator", path), traceExplorerUrlTemplate, metricsEndpoint: observabilityMetricsEndpointConfig(raw.metricsEndpoint, `${path}.metricsEndpoint`), workbench: observabilityWorkbenchConfig(raw.workbench, `${path}.workbench`), webProbe: observabilityWebProbeConfig(raw.webProbe, `${path}.webProbe`), recordingRules, warningAlerts, }; } function observabilityWebProbeConfig(value: unknown, path: string): HwlabRuntimeObservabilityWebProbeSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); const allowed = new Set(["sentinel", "sentinels", "monitorRoot"]); for (const key of Object.keys(raw)) { if (!allowed.has(key)) throw new Error(`${path}.${key} is not allowed; observability.webProbe currently only owns sentinel/sentinels/monitorRoot`); } if (raw.sentinel !== undefined && raw.sentinels !== undefined) throw new Error(`${path} may declare sentinel or sentinels, not both`); const sentinel = raw.sentinel === undefined ? undefined : webProbeSentinelConfig(raw.sentinel, `${path}.sentinel`); const sentinels = raw.sentinels === undefined ? undefined : webProbeSentinelRegistryConfig(raw.sentinels, `${path}.sentinels`); const monitorRoot = raw.monitorRoot === undefined ? undefined : webProbeMonitorRootConfig(raw.monitorRoot, `${path}.monitorRoot`); if (monitorRoot !== undefined) { if (sentinels !== undefined && !sentinels.some((item) => item.id === monitorRoot.sentinelId)) { throw new Error(`${path}.monitorRoot.sentinelId must reference one entry from ${path}.sentinels`); } if (sentinel !== undefined && monitorRoot.sentinelId !== "workbench-dsflash-go-tool-call-10x") { throw new Error(`${path}.monitorRoot.sentinelId must be workbench-dsflash-go-tool-call-10x for legacy sentinel config`); } } return { ...(sentinel === undefined ? {} : { sentinel }), ...(sentinels === undefined ? {} : { sentinels }), ...(monitorRoot === undefined ? {} : { monitorRoot }), }; } function webProbeMonitorRootConfig(value: unknown, path: string): HwlabRuntimeWebProbeMonitorRootSpec { const raw = asRecord(value, path); const allowed = new Set(["enabled", "sentinelId", "publicBaseUrl", "routePrefix", "caddyManagedBlockOwner"]); for (const key of Object.keys(raw)) { if (!allowed.has(key)) throw new Error(`${path}.${key} is not allowed; monitorRoot may only contain enabled/sentinelId/publicBaseUrl/routePrefix/caddyManagedBlockOwner`); } const sentinelId = stringField(raw, "sentinelId", path); if (!/^[a-z0-9][a-z0-9-]{1,80}$/u.test(sentinelId)) throw new Error(`${path}.sentinelId must be a stable lowercase sentinel id`); const publicBaseUrl = stringField(raw, "publicBaseUrl", path).replace(/\/+$/u, ""); try { const parsed = new URL(publicBaseUrl); if (parsed.protocol !== "https:") throw new Error("must use https"); if (parsed.pathname !== "/" || parsed.search.length > 0 || parsed.hash.length > 0) throw new Error("must point to the public origin root"); } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error(`${path}.publicBaseUrl must be an https origin root URL: ${message}`); } const routePrefix = stringField(raw, "routePrefix", path); if (routePrefix !== "/") throw new Error(`${path}.routePrefix must be / for the monitor root switch`); const caddyManagedBlockOwner = stringField(raw, "caddyManagedBlockOwner", path); if (!/^[a-z0-9][a-z0-9-]{1,100}$/u.test(caddyManagedBlockOwner)) throw new Error(`${path}.caddyManagedBlockOwner must be a stable lowercase owner id`); return { enabled: booleanField(raw, "enabled", path), sentinelId, publicBaseUrl, routePrefix, caddyManagedBlockOwner, }; } function validateTraceExplorerUrlTemplate(template: string, path: string): void { if (!template.includes("{trace_id}")) throw new Error(`${path} must include {trace_id}`); if (template.startsWith("//")) throw new Error(`${path} must not be protocol-relative`); const placeholders = Array.from(template.matchAll(/\{([A-Za-z0-9_:-]+)\}/gu), (match) => match[0]); if (placeholders.length === 0 || placeholders.some((placeholder) => placeholder !== "{trace_id}")) { throw new Error(`${path} may only use the {trace_id} placeholder`); } try { const url = new URL(template.replaceAll("{trace_id}", "trc_template_probe"), "https://hwlab.local"); if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("unsupported protocol"); } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error(`${path} must be an http(s) or same-origin relative URL template: ${message}`); } } function observabilityMetricsEndpointConfig(value: unknown, path: string): HwlabRuntimeObservabilityMetricsEndpointSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); const scheme = stringField(raw, "scheme", path); if (scheme !== "http") throw new Error(`${path}.scheme must be http`); const scrapeMode = stringField(raw, "scrapeMode", path); if (scrapeMode !== "pod-loopback") throw new Error(`${path}.scrapeMode must be pod-loopback`); const publicRawMetrics = stringField(raw, "publicRawMetrics", path); if (publicRawMetrics !== "denied") throw new Error(`${path}.publicRawMetrics must be denied`); return { serviceName: stringField(raw, "serviceName", path), containerName: stringField(raw, "containerName", path), port: numberField(raw, "port", path), scheme, path: stringField(raw, "path", path), scrapeMode, publicRawMetrics, }; } function observabilityWorkbenchConfig(value: unknown, path: string): HwlabRuntimeObservabilityWorkbenchSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); return { enabled: booleanField(raw, "enabled", path), summaryPath: stringField(raw, "summaryPath", path), lowSampleThreshold: numberField(raw, "lowSampleThreshold", path), metricPrefixes: stringArrayField(raw, "metricPrefixes", path), requiredSeries: stringArrayField(raw, "requiredSeries", path), backendLabelDenylist: stringArrayField(raw, "backendLabelDenylist", path), maxUnknownEventLines: nonNegativeIntegerField(raw, "maxUnknownEventLines", path), }; } function observabilityRecordingRulesConfig(value: unknown, path: string): HwlabRuntimeObservabilityRecordingRuleSpec[] { if (value === undefined) return []; if (!Array.isArray(value)) throw new Error(`${path} must be an array`); return value.map((item, index) => { const itemPath = `${path}[${index}]`; const raw = asRecord(item, itemPath); const quantile = positiveNumberField(raw, "quantile", itemPath); if (quantile >= 1) throw new Error(`${itemPath}.quantile must be less than 1`); return { id: stringField(raw, "id", itemPath), metric: stringField(raw, "metric", itemPath), sourceMetric: stringField(raw, "sourceMetric", itemPath), quantile, window: stringField(raw, "window", itemPath), minSamples: numberField(raw, "minSamples", itemPath), groupBy: stringArrayField(raw, "groupBy", itemPath), matchLabels: optionalStringRecord(raw.matchLabels, `${itemPath}.matchLabels`), }; }); } function observabilityWarningAlertsConfig(value: unknown, path: string): HwlabRuntimeObservabilityWarningAlertSpec[] { if (value === undefined) return []; if (!Array.isArray(value)) throw new Error(`${path} must be an array`); return value.map((item, index) => { const itemPath = `${path}[${index}]`; const raw = asRecord(item, itemPath); const severity = stringField(raw, "severity", itemPath); if (severity !== "warning") throw new Error(`${itemPath}.severity must be warning`); return { id: stringField(raw, "id", itemPath), ruleId: stringField(raw, "ruleId", itemPath), severity, thresholdSeconds: positiveNumberField(raw, "thresholdSeconds", itemPath), minSamples: numberField(raw, "minSamples", itemPath), for: stringField(raw, "for", itemPath), matchLabels: optionalStringRecord(raw.matchLabels, `${itemPath}.matchLabels`), }; }); } function runtimeImageRewritesConfig(value: unknown, path: string): HwlabRuntimeImageRewriteSpec[] { if (value === undefined) return []; if (!Array.isArray(value)) throw new Error(`${path} must be an array`); return value.map((item, index) => { const raw = asRecord(item, `${path}[${index}]`); return { source: stringField(raw, "source", `${path}[${index}]`), target: stringField(raw, "target", `${path}[${index}]`), }; }); } function runtimeImageBuildsConfig(value: unknown, path: string): HwlabRuntimeImageBuildSpec[] { if (value === undefined) return []; if (!Array.isArray(value)) throw new Error(`${path} must be an array`); return value.map((item, index) => { const itemPath = `${path}[${index}]`; const raw = asRecord(item, itemPath); const kind = stringField(raw, "kind", itemPath); if (kind !== "moonbridge") throw new Error(`${itemPath}.kind must be moonbridge`); return { id: stringField(raw, "id", itemPath), kind, target: stringField(raw, "target", itemPath), sourceRepo: stringField(raw, "sourceRepo", itemPath), sourceRef: stringField(raw, "sourceRef", itemPath), builderImage: stringField(raw, "builderImage", itemPath), goProxy: stringField(raw, "goProxy", itemPath), dockerNetworkMode: enumStringField(raw, "dockerNetworkMode", itemPath, ["default", "host"]), }; }); } function readHwlabNodeLaneConfig(): HwlabNodeLaneConfig { const path = rootPath(HWLAB_NODE_LANE_CONFIG_PATH); const raw = readFileSync(path, "utf8"); const parsed = asRecord(Bun.YAML.parse(raw) as unknown, HWLAB_NODE_LANE_CONFIG_PATH); validateConfigEnvelope(parsed); const defaultTarget = parseDefaultTarget(asRecord(parsed.defaults, `${HWLAB_NODE_LANE_CONFIG_PATH}.defaults`)); const requiredNoProxy = stringArrayField(parsed, "requiredNoProxy", HWLAB_NODE_LANE_CONFIG_PATH); for (const required of ["hyueapi.com", ".hyueapi.com"]) { if (!requiredNoProxy.includes(required)) throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.requiredNoProxy must include ${required}`); } const nodes = Object.fromEntries(sortedRecordEntries(parsed.nodes, "nodes").map(([id, item]) => [id, nodeConfig(id, item)])); const networkProfiles = Object.fromEntries( sortedRecordEntries(parsed.networkProfiles, "networkProfiles").map(([id, item]) => [id, networkProfileConfig(id, item, requiredNoProxy)]), ); const downloadProfiles = Object.fromEntries( sortedRecordEntries(parsed.downloadProfiles, "downloadProfiles").map(([id, item]) => [id, downloadProfileConfig(id, item)]), ); const laneEntries = sortedRecordEntries(parsed.lanes, "lanes"); const lanes = Object.fromEntries(laneEntries.map(([id, item]) => { assertYamlDeclaredLaneId(id, `lanes.${id}`); return [id, laneConfig(id, item)]; })) as Record; const laneTargets: Partial>> = {}; for (const [id, item] of laneEntries) { assertYamlDeclaredLaneId(id, `lanes.${id}`); if (item.targets === undefined) continue; laneTargets[id] = Object.fromEntries(sortedRecordEntries(item.targets, `lanes.${id}.targets`).map(([nodeId, target]) => [ nodeId, laneTargetConfig(id, nodeId, item, target), ])); } for (const node of Object.values(nodes)) { if (networkProfiles[node.networkProfileId] === undefined) throw new Error(`nodes.${node.id}.networkProfile references missing profile ${node.networkProfileId}`); if (downloadProfiles[node.downloadProfileId] === undefined) throw new Error(`nodes.${node.id}.downloadProfile references missing profile ${node.downloadProfileId}`); } for (const lane of [...Object.values(lanes), ...Object.values(laneTargets).flatMap((targets) => Object.values(targets))]) { if (nodes[lane.node] === undefined) throw new Error(`lanes.${lane.id}.node references missing node ${lane.node}`); } if (nodes[defaultTarget.node] === undefined) throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.defaults.node references missing node ${defaultTarget.node}`); const defaultLane = laneTargets[defaultTarget.lane]?.[defaultTarget.node] ?? lanes[defaultTarget.lane]; if (defaultLane === undefined || defaultLane.node !== defaultTarget.node) { throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.defaults must reference a declared lane target`); } for (const lane of Object.values(lanes)) { if (lane.activeTarget === undefined) continue; if (nodes[lane.activeTarget] === undefined) { throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.lanes.${lane.id}.activeTarget references missing node ${lane.activeTarget}`); } const target = laneTargets[lane.id]?.[lane.activeTarget]; if (target === undefined && lane.node !== lane.activeTarget) { throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.lanes.${lane.id}.activeTarget must reference lanes.${lane.id}.targets.${lane.activeTarget}`); } } return { defaultTarget, requiredNoProxy, nodes, lanes, laneTargets, networkProfiles, downloadProfiles }; } function validateConfigEnvelope(parsed: Record): void { if (parsed.version !== 1) throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.version must be 1`); if (parsed.kind !== "HwlabNodeLaneConfig") throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.kind must be HwlabNodeLaneConfig`); const metadata = asRecord(parsed.metadata, `${HWLAB_NODE_LANE_CONFIG_PATH}.metadata`); if (stringField(metadata, "name", `${HWLAB_NODE_LANE_CONFIG_PATH}.metadata`) !== "hwlab-node-lanes") { throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.metadata.name must be hwlab-node-lanes`); } } function parseDefaultTarget(raw: Record): { readonly node: string; readonly lane: HwlabRuntimeLane } { const lane = stringField(raw, "lane", `${HWLAB_NODE_LANE_CONFIG_PATH}.defaults`); assertYamlDeclaredLaneId(lane, `${HWLAB_NODE_LANE_CONFIG_PATH}.defaults.lane`); return { node: stringField(raw, "node", `${HWLAB_NODE_LANE_CONFIG_PATH}.defaults`), lane, }; } const HWLAB_NODE_LANE_CONFIG = readHwlabNodeLaneConfig(); function buildRuntimeLaneSpec(config: HwlabLaneConfig): HwlabRuntimeLaneSpec { const node = HWLAB_NODE_LANE_CONFIG.nodes[config.node]; const networkProfile = HWLAB_NODE_LANE_CONFIG.networkProfiles[node.networkProfileId]; const downloadProfile = HWLAB_NODE_LANE_CONFIG.downloadProfiles[node.downloadProfileId]; return { lane: config.id, nodeId: node.id, nodeRoute: node.route, nodeKubeRoute: node.kubeRoute, gitopsRoot: node.gitopsRoot, minor: config.minor, version: config.version, sourceBranch: config.sourceBranch, workspace: config.workspace, cicdRepo: config.cicdRepo, cicdRepoLock: config.cicdRepoLock, app: config.app, pipeline: config.pipeline, pipelineRunPrefix: config.pipelineRunPrefix, serviceAccountName: config.serviceAccountName, controlPlaneFieldManager: config.controlPlaneFieldManager, gitUrl: config.git.url, gitReadUrl: config.git.readUrl, gitWriteUrl: config.git.writeUrl, argoRepoUrl: config.argo.repoURL ?? config.git.readUrl, gitopsBranch: config.gitopsBranch, catalogPath: config.catalogPath, runtimePath: config.runtime.path, runtimeNamespace: config.runtime.namespace, runtimeRenderDir: config.runtime.renderDir, tektonDir: config.tektonDir, argoApplicationFile: config.argoApplicationFile, registryPrefix: config.registryPrefix, baseImage: config.baseImage, ...(config.baseImageSource === undefined ? {} : { baseImageSource: config.baseImageSource }), serviceIds: config.serviceIds, publicWebUrl: config.public.webUrl, publicApiUrl: config.public.apiUrl, stepEnv: config.stepEnv, ...(config.buildkit === undefined ? {} : { buildkit: config.buildkit }), ...(config.bootstrapAdmin === undefined ? {} : { bootstrapAdmin: config.bootstrapAdmin }), ...(config.codeAgentProvider === undefined ? {} : { codeAgentProvider: config.codeAgentProvider }), ...(config.codeAgentRuntime === undefined ? {} : { codeAgentRuntime: config.codeAgentRuntime }), ...(config.sourceWorkspace === undefined ? {} : { sourceWorkspace: config.sourceWorkspace }), ...(config.externalPostgres === undefined ? {} : { externalPostgres: config.externalPostgres }), ...(config.runtimeStore === undefined ? {} : { runtimeStore: config.runtimeStore }), ...(config.webProbe === undefined ? {} : { webProbe: config.webProbe }), publicExposure: config.publicExposure, observability: config.observability, runtimeImageRewrites: config.runtimeImageRewrites, runtimeImageBuilds: config.runtimeImageBuilds, networkProfileId: networkProfile.id, downloadProfileId: downloadProfile.id, networkProfile, downloadProfile, }; } const RUNTIME_LANE_SPECS = Object.fromEntries( Object.values(HWLAB_NODE_LANE_CONFIG.lanes).map((config) => [config.id, buildRuntimeLaneSpec(config)]), ) as Record; export function isHwlabRuntimeLane(value: string): value is HwlabRuntimeLane { return Object.prototype.hasOwnProperty.call(RUNTIME_LANE_SPECS, value); } export function hwlabRuntimeLaneSpec(lane: HwlabRuntimeLane): HwlabRuntimeLaneSpec { return RUNTIME_LANE_SPECS[lane]; } export function hwlabRuntimeLaneSpecForNode(lane: HwlabRuntimeLane, nodeId: string): HwlabRuntimeLaneSpec { const targetSpec = HWLAB_NODE_LANE_CONFIG.laneTargets[lane]?.[nodeId]; if (targetSpec !== undefined) return buildRuntimeLaneSpec(targetSpec); const defaultSpec = RUNTIME_LANE_SPECS[lane]; if (defaultSpec.nodeId === nodeId) return defaultSpec; const knownNodes = unique([defaultSpec.nodeId, ...Object.keys(HWLAB_NODE_LANE_CONFIG.laneTargets[lane] ?? {})]).join(", "); throw new Error(`lane ${lane} has no target for node ${nodeId}; known nodes: ${knownNodes}`); } export function hwlabRuntimeLaneIds(): HwlabRuntimeLane[] { return Object.keys(RUNTIME_LANE_SPECS) as HwlabRuntimeLane[]; } export function hwlabRuntimeNodeIds(): string[] { return Object.keys(HWLAB_NODE_LANE_CONFIG.nodes); } export function hwlabDefaultRuntimeTarget(): { readonly node: string; readonly lane: HwlabRuntimeLane } { return { ...HWLAB_NODE_LANE_CONFIG.defaultTarget }; } export function hwlabRuntimeActiveTarget(lane: HwlabRuntimeLane): { readonly node: string; readonly lane: HwlabRuntimeLane } { const config = HWLAB_NODE_LANE_CONFIG.lanes[lane]; return { node: config.activeTarget ?? config.node, lane, }; } export function hwlabRuntimeActiveLaneSpec(lane: HwlabRuntimeLane): HwlabRuntimeLaneSpec { const target = hwlabRuntimeActiveTarget(lane); return hwlabRuntimeLaneSpecForNode(target.lane, target.node); } export function hwlabRuntimeLaneConfigPath(): string { return HWLAB_NODE_LANE_CONFIG_PATH; } export function hwlabRequiredNoProxyEntries(): string[] { return [...HWLAB_NODE_LANE_CONFIG.requiredNoProxy]; }