# G14 GitOps CI/CD

G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD 必须只作用于 G14 k3s，不接管 D601 legacy 运行面，不使用 UniDesk Code Queue 作为调度器，也不把 UniDesk backend、provider-gateway 或 microservice proxy 当作 HWLAB runtime。

## 目标模型

- Source of truth：业务版本以 Git source commit 为唯一身份；镜像 tag、OCI labels、runtime annotation 和 Argo CD desired state 都必须记录同一个 source commit。
- 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:<shortCommit>`；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/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/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 决定。

## 门禁最小化与扩容治理

不要滑向不必要的复杂门禁是 HWLAB G14 CI/CD 和 `v0.2` 扩容的通用原则。架构迁移、分支扩容和运行面治理应优先靠固定边界、清晰命名、唯一真相源、标准入口和长期参考文档收敛；不要把每个设计约定、运行策略、观测项或回滚手册都做成新的 preflight、guard、gate 或报告生成器。

旧 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/node/runtime-v02`，Argo Application 必须指向 `v0.2` GitOps lane，公网入口只能是 `19666/19667`，`v0.2` source branch 不跟踪生成物，旧 DEV/D601/main 门禁不进入 `v0.2` 调用链。其他事项先写成决策表或 runbook；只有被证明无法靠上述边界和标准入口自然收敛时，才允许新增最小检查。

## 生成入口

`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/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_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_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 gitops:render -- --source-revision <sourceCommit>
```

人工 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

当前 G14 已完全删除 `CI.json` 和 `ci-json` step。下面的对比只保留为迁移理由：Tekton + Argo 是唯一发布控制面；repo-local 校验已经收敛为 render 内建的少量原语 task，而不是再保留一条 shell runner 路线。

| 维度 | 原生 k8s Tekton + Argo 配置面 | 已废弃的 `CI.json` runner 路线 |
| --- | --- | --- |
| 权威职责 | 构建、发布、GitOps promotion、Argo sync、runtime rollout | repo-local 静态合同、schema、focused smoke、预检命令 |
| 真相来源 | PipelineRun、TaskRun result、`G14-gitops` revision、Argo Application、live workload | 仓库内 `CI.json` 命令、runner 镜像、单个 `ci-json` TaskRun 日志 |
| 优点 | 控制面声明式、k8s 可观测、并行 fan-out、TaskRun result 可复用、CD 与 runtime 验收边界清楚 | 改 repo 内检查成本低、命令贴近源码、适合 source-only 合同和 focused preflight、无需每加一条检查就手写一套新 Tekton Task |
| 缺点 | render/generated/cluster 模板三层更复杂，控制面改动要等 reconciler apply 后才能验证，source 与 generated 漂移会制造噪声 | shell 包装、命令链和日志语义更脆弱；多个检查挤在一个 step 内时失败隔离差，天然不提供 GitOps desired state、Argo rollout 或 live runtime 证据 |
| 适用场景 | 需要发布真相、并发构建、artifact identity、GitOps promotion、CD rollout、live runtime 判定 | 需要 repo-local 源码检查、静态合同、快速 smoke、focused preflight，但不需要声明 runtime 期望状态 |
| 不该承担的职责 | 不应回退成 D601 legacy JS CD 或 target-side build | 不应被提升为独立 CD 路线，也不应用来替代 Argo sync、live workload ready 或公网 health |

选择规则：

- 只要问题涉及 release truth、rollout、runtime desired state、namespace workload 健康、public health 或 artifact provenance，就必须回到 Tekton + Argo 配置面。
- 只要问题仍停留在 repo-local 静态合同或 focused preflight，就把它实现成独立的原语 Tekton task；不值得长期保留的检查直接删除，而不是重新引入 `CI.json` runner。
- 不要再创建第二套 shell 命令发布面；如果一项检查开始依赖 rollout 顺序、TaskRun results、GitOps branch、Argo revision 或 live runtime，它就必须属于 Tekton/Argo 标准路径。

## Monorepo 组件计划与兼容 render

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/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/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/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 回退。

## 加速判定与当前瓶颈

G14 CI/CD 加速的第一判定标准不是 PipelineRun 总耗时单点变短，而是 monorepo 组件粒度的“少构建、少滚动、可并发”是否成立。

