From 18bdaf313f009ead877bece26c11ccf068b2aefb Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Sun, 7 Jun 2026 21:49:54 +0800 Subject: [PATCH] fix: launch v02 bridge containers with env reuse --- .../hwlab-codex-api-responses-forwarder.sh | 30 ++++ .../boot/hwlab-deepseek-responses-bridge.sh | 29 ++++ docs/reference/spec-v02-cicd.md | 1 + scripts/g14-ci-plan.test.mjs | 6 + scripts/g14-gitops-render.mjs | 132 ++++++++++++------ scripts/g14-gitops-render.test.ts | 25 +++- scripts/src/g14-ci-plan-lib.mjs | 2 + 7 files changed, 178 insertions(+), 47 deletions(-) create mode 100644 deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh create mode 100644 deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh diff --git a/deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh b/deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh new file mode 100644 index 00000000..ef3ad489 --- /dev/null +++ b/deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh @@ -0,0 +1,30 @@ +#!/bin/sh +set -eu + +export HWLAB_SERVICE_ID="hwlab-codex-api-responses-forwarder" +export HWLAB_ARTIFACT_KIND="bun-command" +export HWLAB_SERVICE_ENTRYPOINT="cmd/hwlab-codex-api-responses-forwarder/main.ts" +export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-git-mirror-checkout}" +export HWLAB_COMMIT_ID="${HWLAB_BOOT_COMMIT:?HWLAB_BOOT_COMMIT is required}" +export HWLAB_REVISION="${HWLAB_BOOT_COMMIT}" +export HWLAB_IMAGE="${HWLAB_ENVIRONMENT_IMAGE:-${HWLAB_IMAGE:-}}" +export HWLAB_IMAGE_DIGEST="${HWLAB_ENVIRONMENT_DIGEST:-${HWLAB_IMAGE_DIGEST:-unknown}}" +export PORT="${PORT:-${HWLAB_CODE_AGENT_CODEX_API_FORWARDER_PORT:-49280}}" +export HWLAB_PORT="$PORT" +export HWLAB_CODE_AGENT_CODEX_API_FORWARDER_PORT="$PORT" + +bun_bin="${HWLAB_BUN_COMMAND:-}" +if [ -z "$bun_bin" ]; then + if command -v bun >/dev/null 2>&1; then + bun_bin="$(command -v bun)" + elif [ -x /usr/local/bin/bun ]; then + bun_bin="/usr/local/bin/bun" + elif [ -x ./node_modules/.bin/bun ]; then + bun_bin="./node_modules/.bin/bun" + else + echo "hwlab-codex-api-responses-forwarder boot failed: bun not found" >&2 + exit 127 + fi +fi + +exec "$bun_bin" run cmd/hwlab-codex-api-responses-forwarder/main.ts diff --git a/deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh b/deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh new file mode 100644 index 00000000..dc2caeee --- /dev/null +++ b/deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh @@ -0,0 +1,29 @@ +#!/bin/sh +set -eu + +export HWLAB_SERVICE_ID="hwlab-deepseek-responses-bridge" +export HWLAB_ARTIFACT_KIND="bun-command" +export HWLAB_SERVICE_ENTRYPOINT="cmd/hwlab-deepseek-responses-bridge/main.ts" +export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-git-mirror-checkout}" +export HWLAB_COMMIT_ID="${HWLAB_BOOT_COMMIT:?HWLAB_BOOT_COMMIT is required}" +export HWLAB_REVISION="${HWLAB_BOOT_COMMIT}" +export HWLAB_IMAGE="${HWLAB_ENVIRONMENT_IMAGE:-${HWLAB_IMAGE:-}}" +export HWLAB_IMAGE_DIGEST="${HWLAB_ENVIRONMENT_DIGEST:-${HWLAB_IMAGE_DIGEST:-unknown}}" +export PORT="${PORT:-4000}" +export HWLAB_PORT="$PORT" + +bun_bin="${HWLAB_BUN_COMMAND:-}" +if [ -z "$bun_bin" ]; then + if command -v bun >/dev/null 2>&1; then + bun_bin="$(command -v bun)" + elif [ -x /usr/local/bin/bun ]; then + bun_bin="/usr/local/bin/bun" + elif [ -x ./node_modules/.bin/bun ]; then + bun_bin="./node_modules/.bin/bun" + else + echo "hwlab-deepseek-responses-bridge boot failed: bun not found" >&2 + exit 127 + fi +fi + +exec "$bun_bin" run cmd/hwlab-deepseek-responses-bridge/main.ts diff --git a/docs/reference/spec-v02-cicd.md b/docs/reference/spec-v02-cicd.md index 6f2dcc7b..c64ca52d 100644 --- a/docs/reference/spec-v02-cicd.md +++ b/docs/reference/spec-v02-cicd.md @@ -129,6 +129,7 @@ devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。` v0.2 保留 runtime service(`hwlab-cloud-api`、`hwlab-cloud-web`、`hwlab-gateway`、`hwlab-edge-proxy`、`hwlab-agent-skills`)必须使用 `env-reuse-git-mirror-checkout`。 只有 package/runtime/env、env image 构建定义、launcher 输入变化或缺少可复用 env digest 时才构建 `-env` 镜像。 service source 或 boot script 的 code-only 变化只更新三变量和 GitOps desired state,不再重新构建业务镜像。 + `hwlab-codex-api-forwarder` sidecar 和 `hwlab-deepseek-proxy` 的 `responses-bridge` 是 `hwlab-cloud-api` 的运行面关联容器,复用 `hwlab-cloud-api-env` 并通过各自 boot script 启动;它们不是独立 service matrix 成员,也不得增加第六个 env-reuse service。 CLI、CaseRun、HWPOD workspace 工具、文档、测试和其他非 service 资产不进入 env-reuse service 触发面。 8. promotion 刷新 `deploy/artifact-catalog.v02.json`,render `deploy/gitops/g14/runtime-v02/**`,只在本 PipelineRun 的 source commit 仍是当前 `origin/v0.2` head 时推送到 `devops-infra` mirror/relay 的 `v0.2-gitops`;若 source branch 已推进,本轮输出 superseded/no-op,写出 `runtime-ready-required=false`,不得回写旧 GitOps revision。 9. `hwlab-g14-v02` 从本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/g14/runtime-v02` 同步到 `hwlab-v02`。 diff --git a/scripts/g14-ci-plan.test.mjs b/scripts/g14-ci-plan.test.mjs index 15d7bf4a..f220fca4 100644 --- a/scripts/g14-ci-plan.test.mjs +++ b/scripts/g14-ci-plan.test.mjs @@ -146,6 +146,8 @@ test("component model uses built-in service paths", () => { "cmd/hwlab-cloud-api/", "cmd/hwlab-codex-api-responses-forwarder/", "cmd/hwlab-deepseek-responses-bridge/", + "deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh", + "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh", "internal/agent/agentrun-dispatch.mjs", "internal/agent/prompts/", "internal/audit/", @@ -560,6 +562,7 @@ async function createFixtureRepo(options = {}) { const serviceIds = options.serviceIds ?? ["hwlab-cloud-api", "hwlab-cloud-web"]; const repo = await mkdtemp(path.join(os.tmpdir(), "hwlab-g14-ci-plan-")); await mkdir(path.join(repo, "cmd/hwlab-cloud-api"), { recursive: true }); + await mkdir(path.join(repo, "cmd/hwlab-codex-api-responses-forwarder"), { recursive: true }); await mkdir(path.join(repo, "cmd/hwlab-deepseek-responses-bridge"), { recursive: true }); await mkdir(path.join(repo, "cmd/hwlab-gateway"), { recursive: true }); await mkdir(path.join(repo, "cmd/hwlab-edge-proxy"), { recursive: true }); @@ -580,6 +583,7 @@ async function createFixtureRepo(options = {}) { await writeFile(path.join(repo, "package.json"), JSON.stringify({ type: "module" }, null, 2)); await writeFile(path.join(repo, "package-lock.json"), JSON.stringify({ lockfileVersion: 3 }, null, 2)); await writeFile(path.join(repo, "cmd/hwlab-cloud-api/main.ts"), "console.log('api');\n"); + await writeFile(path.join(repo, "cmd/hwlab-codex-api-responses-forwarder/main.ts"), "console.log('forwarder');\n"); await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.ts"), "console.log('bridge');\n"); await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.test.ts"), "console.log('test');\n"); await writeFile(path.join(repo, "cmd/hwlab-gateway/main.ts"), "console.log('gateway');\n"); @@ -604,6 +608,8 @@ async function createFixtureRepo(options = {}) { await writeFile(path.join(repo, ".hwlab/hwpod-spec.yaml"), "kind: Hwpod\nmetadata:\n name: d601-f103-v2\nspec:\n nodeBinding:\n nodeId: node-d601-f103-v2\n workspace:\n path: F:\\\\Work\\\\D601-HWLAB\n targetDevice:\n board: D601-F103-V2\n debugProbe:\n type: daplink\n ioProbe:\n uart:\n port: COM9\n"); await writeFile(path.join(repo, "deploy/runtime/boot/hwlab-cloud-web.sh"), "#!/bin/sh\nbun run --cwd web/hwlab-cloud-web build\nexec bun .hwlab-cloud-web-runtime.mjs\n"); await writeFile(path.join(repo, "deploy/runtime/boot/hwlab-cloud-api.sh"), "#!/bin/sh\nexec bun run cmd/hwlab-cloud-api/main.ts\n"); + await writeFile(path.join(repo, "deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh"), "#!/bin/sh\nexec bun run cmd/hwlab-codex-api-responses-forwarder/main.ts\n"); + await writeFile(path.join(repo, "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh"), "#!/bin/sh\nexec bun run cmd/hwlab-deepseek-responses-bridge/main.ts\n"); await writeFile(path.join(repo, "deploy/runtime/boot/hwlab-gateway.sh"), "#!/bin/sh\nexec bun run cmd/hwlab-gateway/main.ts\n"); await writeFile(path.join(repo, "deploy/runtime/boot/hwlab-edge-proxy.sh"), "#!/bin/sh\nexec bun run cmd/hwlab-edge-proxy/main.ts\n"); await writeFile(path.join(repo, "deploy/runtime/boot/hwlab-agent-skills.sh"), "#!/bin/sh\nexec bun .hwlab-agent-skills-runtime.mjs\n"); diff --git a/scripts/g14-gitops-render.mjs b/scripts/g14-gitops-render.mjs index 0c783674..0f4518ac 100644 --- a/scripts/g14-gitops-render.mjs +++ b/scripts/g14-gitops-render.mjs @@ -477,6 +477,48 @@ function bootMetadataForService({ args, catalog, deployService, serviceId, sourc }; } +function envReuseBootShForContainer({ serviceId, container, defaultBootSh }) { + if (serviceId === "hwlab-cloud-api" && container?.name === "hwlab-codex-api-forwarder") { + return "deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh"; + } + return defaultBootSh; +} + +function applyEnvReuseBootEnv(container, bootMetadata, { sourceBranch, bootSh = bootMetadata?.bootSh } = {}) { + if (!container || !bootMetadata) return; + delete container.command; + delete container.args; + container.env ??= []; + upsertEnv(container.env, "HWLAB_RUNTIME_MODE", bootMetadata.runtimeMode); + upsertEnv(container.env, "HWLAB_BOOT_REPO", bootMetadata.bootRepo); + upsertEnv(container.env, "HWLAB_BOOT_COMMIT", bootMetadata.bootCommit); + upsertEnv(container.env, "HWLAB_BOOT_REF", sourceBranch); + upsertEnv(container.env, "HWLAB_BOOT_SH", bootSh); + upsertEnv(container.env, "HWLAB_BOOT_READ_URL", defaultV02GitReadUrl); + upsertEnv(container.env, "HWLAB_ENVIRONMENT_IMAGE", bootMetadata.environmentImage ?? ""); + upsertEnv(container.env, "HWLAB_ENVIRONMENT_DIGEST", bootMetadata.environmentDigest ?? ""); + upsertEnv(container.env, "HWLAB_ENVIRONMENT_INPUT_HASH", bootMetadata.environmentInputHash ?? ""); + upsertEnv(container.env, "HWLAB_CODE_INPUT_HASH", bootMetadata.codeInputHash ?? ""); + upsertEnv(container.env, "HWLAB_IMAGE_DIGEST", bootMetadata.environmentDigest ?? "unknown"); +} + +function annotateEnvReusePodTemplate(podMetadata, bootMetadata) { + if (!podMetadata || !bootMetadata) return; + annotate(podMetadata, { + "hwlab.pikastech.local/runtime-mode": bootMetadata.runtimeMode, + "hwlab.pikastech.local/boot-repo": bootMetadata.bootRepo, + "hwlab.pikastech.local/boot-commit": bootMetadata.bootCommit, + "hwlab.pikastech.local/boot-sh": bootMetadata.bootSh, + "hwlab.pikastech.local/environment-digest": bootMetadata.environmentDigest ?? "not_published", + "hwlab.pikastech.local/environment-input-hash": bootMetadata.environmentInputHash ?? "unknown", + "hwlab.pikastech.local/code-input-hash": bootMetadata.codeInputHash ?? "unknown" + }); + label(podMetadata, { + "hwlab.pikastech.local/runtime-mode": bootMetadata.runtimeMode, + "hwlab.pikastech.local/boot-commit": bootMetadata.bootCommit + }); +} + function runtimeArtifactForService({ catalog, deploy, serviceId, source, registryPrefix, useDeployImages = false, digestPin = false, envReuseServiceIds = null }) { const catalogService = catalogServiceById(catalog, serviceId); if (v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds)) { @@ -943,30 +985,11 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT", runtimeCommit); } if (bootMetadata) { - upsertEnv(container.env, "HWLAB_RUNTIME_MODE", bootMetadata.runtimeMode); - upsertEnv(container.env, "HWLAB_BOOT_REPO", bootMetadata.bootRepo); - upsertEnv(container.env, "HWLAB_BOOT_COMMIT", bootMetadata.bootCommit); - upsertEnv(container.env, "HWLAB_BOOT_REF", sourceBranch); - upsertEnv(container.env, "HWLAB_BOOT_SH", bootMetadata.bootSh); - upsertEnv(container.env, "HWLAB_BOOT_READ_URL", defaultV02GitReadUrl); - upsertEnv(container.env, "HWLAB_ENVIRONMENT_IMAGE", bootMetadata.environmentImage ?? ""); - upsertEnv(container.env, "HWLAB_ENVIRONMENT_DIGEST", bootMetadata.environmentDigest ?? ""); - upsertEnv(container.env, "HWLAB_ENVIRONMENT_INPUT_HASH", bootMetadata.environmentInputHash ?? ""); - upsertEnv(container.env, "HWLAB_CODE_INPUT_HASH", bootMetadata.codeInputHash ?? ""); - upsertEnv(container.env, "HWLAB_IMAGE_DIGEST", bootMetadata.environmentDigest ?? "unknown"); - annotate(podTemplate.metadata, { - "hwlab.pikastech.local/runtime-mode": bootMetadata.runtimeMode, - "hwlab.pikastech.local/boot-repo": bootMetadata.bootRepo, - "hwlab.pikastech.local/boot-commit": bootMetadata.bootCommit, - "hwlab.pikastech.local/boot-sh": bootMetadata.bootSh, - "hwlab.pikastech.local/environment-digest": bootMetadata.environmentDigest ?? "not_published", - "hwlab.pikastech.local/environment-input-hash": bootMetadata.environmentInputHash ?? "unknown", - "hwlab.pikastech.local/code-input-hash": bootMetadata.codeInputHash ?? "unknown" - }); - label(podTemplate.metadata, { - "hwlab.pikastech.local/runtime-mode": bootMetadata.runtimeMode, - "hwlab.pikastech.local/boot-commit": bootMetadata.bootCommit + applyEnvReuseBootEnv(container, bootMetadata, { + sourceBranch, + bootSh: envReuseBootShForContainer({ serviceId, container, defaultBootSh: bootMetadata.bootSh }) }); + if (container.name === serviceId) annotateEnvReusePodTemplate(podTemplate.metadata, bootMetadata); } if (serviceId === "hwlab-cloud-api" || serviceId === "hwlab-edge-proxy") { upsertEnv(container.env, "HWLAB_PUBLIC_ENDPOINT", runtimeEndpoint); @@ -4057,13 +4080,17 @@ remotePort = ${edgeRemotePort} }; } -function deepSeekProxyManifest({ profile = "dev", source, registryPrefix = defaultRegistryPrefix, catalog = null, useDeployImages = false, metricsSidecarSha256 = null } = {}) { +function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14", sourceRepo = defaultSourceRepo, deploy = null, registryPrefix = defaultRegistryPrefix, catalog = null, useDeployImages = false, metricsSidecarSha256 = null } = {}) { const namespace = namespaceNameForProfile(profile); const bridgeServiceId = "hwlab-cloud-api"; const digestPin = profile === "v02"; - const bridgeImage = runtimeImageForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin }); - const bridgeCommit = runtimeCommitForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin }); - const bridgeImageTag = runtimeImageTagForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin }); + const envReuseServiceIds = profile === "v02" ? new Set([bridgeServiceId]) : null; + const bridgeImage = runtimeImageForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); + const bridgeCommit = runtimeCommitForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); + const bridgeImageTag = runtimeImageTagForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); + const bridgeBootMetadata = profile === "v02" && v02EnvReuseEnabled(catalog, bridgeServiceId, envReuseServiceIds) + ? bootMetadataForService({ args: { sourceRepo, registryPrefix }, catalog, deployService: deployServiceForBoot(deploy, bridgeServiceId), serviceId: bridgeServiceId, source }) + : null; const labels = { "app.kubernetes.io/name": "hwlab-deepseek-proxy", "app.kubernetes.io/part-of": "hwlab", @@ -4086,6 +4113,37 @@ function deepSeekProxyManifest({ profile = "dev", source, registryPrefix = defau "hwlab.pikastech.local/bridge-image": bridgeImage, ...v02MetricsSidecarAnnotations(profile === "v02" ? metricsSidecarSha256 : null) }; + const responsesBridgeContainer = { + name: "responses-bridge", + image: bridgeImage, + imagePullPolicy: "IfNotPresent", + command: ["/usr/local/bin/bun", "run", "/app/cmd/hwlab-deepseek-responses-bridge/main.ts"], + env: [ + { name: "PORT", value: "4000" }, + { name: "HWLAB_COMMIT_ID", value: bridgeCommit }, + { name: "HWLAB_IMAGE", value: bridgeImage }, + { name: "HWLAB_IMAGE_TAG", value: bridgeImageTag }, + { name: "HWLAB_DEEPSEEK_BRIDGE_UPSTREAM", value: "http://127.0.0.1:4001" }, + { name: "HWLAB_DEEPSEEK_BRIDGE_MODEL", value: deepSeekProfileModel } + ], + ports: [{ name: "http", containerPort: 4000 }], + readinessProbe: { httpGet: { path: "/health/readiness", port: "http" }, initialDelaySeconds: 10, periodSeconds: 10 }, + livenessProbe: { httpGet: { path: "/health/liveliness", port: "http" }, initialDelaySeconds: 20, periodSeconds: 20 } + }; + if (bridgeBootMetadata) { + applyEnvReuseBootEnv(responsesBridgeContainer, bridgeBootMetadata, { sourceBranch, bootSh: "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh" }); + Object.assign(templateAnnotations, { + "hwlab.pikastech.local/bridge-runtime-mode": bridgeBootMetadata.runtimeMode, + "hwlab.pikastech.local/bridge-boot-repo": bridgeBootMetadata.bootRepo, + "hwlab.pikastech.local/bridge-boot-commit": bridgeBootMetadata.bootCommit, + "hwlab.pikastech.local/bridge-boot-sh": "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh", + "hwlab.pikastech.local/bridge-environment-digest": bridgeBootMetadata.environmentDigest ?? "not_published" + }); + Object.assign(templateLabels, { + "hwlab.pikastech.local/bridge-runtime-mode": bridgeBootMetadata.runtimeMode, + "hwlab.pikastech.local/bridge-boot-commit": bridgeBootMetadata.bootCommit + }); + } return { apiVersion: "v1", kind: "List", @@ -4122,23 +4180,7 @@ function deepSeekProxyManifest({ profile = "dev", source, registryPrefix = defau { name: "moonbridge-config", mountPath: "/config" } ] }], - containers: [{ - name: "responses-bridge", - image: bridgeImage, - imagePullPolicy: "IfNotPresent", - command: ["/usr/local/bin/bun", "run", "/app/cmd/hwlab-deepseek-responses-bridge/main.ts"], - env: [ - { name: "PORT", value: "4000" }, - { name: "HWLAB_COMMIT_ID", value: bridgeCommit }, - { name: "HWLAB_IMAGE", value: bridgeImage }, - { name: "HWLAB_IMAGE_TAG", value: bridgeImageTag }, - { name: "HWLAB_DEEPSEEK_BRIDGE_UPSTREAM", value: "http://127.0.0.1:4001" }, - { name: "HWLAB_DEEPSEEK_BRIDGE_MODEL", value: deepSeekProfileModel } - ], - ports: [{ name: "http", containerPort: 4000 }], - readinessProbe: { httpGet: { path: "/health/readiness", port: "http" }, initialDelaySeconds: 10, periodSeconds: 10 }, - livenessProbe: { httpGet: { path: "/health/liveliness", port: "http" }, initialDelaySeconds: 20, periodSeconds: 20 } - }, { + containers: [responsesBridgeContainer, { name: "moonbridge", image: moonBridgeImage, imagePullPolicy: "IfNotPresent", @@ -4785,7 +4827,7 @@ async function plannedFiles(args) { putJson(`${runtimePath}/services.yaml`, transformServices({ services, namespace, labels: profileLabels, annotations, profile })); putJson(`${runtimePath}/health-contract.yaml`, transformHealthContract(health, namespace, profileLabels, annotations, endpoints.runtimeEndpoint, endpoints.webEndpoint, profile)); putJson(`${runtimePath}/workloads.yaml`, transformWorkloads({ workloads, deploy, catalog: artifactCatalog, source, sourceBranch: args.sourceBranch, registryPrefix: args.registryPrefix, runtimeEndpoint: endpoints.runtimeEndpoint, webEndpoint: endpoints.webEndpoint, profile, useDeployImages: args.useDeployImages, metricsSidecarSha256 })); - putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile, source, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages, metricsSidecarSha256 })); + putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile, source, sourceBranch: args.sourceBranch, sourceRepo: args.sourceRepo, deploy, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages, metricsSidecarSha256 })); if (profile === "v02") putJson(`${runtimePath}/postgres.yaml`, v02PostgresManifest({ migrationSql, source })); if (profile === "v02") putJson(`${runtimePath}/openfga.yaml`, v02OpenFgaManifest({ source })); if (profile === "v02") putJson(`${runtimePath}/observability.yaml`, observabilityManifest({ namespace, labels: profileLabels, annotations, metricsSidecarScript })); diff --git a/scripts/g14-gitops-render.test.ts b/scripts/g14-gitops-render.test.ts index cd736f14..0fd58193 100644 --- a/scripts/g14-gitops-render.test.ts +++ b/scripts/g14-gitops-render.test.ts @@ -352,13 +352,26 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots assert.match(workloads, /"name": "hwlab-v02-bootstrap-admin"/u); const cloudApi = workloadsJson.items.find((item) => item.metadata?.name === "hwlab-cloud-api"); assert.ok(cloudApi, "expected v02 hwlab-cloud-api workload"); - const cloudApiEnv = cloudApi.spec.template.spec.containers[0].env; + const cloudApiContainer = cloudApi.spec.template.spec.containers.find((container) => container.name === "hwlab-cloud-api"); + const codexForwarder = cloudApi.spec.template.spec.containers.find((container) => container.name === "hwlab-codex-api-forwarder"); + assert.ok(cloudApiContainer, "expected cloud-api app container"); + assert.ok(codexForwarder, "expected codex API forwarder sidecar"); + const cloudApiEnv = cloudApiContainer.env; const cloudApiCommit = cloudApiEnv.find((entry) => entry.name === "HWLAB_COMMIT_ID")?.value; + const cloudApiBootEnv = new Map(cloudApiEnv.map((entry) => [entry.name, entry.value])); + assert.equal(cloudApiBootEnv.get("HWLAB_RUNTIME_MODE"), "env-reuse-git-mirror-checkout"); + assert.equal(cloudApiBootEnv.get("HWLAB_BOOT_SH"), "deploy/runtime/boot/hwlab-cloud-api.sh"); + const codexForwarderEnv = new Map(codexForwarder.env.map((entry) => [entry.name, entry.value])); + assert.equal(codexForwarder.command, undefined); + assert.equal(codexForwarderEnv.get("HWLAB_RUNTIME_MODE"), "env-reuse-git-mirror-checkout"); + assert.equal(codexForwarderEnv.get("HWLAB_BOOT_SH"), "deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh"); + assert.equal(codexForwarderEnv.get("HWLAB_BOOT_COMMIT"), cloudApiCommit); + assert.equal(codexForwarderEnv.get("HWLAB_BOOT_READ_URL"), "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"); assert.ok(cloudApiEnv.some((entry) => entry.name === "HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT" && entry.value === cloudApiCommit)); assert.ok(cloudApiEnv.some((entry) => entry.name === "HWLAB_CODE_AGENT_ADAPTER" && entry.value === "agentrun-v01")); assert.equal(cloudApi.spec.template.spec.initContainers?.some((entry) => entry.name === "hwlab-code-agent-workspace-init"), false); assert.equal(cloudApi.spec.template.spec.volumes?.some((entry) => /code-agent-codex|code-agent-workspace/u.test(entry.name)), false); - assert.equal(cloudApi.spec.template.spec.containers[0].volumeMounts?.some((entry) => /code-agent-codex|code-agent-workspace/u.test(entry.name)), false); + assert.equal(cloudApiContainer.volumeMounts?.some((entry) => /code-agent-codex|code-agent-workspace/u.test(entry.name)), false); assert.equal(cloudApiEnv.some((entry) => entry.name === "HWLAB_CODE_AGENT_PROVIDER" && entry.value === "codex-stdio"), false); assert.equal(cloudApiEnv.some((entry) => entry.name === "HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR"), false); assert.ok(cloudApiEnv.some((entry) => entry.name === "UNIDESK_MAIN_SERVER_IP" && entry.value === "http://74.48.78.17:18081")); @@ -368,6 +381,14 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots assert.ok(cloudApiEnv.some((entry) => entry.name === "HWLAB_OPENFGA_MODE" && entry.value === "enforce")); assert.ok(cloudApiEnv.some((entry) => entry.name === "HWLAB_OPENFGA_API_URL" && entry.value === "http://hwlab-openfga.hwlab-v02.svc.cluster.local:8080")); assert.ok(cloudApiEnv.some((entry) => entry.name === "HWLAB_OPENFGA_AUTHN_TOKEN" && entry.valueFrom?.secretKeyRef?.name === "hwlab-v02-openfga" && entry.valueFrom?.secretKeyRef?.key === "authn-preshared-key")); + const deepseekResponsesBridge = deepseekDeployment.spec.template.spec.containers.find((container) => container.name === "responses-bridge"); + assert.ok(deepseekResponsesBridge, "expected DeepSeek responses bridge container"); + const deepseekBridgeEnv = new Map(deepseekResponsesBridge.env.map((entry) => [entry.name, entry.value])); + assert.equal(deepseekResponsesBridge.command, undefined); + assert.equal(deepseekBridgeEnv.get("HWLAB_RUNTIME_MODE"), "env-reuse-git-mirror-checkout"); + assert.equal(deepseekBridgeEnv.get("HWLAB_BOOT_SH"), "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh"); + assert.equal(deepseekBridgeEnv.get("HWLAB_BOOT_COMMIT"), cloudApiCommit); + assert.equal(deepseekBridgeEnv.get("HWLAB_BOOT_READ_URL"), "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"); assert.equal(workloadsJson.items.some((item) => item.metadata?.labels?.["hwlab.pikastech.local/service-id"] === "hwlab-agent-worker"), false); const kustomization = await readFile(path.join(outDir, "runtime-v02", "kustomization.yaml"), "utf8"); assert.doesNotMatch(kustomization, /code-agent-codex-config/u); diff --git a/scripts/src/g14-ci-plan-lib.mjs b/scripts/src/g14-ci-plan-lib.mjs index a91393f7..4b04b793 100644 --- a/scripts/src/g14-ci-plan-lib.mjs +++ b/scripts/src/g14-ci-plan-lib.mjs @@ -61,6 +61,8 @@ const serviceSpecificPaths = Object.freeze({ "cmd/hwlab-cloud-api/", "cmd/hwlab-codex-api-responses-forwarder/", "cmd/hwlab-deepseek-responses-bridge/", + "deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh", + "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh", "internal/cloud/", "internal/db/", "internal/audit/",