# Code Queue Deploy

`bun scripts/cli.ts codex deploy <commitId>` 是旧兼容入口，现已禁用。原因是它会通过 backend-core `host.ssh` 维护通道直连 D601 部署 Code Queue，把维护入口扩张成第二套部署系统。

P0 控制面说明：D601 生产/DEV Kubernetes 入口只允许原生 k3s；Docker Desktop Kubernetes 已停用且不得重新启用。裸 `kubectl` 仍可能命中残留 `docker-desktop` kubeconfig，不能作为 Code Queue 恢复、部署或回滚证据。详细证据和治理计划见 [pikasTech/unidesk#138](https://github.com/pikasTech/unidesk/issues/138)，2026-05-23 恢复过程见 [pikasTech/unidesk#118](https://github.com/pikasTech/unidesk/issues/118)。

Code Queue 后续正式生产部署必须走一条受控 CD 路径并单独审查；当前阶段只提供 dev artifact consumer 的 dry-run/source/contract readiness。`deploy apply --env dev --service code-queue --dry-run` 或 `artifact-registry deploy-service --env dev --service code-queue --dry-run` 可以计划消费 D601 registry 中的 `unidesk/code-queue:<commit>`，但输出必须显示 `selfBootstrapGuard`、`requiresSupervisorApproval`、pull-only/no-build、image tag/digest provenance，并说明只会在获授权后更新 `unidesk-dev` Code Queue execution slice。非 dry-run DEV apply 必须由 human operator/supervisor 在 Code Queue 任务之外授权；当前 Code Queue runner 不能自己上线 Code Queue。`--env prod --service code-queue` 必须明确 unsupported，不能执行生产 artifact deploy、rollout 或 manifest 变更。persistent dev apply 的完整服务范围见 `docs/reference/dev-environment.md`，Code Queue temporary smoke 仍通过 `ci run-dev-e2e`，规则见 `docs/reference/dev-ci-runner.md`。

The reproducible dry-run delivery path is:

1. CI publishes `127.0.0.1:5000/unidesk/code-queue:<full-sha>` from pushed Git and reports the immutable digest/label summary.
2. A runner or operator runs `bun scripts/cli.ts deploy plan --env dev --service code-queue` or `bun scripts/cli.ts deploy apply --env dev --service code-queue --commit <full-sha> --dry-run`; this is non-mutating evidence only.
3. A human operator reviews the dry-run and, if accepted, separately authorizes DEV apply. That apply may touch only `unidesk-dev` Code Queue execution objects.
4. PROD remains unsupported. A dry-run or plan may document the gap, but no Code Queue runner may perform prod apply, rollout restart, scheduler/runner rebuild, interrupt or cancel as part of delivering Code Queue itself.

This contract is checked by `bun scripts/code-queue-cicd-dry-run-contract-test.ts`. The test is intentionally lightweight and does not run full e2e, Playwright, Code Queue deployment, or task interruption flows.

## Command

```bash
bun scripts/cli.ts codex deploy <commitId>
```

该命令必须返回结构化错误，说明维护通道直连 D601 部署已禁用；不得再创建后台部署 job。`--skip-build` 不再支持。

## Pipeline

历史部署 job 曾固定为以下步骤；它们现在只能作为未来受控 Code Queue CD 的目标行为，不能由 `codex deploy` 或维护通道直连触发：

1. 对 Code Queue 部署先确保 PostgreSQL 中存在 `unidesk_deploy_ssh_identities(id='github.com')`，该记录保存 GitHub deploy SSH identity 的 private key、public key fingerprint 和 github.com `known_hosts` 行。未来受控 CD 不得把 secret 写入 task payload、deploy 日志、Docker image 或 Kubernetes Secret。
2. 在 D601 的 deploy cache 中通过本机 provider-gateway WS egress proxy 执行 `git fetch` remote，并用 `git archive <commitId>` 导出 tracked files 到一次性 export 目录；不得让 D601 直连 GitHub，也不得临时创建 SSH SOCKS、公网 master proxy 或 backend-core/provider-ingress fallback。
3. 用 `rsync --delete` 同步导出的 repo 到 `/home/ubuntu/cq-deploy`，保留 `.state/`、`logs/`、`.git/`、`node_modules/` 和 `dist/`。同步完成后、任何 scheduler/read/write rollout 前，必须运行 Code Queue hostPath source guard：`bun scripts/code-queue-source-guard.ts --root /home/ubuntu/cq-deploy` 或等价 `bun scripts/cli.ts deploy guard code-queue-source --root /home/ubuntu/cq-deploy`。该 guard 至少扫描 `src/components/microservices/code-queue/src/**/*.ts` 的相对 `import` / `export ... from` / `import(...)`，确认目标文件存在；失败必须返回结构化 `degradedReason=missing-relative-import-target` 或 `source-root-missing`，并阻止 rollout。
4. 在 D601 用目标 Docker daemon 的本地 BuildKit builder 构建 `unidesk-code-queue:d601`，复用 D601 上已有基础镜像、inline cache 和 Code Queue build-base；provider-gateway WS egress 是唯一允许的构建代理通道，只作为本次 build 的环境变量与 build-arg 注入，并配合本次 build 的 `--network host` 让 RUN 阶段访问 D601 宿主 loopback proxy，不能污染 D601 宿主 Docker/HTTP proxy 配置，不能新建 SSH SOCKS、公网 master proxy 或直连 fallback。
5. `docker save` 镜像并导入原生 k3s containerd：`docker save unidesk-code-queue:d601 | sudo ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images import -`。导入后必须用同一个 containerd socket 验证 `unidesk-code-queue:d601` tag 存在；D601 Docker daemon 的本地 tag 不是 k3s containerd 的 source of truth。
6. `KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply -f src/components/microservices/k3sctl-adapter/k3s/code-queue.k8s.yaml`，其中包含 Code Queue、`d601-provider-egress-proxy` 和 `d601-tcp-egress-gateway`。apply 后必须验证 `code-queue`、`code-queue-read`、`code-queue-write`、`d601-provider-egress-proxy`、`d601-tcp-egress-gateway` 这些 Deployment 的 container image 都是 `unidesk-code-queue:d601`，不能让 kubelet 回退到 Docker Hub 或其他外部 registry。
7. 将解析后的 40 位 remote commit 写入 `deployment/code-queue` 的 `CODE_QUEUE_DEPLOY_COMMIT` / `CODE_QUEUE_DEPLOY_REQUESTED_COMMIT`，并记录到 Deployment annotation。
8. `KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n unidesk rollout restart deployment/d601-tcp-egress-gateway deployment/code-queue` 并等待 rollout 完成。
9. 通过 backend-core 的真实微服务代理读取 Code Queue `/health`，强制校验 `deploy.commit` 等于本次解析出的 remote commit；如果健康的是旧服务或旧 Pod，job 必须失败。

## HostPath Source Guard

生产 `unidesk` namespace 的 Code Queue scheduler/read/write 仍处在 hostPath source 过渡模式：Pod 内 `/app` 和 `/root/unidesk` 都挂载 D601 host 的 `/home/ubuntu/cq-deploy`。这意味着镜像内已经包含的源码会被 hostPath 覆盖，镜像构建成功不能证明运行时源码完整。已知失效模式是 hostPath repo 部分同步：`src/components/microservices/code-queue/src/index.ts` 已包含 `./runtime-preflight` 导入，但 `/home/ubuntu/cq-deploy/src/components/microservices/code-queue/src/runtime-preflight.ts` 缺失，scheduler 启动即因 Bun 模块解析失败进入 CrashLoopBackOff。

`/home/ubuntu/cq-deploy` 是生产 Code Queue/k3s 共享 hostPath 部署仓库，也是当前 `code-queue.k8s.yaml` 中 repo、state 和 logs hostPath 的根。`/home/ubuntu/unidesk-code-queue-deploy` 是历史/开发工作区口径，不是生产 Pod 的 `/app` source mount；生产 rollout 判断必须以 `/home/ubuntu/cq-deploy` 为准。若两者需要保留软链接或迁移关系，必须先在 manifest、config 和文档中显式统一，不能让部署脚本从一个路径同步而 Pod 从另一个路径启动。

人工排查 D601 生产 k3s 状态时必须显式使用原生 k3s kubeconfig：

```bash
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n unidesk get deploy,svc,pod,endpoints
```

D601 默认 `kubectl` context 可能指向 Docker Desktop、kind 或其他本地集群，不能作为 UniDesk 原生 k3s scheduler 是否 ready、Service 是否有 endpoint、Pod 是否 CrashLoop 的证据。

hostPath source 模式只允许作为过渡态。长期生产 Code Queue 应收敛到 commit-pinned artifact/image CD：以 pushed commit 为版本真相，CI 产出带 source commit label 的镜像，CD 只消费 commit-pinned image、更新 manifest/env/annotation 并验证 live health commit；生产 Pod 不应再用可部分同步的 hostPath source 覆盖镜像内应用源码。

## Observability

未来受控 Code Queue CD 实现后，部署触发本身不应阻塞等待完成。返回 JSON 中必须包含 run id、status command 或等价查询入口；后台日志必须有界可查，失败时能显示最后日志尾部。

部署 run 到 `succeeded` 时，必须已经完成 live commit 验证。需要人工复核时可用以下命令确认 `deploy.commit`：

```bash
bun scripts/cli.ts microservice health code-queue
bun scripts/cli.ts microservice proxy code-queue '/api/tasks/overview?limit=5&transcriptLimit=1&compact=1&afterSeq=0&preferId='
```

D601 原生 k3s 的人工诊断必须显式使用 host kubeconfig：`KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n unidesk ...`。D601 上的默认 `kubectl` context 可能指向 Docker Desktop 或其他本地集群，不能作为 UniDesk Code Queue 部署是否 ready 的证据。部署后直接查 k3s 时，至少确认 `deployment/code-queue`、`code-queue-read`、`code-queue-write`、`d601-provider-egress-proxy` 和 `d601-tcp-egress-gateway` ready，两个 egress Service 的 Endpoints 非空，Pod 环境中的 `UNIDESK_DEPLOY_REQUESTED_COMMIT`/`CODE_QUEUE_DEPLOY_REQUESTED_COMMIT` 等于期望 commit，并且 scheduler `/health` 暴露 PostgreSQL ready、`storage.lastError=null`、egress proxy connected、stale active/retry_wait reconcile 状态和 MiniMax `NO_PROXY` 例外。

`bun scripts/cli.ts microservice diagnostics code-queue` 必须作为 Code Queue 的长期诊断入口。它必须显式报告 `d601-provider-egress-proxy` 和 `d601-tcp-egress-gateway` 的 Deployment available、Endpoint non-empty、scheduler 到 PostgreSQL route、以及 stale active/retry_wait reconcile；任何一项失败都应让诊断结果 degraded/failing，而不是只显示 scheduler HTTP healthy。

## Pull Request Delivery

Code Queue worker 可以在任务明确要求审查型交付时创建 Pull Request。PR 交付不是默认出口；默认集成仍遵循项目当前 master-only 规则，直到具体任务或指挥官要求改为 PR。PR 型任务必须从最新目标线创建短生命周期分支，报告源分支、目标分支、PR URL、关联 issue、验证证据和未完成风险；分支命名应使用 `probe/`、`code-queue/` 或其他明确任务前缀，禁止把隐藏分支当成长期交付状态。

Code Queue runtime 提供 `/api/runtime-preflight` 作为 PR 能力探测入口；CLI 稳定入口是 `bun scripts/cli.ts codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N]`。默认请求只检查本地工具、凭证可见性、Git worktree、HOME、known_hosts、agent port 和 proxy DNS；`--remote` 会增加 GitHub 网络、issue API、SSH/HTTPS `git ls-remote`、GitHub SSH、`gh auth status`、`gh repo view`、`gh issue read/view` 和只读 `gh pr list` 探测；`--push-dry-run` 会额外执行 `git push --dry-run`，验证远端写权限但不创建分支；`--pr-create-dry-run` 会在 runner 内生成受控 PR body 并执行 `scripts/cli.ts gh pr create --dry-run`，只证明 PR body guard 和命令形态可用，不 POST GitHub。探测输出只报告 `GH_TOKEN`/`GITHUB_TOKEN` 是否存在，不得输出 token 内容，并通过 `prCapabilityContract` 明确 token source、target branch、expected PR handoff、dry-run 不写远端和 merge unsupported 边界。缺少 runner env token 时，`authBroker.source="broker/auth-broker-needed"` 是标准结构化证据；系统 `gh` binary 缺失必须与 UniDesk REST `bun scripts/cli.ts gh` 可用性分开报告。backend-core 的稳定 `code-queue` proxy 必须把 `/api/runtime-preflight` 路由到 D601 scheduler，而不是主 server `code-queue-mgr`，因为 token 和 PR runner 能力属于执行面环境。

PR 创建依赖以下最小运行时能力：

- runtime image 必须包含 `git`、`gh`、`jq`、`ca-certificates`、`curl` 和 `openssh-client`；D601 provider dev container 准备脚本也必须补齐这些工具。
- GitHub 凭证只能通过运行时 secret、环境变量或已有 SSH identity 注入。优先使用 `GH_TOKEN`，兼容 `GITHUB_TOKEN`；不得把 token 写入 Git remote、任务 prompt、日志、镜像或仓库文件。
- `CODE_QUEUE_REMOTE_CODEX_ENV_KEYS` 默认允许把 `GH_TOKEN`/`GITHUB_TOKEN` 以及 `GH_HOST`、`GITHUB_API_URL`、`GH_REPO` 从 scheduler 传给 provider dev container，供隔离执行环境内的 `gh repo view`、`gh issue read/view`、`gh pr list` 和 `gh pr create` 使用。
- DEV 的持久注入路径复用 `unidesk-dev-runtime-secrets`：把 `GH_TOKEN` 或 `GITHUB_TOKEN` 写入该 Secret 后，通过受控 `deploy apply --env dev --service code-queue` 或等价 dev-only rollout 重启 Code Queue scheduler/read/write。default/prod runner 使用生产 `unidesk` namespace 的 `code-queue-env` Secret；仅修复 dev Secret 不会覆盖 default queue scheduler。不要在 PROD Code Queue 上直接 patch Secret 或 rollout，生产 token 覆盖需要显式 release/runtime 授权。

安全验证顺序固定为先只读、再 dry-run、最后才创建真实 PR。优先执行 `/api/runtime-preflight`、`/api/runtime-preflight?remote=1`、带 `pushDryRun=1` 的 probe ref，以及带 `prCreateDryRun=1` 的 PR body/create dry-run guard；只有工具、token、网络、push dry-run 和 PR body guard 都满足且任务明确允许时，才创建 draft PR 或普通 PR。若创建真实 probe PR，最终报告必须记录 URL 并说明保留或关闭状态。

preflight failure 必须定位到具体缺失项。已知必须区分的状态包括：`auth-missing`（`GH_TOKEN`/`GITHUB_TOKEN` 未注入，且需要 env token 或 auth-broker）、`control-plane-missing`（远程 frontend/backend-core/code-queue proxy 不可达）、`git-remote-gap`（`git ls-remote` 或 `git push --dry-run` 失败）、系统 `gh`/`hub` 缺失、`GH_HOST`/`GITHUB_API_URL`/`GH_REPO` 未注入、`/root/.config/gh/hosts.yml` 与 `/root/.git-credentials` 缺失、SSH Git 可读但 GitHub issue/API 匿名访问返回 404、匿名 HTTPS Git 不可用、以及 `HTTP_PROXY`/`HTTPS_PROXY` 指向的 egress proxy 在 runner 内不可解析。发现这类缺口时，worker 应报告 preflight error 和阻塞项，不得强行创建 PR。

## Boundaries

Code Queue 由 D601 k3s/k8s 控制面代管，不再通过 `server rebuild`、`codex deploy`、维护通道直连 D601 或手工 `docker compose up` 作为正式部署路径。Code Queue 部署必须在自身正在执行任务时仍可运行；服务重启后由 restart-recovery 恢复任务状态，不能等待当前 Code Queue task 退出后再部署。

## TCP Egress Gateway

D601 k3s Pod 不能依赖主 server 公开 `15432`/`4255` 作为直连 TCP 入口；PostgreSQL 也不会自动使用 HTTP proxy 环境变量。`code-queue.k8s.yaml` 因此提供一个通用 `d601-tcp-egress-gateway`：

- Pod 内业务只访问集群内 Service：`d601-tcp-egress-gateway.unidesk.svc.cluster.local:15432` 和 `:4255`。
- gateway 通过 D601 provider-gateway egress proxy 的 HTTP CONNECT 转发到底层 TCP 目标。
- 新增 TCP 依赖时只扩展 `TCP_EGRESS_ROUTES`，不要在业务容器里散落公网直连地址或 ad hoc 隧道脚本。