- 触发延迟：`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。
- 运行态验证：GitOps promote 成功只说明 `G14-gitops` 分支更新；最终通过必须看 Argo Application revision、sync 状态、目标 namespace Deployment/StatefulSet ready、公网 health。Argo 还停在旧 revision 时，优先做 `argocd.argoproj.io/refresh=hard` 刷新；不要把已经推送的 `G14-gitops` 分支误判成运行面已滚动。
- Argo health 判定：v0.2 不再生成 HWLAB 自有 agent worker Job template；AgentRun v0.1 作为外部共享执行基础设施接入。`hwlab-cli` 不创建镜像、Service 或 Job template，只在固定 repo 内短连接执行。真正发布验收仍以长驻 workload ready、公网 health 和失败 Pod 清单为准。

当前仍然慢的主要位置：

- `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`、`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

G14/v0.2 Code Agent 执行委托给 AgentRun `v0.1`。Per-profile config、credential、dynamic slug 和 validate 的唯一管理规格见 [spec-v02-provider-management.md](spec-v02-provider-management.md)；session dispatch 与 nested child `spawn` env-only 继承规则见 [agentrun-code-agent-dispatch.md](agentrun-code-agent-dispatch.md)。GitOps 只管理共享运行面、bridge/forwarder、NO_PROXY、SecretRef 约定和镜像；新增或调整普通 provider profile slug 不应为了静态 allowlist 修改 Cloud API/Web 服务代码，也不应因此单独触发 CI/CD。

当前 profile 示例不是静态枚举：`deepseek` 通过集群内 DeepSeek Responses bridge/Moon Bridge，`dsflash-go` 等动态 slug 由 AgentRun profile config/SecretRef 承接，`codex-api` 使用同 Pod loopback forwarder 直连 hyueapi，`minimax-m3` 由 AgentRun `backendProfile=minimax-m3` 承接。HWLAB 对外的 conversation/session/thread 合同不随 AgentRun backend 切换而改变；adapter 内部传给 AgentRun 的 `sessionRef.sessionId` 必须按 `backendProfile` 分域，`accessController` 持久化记录仍以 HWLAB 原始 session 归属为准，嵌套 `agentRun.sessionId` 保存 AgentRun scoped sessionRef。G14 cloud-api 的 `NO_PROXY/no_proxy` 必须包含 `hyueapi.com` 和 `.hyueapi.com`；`codex-api` 的 hyueapi upstream 由同 Pod loopback forwarder 直连，不能被 proxy 注入污染。

G14 `codex-api` 不得把 `http://172.26.26.227:17680/v1/responses` 作为默认 base URL；该地址只保留为 D601 legacy Code Queue runner 或历史 egress 对照线索。G14 上的 `codex-api` 应先进入同 Pod `127.0.0.1` loopback forwarder，再由 forwarder 直连 `hyueapi.com` / `.hyueapi.com`；这两个域名必须同时进入 `NO_PROXY` 和 `no_proxy`。DeepSeek profile 可以通过集群内 bridge/Moon Bridge 转换，`codex-api` 不能用 DeepSeek bridge 伪装通过，也不能因为默认 profile 切到 DeepSeek 而删除或覆盖 `codex-api` 的独立模型、base URL、auth 和最小闭环验证。

共享 bridge/forwarder/runtime 变更必须按 [Code Agent Chat Readiness Runbook](code-agent-chat-readiness.md) 的分层方法先做目标 pod 最小闭环。普通 AgentRun profile config/credential/validate 走 [spec-v02-provider-management.md](spec-v02-provider-management.md)，不通过 GitOps render 或服务发布试错。裸 HTTP/SSE 请求通过只能证明 upstream、认证和模型可用；没有 AgentRun command result `completed` + 非空 reply 前，不得把 Workbench 状态标成 DEV-LIVE reply pass。

