diff --git a/deploy/deploy.json b/deploy/deploy.json index c9981666..0f03249d 100644 --- a/deploy/deploy.json +++ b/deploy/deploy.json @@ -136,10 +136,12 @@ "imageTagMode": "full", "envReuseServices": [ "hwlab-cloud-web", + "hwlab-agent-worker", "hwlab-device-pod" ], "bootScripts": { "hwlab-cloud-web": "deploy/runtime/boot/hwlab-cloud-web.sh", + "hwlab-agent-worker": "deploy/runtime/boot/hwlab-agent-worker.sh", "hwlab-device-pod": "deploy/runtime/boot/hwlab-device-pod.sh" }, "services": [ diff --git a/deploy/runtime/boot/hwlab-agent-worker.sh b/deploy/runtime/boot/hwlab-agent-worker.sh new file mode 100644 index 00000000..d208a862 --- /dev/null +++ b/deploy/runtime/boot/hwlab-agent-worker.sh @@ -0,0 +1,29 @@ +#!/bin/sh +set -eu + +export HWLAB_SERVICE_ID="hwlab-agent-worker" +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}}" + +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-agent-worker boot failed: bun not found" >&2 + exit 127 + fi +fi + +if [ "$#" -eq 0 ]; then + set -- health +fi + +exec "$bun_bin" cmd/hwlab-agent-worker/main.ts "$@" diff --git a/docs/reference/spec-v02-cicd.md b/docs/reference/spec-v02-cicd.md index 81ed09bb..265c1427 100644 --- a/docs/reference/spec-v02-cicd.md +++ b/docs/reference/spec-v02-cicd.md @@ -115,7 +115,7 @@ devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。` 5. affected service 通过 BuildKit 发布到 G14 本地 registry;reused service 复用 catalog digest。 所有 selected service 的 build TaskRun 都只依赖 `plan-artifacts`,不按 service 串行排队,也不设置 8 并发或其他 Pipeline 级限流。 实际并发由 Tekton controller、G14 节点资源、PVC I/O、BuildKit sidecar 和本地 registry 承载能力决定。 - `hwlab-cloud-web` 和 `hwlab-device-pod` 必须使用 `env-reuse-git-mirror-checkout`。 + `hwlab-cloud-web`、`hwlab-agent-worker` 和 `hwlab-device-pod` 必须使用 `env-reuse-git-mirror-checkout`。 只有 package/runtime/env 输入变化时才构建 `-env` 镜像。 纯前端源码、Device Pod 源码或 boot code 变化只更新三变量和 GitOps desired state, 不再重新构建业务镜像。 @@ -200,7 +200,7 @@ Git 读写都走 `devops-infra` 本地 mirror/relay。读路径用 HTTP mirror c 任何仍需要访问 GitHub canonical remote 的 SSH 操作都必须显式走 G14 proxy。Host SSH config 可以使用 OpenBSD `nc -X connect -x 127.0.0.1:10808 %h %p`;Tekton `GIT_SSH_COMMAND`、git-mirror `sync`/`flush` 和其他容器内 `git@github.com` 或 `ssh://git@ssh.github.com:443` 路径必须使用 repo-owned Node HTTP CONNECT ProxyCommand,并保留短 `ConnectTimeout` 与 `ServerAlive*`。只改 `HTTP_PROXY`/`HTTPS_PROXY` 对 OpenSSH 无效;BusyBox `nc` 不支持 `-X connect`,不得再把 `ssh.github.com:443` 当作等价 proxy 或让 GitHub SSH 直连反复 60s 超时。 -env image 复用把系统依赖和业务代码身份分离。`environmentDigest` 表示可复用运行环境,`HWLAB_BOOT_REPO`、`HWLAB_BOOT_COMMIT` 和 `HWLAB_BOOT_SH` 表示本次代码启动身份。code-only 变更只更新 boot metadata 和 runtime identity,不发布新 env image;只有真实 env 输入变化或缺少可复用 env digest 时才进入 env rebuild。`package.json` 只按 dependency/runtime 字段参与 env/runtime hash,`scripts` 只属于开发入口,不得因为清理旧门禁脚本而重建所有 service 或重建 env image。 +env image 复用把系统依赖和业务代码身份分离。`environmentDigest` 表示可复用运行环境,`HWLAB_BOOT_REPO`、`HWLAB_BOOT_COMMIT` 和 `HWLAB_BOOT_SH` 表示本次代码启动身份。code-only 变更只更新 boot metadata 和 runtime identity,不发布新 env image;只有真实 env 输入变化或缺少可复用 env digest 时才进入 env rebuild。`hwlab-agent-worker` 是 suspended Job template,也必须复用 env image:worker 代码变化只能更新 boot commit 和 Job template identity,不能重新构建 `hwlab-agent-worker` service image。`package.json` 只按 dependency/runtime 字段参与 env/runtime hash,`scripts` 只属于开发入口,不得因为清理旧门禁脚本而重建所有 service 或重建 env image。 BuildKit publish 采用 service 级全并行 fan-out。 每个 service build task 拥有独立 BuildKit sidecar、独立 service workdir 和独立 report 文件,均从同一 `plan-artifacts` 结果判断是否需要执行。 diff --git a/docs/reference/spec-v02-hwlab-agent-worker.md b/docs/reference/spec-v02-hwlab-agent-worker.md index 16565896..b12478ed 100644 --- a/docs/reference/spec-v02-hwlab-agent-worker.md +++ b/docs/reference/spec-v02-hwlab-agent-worker.md @@ -13,6 +13,7 @@ - `cmd/hwlab-agent-worker/main.ts` 提供 CLI 命令 `health` 和 `dry-run/smoke`。 - `internal/agent/runtime.mjs` 提供 `runLocalWorkerDryRun`,读取 manager 创建的本地 session 状态并写入 evidence。 - Job template 通过 env 注入 `HWLAB_AGENT_SESSION_ID`、`HWLAB_SKILL_COMMIT_ID` 和 `HWLAB_SKILL_VERSION`。 +- v0.2 CI/CD 中 worker 使用 `env-reuse-git-mirror-checkout`:环境镜像只承载 runtime、依赖、launcher 和 git client,实际 worker 代码由 `HWLAB_BOOT_REPO`、`HWLAB_BOOT_COMMIT`、`HWLAB_BOOT_SH=deploy/runtime/boot/hwlab-agent-worker.sh` 从 `devops-infra` git mirror checkout 后启动。worker code-only 变更不得触发 `hwlab-agent-worker` service image BuildKit 重建,只允许更新 boot commit 和 Job template identity。 ## API 接口说明 @@ -38,6 +39,7 @@ | --- | --- | --- | | CLI health/dry-run | 已实现 | worker 以 JSON 输出。 | | suspended Job template | 已实现 | runtime-v02 中存在 `hwlab-agent-worker-template`。 | +| v0.2 env-reuse 启动 | 已实现 | `deploy/runtime/boot/hwlab-agent-worker.sh` 通过 git mirror checkout 后执行 worker CLI;code-only 变更复用 env image。 | | session-scoped evidence 写入 | 已实现 | 本地 dry-run 覆盖 trace/evidence。 | | 长驻 HTTP API | 不适用 | worker 不是长驻服务。 | | 生产调度与真实执行 | 未完全实现 | 当前仍是 skeleton/dry-run 模式。 | diff --git a/scripts/g14-ci-plan.test.mjs b/scripts/g14-ci-plan.test.mjs index 8b13e2dd..3eb172e8 100644 --- a/scripts/g14-ci-plan.test.mjs +++ b/scripts/g14-ci-plan.test.mjs @@ -354,6 +354,98 @@ test("v02 planner marks cloud-web code-only change as env reuse rollout", async assert.equal(plan.ciCdPlan.noImageBuildReason, "env-reuse-code-only-rollout"); }); +test("v02 planner marks agent-worker code-only change as env reuse rollout", async () => { + const repo = await createFixtureRepo({ includeDevicePod: true, includeAgentWorker: true }); + const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); + const initialPlan = await createG14CiPlan({ + repoRoot: repo, + lane: "v02", + baseRef: "HEAD", + targetRef: initialSha, + artifactCatalogPath: "deploy/artifact-catalog.v02.json", + services: ["hwlab-agent-worker"] + }); + const initialWorker = initialPlan.services.find((service) => service.serviceId === "hwlab-agent-worker"); + await writeFile(path.join(repo, "deploy/artifact-catalog.v02.json"), JSON.stringify(createCatalogFixture("ready", { + agentWorkerEnvironmentInputHash: initialWorker.environmentInputHash, + agentWorkerCodeInputHash: initialWorker.codeInputHash, + agentWorkerBootCommit: initialPlan.sourceCommitId + }), null, 2)); + await git(repo, ["add", "deploy/artifact-catalog.v02.json"]); + await git(repo, ["commit", "-m", "seed v02 worker catalog"]); + + await writeFile(path.join(repo, "cmd/hwlab-agent-worker/main.ts"), "console.log('worker v2');\n"); + await git(repo, ["add", "."]); + await git(repo, ["commit", "-m", "change agent worker code"]); + + const plan = await createG14CiPlan({ + repoRoot: repo, + lane: "v02", + baseRef: "HEAD~1", + targetRef: "HEAD", + artifactCatalogPath: "deploy/artifact-catalog.v02.json", + services: ["hwlab-agent-worker"] + }); + const worker = plan.services.find((service) => service.serviceId === "hwlab-agent-worker"); + assert.equal(worker.runtimeMode, "env-reuse-git-mirror-checkout"); + assert.equal(worker.envChanged, false); + assert.equal(worker.codeChanged, true); + assert.equal(worker.buildRequired, false); + assert.equal(worker.bootSh, "deploy/runtime/boot/hwlab-agent-worker.sh"); + assert.deepEqual(plan.affectedServices, ["hwlab-agent-worker"]); + assert.deepEqual(plan.rolloutServices, ["hwlab-agent-worker"]); + assert.deepEqual(plan.buildServices, []); + assert.equal(plan.imageBuildRequired, false); + assert.equal(plan.ciCdPlan.noImageBuildReason, "env-reuse-code-only-rollout"); +}); + +test("v02 planner rebuilds agent-worker env image only for runtime dependency changes", async () => { + const repo = await createFixtureRepo({ includeDevicePod: true, includeAgentWorker: true }); + const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); + const initialPlan = await createG14CiPlan({ + repoRoot: repo, + lane: "v02", + baseRef: "HEAD", + targetRef: initialSha, + artifactCatalogPath: "deploy/artifact-catalog.v02.json", + services: ["hwlab-agent-worker"] + }); + const initialWorker = initialPlan.services.find((service) => service.serviceId === "hwlab-agent-worker"); + await writeFile(path.join(repo, "deploy/artifact-catalog.v02.json"), JSON.stringify(createCatalogFixture("ready", { + agentWorkerEnvironmentInputHash: initialWorker.environmentInputHash, + agentWorkerCodeInputHash: initialWorker.codeInputHash, + agentWorkerBootCommit: initialPlan.sourceCommitId + }), null, 2)); + await git(repo, ["add", "deploy/artifact-catalog.v02.json"]); + await git(repo, ["commit", "-m", "seed v02 worker catalog"]); + + await writeFile(path.join(repo, "package.json"), JSON.stringify({ + type: "module", + dependencies: { + "@openai/codex": "^0.129.0" + } + }, null, 2)); + await git(repo, ["add", "package.json"]); + await git(repo, ["commit", "-m", "change worker env dependency"]); + + const plan = await createG14CiPlan({ + repoRoot: repo, + lane: "v02", + baseRef: "HEAD~1", + targetRef: "HEAD", + artifactCatalogPath: "deploy/artifact-catalog.v02.json", + services: ["hwlab-agent-worker"] + }); + const worker = plan.services.find((service) => service.serviceId === "hwlab-agent-worker"); + assert.equal(worker.runtimeMode, "env-reuse-git-mirror-checkout"); + assert.equal(worker.envChanged, true); + assert.equal(worker.buildRequired, true); + assert.deepEqual(plan.affectedServices, ["hwlab-agent-worker"]); + assert.deepEqual(plan.buildServices, ["hwlab-agent-worker"]); + assert.ok(worker.reason.includes("environment-input-changed")); + assert.ok(worker.reason.includes("runtime-deps-changed")); +}); + test("v02 planner keeps hwlab-cli-only changes out of runtime service builds", async () => { const repo = await createFixtureRepo({ includeDevicePod: true }); const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); @@ -458,9 +550,11 @@ async function createFixtureRepo(options = {}) { const deployServices = options.deployServices !== false; const k3sServiceMappings = options.k3sServiceMappings === true; const includeDevicePod = options.includeDevicePod === true; + const includeAgentWorker = options.includeAgentWorker === true; 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-deepseek-responses-bridge"), { recursive: true }); + await mkdir(path.join(repo, "cmd/hwlab-agent-worker"), { recursive: true }); await mkdir(path.join(repo, "web/hwlab-cloud-web"), { recursive: true }); await mkdir(path.join(repo, "internal/dev-entrypoint"), { recursive: true }); await mkdir(path.join(repo, "internal/device-pod"), { recursive: true }); @@ -479,6 +573,7 @@ async function createFixtureRepo(options = {}) { await writeFile(path.join(repo, "cmd/hwlab-cloud-api/main.ts"), "console.log('api');\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-agent-worker/main.ts"), "console.log('worker');\n"); await writeFile(path.join(repo, "web/hwlab-cloud-web/index.html"), "\n"); await writeFile(path.join(repo, "internal/dev-entrypoint/artifact-runtime.mjs"), "export const artifactRuntime = 1;\n"); await writeFile(path.join(repo, "internal/dev-entrypoint/cloud-web-runtime.mjs"), "export const runtime = 1;\n"); @@ -493,6 +588,7 @@ async function createFixtureRepo(options = {}) { await writeFile(path.join(repo, "skills/device-pod-cli/scripts/device-pod-cli.mjs"), "console.log('skill wrapper');\n"); await writeFile(path.join(repo, "skills/device-pod-cli/assets/device-host-cli.mjs"), "console.log('host cli');\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-agent-worker.sh"), "#!/bin/sh\nexec bun cmd/hwlab-agent-worker/main.ts \"$@\"\n"); await writeFile(path.join(repo, "deploy/runtime/boot/hwlab-device-pod.sh"), "#!/bin/sh\nexec bun cmd/hwlab-device-pod/main.ts\n"); await writeFile(path.join(repo, "deploy/runtime/launcher/hwlab-env-reuse-launcher.ts"), "console.log('launcher');\n"); await writeFile(path.join(repo, "skills/hwlab-agent-runtime/SKILL.md"), "agent runtime skill\n"); @@ -506,7 +602,7 @@ async function createFixtureRepo(options = {}) { baseRef: "HEAD", targetRef: "HEAD", artifactCatalogPath: "deploy/nonexistent-artifact-catalog.json", - services: ["hwlab-cloud-api", "hwlab-cloud-web", ...(includeDevicePod ? ["hwlab-device-pod"] : [])] + services: ["hwlab-cloud-api", "hwlab-cloud-web", ...(includeAgentWorker ? ["hwlab-agent-worker"] : []), ...(includeDevicePod ? ["hwlab-device-pod"] : [])] }); const componentInputHashes = Object.fromEntries(initialPlan.services.map((service) => [service.serviceId, service.componentInputHash])); const catalog = createCatalogFixture(catalogMode, { sourceCommitId: initialSha, componentInputHashes }); @@ -541,9 +637,10 @@ function createDeployFixture({ deployServices, k3sServiceMappings, includeDevice lanes: { v02: { sourceRepo: "git@github.com:pikasTech/HWLAB.git", - envReuseServices: ["hwlab-cloud-web", "hwlab-device-pod"], + envReuseServices: ["hwlab-cloud-web", "hwlab-agent-worker", "hwlab-device-pod"], bootScripts: { "hwlab-cloud-web": "deploy/runtime/boot/hwlab-cloud-web.sh", + "hwlab-agent-worker": "deploy/runtime/boot/hwlab-agent-worker.sh", "hwlab-device-pod": "deploy/runtime/boot/hwlab-device-pod.sh" } } @@ -571,7 +668,7 @@ function createDeployFixture({ deployServices, k3sServiceMappings, includeDevice function createCatalogFixture(mode, options = {}) { const digest = mode === "ready" ? `sha256:${"1".repeat(64)}` : "not_published"; return { - services: ["hwlab-cloud-api", "hwlab-cloud-web", "hwlab-device-pod"].map((serviceId) => ({ + services: ["hwlab-cloud-api", "hwlab-cloud-web", "hwlab-agent-worker", "hwlab-device-pod"].map((serviceId) => ({ serviceId, commitId: "abc1234", sourceCommitId: options.sourceCommitId ?? "abc1234", @@ -581,20 +678,26 @@ function createCatalogFixture(mode, options = {}) { ...((options.componentInputHashes?.[serviceId] || (serviceId === "hwlab-cloud-api" && options.cloudApiComponentInputHash)) ? { componentInputHash: options.cloudApiComponentInputHash ?? options.componentInputHashes?.[serviceId] } : {}), - ...((serviceId === "hwlab-cloud-web" || serviceId === "hwlab-device-pod") ? { + ...((serviceId === "hwlab-cloud-web" || serviceId === "hwlab-agent-worker" || serviceId === "hwlab-device-pod") ? { runtimeMode: "env-reuse-git-mirror-checkout", envReuse: true, environmentImage: `127.0.0.1:5000/hwlab/${serviceId}-env:env-abc1234`, environmentDigest: digest, environmentInputHash: serviceId === "hwlab-cloud-web" ? options.cloudWebEnvironmentInputHash ?? "e".repeat(64) + : serviceId === "hwlab-agent-worker" + ? options.agentWorkerEnvironmentInputHash ?? "e".repeat(64) : options.devicePodEnvironmentInputHash ?? "e".repeat(64), codeInputHash: serviceId === "hwlab-cloud-web" ? options.cloudWebCodeInputHash ?? "c".repeat(64) + : serviceId === "hwlab-agent-worker" + ? options.agentWorkerCodeInputHash ?? "c".repeat(64) : options.devicePodCodeInputHash ?? "c".repeat(64), bootRepo: "git@github.com:pikasTech/HWLAB.git", bootCommit: serviceId === "hwlab-cloud-web" ? options.cloudWebBootCommit ?? "abc1234" + : serviceId === "hwlab-agent-worker" + ? options.agentWorkerBootCommit ?? "abc1234" : options.devicePodBootCommit ?? "abc1234", bootSh: `deploy/runtime/boot/${serviceId}.sh` } : {}) diff --git a/scripts/g14-gitops-render.mjs b/scripts/g14-gitops-render.mjs index 92b9713f..f06e0459 100644 --- a/scripts/g14-gitops-render.mjs +++ b/scripts/g14-gitops-render.mjs @@ -416,7 +416,8 @@ function catalogServiceById(catalog, serviceId) { return (catalog?.services ?? []).find((service) => service?.serviceId === serviceId) ?? null; } -function v02EnvReuseEnabled(catalog, serviceId) { +function v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds = null) { + if (envReuseServiceIds?.has(serviceId)) return true; const service = catalogServiceById(catalog, serviceId); return service?.runtimeMode === v02EnvReuseRuntimeMode || service?.envReuse === true; } @@ -458,17 +459,17 @@ function bootMetadataForService({ args, catalog, deployService, serviceId, sourc }; } -function runtimeArtifactForService({ catalog, serviceId, source, registryPrefix, useDeployImages = false, digestPin = false }) { +function runtimeArtifactForService({ catalog, deploy, serviceId, source, registryPrefix, useDeployImages = false, digestPin = false, envReuseServiceIds = null }) { const catalogService = catalogServiceById(catalog, serviceId); - if (catalogService?.runtimeMode === v02EnvReuseRuntimeMode || catalogService?.envReuse === true) { - const environmentImage = catalogService.environmentImage ?? imageFor(registryPrefix, `${serviceId}-env`, `env-${String(catalogService.environmentInputHash ?? source.full).slice(0, 12)}`); - const runtimeImage = digestPin && useDeployImages ? digestPinnedImage(environmentImage, catalogService.environmentDigest ?? catalogService.digest) : environmentImage; + if (v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds)) { + const environmentImage = catalogService?.environmentImage ?? imageFor(registryPrefix, `${serviceId}-env`, `env-${String(catalogService?.environmentInputHash ?? source.full).slice(0, 12)}`); + const runtimeImage = digestPin && useDeployImages ? digestPinnedImage(environmentImage, catalogService?.environmentDigest ?? catalogService?.digest) : environmentImage; return { image: runtimeImage, imageTag: imageTagFromReference(environmentImage) ?? source.imageTag ?? source.short, - commit: catalogService.bootCommit ?? source.full, + commit: catalogService?.bootCommit ?? source.full, runtimeMode: v02EnvReuseRuntimeMode, - environmentDigest: catalogService.environmentDigest ?? catalogService.digest ?? null + environmentDigest: catalogService?.environmentDigest ?? catalogService?.digest ?? null }; } const fallbackImageTag = source.imageTag ?? source.short; @@ -753,6 +754,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = const profileLabel = runtimeLabelForProfile(profile); const gitopsTarget = gitopsTargetForProfile(profile); const digestPin = profile === "v02"; + const envReuseServiceIds = envReuseServiceIdsForLane(deploy, profile); const stableRuntimeLabels = { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/environment": profileLabel, @@ -784,7 +786,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = const podTemplate = item?.spec?.template; if (!podTemplate?.spec?.containers) continue; const templateServiceId = serviceIdForWorkload(item, podTemplate.spec.containers[0]); - const templateArtifact = runtimeArtifactForService({ catalog, serviceId: templateServiceId, source, registryPrefix, useDeployImages, digestPin }); + const templateArtifact = runtimeArtifactForService({ catalog, deploy, serviceId: templateServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); const templateSourceCommit = profile === "v02" ? (templateArtifact.commit ?? source.full) : source.full; const templateArtifactCommit = templateArtifact.commit; label(podTemplate.metadata ??= {}, { @@ -808,8 +810,8 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = const serviceId = serviceIdForWorkload(item, container); const deployService = deployServices.get(serviceId); if (!deployService) continue; - const artifact = runtimeArtifactForService({ catalog, serviceId, source, registryPrefix, useDeployImages, digestPin }); - const envReuse = profile === "v02" && v02EnvReuseEnabled(catalog, serviceId); + const artifact = runtimeArtifactForService({ catalog, deploy, serviceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); + const envReuse = profile === "v02" && v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds); const bootMetadata = envReuse ? bootMetadataForService({ args: { sourceRepo: deploy?.lanes?.v02?.sourceRepo || defaultSourceRepo, registryPrefix }, catalog, deployService, serviceId, source }) : null; const image = artifact.image; const runtimeCommit = artifact.commit; diff --git a/scripts/g14-gitops-render.test.ts b/scripts/g14-gitops-render.test.ts index 5497dadc..318c9eff 100644 --- a/scripts/g14-gitops-render.test.ts +++ b/scripts/g14-gitops-render.test.ts @@ -229,9 +229,15 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots assert.ok(cloudApi, "expected v02 hwlab-cloud-api workload"); const cloudApiEnv = cloudApi.spec.template.spec.containers[0].env; assert.ok(cloudApiEnv.some((entry) => entry.name === "HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT" && entry.value === sourceRevision)); - const agentWorker = workloadsJson.items.find((item) => item.metadata?.name === "hwlab-agent-worker"); + const agentWorker = workloadsJson.items.find((item) => item.metadata?.labels?.["hwlab.pikastech.local/service-id"] === "hwlab-agent-worker"); + assert.ok(agentWorker, "expected v02 hwlab-agent-worker suspended Job template"); const agentWorkerEnv = agentWorker?.spec?.template?.spec?.containers?.[0]?.env ?? []; - assert.equal(agentWorkerEnv.some((entry) => entry.name === "HWLAB_BOOT_COMMIT"), false, "agent-worker must not be switched to env reuse by device-pod work"); + assert.ok(agentWorkerEnv.some((entry) => entry.name === "HWLAB_RUNTIME_MODE" && entry.value === "env-reuse-git-mirror-checkout")); + assert.ok(agentWorkerEnv.some((entry) => entry.name === "HWLAB_BOOT_REPO" && entry.value === "git@github.com:pikasTech/HWLAB.git")); + assert.ok(agentWorkerEnv.some((entry) => entry.name === "HWLAB_BOOT_COMMIT" && entry.value === sourceRevision)); + assert.ok(agentWorkerEnv.some((entry) => entry.name === "HWLAB_BOOT_SH" && entry.value === "deploy/runtime/boot/hwlab-agent-worker.sh")); + assert.equal(agentWorker?.spec?.template?.metadata?.annotations?.["hwlab.pikastech.local/runtime-mode"], "env-reuse-git-mirror-checkout"); + assert.equal(agentWorker?.spec?.template?.metadata?.annotations?.["hwlab.pikastech.local/boot-commit"], sourceRevision); const workloadTemplates = workloadsJson.items.filter((item) => item.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/source-commit"]); assert.ok(workloadTemplates.length > 0, "expected rendered pod-template source labels"); assert.equal(devicePod.spec.template.metadata.labels["hwlab.pikastech.local/source-commit"], sourceRevision); diff --git a/scripts/src/g14-ci-plan-lib.mjs b/scripts/src/g14-ci-plan-lib.mjs index 6c405e31..99389390 100644 --- a/scripts/src/g14-ci-plan-lib.mjs +++ b/scripts/src/g14-ci-plan-lib.mjs @@ -83,8 +83,6 @@ const serviceSpecificPaths = Object.freeze({ "hwlab-agent-worker": [ "cmd/hwlab-agent-worker/", "internal/agent/", - "internal/db/", - "internal/audit/", "skills/hwlab-agent-runtime/" ], "hwlab-device-pod": ["cmd/hwlab-device-pod/", "internal/device-pod/"],