From 187fb80a5d10022fb2ddca213bf6233251b7e41f Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Mon, 8 Jun 2026 22:19:30 +0800 Subject: [PATCH] refactor: make v0.3 runtime node-scoped --- .gitignore | 4 +- AGENTS.md | 26 +- deploy/deploy.yaml | 6 +- deploy/frp/README.md | 12 +- .../frp/{frpc.g14.toml => frpc.node-dev.toml} | 4 +- ...frpc.g14-prod.toml => frpc.node-prod.toml} | 4 +- docs/reference/cloud-workbench.md | 2 +- docs/reference/dev-runtime-boundary.md | 8 +- docs/reference/dev-runtime-hotfix-runbook.md | 2 +- ...erformance.md => node-cicd-performance.md} | 6 +- ...g14-gitops-cicd.md => node-gitops-cicd.md} | 82 ++-- docs/reference/spec-lane-expansion.md | 20 +- docs/reference/spec-v02-cicd.md | 66 ++-- .../spec-v02-observability-monitoring.md | 6 +- docs/reference/spec-v02-services.md | 4 +- internal/agent/agentrun-dispatch.mjs | 12 +- internal/agent/agentrun-dispatch.test.mjs | 13 +- internal/cloud/code-agent-agentrun-adapter.ts | 43 ++- package.json | 2 +- scripts/artifact-publish.mjs | 58 +-- scripts/{g14-ci-plan.mjs => ci-plan.mjs} | 14 +- ...{g14-ci-plan.test.mjs => ci-plan.test.mjs} | 71 ++-- scripts/g14-artifact-publish.mjs | 4 - ...14-gitops-render.mjs => gitops-render.mjs} | 357 +++++++++--------- ...s-render.test.ts => gitops-render.test.ts} | 84 ++++- scripts/hwlab-lane-expand.mjs | 9 +- scripts/refresh-artifact-catalog.mjs | 10 +- scripts/refresh-artifact-catalog.test.mjs | 6 +- .../src/artifact-runtime-readiness-guard.mjs | 12 +- scripts/src/check-plan.mjs | 4 +- .../{g14-ci-plan-lib.mjs => ci-plan-lib.mjs} | 12 +- scripts/src/deploy-desired-state-plan.mjs | 2 +- scripts/src/dev-base-image-preflight.mjs | 2 +- scripts/src/dev-edge-health-smoke-lib.mjs | 2 +- .../src/dev-evidence-blocker-aggregator.mjs | 2 +- scripts/src/dev-gate-preflight.mjs | 12 +- scripts/src/runtime-lane.ts | 8 +- scripts/validate-runtime-boundary.mjs | 8 +- 38 files changed, 549 insertions(+), 450 deletions(-) rename deploy/frp/{frpc.g14.toml => frpc.node-dev.toml} (83%) rename deploy/frp/{frpc.g14-prod.toml => frpc.node-prod.toml} (81%) rename docs/reference/{g14-cicd-performance.md => node-cicd-performance.md} (97%) rename docs/reference/{g14-gitops-cicd.md => node-gitops-cicd.md} (74%) rename scripts/{g14-ci-plan.mjs => ci-plan.mjs} (75%) rename scripts/{g14-ci-plan.test.mjs => ci-plan.test.mjs} (96%) delete mode 100644 scripts/g14-artifact-publish.mjs rename scripts/{g14-gitops-render.mjs => gitops-render.mjs} (92%) rename scripts/{g14-gitops-render.test.ts => gitops-render.test.ts} (90%) rename scripts/src/{g14-ci-plan-lib.mjs => ci-plan-lib.mjs} (99%) diff --git a/.gitignore b/.gitignore index a26ad803..99bf1bbe 100644 --- a/.gitignore +++ b/.gitignore @@ -39,8 +39,8 @@ yarn-error.log* .worktree/ .preserved-untracked/ -# G14 generated GitOps desired state is stored on the G14-gitops branch. -/deploy/gitops/g14/ +# Generated GitOps desired state is stored on the GitOps branch. +/deploy/gitops/node/ # v0.2 generated artifact catalog is stored only on the v0.2-gitops branch. /deploy/artifact-catalog.v02.json diff --git a/AGENTS.md b/AGENTS.md index ba258acb..4f2b95bb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,11 +30,11 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥 ## P0 G14 原生 k8s/GitOps 运行面归一 -- G14 是当前 HWLAB DEV/PROD 原生 k8s 与 GitOps 运行面真相;`hwlab-dev` 与 `hwlab-prod` 均由 G14 k3s、Tekton、`G14-gitops` 和 Argo CD 管理,详见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。 +- G14 是当前 HWLAB DEV/PROD 原生 k8s 与 GitOps 运行面真相;`hwlab-dev` 与 `hwlab-prod` 均由 G14 k3s、Tekton、`G14-gitops` 和 Argo CD 管理,详见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。 - `v0.2` 是 G14 上的加法扩容线:固定分支 `v0.2`、固定开发 workspace `G14:/root/hwlab-v02`、固定 CI/CD source repo `G14:/root/hwlab-v02-cicd.git`、固定 namespace `hwlab-v02`、FRP 入口 `74.48.78.17:19666/19667`;创建和集成 `v0.2` 不得删除、重命名、复用或改义现有 `G14`/`G14-gitops` 分支、`hwlab-dev`/`hwlab-prod` namespace 或 17666/17667、18666/18667 入口。 - `v0.2` 的人写 deploy/runtime 配置单一出处是 `deploy/deploy.yaml`;`deploy/deploy.json` 不再是 v0.2 兼容源。所有脚本、renderer、planner 和 CLI 必须通过 `scripts/src/structured-config.mjs` / `scripts/src/deploy-config.mjs` 这类格式无关读写层消费配置,禁止把 YAML parser import 或 ad hoc YAML 解析散落到业务脚本里。 -- G14 GitOps render 若改变 Argo Application、AppProject、runtime path 或 DEV/PROD 拆分目录,必须同步应用 `deploy/gitops/g14/argocd/project.yaml`、`application-dev.yaml` 和 `application-prod.yaml`;只推 `G14-gitops` 分支不等于 Argo 已切到新 path。 -- D601 HWLAB DEV、D601 `dev-cd-apply`、`ci-publish` 和旧 `main` JS 脚本式 CI/CD 已退出 G14 发布入口;G14 Tekton 只能通过 `scripts/g14-artifact-publish.mjs` 作为集群内 build/push helper,再由 GitOps/Argo CD rollout,新开发、发布、验收、文档和运行面实验不得把 D601 或 legacy 脚本 CD 当作当前 HWLAB runtime source-of-truth。 +- G14 GitOps render 若改变 Argo Application、AppProject、runtime path 或 DEV/PROD 拆分目录,必须同步应用 `deploy/gitops/node/argocd/project.yaml`、`application-dev.yaml` 和 `application-prod.yaml`;只推 `G14-gitops` 分支不等于 Argo 已切到新 path。 +- D601 HWLAB DEV、D601 `dev-cd-apply`、`ci-publish` 和旧 `main` JS 脚本式 CI/CD 已退出 G14 发布入口;G14 Tekton 只能通过 `scripts/artifact-publish.mjs` 作为集群内 build/push helper,再由 GitOps/Argo CD rollout,新开发、发布、验收、文档和运行面实验不得把 D601 或 legacy 脚本 CD 当作当前 HWLAB runtime source-of-truth。 - G14 k3s 操作必须通过 UniDesk route `G14:k3s` 执行;不得用 D601 kubeconfig、Docker Desktop Kubernetes、master server 本地 check/build 或旧 JS CD 结果作为 G14 DEV/PROD 通过证据。 ## P0 HyueAPI Direct NO_PROXY 规则 @@ -49,7 +49,7 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥 ## P0 Legacy CD 删除纪律 - 旧 D601 `ci-publish`、`dev-cd-apply`、`dev-deploy-apply` 和 `dev-artifact-publish` 脚本入口已经从当前发布面删除;不要恢复这些文件、CLI 子命令或文档入口。 -- 当前发布只走 G14 k3s Tekton + `G14-gitops` + Argo CD;镜像构建 helper 是 `scripts/g14-artifact-publish.mjs`,desired state 由 `scripts/g14-gitops-render.mjs` 生成,细则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。 +- 当前发布只走 G14 k3s Tekton + `G14-gitops` + Argo CD;镜像构建 helper 是 `scripts/artifact-publish.mjs`,desired state 由 `scripts/gitops-render.mjs` 生成,细则见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。 - 发现旧脚本、旧文档或旧 `hwlab-cli cicd` 再次进入 G14 Pipeline、AGENTS 或长期参考时,优先删除旧入口并把调用方改到 G14 GitOps 路径,不再做兼容保留。 ## P0 门禁最小化纪律 @@ -73,7 +73,7 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥 - Runner 和指挥常用工作区是 `/workspace/hwlab`;进入仓库先检查分支与工作树状态,详见 [docs/reference/commander-collaboration.md](docs/reference/commander-collaboration.md)。 - G14 DEV/PROD CI/CD 由 `G14` source branch、G14 k3s Tekton 和 `G14-gitops` branch 驱动;`v0.2` CI/CD 由 UniDesk 手动 trigger、`/root/hwlab-v02-cicd.git`、`devops-infra` git mirror/relay、G14 k3s Tekton 和 `v0.2-gitops` 驱动;需要构建、Playwright、check、发布预检或运行面验证时放到 G14 k3s/runner/CI/CD,不在 master server 跑重型验证。 - G14/v0.2 远端验证必须用短连接触发后台 job、PipelineRun 或脚本任务,再用短连接轮询 status/tail/exit code;不要用 UniDesk SSH/tran 长连接等待 check、layout、Playwright、Tekton/Argo 或发布动作完整结束,细则见 [docs/reference/spec-v02-cicd.md](docs/reference/spec-v02-cicd.md)。 -- D601 发布/构建 worktree 纪律只适用于 legacy 路径回溯,不再作为当前 HWLAB 发布默认入口;当前入口见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。 +- D601 发布/构建 worktree 纪律只适用于 legacy 路径回溯,不再作为当前 HWLAB 发布默认入口;当前入口见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。 - 交付路径按变更风险选择:单纯文档、CLI/helper 轻量变更直接提交并 push 到当前工作线的 source branch,G14 默认 `origin/G14`,`v0.2` 默认 `origin/v0.2`;业务代码、运行面、发布链路、Secret、权限、数据迁移、PROD 或其他高风险变更走 PR 工作流,PR base 必须匹配当前工作线,不能默认投向 `main`;默认不要合并自己的 PR,用户或指挥官明确授权且满足门禁时可按长期参考自合并,不要改 PROD、不要重启服务。 - `DC-DCSN-P0-2026-003` / [pikasTech/HWLAB#78](https://github.com/pikasTech/HWLAB/issues/78) 是当前 M3 虚拟硬件可信闭环的上位约束;其他任务不得把 SOURCE、LOCAL、DRY-RUN、fixture 或前端状态误报为 M3 DEV-LIVE。 - 仓库禁止创建或提交 repo report 目录;验收、进展和结论只承载在 #7、专题 issue、每日简报或 PR/issue 评论。临时 JSON 只能写入 `/tmp`、`.state` 或 CI artifact,不能进入源码仓库。 @@ -82,8 +82,8 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥 - G14 DEV Cloud Web:`http://74.48.78.17:17666/`,规则见 [docs/reference/dev-runtime-boundary.md](docs/reference/dev-runtime-boundary.md)。 - G14 DEV API/edge/live:`http://74.48.78.17:17667/health/live`,规则见 [docs/reference/dev-runtime-boundary.md](docs/reference/dev-runtime-boundary.md)。 -- G14 PROD 预留入口:`http://74.48.78.17:18666/` 与 `http://74.48.78.17:18667/health/live`,规则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。 -- G14 `v0.2` 规划入口:`http://74.48.78.17:19666/` 与 `http://74.48.78.17:19667/health/live`,只能指向 `hwlab-v02` namespace,规则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。 +- G14 PROD 预留入口:`http://74.48.78.17:18666/` 与 `http://74.48.78.17:18667/health/live`,规则见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。 +- G14 `v0.2` 规划入口:`http://74.48.78.17:19666/` 与 `http://74.48.78.17:19667/health/live`,只能指向 `hwlab-v02` namespace,规则见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。 - Cloud Workbench 默认首页与 UX 约束见 [docs/reference/cloud-workbench.md](docs/reference/cloud-workbench.md)。 ## 规格 @@ -121,9 +121,9 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥 - v0.2 文档治理规格、过程文档迁 issue、`docs/` 根目录清理和 D601/G14 旧口径收敛:[docs/reference/spec-v02-documentation-governance.md](docs/reference/spec-v02-documentation-governance.md) - 架构和 M3 主线:[docs/reference/architecture.md](docs/reference/architecture.md) - DEV 运行态、端口、k3s 和 DB DNS 边界:[docs/reference/dev-runtime-boundary.md](docs/reference/dev-runtime-boundary.md) -- G14 GitOps 发布、SecretRef preflight、runner/host 边界、镜像发布和单纯文档/CLI 直推规则:[docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)、[docs/reference/commander-collaboration.md](docs/reference/commander-collaboration.md) -- G14 GitOps CI/CD、Tekton/Argo CD、集群内 registry 和无锁镜像化发布:[docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md) -- G14 CI/CD 性能基线、根因分析和加速收益估算:[docs/reference/g14-cicd-performance.md](docs/reference/g14-cicd-performance.md) +- G14 GitOps 发布、SecretRef preflight、runner/host 边界、镜像发布和单纯文档/CLI 直推规则:[docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)、[docs/reference/commander-collaboration.md](docs/reference/commander-collaboration.md) +- G14 GitOps CI/CD、Tekton/Argo CD、集群内 registry 和无锁镜像化发布:[docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md) +- G14 CI/CD 性能基线、根因分析和加速收益估算:[docs/reference/node-cicd-performance.md](docs/reference/node-cicd-performance.md) - Code Agent 对话就绪与真实回复判定:[docs/reference/code-agent-chat-readiness.md](docs/reference/code-agent-chat-readiness.md) - AgentRun 手动调度装配、UniDesk SSH passthrough 与 GitHub tool credential 边界:[docs/reference/agentrun-code-agent-dispatch.md](docs/reference/agentrun-code-agent-dispatch.md) - DEV runtime hotfix runbook 与只读审计:[docs/reference/dev-runtime-hotfix-runbook.md](docs/reference/dev-runtime-hotfix-runbook.md) @@ -146,9 +146,9 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥 - Cloud Web 静态检查:`npm run web:check` - Cloud Web 构建:`npm run web:build` - Cloud Web M3 只读护栏:`npm run web:m3-readonly` -- G14 artifact build helper:`node scripts/g14-artifact-publish.mjs --publish ...`,只能由 G14 Tekton task 携带 CI artifact identity 调用;人工发布走 G14 poller/GitOps,不走 legacy CLI CD。 -- G14 monorepo 组件计划:`node scripts/g14-ci-plan.mjs --base-ref --target-ref --pretty`,只读分析 affected/reused services,基于内建 service-path component model 与 `deploy/deploy.yaml`;细则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。 -- G14 GitOps 渲染:`npm run g14:gitops:render`;source 分支不再运行生成物 drift check,发布态由 Tekton 写入 `G14-gitops`。 +- G14 artifact build helper:`node scripts/artifact-publish.mjs --publish ...`,只能由 G14 Tekton task 携带 CI artifact identity 调用;人工发布走 G14 poller/GitOps,不走 legacy CLI CD。 +- G14 monorepo 组件计划:`node scripts/ci-plan.mjs --base-ref --target-ref --pretty`,只读分析 affected/reused services,基于内建 service-path component model 与 `deploy/deploy.yaml`;细则见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。 +- G14 GitOps 渲染:`npm run gitops:render`;source 分支不再运行生成物 drift check,发布态由 Tekton 写入 `G14-gitops`。 - 平行 lane 配置扩容:`npm run lane:expand -- configure --lane v03 --from v02 --write`,只写 `deploy/deploy.yaml` 的新 lane 声明,后续 `v04`/`v05`/`v06` 复用同一入口。 - GitOps lane 严格 TS 检查:`npm run gitops:ts:check`;新拆 GitOps 模块直接进入 `scripts/src/*.ts` 和 `tsconfig.gitops.json`,不要继续堆进单个 `.mjs`。 - DEV 依赖 runtime base 构建:`npm run dev-runtime-base:build` diff --git a/deploy/deploy.yaml b/deploy/deploy.yaml index 2af8108a..3748f3b1 100644 --- a/deploy/deploy.yaml +++ b/deploy/deploy.yaml @@ -81,7 +81,7 @@ profiles: notes: Reserved placeholder only. PROD deployment and acceptance are explicitly out of scope. nodes: G14: - gitopsRoot: deploy/gitops/g14 + gitopsRoot: deploy/gitops/node sourceRepo: git@github.com:pikasTech/HWLAB.git publicHost: 74.48.78.17 lanes: @@ -203,7 +203,6 @@ lanes: additionalEnvPaths: - internal/dev-entrypoint/artifact-runtime.mjs - scripts/artifact-publish.mjs - - scripts/g14-artifact-publish.mjs - scripts/src/dev-artifact-services.mjs hwpodAliases: - command: hwpod @@ -232,7 +231,6 @@ lanes: HWLAB_KEYCLOAK_CLIENT_SECRET: secretRef:hwlab-cloud-web-client/client-secret HWLAB_CODE_AGENT_ADAPTER: agentrun-v01 AGENTRUN_MGR_URL: http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080 - HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: G14 HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: agentrun-v01 HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE: agentrun-v01 HWLAB_CODE_AGENT_AGENTRUN_REPO_URL: http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git @@ -354,7 +352,6 @@ lanes: additionalEnvPaths: - internal/dev-entrypoint/artifact-runtime.mjs - scripts/artifact-publish.mjs - - scripts/g14-artifact-publish.mjs - scripts/src/dev-artifact-services.mjs hwpodAliases: - command: hwpod @@ -383,7 +380,6 @@ lanes: HWLAB_KEYCLOAK_CLIENT_SECRET: secretRef:hwlab-cloud-web-client/client-secret HWLAB_CODE_AGENT_ADAPTER: agentrun-v01 AGENTRUN_MGR_URL: http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080 - HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: G14 HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: agentrun-v01 HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE: agentrun-v01 HWLAB_CODE_AGENT_AGENTRUN_REPO_URL: http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git diff --git a/deploy/frp/README.md b/deploy/frp/README.md index 78ae5684..b80cf141 100644 --- a/deploy/frp/README.md +++ b/deploy/frp/README.md @@ -23,15 +23,15 @@ G14 uses separate preview port ranges and must not reuse D601's `16666` / `16667 - G14 DEV browser endpoint: `http://74.48.78.17:17666` - G14 DEV API/edge endpoint: `http://74.48.78.17:17667` -- G14 DEV frpc proxy names: `hwlab-g14-cloud-web` and `hwlab-g14-edge-proxy` -- G14 DEV k3s Deployment: `hwlab-dev/hwlab-g14-frpc`, generated by `scripts/g14-gitops-render.mjs` +- G14 DEV frpc proxy names: `hwlab-node-cloud-web` and `hwlab-node-edge-proxy` +- G14 DEV k3s Deployment: `hwlab-dev/hwlab-node-frpc`, generated by `scripts/gitops-render.mjs` - G14 PROD browser endpoint: `http://74.48.78.17:18666` - G14 PROD API/edge endpoint: `http://74.48.78.17:18667` -- G14 PROD frpc proxy names: `hwlab-g14-prod-cloud-web` and `hwlab-g14-prod-edge-proxy` -- G14 PROD k3s Deployment: `hwlab-prod/hwlab-g14-prod-frpc`, generated by `scripts/g14-gitops-render.mjs` +- G14 PROD frpc proxy names: `hwlab-node-prod-cloud-web` and `hwlab-node-prod-edge-proxy` +- G14 PROD k3s Deployment: `hwlab-prod/hwlab-node-prod-frpc`, generated by `scripts/gitops-render.mjs` - Master frps allow-list template: `deploy/frp/frps.dev.toml` -- G14 DEV frpc local template: `deploy/frp/frpc.g14.toml` -- G14 PROD frpc local template: `deploy/frp/frpc.g14-prod.toml` +- G14 DEV frpc local template: `deploy/frp/frpc.node-dev.toml` +- G14 PROD frpc local template: `deploy/frp/frpc.node-prod.toml` Only `hwlab-cloud-web:8080` and `hwlab-edge-proxy:6667` are exposed through FRP. Internal services, Tekton, Argo CD, registry, and database stay ClusterIP-only. diff --git a/deploy/frp/frpc.g14.toml b/deploy/frp/frpc.node-dev.toml similarity index 83% rename from deploy/frp/frpc.g14.toml rename to deploy/frp/frpc.node-dev.toml index e8bb7534..e36157ae 100644 --- a/deploy/frp/frpc.g14.toml +++ b/deploy/frp/frpc.node-dev.toml @@ -3,14 +3,14 @@ serverPort = 7000 loginFailExit = true [[proxies]] -name = "hwlab-g14-cloud-web" +name = "hwlab-node-cloud-web" type = "tcp" localIP = "hwlab-cloud-web.hwlab-dev.svc.cluster.local" localPort = 8080 remotePort = 17666 [[proxies]] -name = "hwlab-g14-edge-proxy" +name = "hwlab-node-edge-proxy" type = "tcp" localIP = "hwlab-edge-proxy.hwlab-dev.svc.cluster.local" localPort = 6667 diff --git a/deploy/frp/frpc.g14-prod.toml b/deploy/frp/frpc.node-prod.toml similarity index 81% rename from deploy/frp/frpc.g14-prod.toml rename to deploy/frp/frpc.node-prod.toml index 5d65db0d..b5468c27 100644 --- a/deploy/frp/frpc.g14-prod.toml +++ b/deploy/frp/frpc.node-prod.toml @@ -3,14 +3,14 @@ serverPort = 7000 loginFailExit = true [[proxies]] -name = "hwlab-g14-prod-cloud-web" +name = "hwlab-node-prod-cloud-web" type = "tcp" localIP = "hwlab-cloud-web.hwlab-prod.svc.cluster.local" localPort = 8080 remotePort = 18666 [[proxies]] -name = "hwlab-g14-prod-edge-proxy" +name = "hwlab-node-prod-edge-proxy" type = "tcp" localIP = "hwlab-edge-proxy.hwlab-prod.svc.cluster.local" localPort = 6667 diff --git a/docs/reference/cloud-workbench.md b/docs/reference/cloud-workbench.md index 4461f41d..d728a810 100644 --- a/docs/reference/cloud-workbench.md +++ b/docs/reference/cloud-workbench.md @@ -291,5 +291,5 @@ blockers/failures:`viewport`、`selector`、`failureType`、summary 和 artifa chat 同源 readiness 和 provider credential blocker 边界。 - [spec-v02-services.md](spec-v02-services.md): Cloud Web runtime wrapper and browser-side TypeScript migration boundary. - [pikasTech/HWLAB#532](https://github.com/pikasTech/HWLAB/issues/532): archived historical `docs/cloud-web-workbench.md` content. -- [g14-gitops-cicd.md](g14-gitops-cicd.md): Cloud Web rollout through G14 Tekton, GitOps and Argo CD +- [node-gitops-cicd.md](node-gitops-cicd.md): Cloud Web rollout through node-scoped Tekton, GitOps and Argo CD path. diff --git a/docs/reference/dev-runtime-boundary.md b/docs/reference/dev-runtime-boundary.md index e4f6bb40..80efed57 100644 --- a/docs/reference/dev-runtime-boundary.md +++ b/docs/reference/dev-runtime-boundary.md @@ -19,12 +19,12 @@ The current G14 DEV route is: ```text master hwlab-frps-dev :17666/:17667 --> G14 hwlab-g14-frpc in namespace hwlab-dev +-> G14 hwlab-node-frpc in namespace hwlab-dev -> hwlab-cloud-web on internal :8080 for browser traffic -> hwlab-edge-proxy / hwlab-cloud-api on internal :6667 for API and health master hwlab-frps-dev :18666/:18667 --> G14 hwlab-g14-prod-frpc in namespace hwlab-prod +-> G14 hwlab-node-prod-frpc in namespace hwlab-prod -> hwlab-cloud-web on internal :8080 for browser traffic -> hwlab-edge-proxy / hwlab-cloud-api on internal :6667 for API and health @@ -34,7 +34,7 @@ master hwlab-frps-dev :19666/:19667 -> hwlab-edge-proxy / hwlab-cloud-api on internal :6667 for API and health ``` -G14 PROD is rendered by GitOps as namespace `hwlab-prod` and Argo CD application `hwlab-g14-prod`; it uses the `:18666/:18667` public endpoints when enabled. G14 desired state is generated into `deploy/gitops/g14/runtime-dev` and `deploy/gitops/g14/runtime-prod` and promoted through the `G14-gitops` branch. +G14 PROD is rendered by GitOps as namespace `hwlab-prod` and Argo CD application `hwlab-node-prod`; it uses the `:18666/:18667` public endpoints when enabled. G14 desired state is generated into `deploy/gitops/node/runtime-dev` and `deploy/gitops/node/runtime-prod` and promoted through the `G14-gitops` branch. G14 `v0.2` is an additive lane, not a replacement for DEV or PROD. It uses source branch `v0.2`, fixed development workspace `G14:/root/hwlab-v02`, dedicated CI/CD source repo `G14:/root/hwlab-v02-cicd.git`, namespace `hwlab-v02`, and public ports `19666/19667`. The `v0.2` GitOps branch, runtime path and Argo Application must be distinct from DEV/PROD before any automated rollout is enabled. @@ -44,7 +44,7 @@ G14 native k3s is the HWLAB DEV/PROD runtime source of truth. Commands that obse ```sh tran G14:k3s kubectl -n hwlab-dev get deploy,svc,pod -o wide -tran G14:k3s kubectl -n argocd get application hwlab-g14-dev hwlab-g14-prod +tran G14:k3s kubectl -n argocd get application hwlab-node-dev hwlab-node-prod tran G14:k3s kubectl -n hwlab-v02 get deploy,svc,pod -o wide ``` diff --git a/docs/reference/dev-runtime-hotfix-runbook.md b/docs/reference/dev-runtime-hotfix-runbook.md index 5802c758..68365a71 100644 --- a/docs/reference/dev-runtime-hotfix-runbook.md +++ b/docs/reference/dev-runtime-hotfix-runbook.md @@ -22,7 +22,7 @@ tran G14:k3s kubectl get nodes -o wide tran G14:k3s kubectl get ns hwlab-dev hwlab-prod ``` -结果必须显示 G14 原生 k3s 中存在 `hwlab-dev`;需要验证 PROD 发布面时也必须显示 `hwlab-prod`。出现 D601 kubeconfig、Docker Desktop Kubernetes、`desktop-control-plane`、`127.0.0.1:11700` 或第二套 `hwlab-dev` 控制面时,停止 hotfix、回滚和验收判断。此边界与 [dev-runtime-boundary.md](dev-runtime-boundary.md) 和 [g14-gitops-cicd.md](g14-gitops-cicd.md) 保持一致。 +结果必须显示 G14 原生 k3s 中存在 `hwlab-dev`;需要验证 PROD 发布面时也必须显示 `hwlab-prod`。出现 D601 kubeconfig、Docker Desktop Kubernetes、`desktop-control-plane`、`127.0.0.1:11700` 或第二套 `hwlab-dev` 控制面时,停止 hotfix、回滚和验收判断。此边界与 [dev-runtime-boundary.md](dev-runtime-boundary.md) 和 [node-gitops-cicd.md](node-gitops-cicd.md) 保持一致。 ## kubectl 热修命令形状 diff --git a/docs/reference/g14-cicd-performance.md b/docs/reference/node-cicd-performance.md similarity index 97% rename from docs/reference/g14-cicd-performance.md rename to docs/reference/node-cicd-performance.md index 06d0796d..db5c0423 100644 --- a/docs/reference/g14-cicd-performance.md +++ b/docs/reference/node-cicd-performance.md @@ -6,9 +6,9 @@ G14 CI/CD 耗时必须按阶段测量,不能只看一个 PipelineRun 总时长: -1. Source trigger:`G14` source commit 被 `hwlab-g14-branch-poller` 发现。 +1. Source trigger:`G14` source commit 被 `hwlab-node-branch-poller` 发现。 2. Source preparation:`prepare-source` clone source、checkout 精确 revision、导入最新 GitOps artifact catalog,并安装必要 npm 依赖。 -3. Primitive validation:`repo-reports-guard`、`g14-contract-check`、`codex-api-forwarder-check` 从准备好的 workspace 并行运行。 +3. Primitive validation:`repo-reports-guard`、`node-contract-check`、`codex-api-forwarder-check` 从准备好的 workspace 并行运行。 4. Artifact planning:`plan-artifacts` 根据 component input hash 和上一轮 catalog 判断 affected/reused services。 5. Per-service fan-out:affected services 通过 BuildKit 发布镜像;reused services 复用 catalog identity,不能改变 workload pod template。 6. Artifact collection:`collect-artifacts` 汇总 per-service results 并验证 publish report。 @@ -24,7 +24,7 @@ G14 CI/CD 耗时必须按阶段测量,不能只看一个 PipelineRun 总时长 ## Timing 输出合同 -G14 Tekton 日志中的分段耗时统一使用 JSON 行事件:`event="g14-cicd-timing"`、`schemaVersion="v1"`、`stage`、`status`、`durationMs`,并尽量携带 `pipelineRun`、`taskRun`、`task`、`revision` 和 `serviceId`。后续性能对比只消费这些稳定字段,不从自然语言日志里反推耗时。 +G14 Tekton 日志中的分段耗时统一使用 JSON 行事件:`event="node-cicd-timing"`、`schemaVersion="v1"`、`stage`、`status`、`durationMs`,并尽量携带 `pipelineRun`、`taskRun`、`task`、`revision` 和 `serviceId`。后续性能对比只消费这些稳定字段,不从自然语言日志里反推耗时。 当前稳定 stage 名称:`source-clone`、`catalog-fetch`、`npm-ci`、`buildkit-context-transfer`、`dependency-install`、`cache-import`、`cache-export`、`gitops-render`、`gitops-clone`、`gitops-commit`、`gitops-push`、`argo-refresh`、`workload-ready`。 diff --git a/docs/reference/g14-gitops-cicd.md b/docs/reference/node-gitops-cicd.md similarity index 74% rename from docs/reference/g14-gitops-cicd.md rename to docs/reference/node-gitops-cicd.md index 202d54ea..c721817e 100644 --- a/docs/reference/g14-gitops-cicd.md +++ b/docs/reference/node-gitops-cicd.md @@ -5,12 +5,12 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD ## 目标模型 - Source of truth:业务版本以 Git source commit 为唯一身份;镜像 tag、OCI labels、runtime annotation 和 Argo CD desired state 都必须记录同一个 source commit。 -- CI:Tekton 在 G14 k3s 内运行 `hwlab-g14-ci-image-publish` Pipeline,由 `scripts/g14-gitops-render.mjs` 直接生成原生 task;最小校验固定为 `repo-reports-guard`、`g14-contract-check`、`codex-api-forwarder-check`,随后按 component plan 做 per-service BuildKit publish 与 GitOps promote;没有 `CI.json` runner、DIND 单任务发布或 Docker fallback。 +- CI:Tekton 在 G14 k3s 内运行 `hwlab-node-ci-image-publish` Pipeline,由 `scripts/gitops-render.mjs` 直接生成原生 task;最小校验固定为 `repo-reports-guard`、`node-contract-check`、`codex-api-forwarder-check`,随后按 component plan 做 per-service BuildKit publish 与 GitOps promote;没有 `CI.json` runner、DIND 单任务发布或 Docker fallback。 - Artifact:镜像使用 commit tag,例如 `127.0.0.1:5000/hwlab/hwlab-cloud-api:`;digest 由 registry 返回,CI report 只作为审计证据,不作为 CD 真相。发布态 artifact catalog 由 Tekton 写入 `G14-gitops:deploy/artifact-catalog.dev.json`。 -- Branch split:`G14` 是源码监控分支,只保存人写源码、声明和 seed contract;`G14-gitops` 是 Tekton promotion 写入的生成分支,保存 `deploy/artifact-catalog.dev.json` 与 `deploy/gitops/g14/**` desired state。CI/CD 不再把 catalog promotion commit 写回 `G14`。 +- Branch split:`G14` 是源码监控分支,只保存人写源码、声明和 seed contract;`G14-gitops` 是 Tekton promotion 写入的生成分支,保存 `deploy/artifact-catalog.dev.json` 与 `deploy/gitops/node/**` desired state。CI/CD 不再把 catalog promotion commit 写回 `G14`。 - v0.2 扩容线:`v0.2` 必须从当前 `G14` fork 出来,并以 `G14:/root/hwlab-v02` 作为固定开发 workspace、`G14:/root/hwlab-v02-cicd.git` 作为固定 CI/CD source repo、`hwlab-v02` 作为固定 runtime namespace。`v0.2` CI/CD 只能作为新增 lane 接入现有 G14 Tekton/Argo 体系,不得改写、删除、暂停或重定向现有 `G14` poller、`G14-gitops` DEV/PROD desired state、`hwlab-dev` 或 `hwlab-prod`;详细规格见 [spec-v02-cicd.md](spec-v02-cicd.md)。 -- CD:Argo CD 只消费 `G14-gitops:deploy/gitops/g14/runtime-dev` 与 `deploy/gitops/g14/runtime-prod` 的 Git desired state,不重新构建镜像,不读取 D601 状态,不获取 legacy DEV CD Lease。 -- FRP:G14 DEV 通过 `hwlab-dev/hwlab-g14-frpc` 暴露 `17666/17667`,G14 PROD 通过 `hwlab-prod/hwlab-g14-prod-frpc` 暴露 `18666/18667`;`v0.2` 规划通过 `hwlab-v02` 内独立 frpc 暴露 `19666/19667`。master frps 的 `deploy/frp/frps.dev.toml` 与实际 `/etc/frp/frps.toml` 必须放行对应端口,但 `v0.2` 放行只能新增 19xxx 入口,不能复用或覆盖 DEV/PROD 入口。 +- CD:Argo CD 只消费 `G14-gitops:deploy/gitops/node/runtime-dev` 与 `deploy/gitops/node/runtime-prod` 的 Git desired state,不重新构建镜像,不读取 D601 状态,不获取 legacy DEV CD Lease。 +- FRP:G14 DEV 通过 `hwlab-dev/hwlab-node-frpc` 暴露 `17666/17667`,G14 PROD 通过 `hwlab-prod/hwlab-node-prod-frpc` 暴露 `18666/18667`;`v0.2` 规划通过 `hwlab-v02` 内独立 frpc 暴露 `19666/19667`。master frps 的 `deploy/frp/frps.dev.toml` 与实际 `/etc/frp/frps.toml` 必须放行对应端口,但 `v0.2` 放行只能新增 19xxx 入口,不能复用或覆盖 DEV/PROD 入口。 - 并行性:不同 source commit 的 CI build 不共享发布锁;并行安全由 immutable commit tag/digest 和 Git desired state 保证。最终运行版本由 Argo CD 当前同步的 Git revision 决定。 ## 门禁最小化与扩容治理 @@ -19,38 +19,38 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD 旧 DEV/D601/main 门禁如果阻碍当前 G14 或 `v0.2` 路径,默认处理是从当前调用链删除,而不是做兼容性迁移、fallback、legacy mode、双路径绕行或在旧门禁上叠加例外。新增门禁只能覆盖明确高价值风险,且必须最小、低噪声、容易删除;资源配额、RBAC 命名、清理策略、回滚顺序、人工同步策略等默认是设计约定或 runbook,不是 CI/CD 通过条件。 -`v0.2` 的硬边界只包括:source branch 必须是 `v0.2`,CI/CD source repo 必须是 `/root/hwlab-v02-cicd.git`,GitOps branch 必须是 `v0.2-gitops`,Git mirror/relay 必须来自 `devops-infra`,runtime namespace 必须是 `hwlab-v02`,runtime path 必须是 `deploy/gitops/g14/runtime-v02`,Argo Application 必须指向 `v0.2` GitOps lane,公网入口只能是 `19666/19667`,`v0.2` source branch 不跟踪生成物,旧 DEV/D601/main 门禁不进入 `v0.2` 调用链。其他事项先写成决策表或 runbook;只有被证明无法靠上述边界和标准入口自然收敛时,才允许新增最小检查。 +`v0.2` 的硬边界只包括:source branch 必须是 `v0.2`,CI/CD source repo 必须是 `/root/hwlab-v02-cicd.git`,GitOps branch 必须是 `v0.2-gitops`,Git mirror/relay 必须来自 `devops-infra`,runtime namespace 必须是 `hwlab-v02`,runtime path 必须是 `deploy/gitops/node/runtime-v02`,Argo Application 必须指向 `v0.2` GitOps lane,公网入口只能是 `19666/19667`,`v0.2` source branch 不跟踪生成物,旧 DEV/D601/main 门禁不进入 `v0.2` 调用链。其他事项先写成决策表或 runbook;只有被证明无法靠上述边界和标准入口自然收敛时,才允许新增最小检查。 ## 生成入口 -`scripts/g14-gitops-render.mjs` 是 G14 专用转换器: +`scripts/gitops-render.mjs` 是 G14 专用转换器: - 架构迁移时,过时的自检、预检、guard、gate 优先删除,不在旧门禁上叠加例外或复杂度;新门禁只允许覆盖明确高价值风险,必须保持最小、低噪声、易迁移。 - 直接声明 Tekton Pipeline、最小原语校验 task 和 PipelineRun 样板;不再读取 `CI.json` 或生成 `ci-json` step。 - 读取 `deploy/deploy.yaml` 与 `deploy/k8s/*`,生成 Argo CD 可消费的 G14 runtime Kustomize path。 -- G14 Tekton 的镜像构建发布入口必须是 `scripts/g14-artifact-publish.mjs`;它只是集群内 Task 的 build/push helper,不做 rollout、不写 D601、不获取 legacy DEV CD Lease。旧 `scripts/dev-artifact-publish.mjs` 入口已删除;`dev-cd-apply`、`ci-publish` 和旧 `main` JS CD 入口禁止出现在 G14 Pipeline 生成脚本和 G14 验收证据中。 -- `g14-contract-check` 在 fresh source clone 内取当前 `HEAD`,把 GitOps 产物渲染到 `mktemp -d` 临时目录,并立刻用同一个 `--source-revision` 执行 `--check`;它校验 render 代码、原生 Tekton 产物生成合同和 forbidden-fragment 护栏,不依赖 source branch 预先存在 `deploy/gitops/g14/source.json`。面向已发布 runtime 的 `source.json` 只存在于 `G14-gitops` 生成分支,作为 promotion evidence。 -- 生成 `hwlab-g14-branch-poller` CronJob:它使用 G14 集群内的 Git SSH Secret 轮询 `G14` 分支,按 source commit 创建确定命名的 Tekton PipelineRun。 -- 生成 `hwlab-g14-control-plane-reconciler` CronJob:它使用同一个 Git SSH Secret 轮询 `G14`,运行 repo 内 `scripts/g14-gitops-render.mjs`,并 server-side apply 生成的 Tekton RBAC、Pipeline、Poller 和 Reconciler manifests;因此 CI 控制面变化应自动进入 G14 k3s,不需要人工长期执行 render/apply。 -- 默认输出到 `deploy/gitops/g14/`。 -- 默认 GitOps 生成分支是 `G14-gitops`;Pipeline 成功后把本次 source commit 对应的 `deploy/artifact-catalog.dev.json` 与 `deploy/gitops/g14/**` 推送到该分支,避免把生成提交继续写回 `G14`。 -- `v0.2` GitOps lane 必须使用独立生成身份,例如 `v0.2-gitops`、`deploy/artifact-catalog.v02.json` 和 `deploy/gitops/g14/runtime-v02`,并由独立 Argo CD Application 指向 `hwlab-v02`。若后续实现选择复用某个脚本入口,也必须通过显式参数区分 source branch、catalog、runtime path、Application 和 namespace,不能靠修改默认值让 `G14` DEV/PROD 行为漂移。 +- G14 Tekton 的镜像构建发布入口必须是 `scripts/artifact-publish.mjs`;它只是集群内 Task 的 build/push helper,不做 rollout、不写 D601、不获取 legacy DEV CD Lease。旧 `scripts/dev-artifact-publish.mjs` 入口已删除;`dev-cd-apply`、`ci-publish` 和旧 `main` JS CD 入口禁止出现在 G14 Pipeline 生成脚本和 G14 验收证据中。 +- `node-contract-check` 在 fresh source clone 内取当前 `HEAD`,把 GitOps 产物渲染到 `mktemp -d` 临时目录,并立刻用同一个 `--source-revision` 执行 `--check`;它校验 render 代码、原生 Tekton 产物生成合同和 forbidden-fragment 护栏,不依赖 source branch 预先存在 `deploy/gitops/node/source.json`。面向已发布 runtime 的 `source.json` 只存在于 `G14-gitops` 生成分支,作为 promotion evidence。 +- 生成 `hwlab-node-branch-poller` CronJob:它使用 G14 集群内的 Git SSH Secret 轮询 `G14` 分支,按 source commit 创建确定命名的 Tekton PipelineRun。 +- 生成 `hwlab-node-control-plane-reconciler` CronJob:它使用同一个 Git SSH Secret 轮询 `G14`,运行 repo 内 `scripts/gitops-render.mjs`,并 server-side apply 生成的 Tekton RBAC、Pipeline、Poller 和 Reconciler manifests;因此 CI 控制面变化应自动进入 G14 k3s,不需要人工长期执行 render/apply。 +- 默认输出到 `deploy/gitops/node/`。 +- 默认 GitOps 生成分支是 `G14-gitops`;Pipeline 成功后把本次 source commit 对应的 `deploy/artifact-catalog.dev.json` 与 `deploy/gitops/node/**` 推送到该分支,避免把生成提交继续写回 `G14`。 +- `v0.2` GitOps lane 必须使用独立生成身份,例如 `v0.2-gitops`、`deploy/artifact-catalog.v02.json` 和 `deploy/gitops/node/runtime-v02`,并由独立 Argo CD Application 指向 `hwlab-v02`。若后续实现选择复用某个脚本入口,也必须通过显式参数区分 source branch、catalog、runtime path、Application 和 namespace,不能靠修改默认值让 `G14` DEV/PROD 行为漂移。 - 默认 registry prefix 是 `127.0.0.1:5000/hwlab`,用于 G14 单节点 k3s 的 node-local registry。 - 默认 CI/CD proxy 是 G14 本机 `http://127.0.0.1:10808` / `socks5h://127.0.0.1:10808`。Tekton CI step、BuildKit sidecar 和 publish step 都注入 proxy/no_proxy;服务镜像构建只允许通过 Pod 内 BuildKit Unix socket 直接 push 到 G14 本地 registry,不能回退到 Docker daemon、DIND 或 host Docker。 -- `prepare-source` 和最小原语校验 task 不允许每次运行时重新 `apk add`、`apt-get install` 或临时下载 browser/runtime 依赖。当前固定工具镜像是 `127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1`,包含 Node 22、npm、Bun、Git、OpenSSH、curl、Python3 和 Docker CLI。镜像内容由 `deploy/ci/hwlab-ci-node-tools.Dockerfile` 声明;脚本启动时必须输出工具与 proxy preflight 结构化日志。缺少工具时应先在 G14 构建并推送新的工具镜像,再修改 `HWLAB_G14_CI_TOOLS_IMAGE`/render 默认值,不能回退到 runtime 安装。 +- `prepare-source` 和最小原语校验 task 不允许每次运行时重新 `apk add`、`apt-get install` 或临时下载 browser/runtime 依赖。当前固定工具镜像是 `127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1`,包含 Node 22、npm、Bun、Git、OpenSSH、curl、Python3 和 Docker CLI。镜像内容由 `deploy/ci/hwlab-ci-node-tools.Dockerfile` 声明;脚本启动时必须输出工具与 proxy preflight 结构化日志。缺少工具时应先在 G14 构建并推送新的工具镜像,再修改 `HWLAB_NODE_CI_TOOLS_IMAGE`/render 默认值,不能回退到 runtime 安装。 - G14 host 只用于 source workspace、GitOps render、k3s 控制和轻量语法/静态合同检查;不要把 host 当成浏览器执行面。低频 browser smoke 已不再属于默认 primitive CI。若确实需要一次性布局、移动端或交互验证,必须显式在 G14 k3s/Tekton 的专用 Playwright 镜像内运行,不能回退成 host 上强装 browser 的长期方案。 -- Poller、control-plane reconciler、image publish 和 GitOps promote step 都不允许每次运行时 `apk add` / `apt-get install`。当前 G14 registry 固定工具镜像是 `127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1`,包含 Node 22、npm、Bun、Git、OpenSSH、curl、Python3 和 Docker CLI;生成的脚本只做 proxy preflight 与工具存在性检查。若需要升级工具,先在 G14 构建/推送新的工具镜像,再修改 `HWLAB_G14_CI_TOOLS_IMAGE`/render 默认值并由 reconciler apply。 -- 服务镜像构建的默认 parent/base image 不得从 Docker Hub 反复拉取。当前 `node:20-bookworm-slim` 已镜像到 G14 registry 的 allowlist 名称:`127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim`,render 默认 `base-image` 和 poller `BASE_IMAGE` 都指向这个本地镜像。需要升级 parent image 时,先通过 G14 proxy 拉取并推送到 G14 registry,再修改 `HWLAB_G14_DEV_BASE_IMAGE`/render 默认值;image publish step 只允许从本地 registry pull base image,且 tag 必须符合 publish gate 的 `hwlab-node20-base`/`hwlab-dev-base`/`hwlab-node-runtime-base` allowlist。 +- Poller、control-plane reconciler、image publish 和 GitOps promote step 都不允许每次运行时 `apk add` / `apt-get install`。当前 G14 registry 固定工具镜像是 `127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1`,包含 Node 22、npm、Bun、Git、OpenSSH、curl、Python3 和 Docker CLI;生成的脚本只做 proxy preflight 与工具存在性检查。若需要升级工具,先在 G14 构建/推送新的工具镜像,再修改 `HWLAB_NODE_CI_TOOLS_IMAGE`/render 默认值并由 reconciler apply。 +- 服务镜像构建的默认 parent/base image 不得从 Docker Hub 反复拉取。当前 `node:20-bookworm-slim` 已镜像到 G14 registry 的 allowlist 名称:`127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim`,render 默认 `base-image` 和 poller `BASE_IMAGE` 都指向这个本地镜像。需要升级 parent image 时,先通过 G14 proxy 拉取并推送到 G14 registry,再修改 `HWLAB_NODE_DEV_BASE_IMAGE`/render 默认值;image publish step 只允许从本地 registry pull base image,且 tag 必须符合 publish gate 的 `hwlab-node20-base`/`hwlab-dev-base`/`hwlab-node-runtime-base` allowlist。 - 任何依赖下载阶段都必须有可观测诊断。生成的 Tekton 脚本在 npm、BuildKit base image/local registry probe 和 GitOps promote 之前输出结构化 `dependency-proxy-probe`、`dependency-curl-probe` 或 `dependency-download-*` 日志,至少包含 phase、目标 URL/镜像、脱敏 proxy、首包耗时、总耗时、下载字节数和速度。CI/CD 卡在下载时,先用这些日志判断是 proxy 不可达、目标源慢、DNS/首包慢还是下载吞吐低,再决定是否切换 G14 代理节点或预热镜像;不能只凭 PipelineRun Running 时长判断业务测试失败。 常用命令: ```sh -npm run g14:gitops:render -- --source-revision +npm run gitops:render -- --source-revision ``` -人工 source workspace 不再对 `deploy/gitops/g14/**` 做生成物对比;这些文件在 source 分支下是忽略的生成物,旧文件和 `source.json` 不是真相。需要看渲染计划时使用 `npm run g14:gitops:render -- --no-write`;正式发布态只看 `G14-gitops`、Argo Application 和 live runtime。 +人工 source workspace 不再对 `deploy/gitops/node/**` 做生成物对比;这些文件在 source 分支下是忽略的生成物,旧文件和 `source.json` 不是真相。需要看渲染计划时使用 `npm run gitops:render -- --no-write`;正式发布态只看 `G14-gitops`、Argo Application 和 live runtime。 ## 原生 k8s Tekton + Argo 配置面 vs 已废弃的 CI.json runner @@ -76,14 +76,14 @@ npm run g14:gitops:render -- --source-revision HWLAB 是 monorepo,G14 CI/CD 加速必须按组件输入判断构建和滚动;v0.2 env-reuse 的组件边界与环境配方直接来自 `deploy/deploy.yaml`,运行态镜像身份来自 per-service artifact catalog;`CI.json` 已删除,不再作为任何 planner 输入。 - `deploy/deploy.yaml` 是当前 v0.2 人写 deploy/runtime 配置单一出处;`deploy/deploy.json` 不得作为 v0.2 兼容源恢复。配置读写由 `scripts/src/structured-config.mjs` 和 `scripts/src/deploy-config.mjs` 统一承载,只有该格式无关层直接处理 YAML/JSON 解析;planner、renderer、smoke、artifact helper 和 CLI 只调用读写 helper。 -- `scripts/g14-ci-plan.mjs` 是只读 planner,默认读取当前 workspace 的 `deploy/deploy.yaml`、`deploy/artifact-catalog.dev.json`,以及 `deploy/deploy.yaml` 内的 lane service declarations/env recipe,输出 `affectedServices`、`reusedServices`、`componentCommitId`、`componentInputHash`、`dockerfileHash`、`baseImageDigest`、`buildArgsHash` 和原因;它不得修改 deploy、catalog 或 GitOps 文件。Tekton `prepare-source` 会先从 `G14-gitops` 注入上一轮发布态 catalog,source 分支里的 catalog 只作为 seed contract。 +- `scripts/ci-plan.mjs` 是只读 planner,默认读取当前 workspace 的 `deploy/deploy.yaml`、`deploy/artifact-catalog.dev.json`,以及 `deploy/deploy.yaml` 内的 lane service declarations/env recipe,输出 `affectedServices`、`reusedServices`、`componentCommitId`、`componentInputHash`、`dockerfileHash`、`baseImageDigest`、`buildArgsHash` 和原因;它不得修改 deploy、catalog 或 GitOps 文件。Tekton `prepare-source` 会先从 `G14-gitops` 注入上一轮发布态 catalog,source 分支里的 catalog 只作为 seed contract。 - v0.2 服务清单来自显式 `--services` 或 `deploy.lanes.v02.envReuseServices`;旧 `deploy.services[]`、`deploy.k3s.serviceMappings[]` 和 `internal/protocol.SERVICE_IDS` 推导路径不再作为 planner 输入。 - v0.2 env-reuse 组件边界和环境镜像配方以 `deploy/deploy.yaml` 的 `lanes.v02.serviceDeclarations`、`lanes.v02.envRecipe` 和 `lanes.v02.bootConfig` 为单一配置点;新增服务或调整 `componentPaths`、`runtimeKind`、`entrypoint`、Bun 版本、系统包、launcher 路径和 HWPOD alias 时先改 deploy 配置与 schema/测试,不再把这些属性硬编码进 planner 或 artifact publish helper。 - `hwpod` 是 runner 内的稳定 HWPOD task 入口,由 `tools/hwpod-cli.ts`、`tools/hwpod-compiler-cli.ts`、`tools/hwpod-ctl.ts`、`tools/hwpod-node.ts` 和 `skills/hwpod-cli/`、`skills/hwpod-ctl/` 组成。修改这些路径时,G14/v0.2 CI 必须至少触发携带 `/usr/local/bin/hwpod` 的 runtime skills/env-reuse 重新装配或 rollout,不能全量复用旧 artifact 后只报告 PipelineRun 成功。 -- `scripts/g14-artifact-publish.mjs` 默认启用组件级 lazy build:先运行 planner,再只构建/推送 `affectedServices`,`reusedServices` 从 `deploy/artifact-catalog.dev.json` 或 lane catalog 复用已有 sha256 digest。非 env-reuse 服务复用前必须满足 artifact provenance 自证:catalog 有可验证 digest、catalog 的 `sourceCommitId` 在当前 repo 可解析、用该 `sourceCommitId` 的 source tree 重新计算出的 `componentInputHash` 等于 catalog 记录、该 hash 再等于本轮 planner 计算值,且 `dockerfileHash`/`buildArgsHash` 没有不一致。catalog 缺 digest、缺 provenance、source tree 无法解析、catalog hash 与 catalog source tree 不一致、或 catalog hash 与本轮 input 不一致时,planner 必须把该服务列为 affected 并重新发布,不能用旧 guard 阻塞,也不能退回 Docker 或 legacy full-build 路线。 +- `scripts/artifact-publish.mjs` 默认启用组件级 lazy build:先运行 planner,再只构建/推送 `affectedServices`,`reusedServices` 从 `deploy/artifact-catalog.dev.json` 或 lane catalog 复用已有 sha256 digest。非 env-reuse 服务复用前必须满足 artifact provenance 自证:catalog 有可验证 digest、catalog 的 `sourceCommitId` 在当前 repo 可解析、用该 `sourceCommitId` 的 source tree 重新计算出的 `componentInputHash` 等于 catalog 记录、该 hash 再等于本轮 planner 计算值,且 `dockerfileHash`/`buildArgsHash` 没有不一致。catalog 缺 digest、缺 provenance、source tree 无法解析、catalog hash 与 catalog source tree 不一致、或 catalog hash 与本轮 input 不一致时,planner 必须把该服务列为 affected 并重新发布,不能用旧 guard 阻塞,也不能退回 Docker 或 legacy full-build 路线。 - Artifact catalog 的 per-service provenance 是镜像 digest 的身份证明,不是本轮 planner 状态缓存。reuse 路径只能保留旧 artifact 自身的 `sourceCommitId`、`componentCommitId`、`componentInputHash`、`dockerfileHash`、`baseImage*` 和 `buildArgsHash`;禁止把当前 planner 的 component/build 元数据写入复用的旧 digest,否则下一轮 planner 会把旧镜像误判成已包含新输入,造成 CI/CD false-green。 -- `scripts/g14-artifact-publish.mjs` 的 publish report 必须携带 planner 的 per-service 元数据;`scripts/refresh-artifact-catalog.mjs` 默认只预览,只有 G14 Tekton promotion 显式传 `--write` 时,才把生成的 `commitId`、`image`、`imageTag`、`digest`、`publishState` 和 component provenance 字段写进当前 workspace 的 `deploy/artifact-catalog.dev.json`,随后只提交到 `G14-gitops`。`deploy/deploy.yaml` 是人写的 runtime config 真相源,不得被 promotion、refresh 脚本或人工发布流程回写镜像身份字段。 -- `scripts/g14-gitops-render.mjs` 只支持混合 desired state:workload 的 container image、`HWLAB_IMAGE`、`HWLAB_IMAGE_TAG` 和 pod template `source-commit` 来自 `deploy/artifact-catalog.dev.json` 的 per-service artifact identity;普通 env、replica、healthPath、profile 等配置来自 `deploy/deploy.yaml`。全局 GitOps metadata 仍记录本次 source commit。`--legacy-source-images` 与 `HWLAB_G14_USE_DEPLOY_IMAGES=0` 已废弃,不得把所有 workload image 回退渲染为同一个 source commit tag。 +- `scripts/artifact-publish.mjs` 的 publish report 必须携带 planner 的 per-service 元数据;`scripts/refresh-artifact-catalog.mjs` 默认只预览,只有 G14 Tekton promotion 显式传 `--write` 时,才把生成的 `commitId`、`image`、`imageTag`、`digest`、`publishState` 和 component provenance 字段写进当前 workspace 的 `deploy/artifact-catalog.dev.json`,随后只提交到 `G14-gitops`。`deploy/deploy.yaml` 是人写的 runtime config 真相源,不得被 promotion、refresh 脚本或人工发布流程回写镜像身份字段。 +- `scripts/gitops-render.mjs` 只支持混合 desired state:workload 的 container image、`HWLAB_IMAGE`、`HWLAB_IMAGE_TAG` 和 pod template `source-commit` 来自 `deploy/artifact-catalog.dev.json` 的 per-service artifact identity;普通 env、replica、healthPath、profile 等配置来自 `deploy/deploy.yaml`。全局 GitOps metadata 仍记录本次 source commit。`--legacy-source-images` 与 `HWLAB_NODE_USE_DEPLOY_IMAGES=0` 已废弃,不得把所有 workload image 回退渲染为同一个 source commit tag。 - G14 Tekton promotion 在推送 `G14-gitops` 前,必须用 publish report 显式 `--write` 刷新 workspace 内的 `deploy/artifact-catalog.dev.json`,然后把刷新后的 catalog 和 rendered GitOps desired state 一起提交到 `G14-gitops`。promotion 不得自动修改或推送 `G14` source branch,也不得自动修改 `deploy/deploy.yaml` 或 `deploy/k8s/base/workloads.yaml`,这样人写配置不会和 CI 生成身份反复冲突。 - Tekton 并发化只能以 planner 输出作为输入;每个 service 都有独立 TaskRun,changed service 启动 BuildKit,unchanged service 只写 reuse result 并复用 catalog digest,且不能改 pod template,避免无意义 rollout。没有完整 per-service desired state 证据时,必须修复 planner/catalog 证据,不能使用 `--full-build`、`--legacy-source-images`、DIND 或 Docker fallback 回退。 @@ -91,7 +91,7 @@ HWLAB 是 monorepo,G14 CI/CD 加速必须按组件输入判断构建和滚动 G14 CI/CD 加速的第一判定标准不是 PipelineRun 总耗时单点变短,而是 monorepo 组件粒度的“少构建、少滚动、可并发”是否成立。 -- 触发延迟:`hwlab-g14-branch-poller` 固定每 1 分钟轮询 `G14`。相对 5 分钟轮询,平均触发等待应从约 2.5 分钟降到约 0.5 分钟,最坏等待从 5 分钟降到 1 分钟。发现 `kubectl -n hwlab-ci get cronjob hwlab-g14-branch-poller -o jsonpath='{.spec.schedule}'` 不是 `* * * * *` 时,应先修 poller/reconciler,而不是手工长期创建 PipelineRun。 +- 触发延迟:`hwlab-node-branch-poller` 固定每 1 分钟轮询 `G14`。相对 5 分钟轮询,平均触发等待应从约 2.5 分钟降到约 0.5 分钟,最坏等待从 5 分钟降到 1 分钟。发现 `kubectl -n hwlab-ci get cronjob hwlab-node-branch-poller -o jsonpath='{.spec.schedule}'` 不是 `* * * * *` 时,应先修 poller/reconciler,而不是手工长期创建 PipelineRun。 - 组件懒构建:docs-only、GitOps-only、poller/reconciler manifest-only 等不影响服务输入的变更,planner 应输出 `affectedServices=[]`、`buildSkippedCount=<全部服务数>`,各 service TaskRun 应写出 `status=reused` 和 `build-backend=reused-catalog`。这类变更不应重新构建任何服务镜像,也不应把 workload image 改成当前 source commit tag。 - 并发 fan-out:per-service TaskRun 应由 Tekton/k8s scheduler 同时调度,多个 service 的 build/reuse 窗口应接近“最慢单个服务任务耗时”,而不是所有服务串行相加。reuse-only 场景的 fan-out 窗口当前基线约为十几秒;真实组件构建场景仍需按 changed service 单独记录 BuildKit 耗时和 cache hit 情况。 - CD rollout:unchanged service 必须复用 `deploy/artifact-catalog.dev.json` 的 image/digest,pod template 不应因全局 source commit 改变而无意义滚动。手写 manifest(例如 `deepseek-proxy`、device-agent 类辅助 workload)只要复用某个已发布服务镜像,也必须走同一个 catalog image 选择逻辑,不能直接用当前 source commit tag。 @@ -100,14 +100,14 @@ G14 CI/CD 加速的第一判定标准不是 PipelineRun 总耗时单点变短, 当前仍然慢的主要位置: -- `prepare-source` 与最小原语校验 task 仍是前段主要开销。它们包含 Git clone、checkout、必要的 `npm ci`、`repo-reports-guard`、`g14-contract-check` 和 `codex-api-forwarder-check`。继续加速应优先减少 workspace cold start 与依赖安装成本,而不是重新塞回一条 shell runner。 +- `prepare-source` 与最小原语校验 task 仍是前段主要开销。它们包含 Git clone、checkout、必要的 `npm ci`、`repo-reports-guard`、`node-contract-check` 和 `codex-api-forwarder-check`。继续加速应优先减少 workspace cold start 与依赖安装成本,而不是重新塞回一条 shell runner。 - `gitops-promote` 仍是大头,当前量级约 100 秒。它包含 source branch freshness check、artifact catalog refresh、render、clone `G14-gitops`、复制 catalog 与 generated desired state、commit 和 push。继续加速应优先减少 git clone/push 成本,例如使用浅 clone、持久 workspace、server-side patch 或把 GitOps repo 操作单独缓存。 - per-service TaskRun 数量增加后会有 k8s 调度和 sidecar 生命周期开销。reuse-only 任务很快,但如果每个 unchanged service 都启动完整 buildkit sidecar,调度开销会抵消部分收益;长期目标是让 unchanged service 走更轻量的 reuse Task,changed service 才启动 BuildKit。 - per-service BuildKit 必须使用 Pod 内 Unix socket 或其他 Pod-scoped 端点,禁止在 hostNetwork 下绑定固定 TCP 端口。多个 service TaskRun 并发时,如果 sidecar 统一监听 `0.0.0.0:1234`,会抢同一个宿主端口,导致主 step 结果成功但 Pod phase 显示 Failed,严重污染 CI 观测。 - Argo sync/health 不是 instant。`Sync=Synced` 后 health 仍可能因为 replicas=0 的模板 Deployment、未排除 health 的模板 Job 或 StatefulSet 旧 revision 显示 `Suspended`/`Progressing`。这时应先看实际长驻 workload ready 与具体 unhealthy resource,不要只看 Application 总 health;如果 suspended Job 只是模板资源,应修 render 注解而不是把 DEV rollout 判成失败。 - StatefulSet 旧 ordinal pod 可能长期卡在历史缺失镜像上,即使 StatefulSet 当前 template 已经修正。DEV 可按运行面热修流程删除旧 Pending pod 让 StatefulSet 按当前 template 重建;PROD namespace 需要明确 maintenance 授权后再做同类删除。 -每次优化 CI/CD 都必须保留可比测量:记录 PipelineRun 总耗时、`prepare-source`、`repo-reports-guard`、`g14-contract-check`、`codex-api-forwarder-check`、`plan-artifacts`、per-service fan-out 起止窗口、`collect-artifacts`、`gitops-promote`、Argo sync 到 workload ready 的时间,并标明本轮是 reuse-only、单组件 build 还是多组件 build。没有这些分段数据,不要只用“感觉变快/变慢”判断优化成败。 +每次优化 CI/CD 都必须保留可比测量:记录 PipelineRun 总耗时、`prepare-source`、`repo-reports-guard`、`node-contract-check`、`codex-api-forwarder-check`、`plan-artifacts`、per-service fan-out 起止窗口、`collect-artifacts`、`gitops-promote`、Argo sync 到 workload ready 的时间,并标明本轮是 reuse-only、单组件 build 还是多组件 build。没有这些分段数据,不要只用“感觉变快/变慢”判断优化成败。 ## Code Agent Provider Profiles @@ -126,15 +126,15 @@ DeepSeek proxy manifest 是 GitOps desired state 的一部分,DEV/PROD 分别 ## Polling 触发 -G14 不要求 GitHub webhook 或 GitHub Actions 配置。`hwlab-ci/hwlab-g14-branch-poller` CronJob 每 1 分钟通过 Git SSH 拉取 `G14` HEAD,并用 source commit 的前 12 位生成 PipelineRun 名称 `hwlab-g14-ci-poll-`。 +G14 不要求 GitHub webhook 或 GitHub Actions 配置。`hwlab-ci/hwlab-node-branch-poller` CronJob 每 1 分钟通过 Git SSH 拉取 `G14` HEAD,并用 source commit 的前 12 位生成 PipelineRun 名称 `hwlab-node-ci-poll-`。 `v0.2` 接入不再新增 HWLAB 发布触发 CronJob;标准入口是 UniDesk CLI `bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm`,由该入口自动 fetch `/root/hwlab-v02-cicd.git`、解析当前 `origin/v0.2` HEAD,并直接创建可区分的 commit-pinned PipelineRun,例如 `hwlab-v02-ci-poll-`。该 lane 的 GitOps promotion 只允许写入 `v0.2` 专属 catalog、runtime path 和 GitOps branch;不得向 `G14` 或现有 DEV/PROD runtime path 写入生成物,也不得从 `/root/hwlab-v02` 工作树状态选择待发布 commit。 G14 lane 中如果同名 PipelineRun 已存在,poller 直接跳过;如果不存在,则创建新的 PipelineRun。这样可以用 Kubernetes 原生 CronJob、ServiceAccount、RBAC 和 Tekton API 实现无 GitHub webhook 的分支监控,同时避免同一个 commit 被反复派单。历史上的 `chore: promote G14 GitOps source ...` source 分支生成提交已经废弃;G14 poller 只应该看到人写 source commit。 -Pipeline 的标准路径是:`G14` source commit -> Tekton 原语校验 task -> commit-tagged image push 到 G14 registry -> refresh artifact catalog -> render `deploy/gitops/g14/**` -> push catalog 与 GitOps desired state 到 `G14-gitops` -> Argo CD 同步 runtime。CD 只消费已经构建好的镜像和 Git desired state,不在 Argo CD 内构建镜像。 +Pipeline 的标准路径是:`G14` source commit -> Tekton 原语校验 task -> commit-tagged image push 到 G14 registry -> refresh artifact catalog -> render `deploy/gitops/node/**` -> push catalog 与 GitOps desired state 到 `G14-gitops` -> Argo CD 同步 runtime。CD 只消费已经构建好的镜像和 Git desired state,不在 Argo CD 内构建镜像。 -GitOps promotion 成功只证明 `G14-gitops` 分支已经写入新 desired state;如果 render 改变了 Argo Application、AppProject、runtime path 或 DEV/PROD 拆分目录,必须同步检查并应用 `deploy/gitops/g14/argocd/project.yaml`、`application-dev.yaml` 和 `application-prod.yaml`。`hwlab-g14-dev` 必须指向 `deploy/gitops/g14/runtime-dev`,`hwlab-g14-prod` 必须指向 `deploy/gitops/g14/runtime-prod`;如果集群里 Application 仍指向旧 `deploy/gitops/g14/runtime`,Argo 会停在旧 revision,即使 CI/publish/promote 全部成功也不会滚动新镜像。 +GitOps promotion 成功只证明 `G14-gitops` 分支已经写入新 desired state;如果 render 改变了 Argo Application、AppProject、runtime path 或 DEV/PROD 拆分目录,必须同步检查并应用 `deploy/gitops/node/argocd/project.yaml`、`application-dev.yaml` 和 `application-prod.yaml`。`hwlab-node-dev` 必须指向 `deploy/gitops/node/runtime-dev`,`hwlab-node-prod` 必须指向 `deploy/gitops/node/runtime-prod`;如果集群里 Application 仍指向旧 `deploy/gitops/node/runtime`,Argo 会停在旧 revision,即使 CI/publish/promote 全部成功也不会滚动新镜像。 观察 PipelineRun、Argo 和 rollout 时使用短连接轮询:一次 `kubectl get` 或有限 `logs --tail` 后返回,由指挥侧间隔重试。不要用长时间 `kubectl wait --timeout=900s` 占住 `tran G14:k3s` 透传锁;如果误用长 wait,只能杀掉本地等待客户端并清理 stale tran lock,不能把这个等待过程当成 CD 失败。 @@ -147,17 +147,17 @@ KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-ci logs pod/`。poller 还没创建 PipelineRun 时,不要先把问题归类为 Tekton 故障。 -- 如果本轮改的是 poller、Pipeline、RBAC、reconciler 或 render 模板,先确认 `hwlab-g14-control-plane-reconciler` 已经把新控制面 apply 进去,再用新 commit 验证。 +- 再看 `hwlab-ci` 里的 PipelineRun 是否出现 `hwlab-node-ci-poll-`。poller 还没创建 PipelineRun 时,不要先把问题归类为 Tekton 故障。 +- 如果本轮改的是 poller、Pipeline、RBAC、reconciler 或 render 模板,先确认 `hwlab-node-control-plane-reconciler` 已经把新控制面 apply 进去,再用新 commit 验证。 5. 确认 CI 真实通过 -- 最低通过条件是:`prepare-source`、`repo-reports-guard`、`g14-contract-check`、`codex-api-forwarder-check`、`plan-artifacts`、per-service fan-out、`collect-artifacts`、`gitops-promote` 全部成功。 +- 最低通过条件是:`prepare-source`、`repo-reports-guard`、`node-contract-check`、`codex-api-forwarder-check`、`plan-artifacts`、per-service fan-out、`collect-artifacts`、`gitops-promote` 全部成功。 - `build-*` fan-out 要按 result 判断 reused 还是 rebuilt;不要只看 TaskRun 名称。 - reuse-only 变更的目标是 `affectedServices=[]`、全部 service `status=reused`,且不触发无意义 rollout。 diff --git a/docs/reference/spec-lane-expansion.md b/docs/reference/spec-lane-expansion.md index 6d2b52f8..522a5bab 100644 --- a/docs/reference/spec-lane-expansion.md +++ b/docs/reference/spec-lane-expansion.md @@ -1,6 +1,6 @@ # HWLAB 平行 Runtime Lane 扩容规格 -本文是 HWLAB `v0.x` 平行 runtime lane 的长期扩容规格。`v0.3` 之后的 `v0.4`、`v0.5`、`v0.6` 等扩容必须沿用同一模式:人工只维护 `deploy/deploy.yaml` 中的 lane 声明,然后通过 repo-owned CLI 生成或应用扩容动作;不得再为每个新 lane 复制多处代码、手写 GitOps YAML、手动拼 namespace/Secret/FRP/Tekton/Argo 对象,或恢复 D601/G14 旧兼容路径。 +本文是 HWLAB `v0.x` 平行 runtime lane 的长期扩容规格。`v0.3` 之后的 `v0.4`、`v0.5`、`v0.6` 等扩容必须沿用同一模式:人工只维护 `deploy/deploy.yaml` 中的 lane 声明,然后通过 repo-owned CLI 生成或应用扩容动作;不得再为每个新 lane 复制多处代码、手写 GitOps YAML、手动拼 namespace/Secret/FRP/Tekton/Argo 对象,或恢复 D601/旧节点兼容路径。 ## 标准入口 @@ -17,9 +17,9 @@ node scripts/hwlab-lane-expand.mjs configure --lane v03 --from v02 --write | `v03` | source branch `v0.3` | | `v03` | GitOps branch `v0.3-gitops` | | `v03` | namespace `hwlab-v03` | -| `v03` | runtime path `deploy/gitops/g14/runtime-v03` | +| `v03` | runtime path `deploy/gitops/node/runtime-v03` | | `v03` | artifact catalog `deploy/artifact-catalog.v03.json` | -| `v03` | Argo Application `hwlab-g14-v03` | +| `v03` | Argo Application `hwlab-node-v03` | | `v03` | Tekton Pipeline `hwlab-v03-ci-image-publish` | | `v03` | default legacy FRP ports `19766/19767` | @@ -37,32 +37,32 @@ node scripts/hwlab-lane-expand.mjs configure --lane v03 --from v02 --write ## GitOps 模块化与严格 TS -`v0.3` 起,GitOps 扩容逻辑不得继续堆进单个 `scripts/g14-gitops-render.mjs`。该文件只保留历史入口兼容、参数解析和高层编排;新增或抽出的 GitOps 逻辑必须进入 `scripts/src/` 下的领域模块,并优先使用 `.ts`。 +`v0.3` 起,GitOps 扩容逻辑不得继续堆进单个 `scripts/gitops-render.mjs`。该文件只保留历史入口兼容、参数解析和高层编排;新增或抽出的 GitOps 逻辑必须进入 `scripts/src/` 下的领域模块,并优先使用 `.ts`。 -第一批严格 TS 模块是 `scripts/src/g14-gitops-lane.ts`,负责 runtime lane 的命名、端口、endpoint、branch、catalog 和 `deploy.lanes.` 回填。该模块由 `tsconfig.gitops.json` 以 `strict`、`exactOptionalPropertyTypes`、`noUncheckedIndexedAccess` 等选项检查;验证入口是: +第一批严格 TS 模块是 `scripts/src/runtime-lane.ts`,负责 runtime lane 的命名、端口、endpoint、branch、catalog 和 `deploy.lanes.` 回填。该模块由 `tsconfig.gitops.json` 以 `strict`、`exactOptionalPropertyTypes`、`noUncheckedIndexedAccess` 等选项检查;验证入口是: ```bash npm run gitops:ts:check ``` -后续从 renderer、Tekton、Argo、FRP、Git mirror 或 artifact helper 继续拆出的模块,必须直接纳入该严格 TS 检查范围;不要先拆成 `.mjs` 再另开长期迁移债。`scripts/g14-gitops-render.mjs` 因为导入 TS 模块,实际执行必须走 Bun 入口: +后续从 renderer、Tekton、Argo、FRP、Git mirror 或 artifact helper 继续拆出的模块,必须直接纳入该严格 TS 检查范围;不要先拆成 `.mjs` 再另开长期迁移债。`scripts/gitops-render.mjs` 因为导入 TS 模块,实际执行必须走 Bun 入口: ```bash -npm run g14:gitops:render -- --lane v03 +npm run gitops:render -- --lane v03 ``` -生成的 Tekton 任务和 control-plane reconciler 也必须使用 `node scripts/run-bun.mjs scripts/g14-gitops-render.mjs ...`,不得再用裸 `node scripts/g14-gitops-render.mjs ...`。 +生成的 Tekton 任务和 control-plane reconciler 也必须使用 `node scripts/run-bun.mjs scripts/gitops-render.mjs ...`,不得再用裸 `node scripts/gitops-render.mjs ...`。 ## 废弃路径 -`G14` 和 D601 旧分支/runtime 只保留归档或事故对照价值,不再是新 HWLAB runtime lane 的 CI/CD 支持对象。`G14` 的归档分支名是 `v0.1`;CI/CD 不再为了 `v0.1` 保持 namespace、Pipeline、GitOps branch、poller、reconciler 或 FRP 兼容。若旧对象仍存在,应通过受控 CLI 分阶段退役,不能作为 v0.3+ 发布证据。 +旧节点和 D601 旧分支/runtime 只保留归档或事故对照价值,不再是新 HWLAB runtime lane 的 CI/CD 支持对象。旧节点归档分支名是 `v0.1`;CI/CD 不再为了 `v0.1` 保持 namespace、Pipeline、GitOps branch、poller、reconciler 或 FRP 兼容。若旧对象仍存在,应通过受控 CLI 分阶段退役,不能作为 v0.3+ 发布证据。 ## 验收边界 一个新 lane 只有同时满足以下条件才算扩容完成: - 远端 source branch 和 GitOps branch 存在,并与 `deploy.lanes.` 声明一致。 -- G14 k3s 中存在独立 namespace、Tekton Pipeline/RBAC、Argo AppProject/Application 和 runtime desired state。 +- 目标 node k3s 中存在独立 namespace、Tekton Pipeline/RBAC、Argo AppProject/Application 和 runtime desired state。 - runtime namespace、SecretRef、PVC、ServiceAccount、FRP proxy、artifact catalog 和 observability 对象均使用 lane 独立命名。 - `control-plane trigger-current --lane ` 能创建 commit-pinned PipelineRun,promotion 只写入对应 GitOps branch/path。 - 公网 Web/API health 命中该 lane 的 namespace 和 revision;不得用 DEV/PROD、D601 或 `v0.1` health 作为通过证据。 diff --git a/docs/reference/spec-v02-cicd.md b/docs/reference/spec-v02-cicd.md index eac61051..aa9959ba 100644 --- a/docs/reference/spec-v02-cicd.md +++ b/docs/reference/spec-v02-cicd.md @@ -20,11 +20,11 @@ CI/CD 内部由 UniDesk 受控触发入口、CI/CD 专用 source repo、devops-i | 接口 | 说明 | | --- | --- | -| `scripts/g14-gitops-render.mjs --lane v02` | v02 GitOps render 入口,负责 namespace、runtime path、catalog 和 endpoint 固定。 | +| `scripts/gitops-render.mjs --lane v02` | v02 GitOps render 入口,负责 namespace、runtime path、catalog 和 endpoint 固定。 | | UniDesk v02 PR auto-CD monitor | 观察并合并 base=`v0.2` 的 ready PR,merge 后调用现有 `trigger-current`、定点 `status` 和 `git-mirror flush`。 | | Tekton `hwlab-v02-ci-image-publish` | 构建 affected images、复用 unchanged digest,并 promotion 到 `v0.2-gitops`。 | | `devops-infra` git mirror/relay | 读写 `v0.2` allowlist refs;CI 读 source/catalog、写 `v0.2-gitops`,并通过手动 CLI flush 到 GitHub。 | -| Argo `argocd/hwlab-g14-v02` | 从 `devops-infra` 本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/g14/runtime-v02` 同步到 `hwlab-v02`。 | +| Argo `argocd/hwlab-node-v02` | 从 `devops-infra` 本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/node/runtime-v02` 同步到 `hwlab-v02`。 | | `http://74.48.78.17:19666/` | v02 Cloud Web 公网入口。 | | `http://74.48.78.17:19667/health/live` | v02 API/live 公网验收入口。 | @@ -45,7 +45,7 @@ CI/CD 内部由 UniDesk 受控触发入口、CI/CD 专用 source repo、devops-i | CI/CD source repo | `G14:/root/hwlab-v02-cicd.git` bare repo,由 UniDesk control-plane 自动 fetch `origin/v0.2`,再从 commit-pinned detached worktree render/apply 控制面 | | GitOps branch | `v0.2-gitops` | | Artifact catalog | `v0.2-gitops:deploy/artifact-catalog.v02.json` | -| Runtime path | `v0.2-gitops:deploy/gitops/g14/runtime-v02` | +| Runtime path | `v0.2-gitops:deploy/gitops/node/runtime-v02` | | Runtime namespace | `hwlab-v02` | | Tekton Pipeline | `hwlab-ci/hwlab-v02-ci-image-publish` | | PR merge auto-CD | UniDesk CLI v02 PR monitor 合并 base=`v0.2` PR 后自动调用 `trigger-current`,并定点观察 merge head | @@ -55,36 +55,36 @@ CI/CD 内部由 UniDesk 受控触发入口、CI/CD 专用 source repo、devops-i | Tekton ServiceAccount | `hwlab-ci/hwlab-v02-tekton-runner` | | PipelineRun prefix | `hwlab-v02-ci-poll-` | | Argo CD AppProject | `argocd/hwlab-v02` | -| Argo CD Application | `argocd/hwlab-g14-v02` | +| Argo CD Application | `argocd/hwlab-node-v02` | | FRP Deployment | `hwlab-v02/hwlab-v02-frpc` | | Web entry | `http://74.48.78.17:19666/` | | API/live entry | `http://74.48.78.17:19667/health/live` | | Git mirror/relay | 独立 `devops-infra` 集群读写服务;不设 CronJob;读 source/catalog,写 `v0.2-gitops`,按需由 UniDesk CLI 手动 sync/flush | | Registry | 维持当前 G14 `hwlab-ci/hwlab-registry` | -`scripts/g14-gitops-render.mjs --lane v02` 是当前 `v0.2` GitOps render 入口。该 lane 必须把默认 source branch 改为 `v0.2`、GitOps branch 改为 `v0.2-gitops`、catalog 改为 `deploy/artifact-catalog.v02.json`、runtime endpoint 改为 `19667`、web endpoint 改为 `19666`,并使用完整 source commit 作为 image tag。 +`scripts/gitops-render.mjs --lane v02` 是当前 `v0.2` GitOps render 入口。该 lane 必须把默认 source branch 改为 `v0.2`、GitOps branch 改为 `v0.2-gitops`、catalog 改为 `deploy/artifact-catalog.v02.json`、runtime endpoint 改为 `19667`、web endpoint 改为 `19666`,并使用完整 source commit 作为 image tag。 ## 真相源 `v0.2` 的发布真相按以下顺序判断: 1. live runtime:`hwlab-v02` namespace 中 Deployment/StatefulSet template、Pod ready、事件、日志和 `19666/19667` 公网 health。 -2. Argo desired state:`argocd/hwlab-g14-v02` 的 revision、sync、health、source branch 和 runtime path。 +2. Argo desired state:`argocd/hwlab-node-v02` 的 revision、sync、health、source branch 和 runtime path。 3. CI/CD source refs:UniDesk control-plane 专用 bare repo `refs/remotes/origin/v0.2`、devops-infra mirror/relay 的 `refs/heads/v0.2` 与 `refs/mirror-stage/heads/v0.2` 必须共同指向最新 source commit。 -4. devops-infra mirror/relay 中的 GitOps branch:`v0.2-gitops` 中的 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/g14/runtime-v02/**`。 +4. devops-infra mirror/relay 中的 GitOps branch:`v0.2-gitops` 中的 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/node/runtime-v02/**`。 5. Tekton 执行证据:UniDesk `trigger-current` 返回的 PipelineRun、TaskRun result、`gitops-promote` 终态。 6. GitHub 上游归档状态:mirror/relay flush 后的 `origin/v0.2-gitops` 与 `origin/v0.2`。 7. 固定开发 workspace:`/root/hwlab-v02` 的 `HEAD`、dirty 状态和 `origin/v0.2` 只作为人工开发对照线索;即使 workspace 脏或落后,也不得影响 CI/CD source commit 选择。 旧 commit 记忆、`G14`/`G14-gitops` DEV/PROD 产物、D601 legacy 路径、GitHub 上游尚未 flush 的短暂落后、source branch 中历史 generated 文件、固定开发 workspace 脏状态和临时 worktree 只能作为线索,不能作为 `v0.2` 发布通过证据。 -source workspace 中被 `.gitignore` 忽略的 `deploy/gitops/g14/runtime-v02/**` 文件只可能是本地生成缓存。若这类文件与 `v0.2-gitops` 或 live runtime 不一致,应删除本地缓存并以 `v0.2-gitops`、Argo 和 live ConfigMap/Deployment 为准;不要在 source branch 修补 ignored generated 文件,也不要把它们的内容写入 issue closeout 作为发布证据。 +source workspace 中被 `.gitignore` 忽略的 `deploy/gitops/node/runtime-v02/**` 文件只可能是本地生成缓存。若这类文件与 `v0.2-gitops` 或 live runtime 不一致,应删除本地缓存并以 `v0.2-gitops`、Argo 和 live ConfigMap/Deployment 为准;不要在 source branch 修补 ignored generated 文件,也不要把它们的内容写入 issue closeout 作为发布证据。 ## Workspace 与 CI/CD 分离 `v0.2` 开发 workspace 和 CI/CD repo 必须分离。`/root/hwlab-v02` 是人工开发、短连接 `hwlab-cli` 和问题复现的固定 workspace;它允许出现并行任务产生的 untracked `.worktree/`、本地 dirty 文件或临时落后状态。CI/CD 不从该 checkout 的 `HEAD`、工作树 clean 状态或本地 branch 读取待发布 commit,也不得因为该 workspace 脏而跳过、误判或复用旧 PipelineRun。 -UniDesk control-plane 必须使用独立 bare repo `/root/hwlab-v02-cicd.git` 作为 CI/CD source repo:每次 `status`、`apply`、`git-mirror apply` 和 `trigger-current` 先自动 fetch `origin/v0.2` 到 `refs/remotes/origin/v0.2`,再用目标 commit 创建 detached temp worktree 运行 `scripts/g14-gitops-render.mjs --lane v02`。该 repo 没有业务工作树,因此不会被人工开发 dirty 状态污染;如果它不可用,应以 `v02-head-unresolved` 或等价结构化错误失败,而不是回退到固定 workspace HEAD。 +UniDesk control-plane 必须使用独立 bare repo `/root/hwlab-v02-cicd.git` 作为 CI/CD source repo:每次 `status`、`apply`、`git-mirror apply` 和 `trigger-current` 先自动 fetch `origin/v0.2` 到 `refs/remotes/origin/v0.2`,再用目标 commit 创建 detached temp worktree 运行 `scripts/gitops-render.mjs --lane v02`。该 repo 没有业务工作树,因此不会被人工开发 dirty 状态污染;如果它不可用,应以 `v02-head-unresolved` 或等价结构化错误失败,而不是回退到固定 workspace HEAD。 devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。`trigger-current --lane v02 --confirm` 在创建 PipelineRun 前必须比较 `expectedSourceHead` 与 mirror `localV02`,不一致时自动执行 bounded `git-mirror sync` Job。`git-mirror status` 必须分别暴露 `localV02`、`githubV02`、`localGitops`、`githubGitops`、`sourceInSync`、`gitopsInSync` 和 `pendingFlush`;`githubInSync` 不得再被理解为只代表 GitOps branch。 @@ -99,7 +99,7 @@ devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。` `v0.2` source branch 不得跟踪: - `deploy/artifact-catalog.v02.json`。 -- `deploy/gitops/g14/runtime-v02/**`。 +- `deploy/gitops/node/runtime-v02/**`。 - Tekton/Argo 的 rendered runtime desired state。 - image digest、publish state、reuse evidence 或 CI 生成的 rollout metadata。 @@ -108,7 +108,7 @@ devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。` `v0.2-gitops` branch 必须包含: - `deploy/artifact-catalog.v02.json`,记录 image tag、digest、source commit、component identity、publish/reuse 状态。 -- `deploy/gitops/g14/runtime-v02/**`,作为 Argo CD 实际消费的 desired state。 +- `deploy/gitops/node/runtime-v02/**`,作为 Argo CD 实际消费的 desired state。 - 必要的 generated metadata,但不得包含 Secret 值。 首次初始化时,如果 `v0.2-gitops:deploy/artifact-catalog.v02.json` 尚不存在,只允许由 `v0.2` lane 的正式初始化步骤创建第一版 catalog。不得 fallback 到 `G14` source catalog、`G14-gitops` catalog、DEV runtime path 或 source branch 生成物。 @@ -131,8 +131,8 @@ devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。` 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`。 +8. promotion 刷新 `deploy/artifact-catalog.v02.json`,render `deploy/gitops/node/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-node-v02` 从本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/node/runtime-v02` 同步到 `hwlab-v02`。 10. UniDesk CLI auto-CD 观察定点 `control-plane status --lane v02 --source-commit ` 或 `--pipeline-run `,确认 PipelineRun、Argo、runtime workload、public probes 和 planArtifacts build/reuse 摘要。历史 merge head 若因后续 `origin/v0.2` 推进而被 superseded,PR 评论以 superseded 收口;最新 head 仍必须继续收敛到 runtime。 11. UniDesk CLI 或 mirror/relay flush 操作把本地 `v0.2-gitops` 推送到 GitHub canonical remote;flush 不在 CI runtime-ready 的关键路径内,但自动 CD 收口必须查询 pending、lastFlushed 和 failure,必要时执行 `git-mirror flush --confirm` 并等待 `pendingFlush=false`。 12. 验收只观察 `hwlab-v02` runtime 和 `19666/19667`,并把 source commit、PipelineRun、GitOps revision、build/reuse 摘要、Argo/runtime/public probe 状态写回 PR 或关联 issue。 @@ -143,7 +143,7 @@ devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。` GitOps promotion 成功后,本地 mirror/relay 的 `v0.2-gitops` 可立即被 Argo 消费;GitHub 上游 flush 是归档与跨节点同步步骤,不在 runtime-ready 关键路径内。`git-mirror status.pendingFlush=true` 时自动 CD 和人工收口都应运行 `git-mirror flush --confirm`,再用该 flush job 的 `job status` 或 `git-mirror status` 确认 `pendingFlush=false`、`localGitops=githubGitops`。不要把 pending flush 误判为 runtime 未发布,也不要在 flush job 仍运行时反复做全量 status。 -`v0.2` 可以复用 G14 的 registry、proxy、BuildKit、工具镜像和脚本库;不得复用 `hwlab-g14-ci-image-publish`、`hwlab-g14-branch-poller`、`hwlab-g14-control-plane-reconciler`、`G14-gitops` runtime path 或 DEV/PROD Argo Application 作为 `v0.2` 发布入口。运行时不得为每个版本硬编码 namespace、catalog、runtime path 或健康判断;版本差异只通过 `deploy.yaml.lanes[profile]`、GitOps render 输入和实际 runtime 对象表达,新增版本不得新增运行时代码分支。 +`v0.2` 可以复用 G14 的 registry、proxy、BuildKit、工具镜像和脚本库;不得复用 `hwlab-node-ci-image-publish`、`hwlab-node-branch-poller`、`hwlab-node-control-plane-reconciler`、`G14-gitops` runtime path 或 DEV/PROD Argo Application 作为 `v0.2` 发布入口。运行时不得为每个版本硬编码 namespace、catalog、runtime path 或健康判断;版本差异只通过 `deploy.yaml.lanes[profile]`、GitOps render 输入和实际 runtime 对象表达,新增版本不得新增运行时代码分支。 `v0.2` 不设 k8s 自动轮询发布。历史上若存在 `hwlab-v02-branch-poller`、`hwlab-v02-control-plane-reconciler` 或等价 CronJob,均视为迁移残留,应由 UniDesk control-plane apply 清理。PR 合并后的自动 CD 只能由 UniDesk CLI monitor 或一次性 reconcile job 驱动,并且必须复用 `trigger-current`、定点 `status` 和 `git-mirror flush`。暂停发布时停止 UniDesk monitor 或停止创建新的 PipelineRun;不要通过新增 CronJob 维持重试。 @@ -280,7 +280,7 @@ P1 no-op runtime skip 只跳过无实际 runtime 变化的等待。planner 输 Pipeline 内部的 no-op fast lane 必须形成完整证据链。 `prepare-source` 输出 `source-clone` 与 `catalog-fetch` timing。 -`plan-artifacts` 输出 `g14-ci-plan`,其中 `buildServices=[]`、`rolloutServices=[]` 且 runtime service 全部 reuse。 +`plan-artifacts` 输出 `ci-plan`,其中 `buildServices=[]`、`rolloutServices=[]` 且 runtime service 全部 reuse。 `gitops-promote` 输出 `skipped-runtime-unchanged`、`runtime-identity-only`,并把 `gitops-commit` 与 `gitops-push` 标为 `skipped`。 PipelineRun 的 skipped tasks 中必须包含 `runtime-ready`。 这条证据链缺一项时,不能只看总耗时通过,应先确认是日志缺失、planner 误判还是 GitOps runtime 比对退化。 @@ -340,7 +340,7 @@ for task in prepare-source plan-artifacts collect-artifacts gitops-promote runti -o jsonpath="{.items[0].metadata.name}") echo "--- $task $pod selected logs ---" pattern="skipped-runtime-unchanged|runtime-ready-required|runtime-identity-only" - pattern="$pattern|g14-cicd-timing|git-operation|g14-ci-plan|artifact_reuse|buildSkippedCount" + pattern="$pattern|node-cicd-timing|git-operation|ci-plan|artifact_reuse|buildSkippedCount" pattern="$pattern|gitops-commit|gitops-push|runtime-ready|progress|argo-refresh" pattern="$pattern|argo-sync-health|workload-ready|observedCount" kubectl logs -n "$ns" "$pod" --all-containers=true | \ @@ -356,7 +356,7 @@ done | `prepare-source` 回到 20s 以上 | 日志是否出现 `npm ci`、npm registry probe、GitHub URL、SSH setup 或长时间 catalog fetch | 拆掉无效依赖安装和重复探针;读路径必须回到 mirror。 | | `source-clone` 超过 5s | PipelineRun 参数 `git-read-url`、mirror service、PVC I/O、`git-mirror status` | 不要改回 GitHub 直连;先修 mirror/read service。 | | catalog 缺失导致全量 build | `prepare-source` 是否从 `v0.2-gitops` 取到 `deploy/artifact-catalog.v02.json` | 先 `git-mirror sync --confirm`;必要时查 mirror refs/object closure。 | -| `buildSkippedCount` 偏低或突然变成 0 | `g14-ci-plan` 的 `affectedServices`、`buildServices`、`rolloutServices`、catalog 加载状态、`package.json` 字段 diff 和 service component path | 真实业务变更可以 build;脚本清理、CLI/测试/文档、host asset 或 code-only env-reuse 变更不应误触发全量 build。 | +| `buildSkippedCount` 偏低或突然变成 0 | `ci-plan` 的 `affectedServices`、`buildServices`、`rolloutServices`、catalog 加载状态、`package.json` 字段 diff 和 service component path | 真实业务变更可以 build;脚本清理、CLI/测试/文档、host asset 或 code-only env-reuse 变更不应误触发全量 build。 | | 多 service rebuild 仍串行执行 | `tekton-v02/pipeline.yaml` 中所有 `build-*` task 的 `runAfter`,以及 TaskRun startTime 是否都紧跟 `plan-artifacts` | build task 只能依赖 `plan-artifacts`;发现 `build-B` 依赖 `build-A` 时直接修 render 脚本和生成物,不保留串行兼容路径。 | | 全并行 rebuild 变慢或不稳定 | TaskRun duration、Pod Pending/Unschedulable、node pressure、PVC I/O、BuildKit sidecar 日志、本地 registry push latency | 优先定位资源容量或 registry/BuildKit 瓶颈;不要先把 Pipeline 拓扑改回串行或固定 8 并发。 | | no-op 仍执行 `runtime-ready` | `gitops-promote` 是否输出 `runtime-ready-required=false` 和 `skipped-runtime-unchanged` | 查 runtime 归一化比较;不要直接删除 `runtime-ready`,只修 no-op 判定。 | @@ -488,7 +488,7 @@ registry 与 git mirror/relay 分属不同基础设施边界。registry 保持 - `hwlab-v02` namespace 只能由 `v0.2` GitOps lane 管理。 - `argocd/hwlab-v02` AppProject destination 只能包含 `hwlab-v02`。 -- `argocd/hwlab-g14-v02` source 必须指向 `devops-infra` 本地 mirror/relay 中的 `v0.2-gitops:deploy/gitops/g14/runtime-v02`,destination 必须是 `hwlab-v02`。 +- `argocd/hwlab-node-v02` source 必须指向 `devops-infra` 本地 mirror/relay 中的 `v0.2-gitops:deploy/gitops/node/runtime-v02`,destination 必须是 `hwlab-v02`。 - `hwlab-v02-frpc` 只能暴露 `19666/19667`,不能复用 `17666/17667` 或 `18666/18667`。 - `v0.2` Secret、DB 凭据、PVC、ServiceAccount 和 runtime config 必须独立命名或独立 namespace scope;文档、issue、trace 和 report 只记录 SecretRef 名称与 key,不记录值。 - `v0.2` 初期不新增自动 registry GC;后续如启用清理,selector 必须带 lane/profile 标签,registry digest 保护集必须同时覆盖 `G14-gitops` 与 `v0.2-gitops`。 @@ -502,11 +502,11 @@ registry 与 git mirror/relay 分属不同基础设施边界。registry 保持 - GitOps branch 必须是 `v0.2-gitops`。 - runtime namespace 必须是 `hwlab-v02`。 - artifact catalog 必须是 `deploy/artifact-catalog.v02.json`。 -- runtime path 必须是 `deploy/gitops/g14/runtime-v02`。 -- Argo Application 必须是 `hwlab-g14-v02`,且只能部署到 `hwlab-v02`。 -- source branch publish 后不得出现 `deploy/artifact-catalog.v02.json` 或 `deploy/gitops/g14/runtime-v02/**` 变更。 -- source workspace 下若残留 ignored `deploy/gitops/g14/runtime-v02/**` 生成物,不能作为 source diff、PR 内容、回归证据或人工修补目标;发现和当前 `v0.2-gitops` 不一致时删除本地缓存。 -- GitOps promotion 的 changed paths 只能落在 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/g14/runtime-v02/**` 及必要的 v02 Argo/GitOps 元数据。 +- runtime path 必须是 `deploy/gitops/node/runtime-v02`。 +- Argo Application 必须是 `hwlab-node-v02`,且只能部署到 `hwlab-v02`。 +- source branch publish 后不得出现 `deploy/artifact-catalog.v02.json` 或 `deploy/gitops/node/runtime-v02/**` 变更。 +- source workspace 下若残留 ignored `deploy/gitops/node/runtime-v02/**` 生成物,不能作为 source diff、PR 内容、回归证据或人工修补目标;发现和当前 `v0.2-gitops` 不一致时删除本地缓存。 +- GitOps promotion 的 changed paths 只能落在 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/node/runtime-v02/**` 及必要的 v02 Argo/GitOps 元数据。 - 公网验收只能使用 `19666/19667`。 - `v0.2` 不得创建或依赖 k8s CronJob;`hwlab-v02-branch-poller`、`hwlab-v02-control-plane-reconciler` 或同类调度器出现时应清理,而不是接入发布链路。 - GitHub webhook 不是 v02 首期自动 CD 的必需入口;若未来接入,只能作为唤醒信号,不能直接决定发布 commit 或创建 PipelineRun。 @@ -527,11 +527,11 @@ registry 与 git mirror/relay 分属不同基础设施边界。registry 保持 - `G14` source branch 的 poller/reconciler 语义。 - `G14-gitops` DEV/PROD catalog 与 runtime desired state。 - `hwlab-dev` 与 `hwlab-prod` namespace。 -- `hwlab-g14-dev` 与 `hwlab-g14-prod` Argo Application。 +- `hwlab-node-dev` 与 `hwlab-node-prod` Argo Application。 - DEV `17666/17667` 与 PROD `18666/18667` FRP 入口。 - D601 legacy 回溯路径和旧运行面边界。 -如果 `v0.2` 接入失败,回滚或暂停只能作用于 `hwlab-v02` lane:停止 UniDesk v02 monitor、停止手动触发新的 v02 PipelineRun、暂停或删除 `hwlab-g14-v02`、回滚 `v0.2-gitops` runtime path、关闭 `hwlab-v02-frpc` 或清理 `hwlab-v02` namespace 资源;不得通过新增 CronJob 维持重试,也不得重启、删除或回滚 DEV/PROD 运行面。 +如果 `v0.2` 接入失败,回滚或暂停只能作用于 `hwlab-v02` lane:停止 UniDesk v02 monitor、停止手动触发新的 v02 PipelineRun、暂停或删除 `hwlab-node-v02`、回滚 `v0.2-gitops` runtime path、关闭 `hwlab-v02-frpc` 或清理 `hwlab-v02` namespace 资源;不得通过新增 CronJob 维持重试,也不得重启、删除或回滚 DEV/PROD 运行面。 ## 验收标准 @@ -542,10 +542,10 @@ registry 与 git mirror/relay 分属不同基础设施边界。registry 保持 - 同一个 source commit 已有 PipelineRun 时不重复创建,也不删除重建;失败 run 的人工重试必须走后续显式 retry 策略,不恢复默认 delete/create。 - 自动 CD 在旧 run 运行期间遇到新 `origin/v0.2` head 时,新 head 可直接触发自己的 PipelineRun;旧 run 继续完成 CI,但 promotion 必须在写 GitOps 前发现 stale head 并 superseded/no-op,不能回写旧 `v0.2-gitops` 或回滚 runtime。 - 最新 `v0.2` source commit 对应的 PipelineRun 完成,且 promotion 写入 `v0.2-gitops`。 -- `v0.2-gitops` 中存在 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/g14/runtime-v02/**`。 -- `argocd/hwlab-g14-v02` 指向 `devops-infra` 本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/g14/runtime-v02`,sync revision 与目标 GitOps revision 对齐。 +- `v0.2-gitops` 中存在 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/node/runtime-v02/**`。 +- `argocd/hwlab-node-v02` 指向 `devops-infra` 本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/node/runtime-v02`,sync revision 与目标 GitOps revision 对齐。 - `hwlab-v02` 中长驻 workload ready,没有把 DEV/PROD namespace 当成 `v0.2` 通过证据。 -- `gitops-promote` 推送本地 mirror/relay 的 `v0.2-gitops` 后应触发 `argocd/hwlab-g14-v02` hard refresh,减少 GitOps push 与 Argo 仓库发现之间的漂移窗口;refresh 触发失败只作为低噪声事件输出,最终通过仍由 `runtime-ready` 判定。 +- `gitops-promote` 推送本地 mirror/relay 的 `v0.2-gitops` 后应触发 `argocd/hwlab-node-v02` hard refresh,减少 GitOps push 与 Argo 仓库发现之间的漂移窗口;refresh 触发失败只作为低噪声事件输出,最终通过仍由 `runtime-ready` 判定。 - mirror/relay status 必须能显示本地 `v0.2-gitops` revision、GitHub 已 flush revision、pending flush revision 和最近一次 flush 错误;CI 通过不要求 GitHub flush 已完成,但 auto-CD 收口必须在 `pendingFlush=true` 时触发 flush 并最终观察到 `pendingFlush=false`。 - `runtime-ready` 必须以 workload Pod template 的 source commit 判断 Argo 刷新,并在 Argo 刷新超时、observer RBAC 不足或 workload 未就绪时失败;不得把 CrashLoop、未刷新或公网不可用的 runtime 标成绿色。 - 启用 env 容器复用的 runtime service 必须在 catalog、Pod template annotation/env 和 `/health/live` 或等价 runtime identity 中同时暴露 env image digest 与 `HWLAB_BOOT_REPO`/`HWLAB_BOOT_COMMIT`/`HWLAB_BOOT_SH`;只暴露镜像 digest 不能证明 service code-only rollout 已生效。 @@ -557,7 +557,7 @@ registry 与 git mirror/relay 分属不同基础设施边界。registry 保持 - `trigger-current` 的异步 job 或 `--wait` 输出必须能显示 trigger 阶段进度;若 control-plane refresh、mirror pre-sync 或 delete/create PipelineRun 卡住,日志必须能定位阶段名和状态。 - 自动 CD 回写 PR 或关联 issue 的 rollout 评论必须包含 source commit、PipelineRun、GitOps revision、planArtifacts build/reuse 摘要、Argo/runtime/public probe 状态和耗时;该评论不能替代需要用户原入口验收的问题关闭证据。 - 自动 CD 过程中 source branch 的 `deploy/deploy.yaml` 不得出现 `commitId`、service `image`、`HWLAB_COMMIT_ID`、`HWLAB_IMAGE` 或 `HWLAB_IMAGE_TAG` 回写。 -- no-op env-reuse fast lane 必须输出完整证据链:`g14-ci-plan` 全复用、`skipped-runtime-unchanged`、`gitops-commit`/`gitops-push` skipped、`runtime-ready` skipped。 +- no-op env-reuse fast lane 必须输出完整证据链:`ci-plan` 全复用、`skipped-runtime-unchanged`、`gitops-commit`/`gitops-push` skipped、`runtime-ready` skipped。 不能只用 PipelineRun `Completed` 或总耗时证明没有退化。 - 真实 rollout 的 `runtime-ready` 必须输出 `started`、周期性 `progress` 和最终 `argo-refresh`/`argo-sync-health`/`workload-ready` 类事件;缺少这些事件时应按可见性回归处理。 - Bun 语法检查和测试入口必须分开:`bun --check ` 只用于非测试 TypeScript 源文件语法/转译检查;`*.test.ts`、`*.test.mts` 或其他使用 `test(...)` 的文件必须用 `bun test ` 或 repo-owned package script 执行。不得再用 `bun --check` 直接跑 `.test.ts`,该命令会在测试运行器外求值并报 `Cannot use test outside of the test runner`,这不是业务回归证据。 @@ -568,11 +568,11 @@ GitOps branch 已更新、source branch render 通过、PipelineRun 名称存在 ## T1 -阅读 docs/reference/spec-v02-cicd.md,然后用 cli 手动测试以下内容:确认 `origin/v0.2` 最新 commit 对应的 PipelineRun 完成,promotion 只写入 `v0.2-gitops`,且 source branch 没有跟踪 `deploy/artifact-catalog.v02.json` 或 `deploy/gitops/g14/runtime-v02/**` 生成物。 +阅读 docs/reference/spec-v02-cicd.md,然后用 cli 手动测试以下内容:确认 `origin/v0.2` 最新 commit 对应的 PipelineRun 完成,promotion 只写入 `v0.2-gitops`,且 source branch 没有跟踪 `deploy/artifact-catalog.v02.json` 或 `deploy/gitops/node/runtime-v02/**` 生成物。 ## T2 -阅读 docs/reference/spec-v02-cicd.md,然后用 cli 手动测试以下内容:查询 Argo `hwlab-g14-v02`,确认 source repo 为 `devops-infra` 本地 mirror/relay,branch/path 为 `v0.2-gitops:deploy/gitops/g14/runtime-v02`,destination namespace 为 `hwlab-v02`,sync revision 与目标 GitOps revision 对齐。 +阅读 docs/reference/spec-v02-cicd.md,然后用 cli 手动测试以下内容:查询 Argo `hwlab-node-v02`,确认 source repo 为 `devops-infra` 本地 mirror/relay,branch/path 为 `v0.2-gitops:deploy/gitops/node/runtime-v02`,destination namespace 为 `hwlab-v02`,sync revision 与目标 GitOps revision 对齐。 ## T3 @@ -615,7 +615,7 @@ GitOps branch 已更新、source branch render 通过、PipelineRun 名称存在 | PR merge 后自动 CD | 待迁移 | 目标见 #848:UniDesk CLI v02 PR monitor 合并 base=`v0.2` PR 后复用 `trigger-current`、定点 `status` 和 `git-mirror flush`;首期不要求 GitHub webhook,也不创建 k8s CronJob。 | | v02 runtime readiness fail-closed | 已实现 | `gitops-promote` 推送后触发 Argo hard refresh;`runtime-ready` 读取 workload Pod template source commit,timeout、observer RBAC 不足或未就绪会让 PipelineRun 失败。 | | v02 裁撤服务不进入发布面 | 已实现 | v02 Tekton build service set、artifact catalog 和 runtime render 只包含保留服务;v02 Argo 开启 prune 清理旧 live 对象;裁撤服务仍可保留在 DEV legacy 源码中。 | -| Argo v02 Application | 已实现 | `hwlab-g14-v02` 指向 v02 GitOps path 和 namespace。 | +| Argo v02 Application | 已实现 | `hwlab-node-v02` 指向 v02 GitOps path 和 namespace。 | | FRP `19666/19667` 入口 | 已实现 | 由 `hwlab-v02-frpc` 与 master frps allowlist 共同提供。 | | SecretRef 独立与 provider 验收 | 已实现/持续约束 | SecretRef 已独立;验收必须做真实短连接聊天。 | | env 容器复用三变量启动 | 已实现/持续约束 | 五个 v0.2 runtime service 均启用 env-reuse fast lane,并由 CI/CD 自动推导 `HWLAB_BOOT_REPO`、`HWLAB_BOOT_COMMIT`、`HWLAB_BOOT_SH`;service code-only rollout 复用 env image digest,只更新代码身份。 | @@ -653,7 +653,7 @@ Code Agent 的 Secret 修复不是只改一个 Secret 对象就结束。`hwlab-c ### GitOps 与 runtime 收敛 -GitOps promotion 成功不等于 runtime 已经运行新版本。验收必须等待 `argocd/hwlab-g14-v02` 的 sync revision 对齐最新 `v0.2-gitops` revision,并确认 `19667/health/live` 的 `environment`、`endpoint`、关键 SecretRef 和 service revision 与目标 lane 对齐。 +GitOps promotion 成功不等于 runtime 已经运行新版本。验收必须等待 `argocd/hwlab-node-v02` 的 sync revision 对齐最新 `v0.2-gitops` revision,并确认 `19667/health/live` 的 `environment`、`endpoint`、关键 SecretRef 和 service revision 与目标 lane 对齐。 Argo `Synced/Healthy` 也不能单独替代公网验证。FRP server 侧 `allowPorts` 缺失时,`hwlab-v02-frpc` 会反复报告 `port not allowed`;这种问题应修 master 侧 `frps` allowlist 并只重启 `hwlab-frps-dev`,不要改 DEV/PROD GitOps、Service 或 v02 runtime path。修复后必须同时验证新增 `19666/19667` 和既有 DEV `17666/17667`。 diff --git a/docs/reference/spec-v02-observability-monitoring.md b/docs/reference/spec-v02-observability-monitoring.md index 55960b0c..9e1f0b05 100644 --- a/docs/reference/spec-v02-observability-monitoring.md +++ b/docs/reference/spec-v02-observability-monitoring.md @@ -6,7 +6,7 @@ - G14 共享监控控制面运行在 `devops-infra` 平台基础设施 namespace,由 UniDesk/G14 基础设施规格管理。它承载 Prometheus Operator、Prometheus 实例、可选 Alertmanager/Grafana 以及跨 namespace scrape 选择策略。 - `hwlab-v02` 只承载 HWLAB 业务服务、`/metrics` endpoint、Service labels/ports、`ServiceMonitor` / `PodMonitor` 和 `PrometheusRule`。不得把 Prometheus Operator、Prometheus、Grafana 或 Alertmanager 部署进 `hwlab-v02`。 -- `hwlab-g14-v02` Argo Application 仍只同步 `deploy/gitops/g14/runtime-v02` 到 `hwlab-v02`。监控控制面不能挂到这个 Application;它只消费业务 namespace 中的监控声明。 +- `hwlab-node-v02` Argo Application 仍只同步 `deploy/gitops/node/runtime-v02` 到 `hwlab-v02`。监控控制面不能挂到这个 Application;它只消费业务 namespace 中的监控声明。 - `hwlab-cli` 或 UniDesk 受控 CLI 可以作为查询入口,但必须通过 runtime namespace/lane 自动解析目标,不得手动暴露 Prometheus 公网地址。 ## 内部架构 @@ -91,7 +91,7 @@ sidecar 必须至少暴露以下基础指标: ### `ServiceMonitor` -`deploy/gitops/g14/runtime-v02` 应生成或包含 v0.2 业务服务的 `ServiceMonitor`。Selector 必须同时约束: +`deploy/gitops/node/runtime-v02` 应生成或包含 v0.2 业务服务的 `ServiceMonitor`。Selector 必须同时约束: - namespace 为 `hwlab-v02` - `hwlab.pikastech.local/gitops-target=v02` @@ -139,7 +139,7 @@ HWLAB v0.2 可声明 `PrometheusRule`,但规则只表达当前 v0.2 目标行 ## T3 -阅读本文,然后用 CLI 手动测试以下内容:读取 `deploy/gitops/g14/runtime-v02` 或 live `hwlab-v02` 对象,确认 `ServiceMonitor` selector 限定 `hwlab-v02`、`gitops-target=v02`、`monitoring=enabled` 和具体 service id;确认不存在把 Prometheus、Grafana 或 Alertmanager 部署到 `hwlab-v02` 的对象。 +阅读本文,然后用 CLI 手动测试以下内容:读取 `deploy/gitops/node/runtime-v02` 或 live `hwlab-v02` 对象,确认 `ServiceMonitor` selector 限定 `hwlab-v02`、`gitops-target=v02`、`monitoring=enabled` 和具体 service id;确认不存在把 Prometheus、Grafana 或 Alertmanager 部署到 `hwlab-v02` 的对象。 ## T4 diff --git a/docs/reference/spec-v02-services.md b/docs/reference/spec-v02-services.md index 0ae6c1a8..fd13b6d5 100644 --- a/docs/reference/spec-v02-services.md +++ b/docs/reference/spec-v02-services.md @@ -96,7 +96,7 @@ origin/v0.2 | `frpc` / `frps` | 外部稳定服务 | 保留 | [spec-v02-frpc.md](spec-v02-frpc.md) | | Postgres | 外部稳定服务 | 保留 | [spec-v02-postgres.md](spec-v02-postgres.md) | | OpenFGA | 外部稳定服务 / 内部授权 PDP | 新增并保留,ClusterIP-only | [spec-v02-openfga-authorization.md](spec-v02-openfga-authorization.md) | -| Argo CD / Tekton / BuildKit / registry | 外部稳定服务 | 保留 | [spec-v02-cicd.md](spec-v02-cicd.md)、[g14-gitops-cicd.md](g14-gitops-cicd.md) | +| Argo CD / Tekton / BuildKit / registry | 外部稳定服务 | 保留 | [spec-v02-cicd.md](spec-v02-cicd.md)、[node-gitops-cicd.md](node-gitops-cicd.md) | | Codex CLI | 外部工具/runtime | 保留 | [code-agent-chat-readiness.md](code-agent-chat-readiness.md) | | Keil / pyOCD / UART 工具 | 外部或主机侧工具 | 由 HWPOD spec 和 node-ops 绑定使用 | [spec-hwpod-harness.md](spec-hwpod-harness.md) | @@ -121,7 +121,7 @@ hwlab-agent-worker ## T1 -阅读 docs/reference/spec-v02-services.md,然后用 CLI 手动测试以下内容:列出 `deploy/deploy.yaml`、`deploy/deploy.schema.json`、`deploy/artifact-catalog.dev.json` 和 `deploy/gitops/g14/runtime-v02`,确认 runtime service set 只包含 `hwlab-cloud-api`、`hwlab-cloud-web`、`hwlab-gateway`、`hwlab-edge-proxy` 和 `hwlab-agent-skills`。 +阅读 docs/reference/spec-v02-services.md,然后用 CLI 手动测试以下内容:列出 `deploy/deploy.yaml`、`deploy/deploy.schema.json`、`deploy/artifact-catalog.dev.json` 和 `deploy/gitops/node/runtime-v02`,确认 runtime service set 只包含 `hwlab-cloud-api`、`hwlab-cloud-web`、`hwlab-gateway`、`hwlab-edge-proxy` 和 `hwlab-agent-skills`。 ## T2 diff --git a/internal/agent/agentrun-dispatch.mjs b/internal/agent/agentrun-dispatch.mjs index fa759f8d..d68af0c1 100644 --- a/internal/agent/agentrun-dispatch.mjs +++ b/internal/agent/agentrun-dispatch.mjs @@ -1,7 +1,8 @@ export const DEFAULT_AGENTRUN_NAMESPACE = "agentrun-v01"; export const DEFAULT_AGENTRUN_MANAGER_URL = "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080"; export const DEFAULT_HWLAB_AGENTRUN_PROJECT_ID = "pikasTech/HWLAB"; -export const DEFAULT_HWLAB_AGENTRUN_PROVIDER_ID = "G14"; +export const HWLAB_AGENTRUN_PROVIDER_ID_ENV = "HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID"; +export const LEGACY_AGENTRUN_PROVIDER_ID_ENV = "AGENTRUN_PROVIDER_ID"; export const DEFAULT_HWLAB_AGENTRUN_REPO_URL = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"; export const DEFAULT_HWLAB_AGENTRUN_BRANCH = "v0.2"; export const DEFAULT_HWLAB_AGENTRUN_BACKEND_PROFILE = "deepseek"; @@ -148,7 +149,7 @@ export function createHwlabAgentRunDispatchAssembly(options = {}) { const namespace = nonEmptyString(options.namespace ?? DEFAULT_AGENTRUN_NAMESPACE, "namespace"); const managerUrl = trimTrailingSlash(nonEmptyString(options.managerUrl ?? DEFAULT_AGENTRUN_MANAGER_URL, "managerUrl")); const projectId = DEFAULT_HWLAB_AGENTRUN_PROJECT_ID; - const providerId = DEFAULT_HWLAB_AGENTRUN_PROVIDER_ID; + const providerId = resolveHwlabAgentRunProviderId(env); const repoUrl = nonEmptyString(options.repoUrl ?? DEFAULT_HWLAB_AGENTRUN_REPO_URL, "repoUrl"); const branch = nonEmptyString(options.branch ?? DEFAULT_HWLAB_AGENTRUN_BRANCH, "branch"); const timeoutMs = positiveInteger(options.timeoutMs ?? 600000, "timeoutMs"); @@ -397,6 +398,13 @@ function positiveInteger(value, fieldName) { return parsed; } +function resolveHwlabAgentRunProviderId(env = process.env) { + const providerId = optionalString(env[HWLAB_AGENTRUN_PROVIDER_ID_ENV] ?? env[LEGACY_AGENTRUN_PROVIDER_ID_ENV]); + if (!providerId) throw new Error(`${HWLAB_AGENTRUN_PROVIDER_ID_ENV} is required and must be injected from the runtime lane node id`); + if (!/^[A-Za-z0-9._-]+$/u.test(providerId)) throw new Error(`${HWLAB_AGENTRUN_PROVIDER_ID_ENV} must be a simple node id`); + return providerId; +} + function nonEmptyString(value, fieldName) { const text = optionalString(value); if (!text) throw new Error(`${fieldName} is required`); diff --git a/internal/agent/agentrun-dispatch.test.mjs b/internal/agent/agentrun-dispatch.test.mjs index c4667ea9..85ed6721 100644 --- a/internal/agent/agentrun-dispatch.test.mjs +++ b/internal/agent/agentrun-dispatch.test.mjs @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import test from "node:test"; +import test, { after, before } from "node:test"; import { DEFAULT_UNIDESK_MAIN_SERVER_ENV, @@ -8,6 +8,17 @@ import { } from "./agentrun-dispatch.mjs"; const commitId = "8da27c83fa56d87e78e488a819afe898cf97c62b"; +let previousProviderId; + +before(() => { + previousProviderId = process.env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID; + process.env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID = "G14"; +}); + +after(() => { + if (previousProviderId === undefined) delete process.env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID; + else process.env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID = previousProviderId; +}); test("HWLAB AgentRun assembly grants GitHub and UniDesk SSH through toolCredentials", () => { const assembly = createHwlabAgentRunDispatchAssembly({ diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index 423825f5..b16520a3 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -16,7 +16,6 @@ const ADAPTER_ID = "agentrun-v01"; const DEFAULT_AGENTRUN_MGR_URL = "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080"; const DEFAULT_TENANT_ID = "hwlab"; const DEFAULT_PROJECT_ID = "pikasTech/HWLAB"; -const DEFAULT_PROVIDER_ID = "G14"; const DEFAULT_REPO_URL = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"; const DEFAULT_RUNNER_NAMESPACE = "agentrun-v01"; const DEFAULT_TIMEOUT_MS = 1_200_000; @@ -71,8 +70,13 @@ export function describeAgentRunAdapterAvailability(env = process.env, options = } catch (error) { blockers.push(agentRunAvailabilityBlocker(error, "resource-bundle-source-commit")); } + let providerId = null; + try { + providerId = resolveAgentRunProviderId(env); + } catch (error) { + blockers.push(agentRunAvailabilityBlocker(error, "provider-id")); + } - const providerId = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID); const runnerNamespace = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE, env.AGENTRUN_RUNTIME_NAMESPACE, DEFAULT_RUNNER_NAMESPACE); const ready = blockers.length === 0 && Boolean(providerId && runnerNamespace && backendProfile && managerUrl && repoUrl); const managerHost = hostForUrl(managerUrl); @@ -233,7 +237,7 @@ export function initialAgentRunChatResult({ params = {}, options = {}, traceId } adapter: ADAPTER_ID, status: "pending-dispatch", backendProfile, - providerId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID), + providerId: resolveAgentRunProviderId(env), managerUrl: resolveAgentRunManagerUrl(env), repoUrl: resolveAgentRunRepoUrl(env), resourceBundleSourceCommit: requireAgentRunSourceCommit(env), @@ -729,7 +733,7 @@ export function agentRunSessionEvidence(payload = {}) { terminalStatus: payload.agentRun.terminalStatus ?? null, traceId: payload.agentRun.traceId ?? payload.traceId ?? payload.providerTrace?.traceId ?? null, lastSeq: payload.agentRun.lastSeq ?? 0, - providerId: payload.agentRun.providerId ?? DEFAULT_PROVIDER_ID, + providerId: payload.agentRun.providerId ?? agentRunProviderIdOrNull(), reuseEligible: payload.agentRun.reuseEligible ?? false, sessionId: payload.agentRun.sessionId ?? payload.sessionId ?? null, conversationId: payload.conversationId ?? payload.agentRun.conversationId ?? null, @@ -828,7 +832,7 @@ function buildAgentRunCreateRunInput({ params, env, traceId, backendProfile, ses } }, resourceBundleRef, - providerId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID), + providerId: resolveAgentRunProviderId(env), backendProfile, executionPolicy: { sandbox: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_SANDBOX, env.HWLAB_CODE_AGENT_CODEX_SANDBOX, "danger-full-access"), @@ -1695,7 +1699,7 @@ function agentRunMapping({ env, managerUrl, backendProfile, run, command, runner adapter: ADAPTER_ID, managerUrl, backendProfile, - providerId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID), + providerId: resolveAgentRunProviderId(env), runId: run.id, commandId: command.id, attemptId: runnerJob?.attemptId ?? runnerJob?.runner?.attemptId ?? null, @@ -1730,7 +1734,7 @@ function agentRunReusedMapping({ previous = {}, run = {}, command = {}, runnerJo adapter: ADAPTER_ID, managerUrl, backendProfile, - providerId: previous.providerId ?? firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID), + providerId: previous.providerId ?? resolveAgentRunProviderId(env), runId: previous.runId ?? run?.id, commandId: command.id, attemptId: runnerJob?.attemptId ?? runnerJob?.runner?.attemptId ?? previous.attemptId ?? null, @@ -1903,6 +1907,31 @@ function requireAgentRunSourceCommit(env) { }); } +function resolveAgentRunProviderId(env = process.env) { + const providerId = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID); + if (!providerId) { + throw Object.assign(new Error("HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID must be configured from the runtime lane node id before AgentRun dispatch"), { + code: "agentrun_provider_id_required", + statusCode: 500 + }); + } + if (!/^[A-Za-z0-9._-]+$/u.test(providerId)) { + throw Object.assign(new Error("HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID must be a simple node id"), { + code: "agentrun_provider_id_invalid", + statusCode: 500 + }); + } + return providerId; +} + +function agentRunProviderIdOrNull(env = process.env) { + try { + return resolveAgentRunProviderId(env); + } catch { + return null; + } +} + function modelForBackendProfile(profile, env = process.env) { const resolved = resolveAgentRunBackendProfile({}, { providerProfile: profile }); if (resolved === "codex") return firstNonEmpty(env.HWLAB_CODE_AGENT_CODEX_API_MODEL, env.HWLAB_CODE_AGENT_MODEL, "gpt-5.5"); diff --git a/package.json b/package.json index 13ce3e23..886b6e71 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "web:build": "(cd web/hwlab-cloud-web && bun run build)", "artifact-catalog:preview-blocked": "node scripts/refresh-artifact-catalog.mjs --target-ref HEAD --blocked --no-write", "lane:expand": "node scripts/hwlab-lane-expand.mjs", - "g14:gitops:render": "node scripts/run-bun.mjs scripts/g14-gitops-render.mjs", + "gitops:render": "node scripts/run-bun.mjs scripts/gitops-render.mjs", "gitops:ts:check": "tsc --noEmit -p tsconfig.gitops.json", "docs:validate:m3-rollout": "node scripts/validate-m3-rollout-runbook.mjs", "d601:k3s:readonly": "node scripts/d601-k3s-readonly-observability.mjs", diff --git a/scripts/artifact-publish.mjs b/scripts/artifact-publish.mjs index 840e0f1b..badcb70d 100644 --- a/scripts/artifact-publish.mjs +++ b/scripts/artifact-publish.mjs @@ -14,7 +14,7 @@ import { fileURLToPath } from "node:url"; import { activeReportLifecycle } from "../internal/dev-report-lifecycle.mjs"; import { ENVIRONMENT_DEV, SERVICE_IDS } from "../internal/protocol/index.mjs"; -import { createG14CiPlan } from "./src/g14-ci-plan-lib.mjs"; +import { createCiPlan } from "./src/ci-plan-lib.mjs"; import { BUILDABLE_IMPLEMENTATION_STATES, resolveDevArtifactServices, @@ -26,7 +26,7 @@ import { readStructuredFile } from "./src/structured-config.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const cliEntrypoint = process.env.HWLAB_ARTIFACT_PUBLISH_ENTRYPOINT || "scripts/artifact-publish.mjs"; -const defaultLane = process.env.HWLAB_ARTIFACT_LANE || process.env.HWLAB_GITOPS_LANE || "g14"; +const defaultLane = process.env.HWLAB_ARTIFACT_LANE || process.env.HWLAB_GITOPS_LANE || "node"; const defaultRegistryPrefix = process.env.HWLAB_DEV_REGISTRY_PREFIX || process.env.HWLAB_DEV_REGISTRY || "127.0.0.1:5000/hwlab"; const defaultBaseImage = process.env.HWLAB_DEV_BASE_IMAGE || null; @@ -121,7 +121,7 @@ function parseArgs(argv) { else throw new Error(`unknown argument ${arg}`); } - assert.ok(args.lane === "g14" || isRuntimeArtifactLane(args.lane), `unknown artifact lane ${args.lane}`); + assert.ok(args.lane === "node" || isRuntimeArtifactLane(args.lane), `unknown artifact lane ${args.lane}`); assert.ok(["short", "full"].includes(args.imageTagMode), `unknown image tag mode ${args.imageTagMode}`); return args; @@ -146,12 +146,12 @@ function artifactIdentityValue(env, name) { return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; } -function g14ArtifactIdentityName(env = process.env) { +function artifactIdentityName(env = process.env) { return ciArtifactIdentityEnvNames.find((name) => artifactIdentityValue(env, name)); } -function g14ArtifactProducer(env = process.env) { - const idName = g14ArtifactIdentityName(env); +function artifactProducer(env = process.env) { + const idName = artifactIdentityName(env); if (!idName) { return { kind: "missing", @@ -160,24 +160,24 @@ function g14ArtifactProducer(env = process.env) { }; } return { - kind: "g14-ci-artifact", + kind: "node-ci-artifact", runId: artifactIdentityValue(env, idName), owner: artifactIdentityValue(env, "HWLAB_CI_ARTIFACT_RUN_OWNER") ?? idName }; } -function g14ArtifactPublishIdentityGuardFailure({ script, mode }) { +function artifactPublishIdentityGuardFailure({ script, mode }) { return { ok: false, status: "blocked", - error: "g14-artifact-publish-identity-required", - code: "g14-artifact-publish-identity-required", + error: "artifact-publish-identity-required", + code: "artifact-publish-identity-required", script, mode, requiredEnv: "HWLAB_CI_ARTIFACT_RUN_ID or standard CI run identity", acceptedEnv: ciArtifactIdentityEnvNames, - entrypoint: `HWLAB_CI_ARTIFACT_RUN_ID= HWLAB_CI_ARTIFACT_RUN_OWNER= node scripts/g14-artifact-publish.mjs --publish --report ${tempReportPath("dev-artifacts.json")}`, - summary: `${script} ${mode} is a G14 artifact publish side-effect step and must run under CI artifact identity. Legacy D601 CD transaction identity is not accepted.`, + entrypoint: `HWLAB_CI_ARTIFACT_RUN_ID= HWLAB_CI_ARTIFACT_RUN_OWNER= node scripts/artifact-publish.mjs --publish --report ${tempReportPath("dev-artifacts.json")}`, + summary: `${script} ${mode} is a node artifact publish side-effect step and must run under CI artifact identity. Legacy D601 CD transaction identity is not accepted.`, devOnly: true, prodTouched: false, cdLockRequired: false, @@ -186,8 +186,8 @@ function g14ArtifactPublishIdentityGuardFailure({ script, mode }) { }; } -function requireG14ArtifactPublishIdentity({ env = process.env, script, mode }) { - return g14ArtifactIdentityName(env) ? null : g14ArtifactPublishIdentityGuardFailure({ script, mode }); +function requireArtifactPublishIdentity({ env = process.env, script, mode }) { + return artifactIdentityName(env) ? null : artifactPublishIdentityGuardFailure({ script, mode }); } function printHelp() { @@ -197,7 +197,7 @@ function printHelp() { "Commit-pinned artifact build/publish helper for controlled CI execution.", "", "options:", - " --lane LANE g14 or runtime lane vNN; default: g14", + " --lane LANE node or runtime lane vNN; default: node", ` --catalog-path PATH default: ${defaultCatalogPath}`, ` --deploy-config PATH default: ${defaultDeployPath}`, " --image-tag-mode MODE short or full; v02 uses full source commit IDs", @@ -235,11 +235,11 @@ function emit(event, payload = {}) { } function ciTimingEnabled(env = process.env) { - return env.HWLAB_G14_CICD_TIMING === "1" || env.HWLAB_CI_TIMING === "1"; + return env.HWLAB_NODE_CICD_TIMING === "1" || env.HWLAB_CI_TIMING === "1"; } function emitCiTiming(payload) { - emit("g14-cicd-timing", { + emit("node-cicd-timing", { schemaVersion: "v1", source: "scripts/artifact-publish.mjs", pipelineRun: process.env.HWLAB_TEKTON_PIPELINERUN ?? null, @@ -305,7 +305,7 @@ function parseDockerfileStepTimings(text) { } catch { continue; } - if (parsed?.event !== "g14-cicd-build-step-timing") continue; + if (parsed?.event !== "node-cicd-build-step-timing") continue; timings.push({ stage: parsed.stage, status: parsed.status ?? "unknown", @@ -369,7 +369,7 @@ function buildkitBaseImagePreflight(baseImage) { blockers: [], nextSteps: [], recommendation: { - source: "G14 k8s registry image used by rootless BuildKit.", + source: "node k8s registry image used by rootless BuildKit.", envVar: "HWLAB_DEV_BASE_IMAGE", recommendedEnvValue: requestedImage }, @@ -638,7 +638,7 @@ function artifactCatalogSkeleton({ args, deployManifest, commitId }) { ciPublished: false, registryVerified: false, provenance: "not_available_until_publish", - note: `${artifactEnvironment(args)} artifact catalog skeleton initialized without DEV/G14 fallback.` + note: `${artifactEnvironment(args)} artifact catalog skeleton initialized without DEV/node fallback.` }, allowedProfiles: [artifactEnvironment(args)], forbiddenProfiles: isRuntimeArtifactLane(args.lane) ? ["dev", "prod"] : ["prod"], @@ -808,7 +808,7 @@ async function resolveServices(serviceIds, catalog) { } function dockerfile(baseImage, port, labels = []) { - const dependencyTimingScript = `started_at=$(node -e "console.log(Date.now())"); status=succeeded; if [ -d /opt/hwlab-node-runtime-base/node_modules ]; then cp -a /opt/hwlab-node-runtime-base/node_modules ./node_modules; fi && if [ -f package-lock.json ]; then npm install --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi && if [ "\${HWLAB_ARTIFACT_KIND:-}" = "bun-command" ]; then bun_pkg=""; case "$(uname -m)" in x86_64|amd64) bun_pkg="@oven/bun-linux-x64@1.3.13" ;; aarch64|arm64) bun_pkg="@oven/bun-linux-aarch64@1.3.13" ;; *) echo "unsupported bun-command architecture: $(uname -m)" >&2; false ;; esac && npm install --no-save --omit=dev --include=optional --ignore-scripts "$bun_pkg" && test -x "node_modules/\${bun_pkg%@*}/bin/bun" && ln -sf "/app/node_modules/\${bun_pkg%@*}/bin/bun" /usr/local/bin/bun; fi && codex_version="$(node -p "require('./node_modules/@openai/codex/package.json').version")" && case "$(uname -m)" in x86_64|amd64) test -e node_modules/@openai/codex-linux-x64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-x64@npm:@openai/codex@\${codex_version}-linux-x64" ;; aarch64|arm64) test -e node_modules/@openai/codex-linux-arm64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-arm64@npm:@openai/codex@\${codex_version}-linux-arm64" ;; esac || status=failed; finished_at=$(node -e "console.log(Date.now())"); node -e "console.log(JSON.stringify({event:'g14-cicd-build-step-timing',stage:'dependency-install',status:process.argv[1],durationMs:Number(process.argv[3])-Number(process.argv[2]),source:'dockerfile-run'}))" "$status" "$started_at" "$finished_at"; test "$status" = succeeded`; + const dependencyTimingScript = `started_at=$(node -e "console.log(Date.now())"); status=succeeded; if [ -d /opt/hwlab-node-runtime-base/node_modules ]; then cp -a /opt/hwlab-node-runtime-base/node_modules ./node_modules; fi && if [ -f package-lock.json ]; then npm install --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi && if [ "\${HWLAB_ARTIFACT_KIND:-}" = "bun-command" ]; then bun_pkg=""; case "$(uname -m)" in x86_64|amd64) bun_pkg="@oven/bun-linux-x64@1.3.13" ;; aarch64|arm64) bun_pkg="@oven/bun-linux-aarch64@1.3.13" ;; *) echo "unsupported bun-command architecture: $(uname -m)" >&2; false ;; esac && npm install --no-save --omit=dev --include=optional --ignore-scripts "$bun_pkg" && test -x "node_modules/\${bun_pkg%@*}/bin/bun" && ln -sf "/app/node_modules/\${bun_pkg%@*}/bin/bun" /usr/local/bin/bun; fi && codex_version="$(node -p "require('./node_modules/@openai/codex/package.json').version")" && case "$(uname -m)" in x86_64|amd64) test -e node_modules/@openai/codex-linux-x64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-x64@npm:@openai/codex@\${codex_version}-linux-x64" ;; aarch64|arm64) test -e node_modules/@openai/codex-linux-arm64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-arm64@npm:@openai/codex@\${codex_version}-linux-arm64" ;; esac || status=failed; finished_at=$(node -e "console.log(Date.now())"); node -e "console.log(JSON.stringify({event:'node-cicd-build-step-timing',stage:'dependency-install',status:process.argv[1],durationMs:Number(process.argv[3])-Number(process.argv[2]),source:'dockerfile-run'}))" "$status" "$started_at" "$finished_at"; test "$status" = succeeded`; const runtimeReadinessScript = `set -eu; fail() { echo "hwlab-artifact-runtime-check-failed:$1" >&2; exit 1; }; mkdir -p /workspace /codex-home; rm -rf /workspace/hwlab; ln -s /app /workspace/hwlab; if [ "\${HWLAB_ARTIFACT_KIND:-}" = "bun-command" ]; then test -x /usr/local/bin/bun || fail bun-missing; /usr/local/bin/bun --version >/tmp/hwlab-bun-version.txt || fail bun-version; fi; chmod -R a+rwX /app /workspace /codex-home || fail chmod; test -x /app/node_modules/.bin/codex || fail codex-bin-missing; /app/node_modules/.bin/codex --version >/tmp/hwlab-codex-version.txt || fail codex-version; test -x /app/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex/codex || test -x /app/node_modules/@openai/codex-linux-arm64/vendor/aarch64-unknown-linux-musl/codex/codex || fail codex-native-missing`; return [ `FROM ${baseImage}`, @@ -1032,7 +1032,7 @@ async function preflight({ args, services, catalog, deployManifest, baseImagePre type: "safety_blocker", scope: "registry", summary: error.message, - next: "Use the G14 local/internal registry prefix, for example 127.0.0.1:5000/hwlab." + next: "Use the node local/internal registry prefix, for example 127.0.0.1:5000/hwlab." }) ); } @@ -1072,7 +1072,7 @@ async function preflight({ args, services, catalog, deployManifest, baseImagePre type: "environment_blocker", scope: "buildkit-command", summary: `BuildKit command ${args.buildkitCommand} is not available in the publish task image.`, - next: "Use the G14 Tekton BuildKit path that prepares buildctl/buildkitd before running --publish." + next: "Use the node Tekton BuildKit path that prepares buildctl/buildkitd before running --publish." }) ); } @@ -2038,14 +2038,14 @@ function createReport({ args, repo, commitId, shortCommit, mode, services, artif documents: [ "docs/reference/MVP-e2e-acceptance.md", "docs/reference/spec-v02-documentation-governance.md", - "docs/reference/g14-gitops-cicd.md", + "docs/reference/node-gitops-cicd.md", "docs/reference/spec-v02-cicd.md" ], summary: `${environment} artifact publish evidence; runtime acceptance stays separate from artifact publication.` }, validationCommands: [ "node --check scripts/artifact-publish.mjs", - "node --check scripts/g14-artifact-publish.mjs", + "node --check scripts/artifact-publish.mjs", "node --check scripts/src/dev-artifact-services.mjs", "node --check scripts/src/registry-capabilities.mjs", "node --check scripts/preflight-dev-base-image.mjs", @@ -2053,7 +2053,7 @@ function createReport({ args, repo, commitId, shortCommit, mode, services, artif "node --check scripts/dev-runtime-base-image.mjs", "node --test scripts/refresh-artifact-catalog.test.mjs", "node scripts/preflight-dev-base-image.mjs", - "node scripts/g14-artifact-publish.mjs --preflight --no-report", + "node scripts/artifact-publish.mjs --preflight --no-report", "node --check scripts/validate-dev-gate-report.mjs", "node scripts/validate-dev-gate-report.mjs" ], @@ -2197,7 +2197,7 @@ async function main() { return; } if (args.mode === "publish") { - const identityGuard = requireG14ArtifactPublishIdentity({ + const identityGuard = requireArtifactPublishIdentity({ script: cliEntrypoint, mode: "--publish" }); @@ -2237,8 +2237,8 @@ async function main() { } const buildCreatedAt = new Date().toISOString(); const repo = repoLabelFromRemote(remoteUrl); - const producer = g14ArtifactProducer(process.env); - const ciPlan = await createG14CiPlan({ + const producer = artifactProducer(process.env); + const ciPlan = await createCiPlan({ repoRoot, lane: args.lane, targetRef: "HEAD", diff --git a/scripts/g14-ci-plan.mjs b/scripts/ci-plan.mjs similarity index 75% rename from scripts/g14-ci-plan.mjs rename to scripts/ci-plan.mjs index 842c202e..523f6996 100644 --- a/scripts/g14-ci-plan.mjs +++ b/scripts/ci-plan.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -import { createG14CiPlan } from "./src/g14-ci-plan-lib.mjs"; +import { createCiPlan } from "./src/ci-plan-lib.mjs"; const args = parseArgs(process.argv.slice(2)); @@ -8,7 +8,7 @@ if (args.help) { process.exit(0); } -const plan = await createG14CiPlan(args); +const plan = await createCiPlan(args); process.stdout.write(args.pretty ? `${JSON.stringify(plan, null, 2)}\n` : `${JSON.stringify(plan)}\n`); function parseArgs(argv) { @@ -38,14 +38,14 @@ function readOption(argv, index, name) { function usage() { return [ - "usage: node scripts/g14-ci-plan.mjs [--lane v02|v03] [--base-ref REF] [--target-ref REF] [--pretty]", + "usage: node scripts/ci-plan.mjs [--lane v02|v03] [--base-ref REF] [--target-ref REF] [--pretty]", "", - "Plan G14 monorepo image builds without mutating CI/CD state.", + "Plan node monorepo image builds without mutating CI/CD state.", "For runtime lane env reuse, service component boundaries and env recipe must come from deploy/deploy.yaml.", "", "examples:", - " node scripts/g14-ci-plan.mjs --lane v02 --base-ref HEAD~1 --target-ref HEAD --pretty", - " node scripts/g14-ci-plan.mjs --lane v03 --base-ref HEAD~1 --target-ref HEAD --pretty", - " node scripts/g14-ci-plan.mjs --services hwlab-cloud-api,hwlab-cloud-web --pretty" + " node scripts/ci-plan.mjs --lane v02 --base-ref HEAD~1 --target-ref HEAD --pretty", + " node scripts/ci-plan.mjs --lane v03 --base-ref HEAD~1 --target-ref HEAD --pretty", + " node scripts/ci-plan.mjs --services hwlab-cloud-api,hwlab-cloud-web --pretty" ].join("\n"); } diff --git a/scripts/g14-ci-plan.test.mjs b/scripts/ci-plan.test.mjs similarity index 96% rename from scripts/g14-ci-plan.test.mjs rename to scripts/ci-plan.test.mjs index 98f21ab5..619dab0b 100644 --- a/scripts/g14-ci-plan.test.mjs +++ b/scripts/ci-plan.test.mjs @@ -9,11 +9,11 @@ import { promisify } from "node:util"; import { classifyGlobalChange, componentModelsForServices, - createG14CiPlan, + createCiPlan, envReuseRecipeForLane, matchingPaths, packageRuntimeFieldsChanged -} from "./src/g14-ci-plan-lib.mjs"; +} from "./src/ci-plan-lib.mjs"; import { readStructuredFile, writeStructuredFile } from "./src/structured-config.mjs"; const execFileAsync = promisify(execFile); @@ -26,7 +26,7 @@ const V02_RUNTIME_SERVICE_IDS = Object.freeze([ "hwlab-agent-skills" ]); -test("G14 CI planner maps bridge changes to cloud-api only", async () => { +test("node CI planner maps bridge changes to cloud-api only", async () => { const repo = await createFixtureRepo(); await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.ts"), "console.log('bridge v2');\n"); await git(repo, ["add", "."]); @@ -40,10 +40,10 @@ test("G14 CI planner maps bridge changes to cloud-api only", async () => { assert.deepEqual(cloudApi.reason, ["code-input-changed", "component-path-changed"]); }); -test("G14 CI planner treats docs-only changes as no image build", async () => { +test("node CI planner treats docs-only changes as no image build", async () => { const repo = await createFixtureRepo(); await mkdir(path.join(repo, "docs/reference"), { recursive: true }); - await writeFile(path.join(repo, "docs/reference/g14-gitops-cicd.md"), "docs v2\n"); + await writeFile(path.join(repo, "docs/reference/node-gitops-cicd.md"), "docs v2\n"); await git(repo, ["add", "."]); await git(repo, ["commit", "-m", "change docs"]); @@ -56,7 +56,7 @@ test("G14 CI planner treats docs-only changes as no image build", async () => { test("planner rebuilds when catalog digest is missing instead of blocking reuse", async () => { const repo = await createFixtureRepo({ catalog: "missing-digest" }); await mkdir(path.join(repo, "docs/reference"), { recursive: true }); - await writeFile(path.join(repo, "docs/reference/g14-gitops-cicd.md"), "docs v2\n"); + await writeFile(path.join(repo, "docs/reference/node-gitops-cicd.md"), "docs v2\n"); await git(repo, ["add", "."]); await git(repo, ["commit", "-m", "change docs"]); @@ -69,7 +69,7 @@ test("planner rebuilds when catalog digest is missing instead of blocking reuse" test("v02 planner rolls cloud-api service code changes without rebuilding service image", async () => { const repo = await createFixtureRepo(); - const initialPlan = await createG14CiPlan({ + const initialPlan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD", @@ -91,7 +91,7 @@ test("v02 planner rolls cloud-api service code changes without rebuilding servic await git(repo, ["add", "cmd/hwlab-cloud-api/main.ts"]); await git(repo, ["commit", "-m", "change cloud-api service code"]); - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -112,11 +112,11 @@ test("v02 planner rolls cloud-api service code changes without rebuilding servic test("v02 planner rebuilds env image when an env reuse catalog digest is missing", async () => { const repo = await createFixtureRepo({ catalog: "missing-digest" }); await mkdir(path.join(repo, "docs/reference"), { recursive: true }); - await writeFile(path.join(repo, "docs/reference/g14-gitops-cicd.md"), "docs only with missing env digest\n"); - await git(repo, ["add", "docs/reference/g14-gitops-cicd.md"]); + await writeFile(path.join(repo, "docs/reference/node-gitops-cicd.md"), "docs only with missing env digest\n"); + await git(repo, ["add", "docs/reference/node-gitops-cicd.md"]); await git(repo, ["commit", "-m", "docs after missing env digest"]); - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -143,7 +143,7 @@ test("artifact reuse paths preserve catalog provenance instead of current planne assert.match(artifactPublish, /envReuseRecipe: servicePlan\.envReuseRecipe \?\? null/u); assert.match(artifactPublish, /componentInputHash: serviceResultValue\(env, service\.serviceId, "COMPONENT_INPUT_HASH"\) \?\? \(reusedServiceImage \? null : service\.componentInputHash \?\? null\)/u); - const gitopsRender = await readFile("scripts/g14-gitops-render.mjs", "utf8"); + const gitopsRender = await readFile("scripts/gitops-render.mjs", "utf8"); assert.match(gitopsRender, /const componentInputHash = envReuse \? \(planned\.componentInputHash \|\| service\.componentInputHash \|\| ""\) : \(service\.componentInputHash \|\| ""\);/u); assert.doesNotMatch(gitopsRender, /"component-input-hash": planned\.componentInputHash \|\| service\.componentInputHash \|\| ""/u); }); @@ -178,7 +178,7 @@ test("v02 planner rejects missing service declarations instead of falling back t await git(repo, ["commit", "-m", "remove v02 declarations"]); await assert.rejects( - () => createG14CiPlan({ + () => createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -200,7 +200,7 @@ test("v02 planner rejects missing boot script declarations instead of guessing p await git(repo, ["commit", "-m", "remove v02 cloud-api boot script"]); await assert.rejects( - () => createG14CiPlan({ + () => createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -218,7 +218,7 @@ test("v02 planner keeps hwpod-spec changes out of runtime service env reuse", as await git(repo, ["add", ".hwlab/hwpod-spec.yaml"]); await git(repo, ["commit", "-m", "change preinstalled hwpod spec"]); - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -240,7 +240,7 @@ test("v02 planner treats AgentRun runtime prompt changes as cloud-api inputs", a await git(repo, ["add", "."]); await git(repo, ["commit", "-m", "seed agentrun prompt"]); const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); - const initialPlan = await createG14CiPlan({ + const initialPlan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD", @@ -261,7 +261,7 @@ test("v02 planner treats AgentRun runtime prompt changes as cloud-api inputs", a await git(repo, ["add", "internal/agent/prompts/hwlab-v02-runtime.md"]); await git(repo, ["commit", "-m", "change agentrun prompt"]); - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -295,7 +295,7 @@ test("planner scopes cloud-web runtime changes to cloud-web", async () => { test("v02 planner treats shared trace renderer changes as cloud-web code rollout inputs", async () => { const repo = await createFixtureRepo(); const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); - const initialPlan = await createG14CiPlan({ + const initialPlan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD", @@ -317,7 +317,7 @@ test("v02 planner treats shared trace renderer changes as cloud-web code rollout await git(repo, ["add", "tools/src/hwlab-cli/trace-renderer.ts"]); await git(repo, ["commit", "-m", "change shared trace renderer"]); - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -399,14 +399,14 @@ test("planner treats dependency changes as runtime image inputs", async () => { }); test("global change classifier distinguishes gitops-only and test-only", () => { - assert.equal(classifyGlobalChange(["deploy/gitops/g14/runtime-dev/workloads.yaml"]).gitopsOnly, true); + assert.equal(classifyGlobalChange(["deploy/gitops/node/runtime-dev/workloads.yaml"]).gitopsOnly, true); assert.equal(classifyGlobalChange(["cmd/hwlab-cloud-api/runtime-options.test.ts"]).testOnly, true); }); test("v02 planner marks cloud-web code-only change as env reuse rollout", async () => { const repo = await createFixtureRepo(); const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); - const initialPlan = await createG14CiPlan({ + const initialPlan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD", @@ -427,7 +427,7 @@ test("v02 planner marks cloud-web code-only change as env reuse rollout", async await git(repo, ["add", "."]); await git(repo, ["commit", "-m", "change cloud web code"]); - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -451,7 +451,7 @@ test("v02 planner marks cloud-web code-only change as env reuse rollout", async test("v02 planner enables env reuse for all retained runtime services", async () => { const repo = await createFixtureRepo({ serviceIds: V02_RUNTIME_SERVICE_IDS }); const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); - const initialPlan = await createG14CiPlan({ + const initialPlan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD", @@ -480,7 +480,7 @@ test("v02 planner enables env reuse for all retained runtime services", async () await git(repo, ["add", "."]); await git(repo, ["commit", "-m", "change all v02 service code"]); - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -506,7 +506,7 @@ test("v02 planner enables env reuse for all retained runtime services", async () test("v02 planner rejects removed HWLAB-owned agent worker service selections", async () => { const repo = await createFixtureRepo(); await assert.rejects( - () => createG14CiPlan({ + () => createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD", @@ -514,14 +514,14 @@ test("v02 planner rejects removed HWLAB-owned agent worker service selections", artifactCatalogPath: "deploy/artifact-catalog.v02.json", services: ["hwlab-agent-worker"] }), - /unknown service IDs for G14 CI plan: hwlab-agent-worker/u + /unknown service IDs for node CI plan: hwlab-agent-worker/u ); }); test("v02 planner keeps hwlab-cli-only changes out of runtime service builds", async () => { const repo = await createFixtureRepo(); const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); - const initialPlan = await createG14CiPlan({ + const initialPlan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD", @@ -544,7 +544,7 @@ test("v02 planner keeps hwlab-cli-only changes out of runtime service builds", a await git(repo, ["add", "."]); await git(repo, ["commit", "-m", "change cli and host asset"]); - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -561,7 +561,7 @@ test("v02 planner keeps hwlab-cli-only changes out of runtime service builds", a test("v02 planner scopes hwpod skill bundle changes to the skills service", async () => { const repo = await createFixtureRepo(); const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); - const initialPlan = await createG14CiPlan({ + const initialPlan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD", @@ -578,7 +578,7 @@ test("v02 planner scopes hwpod skill bundle changes to the skills service", asyn await git(repo, ["add", "."]); await git(repo, ["commit", "-m", "change hwpod runtime cli"]); - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD~1", @@ -619,7 +619,7 @@ test("v02 planner reads env reuse declarations and recipe from deploy config", a test("v03 planner reads env reuse declarations and catalog from v03 lane config", async () => { const repo = await createFixtureRepo(); - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v03", baseRef: "HEAD", @@ -645,7 +645,7 @@ test("v03 planner reads env reuse declarations and catalog from v03 lane config" async function createFixtureRepo(options = {}) { const serviceIds = options.serviceIds ?? ["hwlab-cloud-api", "hwlab-cloud-web"]; const laneServiceIds = options.laneServiceIds ?? V02_RUNTIME_SERVICE_IDS; - const repo = await mkdtemp(path.join(os.tmpdir(), "hwlab-g14-ci-plan-")); + const repo = await mkdtemp(path.join(os.tmpdir(), "hwlab-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 }); @@ -705,7 +705,7 @@ async function createFixtureRepo(options = {}) { await git(repo, ["commit", "-m", "initial"]); if (catalogMode !== false) { const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); - const initialPlan = await createG14CiPlan({ + const initialPlan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD", @@ -726,7 +726,7 @@ async function createFixtureRepo(options = {}) { async function refreshFixtureCatalogForHead(repo) { const sourceCommitId = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim(); const services = ["hwlab-cloud-api", "hwlab-cloud-web"]; - const plan = await createG14CiPlan({ + const plan = await createCiPlan({ repoRoot: repo, lane: "v02", baseRef: "HEAD", @@ -743,7 +743,7 @@ async function refreshFixtureCatalogForHead(repo) { } function planV02CoreServices(repo, options) { - return createG14CiPlan({ + return createCiPlan({ repoRoot: repo, lane: "v02", artifactCatalogPath: "deploy/artifact-catalog.v02.json", @@ -853,7 +853,6 @@ function createDeployFixture({ serviceIds = V02_RUNTIME_SERVICE_IDS } = {}) { additionalEnvPaths: [ "internal/dev-entrypoint/artifact-runtime.mjs", "scripts/artifact-publish.mjs", - "scripts/g14-artifact-publish.mjs", "scripts/src/dev-artifact-services.mjs" ], hwpodAliases: [ diff --git a/scripts/g14-artifact-publish.mjs b/scripts/g14-artifact-publish.mjs deleted file mode 100644 index b52b3564..00000000 --- a/scripts/g14-artifact-publish.mjs +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env node -process.env.HWLAB_ARTIFACT_PUBLISH_ENTRYPOINT ||= "scripts/g14-artifact-publish.mjs"; -process.env.HWLAB_ARTIFACT_PUBLISH_PROFILE ||= "g14"; -await import("./artifact-publish.mjs"); diff --git a/scripts/g14-gitops-render.mjs b/scripts/gitops-render.mjs similarity index 92% rename from scripts/g14-gitops-render.mjs rename to scripts/gitops-render.mjs index 1061a6da..bc2cb958 100644 --- a/scripts/g14-gitops-render.mjs +++ b/scripts/gitops-render.mjs @@ -20,29 +20,29 @@ import { readStructuredFile } from "./src/structured-config.mjs"; const execFileAsync = promisify(execFile); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const defaultOutDir = "deploy/gitops/g14"; -const defaultRegistryPrefix = process.env.HWLAB_G14_REGISTRY_PREFIX || "127.0.0.1:5000/hwlab"; -const defaultSourceRepo = process.env.HWLAB_G14_GIT_URL || "git@github.com:pikasTech/HWLAB.git"; -const defaultGitReadUrl = process.env.HWLAB_G14_GIT_READ_URL || null; +const defaultOutDir = "deploy/gitops/node"; +const defaultRegistryPrefix = process.env.HWLAB_NODE_REGISTRY_PREFIX || "127.0.0.1:5000/hwlab"; +const defaultSourceRepo = process.env.HWLAB_NODE_GIT_URL || "git@github.com:pikasTech/HWLAB.git"; +const defaultGitReadUrl = process.env.HWLAB_NODE_GIT_READ_URL || null; const defaultV02GitReadUrl = process.env.HWLAB_V02_GIT_READ_URL || "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"; const defaultV02GitWriteUrl = process.env.HWLAB_V02_GIT_WRITE_URL || "http://git-mirror-write.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"; const defaultRuntimeLaneGitReadUrl = process.env.HWLAB_RUNTIME_LANE_GIT_READ_URL || defaultV02GitReadUrl; const defaultRuntimeLaneGitWriteUrl = process.env.HWLAB_RUNTIME_LANE_GIT_WRITE_URL || defaultV02GitWriteUrl; -const defaultBranch = process.env.HWLAB_G14_BRANCH || "G14"; -const defaultGitopsBranch = process.env.HWLAB_G14_GITOPS_BRANCH || "G14-gitops"; +const defaultBranch = process.env.HWLAB_NODE_BRANCH || "node"; +const defaultGitopsBranch = process.env.HWLAB_NODE_GITOPS_BRANCH || "node-gitops"; const defaultCatalogPath = process.env.HWLAB_ARTIFACT_CATALOG_PATH || "deploy/artifact-catalog.dev.json"; -const defaultRuntimeEndpoint = process.env.HWLAB_G14_RUNTIME_ENDPOINT || "http://74.48.78.17:17667"; -const defaultWebEndpoint = process.env.HWLAB_G14_WEB_ENDPOINT || "http://74.48.78.17:17666"; -const defaultProdRuntimeEndpoint = process.env.HWLAB_G14_PROD_RUNTIME_ENDPOINT || "http://74.48.78.17:18667"; -const defaultProdWebEndpoint = process.env.HWLAB_G14_PROD_WEB_ENDPOINT || "http://74.48.78.17:18666"; +const defaultRuntimeEndpoint = process.env.HWLAB_NODE_RUNTIME_ENDPOINT || "http://74.48.78.17:17667"; +const defaultWebEndpoint = process.env.HWLAB_NODE_WEB_ENDPOINT || "http://74.48.78.17:17666"; +const defaultProdRuntimeEndpoint = process.env.HWLAB_NODE_PROD_RUNTIME_ENDPOINT || "http://74.48.78.17:18667"; +const defaultProdWebEndpoint = process.env.HWLAB_NODE_PROD_WEB_ENDPOINT || "http://74.48.78.17:18666"; const defaultV02RuntimeEndpoint = process.env.HWLAB_V02_RUNTIME_ENDPOINT || "https://hwlab.74-48-78-17.nip.io"; const defaultV02WebEndpoint = process.env.HWLAB_V02_WEB_ENDPOINT || "https://hwlab.74-48-78-17.nip.io"; -const defaultProxyUrl = process.env.HWLAB_G14_PROXY_URL || "http://127.0.0.1:10808"; -const defaultAllProxyUrl = process.env.HWLAB_G14_ALL_PROXY_URL || "socks5h://127.0.0.1:10808"; -const defaultNoProxy = process.env.HWLAB_G14_NO_PROXY || "hyueapi.com,.hyueapi.com,127.0.0.1,localhost,::1,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,192.168.0.0/16,.svc,.cluster.local,kubernetes.default.svc,registry.hwlab-ci.svc,hwlab-registry.hwlab-ci.svc,git-mirror-http,git-mirror-http.devops-infra,git-mirror-http.devops-infra.svc,git-mirror-http.devops-infra.svc.cluster.local,git-mirror-write,git-mirror-write.devops-infra,git-mirror-write.devops-infra.svc,git-mirror-write.devops-infra.svc.cluster.local"; -const ciToolsRunnerImage = process.env.HWLAB_G14_CI_TOOLS_IMAGE || "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1"; -const buildkitRunnerImage = process.env.HWLAB_G14_BUILDKIT_IMAGE || "moby/buildkit:rootless"; -const defaultDevBaseImage = process.env.HWLAB_G14_DEV_BASE_IMAGE || "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim"; +const defaultProxyUrl = process.env.HWLAB_NODE_PROXY_URL || "http://127.0.0.1:10808"; +const defaultAllProxyUrl = process.env.HWLAB_NODE_ALL_PROXY_URL || "socks5h://127.0.0.1:10808"; +const defaultNoProxy = process.env.HWLAB_NODE_NO_PROXY || "hyueapi.com,.hyueapi.com,127.0.0.1,localhost,::1,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,192.168.0.0/16,.svc,.cluster.local,kubernetes.default.svc,registry.hwlab-ci.svc,hwlab-registry.hwlab-ci.svc,git-mirror-http,git-mirror-http.devops-infra,git-mirror-http.devops-infra.svc,git-mirror-http.devops-infra.svc.cluster.local,git-mirror-write,git-mirror-write.devops-infra,git-mirror-write.devops-infra.svc,git-mirror-write.devops-infra.svc.cluster.local"; +const ciToolsRunnerImage = process.env.HWLAB_NODE_CI_TOOLS_IMAGE || "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1"; +const buildkitRunnerImage = process.env.HWLAB_NODE_BUILDKIT_IMAGE || "moby/buildkit:rootless"; +const defaultDevBaseImage = process.env.HWLAB_NODE_DEV_BASE_IMAGE || "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim"; const defaultServiceIds = Object.freeze([ "hwlab-cloud-api", "hwlab-cloud-web", @@ -86,13 +86,13 @@ const v02RemovedServiceEnvNames = new Set([ "HWLAB_ROUTER_URL", "HWLAB_TUNNEL_CLIENT_URL" ]); -const moonBridgeSourceRef = process.env.HWLAB_G14_MOONBRIDGE_REF || "1b99888d3dae889b79ee602cb875c7907f7e76f2"; -const moonBridgeImage = process.env.HWLAB_G14_MOONBRIDGE_IMAGE || `${defaultRegistryPrefix}/moonbridge:${moonBridgeSourceRef.slice(0, 12)}`; -const deepSeekProfileModel = process.env.HWLAB_G14_DEEPSEEK_PROFILE_MODEL || "deepseek-chat"; -const codexApiProfileModel = process.env.HWLAB_G14_CODEX_API_PROFILE_MODEL || "gpt-5.5"; -const codexApiProfileBaseUrl = process.env.HWLAB_G14_CODEX_API_PROFILE_BASE_URL || "http://127.0.0.1:49280/responses"; -const codexApiForwarderPort = process.env.HWLAB_G14_CODEX_API_FORWARDER_PORT || "49280"; -const codexApiForwarderUpstreamBaseUrl = process.env.HWLAB_G14_CODEX_API_UPSTREAM_BASE_URL || "https://hyueapi.com"; +const moonBridgeSourceRef = process.env.HWLAB_NODE_MOONBRIDGE_REF || "1b99888d3dae889b79ee602cb875c7907f7e76f2"; +const moonBridgeImage = process.env.HWLAB_NODE_MOONBRIDGE_IMAGE || `${defaultRegistryPrefix}/moonbridge:${moonBridgeSourceRef.slice(0, 12)}`; +const deepSeekProfileModel = process.env.HWLAB_NODE_DEEPSEEK_PROFILE_MODEL || "deepseek-chat"; +const codexApiProfileModel = process.env.HWLAB_NODE_CODEX_API_PROFILE_MODEL || "gpt-5.5"; +const codexApiProfileBaseUrl = process.env.HWLAB_NODE_CODEX_API_PROFILE_BASE_URL || "http://127.0.0.1:49280/responses"; +const codexApiForwarderPort = process.env.HWLAB_NODE_CODEX_API_FORWARDER_PORT || "49280"; +const codexApiForwarderUpstreamBaseUrl = process.env.HWLAB_NODE_CODEX_API_UPSTREAM_BASE_URL || "https://hyueapi.com"; const sourceCommitPattern = /^[a-f0-9]{7,40}$/u; const v02EnvReuseRuntimeMode = "env-reuse-git-mirror-checkout"; @@ -106,14 +106,14 @@ const primitiveValidationTasks = Object.freeze([ commands: ["node scripts/repo-reports-guard.mjs"] }, { - name: "g14-contract-check", + name: "node-contract-check", commands: [ "npm run gitops:ts:check", - "node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --help >/dev/null", + "node scripts/run-bun.mjs scripts/gitops-render.mjs --help >/dev/null", "render_check_revision=$(git rev-parse HEAD)", "render_check_dir=$(mktemp -d)", - "node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --source-revision \"$render_check_revision\"", - "node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --check --source-revision \"$render_check_revision\"", + "node scripts/run-bun.mjs scripts/gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --source-revision \"$render_check_revision\"", + "node scripts/run-bun.mjs scripts/gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --check --source-revision \"$render_check_revision\"", `node --input-type=module <<'NODE' import { readdirSync, readFileSync, statSync } from "node:fs"; import path from "node:path"; @@ -122,7 +122,7 @@ const root = process.cwd(); const ciFileName = "CI" + ".json"; const forbiddenCiFragments = [ "ci" + "-json", - "HWLAB_G14_TEKTON" + "_SINGLE_IMAGE_PUBLISH", + "HWLAB_NODE_TEKTON" + "_SINGLE_IMAGE_PUBLISH", "single" + "-dind", "docker" + ":29", "DOCKER" + "_HOST", @@ -132,15 +132,14 @@ const forbiddenCiFragments = [ "--build" + "-backend", "HWLAB_ARTIFACT" + "_BUILD_BACKEND", "--legacy" + "-source-images", - "HWLAB_G14_USE_DEPLOY_IMAGES" + "=0" + "HWLAB_NODE_USE_DEPLOY_IMAGES" + "=0" ]; const scanTargets = [ - "scripts/g14-gitops-render.mjs", + "scripts/gitops-render.mjs", "scripts/src/runtime-lane.ts", "scripts/artifact-publish.mjs", - "scripts/g14-artifact-publish.mjs", - "scripts/src/g14-ci-plan-lib.mjs", - "deploy/gitops/g14/tekton" + "scripts/src/ci-plan-lib.mjs", + "deploy/gitops/node/tekton" ]; const skipDirs = new Set([".git", "node_modules", ".worktree"]); @@ -175,8 +174,7 @@ if (ciFiles.length || violations.length) { process.exit(1); } NODE`, - "node --check scripts/artifact-publish.mjs", - "node --check scripts/g14-artifact-publish.mjs" + "node --check scripts/artifact-publish.mjs" ] }, { @@ -202,8 +200,8 @@ function proxyEnv() { function parseArgs(argv) { const args = { - lane: process.env.HWLAB_GITOPS_LANE || "g14", - nodeId: process.env.HWLAB_NODE_ID || "G14", + lane: process.env.HWLAB_GITOPS_LANE || "node", + nodeId: process.env.HWLAB_NODE_ID || "node", outDir: defaultOutDir, gitopsRoot: defaultOutDir, catalogPath: defaultCatalogPath, @@ -265,7 +263,7 @@ function parseArgs(argv) { } if (!args.gitReadUrl) args.gitReadUrl = args.sourceRepo; if (!args.gitWriteUrl) args.gitWriteUrl = args.sourceRepo; - assert.ok(args.lane === "g14" || isRuntimeLane(args.lane), `unknown lane ${args.lane}`); + assert.ok(args.lane === "node" || isRuntimeLane(args.lane), `unknown lane ${args.lane}`); assert.ok(["short", "full"].includes(args.imageTagMode), `unknown image tag mode ${args.imageTagMode}`); if (isRuntimeLane(args.lane)) { const versionName = versionNameForRuntimeLane(args.lane); @@ -283,13 +281,13 @@ function readOption(argv, index, name) { function usage() { return [ - "usage: node scripts/run-bun.mjs scripts/g14-gitops-render.mjs [options]", + "usage: node scripts/run-bun.mjs scripts/gitops-render.mjs [options]", "", - "Render G14-only Kubernetes-native CI/CD manifests from built-in Tekton CI primitives, deploy/deploy.yaml, and deploy/k8s/*.", + "Render node-scoped Kubernetes-native CI/CD manifests from built-in Tekton CI primitives, deploy/deploy.yaml, and deploy/k8s/*.", "", "options:", - " --lane LANE g14 or runtime lane such as v02/v03/v04; default: g14", - ` --node NODE deployment node id from deploy.nodes; default: ${process.env.HWLAB_NODE_ID || "G14"}`, + " --lane LANE node or runtime lane such as v02/v03/v04; default: node", + ` --node NODE deployment node id from deploy.nodes; runtime lanes default from deploy.lanes..node`, ` --out DIR default: ${defaultOutDir}`, ` --gitops-root DIR path inside GitOps repo; default comes from deploy.nodes..gitopsRoot`, ` --catalog-path PATH default: ${defaultCatalogPath}`, @@ -453,7 +451,7 @@ function v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds = null) { } function bootShForService(deployService, serviceId) { - assert.ok(deployService?.bootSh, `deploy.lanes.v02.bootScripts.${serviceId} is required`); + assert.ok(deployService?.bootSh, `deploy lane bootScripts.${serviceId} is required`); const value = deployService.bootSh; const text = String(value).replaceAll("\\", "/").replace(/^\.\//u, ""); assert.ok(text && !text.startsWith("/") && !text.split("/").includes(".."), `${serviceId} boot script must be repo-relative`); @@ -461,16 +459,16 @@ function bootShForService(deployService, serviceId) { } function envReuseServiceIdsForLane(deploy, lane) { - if (lane !== "v02") return new Set(); - const configured = deploy?.lanes?.v02?.envReuseServices; + if (!isRuntimeLane(lane)) return new Set(); + const configured = deploy?.lanes?.[lane]?.envReuseServices; const allowed = new Set(serviceIdsForLane(lane, deploy)); return new Set((Array.isArray(configured) ? configured : []).filter((serviceId) => allowed.has(serviceId))); } -function deployServiceForBoot(deploy, serviceId) { +function deployServiceForBoot(deploy, serviceId, lane = "v02") { return { serviceId, - bootSh: deploy?.lanes?.v02?.bootScripts?.[serviceId] + bootSh: deploy?.lanes?.[lane]?.bootScripts?.[serviceId] }; } @@ -697,7 +695,7 @@ function artifactCatalogSkeletonService({ args, deploy, source, serviceId, envir }; const envReuseServiceIds = envReuseServiceIdsForLane(deploy, args.lane); if (!envReuseServiceIds.has(serviceId)) return base; - const bootSh = bootShForService(deployServiceForBoot(deploy, serviceId), serviceId); + const bootSh = bootShForService(deployServiceForBoot(deploy, serviceId, args.lane), serviceId); const environmentImage = imageFor(args.registryPrefix, `${serviceId}-env`, `env-${String(source.full).slice(0, 12)}`); return { ...base, @@ -738,7 +736,7 @@ function runtimeLabelForProfile(profile) { } function gitopsTargetForProfile(profile) { - return isRuntimeLane(profile) ? profile : "g14"; + return isRuntimeLane(profile) ? profile : "node"; } function gitopsRootForArgs(args) { @@ -776,24 +774,24 @@ function laneNames(args) { pipelineRunPrefix: `${namespace}-ci-poll-`, runtimeNamespace: namespace, runtimePath: gitopsPathForProfile(args, args.lane), - argoApplications: [`argocd/hwlab-g14-${args.lane}`] + argoApplications: [`argocd/hwlab-node-${args.lane}`] }; } return { - gitopsTarget: "g14", - pipeline: "hwlab-g14-ci-image-publish", - poller: "hwlab-g14-branch-poller", - reconciler: "hwlab-g14-control-plane-reconciler", + gitopsTarget: "node", + pipeline: "hwlab-node-ci-image-publish", + poller: "hwlab-node-branch-poller", + reconciler: "hwlab-node-control-plane-reconciler", serviceAccount: "hwlab-tekton-runner", - pipelineRunPrefix: "hwlab-g14-ci-poll-", + pipelineRunPrefix: "hwlab-node-ci-poll-", runtimeNamespace: "hwlab-dev", runtimePath: gitopsPathForProfile(args, "dev"), - argoApplications: ["argocd/hwlab-g14-dev", "argocd/hwlab-g14-prod"] + argoApplications: ["argocd/hwlab-node-dev", "argocd/hwlab-node-prod"] }; } function argoApplicationName(profile) { - return `hwlab-g14-${profile}`; + return `hwlab-node-${profile}`; } function imageTagForSource(args, source) { @@ -830,16 +828,16 @@ function namespaceScopedHost(value, namespace) { } function secretNameForProfile(secretName, profile) { - if (profile !== "v02") return secretName; - if (secretName === "hwlab-cloud-api-dev-db") return "hwlab-cloud-api-v02-db"; - if (secretName === "hwlab-code-agent-provider") return "hwlab-v02-code-agent-provider"; - if (secretName === "hwlab-code-agent-codex-auth") return "hwlab-v02-code-agent-codex-auth"; - if (secretName === "hwlab-bootstrap-admin") return "hwlab-v02-bootstrap-admin"; + if (!isRuntimeLane(profile)) return secretName; + if (secretName === "hwlab-cloud-api-dev-db") return `hwlab-cloud-api-${profile}-db`; + if (secretName === "hwlab-code-agent-provider") return `hwlab-${profile}-code-agent-provider`; + if (secretName === "hwlab-code-agent-codex-auth") return `hwlab-${profile}-code-agent-codex-auth`; + if (secretName === "hwlab-bootstrap-admin") return `hwlab-${profile}-bootstrap-admin`; return secretName; } function rewritePodSecretRefs(podSpec, profile) { - if (!podSpec || profile !== "v02") return; + if (!podSpec || !isRuntimeLane(profile)) return; for (const volume of podSpec.volumes ?? []) { if (volume?.secret?.secretName) volume.secret.secretName = secretNameForProfile(volume.secret.secretName, profile); } @@ -897,8 +895,8 @@ function deployServicesForProfile(deploy, profile) { const services = new Map((deploy.services ?? []) .filter((service) => allowed.has(service.serviceId)) .map((service) => [service.serviceId, cloneJson(service)])); - const laneServices = profile === "v02" && Array.isArray(deploy.lanes?.v02?.services) - ? deploy.lanes.v02.services + const laneServices = isRuntimeLane(profile) && Array.isArray(deploy.lanes?.[profile]?.services) + ? deploy.lanes[profile].services : []; for (const override of laneServices) { const existing = services.get(override.serviceId) ?? { serviceId: override.serviceId }; @@ -911,6 +909,12 @@ function deployServicesForProfile(deploy, profile) { return services; } +function runtimeNodeIdForProfile(deploy, profile, fallback = "node") { + if (!isRuntimeLane(profile)) return fallback; + const nodeId = deploy?.lanes?.[profile]?.node; + return typeof nodeId === "string" && nodeId.length > 0 ? nodeId : fallback; +} + function v02MetricsLabels(serviceId) { return { "hwlab.pikastech.local/monitoring": "enabled", @@ -978,25 +982,26 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = const namespace = namespaceNameForProfile(profile); const profileLabel = runtimeLabelForProfile(profile); const gitopsTarget = gitopsTargetForProfile(profile); - const digestPin = profile === "v02"; + const runtimeLane = isRuntimeLane(profile); + const digestPin = runtimeLane; const envReuseServiceIds = envReuseServiceIdsForLane(deploy, profile); - const v02ObservableServiceIds = profile === "v02" ? v02ObservableServiceIdsForDeploy(deploy) : new Set(); + const runtimeObservableServiceIds = runtimeLane ? runtimeLaneObservableServiceIdsForDeploy(deploy, profile) : new Set(); const stableRuntimeLabels = { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/environment": profileLabel, "hwlab.pikastech.local/profile": profileLabel }; - result.items = asItems(result, "workloads").filter((item) => !(profile === "v02" && isV02RemovedServiceId(serviceIdForWorkload(item, null)))); + result.items = asItems(result, "workloads").filter((item) => !(runtimeLane && isV02RemovedServiceId(serviceIdForWorkload(item, null)))); for (const item of asItems(result, "workloads")) { item.metadata.namespace = namespace; label(item.metadata, { ...stableRuntimeLabels, "hwlab.pikastech.local/gitops-target": gitopsTarget, "hwlab.pikastech.local/source-commit": source.full, - "unidesk.ai/g14-staging": profile === "dev" ? "true" : "false" + "unidesk.ai/node-staging": profile === "dev" ? "true" : "false" }); annotate(item.metadata, { - "hwlab.pikastech.local/gitops-note": profile === "v02" ? "HWLAB v0.2 GitOps target." : profile === "prod" ? "G14 PROD GitOps target." : "G14 DEV GitOps target.", + "hwlab.pikastech.local/gitops-note": profile === "v02" ? "HWLAB v0.2 GitOps target." : profile === "prod" ? "node PROD GitOps target." : "node DEV GitOps target.", "hwlab.pikastech.local/source-commit": source.full }); if (item.kind === "Job") { @@ -1013,7 +1018,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = if (!podTemplate?.spec?.containers) continue; const templateServiceId = serviceIdForWorkload(item, podTemplate.spec.containers[0]); const templateArtifact = runtimeArtifactForService({ catalog, deploy, serviceId: templateServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); - const templateSourceCommit = profile === "v02" ? (templateArtifact.commit ?? source.full) : source.full; + const templateSourceCommit = runtimeLane ? (templateArtifact.commit ?? source.full) : source.full; const templateArtifactCommit = templateArtifact.commit; label(podTemplate.metadata ??= {}, { ...stableRuntimeLabels, @@ -1021,7 +1026,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = "hwlab.pikastech.local/source-commit": templateSourceCommit, "hwlab.pikastech.local/artifact-source-commit": templateArtifactCommit }); - if (profile === "v02" && v02ObservableServiceIds.has(templateServiceId)) { + if (runtimeLane && runtimeObservableServiceIds.has(templateServiceId)) { label(item.metadata, v02MetricsLabels(templateServiceId)); label(podTemplate.metadata, v02MetricsLabels(templateServiceId)); annotate(podTemplate.metadata, v02MetricsSidecarAnnotations(metricsSidecarSha256)); @@ -1036,7 +1041,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = annotate(podTemplate.metadata, { "hwlab.pikastech.local/source-commit": templateSourceCommit, "hwlab.pikastech.local/artifact-source-commit": templateArtifactCommit, - "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" + "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" }); for (const container of podTemplate.spec.containers) { if (container.name === "hwlab-metrics") continue; @@ -1044,8 +1049,8 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = const deployService = deployServices.get(serviceId); if (!deployService) continue; 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 envReuse = runtimeLane && v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds); + const bootMetadata = envReuse ? bootMetadataForService({ args: { sourceRepo: deploy?.lanes?.[profile]?.sourceRepo || defaultSourceRepo, registryPrefix }, catalog, deployService, serviceId, source }) : null; const image = artifact.image; const runtimeCommit = artifact.commit; const runtimeImageTag = artifact.imageTag; @@ -1056,7 +1061,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = if (Object.hasOwn(entry, "value")) entry.value = namespaceScopedHost(entry.value, namespace); } applyDeployEnv(container.env, deployService, namespace, profile); - if (profile === "v02") container.env = removeV02LegacySimulatorEnv(container.env); + if (runtimeLane) container.env = removeV02LegacySimulatorEnv(container.env); upsertEnv(container.env, "HWLAB_ENVIRONMENT", profileLabel); upsertEnv(container.env, "HWLAB_COMMIT_ID", runtimeCommit); upsertEnv(container.env, "HWLAB_IMAGE", image); @@ -1066,8 +1071,8 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = } upsertEnv(container.env, "HWLAB_GITOPS_TARGET", gitopsTarget); upsertEnv(container.env, "HWLAB_GITOPS_PROFILE", profileLabel); - upsertEnv(container.env, "HWLAB_GITOPS_SOURCE_COMMIT", profile === "v02" ? runtimeCommit : source.full); - if (profile === "v02" && serviceId === "hwlab-cloud-api") { + upsertEnv(container.env, "HWLAB_GITOPS_SOURCE_COMMIT", runtimeLane ? runtimeCommit : source.full); + if (runtimeLane && serviceId === "hwlab-cloud-api") { upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT", runtimeCommit); } if (bootMetadata) { @@ -1080,32 +1085,17 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = if (serviceId === "hwlab-cloud-api" || serviceId === "hwlab-edge-proxy") { upsertEnv(container.env, "HWLAB_PUBLIC_ENDPOINT", runtimeEndpoint); } - if (profile === "v02" && serviceId === "hwlab-edge-proxy") { + if (runtimeLane && serviceId === "hwlab-edge-proxy") { useLocalHealthProbe(container, "/health"); } if (serviceId === "hwlab-cloud-api") { - if (profile === "v02") { + if (runtimeLane) { removeV02RepoOwnedCodeAgentPodConfig(podTemplate.spec); upsertEnv(container.env, "HWLAB_M3_IO_CONTROL_ENABLED", "false"); - upsertEnv(container.env, "HWLAB_ACCESS_CONTROL_REQUIRED", "1"); - upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_ID", "usr_v02_admin"); - upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_USERNAME", "admin"); - upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_DISPLAY_NAME", "HWLAB v0.2 Admin"); - upsertEnvEntry(container.env, deployEnvEntry("HWLAB_BOOTSTRAP_ADMIN_PASSWORD_HASH", "secretRef:hwlab-v02-bootstrap-admin/password-hash", namespace, profile)); - upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_API_KEY_ID", "key_master_server_admin"); - upsertEnvEntry(container.env, deployEnvEntry("HWLAB_BOOTSTRAP_ADMIN_API_KEY", "secretRef:hwlab-v02-master-server-admin-api-key/api-key", namespace, profile)); + upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID", runtimeNodeIdForProfile(deploy, profile)); upsertEnv(container.env, "HWLAB_OPENFGA_MODE", "enforce"); - upsertEnv(container.env, "HWLAB_OPENFGA_API_URL", "http://hwlab-openfga.hwlab-v02.svc.cluster.local:8080"); - upsertEnvEntry(container.env, deployEnvEntry("HWLAB_OPENFGA_AUTHN_TOKEN", "secretRef:hwlab-v02-openfga/authn-preshared-key", namespace, profile)); - upsertEnv(container.env, "HWLAB_KEYCLOAK_ISSUER", "https://auth.74-48-78-17.nip.io/realms/hwlab"); - upsertEnv(container.env, "HWLAB_KEYCLOAK_CLIENT_ID", "hwlab-cloud-web"); - upsertEnvEntry(container.env, deployEnvEntry("HWLAB_KEYCLOAK_CLIENT_SECRET", "secretRef:hwlab-cloud-web-client/client-secret", namespace, profile)); - upsertEnv(container.env, "HWLAB_CODE_AGENT_ADAPTER", "agentrun-v01"); - upsertEnv(container.env, "AGENTRUN_MGR_URL", "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080"); - upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID", "G14"); - upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE", "agentrun-v01"); - upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE", "agentrun-v01"); - upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_REPO_URL", "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"); + upsertEnv(container.env, "HWLAB_OPENFGA_API_URL", `http://hwlab-openfga.${namespace}.svc.cluster.local:8080`); + upsertEnvEntry(container.env, deployEnvEntry("HWLAB_OPENFGA_AUTHN_TOKEN", `secretRef:hwlab-${profile}-openfga/authn-preshared-key`, namespace, profile)); upsertEnv(container.env, "UNIDESK_MAIN_SERVER_IP", "http://74.48.78.17:18081"); } else { syncCodeAgentWorkspaceInitContainer(podTemplate.spec.initContainers, { image, runtimeCommit, runtimeImageTag }); @@ -1150,7 +1140,7 @@ function transformHealthContract(value, namespace, labels, annotations, runtimeE result.data["cloud-api"] = `GET /health/live through ${runtimeEndpoint}`; } if (Object.hasOwn(result.data, "tunnel-client")) { - result.data["tunnel-client"] = "disabled for G14 GitOps; no FRP or production traffic tunnel is required"; + result.data["tunnel-client"] = "disabled for node GitOps; no FRP or production traffic tunnel is required"; } } return result; @@ -1278,7 +1268,7 @@ ci_timing_emit() { finished_ms="$(ci_now_ms)" duration_ms="$((finished_ms - started_ms))" service_id="\${HWLAB_TIMING_SERVICE_ID:-}" - node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:"g14-cicd-timing",schemaVersion:"v1",stage,status,durationMs:Number(durationMs),pipelineRun:process.env.HWLAB_TEKTON_PIPELINERUN||null,taskRun:process.env.HWLAB_TEKTON_TASKRUN||null,task:process.env.HWLAB_TEKTON_TASK||null,revision:process.env.HWLAB_SOURCE_REVISION||null,serviceId:serviceId||null,source:"scripts/g14-gitops-render.mjs",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' "$stage" "$status" "$duration_ms" "$service_id" + node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:"node-cicd-timing",schemaVersion:"v1",stage,status,durationMs:Number(durationMs),pipelineRun:process.env.HWLAB_TEKTON_PIPELINERUN||null,taskRun:process.env.HWLAB_TEKTON_TASKRUN||null,task:process.env.HWLAB_TEKTON_TASK||null,revision:process.env.HWLAB_SOURCE_REVISION||null,serviceId:serviceId||null,source:"scripts/gitops-render.mjs",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' "$stage" "$status" "$duration_ms" "$service_id" } `; } @@ -1344,7 +1334,7 @@ const timeout = setTimeout(() => { }, 15000); socket.on("connect", () => { - socket.write("GET " + target.href + " HTTP/1.1\\r\\nHost: " + target.host + "\\r\\nUser-Agent: hwlab-g14-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n"); + socket.write("GET " + target.href + " HTTP/1.1\\r\\nHost: " + target.host + "\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n"); }); socket.on("data", (chunk) => { @@ -1601,7 +1591,7 @@ function prepareSourceScript() { " echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seeded-v02-skeleton\"}'", "fi", "ci_timing_emit catalog-fetch succeeded \"$catalog_fetch_started_ms\"", - `echo ${shellSingleQuote(`G14 prepare-source complete; validation task count=${primitiveValidationTasks.length}`)}` + `echo ${shellSingleQuote(`node prepare-source complete; validation task count=${primitiveValidationTasks.length}`)}` ]; return `${lines.join("\n")}\n`; } @@ -1656,7 +1646,7 @@ echo '{"event":"ci-base-image","phase":"plan-artifacts","image":"${ciToolsRunner for tool in node git; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"plan-artifacts","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done cd /workspace/source/repo test "$(git rev-parse HEAD)" = "$(params.revision)" -node scripts/g14-ci-plan.mjs --lane "$(params.lane)" --target-ref HEAD --deploy-config deploy/deploy.yaml --artifact-catalog "$(params.catalog-path)" --registry-prefix "$(params.registry-prefix)" --services "$(params.services)" > /workspace/source/ci-plan.json +node scripts/ci-plan.mjs --lane "$(params.lane)" --target-ref HEAD --deploy-config deploy/deploy.yaml --artifact-catalog "$(params.catalog-path)" --registry-prefix "$(params.registry-prefix)" --services "$(params.services)" > /workspace/source/ci-plan.json node - <<'NODE' const fs = require("node:fs"); const plan = JSON.parse(fs.readFileSync("/workspace/source/ci-plan.json", "utf8")); @@ -1698,7 +1688,7 @@ fs.writeFileSync("/workspace/source/affected-services.json", JSON.stringify({ services: plan.services || [], entries }, null, 2) + String.fromCharCode(10)); -console.log(JSON.stringify({ event: "g14-ci-plan", sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], reusedServices: plan.reusedServices || [], buildSkippedCount: plan.buildSkippedCount || 0 })); +console.log(JSON.stringify({ event: "ci-plan", sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], reusedServices: plan.reusedServices || [], buildSkippedCount: plan.buildSkippedCount || 0 })); NODE `; } @@ -1772,7 +1762,7 @@ export HWLAB_CI_ARTIFACT_RUN_ID="$(context.pipelineRun.name)-$(params.service-id export HWLAB_CI_ARTIFACT_RUN_OWNER="tekton" export HWLAB_DEV_REGISTRY_PREFIX="$(params.registry-prefix)" export HWLAB_DEV_REGISTRY_K8S_NATIVE=1 -export HWLAB_G14_CICD_TIMING=1 +export HWLAB_NODE_CICD_TIMING=1 export HWLAB_TEKTON_PIPELINERUN export HWLAB_TEKTON_TASKRUN export HWLAB_TEKTON_TASK @@ -1799,7 +1789,7 @@ if [ "$buildkit_ready" != "1" ]; then echo '{"event":"buildkit-sidecar-not-ready","serviceId":"'"$(params.service-id)"'","addr":"'"$HWLAB_BUILDKIT_ADDR"'"}' >&2 exit 1 fi -node scripts/g14-artifact-publish.mjs --publish --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --registry-prefix "$(params.registry-prefix)" --report "/workspace/source/service-results/$(params.service-id).json" --tekton-results-dir /tekton/results --quiet-build --concurrency 1 --services "$(params.service-id)" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr "$HWLAB_BUILDKIT_ADDR" +node scripts/artifact-publish.mjs --publish --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --registry-prefix "$(params.registry-prefix)" --report "/workspace/source/service-results/$(params.service-id).json" --tekton-results-dir /tekton/results --quiet-build --concurrency 1 --services "$(params.service-id)" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr "$HWLAB_BUILDKIT_ADDR" `; } @@ -1819,7 +1809,7 @@ export HWLAB_ARTIFACT_LANE="$(params.lane)" export HWLAB_ARTIFACT_CATALOG_PATH="$(params.catalog-path)" export HWLAB_DEPLOY_MANIFEST_PATH="deploy/deploy.yaml" export HWLAB_ARTIFACT_IMAGE_TAG_MODE="$(params.image-tag-mode)" -node scripts/g14-artifact-publish.mjs --publish --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --registry-prefix "$(params.registry-prefix)" --report /workspace/source/dev-artifacts.json --quiet-build --concurrency 1 --services "$(params.services)" --external-service-report-dir /workspace/source/service-results +node scripts/artifact-publish.mjs --publish --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --registry-prefix "$(params.registry-prefix)" --report /workspace/source/dev-artifacts.json --quiet-build --concurrency 1 --services "$(params.services)" --external-service-report-dir /workspace/source/service-results node scripts/refresh-artifact-catalog.mjs --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --target-ref HEAD --publish-report /workspace/source/dev-artifacts.json --no-write `; } @@ -1883,21 +1873,21 @@ check_source_head() { } check_source_head before-render -git config --global user.name "HWLAB G14 GitOps Bot" -git config --global user.email "hwlab-g14-gitops-bot@users.noreply.github.com" +git config --global user.name "HWLAB node GitOps Bot" +git config --global user.email "hwlab-node-gitops-bot@users.noreply.github.com" catalog_path="$(params.catalog-path)" runtime_path="$(params.runtime-path)" argo_hard_refresh_runtime_lane() { if [ "$runtime_lane" != "true" ]; then return 0; fi - ARGO_APPLICATION="hwlab-g14-$lane" ARGO_FIELD_MANAGER="hwlab-$lane-gitops-promote" node <<'NODE' + ARGO_APPLICATION="hwlab-node-$lane" ARGO_FIELD_MANAGER="hwlab-$lane-gitops-promote" node <<'NODE' const fs = require("node:fs"); const https = require("node:https"); const host = process.env.KUBERNETES_SERVICE_HOST; const port = process.env.KUBERNETES_SERVICE_PORT || "443"; const startedAt = Date.now(); -const application = process.env.ARGO_APPLICATION || "hwlab-g14-v02"; +const application = process.env.ARGO_APPLICATION || "hwlab-node-v02"; const fieldManager = process.env.ARGO_FIELD_MANAGER || "hwlab-v02-gitops-promote"; const pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null; const taskRun = process.env.HWLAB_TEKTON_TASKRUN || null; @@ -1906,7 +1896,7 @@ const revision = process.env.HWLAB_SOURCE_REVISION || null; function emit(payload) { console.log(JSON.stringify({ - event: "g14-cicd-timing", + event: "node-cicd-timing", schemaVersion: "v1", stage: "argo-hard-refresh", pipelineRun, @@ -1914,7 +1904,7 @@ function emit(payload) { task, revision, application, - source: "scripts/g14-gitops-render.mjs", + source: "scripts/gitops-render.mjs", at: new Date().toISOString(), ...payload })); @@ -1975,8 +1965,8 @@ if [ -s /workspace/source/dev-artifacts.json ]; then node scripts/refresh-artifact-catalog.mjs --lane "$lane" --catalog-path "$catalog_path" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --target-ref "$(params.revision)" --publish-report /workspace/source/dev-artifacts.json --write fi gitops_render_started_ms="$(ci_now_ms)" -node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images -node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images --check +node scripts/run-bun.mjs scripts/gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images +node scripts/run-bun.mjs scripts/gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images --check ci_timing_emit gitops-render succeeded "$gitops_render_started_ms" check_source_head before-push workdir="$(mktemp -d)" @@ -1997,7 +1987,7 @@ if git_timed gitops-branch-ls 45 git ls-remote --exit-code --heads origin "$(par mkdir -p "$old_runtime_snapshot" cp -a "$runtime_path"/. "$old_runtime_snapshot"/ fi - if [ "$runtime_lane" = "true" ]; then rm -rf "$runtime_path" "$catalog_path"; else rm -rf deploy/gitops/g14 "$catalog_path"; fi + if [ "$runtime_lane" = "true" ]; then rm -rf "$runtime_path" "$catalog_path"; else rm -rf deploy/gitops/node "$catalog_path"; fi else git checkout --orphan "$(params.gitops-branch)" git rm -rf . >/dev/null 2>&1 || true @@ -2010,9 +2000,9 @@ if [ "$runtime_lane" = "true" ]; then git add "$catalog_path" "$runtime_path" else mkdir -p deploy/gitops - cp -a /workspace/source/repo/deploy/gitops/g14 deploy/gitops/g14 + cp -a /workspace/source/repo/deploy/gitops/node deploy/gitops/node cp "/workspace/source/repo/$catalog_path" "$catalog_path" - git add "$catalog_path" deploy/gitops/g14 + git add "$catalog_path" deploy/gitops/node fi if [ "$runtime_lane" = "true" ] && [ -s /workspace/source/affected-services.json ]; then runtime_noop_decision="$(node - "$runtime_path" "$old_runtime_snapshot" /workspace/source/affected-services.json <<'NODE' @@ -2116,7 +2106,7 @@ if git diff --cached --quiet; then fi short="$(printf '%.7s' "$(params.revision)")" gitops_commit_started_ms="$(ci_now_ms)" -git commit -m "chore: promote G14 GitOps source $short" +git commit -m "chore: promote node GitOps source $short" ci_timing_emit gitops-commit succeeded "$gitops_commit_started_ms" gitops_push_started_ms="$(ci_now_ms)" git_timed gitops-push 120 git push origin "HEAD:$(params.gitops-branch)" @@ -2142,8 +2132,8 @@ revision="$(git rev-parse HEAD)" : "\${LANE:?LANE is required}" : "\${CATALOG_PATH:?CATALOG_PATH is required}" : "\${IMAGE_TAG_MODE:?IMAGE_TAG_MODE is required}" -node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX" -node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX" --check +node scripts/run-bun.mjs scripts/gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX" +node scripts/run-bun.mjs scripts/gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX" --check node <<'NODE' const fs = require("node:fs"); const https = require("node:https"); @@ -2166,11 +2156,11 @@ const argoFiles = String(process.env.ARGO_FILES || "").split(",").map((item) => const fieldManager = requiredEnv("FIELD_MANAGER"); const eventName = requiredEnv("RECONCILE_EVENT"); const manifestFiles = [ - "deploy/gitops/g14/" + tektonDir + "/rbac.yaml", - "deploy/gitops/g14/" + tektonDir + "/pipeline.yaml", + "deploy/gitops/node/" + tektonDir + "/rbac.yaml", + "deploy/gitops/node/" + tektonDir + "/pipeline.yaml", ...(process.env.LANE === "v02" ? [] : [ - "deploy/gitops/g14/" + tektonDir + "/poller.yaml", - "deploy/gitops/g14/" + tektonDir + "/control-plane-reconciler.yaml" + "deploy/gitops/node/" + tektonDir + "/poller.yaml", + "deploy/gitops/node/" + tektonDir + "/control-plane-reconciler.yaml" ]), ...argoFiles ]; @@ -2280,7 +2270,7 @@ git remote set-url origin "$GIT_READ_URL" revision="$(git rev-parse HEAD)" subject="$(git log -1 --pretty=%s)" case "$subject" in - "chore: promote G14 GitOps source "*) + "chore: promote node GitOps source "*) echo "Skipping generated GitOps promotion commit $revision" exit 0 ;; @@ -2410,7 +2400,7 @@ NODE `; } -function ciLaneSettings(argsOrLane = "g14") { +function ciLaneSettings(argsOrLane = "node") { const lane = typeof argsOrLane === "string" ? argsOrLane : argsOrLane.lane; if (isRuntimeLane(lane)) { const namespace = namespaceNameForProfile(lane); @@ -2430,14 +2420,14 @@ function ciLaneSettings(argsOrLane = "g14") { }; } return { - lane: "g14", + lane: "node", profile: "dev", - gitopsTarget: "g14", - pipelineName: "hwlab-g14-ci-image-publish", - pipelineRunPrefix: "hwlab-g14-ci-poll", + gitopsTarget: "node", + pipelineName: "hwlab-node-ci-image-publish", + pipelineRunPrefix: "hwlab-node-ci-poll", serviceAccountName: "hwlab-tekton-runner", - pollerName: "hwlab-g14-branch-poller", - controlPlaneReconcilerName: "hwlab-g14-control-plane-reconciler", + pollerName: "hwlab-node-branch-poller", + controlPlaneReconcilerName: "hwlab-node-control-plane-reconciler", tektonDir: "tekton", catalogPath: "deploy/artifact-catalog.dev.json", imageTagMode: "short", @@ -2445,7 +2435,7 @@ function ciLaneSettings(argsOrLane = "g14") { }; } -function tektonRbac(args = { lane: "g14" }) { +function tektonRbac(args = { lane: "node" }) { const settings = ciLaneSettings(args); if (isRuntimeLane(settings.lane)) { const runtimeObserverName = `${settings.runtimeNamespace}-runtime-observer`; @@ -2521,7 +2511,7 @@ function tektonRbac(args = { lane: "g14" }) { { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", - metadata: { name: "hwlab-g14-control-plane-reconciler", namespace: "hwlab-dev" }, + metadata: { name: "hwlab-node-control-plane-reconciler", namespace: "hwlab-dev" }, rules: [ { apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] } ] @@ -2529,9 +2519,9 @@ function tektonRbac(args = { lane: "g14" }) { { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", - metadata: { name: "hwlab-g14-control-plane-reconciler", namespace: "hwlab-dev" }, + metadata: { name: "hwlab-node-control-plane-reconciler", namespace: "hwlab-dev" }, subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }], - roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-g14-control-plane-reconciler" } + roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-node-control-plane-reconciler" } }, { apiVersion: "rbac.authorization.k8s.io/v1", @@ -2683,7 +2673,7 @@ function planArtifactsTask({ serviceIds = defaultServiceIds } = {}) { { name: "registry-prefix" }, { name: "services" } ], - results: serviceIds.map((serviceId) => ({ name: affectedResultName(serviceId), description: `${serviceId} changed according to g14-ci-plan` })), + results: serviceIds.map((serviceId) => ({ name: affectedResultName(serviceId), description: `${serviceId} changed according to ci-plan` })), workspaces: [{ name: "source" }], steps: [{ name: "plan", @@ -2808,13 +2798,13 @@ const progressEveryMs = 10000; function emit(payload) { console.log(JSON.stringify({ - event: "g14-cicd-timing", + event: "node-cicd-timing", schemaVersion: "v1", pipelineRun, taskRun, task, revision, - source: "scripts/g14-gitops-render.mjs", + source: "scripts/gitops-render.mjs", at: new Date().toISOString(), ...payload })); @@ -3075,7 +3065,7 @@ function runtimeReadyTask({ profile = "dev" } = {}) { }; } -function imagePublishTaskSet(args = { lane: "g14" }, deploy = null) { +function imagePublishTaskSet(args = { lane: "node" }, deploy = null) { const serviceIds = serviceIdsForLane(args.lane, deploy); const buildTasks = serviceIds.map((serviceId) => perServiceBuildTask(serviceId, { runAfter: ["plan-artifacts"] @@ -3087,7 +3077,7 @@ function imagePublishTaskSet(args = { lane: "g14" }, deploy = null) { ]; } -function tektonPipeline(args = { lane: "g14" }, deploy = null) { +function tektonPipeline(args = { lane: "node" }, deploy = null) { const settings = ciLaneSettings(args); const runtimePath = gitopsPathForProfile(args, settings.profile); return { @@ -3101,7 +3091,7 @@ function tektonPipeline(args = { lane: "g14" }, deploy = null) { "hwlab.pikastech.local/gitops-target": settings.gitopsTarget }, annotations: { - "hwlab.pikastech.local/source-config": "scripts/g14-gitops-render.mjs#tekton-native-primitive-ci", + "hwlab.pikastech.local/source-config": "scripts/gitops-render.mjs#tekton-native-primitive-ci", "hwlab.pikastech.local/ci-contract": "tekton-native-primitive-tasks", "hwlab.pikastech.local/policy": "native-per-service-taskrun-image-publish" } @@ -3349,7 +3339,7 @@ function tektonControlPlaneReconcilerCronJob(args) { annotations: { "hwlab.pikastech.local/source-branch": args.sourceBranch, "hwlab.pikastech.local/gitops-branch": args.gitopsBranch, - "hwlab.pikastech.local/purpose": "render-and-apply-g14-ci-control-plane" + "hwlab.pikastech.local/purpose": "render-and-apply-node-ci-control-plane" } }, spec: { @@ -4115,7 +4105,7 @@ function contentType(filePath) { }; } -function argoProject(args = { lane: "g14" }) { +function argoProject(args = { lane: "node" }) { if (isRuntimeLane(args.lane)) { const namespace = namespaceNameForProfile(args.lane); return { @@ -4123,7 +4113,7 @@ function argoProject(args = { lane: "g14" }) { kind: "AppProject", metadata: { name: namespace, namespace: "argocd" }, spec: { - description: `HWLAB ${versionNameForRuntimeLane(args.lane)} additive GitOps project on G14; DEV/PROD remain outside this lane.`, + description: `HWLAB ${versionNameForRuntimeLane(args.lane)} additive GitOps project on target node; DEV/PROD remain outside this lane.`, sourceRepos: [args.gitReadUrl], destinations: [{ server: "https://kubernetes.default.svc", namespace }], clusterResourceWhitelist: [{ group: "", kind: "Namespace" }], @@ -4134,9 +4124,9 @@ function argoProject(args = { lane: "g14" }) { return { apiVersion: "argoproj.io/v1alpha1", kind: "AppProject", - metadata: { name: "hwlab-g14", namespace: "argocd" }, + metadata: { name: "hwlab-node", namespace: "argocd" }, spec: { - description: "HWLAB G14 GitOps project; D601 remains outside this project.", + description: "HWLAB node GitOps project; D601 remains outside this project.", sourceRepos: [defaultSourceRepo], destinations: [ { server: "https://kubernetes.default.svc", namespace: "hwlab-dev" }, @@ -4152,7 +4142,7 @@ function argoApplication(args, profile = "dev") { const namespace = namespaceNameForProfile(profile); const runtimePath = runtimePathForProfile(profile); const gitopsTarget = gitopsTargetForProfile(profile); - const project = isRuntimeLane(profile) ? namespace : "hwlab-g14"; + const project = isRuntimeLane(profile) ? namespace : "hwlab-node"; const repoURL = isRuntimeLane(profile) ? args.gitReadUrl : args.sourceRepo; return { apiVersion: "argoproj.io/v1alpha1", @@ -4171,12 +4161,12 @@ function argoApplication(args, profile = "dev") { }; } -function g14FrpcManifest({ profile = "dev", source = null } = {}) { +function nodeFrpcManifest({ profile = "dev", source = null } = {}) { const namespace = namespaceNameForProfile(profile); const profileLabel = runtimeLabelForProfile(profile); - const deploymentName = isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-g14-prod-frpc" : "hwlab-g14-frpc"; + const deploymentName = isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-node-prod-frpc" : "hwlab-node-frpc"; const configName = `${deploymentName}-config`; - const proxyPrefix = isRuntimeLane(profile) ? `hwlab-${profile}` : profile === "prod" ? "hwlab-g14-prod" : "hwlab-g14"; + const proxyPrefix = isRuntimeLane(profile) ? `hwlab-${profile}` : profile === "prod" ? "hwlab-node-prod" : "hwlab-node"; const webRemotePort = isRuntimeLane(profile) ? defaultPortForRuntimeLane(profile) : profile === "prod" ? 18666 : 17666; const edgeRemotePort = isRuntimeLane(profile) ? webRemotePort + 1 : profile === "prod" ? 18667 : 17667; const labels = { @@ -4260,16 +4250,17 @@ remotePort = ${edgeRemotePort} }; } -function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14", sourceRepo = defaultSourceRepo, deploy = null, registryPrefix = defaultRegistryPrefix, catalog = null, useDeployImages = false, metricsSidecarSha256 = null } = {}) { +function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = defaultBranch, 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 envReuseServiceIds = profile === "v02" ? new Set([bridgeServiceId]) : null; + const runtimeLane = isRuntimeLane(profile); + const digestPin = runtimeLane; + const envReuseServiceIds = runtimeLane ? envReuseServiceIdsForLane(deploy, profile) : 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 }) + const bridgeBootMetadata = runtimeLane && v02EnvReuseEnabled(catalog, bridgeServiceId, envReuseServiceIds) + ? bootMetadataForService({ args: { sourceRepo: deploy?.lanes?.[profile]?.sourceRepo || sourceRepo, registryPrefix }, catalog, deployService: deployServiceForBoot(deploy, bridgeServiceId, profile), serviceId: bridgeServiceId, source }) : null; const labels = { "app.kubernetes.io/name": "hwlab-deepseek-proxy", @@ -4279,7 +4270,7 @@ function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14", "hwlab.pikastech.local/service-id": "hwlab-deepseek-proxy", "hwlab.pikastech.local/source-commit": source.full }; - if (profile === "v02") Object.assign(labels, v02MetricsLabels("hwlab-deepseek-proxy")); + if (runtimeLane) Object.assign(labels, v02MetricsLabels("hwlab-deepseek-proxy")); const templateLabels = { ...labels, "hwlab.pikastech.local/source-commit": bridgeCommit, @@ -4291,7 +4282,7 @@ function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14", "hwlab.pikastech.local/bridge-source-commit": bridgeCommit, "hwlab.pikastech.local/bridge-image-tag": bridgeImageTag, "hwlab.pikastech.local/bridge-image": bridgeImage, - ...v02MetricsSidecarAnnotations(profile === "v02" ? metricsSidecarSha256 : null) + ...v02MetricsSidecarAnnotations(runtimeLane ? metricsSidecarSha256 : null) }; const responsesBridgeContainer = { name: "responses-bridge", @@ -4372,12 +4363,12 @@ function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14", { name: "moonbridge-config", mountPath: "/config", readOnly: true }, { name: "moonbridge-data", mountPath: "/data" } ] - }, ...(profile === "v02" ? [v02MetricsSidecarContainer({ deploy, serviceId: "hwlab-deepseek-proxy", namespace, gitopsTarget: gitopsTargetForProfile(profile) })] : [])], + }, ...(runtimeLane ? [v02MetricsSidecarContainer({ deploy, serviceId: "hwlab-deepseek-proxy", namespace, gitopsTarget: gitopsTargetForProfile(profile) })] : [])], volumes: [ { name: "moonbridge-scripts", configMap: { name: "hwlab-deepseek-proxy-config" } }, { name: "moonbridge-config", emptyDir: {} }, { name: "moonbridge-data", emptyDir: {} }, - ...(profile === "v02" ? [v02MetricsSidecarVolume()] : []) + ...(runtimeLane ? [v02MetricsSidecarVolume()] : []) ] } } @@ -4387,7 +4378,7 @@ function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14", apiVersion: "v1", kind: "Service", metadata: { name: "hwlab-deepseek-proxy", namespace, labels }, - spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" }, ports: [{ name: "http", port: 4000, targetPort: "http" }, ...(profile === "v02" ? [{ name: "metrics", port: 9100, targetPort: "metrics" }] : [])] } + spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" }, ports: [{ name: "http", port: 4000, targetPort: "http" }, ...(runtimeLane ? [{ name: "metrics", port: 9100, targetPort: "metrics" }] : [])] } } ] }; @@ -4620,12 +4611,12 @@ function deviceAgent71FreqManifest({ profile = "dev", source, registryPrefix, ca "hwlab.pikastech.local/component": "device-agent", "hwlab.pikastech.local/device-id": "71-freq", "hwlab.pikastech.local/environment": runtimeLabelForProfile(profile), - "hwlab.pikastech.local/gitops-target": "g14", + "hwlab.pikastech.local/gitops-target": "node", "hwlab.pikastech.local/profile": runtimeLabelForProfile(profile), "hwlab.pikastech.local/service-id": name, "hwlab.pikastech.local/source-commit": source.full }; - const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" }; + const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" }; const selector = { "app.kubernetes.io/name": name }; const script = deviceAgent71FreqServerScript(); const scriptSha256 = createHash("sha256").update(script).digest("hex"); @@ -4795,7 +4786,7 @@ function v02OpenFgaManifest({ profile = "v02", source }) { "hwlab.pikastech.local/service-id": name, "hwlab.pikastech.local/source-commit": source.full }; - const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" }; + const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" }; const selector = { "app.kubernetes.io/name": name }; const env = [ { name: "OPENFGA_DATASTORE_ENGINE", value: "postgres" }, @@ -4877,7 +4868,7 @@ function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile == if (!isRuntimeLane(profile)) resources.splice(1, 0, "code-agent-codex-config.yaml"); if (isRuntimeLane(profile)) resources.push("postgres.yaml", "openfga.yaml", "observability.yaml"); if (includeDeviceAgent) resources.push("device-agent-71-freq.yaml"); - resources.push("g14-frpc.yaml"); + resources.push("node-frpc.yaml"); return { apiVersion: "kustomize.config.k8s.io/v1beta1", kind: "Kustomization", @@ -4890,33 +4881,33 @@ function readme({ source, args }) { if (args.lane === "v02") { return `# HWLAB v0.2 GitOps CI/CD -This directory is generated by \`scripts/g14-gitops-render.mjs --lane v02\` from source branch \`${args.sourceBranch}\`. +This directory is generated by \`scripts/gitops-render.mjs --lane v02\` from source branch \`${args.sourceBranch}\`. -- Target: additive G14 v0.2 lane only. +- Target: additive node-scoped v0.2 lane only. - CI: Tekton Pipeline \`hwlab-ci/hwlab-v02-ci-image-publish\`. - Git mirror/relay: v0.2 CI reads source/catalog from \`${args.gitReadUrl}\` and writes GitOps promotion to \`${args.gitWriteUrl}\`; canonical repo \`${args.sourceRepo}\` remains the source identity and manual flush upstream. - Artifact contract: runtime images come from \`${args.catalogPath}\`; source branch \`v0.2\` does not track this generated catalog. -- Manual trigger: run \`bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm\` from UniDesk to create commit-pinned PipelineRun \`hwlab-v02-ci-poll-\` for the current \`origin/v0.2\` commit. +- Manual trigger: run \`bun scripts/cli.ts hwlab node control-plane trigger-current --lane v02 --confirm\` from UniDesk to create commit-pinned PipelineRun \`hwlab-v02-ci-poll-\` for the current \`origin/v0.2\` commit. - Branch split: source branch \`${args.sourceBranch}\` remains human-authored source; generated desired state is promoted to local mirror branch \`${args.gitopsBranch}\`. -- CD: Argo CD Application \`argocd/hwlab-g14-v02\` consumes \`${args.gitReadUrl}:${args.gitopsBranch}:deploy/gitops/g14/runtime-v02\` and deploys only to namespace \`hwlab-v02\`. +- CD: Argo CD Application \`argocd/hwlab-node-v02\` consumes \`${args.gitReadUrl}:${args.gitopsBranch}:deploy/gitops/node/runtime-v02\` and deploys only to namespace \`hwlab-v02\`. - Public preview: FRP exposes v0.2 web \`${args.webEndpoint}\` / edge \`${args.runtimeEndpoint}\` through \`hwlab-v02/hwlab-v02-frpc\`. -- G14 DEV/PROD boundary: this lane does not modify \`G14\`, \`G14-gitops\`, \`hwlab-dev\`, \`hwlab-prod\`, 17666/17667 or 18666/18667. +- node DEV/PROD boundary: this lane does not modify archived DEV/PROD branches, \`hwlab-dev\`, \`hwlab-prod\`, 17666/17667 or 18666/18667. - CronJob policy: v0.2 does not generate \`hwlab-v02-branch-poller\` or \`hwlab-v02-control-plane-reconciler\`; obsolete live CronJobs should be deleted by the UniDesk control-plane apply command. - Gate policy: old DEV/D601/main gates do not enter this lane; new checks stay limited to the fixed branch, namespace, catalog, runtime path and Argo boundaries. `; } - return `# HWLAB G14 GitOps CI/CD + return `# HWLAB node GitOps CI/CD -This directory is generated from built-in Tekton-native CI primitive declarations plus human-authored \`deploy/deploy.yaml\`, CI-authored \`deploy/artifact-catalog.dev.json\`, and \`deploy/k8s/*\` by \`scripts/g14-gitops-render.mjs\`. +This directory is generated from built-in Tekton-native CI primitive declarations plus human-authored \`deploy/deploy.yaml\`, CI-authored \`deploy/artifact-catalog.dev.json\`, and \`deploy/k8s/*\` by \`scripts/gitops-render.mjs\`. -- Target: G14 k3s only. -- CI: Tekton Pipeline \`hwlab-ci/hwlab-g14-ci-image-publish\`. +- Target: node k3s only. +- CI: Tekton Pipeline \`hwlab-ci/hwlab-node-ci-image-publish\`. - Artifact contract: runtime images come from \`deploy/artifact-catalog.dev.json\`; \`deploy/deploy.yaml\` only carries human-authored config such as env and topology. - Branch split: source branch \`${args.sourceBranch}\` remains the watched code branch; generated desired state is promoted to \`${args.gitopsBranch}\`. -- CD: Argo CD Applications \`argocd/hwlab-g14-dev\` and \`argocd/hwlab-g14-prod\` consume \`${args.gitopsBranch}:deploy/gitops/g14/runtime-dev\` and \`runtime-prod\`. -- Public preview: FRP exposes G14 DEV web \`${args.webEndpoint}\` / edge \`${args.runtimeEndpoint}\` through \`hwlab-dev/hwlab-g14-frpc\`, and G14 PROD web \`${args.prodWebEndpoint}\` / edge \`${args.prodRuntimeEndpoint}\` through \`hwlab-prod/hwlab-g14-prod-frpc\`; D601 keeps \`:16666/:16667\`. -- Manual polling: run \`kubectl -n hwlab-ci create job --from=cronjob/hwlab-g14-branch-poller hwlab-g14-branch-poller-manual-$(date -u +%Y%m%d%H%M%S)\`; this reuses the same poller path and creates a commit-pinned Tekton PipelineRun only when needed. -- Control-plane reconcile: CronJob \`hwlab-ci/hwlab-g14-control-plane-reconciler\` polls \`${args.sourceBranch}\`, runs \`scripts/g14-gitops-render.mjs\`, and server-side-applies the generated Tekton RBAC/Pipeline/Poller/Reconciler manifests so CI control-plane changes do not require a manual render/apply step. +- CD: Argo CD Applications \`argocd/hwlab-node-dev\` and \`argocd/hwlab-node-prod\` consume \`${args.gitopsBranch}:deploy/gitops/node/runtime-dev\` and \`runtime-prod\`. +- Public preview: FRP exposes node DEV web \`${args.webEndpoint}\` / edge \`${args.runtimeEndpoint}\` through \`hwlab-dev/hwlab-node-frpc\`, and node PROD web \`${args.prodWebEndpoint}\` / edge \`${args.prodRuntimeEndpoint}\` through \`hwlab-prod/hwlab-node-prod-frpc\`; D601 keeps \`:16666/:16667\`. +- Manual polling: run \`kubectl -n hwlab-ci create job --from=cronjob/hwlab-node-branch-poller hwlab-node-branch-poller-manual-$(date -u +%Y%m%d%H%M%S)\`; this reuses the same poller path and creates a commit-pinned Tekton PipelineRun only when needed. +- Control-plane reconcile: CronJob \`hwlab-ci/hwlab-node-control-plane-reconciler\` polls \`${args.sourceBranch}\`, runs \`scripts/gitops-render.mjs\`, and server-side-applies the generated Tekton RBAC/Pipeline/Poller/Reconciler manifests so CI control-plane changes do not require a manual render/apply step. - D601 boundary: this path does not target D601, does not use UniDesk Code Queue, and does not change D601 production traffic. - Concurrency: CI builds are per source commit and do not acquire the legacy DEV CD Lease; Argo CD reconciles the Git desired state. `; @@ -4956,7 +4947,7 @@ async function plannedFiles(args) { : null; const metricsSidecarSha256 = metricsSidecarScript ? createHash("sha256").update(metricsSidecarScript).digest("hex") : null; const baseLabels = { "hwlab.pikastech.local/gitops-target": settings.gitopsTarget, "hwlab.pikastech.local/source-commit": source.full }; - const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" }; + const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" }; const files = new Map(); const putJson = (relative, value) => files.set(path.join(args.outDir, relative), jsonManifest(value)); const putText = (relative, value) => files.set(path.join(args.outDir, relative), textFile(value)); @@ -4966,7 +4957,7 @@ async function plannedFiles(args) { })); const sourceDescriptor = { - kind: isRuntimeLane(args.lane) ? `hwlab-${args.lane}-gitops-source` : "hwlab-g14-gitops-source", + kind: isRuntimeLane(args.lane) ? `hwlab-${args.lane}-gitops-source` : "hwlab-node-gitops-source", sourceCommit: source.full, sourceShortCommit: source.short, sourceImageTag: source.imageTag, @@ -4979,7 +4970,7 @@ async function plannedFiles(args) { registryPrefix: args.registryPrefix, runtimePaths: profiles.map((profile) => gitopsPathForProfile(args, profile)), publicEndpoints, - frpDeployments: Object.fromEntries(profiles.map((profile) => [profile, `${namespaceNameForProfile(profile)}/${isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-g14-prod-frpc" : "hwlab-g14-frpc"}`])), + frpDeployments: Object.fromEntries(profiles.map((profile) => [profile, `${namespaceNameForProfile(profile)}/${isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-node-prod-frpc" : "hwlab-node-frpc"}`])), tektonPipeline: `hwlab-ci/${settings.pipelineName}`, argoApplications: profiles.map((profile) => `argocd/${argoApplicationName(profile)}`) }; @@ -5025,7 +5016,7 @@ async function plannedFiles(args) { if (isRuntimeLane(profile)) putJson(`${runtimePath}/openfga.yaml`, v02OpenFgaManifest({ profile, source })); if (isRuntimeLane(profile)) putJson(`${runtimePath}/observability.yaml`, observabilityManifest({ deploy, profile, namespace, labels: profileLabels, annotations, metricsSidecarScript })); if (includeDeviceAgent) putJson(`${runtimePath}/device-agent-71-freq.yaml`, deviceAgent71FreqManifest({ profile, source, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages })); - putJson(`${runtimePath}/g14-frpc.yaml`, g14FrpcManifest({ profile, source })); + putJson(`${runtimePath}/node-frpc.yaml`, nodeFrpcManifest({ profile, source })); } return { files, source }; } diff --git a/scripts/g14-gitops-render.test.ts b/scripts/gitops-render.test.ts similarity index 90% rename from scripts/g14-gitops-render.test.ts rename to scripts/gitops-render.test.ts index 59795ca2..2c4138c5 100644 --- a/scripts/g14-gitops-render.test.ts +++ b/scripts/gitops-render.test.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; -import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; @@ -11,6 +11,28 @@ function taskByName(pipeline, name) { return task; } +async function collectGeneratedFiles(root) { + const files = []; + async function walk(dir) { + const entries = await readdir(dir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) await walk(fullPath); + else if (entry.isFile()) files.push(fullPath); + } + } + await walk(root); + return files.sort(); +} + +function collectContainersFromItem(item) { + const podSpec = item?.spec?.template?.spec ?? item?.spec?.jobTemplate?.spec?.template?.spec ?? item?.spec?.template?.spec; + return [ + ...(podSpec?.initContainers ?? []), + ...(podSpec?.containers ?? []) + ]; +} + function extractRuntimeNoopDecisionScript(gitopsPromoteScript) { const marker = `runtime_noop_decision="$(node - "$runtime_path" "$old_runtime_snapshot" /workspace/source/affected-services.json <<'NODE'\n`; const start = gitopsPromoteScript.indexOf(marker); @@ -91,7 +113,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots const render = spawnSync(process.execPath, [ "scripts/run-bun.mjs", - "scripts/g14-gitops-render.mjs", + "scripts/gitops-render.mjs", "--lane", "v02", "--catalog-path", "deploy/artifact-catalog.v02.json", "--image-tag-mode", "full", @@ -130,7 +152,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots assert.match(runtimeReadyScript, /if \(!observedServiceIds\) \{/u); assert.match(runtimeReadyScript, /const refresh = await waitForArgoRefresh\(\);\n if \(!refresh\.observed\) return;/u); assert.ok(runtimeReady.taskSpec.params.some((param) => param.name === "argo-application")); - assert.ok(runtimeReady.params.some((param) => param.name === "argo-application" && param.value === "hwlab-g14-v02")); + assert.ok(runtimeReady.params.some((param) => param.name === "argo-application" && param.value === "hwlab-node-v02")); assert.ok(runtimeReady.workspaces?.some((workspace) => workspace.name === "source" && workspace.workspace === "source")); assert.ok(runtimeReady.taskSpec.workspaces?.some((workspace) => workspace.name === "source")); @@ -152,7 +174,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots } }), "runtime-identity-only"); assert.match(gitopsPromoteScript, /argo_hard_refresh_runtime_lane\(\) \{/u); - assert.match(gitopsPromoteScript, /ARGO_APPLICATION="hwlab-g14-\$lane" ARGO_FIELD_MANAGER="hwlab-\$lane-gitops-promote"/u); + assert.match(gitopsPromoteScript, /ARGO_APPLICATION="hwlab-node-\$lane" ARGO_FIELD_MANAGER="hwlab-\$lane-gitops-promote"/u); assert.match(gitopsPromoteScript, /argocd\.argoproj\.io\/refresh": "hard"/u); assert.match(gitopsPromoteScript, /applications\/" \+ encodeURIComponent\(application\) \+ "\?fieldManager=" \+ encodeURIComponent\(fieldManager\)/u); assert.match(gitopsPromoteScript, /BatchMode=yes/u); @@ -231,7 +253,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots assert.match(gitMirrorScript, /refs\/mirror-stage\/heads\/v0\.3/u); assert.match(gitMirrorScript, /for gitops_branch in 'v0\.2-gitops' 'v0\.3-gitops'/u); assert.match(gitMirrorScript, /keeps local \$name ahead of GitHub/u); - assert.match(gitMirror, /deploy\/artifact-catalog\.v03\.json\|deploy\/gitops\/g14\/runtime-v03\/\*/u); + assert.match(gitMirror, /deploy\/artifact-catalog\.v03\.json\|deploy\/gitops\/node\/runtime-v03\/\*/u); assert.ok((gitMirrorJson.items || []).every((item) => item.kind !== "Secret")); assert.ok((gitMirrorJson.items || []).every((item) => item.kind !== "CronJob")); assert.ok((gitMirrorJson.items || []).every((item) => !item.data?.["ssh-privatekey"])); @@ -281,7 +303,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots const collectScript = collectArtifacts.taskSpec.steps[0].script; assert.match(collectScript, /--external-service-report-dir \/workspace\/source\/service-results/u); const cloudWebBuildScript = taskByName(pipelineJson, "build-hwlab-cloud-web").taskSpec.steps.find((step) => step.name === "publish")?.script ?? ""; - assert.match(cloudWebBuildScript, /node scripts\/g14-artifact-publish\.mjs --publish/u); + assert.match(cloudWebBuildScript, /node scripts\/artifact-publish\.mjs --publish/u); const artifactPublishSource = await readFile("scripts/artifact-publish.mjs", "utf8"); assert.match(artifactPublishSource, /\["run", "--cwd", "web\/hwlab-cloud-web", "check"\]/u); @@ -426,11 +448,11 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots assert.match(application, /git-mirror-http\.devops-infra\.svc\.cluster\.local/u); assert.doesNotMatch(application, /git@github\.com:pikasTech\/HWLAB\.git/u); - for (const generatedFile of ["deepseek-proxy.yaml", "postgres.yaml", "openfga.yaml", "g14-frpc.yaml"]) { + for (const generatedFile of ["deepseek-proxy.yaml", "postgres.yaml", "openfga.yaml", "node-frpc.yaml"]) { const content = await readFile(path.join(outDir, "runtime-v02", generatedFile), "utf8"); assert.match(content, /"hwlab\.pikastech\.local\/source-commit"/u); } - const frpc = JSON.parse(await readFile(path.join(outDir, "runtime-v02", "g14-frpc.yaml"), "utf8")); + const frpc = JSON.parse(await readFile(path.join(outDir, "runtime-v02", "node-frpc.yaml"), "utf8")); const frpcDeployment = frpc.items.find((item) => item.kind === "Deployment"); const frpcConfigLabel = frpcDeployment.spec.template.metadata.labels["hwlab.pikastech.local/config-sha256"]; const frpcConfigAnnotation = frpcDeployment.spec.template.metadata.annotations["hwlab.pikastech.local/config-sha256"]; @@ -485,3 +507,49 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots await rm(outDir, { recursive: true, force: true }); } }); + +test("v03 render keeps node identity as data instead of generated structure", async () => { + const outDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-v03-render-test-")); + try { + const render = spawnSync(process.execPath, [ + "scripts/run-bun.mjs", + "scripts/gitops-render.mjs", + "--lane", "v03", + "--node", "G14", + "--catalog-path", "deploy/artifact-catalog.v03.json", + "--image-tag-mode", "full", + "--source-branch", "v0.3", + "--gitops-branch", "v0.3-gitops", + "--out", outDir, + "--source-revision", "HEAD" + ], { cwd: process.cwd(), encoding: "utf8" }); + assert.equal(render.status, 0, render.stderr || render.stdout); + + const generatedFiles = await collectGeneratedFiles(outDir); + const generatedPaths = generatedFiles.map((filePath) => path.relative(outDir, filePath)); + assert.ok(generatedPaths.includes("runtime-v03/node-frpc.yaml")); + assert.ok(generatedPaths.includes("argocd/application-v03.yaml")); + assert.ok(generatedPaths.includes("tekton-v03/pipeline.yaml")); + for (const relativePath of generatedPaths) { + assert.doesNotMatch(relativePath, /g14/i, `legacy node token leaked into generated path ${relativePath}`); + } + + const workloads = await readFile(path.join(outDir, "runtime-v03", "workloads.yaml"), "utf8"); + const workloadsJson = JSON.parse(workloads); + const agentRunNodeEnv = (workloadsJson.items ?? []) + .flatMap((item) => collectContainersFromItem(item)) + .flatMap((container) => container.env ?? []) + .filter((entry) => entry.name === "HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID"); + assert.ok(agentRunNodeEnv.length > 0, "expected AgentRun provider id env to carry configured node id"); + assert.deepEqual([...new Set(agentRunNodeEnv.map((entry) => entry.value))], ["G14"]); + + const allowedNodeValue = /"name":\s*"HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID"[\s\S]{0,120}"value":\s*"G14"/gu; + for (const filePath of generatedFiles) { + const text = await readFile(filePath, "utf8"); + const sanitized = text.replace(allowedNodeValue, "__CONFIGURED_NODE_ID__"); + assert.doesNotMatch(sanitized, /g14|G14/u, `legacy node token leaked outside node data in ${path.relative(outDir, filePath)}`); + } + } finally { + await rm(outDir, { recursive: true, force: true }); + } +}); diff --git a/scripts/hwlab-lane-expand.mjs b/scripts/hwlab-lane-expand.mjs index 2406feed..326675ca 100644 --- a/scripts/hwlab-lane-expand.mjs +++ b/scripts/hwlab-lane-expand.mjs @@ -38,9 +38,9 @@ process.stdout.write(`${JSON.stringify({ copiedFiles: result.copiedFiles.map((file) => ({ from: file.from, to: file.to })), config: laneSummary(result.config), next: { - render: `node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane ${result.lane} --source-branch ${result.config.sourceBranch} --gitops-branch ${result.config.gitopsBranch} --catalog-path ${result.config.artifactCatalog} --image-tag-mode ${result.config.imageTagMode ?? "full"}`, - controlPlane: `bun scripts/cli.ts hwlab g14 control-plane apply --lane ${result.lane} --confirm`, - trigger: `bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane ${result.lane} --confirm` + render: `node scripts/run-bun.mjs scripts/gitops-render.mjs --lane ${result.lane} --source-branch ${result.config.sourceBranch} --gitops-branch ${result.config.gitopsBranch} --catalog-path ${result.config.artifactCatalog} --image-tag-mode ${result.config.imageTagMode ?? "full"}`, + controlPlane: `bun scripts/cli.ts hwlab node control-plane apply --lane ${result.lane} --confirm`, + trigger: `bun scripts/cli.ts hwlab node control-plane trigger-current --lane ${result.lane} --confirm` } }, null, 2)}\n`); @@ -136,7 +136,8 @@ async function configureLane(deploy, options) { const target = deepTransform(clone(source), replacements); target.name = targetInfo.versionName; - target.node = target.node ?? source.node ?? "G14"; + target.node = target.node ?? source.node; + assert.ok(target.node, "target lane node is required; set deploy.lanes..node or source lane node"); target.sourceBranch = targetInfo.sourceBranch; target.gitopsBranch = targetInfo.gitopsBranch; target.namespace = targetInfo.namespace; diff --git a/scripts/refresh-artifact-catalog.mjs b/scripts/refresh-artifact-catalog.mjs index 4dcdef54..7495165f 100644 --- a/scripts/refresh-artifact-catalog.mjs +++ b/scripts/refresh-artifact-catalog.mjs @@ -16,7 +16,7 @@ import { readStructuredFile } from "./src/structured-config.mjs"; const execFileAsync = promisify(execFile); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const defaultLane = process.env.HWLAB_ARTIFACT_LANE || process.env.HWLAB_GITOPS_LANE || "g14"; +const defaultLane = process.env.HWLAB_ARTIFACT_LANE || process.env.HWLAB_GITOPS_LANE || "node"; const defaultCatalogPath = process.env.HWLAB_ARTIFACT_CATALOG_PATH || "deploy/artifact-catalog.dev.json"; const defaultDeployPath = process.env.HWLAB_DEPLOY_MANIFEST_PATH || "deploy/deploy.yaml"; const defaultPublishReportPath = tempReportPath("dev-artifacts.json"); @@ -99,7 +99,7 @@ function parseArgs(argv) { } if (!args.help) { - assert.ok(args.lane === "g14" || isRuntimeArtifactLane(args.lane), `unknown artifact lane ${args.lane}`); + assert.ok(args.lane === "node" || isRuntimeArtifactLane(args.lane), `unknown artifact lane ${args.lane}`); assert.ok(["short", "full"].includes(args.imageTagMode), `unknown image tag mode ${args.imageTagMode}`); assert.notEqual(args.blocked && Boolean(args.publishReportPath), true, "--blocked and --publish-report are mutually exclusive"); assert.ok(args.blocked || args.publishReportPath, `choose --blocked or --publish-report ${defaultPublishReportPath}`); @@ -122,7 +122,7 @@ function usage() { "usage: node scripts/refresh-artifact-catalog.mjs --target-ref REF (--blocked|--publish-report PATH) [--write|--no-write]", "", "Preview or explicitly write DEV artifact catalog identity without faking digest evidence.", - "Default mode is read-only. --write is reserved for G14 Tekton GitOps promotion and never writes deploy/deploy.yaml.", + "Default mode is read-only. --write is reserved for node Tekton GitOps promotion and never writes deploy/deploy.yaml.", "", "examples:", " node scripts/refresh-artifact-catalog.mjs --target-ref HEAD --blocked --no-write", @@ -225,7 +225,7 @@ function artifactCatalogSkeleton({ args, deploy, target, registryPrefix = defaul ciPublished: false, registryVerified: false, provenance: "not_available_until_publish", - note: `${environment} artifact catalog skeleton initialized without DEV/G14 fallback.` + note: `${environment} artifact catalog skeleton initialized without DEV/node fallback.` }, healthContract: { method: "GET", @@ -426,7 +426,7 @@ function publishRecordsFromReport(report, target, serviceIds = SERVICE_IDS) { return { records, requiredServiceIds, disabledServiceIds }; } -function refreshDocuments({ deploy, catalog, target, publishRecords, serviceInventory, provenancePath, registryPrefix, lane = "g14", serviceIds = SERVICE_IDS }) { +function refreshDocuments({ deploy, catalog, target, publishRecords, serviceInventory, provenancePath, registryPrefix, lane = "node", serviceIds = SERVICE_IDS }) { const published = Boolean(publishRecords); const records = publishRecords?.records ?? null; const requiredIds = new Set(publishRecords?.requiredServiceIds ?? serviceInventory.requiredServiceIds); diff --git a/scripts/refresh-artifact-catalog.test.mjs b/scripts/refresh-artifact-catalog.test.mjs index 5aa48740..592f3738 100644 --- a/scripts/refresh-artifact-catalog.test.mjs +++ b/scripts/refresh-artifact-catalog.test.mjs @@ -43,7 +43,7 @@ test("refresh accepts an absolute publish report path", async () => { const reportPath = path.join(tempDir, "dev-artifacts.json"); await writeFile(reportPath, `${JSON.stringify({ reportVersion: "v1", - taskId: "g14-artifact-publish", + taskId: "artifact-publish", commitId: shortCommitId, artifactPublish: { status: "published", @@ -112,7 +112,7 @@ test("refresh is read-only by default", async () => { const reportPath = path.join(tempDir, "dev-artifacts.json"); await writeFile(reportPath, `${JSON.stringify({ reportVersion: "v1", - taskId: "g14-artifact-publish", + taskId: "artifact-publish", commitId: shortCommitId, artifactPublish: { status: "published", @@ -170,7 +170,7 @@ test("refresh keeps reused service image tags from the publish report", async () const reportPath = path.join(tempDir, "dev-artifacts.json"); await writeFile(reportPath, `${JSON.stringify({ reportVersion: "v1", - taskId: "g14-artifact-publish", + taskId: "artifact-publish", commitId: shortCommitId, artifactPublish: { status: "published", diff --git a/scripts/src/artifact-runtime-readiness-guard.mjs b/scripts/src/artifact-runtime-readiness-guard.mjs index 76ed4604..04e2c115 100644 --- a/scripts/src/artifact-runtime-readiness-guard.mjs +++ b/scripts/src/artifact-runtime-readiness-guard.mjs @@ -141,7 +141,7 @@ export function evaluateArtifactRuntimeReadiness({ pass: commitsMatch(catalog.commitId, target.commitId), type: "observability_blocker", summary: `Artifact catalog commit=${catalog.shortCommitId}; target=${target.shortCommitId}.`, - nextTask: "Do not refresh source desired-state. Let G14 Tekton promotion write artifact catalog identity to G14-gitops, then verify Argo/runtime revision." + nextTask: "Do not refresh source desired-state. Let node Tekton promotion write artifact catalog identity to the configured GitOps branch, then verify Argo/runtime revision." }); addCheck(checks, { id: "artifact-report-catalog-match", @@ -162,7 +162,7 @@ export function evaluateArtifactRuntimeReadiness({ pass: desiredState.matchesTarget, type: "observability_blocker", summary: `Desired-state deploy=${desiredState.deployCommitId}; target=${target.shortCommitId}; convergence=${desiredState.targetConvergence.state}; pending=${desiredState.targetConvergence.pendingTargetFields}.`, - nextTask: `Run node scripts/deploy-desired-state-plan.mjs --promotion-commit ${target.shortCommitId} --check as a read-only review, then rely on G14 Tekton promotion for catalog writes.` + nextTask: `Run node scripts/deploy-desired-state-plan.mjs --promotion-commit ${target.shortCommitId} --check as a read-only review, then rely on node Tekton promotion for catalog writes.` }); addCheck(checks, { id: "rollout-read-access", @@ -303,7 +303,7 @@ export function buildArtifactRuntimeReadinessReport({ sourceContract: { status: "pass", documents: [ - "docs/reference/g14-gitops-cicd.md" + "docs/reference/node-gitops-cicd.md" ], summary: "Target commit, artifact publish/catalog identity, desired-state convergence, API runtime identity, and Cloud Web served runtime identity are separate gates." }, @@ -1220,7 +1220,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) { return [ "bun run --cwd web/hwlab-cloud-web build", "bun run --cwd web/hwlab-cloud-web check", - "node scripts/g14-artifact-publish.mjs --preflight --no-report", + "node scripts/artifact-publish.mjs --preflight --no-report", "node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked --no-write", guardCommand ]; @@ -1229,7 +1229,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) { return [ "node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --pretty", "node scripts/deploy-desired-state-plan.mjs --promotion-commit --check", - "node scripts/g14-gitops-render.mjs --no-write", + "node scripts/gitops-render.mjs --no-write", guardCommand ]; } @@ -1241,7 +1241,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) { ]; } return [ - "node scripts/g14-gitops-render.mjs --no-write", + "node scripts/gitops-render.mjs --no-write", "node scripts/artifact-runtime-readiness-guard.mjs --target-ref origin/main --check --live --no-report" ]; } diff --git a/scripts/src/check-plan.mjs b/scripts/src/check-plan.mjs index 68e027a0..d9061708 100644 --- a/scripts/src/check-plan.mjs +++ b/scripts/src/check-plan.mjs @@ -101,8 +101,8 @@ export const checkProfiles = Object.freeze({ { id: "check-090-deploy-refresh-artifact-catalog", group: "deploy", command: ["node","--check","scripts/refresh-artifact-catalog.mjs"] }, { id: "check-091-deploy-refresh-artifact-catalog-test", group: "deploy", command: ["node","--check","scripts/refresh-artifact-catalog.test.mjs"] }, { id: "check-092-artifact-artifact-publish", group: "artifact", command: ["node","--check","scripts/artifact-publish.mjs"] }, - { id: "check-093-artifact-g14-artifact-publish", group: "artifact", command: ["node","--check","scripts/g14-artifact-publish.mjs"] }, - { id: "check-093a-artifact-g14-gitops-render-test", group: "artifact", command: ["node","scripts/run-bun.mjs","test","scripts/g14-gitops-render.test.ts"] }, + { id: "check-093-artifact-artifact-publish", group: "artifact", command: ["node","--check","scripts/artifact-publish.mjs"] }, + { id: "check-093a-artifact-gitops-render-test", group: "artifact", command: ["node","scripts/run-bun.mjs","test","scripts/gitops-render.test.ts"] }, { id: "check-094-artifact-dev-runtime-base-image", group: "artifact", command: ["node","--check","scripts/dev-runtime-base-image.mjs"] }, { id: "check-095-artifact-dev-artifact-services", group: "artifact", command: ["node","--check","scripts/src/dev-artifact-services.mjs"] }, { id: "check-096-artifact-registry-capabilities", group: "artifact", command: ["node","--check","scripts/src/registry-capabilities.mjs"] }, diff --git a/scripts/src/g14-ci-plan-lib.mjs b/scripts/src/ci-plan-lib.mjs similarity index 99% rename from scripts/src/g14-ci-plan-lib.mjs rename to scripts/src/ci-plan-lib.mjs index 36a5472e..c42f7f8e 100644 --- a/scripts/src/g14-ci-plan-lib.mjs +++ b/scripts/src/ci-plan-lib.mjs @@ -7,7 +7,7 @@ import { readStructuredFileIfPresent } from "./structured-config.mjs"; const execFileAsync = promisify(execFile); -export const G14_CI_PLAN_VERSION = "v1"; +export const CI_PLAN_VERSION = "v1"; export const ENV_REUSE_RUNTIME_MODE = "env-reuse-git-mirror-checkout"; export const V02_ENV_REUSE_RUNTIME_MODE = ENV_REUSE_RUNTIME_MODE; @@ -41,12 +41,12 @@ export const DEFAULT_DOCS_ONLY_PATHS = Object.freeze([ export const DEFAULT_GITOPS_ONLY_PATHS = Object.freeze([ "deploy/gitops/", "deploy/frp/", - "scripts/g14-gitops-render.mjs", + "scripts/gitops-render.mjs", "scripts/src/runtime-lane.ts", "tsconfig.gitops.json" ]); -export async function createG14CiPlan(options = {}) { +export async function createCiPlan(options = {}) { const repoRoot = options.repoRoot ?? process.cwd(); const deployConfigPath = options.deployConfigPath ?? "deploy/deploy.yaml"; const targetRef = options.targetRef ?? "HEAD"; @@ -218,7 +218,7 @@ export async function createG14CiPlan(options = {}) { const buildServices = services.filter((service) => service.buildRequired).map((service) => service.serviceId); const reusedServices = services.filter((service) => !service.affected).map((service) => service.serviceId); return { - planVersion: G14_CI_PLAN_VERSION, + planVersion: CI_PLAN_VERSION, sourceCommitId, shortCommitId, baseRef, @@ -227,7 +227,7 @@ export async function createG14CiPlan(options = {}) { deployConfig: deployConfigPath, artifactCatalog: artifactCatalog ? artifactCatalogPath : null, lane, - ciContractSource: "scripts/g14-gitops-render.mjs", + ciContractSource: "scripts/gitops-render.mjs", mode: "advisory-read-only", currentRenderCompatible: true, plannerMutatesDeployJson: false, @@ -595,7 +595,7 @@ function resolveServiceIds({ options, laneConfig, lane }) { const serviceIds = uniqueSorted(options.services.map(String)); const unknownServiceIds = serviceIds.filter((serviceId) => !allowedServiceIds.has(serviceId)); if (unknownServiceIds.length > 0) { - throw new Error(`unknown service IDs for G14 CI plan: ${unknownServiceIds.join(", ")}`); + throw new Error(`unknown service IDs for node CI plan: ${unknownServiceIds.join(", ")}`); } return { source: "cli-services", serviceIds }; } diff --git a/scripts/src/deploy-desired-state-plan.mjs b/scripts/src/deploy-desired-state-plan.mjs index 54e36d91..88de3437 100644 --- a/scripts/src/deploy-desired-state-plan.mjs +++ b/scripts/src/deploy-desired-state-plan.mjs @@ -974,7 +974,7 @@ export async function buildDesiredStatePlan(options = {}) { humanAuthoredTruth: [deployPath, workloadsPath], artifactIdentityTruth: [catalogPath], nonAuthoritativeEvidence: [artifactReportPath], - note: "This planner is read-only. deploy.yaml is human-authored config; artifact-catalog carries generated image identity and is written by G14 Tekton promotion only to G14-gitops. It does not prove image existence, registry reachability, a real DEV apply, or M3 DEV-LIVE hardware-loop evidence." + note: "This planner is read-only. deploy.yaml is human-authored config; artifact-catalog carries generated image identity and is written by node Tekton promotion only to the configured GitOps branch. It does not prove image existence, registry reachability, a real DEV apply, or M3 DEV-LIVE hardware-loop evidence." }, cloudApiDb: cloudApiDb ?? { status: "blocked", diff --git a/scripts/src/dev-base-image-preflight.mjs b/scripts/src/dev-base-image-preflight.mjs index 72073353..3ecf3716 100644 --- a/scripts/src/dev-base-image-preflight.mjs +++ b/scripts/src/dev-base-image-preflight.mjs @@ -67,7 +67,7 @@ function provisionPlan({ exactBlocker = null, envValue = recommendedNode20Image, `${envVarName}=${recommendedNode20Image} node scripts/preflight-dev-base-image.mjs` ], artifactPublishDryRun: [ - `${envVarName}=${envValue} node scripts/g14-artifact-publish.mjs --preflight --no-report` + `${envVarName}=${envValue} node scripts/artifact-publish.mjs --preflight --no-report` ] } }; diff --git a/scripts/src/dev-edge-health-smoke-lib.mjs b/scripts/src/dev-edge-health-smoke-lib.mjs index 478ed000..fdeb716a 100644 --- a/scripts/src/dev-edge-health-smoke-lib.mjs +++ b/scripts/src/dev-edge-health-smoke-lib.mjs @@ -168,7 +168,7 @@ async function createDevGateReport(edgeHealth) { documents: [ "docs/reference/dev-runtime-boundary.md", "docs/reference/spec-v02-documentation-governance.md", - "docs/reference/g14-gitops-cicd.md", + "docs/reference/node-gitops-cicd.md", "deploy/frp/README.md", "deploy/master-edge/README.md" ], diff --git a/scripts/src/dev-evidence-blocker-aggregator.mjs b/scripts/src/dev-evidence-blocker-aggregator.mjs index 1ebd6e02..52cf00dc 100644 --- a/scripts/src/dev-evidence-blocker-aggregator.mjs +++ b/scripts/src/dev-evidence-blocker-aggregator.mjs @@ -327,7 +327,7 @@ function buildEvidence() { category: "repository-governance", level: "SOURCE", status: "pass", - sources: ["scripts/repo-reports-guard.mjs", "package.json", "scripts/g14-gitops-render.mjs"], + sources: ["scripts/repo-reports-guard.mjs", "package.json", "scripts/gitops-render.mjs"], commands: ["node scripts/repo-reports-guard.mjs"], summary: "Repository report files are removed and guarded against recurrence." }) diff --git a/scripts/src/dev-gate-preflight.mjs b/scripts/src/dev-gate-preflight.mjs index 16297308..65c81592 100644 --- a/scripts/src/dev-gate-preflight.mjs +++ b/scripts/src/dev-gate-preflight.mjs @@ -70,7 +70,7 @@ const artifactBuildInputFiles = new Set([ "package.json", "package-lock.json", "npm-shrinkwrap.json", - "scripts/g14-artifact-publish.mjs", + "scripts/artifact-publish.mjs", "scripts/src/dev-artifact-services.mjs" ]); @@ -674,10 +674,10 @@ async function validateLocalContracts(reporter, contracts, optionalReports, targ function validateArtifactPublishReport(reporter, artifactReport, artifactIdentity, targetShortCommit, targetCommit, targetRef) { if (!artifactReport) { - reporter.check("g14-artifact-publish-report", "registry", "blocked", "No G14 artifact publish report is present."); + reporter.check("artifact-publish-report", "registry", "blocked", "No node artifact publish report is present."); reporter.block({ type: "runtime_blocker", - scope: "g14-artifact-publish", + scope: "artifact-publish", summary: `${defaultArtifactReportPath} is missing, so the preflight has no publish evidence for HWLAB runtime artifacts.`, nextTask: `Run the DEV artifact publish workflow and keep its temporary JSON at ${defaultArtifactReportPath}; do not commit report output.` }); @@ -721,7 +721,7 @@ function validateArtifactPublishReport(reporter, artifactReport, artifactIdentit const summary = sourceMatchesTarget ? "DEV artifact publish report covers all required enabled service IDs with immutable digests for the target commit, and records disabled service reasons." : `DEV artifact publish report covers artifact source ${artifactIdentity.artifactSource.shortCommitId}; target ${targetRef} ${targetShortCommit} has no artifact build input changes since that source.`; - reporter.check("g14-artifact-publish-report", "registry", "pass", summary, [artifactIdentity?.targetCoverage].filter(Boolean)); + reporter.check("artifact-publish-report", "registry", "pass", summary, [artifactIdentity?.targetCoverage].filter(Boolean)); return; } @@ -731,14 +731,14 @@ function validateArtifactPublishReport(reporter, artifactReport, artifactIdentit ? `source states resolved: ${sourcePresentCount} source-present, ${intentionallyDisabledCount} intentionally-disabled` : "source states are not fully resolved"; reporter.check( - "g14-artifact-publish-report", + "artifact-publish-report", "registry", "blocked", `DEV artifact publish report status is ${artifactPublish?.status ?? "missing"} with ${publishedCount}/${requiredServices.length || serviceCount} required services published for source ${artifactPublish?.sourceCommitId ?? artifactReport.commitId ?? "unknown"}; ${sourceSummary}.` ); reporter.block({ type: "runtime_blocker", - scope: "g14-artifact-publish", + scope: "artifact-publish", summary: `${defaultArtifactReportPath} does not prove all required HWLAB service artifacts for ${targetRef} ${targetShortCommit}; current status is ${artifactPublish?.status ?? "missing"} with ${publishedCount}/${requiredServices.length || serviceCount} required services published and ${sourceSummary}.`, nextTask: "Complete DEV artifact publishing for every enabled required HWLAB service at the artifact source commit, or prove the target commit has no artifact build input changes; keep disabled services marked not_published with reasons." }); diff --git a/scripts/src/runtime-lane.ts b/scripts/src/runtime-lane.ts index e1e58c85..79264f33 100644 --- a/scripts/src/runtime-lane.ts +++ b/scripts/src/runtime-lane.ts @@ -60,8 +60,8 @@ export type DeployConfig = { lanes?: Record; }; -const defaultNodeId = "G14"; -const defaultGitopsRoot = "deploy/gitops/g14"; +const defaultNodeId = "node"; +const defaultGitopsRoot = "deploy/gitops/node"; const defaultPublicHost = "74.48.78.17"; export function isRuntimeLane(lane: unknown): lane is string { @@ -143,8 +143,8 @@ export function applyRuntimeLaneDeployConfig(args: GitOpsRenderArgs, deploy: Dep const configuredEndpoint = laneConfig.endpoint ?? defaultEndpoint; const configuredWebEndpoint = laneConfig.publicEndpoints?.frontend ?? configuredEndpoint; const configuredRuntimeEndpoint = laneConfig.publicEndpoints?.api ?? configuredEndpoint; - const defaultBranch = defaults.defaultBranch ?? "G14"; - const defaultGitopsBranch = defaults.defaultGitopsBranch ?? "G14-gitops"; + const defaultBranch = defaults.defaultBranch ?? "node"; + const defaultGitopsBranch = defaults.defaultGitopsBranch ?? "node-gitops"; const defaultCatalogPath = defaults.defaultCatalogPath ?? "deploy/artifact-catalog.dev.json"; const defaultRuntimeEndpoint = defaults.defaultRuntimeEndpoint ?? "http://74.48.78.17:17667"; const defaultWebEndpoint = defaults.defaultWebEndpoint ?? "http://74.48.78.17:17666"; diff --git a/scripts/validate-runtime-boundary.mjs b/scripts/validate-runtime-boundary.mjs index 688e7e67..6234da77 100644 --- a/scripts/validate-runtime-boundary.mjs +++ b/scripts/validate-runtime-boundary.mjs @@ -249,10 +249,10 @@ function assertCloudApiImageRuntimeEntrypoints({ packageJson, artifactPublisher, assertIncludes(packageJson.scripts?.check, `bun build ${expected} --target=bun --packages=external`, `${label} package check`); assertIncludes(packageJson.scripts?.check, `bun ${expected} --check`, `${label} package runtime check`); } - for (const expected of ["scripts/g14-artifact-publish.mjs", "runtime-dev", "runtime-prod"]) { - assertIncludes(gitopsRenderSource, expected, `${label} G14 GitOps render source`); + for (const expected of ["scripts/artifact-publish.mjs", "runtime-dev", "runtime-prod"]) { + assertIncludes(gitopsRenderSource, expected, `${label} node GitOps render source`); } - for (const expected of ["name: \"prepare-source\"", "name: \"repo-reports-guard\"", "name: \"g14-contract-check\"", "name: \"codex-api-forwarder-check\""]) { + for (const expected of ["name: \"prepare-source\"", "name: \"repo-reports-guard\"", "name: \"node-contract-check\"", "name: \"codex-api-forwarder-check\""]) { assertIncludes(gitopsRenderSource, expected, `${label} Tekton primitive CI task`); } } @@ -446,7 +446,7 @@ assertDurableRuntimeRunbook(await readText("docs/reference/dev-runtime-boundary. assertCloudApiImageRuntimeEntrypoints({ packageJson: await readJSON("package.json"), artifactPublisher: await readText("scripts/artifact-publish.mjs"), - gitopsRenderSource: await readText("scripts/g14-gitops-render.mjs") + gitopsRenderSource: await readText("scripts/gitops-render.mjs") }); console.log(