Codex app-server 当前要求 provider `wire_api="responses"`，不得把 DeepSeek profile 切到旧 `chat` wire API。DeepSeek profile 的真实 Responses 转换层固定使用 Moon Bridge；不要在 HWLAB 里手写完整 Responses-to-Chat/Anthropic 转换器，因为这会破坏 Moon Bridge 对 prompt cache、tool-result 顺序和模型目录的成熟处理。Codex 发往 `/v1/responses` 的请求体可能带 `Content-Encoding: zstd`，而 Moon Bridge 不负责解压 Codex zstd body。因此 GitOps 中的 `hwlab-deepseek-proxy` Pod 必须包含 repo-owned `hwlab-deepseek-responses-bridge` sidecar：Service 端口 4000 指向 bridge，bridge 只做 zstd request body 解压、删除 `Content-Encoding`/重写 `Content-Length`、丢弃非 `function` tool 类型和 Codex-style `GET /v1/models` 目录适配，再把请求转发给 4001 的 Moon Bridge。DeepSeek API 只接受 `function` tools，bridge 必须在转发前丢弃 Codex Responses 请求里的非 `function` tool 类型（例如 `web_search`、`image_generation`），但不得移除 shell/apply-patch 等 function tools。模型、cache、tool 调用顺序、密钥和真实推理仍由 Moon Bridge 管理；bridge 不新增业务 gate，也不得把兼容失败伪装成 SOURCE/legacy blocker。

DeepSeek proxy manifest 是 GitOps desired state 的一部分，DEV/PROD 分别生成在 `runtime-dev/deepseek-proxy.yaml` 和 `runtime-prod/deepseek-proxy.yaml`。Moon Bridge 镜像由 `deploy/moonbridge/Dockerfile` 从上游 `ZhiYi-R/moon-bridge` 固定 commit 构建，默认镜像为 G14 本地 registry 的 `moonbridge:<上游短 commit>`；GitHub、Google 或 Docker base image 下载必须优先使用 G14 节点本地 proxy。DeepSeek key 仍通过 `hwlab-code-agent-provider/openai-api-key` Secret 注入到 init container 并写入 Pod 内 `emptyDir` 配置文件；ConfigMap、文档、trace、health、issue 和日志不得打印 Secret 值。

## Polling 触发

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-<short12>`。

`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-<short12>`。该 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/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/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 失败。

下载阶段排障命令：

```sh
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-ci logs pod/<pipelinerun-task-pod> --all-containers --tail=400 \
  | grep -E 'dependency-(proxy|curl|download)'
```

如果 probe 显示 `proxy-env-missing`、`proxy-connect-failed`、`timeout` 或速度长期接近 0，应先按 `/root/docs/vpn-proxy-ops.md` 检查 v2rayN/Hysteria，再重跑 polling。若 probe 正常但后续校验失败，按对应 Tekton task 日志和业务检查排障。

手动 render/apply 只允许作为 bootstrap 或 reconciler 故障抢修；正常路径必须由 `hwlab-node-control-plane-reconciler` 自动完成 CI 控制面 manifest 更新。

修改 `scripts/gitops-render.mjs`、Tekton Pipeline、RBAC、poller 或 reconciler 这类 CI 控制面后，不能立刻用旧集群 PipelineRun 作为新模板验证。必须先确认 `hwlab-node-control-plane-reconciler` 已完成并且集群内 Pipeline manifest 包含新字段，再触发手动 poller 或等待下一轮 source commit；否则 PipelineRun 会在旧 Pipeline 下创建，容易把已经修复的 sidecar、proxy 或 TaskRun 行为误判为仍然失败。

## 集群资源

- `hwlab-ci`：registry、Tekton runner RBAC、Polling CronJob、Pipeline/PipelineRun 所在 namespace。
- `hwlab-ci/hwlab-node-control-plane-reconciler`：自动 render/apply G14 CI 控制面 manifest 的 CronJob。
- `argocd`：Argo CD 控制面和 `hwlab-node-dev` / `hwlab-node-prod` Application 所在 namespace。
- `hwlab-dev`：HWLAB DEV runtime namespace，由 Argo CD 应用 `deploy/gitops/node/runtime-dev`。
- `hwlab-prod`：HWLAB PROD runtime namespace，由 Argo CD 应用 `deploy/gitops/node/runtime-prod`；通过 `hwlab-node-prod-frpc` 映射到 `18666/18667`，正式验收必须同时检查 Argo sync、Deployment ready、FRP public health 与 source commit。
- `hwlab-v02`：HWLAB `v0.2` runtime namespace，只能由 `v0.2` 专属 Argo CD Application 和 GitOps runtime path 管理；规划通过独立 frpc 映射到 `19666/19667`。创建 `hwlab-v02` 不得删除、重命名或改写 `hwlab-dev`/`hwlab-prod`。

G14 registry 由 Kubernetes Deployment `hwlab-ci/hwlab-registry` 承载，使用 host network 暴露 `127.0.0.1:5000` 给 k3s/containerd 和 host-network Tekton build pod。旧 host Docker registry 不能与该 Deployment 同时占用 5000 端口。

G14 k3s/containerd 的 Pod 镜像拉取也必须长期使用 G14 本机代理。主机配置记录在 `/root/docs/kubernetes-ops.md`，当前 systemd env 文件是 `/etc/systemd/system/k3s.service.env`。修改代理后需要 `systemctl daemon-reload && systemctl restart k3s`，并确认 `k3s-server` 进程环境中存在 `HTTP_PROXY` / `NO_PROXY`。

## 凭证边界

HWLAB repo 是私有仓库时，Tekton 和 Argo CD 需要各自的 Git SSH Secret。Secret 只能从 G14 本机已有 SSH key 创建，不能写入 Git，不能打印 key 内容。

Tekton 约定 Secret 名称：`hwlab-ci/hwlab-git-ssh`。

Argo CD 约定 repository Secret：`argocd/hwlab-git-ssh`，并带 label `argocd.argoproj.io/secret-type=repository`。

## D601 边界

G14 GitOps manifests 不包含 D601 kubeconfig、D601 node guard、D601 FRP 公网入口或 UniDesk Code Queue 调度入口。D601 仍由既有生产路径维护；G14 GitOps 的安装、PipelineRun、Argo sync 和 registry 操作都不能对 D601 执行 kubectl、docker 或流量切换动作。

## 真相源与常见误判

G14 PR、CI、CD 的判断应按以下顺序收敛真相，越靠前越接近最终运行面：

1. live runtime：目标 namespace 的 Deployment/StatefulSet template、Pod ready、`describe`、容器日志和公网 health。
2. Argo desired state：`hwlab-node-dev` / `hwlab-node-prod` 的 Application revision、sync、health 和实际 runtime path；`v0.2` 接入后还要看 `hwlab-node-v02` 或等价专属 Application 是否指向 `v0.2` GitOps lane 和 `hwlab-v02`。
3. Tekton 执行证据：branch-poller 日志、PipelineRun、TaskRun results、`gitops-promote` 终态。
4. 干净 source workspace：`origin/G14` 当前内容，以及 `npm run gitops:render -- --no-write` 和 planner 输出。
5. 对照线索：旧 commit 记忆、坏 worktree、D601 legacy 路径、脚本旧默认值，只能当线索，不能当真相。

常见误判与纠正：

- 不要把 `/root/hwlab` 当前 checkout、任意 `/tmp` worktree 或带 conflict marker 的目录当 source truth；只有跟踪 `origin/G14` 的干净 worktree 才能作为当前发布依据。
- 不要按“某个预期 commit 是否出现在 ancestry 中”判断功能是否已经合入；当前 `origin/G14` 实际内容比历史 commit 轨迹更重要。
- 改了 source 但没有触发 Tekton promotion 时，`G14-gitops` 仍停在旧 generated state 是正常现象，不等于 Tekton 或 Argo 本身坏掉。
- 不要把 `build-*` TaskRun 名称直接当作镜像重建证据；reuse-only 变更也会扇出同名 TaskRun，必须看 Tekton result 的 `status=reused` 与 `build-backend=reused-catalog`。
- 不要把 `G14-gitops` 分支已更新误判成 DEV 或 PROD 已滚动；只有 Argo Application revision、目标 workload ready 和 live manifest 生效，才算 CD 真实通过。
- 不要把 health payload 中的镜像 commit 当成 runtime manifest commit；runtime-only 修复可能复用旧镜像，只改变 probe、env、annotation 或 sidecar 行为。
- sidecar 监听 `127.0.0.1` 时，Pod-IP `httpGet` probe 失败不等于 sidecar 自身 crash；必须同时对照 sidecar listen 日志和 probe target 语义。
- 真实 live smoke 超时要先排除脚本入口和 timeout 误报；健康的 Codex stdio 冷启动首 token 可能需要数十秒，10 秒级 transport timeout 不能直接判服务故障。

- 不要重新引入把多条检查挤成顶层 shell 链的 runner。旧 `CI.json` 的 `a && b && c` 一旦直接内联到顶层 shell，就会出现前半失败但 TaskRun 继续成功的假绿；原语化 CI 的原则是每个检查保持独立 task/result，失败语义直接由 Tekton 负责。

## PR -> CI -> CD 最短零误判 SOP

1. 工作区与路由预检

```sh
bun scripts/cli.ts ssh G14:/root/hwlab shell 'git status --short --branch && git remote -v | sed -n "1,4p"'
```

- `/root/hwlab` 是固定 source workspace 和 worktree 管理入口；完成预检后，在 `/root/hwlab/.worktree/<task>` 从最新 `origin/G14` 创建任务专属 worktree，再开始本轮代码、文档、测试和提交修改。
- 只有独立 worktree 跟踪正确 base 且 `git status` 只包含本任务文件时才继续；不要在 `/root/hwlab` 根目录直接堆叠并行开发改动，也不要复用其他任务遗留 worktree。
- k3s 只走 `G14:k3s`；不要混用 D601 kubeconfig、master server 执行面或旧 SSH route 语法。

2. Source / PR 预检

```sh
npm run gitops:render -- --no-write
node scripts/ci-plan.mjs --base-ref origin/G14 --target-ref HEAD --pretty
```

- 先看当前内容和长期参考，不按旧 commit 记忆判断“功能是否已合入”。
- GitOps、manifest、poller/reconciler、provider profile 相关改动先跑 render no-write；planner 用于判断本轮是 docs-only、reuse-only 还是需要真实构建。不要在 source 分支对 ignored generated output 跑 drift check。
- provider/profile 变更在进入 PR 或 CI 之前，先按 `code-agent-chat-readiness.md` 做目标 Pod 最小���环；不要把完整 CI/CD 当成 transport 试错工具。

3. 合并或推送到 `G14`

- `G14` 是 poller 的唯一 source branch。只有 source 侧检查通过后，才进入 merge 或 push。
- push 被 fast-forward 拒绝时，先 `fetch` 和 `rebase` 到最新 `origin/G14`；不要为了抢跑 poller 强推覆盖别人的 GitOps promote 提交。

4. 确认 CI 已接单

- 先看最新 branch-poller job 或日志，确认它是否识别到新的 `G14` HEAD。
- 再看 `hwlab-ci` 里的 PipelineRun 是否出现 `hwlab-node-ci-poll-<short12>`。poller 还没创建 PipelineRun 时，不要先把问题归类为 Tekton 故障。
- 如果本轮改的是 poller、Pipeline、RBAC、reconciler 或 render 模板，先确认 `hwlab-node-control-plane-reconciler` 已经把新控制面 apply 进去，再用新 commit 验证。

5. 确认 CI 真实通过

- 最低通过条件是：`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。

6. 确认 CD 真实通过

- 先确认 `G14-gitops` 头 revision 已更新，再看 Argo Application 当前 revision 是否追上该 GitOps revision。
- 然后检查目标 namespace 的 Deployment、StatefulSet、ReplicaSet、Pod ready 和失败事件。
- runtime-only 修复要直接检查 live Deployment template 是否带上新的 probe、env 或 annotation；不要只盯公网 health 里的镜像 commit。

7. 做最终运行态验证

- 先看公网 `/health/live`，再跑 focused live smoke；业务 smoke 必须在目标运行面已经 Healthy 后进行。
- Code Agent 对话链路用 `node scripts/code-agent-chat-smoke.mjs --live` 或显式 `--url http://74.48.78.17:17667/ --timeout-ms 45000`；通过标准是“真实 DEV 路由 + `completed` + 非空 assistant reply”，不是单纯 HTTP 200、非 JSON chunk 或 10 秒内首包。
