# UniDesk User Services Reference

本文中的 `mdtodo` 是遗留微服务与兼容运行面名；任务管理与迁移统一遵循 `$unidesk-tasktree`。

UniDesk 用户服务是挂载到 UniDesk 核心服务上的、面向用户使用的非核心业务服务；底层配置、API、CLI 和 E2E check 名称仍保留 `microservice` 兼容命名。UniDesk 核心服务（frontend、backend-core、database、provider-gateway、主 server 控制入口）不得依赖某个用户服务存在；缺少部分或全部用户服务时，核心仍必须能启动、运行和完成基础运维。

用户服务可运行在计算节点 Docker、主 server Docker 或受控 k8s 中。外部业务服务只登记仓库引用和运行映射；UniDesk 自有服务可以在 `src/components/microservices/` 维护源码，并由对应 YAML 声明运行、Secret、持久化和交付责任。

## Boundary

- 用户服务后端端口默认只绑定计算节点本机地址，例如 `127.0.0.1:<port>`，不得直接暴露公网。
- 浏览器只访问 UniDesk frontend；frontend 通过同源 `/api/microservices/*` 代理到 backend-core。`deployment.mode=unidesk-direct` 的用户服务由 backend-core 通过目标 provider-gateway 的 `microservice.http` 能力访问计算节点本机后端；`deployment.mode=internal-sidecar` 的主 server 内置控制面服务由 backend-core 直接访问同一 Compose 网络内的显式服务名；`deployment.mode=k3sctl-managed` 的用户服务只允许经 `k3sctl-adapter` 微服务进入 k3s 标准服务路由。NC01 host-k8s 用户服务由 `config/unidesk-host-k8s.yaml` 渲染到 backend-core catalog，并由 backend-core 直接访问同 namespace ClusterIP Service，不经过 provider-gateway。
- backend-core REST API、database 和计算节点用户服务后端都不得新增公网端口；公网入口仅限 production frontend、dev frontend proxy 和 provider ingress。dev frontend proxy 的唯一权威规则见 `docs/reference/dev-environment.md`。
- `microservice.http` 只允许 provider-gateway 访问 `http://127.0.0.1`、`http://localhost`、`http://host.docker.internal` 这类节点本地地址，或明确登记为同一私有 Docker network 内的服务名；主 server 内置用户服务可使用同一 Compose 网络内的显式服务名，例如 `oa-event-flow:4255`。k3s 代管服务不得把业务容器地址登记成 provider-gateway 直连目标。backend-core 还必须用 `allowedPathPrefixes` 和 `allowedMethods` 同时限制可代理路径和 HTTP 方法。

## Config Contract

`config.json.microservices` 是计算节点和主 server Compose 用户服务的登记来源。NC01 host-k8s 用户服务以 `config/unidesk-host-k8s.yaml` 为真相，renderer 生成等价 backend-core catalog；不得同时让两个来源拥有同一运行对象。`config.json` 条目通常包含：

- `id`、`name`、`providerId` 和 `description`，用于 CLI、backend-core 和 frontend 统一识别。
- `repository.url` 与 `repository.commitId`，用于记录外部业务代码的权威来源；UniDesk 自有服务可以 vendoring 到 `src/components/microservices/`。
- `repository.dockerfile`、`repository.composeFile`、`repository.composeService` 和 `repository.containerName`，用于说明部署应复用业务仓库自身维护的 Dockerfile/docker-compose。
- `backend.nodeBaseUrl`、`backend.nodeBindHost`、`backend.nodePort`、`backend.proxyMode`、`backend.public=false`、`backend.frontendOnly=true`、`backend.allowedMethods`、`backend.allowedPathPrefixes` 和 `backend.healthPath`，用于定义计算节点端口到 UniDesk frontend-only 代理的映射。
- `deployment.mode`，用于明确部署责任边界；`unidesk-direct` 表示 UniDesk 直接登记和探测目标 provider 上的容器，`internal-sidecar` 表示主 server Compose 内的轻量控制面/基础设施服务，`k3sctl-managed` 表示 UniDesk 只登记逻辑服务并经 `deployment.adapterServiceId` 指向的 `k3sctl-adapter` 访问，代管条目还必须写明 `k3sServiceId`、`namespace`、`expectedNodeIds` 和当前 `activeNodeId`。
- `development.providerId`、`development.sshPassthrough=true` 和 `development.worktreePath`，用于说明开发调试入口必须在计算节点上通过 UniDesk SSH 透传完成。
- `frontend.route` 和 `frontend.integrated=true`，用于说明该业务前端已经整合到 UniDesk React 控制台，而不是继续公开业务自身前端。
- `HWLAB` 是前端内置的外部静态入口，固定目标为 `http://74.48.78.17:6666`；它不属于 `config.json` 的用户服务登记，不进入 health/proxy/runtime 采集，也不需要 provider-gateway、backend-core 或 microservice HTTP 代理参与。

## Runtime Configuration And Persistence Contract

每个长期运行的用户服务都必须把“登记配置、运行面恢复、状态持久化、真实 health”作为同一交付项处理，不能只把容器拉起后登记到目录：

- 配置来源：计算节点/Compose 服务由 `config.json` 登记代理与前端入口；YAML-first 服务由对应 YAML 声明运行面和 Secret sourceRef。不得把 token、登录态、运行日志或节点私钥提交到 UniDesk 仓库。
- 容器恢复：长驻业务后端必须使用业务仓库自己的 Compose 或显式 `docker run` 固化容器名、镜像、端口、环境变量、healthcheck、`restart` policy 和持久化挂载；`repository.composeService`、`repository.containerName` 与实际 Docker 容器必须一致，便于 `microservice list/status`、Docker 状态页和自动恢复脚本互相校验。
- 重启策略：provider-gateway 必须使用 `restart: always`；普通用户服务至少使用 `restart: unless-stopped`，如果服务是节点核心依赖或无人值守入口，可以提升为 `always`，但必须说明手动停止后的恢复语义。仅靠 restart policy 只能覆盖 Docker daemon 已重新启动后的容器恢复，不能替代 Windows 登录任务、systemd、Docker Desktop 自启动或 WSL keepalive。
- 持久化边界：任务、队列、账号会话、订阅、token、未读状态和业务数据不得只存在容器 writable layer 或浏览器本地存储；应写入主 PostgreSQL、业务数据库、Docker named volume 或节点 host bind mount。`.state/` 默认只能放可重建缓存、日志、归档和服务自有小状态；如果某服务把 `.state/` 作为权威状态目录，必须在本节或服务小节明确字段、备份和重启恢复方式。
- 自恢复入口：计算节点上的服务应有节点本地的幂等 autorecover 脚本，在 Docker ready 后用 `docker inspect`、退出码和业务 HTTP readiness 判断是否需要 `docker compose -f <compose> up -d --force-recreate <service...>`；该脚本必须从节点本地计划任务、systemd、Docker Desktop 自启动后的 WSL keepalive 或等价守护触发，不依赖正在被恢复的 UniDesk provider-gateway SSH 会话。
- 真实 health：`/health` 必须证明业务“可用”而不是“进程存在”。需要登录、外部连接、GPU、镜像、数据库或后台 worker 的服务，必须在 health body 中暴露这些依赖状态；依赖未满足时必须返回非 healthy 或 `ok=false`，不能让 `/ops/status/` 把不可发消息、不可训练或不可访问的服务计为可用。
- 验收记录：新增或迁移用户服务时，交付说明必须记录 Compose/docker run 路径、restart policy、端口绑定、持久化挂载、关键环境变量来源、health readiness 字段、容器重启或 Docker daemon 重启后的恢复验证，以及公网 UniDesk frontend 或 `microservice health/proxy` 的真实链路结果。

## Compute-Node Development Convention

主 server 本地开发边界固定为只开发 UniDesk frontend 与必要的 UniDesk 配置/代理登记；非 UniDesk 核心功能的后端、Dockerfile、GPU/训练容器、业务数据迁移和业务调试不得默认占用主 server 有限主机资源。涉及 findjob、pipeline、MET Nonlinear 这类业务功能时，应通过 `trans <PROVIDER_ID> ...` 或 remote CLI SSH 透传进入计算节点，在计算节点本地业务仓库中开发、构建和调试；开发完成后，只把业务服务以用户服务形式登记到 UniDesk。

外部业务仓库由业务系统自己维护，包括源码、Dockerfile、docker-compose、配置模板和业务测试。UniDesk 只引用其仓库 URL、commit id 和运行元数据，不得复制形成双维护。UniDesk 自有用户服务直接维护在 `src/components/microservices/`，不再保留第二份外部源码真相。

Code Queue runner 也是分布式开发执行面。runner 镜像必须内置 `tran`，让 runner 在执行任务时能通过公网 frontend 控制面访问 D601、G14、host workspace、k3s 控制面和目标 pod。runner 内应优先使用 `tran <provider> argv ...`、`tran <provider>:k3s kubectl ...`、`tran <provider>:k3s:<namespace>:<workload> argv ...` 这类结构化命令；需要 stdin 的 `sh`/`bash`、`apply-patch`、`py` 操作同样通过 frontend `/ws/ssh` 流式通道执行，不应退回 `/api/dispatch` task polling。这个边界避免把 provider token、backend-core 内网 DNS 或长命令多层引号作为 runner 可用性的前提，也避免大 stdout 被 task JSON compact 截断。

## Managed User Services

主 server 只承载对统一入口、状态迁移或控制面自动化有明确必要的用户服务；NC01 k8s 承载由 `config/unidesk-host-k8s.yaml` 管理的服务。两类服务都遵守不暴露公网业务端口、前端统一 React 控件化展示的规则。持久化真相由服务 YAML 明确声明；`.state/` 只能保存日志归档、缓存或可重建工件。

### Todo Note On NC01

Todo Note 作为 `id=todo-note` 的 NC01 用户服务运行：

- 源码：`src/components/microservices/todo-note`，由 UniDesk 统一维护。
- 运行面：`config/unidesk-host-k8s.yaml` 声明 `unidesk/deployment/todo-note`、`unidesk/service/todo-note`、私有端口 `4288`、资源、健康探针、Secret sourceRef、GitHub 存储和 PaC delivery；禁止 NodePort、hostPort 和独立公开前端。
- 交付：`CI.json` producer 为 `platform-infra pipelines-as-code`。NC01 consumer `unidesk-host` 复用 Repository `sentinel-nc01-v03`，Tekton 构建并推送镜像，GitOps 分支记录 digest，Argo CD 独占 Todo Note Service/Deployment。
- 持久化：GitHub repo `pikasTech/decision-center-data` branch `main` 的 `todo-note/` 是权威数据；Decision Center 使用同仓 `data/`。PostgreSQL 表 `todo_note_instances` 和 `todo_note_history` 只作为索引/cache，启动时必须与 GitHub 真相协调。
- 代理路径：只允许 `/api/` 前缀；允许方法为 `GET`、`HEAD`、`POST`、`DELETE`，用于保持 Todo Note 原有清单创建/删除、任务增删改、提醒、展开/收起、移动、撤销/重做等功能。
- **写操作端点形态（2026-06-01 复盘 [#188](https://github.com/pikasTech/unidesk/issues/188) 固化）**：Todo Note 不走 REST 集合（如 `/api/instances/:id/todos`），所有 task 写都走 **action 队列**模式 `POST /api/instances/:id/actions` + body `{action: {type, ...}}`。已注册 action type：`addTodo` / `updateTodoTitle` / `toggleTodoCompleted` / `toggleTodoExpanded` / `setAllTodosExpanded` / `moveTodo` / `deleteTodo` / `renameInstance` / `setTodoReminder`。其他写端点：`POST /api/instances`（body `{name}` 新建清单）、`DELETE /api/instances/:id`、`POST /api/instances/:id/undo`、`POST /api/instances/:id/redo`。
- **`TODO_NOTE_BACKEND_ONLY=1` 真实语义**：仅关闭 Todo Note 自带 Vite 前端 SPA，不阻挡任何已注册 API 路由（包括所有 POST/DELETE 写）。`/api/health` 暴露的 `backendOnly` 字段是观察用，不是读路径开关。看到 `404 {"error":"Todo Note is running in backend-only mode"}` 时的第一反应是路径写错（用了不存在的 REST 端点被 catch-all 兜底），不是写被 mode 锁。CLI 写操作范式见 `docs/reference/cli.md` 的 `microservice proxy` 段。

- **Catch-all 误导文案 + proxy 结构化诊断（issue #198）**：上游 `gitee.com/Lyon1998/todo_note` 的 catch-all 把所有未注册路径都回 `404 {"ok":false,"error":"Todo Note is running in backend-only mode"}`，秘书/agent 看到这条第一反应容易误判成 "写被 mode 锁了"。UniDesk backend-core 在 `/api/microservices/todo-note/proxy/...` 同源代理层检测到该特征 body 时会改写为结构化诊断（`error: "Todo Note route not found"`、`writableApiEndpoints`、`actionTypes`、`backendOnly: true`、`method`、`path`、`issueReference`、`upstreamBody` 完整保留原始误导文案用于审计），并把 `bodyRewritten: true` 显式返回；CLI 侧保留相同改写作为本地/旧 runtime 兜底。上游 catch-all 修复 PR 合并后，该特征 body 不再出现，proxy/CLI 改写自动退化为 no-op。
  - CLI 侧仍提供 `--check-path` 预检：未命中时返回同源结构化诊断并**不调用 upstream**；命中时返回 `ok: true, matched: true, endpoint: {...}`。当前 `--check-path` 只支持 `service=todo-note`，对其他服务返回结构化 `unsupported` 错误。命令样例见 `docs/reference/cli.md` 的 `microservice proxy` 段。
  - 该改写只用于兼容旧运行面响应；当前 vendored 服务直接返回明确的 route-not-found 诊断。版本发布必须走 NC01 PaC/Argo，并继续用 `microservice health todo-note` + `microservice proxy todo-note /api/instances/<id>/actions` 验证。
- UniDesk 前端：`用户服务 / Todo Note` React 页面负责展示清单列表、树形任务、筛选、提醒、拖放/上移下移、撤销/重做、字号控制和显式原始 JSON 按钮。

Todo Note 首次迁移使用旧运行面的只读 exporter 生成完整 snapshot，通过 `POST /api/storage/import` 的 `confirm=true` 入口导入；大快照使用 `snapshotGzipBase64`，不得提高 backend-core 请求体上限。导入后 `/api/storage/status` 与 `/api/storage/verify` 必须证明 GitHub 和 PostgreSQL cache 对齐，并看到 `CONSTAR`、`大论文`、`找工作`、`小论文`、`事务`、`HWLAB` 六个清单和完整 1167 条历史。随后通过代理创建唯一临时清单、添加任务、切换完成、撤销/重做、删除临时清单，再次运行 GitHub storage verify；只有这些证据通过后才能停用 CC01 旧容器和删除旧 Compose/外部仓库部署元数据。


### OA Event Flow On Main Server

当前 OA Event Flow 作为 `id=oa-event-flow` 的用户服务登记在 `config.json`：

- Provider：`main-server`，由 backend-core 直接访问同一 Compose 网络内的 `http://oa-event-flow:4255`，公网不发布 `4255`。
- 代码引用：`https://github.com/pikasTech/unidesk` 与配置中的 `repository.commitId`；服务源码位于 `src/components/microservices/oa-event-flow`，属于 UniDesk 自有主 server 用户服务。
- 部署引用：UniDesk 根仓库 `docker-compose.yml` 中的 `oa-event-flow` service，Dockerfile 为 `src/components/microservices/oa-event-flow/Dockerfile`，容器名为 `oa-event-flow-backend`。
- 数据库：事件表、projection offset、Trace/STEP stats 和 Trace step 投影写入主 PostgreSQL；服务启动时自动创建/补齐 schema，不依赖仅首次生效的 database init SQL。
- API：`GET /health`；`POST /api/events`；`GET /api/events`；`GET /api/events/stream`；`GET /api/stats/trace`；`GET /api/diagnostics`。
- 代理路径：只允许 `/health`、`/logs` 和 `/api/` 前缀；允许方法为 `GET`、`HEAD`、`POST`。
- UniDesk 前端：`用户服务 / OA Event Flow` React 页面负责展示服务健康、事件表、tag 过滤、live stream 状态、Trace/STEP stats 表、Code Queue/Pipeline 标签入口和显式原始 JSON 按钮。

OA Event Flow 在 UniDesk 语境中按共享控制面基础设施管理：不得暴露公网端口，不得把事件或统计权威状态写入 `.state/`；标准发布为 `bun scripts/cli.ts ci publish-user-service --service oa-event-flow --commit <full-sha>`，dev/prod 都通过 `bun scripts/cli.ts deploy apply --env dev|prod --service oa-event-flow` 消费同一 commit-pinned artifact 并验证 `/health.deploy.commit`；Code Queue 与 Pipeline 都必须通过该服务发布事实事件、订阅 tag stream 和读取统计中心。共享事件流、统计中心和完成门禁见 `docs/reference/oa-event-flow.md`。

### Code Queue Manager On Main Server

`code-queue-mgr` 是主 server Compose 内的轻量 Code Queue 控制面，登记为 `deployment.mode=internal-sidecar`，Provider 为 `main-server`，后端地址为 Compose 网络内 `http://code-queue-mgr:4278`。它不直接出现在前端业务标签中，而是作为稳定 `code-queue` 用户服务代理路径的内部控制面目标。

- 职责：队列 CRUD、任务提交、批量提交、任务移动、queued prompt edit、已读状态、历史摘要、overview、stats、summary、prompt、output/transcript/trace 的轻量 PostgreSQL 读取。
- 非职责：不运行 Codex/OpenCode，不包含 Playwright/Chromium，不持有 Docker socket，不创建 dev-container，不执行 judge，不管理 active run steer/interrupt，不做任务调度或 runner。
- 资源边界：目标常驻内存不超过 100 MB，默认 PostgreSQL pool 为 `CODE_QUEUE_MGR_DATABASE_POOL_MAX=2` 与 `CODE_QUEUE_TRACE_DATABASE_POOL_MAX=1`，`/health` 必须暴露 `role=master-control-plane`、`schemaReady`、连接池上限和 `noRunnerDependencies=true`。
- 标准发布：`bun scripts/cli.ts ci publish-user-service --service code-queue-mgr --commit <full-sha>` 会在 D601 registry 发布 `127.0.0.1:5000/unidesk/code-queue-mgr:<commit>`；dev 可以做 `bun scripts/cli.ts deploy apply --env dev --service code-queue-mgr` 验证，prod 只允许 `bun scripts/cli.ts deploy apply --env prod --service code-queue-mgr --dry-run` 或经 supervisor 单独确认后的 live apply。
- Runtime healthcheck：生产镜像是 Rust slim runtime，不包含 Bun；Docker Compose healthcheck 必须使用镜像内 `code-queue-mgr --healthcheck` 对 `127.0.0.1:4278/health` 做带超时探针，或使用等价的 Rust binary 子命令。不得把 TypeScript/Bun `fetch` 探针用于 `code-queue-mgr`，否则 `server rebuild code-queue-mgr` 的 post-up validation 会误判失败。
- 路由：CLI/WebUI 仍只访问 `/api/microservices/code-queue/proxy/...`；backend-core 在内部把控制/读取路径转到 `code-queue-mgr`，把 active run、judge、dev-container、执行面健康和 scheduler 相关路径转到 D601 执行面。
- 行为兼容：提交与 queued prompt edit 必须保留 Code Queue 环境提示注入、`--reference-task-id`/引用输入解析和引用任务上下文注入，避免 master 控制面路径与 D601 原写服务语义分叉。

### Code Agent Sandbox On Main Server

`code-agent-sandbox` 是主 server Compose 内的独立 Code Agent Sandbox 骨架，登记为 `deployment.mode=internal-sidecar`，Provider 为 `main-server`，后端地址为 Compose 网络内 `http://code-agent-sandbox:4260`。它目前不依赖 Code Queue，也不承担 queue 产品逻辑。

- 职责：统一 adapter 契约、模式边界、凭证边界、状态恢复表达和只读诊断入口。
- 代理路径：允许 `/health`、`/diagnostics`、`/trace`、`/logs` 和 `/api/` 前缀；允许方法为 `GET`、`HEAD`、`POST`、`PUT`。
- 模式：`full-isolation`、`half-isolation`、`bridge` 三种模式都必须在 health/diagnostics 中可见，bridge 模式只读宿主现状，不改写宿主配置。
- 适配器契约：`start`、`attach`、`prompt`、`steer`、`interrupt`、`resume`、`fork`、`trace`、`terminal status` 和 `artifact summary` 统一由 adapter 层暴露。
- 恢复边界：服务只承诺状态可恢复、未开始任务可重调度、中断 attempt 可 retry/resume、结果不丢，不承诺 live migration。

### Project Manager On Main Server

当前 Project Manager 作为 `id=project-manager` 的用户服务登记在 `config.json`：

- Provider：`main-server`，由本机 provider-gateway/直接内网代理访问同一 Compose 网络内的 `http://project-manager:4233`。
- 代码引用：`https://github.com/pikasTech/unidesk` 与配置中的 `repository.commitId`；服务源码位于 `src/components/microservices/project-manager`，属于 UniDesk 自有主 server 用户服务。
- 部署引用：UniDesk 根仓库 `docker-compose.yml` 中的 `project-manager` service，Dockerfile 为 `src/components/microservices/project-manager/Dockerfile`，容器名为 `project-manager-backend`。
- 数据库：项目清单写入主 PostgreSQL 表 `project_manager_projects`；服务启动时自动创建/补齐 schema，不依赖仅首次生效的 database init SQL。
- 初始数据来源：D601 Windows 文件 `C:\Users\liang\xwechat_files\wxid_01rxm0yxjksk12_345f\msg\file\2026-05\合作项目列表_I_20260309.xlsx`，通过 UniDesk SSH 透传读取到主 server 后，用 `/api/import/excel` 导入。当前 Excel 表头为 `序号`、`合同号`、`项目名称`、`当前状况`、`待完成`、`付款情况`、`其它`。
- API：`GET /health`；`GET|POST /api/projects`；`GET|PUT|DELETE /api/projects/{id}`；`POST /api/import/excel`；`POST /api/import/projects`；`GET /api/projects/export.xlsx`。
- 代理路径：只允许 `/health`、`/logs` 和 `/api/` 前缀；允许方法为 `GET`、`HEAD`、`POST`、`PUT`、`DELETE`。
- UniDesk 前端：`用户服务 / Project Manager` React 页面负责展示主 server 仓库引用、私有后端映射、项目指标、项目表格、筛选搜索、编辑表单、Excel 导入和 Excel 导出；完整原始 JSON 只能通过显式 `查看原始JSON` 打开。

Project Manager 的标准发布是 `bun scripts/cli.ts ci publish-user-service --service project-manager --commit <full-sha>`，随后用 `bun scripts/cli.ts deploy apply --env dev --service project-manager` 和 `bun scripts/cli.ts deploy apply --env prod --service project-manager` 消费同一 commit-pinned artifact 并验证 live commit / requestedCommit。Project Manager 在 UniDesk 语境中按纯后端服务管理：不得将 `4233` 映射为公网端口。浏览器只能通过 UniDesk frontend 的 `/api/microservices/project-manager/health` 和 `/api/microservices/project-manager/proxy/...` 同源代理访问项目管理后端。

### Baidu Netdisk On Main Server

当前 Baidu Netdisk 作为 `id=baidu-netdisk` 的用户服务登记在 `config.json`：

- Provider：`main-server`，由 backend-core 直接访问同一 Compose 网络内的 `http://baidu-netdisk:4244`，公网不发布 `4244`。
- 代码引用：`https://github.com/pikasTech/unidesk` 与配置中的 `repository.commitId`；服务源码位于 `src/components/microservices/baidu-netdisk`，属于 UniDesk 自有主 server 用户服务。
- 部署引用：UniDesk 根仓库 `docker-compose.yml` 中的 `baidu-netdisk` service，Dockerfile 为 `src/components/microservices/baidu-netdisk/Dockerfile`，容器名为 `baidu-netdisk-backend`。
- 标准发布：`baidu-netdisk` 是主 server 直管微服务的镜像化样板，也是 PGDATA 到百度网盘日备份链路依赖服务。CI 使用 `bun scripts/cli.ts ci publish-user-service --service baidu-netdisk --commit <full-sha>` 在 D601 registry 发布 `127.0.0.1:5000/unidesk/baidu-netdisk:<commit>`；dev 验证使用 `bun scripts/cli.ts deploy apply --env dev --service baidu-netdisk`；prod 发布使用 `bun scripts/cli.ts deploy apply --env prod --service baidu-netdisk`。两条 CD 都必须消费同一类 commit-pinned artifact、只 recreate `baidu-netdisk-backend`、并验证 image label、`/health.deploy.commit` 和 `/health.auth` 门禁；`server rebuild baidu-netdisk` 只保留为维护/非标准路径。
- 配置密钥：Compose 只透传 `UNIDESK_BAIDU_NETDISK_CLIENT_ID`、`UNIDESK_BAIDU_NETDISK_CLIENT_SECRET`、`UNIDESK_BAIDU_NETDISK_TOKEN_KEY` 与可选 `UNIDESK_BAIDU_NETDISK_APP_ROOT`；当前默认工作根目录为 `/`，如需收回到应用目录可显式设为 `/apps/<name>`；不得把百度 AppSecret、token key、access token 或 refresh token 写入仓库文件。
- artifact CD 密钥与健康门禁：dev/prod `deploy apply` 或 `artifact-registry deploy-service` 在 live apply 前必须从 canonical `.state/docker-compose.env` 确认 `UNIDESK_BAIDU_NETDISK_CLIENT_ID`、`UNIDESK_BAIDU_NETDISK_CLIENT_SECRET`、`UNIDESK_BAIDU_NETDISK_TOKEN_KEY` 存在，只能输出 present/length/boolean，不得打印值。单服务 recreate 后必须确认 `/health.auth.configured`、`clientIdConfigured`、`clientSecretConfigured`、`tokenKeyConfigured`、`loggedIn` 全部为 true；否则发布不能视为成功，需先恢复 env 注入并重新执行受控单服务 recreate。
- 配置步骤：`UNIDESK_BAIDU_NETDISK_TOKEN_KEY` 可由本机生成；百度 `client_id` 和 `client_secret` 必须由账号拥有者在百度网盘开放平台创建应用后提供，操作清单见 `docs/issue/baidu-netdisk-env-setup.md`。该环境配置说明只用于密钥、登录和维护验证；按 commit 发布或恢复 desired state 时仍必须使用上面的 CI artifact 加 dev/prod `deploy apply` 路径，不能把本地 `server rebuild baidu-netdisk` 当作镜像化交付证据。
- PGDATA 备份预检：`schedule upsert-pgdata-backup` 创建的 PGDATA 到百度网盘日备份在执行 `pg_basebackup`、打包和上传前必须先做轻量 preflight。preflight 至少检查 `baidu-netdisk /health`、`clientIdConfigured`、`clientSecretConfigured`、`tokenKeyConfigured`、`loggedIn=true`、共享 staging 目录可写，以及 backend-core 到 PostgreSQL 的基本 `select 1` 可达性。preflight 失败必须快速结束 run，不得生成大备份文件或启动上传；run `result` 必须包含 `stage=preflight`、`failureKind`、`retryable`、`recommendedAction`、`observedAt` 和分项 `checks`。
- PGDATA 备份失败分类：`failureKind=config-missing` 表示 Baidu Netdisk client id/client secret/token key 缺失，通常不可直接重试，先恢复 runtime env 并验证 `microservice health baidu-netdisk`；`auth-missing` 表示未登录或登录态不可用，先在 UniDesk Baidu Netdisk 页面重新登录；`staging-unwritable` 表示 `/data/baidu-netdisk-staging` 对 backend-core 不可写或路径配置错误，先修复挂载/权限；`database-unreachable` 表示 backend-core 到 PostgreSQL 路由或凭据不可用，先修复数据库可达性；`baidu-unreachable` 表示 Baidu Netdisk 服务健康端点不可达或服务自身不健康，先恢复服务健康；`unknown` 只用于未归类异常。排障时先用 `bun scripts/cli.ts schedule get unidesk-pgdata-baidu-daily` 或 `bun scripts/cli.ts schedule runs unidesk-pgdata-baidu-daily --limit 5` 查看最近 run 的 `result.failureKind`，不要根据全局空数组判断没有历史。
- PGDATA 备份重试入口：失败 run 修复后使用 `bun scripts/cli.ts schedule retry-run <failedRunId>`，CLI 会输出 `originalRunId`、原 `scheduleId`、新 `newRunId` 和 `observeCommand`；也可用 `bun scripts/cli.ts schedule run unidesk-pgdata-baidu-daily --wait-ms <N>` 手动触发，同样会输出新 run id。若 `--wait-ms` 超时，继续执行返回的 `observeCommand` 观察，不要手工拼 backend-core API 路径。
- 数据库：OAuth 设备码会话、账号摘要、加密 token、传输任务和事件写入主 PostgreSQL 表 `baidu_netdisk_*`；服务启动时自动创建/补齐 schema，不依赖仅首次生效的 database init SQL。
- 文件边界：v1 只支持容器 staging 目录 `/data/staging` 与百度网盘配置工作根之间的后台上传/下载任务；staging 目录由主 server `.state/baidu-netdisk/staging` 挂载，`.state/` 只保存可重建文件缓存，不作为 token 或任务权威状态。当前授权账号已实测可对百度网盘根目录 `/` 执行列表、上传、获取 dlink、下载和删除临时探针，因此 `UNIDESK_BAIDU_NETDISK_APP_ROOT` 默认直接设为 `/`；仅当该值配置为 `/apps/...` 时，后端才会确保应用目录存在，目录已存在时必须返回/记录 `errno=-8` 并继续，禁止使用会重命名的策略重复创建 `_YYYYMMDD_...` 目录。
- API：`GET /health`；`GET /api/auth/status`；`POST /api/auth/device/start`；`GET /api/auth/device/status`；`POST /api/auth/refresh`；`POST /api/auth/logout`；`GET /api/account`；`GET /api/files`；`GET /api/files/meta`；`POST /api/folders`；`POST /api/files/manage`；`POST /api/transfers/upload-from-path`；`POST /api/transfers/download-to-path`；`POST /api/self-test`；`GET /api/transfers`；`GET|POST /api/transfers/{id}/cancel|retry`；`GET /logs`。
- 授权轮询：百度设备码轮询返回的 `authorization_pending` 和 `slow_down` 是正常中间态，后端必须把它们更新为 pending session（`slow_down` 增加轮询间隔）而不是向前端抛 HTTP 错误；只有拒绝、过期或未知 OAuth 错误才进入 rejected/expired/failed。
- 自测：`POST /api/self-test` 会在 staging 生成小文本、上传到配置工作根、通过 `/api/files` 找到 `fs_id`、下载回 staging 并校验 MD5；该端点不得回显 token/dlink，适合 CLI、前端按钮和交付验收使用。
- 代理路径：只允许 `/health`、`/logs` 和 `/api/` 前缀；允许方法为 `GET`、`HEAD`、`POST`、`DELETE`。
- UniDesk 前端：`用户服务 / Baidu Netdisk` React 页面负责展示设备码登录卡、账号容量、配置工作根文件表、staging 上传/下载任务、上传/下载自测按钮与结果、脱敏日志和显式原始 JSON 按钮。

Baidu Netdisk 在 UniDesk 语境中按纯后端服务管理：不得暴露百度 token、dlink 或 staging 文件字节流给浏览器；浏览器只能通过 UniDesk frontend 的 `/api/microservices/baidu-netdisk/health` 和 `/api/microservices/baidu-netdisk/proxy/...` 同源代理访问控制面 JSON。

### File Browser Host Files

当前 File Browser 作为两组用户服务登记在 `config.json`，共用上游 `https://github.com/filebrowser/filebrowser` 的 `filebrowser/filebrowser:v2.63.3` 镜像和 commit `ca5e249e3c0c94159c2136a0cd431a424eb18472`；主 server 不再运行 File Browser 容器，避免占用主 server CPU/内存和主机根目录遍历资源：

- `id=filebrowser`：Provider 为 `D518`，服务在 D518 节点本机绑定 `4251`，provider-gateway 容器内通过 `http://host.docker.internal:4251` 访问；容器名为 `unidesk-filebrowser-d518`，挂载 D518 WSL host `/` 到 `/srv`，因此可浏览 `/mnt/c` 等 Windows 盘符。D518 Docker Desktop 的 `host.docker.internal` 指向 Windows host IP，实际部署需使用 `0.0.0.0:4251->8080` 才能让 provider-gateway 容器访问。
- `id=filebrowser-d601`：Provider 为 `D601`，服务在 D601 节点本机绑定 `127.0.0.1:4251`，provider-gateway 容器内通过 `http://host.docker.internal:4251` 访问；容器名为 `unidesk-filebrowser-d601`，挂载 D601 WSL host `/` 到 `/srv`，因此可浏览 `/mnt/c` 等 Windows 盘符。
- 启动参数必须包含 `--baseURL /api/microservices/<id>/proxy`、`--noauth`、`--disableExec`、`--disableTypeDetectionByHeader`、`--disableImageResolutionCalc` 和 `--disableThumbnails`；容器必须使用 `user: "0:0"` 或 `--user 0:0`，否则 upstream 镜像默认非 root 用户会导致 `/database/filebrowser.db` 写入失败。禁用头部类型探测、图片尺寸计算和缩略图可避免 Windows 盘根目录中的 `hiberfil.sys`、`pagefile.sys` 等受保护文件导致整个目录浏览失败。
- 代理路径允许 `/`，允许方法为 `GET`、`HEAD`、`POST`、`PUT`、`PATCH` 和 `DELETE`；File Browser 的 `/api/login`、`/api/resources` 和上传 API 需要透传 `X-Auth`、`Range`、`Tus-Resumable` 等请求头。
- provider-gateway 容器必须配置 `extra_hosts: ["host.docker.internal:host-gateway"]`，确保 D601/D518 Linux Docker 容器内能访问 WSL host 侧 `127.0.0.1:4251` 映射。
- UniDesk 前端：`用户服务 / File Browser` React 页面展示 D518 主目标和 D601 备用目标的健康状态、仓库引用、私有后端映射，并以 iframe 嵌入对应 File Browser WebUI；页面必须提供截图导出入口，并对上游 WebUI 注入紧凑布局样式，避免 material icon 字体异常时 `folder` 文本遮挡文件名；File Browser 自身端口不得直接暴露公网。

### k3s Control Plane Adapter On D601

`k3sctl-adapter` 是 UniDesk 直管的控制面适配微服务；它本身仍按 `deployment.mode=unidesk-direct` 登记在 D601，由 UniDesk 负责健康检查、重建和前端可见性，但它管理的业务服务必须走 k3s 标准服务路由，不得再被 backend-core 直接下发到业务容器所在 provider。

- Provider：`D601`，由 D601 provider-gateway 仅维护和访问 `k3sctl-adapter` 的本机私有端口 `127.0.0.1:4266`；provider-gateway 不再作为 `code-queue` 业务请求的直接代理。
- 代码引用：`https://github.com/pikasTech/unidesk` 与配置中的 `repository.commitId`；服务源码位于 `src/components/microservices/k3sctl-adapter`，属于 UniDesk 自有控制面组件。
- 部署引用：UniDesk 仓库中的 `src/components/microservices/k3sctl-adapter/docker-compose.d601.yml`，Dockerfile 为 `src/components/microservices/k3sctl-adapter/Dockerfile`，容器名为 `k3sctl-adapter`；当前 D601 Docker Desktop 包装必须挂载宿主 kubeconfig 与 WSL 维护 SSH key，并由容器入口脚本建立到 WSL host 原生 k3s API 的 SSH local tunnel。
- P0 控制面边界：D601 只能把自部署原生 k3s 作为 Kubernetes source of truth；Docker Desktop Kubernetes 已停用并清理数据，不得重新启用，也不得承载 `unidesk*`、`hwlab-dev` 或 Code Queue 资源。Docker Desktop daemon 可继续承载 `k3sctl-adapter` 这类故障域外 Docker 服务，但不能成为第二个 Kubernetes 编排器；事故证据与治理见 [GitHub issue #138](https://github.com/pikasTech/unidesk/issues/138)。
- 控制桥边界：`k3sctl-adapter` 是 UniDesk 到 k3s 的控制桥，不是被 k3s 管理的业务 workload；它必须保持 `deployment.mode=unidesk-direct`，或迁移为等价的 UniDesk/systemd 直管宿主服务，不得改成 `k3sctl-managed` Deployment。原因是 UniDesk 依赖 adapter 做 k3s 服务代理、部署验证和故障诊断；如果 adapter 自身依赖 k3s Deployment、Service、CNI 或 kube-proxy 才能存活，k3s 网络故障时会失去修复 k3s 的入口，形成依赖顺序倒置。
- 原生 API 连接：D601 原生 k3s 的 kubeconfig 固定来自宿主 `/etc/rancher/k3s/k3s.yaml`，adapter 内部挂载为 `/var/lib/unidesk/k3s/kubeconfig`；当 kubeconfig server 是 `127.0.0.1:6443` 时，adapter 容器必须通过受控 SSH local tunnel 把容器内 `127.0.0.1:6443` 转发到 WSL host `127.0.0.1:6443`，并设置 `K3SCTL_KUBE_API_CONNECT_HOST=127.0.0.1`。不得依赖 Docker Desktop 的 `network_mode: host`，因为它进入的是 Docker Desktop VM 网络而不是 D601 WSL Ubuntu 网络；也不得依赖 `host.docker.internal:6443`、旧 `rancher/k3s` 容器 IP、NodePort 或手工 service endpoint。
- k3s 实现：D601 控制面、D518 或其他计算资源节点上的 k3s agent/worker 都必须原生安装在节点 host OS 或 WSL 发行版内，以 `/usr/local/bin/k3s` 和 systemd `k3s.service`/`k3s-agent.service` 运行；不得用 Docker、Compose、`rancher/k3s` 长驻容器、kind/k3d 或其他容器化方式承载 k3s 控制面或 kubelet。Docker 只允许用于 provider-gateway、业务容器镜像构建、运行用户 workload 或临时提取 k3s 二进制/镜像 artifact，不能成为 k3s runtime 边界。验收时必须证明 `systemctl is-active k3s` 或 agent 服务正常、`kubectl get nodes -o wide` 看到真实节点 OS/内核、k3s containerd socket 位于 `/run/k3s/containerd/containerd.sock`，且不存在 active `rancher/k3s` 控制面容器。
- k3s 路由对象：`k3sctl-managed` 可以落到 k3s、k8s 或等价标准 Kubernetes 控制面，但必须使用 Kubernetes 原生命名空间、Deployment、Service、readiness/liveness probe、Kubernetes API service proxy 等规范对象；不得把裸容器端口、NodePort、SSH curl、provider-gateway `microservice.http` 或 host 直连地址伪装成 k3s 服务路由。WSL 节点的 hostPath 和 local-path 语义必须解析到 WSL host 文件系统；例如 D601 Code Queue Pod 的 `/workspace` 必须映射 WSL `/home/ubuntu`，不能映射到容器化 k3s 内部的 `/home/ubuntu`。
- k3s 系统组件：D601 原生 k3s server 必须禁用非必要的 `traefik`、`servicelb` 和 `metrics-server`，只保留业务必需的 API server、CoreDNS 与 local-path provisioner；CoreDNS 和 local-path provisioner 固定运行在 D601 控制面节点，避免跨 WSL/NAT 节点维护隧道限制导致系统 DNS/readiness 抖动。
- manifest：代管服务声明放在 `src/components/microservices/k3sctl-adapter/k3s/*.k3s.json`，adapter 启动时通过 `K3SCTL_MANIFEST_PATHS` 读取；manifest 是当前计划内 k3s 实例、active instance、single writer、expected nodes 和 health policy 的权威来源。`K3SCTL_SERVICES_JSON` 不得承载 static HTTP 服务、不得覆盖同名服务、不得作为隐藏 fallback；如需追加服务也必须提供完整 `ManagedKubernetesService` manifest。
- API：`GET /health` 只表示 adapter 控制面自身可用，并把代管服务 serving 健康作为 `managedServicesHealthy` 字段展示；`GET /api/control-plane` 返回控制面、manifest、kubectl/k3s snapshot 和代管服务状态；`GET /api/services` 返回代管服务列表；`GET|HEAD /api/services/<id>/health` 返回该 k3s 服务的 active serving 健康；`/api/services/<id>/proxy/*` 是业务请求进入 active service 的唯一代理入口。
- 代理路径：adapter 访问 active 业务服务的唯一正式路径是 Kubernetes API service proxy：`/api/v1/namespaces/<namespace>/services/<service>:<port>/proxy/...`。当前 Code Queue 运行拓扑只把 D601 写入 `expectedNodeIds`；任何远端 standby/worker 节点必须先完成原生 k3s-agent、稳定控制面网络、镜像分发和 hostPath 语义验证，才能加入 manifest。业务请求不得退化为 provider-gateway 直连 Code Queue HTTP 端口。standby/worker 节点如果受 kubelet/service-proxy 可达性限制，可以在 manifest 中显式使用 `healthMode=pod-ready` 作为拓扑健康探针；这只读取 Kubernetes Pod readiness，不是业务代理路径，也不能替代 active Service proxy。
- 拓扑健康：`expectedNodeIds` 负责展示计划内节点；当前 Code Queue 目标拓扑为 D601 原生 k3s 单节点多服务，`presentNodeIds` 应包含 `D601`、`missingNodeIds=[]`、`topologyComplete=true`、`status=healthy`。不能把未完成原生 k3s 接入或仍依赖 Docker 化 k3s 的节点列为 expected node；只有显式 `requireAllInstancesHealthy=true` 的服务才允许把缺失 standby/worker 节点提升为整体不健康。
- 前端：`用户服务 / k3s Control` React 页面必须只通过 `/api/microservices/k3sctl-adapter/proxy/api/control-plane` 通信，展示控制面状态、manifest、D601 scheduler/read/write 实例、active instance、Kubernetes API service proxy/no-fallback 路径和显式原始 JSON 按钮；页面不得直接访问 provider-gateway、D601/D518 业务容器端口、NodePort 或 raw k3s/kubectl API。

### D601 Dev Namespace Foundation

D601 开发环境底座只允许创建 `unidesk-dev` namespace 与 dev 专用对象，manifest 固定为 `src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-foundation.k8s.yaml`。该 manifest 包含 `postgres-dev` 独立 PostgreSQL StatefulSet/Service/PVC、dev-only secret/config 模板、dev DB 初始化 SQL 和迁移 Job、ResourceQuota/LimitRange，以及 `unidesk-dev-db-guard`。它不得修改生产 `unidesk` namespace、生产 PostgreSQL、生产 PVC、生产 Deployment/Service/Secret 或主 server Docker Compose。

`postgres-dev` 是 dev backend-core 与 dev Code Queue 状态的默认唯一数据库。dev 运行时必须使用 `postgres-dev.unidesk-dev.svc.cluster.local:5432/unidesk_dev` 和 dev Provider 身份 `D601-dev`；不得共享生产 `d601-tcp-egress-gateway.../unidesk`。Persistent dev backend-core/frontend/MDTODO/ClaudeQQ artifact rollout, dev-only Code Queue artifact rollout, public dev frontend port and Rust CI artifact boundary are defined in `docs/reference/dev-environment.md`; Decision Center is governed by the NC01 k8s/GitHub storage lane.

验收入口：先运行 `bun scripts/cli.ts dev-env validate` 做静态资源与 DB URL 护栏检查；具备 D601 kubeconfig 时运行 `bun scripts/cli.ts dev-env validate --kubectl-dry-run` 做 Kubernetes client dry-run。首次或镜像缓存不确定时，先运行 `bun scripts/cli.ts dev-env prewarm-images`，把 `postgres:16-alpine` 和 local-path helper 所需的 `rancher/mirrored-library-busybox:1.36.1` 导入 D601 原生 k3s containerd；否则 D601 的 Docker 代理/缓存正常也不能保证 k3s/containerd 能实时拉到外部镜像。若实际 apply，只能 apply 到 `unidesk-dev`，随后用 `KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n unidesk-dev get pods,svc,pvc` 验证 dev DB ready，并对比 apply 前后的 `KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n unidesk get deploy,sts,svc,secret,pvc -o name` 证明生产 workload 未变化。

D601 上必须显式使用原生 k3s kubeconfig：`KUBECONFIG=/etc/rancher/k3s/k3s.yaml`。默认 `kubectl` context 可能是 Docker Desktop，不能作为 UniDesk k3s deploy 或 dry-run 验收目标。

### D601 Dev Core Services

`backend-core-dev` 与 `frontend-dev` 的第一版 manifest 固定为 `src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-core.k8s.yaml`。该 manifest 只允许创建 `unidesk-dev` 内的 `backend-core-dev`、`frontend-dev` Deployment/Service；不得修改生产主 server Compose、生产 `unidesk` namespace 或生产 backend/frontend。Rollout uses `deploy apply --env dev --service backend-core|frontend` under the rules in `docs/reference/dev-environment.md`.

`backend-core-dev` 必须从 `unidesk-dev-runtime-config` 和 `unidesk-dev-runtime-secrets` 注入 dev-only 配置，使用 `postgres-dev.../unidesk_dev`、dev Provider token、dev log path 和 `UNIDESK_DEPLOY_REF=origin/master:deploy.json#environments.dev`。`frontend-dev` 必须把 `CORE_INTERNAL_URL` 指向 `backend-core-dev.unidesk-dev.svc.cluster.local:8080`，页面在 dev identity 下显示 DEV 标记，`/health` 返回 dev namespace、database、service id、deploy ref 和 commit metadata。生产环境未设置 dev identity 时，backend-core 和 frontend health payload 保持生产兼容形状。

`unidesk-dev-core.k8s.yaml` 当前使用 placeholder image/commit；正式 rollout 需要先由 CI 发布 commit-pinned artifact，再由 `deploy apply --env dev --service backend-core|frontend` 从 `origin/master:deploy.json#environments.dev` 读取 commit、拉取或导入成品镜像并更新 Deployment。当前验收只做静态校验和 Kubernetes client dry-run，不能把 placeholder manifest 当成已上线。

`unidesk-dev-mdtodo.k8s.yaml`、`unidesk-dev-claudeqq.k8s.yaml` 和 `unidesk-dev-code-queue.k8s.yaml` 是 D601 registry artifact consumer 的 dev manifests。它们只能创建 `unidesk-dev` 内的 ClusterIP Service/Deployment 对象，不得修改生产 `unidesk` namespace、主 server Compose 或新增 NodePort/hostPort。受控 rollout 必须使用 commit-pinned registry artifact、stamp `UNIDESK_DEPLOY_*` metadata，并通过 Kubernetes API service proxy 检查服务健康。

### Code Queue k3s-Managed

当前对外 `id=code-queue` 是稳定用户服务 ID，实际按 master 控制面与 D601 执行面拆分。队列管理、提交、历史摘要、已读状态和轻量 Trace 读取默认由主 server `code-queue-mgr` 直管 PostgreSQL；D601 k3s Code Queue 作为执行面代管，负责 scheduler/runner、dev-container、active run steer/interrupt、judge、输出/attempt/通知写回，并接入统一 `oa-event-flow` 发布 Trace/STEP 事实事件与读取统计中心：

- Orchestrator：稳定 `code-queue` ID 的控制/读取路径由 backend-core 分流到 `deployment.mode=internal-sidecar` 的 `code-queue-mgr`；D601 执行面仍登记为 `deployment.mode=k3sctl-managed`，`deployment.adapterServiceId=k3sctl-adapter`，`deployment.k3sServiceId=code-queue`，`backend.proxyMode=k3sctl-adapter-http`，`backend.nodeBaseUrl=k3s://code-queue`。对外登记的 `code-queue` ID 保持稳定，frontend/CLI 不需要知道内部拆分。
- Direct path ban：`code-queue` 不得再登记 `http://code-queue:4222`、`http://host.docker.internal:4222`、NodePort 或 provider-gateway `microservice.http` 作为业务代理目标；frontend 也不得使用旧 `/api/code-queue-direct` 兼容别名作为 Code Queue 页面数据源。provider-gateway 只允许用于维护 D601/D518、部署 adapter、部署 k3s/k8s 节点或诊断节点本机容器。
- Artifact consumer boundary：`code-queue` 只支持 dev artifact consumer。`--env dev --service code-queue` 可从 D601 registry 消费 `unidesk/code-queue:<commit>` 并更新 `unidesk-dev` 的 scheduler/read/write/provider-egress-proxy dev Deployments；`--env prod --service code-queue` 必须明确 unsupported，不得执行 production artifact deploy、rollout 或 manifest 变更。
- Claim/move consistency：master `code-queue-mgr` 和 D601 scheduler 都必须以 PostgreSQL 状态为权威；move 只允许未 claim 的 `queued`/`retry_wait` 行，merge 在 source/target queue 存在 `running`/`judging` 或 claim marker 时整体返回 409。稳定 `code-queue` 代理可用 `/api/queue-claim-move/self-test` 验证 claimed task move 会被拒绝且数据库仍保持 running/source queue 状态。
- D601 Service boundary：D601 内部可以继续保留 `code-queue-read`、`code-queue-write` 和 `code-queue-scheduler` 三个 Kubernetes Service 作为执行面兼容和过渡对象，但普通提交、queue CRUD、history、readAt 和轻量 overview 不得依赖 `code-queue-write` 或 D601 egress 可用；`code-queue-write` 不 ready 时，主 server `code-queue-mgr` 仍应保证 CLI/WebUI 的提交、列表和历史读取可用。需要 active run、dev-container、judge 或执行面健康的路径才进入 D601 scheduler。
- 服务拆分语义：`code-queue-read` 只承载 GET/HEAD 查询、overview、任务详情、Trace/output/transcript、统计和只读健康，可多副本滚动更新；它必须设置 `CODE_QUEUE_SERVICE_ROLE=read` 与 `CODE_QUEUE_SCHEDULER_ENABLED=false`，且不得接受入队、queue 变更、已读、重试、移动、追加 prompt 或打断这类 mutation。`code-queue-write` 承载入队、queue 创建/合并/更新、已读、手动重试、移动等命令写入，初期保持单副本和 `CODE_QUEUE_SERVICE_ROLE=write`，只把命令和任务状态写入 PostgreSQL，不启动 agent 子进程。`code-queue-scheduler` 是唯一拥有 scheduler 和 active run 的执行服务，设置 `CODE_QUEUE_SERVICE_ROLE=scheduler` 与 `CODE_QUEUE_SCHEDULER_ENABLED=true`，负责从 PostgreSQL 热任务集轮询新写入任务、推进队列、启动 Codex/OpenCode、处理 running task 的 steer/interrupt、发送终态通知和暴露执行端 `/health`。普通 Service 负载均衡不得把 mutation 打到 read，也不得把 running task 控制打到 write。
- 实例语义：D601 是当前唯一 active 执行节点，`code-queue-scheduler` 以一个 scheduler Pod 承载长生命周期 Codex/OpenCode 子进程并轮询主 PostgreSQL 中由 `code-queue-mgr` 写入的 queued/retry_wait 任务。D518 不属于当前 Code Queue k3s 拓扑；在没有原生 k3s-agent 与稳定 Kubernetes 网络前，不得把 D518 写回 `expectedNodeIds` 或恢复 `code-queue-d518` standby。D601 scheduler 默认关闭 `CODE_QUEUE_STARTUP_OA_BACKFILL_ENABLED`；历史 OA Trace/STEP 回填必须通过显式 `/api/oa/backfill` 运维动作触发，不能在每次 Pod 重启时自动批量发布旧事件。
- 滚动更新边界：master `code-queue-mgr` 保证 D601 抖动或执行面滚动更新期间普通提交、queue 管理和历史读取仍可用；但当前 D601 scheduler Pod 内仍直接承载正在运行的 agent 子进程，scheduler Pod 被替换时 active task 仍会进入 restart-recovery/retry 语义，不能宣称 running task 零中断。真正的长期目标是继续把调度器和执行器拆开：scheduler 只负责 claim task 并创建 Kubernetes Job/Pod 或独立 worker，runner 把输出、状态、attempt、事件和通知写回 PostgreSQL/OA Event Flow/归档；只有这样 controller/scheduler 滚动更新才不会影响正在执行的任务。
- Active run liveness：Code Queue 活性判断必须同时读取 PostgreSQL 任务状态、D601 scheduler 本地 active run/active slot/active queue、scheduler-owned heartbeat、Trace/OA 持久化进度和 OA publisher pending/lastError。scheduler heartbeat 至少包含 `taskId`、`attempt`、`activeTurnId`/`codexThreadId`、`owner`/`schedulerInstance`、`lastLocalHeartbeatAt`、`lastObservedAgentEventAt`、`lastPersistedTraceAt`、`outputMaxSeq` 与 `agentPort`；后续如拆出独立 runner，可以在同一结构上追加 worker/claim lease 字段。master `code-queue-mgr` 的 `postgres-control-plane` 视图不能单独作为“任务已卡死/未执行”的依据；当 master 看到 `databaseActiveTaskCount>0`、本地 `activeRunSlotCount=0`，但存在新鲜 scheduler heartbeat 时，`executionDiagnostics.state` 必须报告 `split-brain`/`degraded` 而不是 `healthy`。
- Trace gap 与 stale active：Trace/OA 长时间没有新 seq 或 publisher 有 pending/lastError 只说明持久化链路可能 degraded；只要 scheduler-owned `lastLocalHeartbeatAt` 仍新鲜，就必须归类为 trace gap，不得触发 stale retry。只有 PostgreSQL 仍为 `running`/`judging`、scheduler 本地没有 active run/slot/waiter，并且 owner heartbeat 已过期时，任务才允许进入 stale recovery candidate；缺失 heartbeat 是 degraded 诊断，不是自动恢复许可。
- Restart recovery：D601 scheduler 启动或 reconciliation 时只能由 scheduler-owned 恢复入口处理 stale active，必须留下 recovery reason、source/method 审计事件，并使用条件更新防止覆盖并发 owner 写入；恢复原因需要区分 user interrupt、admin stale recovery 和 service restart recovery。禁止裸改 production PostgreSQL 任务状态，禁止把 production scheduler/数据库作为破坏性测试对象。health/overview 中的 `activeTaskIds` 只代表当前进程真实持有的 agent run；数据库里仍处于 `running`/`judging` 但没有本地 run 的任务只能作为 scheduler 侧 `orphanedActiveTaskIds` 或 diagnostics 暴露，不能计入 active run slot。主 server 直管 `code-queue-mgr` 只有 PostgreSQL 视角，不得把数据库中的 `running`/`judging` 误报为真实 active run；只能作为 `databaseActiveTaskIds`/`executionStateSource=postgres-control-plane` 这类控制面状态返回。
- Transient dependency recovery：D601 scheduler/read/write 通过 provider egress 和 TCP gateway 访问主 PostgreSQL、OA Event Flow 与模型 API，必须把 `CONNECTION_CLOSED`、`CONNECT_TIMEOUT`、stale PostgreSQL client、provider egress 瞬时失败和 MiniMax judge provider 初始化失败视为可恢复运行时抖动。实现上应轮换失效数据库 client、重试或降级 judge provider 初始化、释放 active run slot 并继续扫描后续 queued/retry_wait 任务；不得因为一次连接关闭、一次 judge provider transient error 或滚动更新窗口让 scheduler 长期停止推进。
- 部署引用：Code Queue 镜像仍复用 `src/components/microservices/code-queue/Dockerfile`，Kubernetes 运行清单为 `src/components/microservices/k3sctl-adapter/k3s/code-queue.k8s.yaml`，`config.json` 对外记录 k3s manifest `src/components/microservices/k3sctl-adapter/k3s/code-queue.k3s.json`；主 server 根目录 `docker-compose.yml` 不包含 `code-queue` service，旧 D601 direct Compose 文件只作为迁移/本地诊断参考，不是正式运行入口。
- 主服务依赖映射：Code Queue 仍以主 PostgreSQL 为权威数据库，但 D601 k3s Pod 不能依赖公网直连 `74.48.78.17:15432/4255`。Pod 内 `DATABASE_URL` 和 `OA_EVENT_FLOW_BASE_URL` 必须指向集群内 `d601-tcp-egress-gateway` Service，再由该 gateway 通过 D601 provider-gateway egress proxy 的 HTTP CONNECT 转发到主 PostgreSQL 和 OA Event Flow；新增 TCP 依赖时扩展 `TCP_EGRESS_ROUTES`，不得在业务容器里新增一次性公网直连或 ad hoc 隧道。D601 active 实例的 `CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL` 必须使用集群内 ClaudeQQ Service `http://claudeqq.unidesk.svc.cluster.local:3290`，并把 `claudeqq`/`claudeqq.unidesk.svc.cluster.local` 加入 `NO_PROXY`，避免任务完成通知被默认出网代理错误转发。旧 `http://host.docker.internal:3290` 只允许作为迁移期诊断，不得作为 Code Queue k3s Pod 的正式通知路径。这些端口映射只服务受控节点运行时，必须用防火墙或等价策略限制来源，不得成为浏览器或任意公网客户端入口。
- K8s 探针与启动维护：Kubernetes liveness/startup probe 必须使用轻量 `/live`，readiness 和用户服务健康使用 `/health`；`/health` 不得执行全量任务聚合、历史回填或长事务索引维护，历史任务总览应由 `/api/tasks/overview` 读取 PostgreSQL。启动时允许后台执行队列元数据 flush、通知 outbox 读取、任务表索引维护和 overview warmup，但这些维护不得阻塞 Bun server、readiness endpoint 或 frontend overview；通知表索引和大批量 OA backfill 不得作为默认启动副作用。
- MiniMax/OpenCode 并发：`minimax-m3` 与 `minimax-m2.7` 是两路并行配置，都通过 OpenCode JSON 事件端口运行；每个 Code Queue task 必须使用独立的 OpenCode XDG data/config/cache/state 目录，禁止多队列并发任务共享同一个 OpenCode SQLite/WAL 状态目录，否则并发 smoke 会触发 `PRAGMA journal_mode = WAL` 之类的数据库锁或初始化错误。用于验证 k3s/k8s 链路的 MiniMax smoke 以“至少 4 个任务、分布到 2 个 queue、至少 2 个终态成功”为链路验收线；剩余失败如果是 OpenCode 最终回复捕获、业务任务判定或模型限流，应作为 Code Queue 执行可靠性问题单独排查，不能反推 k3s 代理链路失败。
- 默认出网代理：D601 active Code Queue Pod 必须默认把 `HTTP_PROXY`、`HTTPS_PROXY` 和 `ALL_PROXY` 注入给 Codex/OpenCode、`git`、`curl`、`npm` 等任务子进程；当前唯一上游是 D601 provider-gateway egress HTTP CONNECT 代理，并通过 Kubernetes `Service d601-provider-egress-proxy` 暴露给 `unidesk` namespace 内的 Pod。该 Service 通过 selector 指向 D601 上的 hostNetwork 桥接 Pod，桥接 Pod 在集群端监听 service port `18789`、在宿主侧只连接 `127.0.0.1:18789` 的 provider-gateway egress endpoint；不得再用手工 EndpointSlice、provider-gateway Docker bridge IP 或固定 `172.*` 地址作为长期拓扑。Pod 内代理 URL 使用 `http://d601-provider-egress-proxy.unidesk.svc.cluster.local:18789`，provider-gateway 宿主端口仍只允许绑定 `127.0.0.1`，不得开放公网；桥接 Pod 或 provider-gateway 重建后必须用 Code Queue `/health.egressProxy.connected=true` 验证。这里的 provider-gateway 只承担出网代理，不承担 Code Queue 业务 HTTP 代理；业务访问仍只能走 Kubernetes API service proxy。k3s/k8s 原生 egress gateway、service mesh 或 CNI egress policy 只作为后续网络层增强方向，当前交付态不引入第二套出网控制面。远程开发/执行容器不得只依赖这些环境变量，必须在容器网络层用 TUN 默认路由和 OUTPUT 防火墙强制外网流量只能经 master TUN 出口。
- 出网代理无 fallback 纪律：Code Queue 的运行时配置只允许一个默认出网路径，即 provider-gateway egress proxy；不得在代码中同时保留 Code Queue 自建 WebSocket proxy、临时 shell proxy、D601 本地直连公网、主 server direct HTTP proxy 等隐式分支。任何新增网络 fallback 都必须先进入本参考文档并配套 `/health` 可见状态，否则视为残留旧路径。
- 上线纪律：Code Queue 相关的前端或后端改进必须在同一任务内正式上线并验证公网 frontend 或 live API，不能只停留在源码、构建产物或“后续再上线”。修改 Code Queue 自身时不得等待当前 Code Queue task 结束、等待 queue idle 或等待 `0 running` 后才重启；D601 active 实例的后端部署必须经未来受控 target-side CD 路径执行 build-first 镜像替换、k3s image import、manifest apply、rollout 和健康验证，并用 k3s adapter、Code Queue live API 或公网 frontend 证明任务和队列仍可读可继续。
- 期望状态部署：Code Queue 仍由 `deploy.json` 的 repo 与 commit 声明版本，但维护通道直连 D601 不能再用 `deploy apply --service code-queue` 或 `codex deploy <commitId>` 部署 Code Queue。未来受控 Code Queue CD 应复用 `docs/reference/deploy.md` 的 target-side build 规范在 D601 构建、导入 k3s、rollout 并验证 live commit，不得维护第二套部署语义。
- 更名与灾备恢复：旧版 Codex 队列服务名只允许作为兼容诊断和一次性迁移来源；`code-queue-backend` 容器自身 `/health` 正常但 `microservice health code-queue` 返回 provider 直连错误时，优先判定为 backend-core 仍加载旧 `MICROSERVICES_JSON` 或 adapter manifest 未刷新，必须刷新 `.state/docker-compose.env`、重建/替换 `backend-core` 与 `k3sctl-adapter`，随后用 `microservice list` 验证 `code-queue` 的 `runtime.orchestrator=k3sctl`、`backend.proxyMode=k3sctl-adapter-http` 和无业务容器直连摘要。正式 k3s 部署成功后，旧 direct Docker `code-queue-backend` 必须停止并移除，不能与 `code-queue-scheduler` 同时运行；否则会形成双 scheduler、双健康来源和错误的恢复判断。
- Codex 认证：容器必须从 D601 的 `/home/ubuntu/.codex/config.toml` 同步 Codex provider 配置到 D601 `.state/code-queue/codex-home/config.toml`，并只读挂载 `/home/ubuntu/.codex/auth.json` 到容器 `/root/.codex/auth.json` 后同步到 `.state/code-queue/codex-home/auth.json`，让 `codex app-server` 使用与 host 一致的 provider 登录态；同时通过 D601 `.state/code-queue-d601.env` 或 k8s `code-queue-env` secret 透传 `OPENAI_API_KEY`、`CRS_OAI_KEY` 等 provider 所需变量。这些 provider 环境变量和 auth 文件不得写入仓库，必须由 D601 运行时文件或 k8s secret 注入，确保容器重建和重启后不会丢失认证。新增 provider 的 `env_key` 时必须增加同类运行时透传和 Compose/k8s 持久化，禁止把 Codex 或 MiniMax 密钥写入仓库文件。Code Queue 容器必须只读挂载 D601 WSL host 的 SSH 目录到 `/root/.ssh`（默认 `/home/ubuntu/.ssh`），让容器内 `git push`、`ssh -T git@github.com` 与 WSL host 使用同一套 GitHub SSH key/known_hosts；不得把私钥复制进镜像或仓库。
- Skill 注入边界：DEV Code Queue scheduler/read/write Pod 必须把宿主 `/home/ubuntu/.agents/skills` 只读挂载到容器 `/root/.agents/skills`，并设置 `UNIDESK_SKILLS_PATH=/root/.agents/skills`，让执行任务能读取 `cli-spec` 等技能；只允许挂载 skill 目录本身，不得把宿主 `~/.agents`、`~/.codex`、token、auth JSON 或其他隐私配置整体暴露给任务容器。`/health` 和 `/api/dev-ready` 必须暴露非敏感 `skills` 状态：路径、exists、available、readonly、skillCount、`cliSpecAvailable` 和修复建议；CLI `codex dev-ready` 可读取该摘要。当前交付只要求 DEV manifest 和旧 direct Compose 诊断路径具备只读 skill 注入；PROD Code Queue 发布前必须单独审查隔离级别，不能把 DEV 桥接模式直接推广为生产默认。
- Develop-ready 镜像：Code Queue 镜像必须在启动前预装 UniDesk/Pipeline 调试所需工具，至少包含 `codex`、`bun`、`node`、`npm`/`npx`、`git`、`rg`、`curl`、`python3`/`pip3`、`docker`、`docker compose`、`docker-compose`、`jq`、`ssh`、`rsync`、`make`、`gcc`/`g++`、`iptables`、`tar`、`gzip` 和 `unzip`；不得依赖 Codex 任务运行时再 `apt-get install` 这些基础环境。
- 远程开发容器与任务执行 Provider：Code Queue 必须能通过 live API 拉起 D601 等计算节点上的开发容器，入口为 `POST /api/dev-containers/<providerId>/start`，默认 Provider 为 `D601`。该流程由 Code Queue 调用 UniDesk SSH 维护桥在目标节点创建 `unidesk-codex-dev-<providerId>`；人工入口写 `trans <providerId>`，内部服务调用仍复用同一 route parser 和 broker。在 Code Queue 所在节点与开发容器之间建立 `ssh -w` TUN 点对点链路；服务所在节点负责对开发容器的 TUN 源地址做 NAT/MASQUERADE，开发容器默认路由和 DNS 改走该 TUN，从而让 `ping google.com`、DNS、HTTP(S) 等出网都经主 server 全局代理，而不是依赖 D601 本地网络。提交 Code Queue 任务时必须支持选择执行 Provider：`D601` 在 D601 原生 k3s 的 active Code Queue scheduler/runner Pod 中本机执行，默认工作目录为 `/workspace`，并且 `/workspace` 必须映射 D601 WSL host 的 `/home/ubuntu`；同一个 hostPath 还必须挂载到容器内 `/home/ubuntu`，让 WSL home 里的绝对 symlink（例如 `/workspace/cq-deploy -> /home/ubuntu/unidesk-code-queue-deploy`）在任务中可解析，不能只看到 symlink 名而无法进入目标目录。`/root/unidesk` 与 `/app` 必须单独映射 `/home/ubuntu/cq-deploy` 作为服务部署仓库；其他 Provider 在对应 `unidesk-codex-dev-<providerId>` 容器中执行，默认工作目录为 `/home/ubuntu`，可按任务覆盖 `cwd`。远程任务启动前必须自动复用或拉起该 Provider 的开发容器、同步 Codex 配置和允许的运行时 provider 环境变量，并通过同一 master TUN/NAT 链路出网；目标 host 存在 `/mnt` 时，开发容器必须挂载 host `/mnt:/mnt`，确保 D601 这类 WSL 节点的 Windows 盘符路径如 `/mnt/f/Work/ConStart` 在任务容器内可见，避免 agent 因缺少真实工作区而搜索到无关项目。TUN 建立必须幂等处理 stale 状态：启动前清理旧 `tun<id>`、默认路由、旧 tunnel SSH 进程和旧 OUTPUT 跳转，缺失旧设备不能导致失败，冷启动运行时准备要有有界但足够的 timeout。TUN 建立后必须创建 `UD-CQ-EGRESS-<provider>` OUTPUT 链，规则只允许 loopback、既有连接、`tun<id>` 出口以及到 master server 的 SSH tunnel 控制连接，随后 reject 其他 IPv4/IPv6 出站包；这条网络层封口是开发/执行容器的权威外网边界，不能用 `HTTP_PROXY`/`NO_PROXY` 环境变量替代，容器镜像也必须使用已解析出的唯一 `unidesk-code-queue:<provider>` 或显式 `image`，缺失时直接失败，禁止 provider-gateway image、`latest` 或其他隐式镜像 fallback。验收必须保留三类日志：容器建隧道后 `ping google.com` 成功、强制指定原 Docker 网卡直连外网被 `sealed_direct_ping=blocked_expected` 拦截、服务所在节点上对应 `UNIDESK-CODEX-DEV-<providerId>` NAT 链或 `tun<id>` 计数在 ping 前后增长；涉及 WSL 工作区任务时还必须在开发容器内验证目标 `/mnt/...` 路径可读。`GET /api/dev-containers/<providerId>/status` 必须展示默认路由、`route_8_8_8_8`、`egressFirewallChain` 和 OUTPUT 链跳转。开发容器代理密钥只生成到 `.state/code-queue/dev-proxy/` 与目标节点用户目录，不得提交到仓库。
- 远程维护桥调用：Code Queue 已迁移到 D601 后，Code Queue 后端 Pod 内没有主 server 的 `unidesk-backend-core` 容器，不能再把 `trans ...` 实现为本地 `docker exec unidesk-backend-core`。Code Queue runner 发起的 provider 维护命令必须通过主 server frontend authenticated `/ws/ssh` 流式代理进入 backend-core SSH bridge，再由目标 provider-gateway 执行 Host SSH/WSL SSH；stdout/stderr 直接流回 runner，不能经过 `/api/dispatch` task polling 或 JSON compact。需要传递 `sh`/`bash` stdin shell body、`py` 或 `apply-patch` 时也使用同一条 stdin 流式通道，避免恢复到本地 Docker broker、手工 base64 分块上传、交互 shell fallback 或多层引号。
- 远程 Provider 准备不得阻塞控制面：Code Queue 在请求处理、队列调度、远程开发容器准备、Host SSH/WSL SSH 透传、Codex/OpenCode 启动和日志导出路径中，禁止使用会长时间占用 Bun event loop 的同步子进程调用，例如针对远程 Provider 的 `spawnSync`、`execSync` 或 `execFileSync`。远程命令必须通过异步子进程执行，带显式 timeout、超时 kill、stdout/stderr 上限和任务 output 进度记录；远程准备失败只能让对应任务进入失败或 retry，不能让 `POST /api/tasks`、SSE `/api/events`、`/health`、overview 或 frontend/core 用户服务代理等控制面请求等待远程 SSH 结束。凡是改动 D601/远程 Provider 准备、`api/dev-containers/*`、任务入队启动或 `runCodeQueueSsh` 等路径，验收必须在一个远程 SSH/status/start 探针运行期间并发验证容器直连 `/health` 和 `/api/tasks/overview` 仍能在 1s 内返回，证明远程超时不会复发为全站刷新卡死。
- OpenCode 远程执行：`minimax-m3` 与 `minimax-m2.7` 两路并行配置走 OpenCode JSON event port 时，本地和远程命令都必须显式执行 `opencode run ...`；远程 Docker exec 不得退化成 `exec run ...`，否则会在目标容器内变成 `bash: exec: run: not found`。OpenCode JSON stream 的终态判定以“当前进程退出码 + 当前 attempt 的最终 assistant response”为准：`exit=0` 且当前 attempt 产生非空最终回复时，即使上游没有发 `step_finish` 事件，也应视为正常 terminal；非零退出、无当前最终回复或传输关闭才进入 retry。每个 attempt 的 `finalResponse` 必须只来自当前 OpenCode/Codex turn，禁止在当前 turn 未产出最终回复时回退复用 task 上一次 `finalResponse`，否则会把旧任务内容误判为本轮完成。
- Codex 控制：服务内部启动 `codex app-server --listen stdio://`，用 JSON-RPC 调用 `thread/start`、`turn/start`、`turn/steer` 和 `turn/interrupt`，并监听 `turn/completed`、assistant delta、reasoning delta、command output delta、file diff delta 等通知生成前端可轮询的 transcript。
- 用户输入持久化：任务初始 prompt 以 `basePrompt/displayPrompt` 作为结构化来源，运行中追加的 `turn/steer` prompt 必须写入 `promptHistory`；transcript 构建时从这些结构化字段合成 `Submitted prompt` 和 `Steer prompt`，不能只依赖有 600 条上限的 raw output，否则长任务输出增长后会丢失关键人工指令。
- 队列语义：`POST /api/tasks` 或 `/api/tasks/batch` 入队，服务始终只运行一个 Codex turn；当前任务真正终止后才推进下一个任务。`GET /api/tasks` 与 `GET /api/tasks/{id}` 返回队列、attempt、judge 和输出；`GET /api/tasks/{id}/summary` 返回按任务 ID 查询的结构化摘要，包括初始 prompt、最后 assistant message、工具调用摘要、attempt、judge、错误和耗时；CLI 入口是 `bun scripts/cli.ts codex task <taskId>`。`GET|POST /api/tasks/{id}/judge?attempt=N` 与 CLI `bun scripts/cli.ts codex judge <taskId> --attempt N` 用于单步复现指定 attempt 的 judge，必须复用真实队列 worker 的上下文构建、prompt 压缩、MiniMax 调用、JSON 去噪/repair 和 fallback 路径；`dryRun=1`/`--dry-run` 只输出 prompt/payload 和重建诊断，不调用 MiniMax。`POST /api/tasks/{id}/steer` 向运行中 turn 推入 prompt；`POST /api/tasks/{id}/interrupt` 或 `DELETE /api/tasks/{id}` 打断/取消；`POST /api/tasks/{id}/retry` 手动重试。队列 worker 必须隔离单个 task 的异常，不能因为某个 app-server、数据库 claim、judge 异常、judge 超时或 judge 判定 `fail` 让后续 queued 任务停止；`fail` 只把当前任务标为 failed，随后必须继续扫描并推进下一个 queued/retry_wait 任务。数据库 claim 必须有硬超时且失败时释放 active run slot；judge 必须有独立 watchdog，超时后走 fallback judge 并继续推进。当存在 queued/retry_wait 且 worker 空闲时，watchdog 必须自动重新调度。
- 稳定性与重启恢复：Code Queue 的第一目标是长期稳定可用；部署修复或运维排障时不得因为担心容器重启会打断任务而拒绝重启、重建或替换 active Pod。容器重启、服务进程重启和镜像替换后，队列、`promptHistory`、running/judging/retry_wait 任务和 active session 元数据必须从 PostgreSQL 恢复，并在已有 `codexThreadId` 可用时用 `thread/resume` 和 continuation prompt 无缝继续当前任务；如果原 app-server turn 已丢失，也必须把当前任务恢复到可 retry/continue 的状态，不能错误推进下一个任务或永久卡住。D601 侧重建必须走未来受控 target-side CD 路径；禁止先手工 `docker rm`、只手工 `docker compose up` 或用维护通道直连 D601 部署 Code Queue 再依赖后续命令补救，因为中断窗口会让 Pod/容器消失并触发 frontend/core 用户服务代理失败。重启后出现 active task 丢失、手动 steer/interrupt 记录丢失、running 任务卡死、误判完成、跳过当前任务、容器消失或阻塞队列，均属于 Code Queue 的 P0 核心缺陷，必须先修复并补充 restart-recovery 验收，不能把“避免重启”作为交付策略。
- 调度与 active run slot：Code Queue 必须把“queue processor 正在等待/退避/轮询”和“实际占用 Codex/OpenCode 子进程运行槽”分开建模；`CODE_QUEUE_MAX_ACTIVE_QUEUES` 只限制真实 active run slot，不能把 retry backoff、等待内存下降或等待前序任务的 `processingQueues` 计入 active slot，否则设置全局 active slot 上限时，一个空等队列会把其他 runnable queue 永久饿死。多个 queue 同时等待 active slot 时必须显式维护 FIFO waiter 队列，避免某个长 retry/backoff 队列刚释放 slot 就立刻重抢，导致更早进入等待的 `retry_wait` 任务长期饥饿；`/health` 必须同时暴露真实 `activeQueueIds`、`activeRunSlotCount`、等待中的 `processingQueueIds` 和 active slot waiters，排障时以 active run slot 与 waiter 顺序判断是否真的有任务在跑、谁应下一个启动。restart-recovery 后的 `retry_wait` 任务若缺失 `codexThreadId`/OpenCode session id，不得无限拒绝 retry；必须用紧凑 recovery prompt 和原始任务摘要重新开一个 agent thread/session，让任务继续推进并在 Trace 中留下 recovery 证据。任何修改 scheduler、retry backoff、queue move、manual retry、shutdown recovery 或内存等待逻辑时，都必须保留“空等 processor 不占 active run slot”、“等待者 FIFO 不饥饿”和“缺失 thread/session 可恢复”的自测或 live 验证。
- 内存优化过程与防回归：Code Queue 已迁移到 D601，但内存治理仍必须按“PostgreSQL 权威源优先、进程热状态最小化、容器硬上限兜底”的顺序设计。长期可复用的优化路径是：先确认任务、queue、readAt、promptHistory、active session 和通知 outbox 均可从 PostgreSQL 恢复；再把历史任务列表、详情、统计、Trace/output 和 `/health` 的只读查询改为 PostgreSQL 直读或聚合查询；随后只把 `queued`、`running`、`judging`、`retry_wait` 等调度必需任务载入 Bun 堆，并在 PostgreSQL 查询侧裁剪 hot `output`/`events`；最后用 dirty-only flush、append-only 输出归档、Codex SQLite 小批量导出、`bun --smol`、`NODE_OPTIONS=--max-old-space-size=1024`、production scheduler k3s `request/limit memory=15Gi`、legacy Compose `mem_limit=15g`/`memswap_limit=16g` 和 cgroup memory watchdog 作为运行时防线。PostgreSQL 到进程的单次读取足够快，不能为了减少 SQL 查询把全部历史 `task_json`、Trace、output 或统计摘要常驻内存；任何新增缓存都必须有默认较小的环境变量上限、明确淘汰策略、可从 PostgreSQL 或 append-only 归档重建，且不得影响重启恢复。新增或修改 `/api/tasks`、overview、stats、summary、transcript、output、trace、health、flush、scheduler 和通知路径时，禁止在常规请求中调用会物化全量历史任务 JSON 的代码，禁止启动后无条件重写全量历史 task JSON，禁止用未设上限的 `Map`/数组保存历史 output/event/Trace，`CODE_QUEUE_MAX_ACTIVE_QUEUES=0` 表示不按 queue 数量设置全局排队上限，不等价于常规 5 / burst 10；如显式设置为正数，必须同时说明内存预算并补充内存压测验收。memory watchdog 必须以 cgroup working set 为主要判断，且在 swap 仍有余量时不得提前杀掉唯一 active run；否则 TypeScript/Playwright 这类短时高内存验证会被错误中断并让 retry 队列反复震荡。
- 列表/详情延迟优化原则：Code Queue 控制面交互的长期目标是常规历史规模下首屏、`GET /api/tasks/overview`、`POST /api/tasks/<id>/read` 和分页加载均在 1s 内完成；性能面板出现十几秒级 `core_proxy` 或 Code Queue 用户服务代理慢操作时，必须优先按后端查询形态和前后端通信策略定位，不能把问题归因于 React 渲染后只改 UI。后端优化顺序是：先为 queue、status、updated/created 时间、readAt/terminal unread 和常用筛选条件补齐 PostgreSQL 索引；再用 SQL `COUNT`、`GROUP BY`、条件聚合和分页 ID 查询生成 queue/status/stats/unread 摘要；随后按 ID 轻量加载当前页、selected、active 和 unread priority task，禁止为了列表或已读操作解析完整 Trace、output archive、Codex transcript 或物化全量历史 `task_json`。`read`/`read-all` 这类 mutation 必须是 SQL-only 更新并返回最小 patch/queue 计数，不能触发 overview 全量重算或重载所有任务；启动 warm 只能预热小体积聚合和索引路径，不得把历史任务作为常驻缓存。允许 frontend/backend 代理使用秒级、严格有界、mutation 自动失效的 overview micro-cache 来吸收重复刷新，但 cache 只能作为抖动保护，不能替代数据库索引、聚合查询和分页披露，也不能让 stale readAt/queue/status 状态跨设备可见。
- Trace/实时输出热路径防回归：Code Queue 的 `appendOutput`、output archive append、`publishTaskEvent`、SSE `/api/events`、任务列表、overview、task meta 和 `/health` 都属于热路径，必须保持 O(1) 或明确小常数上界；这些路径不得同步调用完整 transcript 构建器、`taskFullOutput`、output archive 全量读取、Codex session/log 文件解析、完整 `task_json` 物化或任何会随历史输出长度增长的统计。输出追加时必须增量维护轻量持久化指标，至少包括 `stepCount`、`llmStepCount`、`outputMaxSeq` 或等价字段；列表、overview、meta、SSE 事件和 `/health` 只能读取这些指标或小体积 SQL 聚合。完整 Trace、`trace-summary`、`trace-steps`、`trace-step`、transcript/output 详情允许在显式详情请求中解析归档，但必须分页或有界、使用短 TTL 或容量受限缓存，并在 archive append 后失效。若 frontend 性能面板出现 Code Queue 用户服务代理 502、`/api/tasks/overview`/trace 接口成批超时，或容器内 `/health` 在 active output 持续追加时也卡住，优先按 Bun event-loop starvation/backpressure 排查，而不是先改 React 渲染；修复必须证明热路径不再随 output/archive 历史线性增长。
- Trace STEP 权威来源：`GET /api/tasks/<id>/trace-steps` 与 `GET /api/tasks/<id>/trace-step` 必须直接从 `oa-event-flow` 读取 `trace-step-created` 事实事件，并在响应中暴露 `source=oa-event-flow`；不得在 OA 事件缺失、读取失败或本地 transcript 数量更多/更少时静默回退到 `task-transcript`、Codex session JSONL、output archive 或内存热状态。OA 事件不可用应显式失败或返回空事实集，避免 STEP 计数和执行过程摘要重新形成双路径分叉。
- 完成判定：app-server `turn/completed` 的 `turn.status=completed|interrupted|failed` 只代表 Codex turn 已结束；即使 `completed` 也必须把原始任务、当前 attempt 的 assistant 最终回复、command/file-change 事件、stderr tail 和 current attempt events 组成 execution record 交给 judge 判断是否真的完成。MiniMax judge 输入必须做有界压缩，保留终态、最终回复、关键错误/命令/部署证据和摘要计数，避免长 transcript 让 MiniMax 请求超时；默认 `UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS=90000`。MiniMax judge 之前和之后都必须保留少量协议级硬门禁：明确用户 interrupt 判为 fail；当前 attempt `terminalStatus=failed|null`、传输在终态前关闭、或当前 attempt 最终回复为空时判为 retry；这些门禁只保护“本轮 turn 是否可被验收”的事实，不得发明业务实现要求。协议门禁通过后，配置了 `UNIDESK_CODE_QUEUE_MINIMAX_API_KEY` 且 MiniMax 可用时，MiniMax `MiniMax-M2.7` 对业务是否 `complete|retry|fail` 的判定是权威结果；当且仅当 MiniMax LLM 调用失效（未配置、额度/限流/网络/超时不可用、JSON 去噪与 repair 全部耗尽、或返回超预算反馈且修复耗尽）时，才允许启用非 LLM/fallback 判断。MiniMax 返回必须先做 JSON 去噪，支持去除 Markdown fence、`json` 标签和从夹杂文本中提取平衡 JSON object；如果去噪后仍无法解析，服务必须把解析错误和上一轮去噪前原始回答反馈给 MiniMax 做 JSON repair 重试，重试次数由 `UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_REPAIR_ATTEMPTS` 控制，默认 `2`，耗尽后才进入 fallback，并在 fallback 原因、task JSON、attempt summary 和 TraceView 中保留 MiniMax 失败阶段、是否超时、耗时、prompt/payload 大小、HTTP 状态、错误名和响应预览。
- Judge 权威边界：MiniMax 成功返回可解析、预算内的 judge JSON 后，Code Queue 不得用旧 attempt 的 429/exceeded retry limit 证据、历史 output 字符串、面向特定任务的正则或 `hardCompletionBlockers`/`retryRequiredReasons` 覆盖一次协议有效的完成判定；尤其不能因为 attempt 1 的限流中断仍在历史输出里，就禁止 MiniMax 把 attempt 2 的正常完成判为 `complete`。允许的本地 safety override 必须限定为协议事实和系统交付纪律：用户显式打断、当前 attempt 未正常终止、当前 attempt 没有最终 assistant response、最终回复停在并发文件确认而非交付、或 runtime/UI/service 变更承认未部署验证。所有 override 都必须写入 `_safetyOverride`、生成紧凑 continuation prompt，并由自测或 judge probe 覆盖；不得把业务猜测伪装成本地硬门禁。
- Retry/推进语义：`retry` 不是新开一个独立任务或完全新 session；只要已有 `codexThreadId`，服务必须 `thread/resume` 原 thread 并 append 一个继续执行 prompt。continuation/judge feedback prompt 只应携带本轮缺口、恢复原因、验收要求和有界原始任务摘要，禁止重新注入完整引用上下文、历史 transcript 或长 JSON；服务重启恢复类 feedback 尤其必须保持短 prompt，依赖现有 thread 上文继续。超长 prompt 必须在 prompt 合成源头解决：每个 feedback/recovery/judge 生成器都要从结构化字段选择必要信息、去重合并缺口并提供按需查询入口，禁止先合成超长 prompt 再在末端用 substring/safePreview 一刀切硬截断；硬截断会静默丢失验收信息，风险高于长 prompt 本身。若 MiniMax `continuePrompt` 超出预算，必须要求 MiniMax 基于原始 judge 输入重新合成紧凑反馈，repair 耗尽后才可进入 fallback；不得把已生成的长 prompt 截尾后发送给 Codex。若 MiniMax 成功返回了预算内 `continuePrompt`，必须原样使用该反馈，不得再用 71-Freq、`period_sum/mpu_read_num`、`mpu_read_num`、历史限流中断等字符串识别把它覆盖成“简洁原始需求 continuation”。只有 judge 判定 `complete` 后，队列 worker 才把当前任务标为成功并推进下一个 queued/retry_wait 任务。非 LLM/fallback 判定产生的 `retry` 最多累计 `3` 次；达到上限后当前任务必须转为 `failed` 并记录原因，worker 继续推进后续 queued/retry_wait 任务，避免 fallback safety override 或硬编码判断造成无限循环。
- Judge 探针与复现：`GET|POST /api/judge/probe` 使用同一套 judge 逻辑跑内置 synthetic execution records，覆盖正常完成、正常结束但只给计划、未上线/未部署的服务或 WebUI 改动、传输中断和用户打断等样本，返回 `hits`、`total`、`hitRate`、每例 `expected` 与 `decision`；该接口不得回显 MiniMax API key。真实任务排障必须优先使用 `codex judge <taskId> --attempt N` 或 `/api/tasks/{id}/judge?attempt=N`，响应要包含 attempt 窗口、promptChars/payloadBytes、stored judge 对比、MiniMax 失败阶段和是否因历史 per-attempt events 缺失而降级为 retained events 重建。
- 模型选择：默认 Codex 模型是 `gpt-5.5`，内置模型队列包含 `gpt-5.5`、`gpt-5.4-mini`、`gpt-5.4`；`gpt-5.5` 的默认 reasoning effort 必须是 `xhigh`，可通过 `CODE_QUEUE_MODEL_REASONING_EFFORTS` 追加或覆盖模型级默认值；每个入队任务可通过前端模型下拉菜单或 API 覆盖 `model`、`cwd`、`reasoningEffort` 和 `maxAttempts`，`maxAttempts` 上限为 `99`。Judge 判定 `retry` 或非用户取消类 `fail` 时必须继续已有 `codexThreadId`，不能新建 session；重试间隔使用指数退避，从 `1s` 开始，最大 `10min`。MiniMax 不可用而进入 fallback/non-LLM 判定时，当前 attempt 的 429、Too Many Requests、exceeded retry limit、overloaded、stream disconnected 等服务/限流错误应判定为 `retry`，不能当作完成；MiniMax 可用时，这些内容只能作为当前 attempt 的 factual evidence 提供给 MiniMax，不能通过硬编码覆盖 MiniMax 结果。
- 状态与日志：`D601` 默认工作目录为容器内 `/workspace`，该路径映射 D601 WSL host 的 `/home/ubuntu`；容器内 `/home/ubuntu` 也必须映射同一个 hostPath，保证从 `/workspace` 看到的绝对 symlink 可以继续解析到真实文件。服务自身仓库路径 `/root/unidesk` 和 `/app` 单独映射 D601 WSL host 的 `/home/ubuntu/cq-deploy`。其他 Provider 的任务默认工作目录为 `/home/ubuntu`，任务 JSON、列表、Trace 摘要和 CLI 查询都必须显示 `providerId`、`executionMode` 与最终 `cwd`。`executionMode=default` 在 D601 本机 Code Queue 容器中运行 Codex/OpenCode；`executionMode=windows-native` 只允许非本机 WSL Provider、Codex 模型和 `/mnt/<drive>` 工作目录，Code Queue 仍会启动远程执行容器，但容器只运行 stdio relay，经 WSL bridge 调用 Windows 宿主原生 `codex app-server --listen stdio://`。Code Queue 的任务、queue、`readAt`/未读状态、attempt、judge、`promptHistory`、active session 元数据、控制状态和 ClaudeQQ 通知 outbox 一律以主 PostgreSQL 为权威，分别写入 `unidesk_code_queue_tasks`、`unidesk_code_queue_queues` 与 `unidesk_code_queue_notifications`；`DATABASE_URL` 是必需配置，服务不得在 PostgreSQL 缺失或不可用时进入文件存储模式。`.state/code-queue/state.json` 不再作为任务或 queue 状态存储，不得重新引入本地 JSON fallback；服务启动必须以 PostgreSQL 为唯一来源恢复队列，并把 running/judging 任务恢复为 retry_wait。D601 资源比主 server 宽裕，但 Code Queue 仍必须把“内存是稀缺资源”作为核心设计约束：历史任务列表、详情、统计和只读 Trace 查询优先从 PostgreSQL 直读，进程内只保留当前 running/judging、queued、retry_wait 等调度必需热任务；需要短期热缓存时必须有严格上限、可裁剪、可从 PostgreSQL 和 append-only 输出归档重建。WebUI 不得用 browser `localStorage`、`sessionStorage` 或 IndexedDB 持久化 task/queue/readAt/unread 等业务状态；浏览器只能保留临时 UI 内存缓存，刷新后必须重新从后端读取 PostgreSQL 权威数据。Codex CLI-like output/Trace 的完整记录可以使用 append-only 文件作为日志型归档，但任务状态、未读状态和列表摘要不得依赖这些文件作为权威来源；`/api/tasks/<id>/transcript` 与 `/api/tasks/<id>/output` 必须能分页重建完整历史，不得因为热状态裁剪而丢失早期 trace。WebUI 必须支持多 queue 查看、显式创建 queue、提交时下拉选择 queue、提交时下拉选择执行 Provider 和执行模式，并支持把已创建且非 active 的任务移动到其他 queue；queue 内串行，queue 间默认并行且不互相排队；`CODE_QUEUE_MAX_ACTIVE_QUEUES` 仅作为显式配置的全局 active slot 上限，`0` 表示不按 queue 数量限流，内存不足时由 cgroup memory pressure 阻止新 run 并在任务响应中暴露 `QUEUED(MEM LIMIT)`。Code Queue 镜像必须内置 Playwright Chromium 浏览器与系统依赖，并使用 `bun --smol` 运行后端，保证队列任务能直接执行公网 frontend Playwright 回归且主进程内存可控。日志写入 D601 `.state/code-queue/logs` 挂载的 UniDesk JSONL 日志；Codex app-server 上游产生的 `logs_*.sqlite` 只能作为短暂缓冲，必须由 Code Queue 周期性导出为 JSONL，避免重新形成大 SQLite 文件；`/logs` 端点返回最近结构化日志。`/health` 的 `queue.storage.primary` 必须恒为 `postgres`，并通过 `queue.storage.postgresReady`、`queue.devReady` 和 `/api/dev-ready` 暴露 PostgreSQL 可用性、develop-ready 自检、必需工具、Docker socket、`docker compose`、默认工作目录、Codex config 状态和 `/root/.ssh` 共享 SSH key 状态；D601 本机默认执行必须能通过 `/root/.ssh` 复用 WSL host GitHub SSH key 执行 Git over SSH，跨 Provider SSH 或 `windows-native` 任务同样需要 `queue.devReady.ssh.ready=true`。Codex CLI-like 输出可能很大，服务必须节流状态持久化，禁止对每个 output delta 同步重写完整 state 导致 `/health` 和控制 API 卡死；容器 healthcheck 必须使用带超时的 HTTP 探针，不能留下堆积的无超时探针进程.
- ClaudeQQ 通知：Code Queue 在 D601 k3s 上通过 `CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL=http://claudeqq.unidesk.svc.cluster.local:3290` 调用 k3s 代管 ClaudeQQ 后端 `POST /api/push/text`，并在旧 ClaudeQQ 只暴露 `/api/send/text` 时按同一目标自动兼容该接口；在每个任务进入 `succeeded`、`failed` 或 `canceled` 终态后向配置目标发送最终 response，并附带 task id、queue、状态、模型、attempt、当前 running/queued/retry_wait 数和任务总耗时；当所有 queue 进入 `0 running / 0 queued` 空闲态时，必须单独发送一次空闲提醒。通知由 `CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED` 控制，目标由 `CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE=private|group`、`CODE_QUEUE_NOTIFY_CLAUDEQQ_USER_ID`、`CODE_QUEUE_NOTIFY_CLAUDEQQ_GROUP_ID` 配置，默认私聊 `645275593`；代理基址、最终 response 最大字符数、单次超时和发送尝试次数分别由 `CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL`、`CODE_QUEUE_NOTIFY_CLAUDEQQ_MAX_RESPONSE_CHARS`、`CODE_QUEUE_NOTIFY_CLAUDEQQ_TIMEOUT_MS` 和 `CODE_QUEUE_NOTIFY_CLAUDEQQ_SEND_ATTEMPTS` 配置。任务终态和队列空闲通知必须先写入 PostgreSQL outbox 表 `unidesk_code_queue_notifications` 再异步发送；不得使用 `.state/code-queue/claudeqq-notifications.json`、`CODE_QUEUE_NOTIFY_CLAUDEQQ_OUTBOX_PATH` 或任何本地 JSON 作为通知权威存储。发送失败、NapCat 离线、代理 502 或容器重启时不能丢通知，必须按 `CODE_QUEUE_NOTIFY_CLAUDEQQ_RETRY_INTERVAL_MS` 指数退避重试并跨进程/容器重启保留。`/health` 的 `queue.notifications.claudeqq` 必须暴露非敏感配置、目标配置状态和 PostgreSQL outbox 统计；`GET /api/notifications/claudeqq` 返回 outbox 明细，`POST /api/notifications/claudeqq/drain` 手动触发发送，`POST /api/notifications/claudeqq/backfill` 可按 `since` 补入某次故障窗口内已终态任务，确保 QQ/NapCat 超时或离线不会让任务完成通知永久丢失。

### ClaudeQQ k3s-Managed

当前 ClaudeQQ 作为 `id=claudeqq` 的 `k3sctl-managed` 用户服务登记在 `config.json`，业务实例由 D601 k3s 控制面代管：

- Orchestrator：`deployment.mode=k3sctl-managed`，`deployment.adapterServiceId=k3sctl-adapter`，`deployment.k3sServiceId=claudeqq`，`backend.proxyMode=k3sctl-adapter-http`，`backend.nodeBaseUrl=k3s://claudeqq`；backend-core 对 ClaudeQQ 的正式链路只能是 `frontend -> backend-core -> k3sctl-adapter -> Kubernetes API service proxy -> Kubernetes Service claudeqq:3290`。
- 部署引用：ClaudeQQ 业务源码来自 `https://gitee.com/lyon1998/agent_skills` 的 `claudeqq` 子目录；镜像构建 Dockerfile 和 `0.0.0.0:3290` API 适配器由 UniDesk 仓库 `src/components/microservices/claudeqq/` 作为 k3s 部署资产注入，不能依赖 D601 未提交工作树文件。Kubernetes 运行清单为 `src/components/microservices/k3sctl-adapter/k3s/claudeqq.k8s.yaml`，`config.json` 对外记录 k3s manifest `src/components/microservices/k3sctl-adapter/k3s/claudeqq.k3s.json`。dev 环境使用 `src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-claudeqq.k8s.yaml` 和 `unidesk-dev-claudeqq.k3s.json`，服务名为 `claudeqq-dev`。旧 `docker-compose.unidesk.yml` 只作为迁移/本地诊断参考，不是正式运行入口。
- 运行对象：ClaudeQQ Pod 在 D601 上以同 Pod 双容器运行 `claudeqq` 和 `napcat`，仅通过 ClusterIP Service 暴露 `claudeqq:3290` 给 UniDesk 代理和 Code Queue 通知链路；NapCat 的 `3000/3001/6099` 只在 Pod 内可达，不得作为 NodePort、hostPort 或 provider-gateway 业务直连目标。
- 持久化路径：NapCat 登录态保存在 D601 hostPath `/home/ubuntu/.agents/skills/claudeqq/napcat/qq`，NapCat 配置和二维码缓存分别保存在 `napcat/config` 与 `napcat/cache`；ClaudeQQ 后端挂载同一业务目录中的 `config.json`、`bot_workspace`、`logs`、`.state` 和只读 `napcat`。这些路径不得改成匿名 volume，避免重建 Pod 后 QQ 登录态和事件/订阅状态丢失。
- 代理/API：只允许 `/health`、`/logs` 和 `/api/` 前缀；允许方法为 `GET`、`HEAD`、`POST` 和 `DELETE`。`POST /api/push/text` 接受 `userId` 或 `groupId` 与 `message`，由 ClaudeQQ 通过同 Pod NapCat HTTP API 发送 QQ 消息；NapCat 不可用时必须快速返回 `status=napcat_offline` 或可解释错误。
- Dev/prod CD：ClaudeQQ 的 dev/prod rollout 都必须走 D601 registry artifact consumer。CI 从 Gitee commit 导出 `claudeqq/` 源码并 overlay UniDesk Dockerfile/adapter，CD 验证同一个 commit-pinned artifact contract，dev 落到 `unidesk-dev/claudeqq-dev`，prod 落到 `unidesk/claudeqq`，健康检查通过 Kubernetes API service proxy 完成；不得回退到维护通道直连或 NodePort/hostPort。
- UniDesk 前端：`用户服务 / ClaudeQQ` React 页面负责展示 D601、仓库引用、私有 k3s 后端映射、NapCat 登录二维码、NapCat HTTP/WS 状态、事件缓存、订阅表、订阅创建表单、消息推送表单、主用户私聊账号 `645275593`、最近 QQ 事件和已发送记录；完整原始 JSON 只能通过显式 `查看原始JSON` 打开。浏览器只能通过 UniDesk frontend 同源代理访问 ClaudeQQ，不得直接访问 D601 `3290/3000/3001/6099`，也不得 iframe ClaudeQQ 旧 WebUI。

### Decision Center NC01 k8s-Managed

当前 Decision Center 生产运行面固定在 NC01 k8s `unidesk` namespace，运行对象是 `deployment/decision-center` 和 `service/decision-center`，并由 `config/unidesk-host-k8s.yaml` 选择目标。它用于沉淀 Codex/人工会议后的会议记录、决议、目标、问题分级、停放事项、证据和按天工作日记，同时也作为外部需求向内部目标分解的工作台。它只负责权威记录、Markdown 日记、需求管理和展示，不承载通用聊天、LLM 会话窗口或自动参谋对话。

- Orchestrator：正式链路是 `frontend/CLI -> NC01 k8s decision-center Service:4277`，CLI 通过受控 NC01 k8s entrypoint 进入 Pod 本地 HTTP 接口；不得把裸 Docker/Compose、NodePort、hostPort、provider-gateway 业务 HTTP 或旧 D601 Decision Center 作为生产入口。
- 部署引用：后端源码位于 UniDesk 仓库 `src/components/microservices/decision-center`，Dockerfile 为 `src/components/microservices/decision-center/Dockerfile`。当前生产部署由 YAML-first k8s 配置控制，目标、namespace、GitHub repo、base path 和 Secret sourceRef 必须来自 YAML；主 server `docker-compose.yml` 不得加入该服务，也不得公开 `4277`。
- 状态权威：Decision Center 持久化权威是 GitHub repo `pikasTech/decision-center-data` 的 `main:data`，GitHub SSH 凭据由 `decision-center-github-ssh` Secret 分发；PostgreSQL 只作为运行时索引/缓存，不再作为会议、决议、目标、问题或日记的 durable truth。不得使用浏览器 `localStorage`、IndexedDB、容器 writable layer 或本地 JSON 文件作为权威存储。
- 记录数据模型：记录类型为 `meeting|decision|goal|external_goal|internal_goal|blocker|debt|experiment`，等级/优先级为 `G0|G1|G2|G3|P0|P1|P2|P3|none`，状态为 `active|blocked|parked|done`，字段包含 `title`、Markdown `summary/body`、`priority`/`level`、`source`、`issueId`、`linkedGoalId`、`tags`、`evidenceLinks`、`sourceSession`、`taskId`、`commitId`、`createdAt` 和 `updatedAt`。
- 文书正规化：Decision Center 记录应逐步从松散需求记录升级为带文号的工程化文书系统，默认文号格式为 `DC-<TYPE>-<PRIORITY>-<YEAR>-<SEQ>`。`TYPE` 固定为 4 位英文缩写，当前保留 `DCSN` 决策/决议、`GOAL` 目标、`PLAN` 计划、`RPRT` 报告/态势评估、`ACTN` 行动项、`ISSU` 问题/阻塞、`RETR` 复盘、`RQST` 请示、`RESP` 批复/答复、`MINS` 纪要；`PRIORITY` 使用 `P0|P1|P2|P3`，G 级目标在文号中映射为 P 级紧迫度，其中 G0 默认映射 P0；`YEAR` 使用签发年份，`SEQ` 是同一 `TYPE+PRIORITY+YEAR` 下从 `001` 起递增的三位序号。示例：`DC-DCSN-P0-2026-001`、`DC-GOAL-P0-2026-001`。
- 文书元数据：正式产品化后，记录模型必须显式保存 `docNo`、`docType`、`docPriority`、`docSeq`、`signer`、`issuedAt`、`effectiveScope`、`supersedes` 和 `supersededBy` 等字段，并为 `docNo` 建立唯一约束；在 schema 完成前，临时过渡可以把文号写入 `title` 前缀、`tags` 和 Markdown `body` 首段，但不得把临时正文约定当作长期数据模型。
- 文书流转：正式文书应支持 `draft` 草拟、`review` 核稿、`issued` 已签发、`active` 执行中、`done` 办结、`void` 作废等状态或等价状态映射；Agent 可以起草、核稿和生成报告，但涉及战略优先级、外部目标和长期约束的 `DCSN`/`GOAL` 文书必须由用户签发后才进入已签发序列。编号一经签发不得复用，作废也必须保留编号和替代关系。
- 需求管理：Decision Center 里的 `external_goal` 记录应承接外部需求或外部目标，`internal_goal`/`goal` 记录应承接拆解后的内部目标，`decision` 记录应承接需求分解后的取舍，`blocker` 记录应承接当前阻塞，`experiment` 记录应承接验证性工作，`debt` 记录应承接必须偿还的技术/流程债。任何新需求都应先写成可验证的外部收益，再分解为这些内部记录，而不是先发散成内部审美或架构偏好。需求管理 API 复用 `decision_center_records`，`/api/requirements` 在同一模型上排除 `meeting`，并提供 list/create/show/update/upsert 的需求语义入口，不引入第二套需求表。
- 日记数据模型：基于 Markdown 的日记系统以“每天一篇”为最小单元，导入器识别 `# YYYY年M月D日`、`# YYYY-MM-DD` 或 `# YYYY/M/D` 标题并拆分为 `entry_date`、`month`、Markdown `body`、`source_file`、`content_hash` 与虚拟 `markdown_path=YYYY-MM/YYYY-MM-DD.md`；同一 `source_file + entry_date` 使用 upsert，内容未变时保持幂等；同一天存在多个 `source_file` 时，列表项应保留 `id` 与 `sourceFile`，按日期读取可用 `sourceFile` 查询参数消歧。
- 日记编辑：工作日记必须支持按真实日期创建当天条目，并支持按日期回看和编辑历史条目；`GET /api/diary/today` 按服务当前真实日期自动创建或返回当天条目，`PUT /api/diary/today` 保存当天 Markdown，`PUT /api/diary/entries/:idOrDate` 允许安全更新 `body`/`markdown`、`title`、`tags` 和 `sourceFile`，按 `YYYY-MM-DD` key 且不存在时创建当天或历史条目，按非日期 id 时只编辑既有条目。GitHub repo 是唯一持久化权威，前端只是编辑入口和展示入口。
- API：只允许 `/health`、`/live`、`/logs` 和 `/api/` 前缀；允许 `GET`、`HEAD`、`POST`、`PUT` 和 `DELETE`。业务 API 包含 `GET /api/records`、`POST /api/records`、`GET|PUT|DELETE /api/records/:id`、`GET|POST|PUT /api/requirements`、`GET|PUT /api/requirements/:id`、`POST /api/meetings/import`、`POST /api/diary/import`、`GET /api/diary/entries`、`GET /api/diary/history`、`GET|POST|PUT /api/diary/today`、`GET|PUT /api/diary/entries/:idOrDate` 和 `GET /api/diary/months`，错误必须返回结构化 JSON，便于 CLI 与 frontend 诊断。记录与日记列表默认只返回摘要级正文，完整 Markdown body 只能由详情接口或显式 `includeBody=true` 获取，避免大 body 列表穿过 frontend/proxy 链路导致超时。
- CLI：`bun scripts/cli.ts decision upload <markdown-file>`、`decision list`、`decision show <id>`、`decision requirement list/create/show/update/upsert`、`decision diary import/list/history/months/today/show/edit/upsert` 和 `decision health` 只能通过 `config/unidesk-host-k8s.yaml` 选中的 NC01 k8s entrypoint 访问 Decision Center，不得直连旧 D601 Service、NodePort 或 provider-gateway `microservice.http`。列表命令默认省略完整正文，需要完整 body 时显式加 `--include-body`；日记编辑验收应使用 `decision diary today` 确认真实日期自动创建当天条目，使用 `decision diary today --edit --body-file <file>` 保存当天 Markdown，使用 `decision diary upsert <YYYY-MM-DD> --body-file <file>` 创建或更新历史日记，再用 `decision diary show <YYYY-MM-DD> --source-file <source>` 或 `decision diary show <id>` 读取确认。
- Dev/prod CD：Decision Center 的当前生产 rollout 必须走 NC01 YAML-first k8s 部署，验证 `deployment/decision-center`、`service/decision-center`、GitHub repo 配置和 `decision-center-github-ssh` Secret presence/fingerprint；不得回退到 D601 registry artifact consumer、维护通道直连、裸 Docker/Compose、NodePort 或 hostPort。旧 D601 dev/prod artifact consumer 描述只保留为迁移历史，不再作为当前生产 closeout 证据。
- UniDesk 前端：`用户服务 / Decision Center` React 页面展示权威记录筛选、当前 G0/G1 目标、P0/P1 blocker、停放事项、最近会议/决议和工作日记；它还应成为需求管理入口，让外部目标、内部拆解和每日工作记录在同一页面中可追溯。日记视图按月份筛选并展示每天 Markdown 正文，未来应支持当天自动创建与历史编辑。默认不得展示裸 JSON，完整原始数据只能通过 `查看原始JSON` 打开。

### MDTODO k3s-Managed

当前 MDTODO 作为 `id=mdtodo` 的 `k3sctl-managed` 用户服务登记在 `config.json`，用于把 D601 Windows 工作区 `F:\Work\vscode-mdtodo` 从 VS Code 扩展形态拆成 UniDesk 可代理的后端服务：

- Orchestrator：`deployment.mode=k3sctl-managed`，`deployment.adapterServiceId=k3sctl-adapter`，`deployment.k3sServiceId=mdtodo`，`backend.proxyMode=k3sctl-adapter-http`，`backend.nodeBaseUrl=k3s://mdtodo`；正式链路只能是 `frontend -> backend-core -> k3sctl-adapter -> Kubernetes API service proxy -> Kubernetes Service mdtodo:4267`。
- 代码与部署引用：后端源码位于 UniDesk 仓库 `src/components/microservices/mdtodo`，Dockerfile 为 `src/components/microservices/mdtodo/Dockerfile`；k3s manifest 为 `src/components/microservices/k3sctl-adapter/k3s/mdtodo.k3s.json`，Kubernetes 运行清单为 `src/components/microservices/k3sctl-adapter/k3s/mdtodo.k8s.yaml`，镜像名固定为 `unidesk-mdtodo:d601`。dev 环境使用 `src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-mdtodo.k8s.yaml` 和 `unidesk-dev-mdtodo.k3s.json`，服务名为 `mdtodo-dev`。
- 持久化边界：D601 的 `F:\Work\vscode-mdtodo` 先同步到 k3s 可见的 WSL hostPath `/home/ubuntu/cq-deploy/.state/mdtodo-workspace`，Pod 将该目录挂载为 `/workspace`，后端直接读写 Markdown TODO 文件；`.state/mdtodo/logs` 只保存 JSONL 日志，不作为任务权威状态。该服务不得把原 VS Code webview 前端或 VSIX 构建产物作为浏览器入口。
- API：`GET /health`、`GET /live`、`GET /logs`；`GET /api/files`；`GET /api/tasks?file=...`；`GET|PATCH|DELETE /api/tasks/{id}`；`POST /api/tasks`；`GET|PUT /api/content`；`POST /api/execute-command`。`/health` 必须证明 hostPath 可读并至少能扫描到 TODO Markdown 文件。
- 代理路径：只允许 `/health`、`/live`、`/logs` 和 `/api/` 前缀；允许方法为 `GET`、`HEAD`、`POST`、`PUT`、`PATCH` 和 `DELETE`。业务请求不得退化为 provider-gateway 直连、NodePort 或 D601 本机端口。
- Dev/prod CD：MDTODO 的 dev/prod rollout 都必须走 D601 registry artifact consumer。dev manifest 在 `unidesk-dev` 中创建最小 ClusterIP Service/Deployment 并种子化一个 dev Markdown TODO 文件，保证 `/health` 可以真实扫描任务文件；prod 使用生产 `mdtodo` manifest。两边都要通过 Kubernetes API service proxy 验证 health/live deploy metadata，不能用 NodePort、hostPort 或 provider-gateway 业务直连替代。
- UniDesk 前端：`用户服务 / MDTODO` React 页面负责展示文件列表、任务树、任务状态、标题/正文编辑、新增/删除任务、执行命令生成和显式原始 JSON 按钮；默认页面不得裸铺完整 Markdown 或 JSON。

## D601 User Services

当前 `D601` 同时承载以下 UniDesk 用户服务：

- `findjob`：FindJob 纯后端服务，UniDesk frontend 渲染岗位指标、岗位预览和草稿报告。
- `pipeline`：Pipeline v2 控制与观测服务，UniDesk frontend 渲染组件矩阵、React Flow 控制图、epoch 甘特图、运行材料索引和 node 精细控制面板。
- `met-nonlinear`：MET Nonlinear 训练编排服务，UniDesk frontend 渲染 GPU/镜像、训练队列、Project config 预览、训练进度、ETA 和历史记录。
- `claudeqq`：ClaudeQQ 纯后端 QQ 消息网关，UniDesk frontend 渲染 NapCat 连接、事件订阅、消息推送、最近 QQ 事件和发送记录。
- `decision-center`：Decision Center 决策权威记录和 Markdown 日记服务，生产由 NC01 k8s 代管，状态持久化到 GitHub repo `pikasTech/decision-center-data`，UniDesk frontend 渲染记录筛选、目标、blocker、停放事项、会议/决议和按月工作日记。

### D601 Docker/k3s Restart Recovery

D601 是 Windows + WSL Ubuntu + Docker Desktop 节点，Docker Desktop 当前 `LiveRestore=false` 时，机器或 Docker daemon 重启会停止容器，恢复链路必须同时覆盖 Windows 登录、WSL keepalive、Docker daemon ready、provider-gateway 和业务用户服务：

- 首选只读诊断入口：`bun scripts/cli.ts check recovery-guardrails`。该入口报告 malformed `/proc/mounts` Docker Desktop `/Docker/host` 9p 行、kubelet mount-table validation risk、stale CRI sandbox count、Code Queue deploy worktree/`/home/ubuntu/cq-deploy` symlink readiness、Code Queue/MDTODO k3s hostPath readiness、MDTODO adjacent hostPath 透明度和 `ContainerCreating` hostPath 分类。输出只给 safe read-only 证据、manual host hotfix redlines 和禁止自动动作；不得据此自动重启 k3s、删除 CRI sandbox、删除 Pod、修改 live hostPath、执行 deploy/rollout 或 destructive prune/reset。完整 hotfix exception、用户数据边界和 ClaudeQQ/用户请示条件见 `docs/reference/devops-hygiene.md`。
- Windows 登录任务：计划任务 `UniDesk-D601-Autostart` 在用户 `DESKTOP-1MHOD9I\liang` 登录时运行 `C:\WINDOWS\System32\cmd.exe /c ""C:\Users\liang\AppData\Local\UniDesk\d601-autostart.cmd""`，工作目录为 `C:\Users\liang\AppData\Local\UniDesk`。
- Windows launcher：`C:\Users\liang\AppData\Local\UniDesk\d601-autostart.cmd` 先启动 `%ProgramFiles%\Docker\Docker\Docker Desktop.exe`，再执行 `C:\Windows\System32\wsl.exe -d Ubuntu -u ubuntu -- /bin/bash -lc "/home/ubuntu/.local/bin/unidesk-d601-autostart task"`；D601 的 WSL distro 名必须写 `Ubuntu`，不能写成未验证的 `Ubuntu-22.04`。
- WSL keepalive：`/home/ubuntu/.local/bin/unidesk-d601-autostart` 使用 `~/.state/unidesk/d601-autostart.lock` 防重复，启动 WSL `sshd`，等待 Docker Desktop daemon 和原生 k3s 就绪，把 `unidesk-provider-gateway-D601` 修正为 `restart always` 且 running，然后调用 `/home/ubuntu/.local/bin/unidesk-microservice-autorecover boot`；进入常驻 watchdog 后每 300 秒重复检查 provider-gateway、Docker 直管服务和 k3s 代管服务。
- 用户服务 autorecover：`/home/ubuntu/.local/bin/unidesk-microservice-autorecover` 只在 Docker 和 k3s ready 后运行；Docker 直管服务仍用容器 `State.Running`、`State.ExitCode` 和轻量 HTTP 探针判断是否需要恢复，MET Nonlinear 失败时在 `/home/ubuntu/met_nonlinear` 执行 `docker compose -f docker-compose.unidesk.yml up -d --force-recreate met-nonlinear-ts`；ClaudeQQ 必须通过 k3s Deployment/Service 恢复和验证，不再用 `docker compose` 作为正式恢复入口。
- 验收命令：Docker/k3s 恢复后必须同时验证 `docker inspect --format '{{.HostConfig.RestartPolicy.Name}} {{.State.Status}}' met-nonlinear-ts`、`kubectl -n unidesk get deploy/pod/svc/endpoints claudeqq -o wide`、`bun scripts/cli.ts microservice health met-nonlinear`、`bun scripts/cli.ts microservice health claudeqq` 和公网 frontend 页面；ClaudeQQ 还必须经 UniDesk 代理验证 `/api/napcat/login` 中 `state=logged_in`、HTTP connected 和 WebSocket connected。

### FindJob On D601

当前 FindJob 作为 `id=findjob` 的用户服务登记在 `config.json`：

- Provider：`D601`。
- 开发工作树：`/home/ubuntu/findjob`，开发和调试必须通过 UniDesk SSH 透传进入 D601。
- 代码引用：`https://gitee.com/Lyon1998/findjob` 与配置中的 `repository.commitId`。
- 部署引用：业务仓库自身 `Dockerfile`、`docker-compose.yml`、`composeService=server`、`containerName=findjob-server`。
- 节点后端：D601 上 `127.0.0.1:3254`，provider-gateway 容器内通过 `http://host.docker.internal:3254` 访问。
- 代理路径：只允许 `/api/` 前缀；`/` 上的业务旧前端即使仍存在，也不作为 UniDesk 用户服务入口使用。
- UniDesk 前端：`用户服务 / FindJob` React 页面负责展示指标、岗位预览、草稿报告和原始 JSON 显式查看按钮。

FindJob 在 UniDesk 语境中按纯后端服务管理：默认页面不得 iframe 或跳转到 findjob 自身前端，也不得直接暴露 D601 的 `3254` 到公网。UniDesk frontend 只能通过 `/api/microservices/findjob/health` 和 `/api/microservices/findjob/proxy/api/...` 访问 FindJob 后端。

### Pipeline On D601

当前 Pipeline v2 作为 `id=pipeline` 的用户服务登记在 `config.json`：

- Provider：`D601`。
- 开发工作树：`/home/ubuntu/pipeline`，开发和调试必须通过 UniDesk SSH 透传进入 D601。
- 代码引用：`https://github.com/pikasTech/pipeline` 与配置中的 `repository.commitId`。
- 部署引用：业务仓库自身 `Dockerfile`、`docker-compose.yml`、`composeService=pipeline-control`、`containerName=pipeline-v2-control`。
- 节点后端：D601 上 `127.0.0.1:18082`，provider-gateway 容器内通过 `http://host.docker.internal:18082` 访问。
- 代理路径：只允许 `/health` 和 `/api/` 前缀；允许方法为 `GET`、`HEAD`、`POST`，其中 `POST` 仅用于 `/api/node-control/...` 这类 node 控制动作；Pipeline 自身 WebUI 前端已废弃，UniDesk 只访问 Pipeline control backend。
- UniDesk 前端：`用户服务 / Pipeline` React 页面负责展示 health、组件数量、React Flow pipeline 控制图框图、epoch 列表、epoch 甘特图、OA/procedure 结构化摘要、运行材料索引、点击 node 后的执行过程抓取、append prompt、guide 和 redo/restart 控件，以及显式原始 JSON 按钮。

Pipeline 在 UniDesk 语境中按控制与观测后端服务管理：默认页面不得 iframe 或跳转到 Pipeline 自身 WebUI，也不得直接暴露 D601 的 `18082` 到公网。UniDesk frontend 只能通过 `/api/microservices/pipeline/health`、`/api/microservices/pipeline/proxy/api/snapshot?...`、`/api/microservices/pipeline/proxy/api/node-control/...` 和供主 server `oa-event-flow` bridge 使用的 `/api/microservices/pipeline/proxy/api/oa-event-flow/diagnostics` 访问 Pipeline control backend；主 server `oa-event-flow` 的迁移 bridge 当前只允许单一路径读取 `/api/snapshot` 与 `/api/oa-event-flow/diagnostics`，发布 `pipeline-run-snapshot` 到统一事件表，不得再实现 ledger/snapshot 双路径 fallback。超大 snapshot 必须使用 `__unideskArrayLimit=registry.components:<limit>,runs:<limit>` 做展示级裁剪。node 控制入口必须走 Pipeline 后端 OA control API，前端不得直接写 `.state`、runner prompt 文件或命令队列；scorer 结果必须在 UniDesk Pipeline UI 中以结构化 score 卡片展示。Pipeline 控制与观测的最终态必须 100% 由统一 `oa-event-flow` 与 Pipeline OA 状态机驱动，不得保留点对点控制、旧审核事件或旧 batch 推进逻辑；共享事件服务规则见 `docs/reference/oa-event-flow.md`，Pipeline 专有控制流规则见 `docs/reference/pipeline-oa-event-flow.md`。

Pipeline 的一个 epoch 是同一个 pipeline 从入口到终态完整执行一遍，UniDesk 前端把同一 `pipelineId` 下的多个 run 作为多个 epoch 管理。甘特图必须从 Pipeline snapshot 中的 `startedAt`、`finishedAt`、`durationMs` 和 procedure run 摘要生成，不得按某个 pipeline 实例或 node 名称硬编码布局。甘特图纵轴按时间从上到下排列，左侧固定时间列，后续每列对应一个 node；当前可见时间窗口内没有工作区间的 node 列应自动隐藏。默认页面不得展示裸 JSON、JSONL、worker log 或 control event 行；运行材料和 node 过程只能作为一组一行的结构化索引展示，完整原始内容只能在操作员点击 `查看原始JSON` 后显示。

### MET Nonlinear On D601

当前 MET Nonlinear 作为 `id=met-nonlinear` 的用户服务登记在 `config.json`：

- Provider：`D601`。
- 开发工作树：`/home/ubuntu/met_nonlinear`，后端、Dockerfile、训练队列和训练容器都必须通过 UniDesk SSH 透传在 D601 开发调试；主 server 本地只允许开发 UniDesk frontend 与代理登记。
- 数据挂载：D601 的 `/mnt/f/BaiduSyncdisk/data` 挂载为训练容器内 `/data/data`，并设置 `MET_DATA_BASE=/data`，让旧配置中的 `data/M50` 解析为 `/data/data/M50`；WSL 本机不安装 TensorFlow 训练环境。
- 代码引用：`https://github.com/pikasTech/met_nonlinear` 与配置中的 `repository.commitId`。
- 部署引用：业务仓库内 `docker-compose.unidesk.yml`、`docker/unidesk/Dockerfile.server`、`docker/unidesk/Dockerfile.ml`、`composeService=met-nonlinear-ts`、`containerName=met-nonlinear-ts`。
- 运行配置：`docker-compose.unidesk.yml` 中 `met-nonlinear-ts` 必须固定 `container_name: met-nonlinear-ts`、`restart: unless-stopped`、`127.0.0.1:3288:3288`、`extra_hosts: ["host.docker.internal:host-gateway"]`，并把 `/var/run/docker.sock`、`/usr/lib/wsl/lib:ro` 和 `/dev/dxg` 挂入容器，让 TS 编排后端能按需拉起 GPU 训练容器并读取 WSL GPU runtime。
- 持久化路径：业务源码与状态通过 `/home/ubuntu/met_nonlinear:/workspace/met_nonlinear` 挂载，训练数据通过 `/mnt/f/BaiduSyncdisk/data:/data` 挂载；`MET_STATE_DIR=/workspace/met_nonlinear/.state/unidesk-met` 保存队列和服务状态，`MET_LOG_DIR=/workspace/met_nonlinear/logs/unidesk-met` 保存服务日志，`MET_HOST_ROOT` 与 `MET_HOST_DATA_ROOT` 必须指向对应 host 路径，避免训练容器从容器内路径误反推 host 文件。
- 节点后端：D601 上 `127.0.0.1:3288`，provider-gateway 容器内通过 `http://host.docker.internal:3288` 访问。
- 代理路径：只允许 `/health` 和 `/api/` 前缀；允许 `GET`、`HEAD`、`POST`、`PUT`，用于读取队列/历史、从已有 Project fork 新 Project、保存队列设置、加入待启动队列和启动队列。
- UniDesk 前端：`用户服务 / MET Nonlinear` React 页面采用类似下载器的工作台交互，负责从项目库选择已有 Project、fork 新 Project、加入待启动队列、启动队列、调整最大并发、分标签展示当前队列/已完成/失败诊断/GPU 与镜像，并展示训练进度、ETA、训练速度 `epoch/h`、历史训练记录和显式原始 JSON 按钮。项目库必须按 `projects/`、`ex_projects/` 的真实目录层级渲染文件树，文件夹计数等于子树 Project 数；项目库和任务列表行都必须可点击打开结构化详情，详情以控件展示 `config.json` 与 `data/` 中的训练状态、模型参数量、模型层和指标，不默认展示裸 JSON。

MET Nonlinear 的长期服务边界写在业务仓库 `~/met_nonlinear/docs/reference/unidesk_microservice.md`：`met-nonlinear-ts` 是长驻 Bun TypeScript 编排后端，`met-nonlinear-ml:tf26` 是按需训练镜像，每个训练任务用一个 `docker run --rm` 容器执行 `python cli.py -t <projectPath>`，训练完成后容器自动销毁。训练镜像 Dockerfile 必须使用中国大陆可达的软件源；当前固定使用 Huawei Cloud mirror 的 `nvidia/cuda:11.2.2-cudnn8-runtime-ubuntu20.04`、Aliyun apt mirror、Tsinghua PyPI mirror、Ubuntu Python 3.8 和 `tensorflow==2.6.0`，避免官方 TensorFlow 2.6 GPU 镜像 Python 3.6 与业务源码类型注解不兼容。

MET Nonlinear 验收必须通过公网 UniDesk frontend 的交互式 UI 完成：选择已有 source Project，设置训练轮数和最大并发，使用 `Fork Project` 创建新的 `projects/unidesk_forks/` Project，确认新 Project 只是被选中而不会直接训练，再加入待启动队列并点击 `启动队列`。验收时必须确认项目库的 `projects/` 与 `ex_projects/` 按文件树层级展开、文件夹 Project 计数与后端返回数量一致；点击项目行后详情显示 `config.json`、`data/` 训练状态、模型参数量和指标；待启动、排队中、训练中、已完成和失败诊断分标签可见；训练队列和已完成行显示 `epoch/h` 训练速度且可点击打开任务详情。最大并发必须按 UI 设置生效，运行中行显示训练进度和 ETA，目标 GPU 为 2080Ti，2080Ti 显存余量低于 20% 时自动限制并发，并确认训练容器结束后不残留。批量规模由 UI 输入框决定，完整验收可以通过输入 `Fork 数量=10`、`训练轮数=200`、`最大并发=3` 执行，但不得把该规模做成专用硬编码按钮。CLI `/api/queue/server-test` 仅保留为后端兼容入口，不作为 frontend 操作入口。

### ClaudeQQ Development On D601

ClaudeQQ 的业务源码和持久化数据仍在 D601，但正式运行由 k3s 代管：

- Provider：`D601`。
- 开发工作树：`/home/ubuntu/.agents/skills/claudeqq`，后端、Dockerfile、订阅分发和 NapCat 连接调试必须通过 UniDesk SSH 透传在 D601 完成；主 server 本地只允许开发 UniDesk frontend、代理登记和 k3s manifest。
- 代码引用：`https://gitee.com/lyon1998/agent_skills` 与配置中的 `repository.commitId`，实际服务目录为仓库内 `claudeqq/`。
- 部署引用：标准 desired state 为根目录 `deploy.json` 中的 `claudeqq` 服务；运行清单为 UniDesk 仓库内 `src/components/microservices/k3sctl-adapter/k3s/claudeqq.k3s.json` 和 `claudeqq.k8s.yaml`，镜像名固定 `unidesk-claudeqq:d601`；构建 Dockerfile 与 API 适配器由 `src/components/microservices/claudeqq/` 注入。业务仓库内 `docker-compose.unidesk.yml` 只作为迁移/本地诊断参考，不是正式部署入口。
- 运行配置：ClaudeQQ Pod 同时包含 `napcat` 与 `claudeqq` 两个容器；`claudeqq` 固定 `CLAUDEQQ_AUTO_REPLY=false`、`CLAUDEQQ_NAPCAT_HTTP_HOST=127.0.0.1`、`CLAUDEQQ_NAPCAT_WS_HOST=127.0.0.1`、`CLAUDEQQ_ONLINE_NOTICE_USER_ID=645275593` 和 `CLAUDEQQ_LOGIN_MONITOR_INTERVAL_MS`。NapCat 的 `3000/3001/6099` 仅 Pod 内访问。
- 节点后端：UniDesk 逻辑后端为 `k3s://unidesk/claudeqq:3290`，由 backend-core 经过 k3sctl-adapter 和 Kubernetes API service proxy 访问 Kubernetes Service `claudeqq:3290`；不得再通过 provider-gateway 业务代理或 D601 本机端口作为正式链路。
- NapCat 登录 API：`GET /api/napcat/login` 和 `GET /api/napcat/status` 返回容器化状态、HTTP/WS 连通性、登录状态和二维码 data URL；`GET /api/napcat/qrcode` 只返回二维码。二维码来源为共享挂载中的 `/napcat/cache/qrcode.png`，由 ClaudeQQ 后端转为 JSON data URL 后经 UniDesk 同源代理给前端展示。`/health` 的 healthy 条件必须包含 `ready=true`、NapCat HTTP connected、NapCat WebSocket connected 和 `loginState=logged_in`；仅有二维码、NapCat 容器 running 或后端进程 running 不能算健康。
- 订阅 API：`GET /api/events/recent` 返回最近 QQ 事件，`GET|POST /api/events/subscriptions` 管理 webhook 订阅，`DELETE /api/events/subscriptions/{id}` 删除订阅；订阅回调使用 HTTP POST JSON，并在配置 secret 时携带 `x-claudeqq-signature` HMAC-SHA256。
- 推送 API：`POST /api/push/text` 接受 `userId` 或 `groupId` 与 `message`，由 ClaudeQQ 通过 NapCat HTTP API 发送 QQ 消息；NapCat 不可用时必须快速返回 `status=napcat_offline` 和具体连接错误；当前人工推送验收只允许发给主用户私聊账号 `645275593`，其他用户服务和 main server 应通过 UniDesk 用户服务代理调用。

## CLI

- `bun scripts/cli.ts microservice list`：列出全部用户服务、provider 映射、仓库引用、后端映射和运行态容器摘要。
- `bun scripts/cli.ts microservice status findjob`：查看单个用户服务的配置与运行态。
- `bun scripts/cli.ts microservice health findjob`：通过 backend-core -> provider-gateway -> D601 本机后端链路探测 FindJob `/api/health`。
- `bun scripts/cli.ts microservice proxy findjob /api/summary`：通过同一私有代理读取业务 API，适合人工验证，不用于公开业务端口。
- `findjob` 的 CD 形态是 D601 direct Compose artifact consumer：dev/prod 都消费 `127.0.0.1:5000/unidesk/findjob:<commit>`，并只重建 `server` compose service；dev validation 先做 registry pull、label、config dry-run 和 health contract，prod 只做 pull-only no-build recreate。
- `bun scripts/cli.ts microservice health pipeline`：通过 backend-core -> provider-gateway -> D601 本机后端链路探测 Pipeline `/health`。
- `bun scripts/cli.ts microservice proxy pipeline '/api/snapshot?__unideskArrayLimit=registry.components:8,runs:3'`：读取 Pipeline snapshot 的有界预览，适合人工验证，不用于公开业务端口；验证甘特图时应确认 run 和 procedureRun 摘要包含 `startedAt`、`finishedAt` 或 `durationMs`；若 body 仍超过 CLI 阈值，默认只输出 `bodyPreview`，需要完整 body 时显式追加 `--raw`。
- `pipeline` 的 CD 形态是 D601 direct Compose artifact consumer：dev/prod 都消费 `127.0.0.1:5000/unidesk/pipeline:<commit>`，并只重建 `pipeline-control` compose service；dev validation 和 prod live 路径与 `findjob` 保持同样的 pull-only no-build 合约。
- Pipeline node 控制写入由 UniDesk frontend 调用同源 `/api/microservices/pipeline/proxy/api/node-control/...` 完成；通用 CLI `microservice proxy` 仍主要作为读取验证入口，不作为人工批量写入工具。
- `bun scripts/cli.ts microservice health met-nonlinear`：通过 backend-core -> provider-gateway -> D601 本机 TS 编排后端链路探测 MET Nonlinear `/health`。
- `bun scripts/cli.ts microservice proxy met-nonlinear /api/queue` 与 `bun scripts/cli.ts microservice proxy met-nonlinear /api/images`：读取 MET Nonlinear 队列、GPU 策略和训练镜像状态，适合人工验证，不用于公开业务端口。
- `bun scripts/cli.ts microservice proxy met-nonlinear '/api/projects?root=projects&limit=500'` 与 `bun scripts/cli.ts microservice proxy met-nonlinear '/api/projects/config?path=projects/<name>' --raw`：验证项目库文件树输入和结构化项目详情；详情应包含 config、progress、data、model、metrics 字段，供前端渲染训练状态、模型参数量和指标。
- `met-nonlinear` 的 CD 形态同样是 D601 direct Compose artifact consumer，但当前 live deploy 被 Dockerfile.contract 与 long-running service contract 不一致阻塞，只允许 dry-run/plan。
- `bun scripts/cli.ts microservice health claudeqq`、`bun scripts/cli.ts microservice proxy claudeqq /api/napcat/login`、`bun scripts/cli.ts microservice proxy claudeqq /api/events/recent` 和 `bun scripts/cli.ts microservice proxy claudeqq /api/events/subscriptions`：验证 ClaudeQQ 后端、NapCat 容器登录、事件订阅和私有代理链路；消息推送使用 `POST /api/push/text`，不得开放 D601 `3290/3000/3001/6099` 公网端口。
- `bun scripts/cli.ts microservice health todo-note`、`bun scripts/cli.ts microservice proxy todo-note /api/instances` 与 `/api/storage/verify`：验证 frontend/backend-core 到 NC01 ClusterIP 服务的私有链路、GitHub 权威存储和 PostgreSQL cache。
- `bun scripts/cli.ts microservice health oa-event-flow`、`bun scripts/cli.ts microservice proxy oa-event-flow /api/diagnostics --raw` 与 `bun scripts/cli.ts microservice proxy oa-event-flow '/api/events?tags=service:code-queue&limit=20' --raw`：验证统一 OA 事件流、事件表、tag 查询和统计中心。
- `bun scripts/cli.ts microservice health k3sctl-adapter` 与 `bun scripts/cli.ts microservice proxy k3sctl-adapter /api/control-plane --raw`：验证 D601 `unidesk-k3s` 控制面 adapter、manifest、D601 scheduler/read/write 实例状态、`presentNodeIds` 包含 `D601`、`missingNodeIds=[]` 和 no-fallback 运行路径。
- `bun scripts/cli.ts microservice health code-queue-mgr`：验证主 server 轻量 Code Queue 控制面，输出必须包含 `role=master-control-plane`、`schemaReady=true`、PostgreSQL pool 上限和 `noRunnerDependencies=true`。
- `bun scripts/cli.ts microservice health code-queue` 与 `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview`：验证稳定 `code-queue` 用户服务路径可用；普通 health/overview/任务摘要/队列管理默认由 backend-core 分流到主 server `code-queue-mgr`，提交和 readAt/未读状态都必须由后端写入 PostgreSQL，frontend 不得用本地存储伪造成功状态。需要 D601 执行面状态时，通过 `k3sctl-adapter /api/control-plane` 查看 scheduler/read/write ready endpoint，或访问执行面专属 dev-ready、judge、active run control 路径；输出不得出现 `serviceId=code-queue` 的 provider-gateway `microservice.http` 业务代理任务。
- `bun scripts/cli.ts microservice diagnostics code-queue`：拆分 k3sctl-managed 链路健康，返回 `providerGateway`、`httpTunnel`、`k3sctlAdapter`、`kubernetesApiServiceProxy` 和 `targetService` 五段状态。该命令仍通过 backend-core 用户服务代理访问，不允许浏览器或 CLI 绕到 k3s、NodePort、Pod IP 或 D601 本机业务端口。
- `bun scripts/cli.ts microservice tunnel-self-test code-queue`：触发一次预期失败的 provider HTTP tunnel 请求，用于确认失败响应包含 `requestId`、`stage`、`x-unidesk-request-id` 和 `x-unidesk-tunnel-error`；该自测只访问 provider 侧无效 loopback 端口，不创建 Code Queue 队列，也不绕过正式 backend-core 入口。
- `bun scripts/cli.ts microservice health filebrowser`、`bun scripts/cli.ts microservice health filebrowser-d601` 与 `bun scripts/cli.ts microservice proxy filebrowser / --max-body-bytes 2000`：验证 D518 主 File Browser 和 D601 备用 File Browser 私有代理链路；浏览器 WebUI 必须通过 `/api/microservices/filebrowser/proxy/` 或 `/api/microservices/filebrowser-d601/proxy/` 访问，不得直接开放 `4251` 公网端口。
- `bun scripts/cli.ts --main-server-ip 74.48.78.17 microservice health findjob`：在计算节点或其他非主 server 主机上通过公网 frontend remote CLI 进行同一验证，不需要主 server SSH key。

`debug dispatch D601 microservice.http --payload-json ...` 仅用于开发调试 provider-gateway 代理能力；正式验收和用户入口应优先使用 `microservice` 命令与 frontend 用户服务页面。

## Frontend Rules

用户服务前端必须整合到 `src/components/frontend/src/` 下的 TypeScript + React 模块中。`app.tsx` 只做 shell/router 和导入分发，业务页面必须拆成独立 TSX，例如 `todo-note.tsx`、`findjob.tsx`、`pipeline.tsx`、`met-nonlinear.tsx`、`code-queue.tsx`、`k3sctl.tsx`。默认展示必须是业务控件：指标卡、状态徽标、表格、草稿卡片、运行卡片、树形任务、表单控件、结构化材料索引、链接和字段摘要；只有操作员点击 `查看原始JSON` 时才允许打开原始 JSON 弹窗。日志、JSONL 和大块 JSON 不得在主界面按行展示，避免把裸数据伪装成 UI。

对于超大业务 JSON，backend-core 可把 `__unideskArrayLimit=<path>:<limit>` 作为 frontend-only 代理参数传给 provider-gateway，由 provider-gateway 在返回前裁剪指定 JSON 数组并写入 `_unidesk.arrayLimits` 元数据。该参数只用于控制 UniDesk 展示预览，不能替代业务后端自身分页 API 的长期设计。CLI 的 `microservice status/health/diagnostics` 默认只返回 compact summary、body 字节数和 `--full|--raw` 展开命令；只有小 body 或无法抽取 summary 时才带有界 preview，防止 Code Queue/k3s health 这类大 JSON 刷屏。`microservice proxy` 还会对超过默认阈值的 body 做二次有界预览，防止人工验证时输出爆炸。只有显式 `--full` 或 `--raw` 才允许倾倒完整 body。

## Verification

用户服务交付必须同时通过后端、CLI 和公网 frontend 验证：

- 在主 server 运行 `bun scripts/cli.ts microservice list`，确认 `findjob` 的 `providerId=D601`、`public=false`、`frontendOnly=true`、仓库 URL、commit id、`127.0.0.1:3254` 映射和 `findjob-server` 容器摘要可见。
- 在主 server 运行 `bun scripts/cli.ts microservice list`，确认 `pipeline` 的 `providerId=D601`、`public=false`、`frontendOnly=true`、仓库 URL、commit id、`127.0.0.1:18082` 映射和 `pipeline-v2-control` 容器摘要可见。
- 在主 server 运行 `bun scripts/cli.ts microservice list`，确认 `met-nonlinear` 的 `providerId=D601`、`public=false`、`frontendOnly=true`、仓库 URL、commit id、`127.0.0.1:3288` 映射和 `met-nonlinear-ts` 容器摘要可见。
- 在主 server 运行 `bun scripts/cli.ts microservice list`，确认 `claudeqq` 的 `providerId=D601`、`public=false`、`frontendOnly=true`、仓库 URL、commit id、`deployment.mode=k3sctl-managed`、`runtime.orchestrator=k3sctl`、`backend.proxyMode=k3sctl-adapter-http`、`backend.nodeBaseUrl=k3s://claudeqq` 和 `k3s://unidesk/claudeqq:3290` 逻辑 Service 映射可见，且不显示业务容器直连摘要。
- 在主 server 运行 `bun scripts/cli.ts microservice list`，确认 `code-queue-mgr` 为 `providerId=main-server`、`deployment.mode=internal-sidecar`、Compose 后端 `http://code-queue-mgr:4278`、`frontend.integrated=false`，并确认稳定 `code-queue` 对外 ID 的说明中体现控制/读取默认由 `code-queue-mgr` 承担、D601 `k3sctl-managed` 只承担执行面；`k3sctl-adapter` 仍为 `providerId=D601`、`deployment.mode=unidesk-direct`、后端私有端口 `127.0.0.1:4266`。
- 在主 server 运行 `bun scripts/cli.ts microservice list`，确认 `filebrowser` 和 `filebrowser-d601` 分别显示为 `providerId=D518` 和 `providerId=D601`，均为 `public=false`、`frontendOnly=true`，仓库 URL 为 `https://github.com/filebrowser/filebrowser`，后端映射为 `host.docker.internal:4251`，容器摘要分别为 `unidesk-filebrowser-d518` 和 `unidesk-filebrowser-d601`；列表中不得再出现主 server `filebrowser-main` 容器。
- 运行 `bun scripts/cli.ts microservice health findjob` 与 `bun scripts/cli.ts microservice proxy findjob /api/summary`，确认真实链路经过 backend-core、WebSocket、D601 provider-gateway 和 D601 本机 FindJob 后端。
- 运行 `bun scripts/cli.ts microservice health pipeline` 与 `bun scripts/cli.ts microservice proxy pipeline '/api/snapshot?__unideskArrayLimit=registry.components:8,runs:3'`，确认真实链路经过 backend-core、WebSocket、D601 provider-gateway 和 D601 本机 Pipeline 后端，且 run/procedure 摘要包含甘特图所需时间字段。
- 运行 `bun scripts/cli.ts microservice health met-nonlinear`、`bun scripts/cli.ts microservice proxy met-nonlinear /api/queue`、`bun scripts/cli.ts microservice proxy met-nonlinear '/api/projects?root=projects&limit=20'` 和 `bun scripts/cli.ts microservice proxy met-nonlinear /api/images`，确认真实链路经过 backend-core、WebSocket、D601 provider-gateway 和 D601 本机 MET Nonlinear TS 后端。
- 运行 `bun scripts/cli.ts microservice health claudeqq`、`bun scripts/cli.ts microservice proxy claudeqq /api/napcat/login`、`bun scripts/cli.ts microservice proxy claudeqq /api/events/recent` 和 `bun scripts/cli.ts microservice proxy claudeqq /api/events/subscriptions`，确认真实链路经过 backend-core、k3sctl-adapter、Kubernetes API service proxy 和 D601 Kubernetes Service `claudeqq:3290`；health 应显示 `service=claudeqq`、`pureBackend=true`、`napcat.containerized=true`、NapCat HTTP/WS 状态、二维码状态和订阅计数。
- 运行 `bun scripts/cli.ts microservice health todo-note`、`bun scripts/cli.ts microservice proxy todo-note /api/instances` 与 `bun scripts/cli.ts microservice proxy todo-note /api/storage/verify --full`，确认真实链路经过公网 frontend、backend-core 和 NC01 `todo-note.unidesk.svc.cluster.local:4288`；输出中必须包含六个迁移清单、1167 条历史、GitHub revision/path 和 PostgreSQL cache 对齐状态。
- 运行 `bun scripts/cli.ts microservice health code-queue-mgr`、`bun scripts/cli.ts microservice health code-queue`、`bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview` 和 `bun scripts/cli.ts codex submit --dry-run ...`，确认稳定 `code-queue` 控制/读取路径经 backend-core 分流到主 server `code-queue-mgr`，不依赖 D601 `code-queue-write` ready endpoint。再运行 `bun scripts/cli.ts microservice health k3sctl-adapter` 与 `bun scripts/cli.ts microservice proxy k3sctl-adapter /api/control-plane --raw`，确认 D601 scheduler/read/write 三个内部 Service 的 ready endpoint 和 no-fallback 拓扑；adapter 验收还必须证明其作为 UniDesk 直管服务运行在 k3s 外部，Docker 形态下挂载宿主 `/etc/rancher/k3s/k3s.yaml` 与 `/run/host-ssh/id_ed25519`，通过容器内 SSH local tunnel 连接 WSL 原生 k3s API，且 D601/D518 上都没有 active `rancher/k3s` 容器。D601 scheduler `/health` 必须仍返回业务后端自己的 `role=scheduler`、`queue.storage.primary=postgres`、`queue.storage.postgresReady=true`、`queue.notifications.claudeqq.outbox.storage=postgres` 和 `egressProxy.connected=true`，不得被 adapter 聚合健康 JSON 替代。还必须在 active Code Queue Pod 内验证主 PostgreSQL 端口映射、主 OA Event Flow 端口映射、集群内 ClaudeQQ `http://claudeqq.unidesk.svc.cluster.local:3290/health` 和 `d601-provider-egress-proxy` 均可访问，并确认 `/workspace` 与 `/home/ubuntu` 指向同一 WSL home hostPath，`/workspace/cq-deploy` 这类绝对 symlink 可以进入真实目录。再通过公网 frontend 提交一个 `gpt-5.5` 小任务，确认任务先由 master `code-queue-mgr` 入库、D601 scheduler 轮询执行、输出实时更新、结束后有 judge 判定，且运行中可追加 prompt 或打断。Code Queue 的重启恢复必须作为验收项：运行中任务存在时重启或重建 scheduler 实例后，任务必须从 PostgreSQL 恢复到可继续执行状态，不能丢失 active task、`promptHistory`、后续 queued 任务、readAt/未读状态或已入 outbox 的 ClaudeQQ 通知。Code Queue 服务名、表名前缀或持久化目录发生迁移后，还必须运行 `bun scripts/cli.ts e2e run --only microservice:catalog-code-queue,microservice:code-queue-status,microservice:code-queue-health,microservice:code-queue-tasks`，证明 backend-core catalog、master mgr、k3s adapter 执行面、PostgreSQL 队列和任务列表都指向稳定 `code-queue`。批量验收必须通过公网 frontend 设置 `入队份数=5` 或使用多段 prompt 分隔，一次性入队 5 条任务，并确认 5 条任务按顺序进入 running/judging/succeeded，而不是只运行第一条。
- Code Queue 内存防回归验收：凡是改动 Code Queue 的持久化、scheduler、输出/Trace、health、列表/详情查询、日志导出或容器运行参数，交付前必须在 D601 用 `kubectl -n unidesk get deploy,pod,svc,endpoints -o wide`、`kubectl -n unidesk describe deploy/code-queue` 或等价 Docker inspect 确认 memory/swap 硬上限符合预算，运行 `kubectl -n unidesk top pod` 或 Docker stats 确认常驻内存、`OOMKilled=false` 和 `RestartCount` 未异常增长，再运行 `bun scripts/cli.ts microservice health code-queue` 确认 `/health` 是轻量 readiness 且暴露 PostgreSQL/notification/outbox 状态。验收还必须覆盖有历史任务存在时的 `/api/tasks/overview`、单任务详情和 output/transcript 查询，证明热状态裁剪不会丢历史输出、也不会重新把全部历史 `task_json` 缓存在进程内；涉及 TypeScript/frontend 验证的任务应能在 D601 Code Queue memory/swap 预算中完成 `bun run --cwd src/components/frontend check` 这类短时高内存命令，而不是被 memory watchdog 反复 SIGTERM。
- Code Queue 延迟防回归验收：凡是改动 Code Queue 列表、overview、readAt、Trace/summary 懒加载、实时 output/SSE 事件发布、frontend 请求策略、backend-core 用户服务代理或 frontend Code Queue 请求路径，交付前必须在有历史任务数据且有 active output 流动的 live 环境验证 `GET /api/tasks/overview`、`POST /api/tasks/<id>/read`、选定 task 的 `trace-step` 和前端 `/app/code-queue/` 首屏均低于 1s 目标；可运行 `bun scripts/src/code-queue-perf.ts --json --target-ms 1000` 采集公网 frontend 下的首屏耗时、最慢 API 和 DOM 完成指标，并用 `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview --raw`、D601 Pod `/health` 与 `/api/tasks/overview` curl、性能面板 `/api/performance` 与 `/api/frontend-performance` 失败/慢操作记录、`kubectl -n unidesk top pod` 或 Docker stats 补充后端耗时、代理 502 和内存/CPU 证据。验收结论必须同时说明是否使用了短 TTL cache、cache 如何被 mutation 或 archive append 失效、数据库索引/聚合是否命中、输出热路径是否只读增量指标，以及分页加载是否跳过 selected/active/stats；不能只展示 cache 命中后的单次快照。
- 运行 `bun scripts/cli.ts microservice health filebrowser`、`bun scripts/cli.ts microservice health filebrowser-d601` 和 `bun scripts/cli.ts microservice proxy filebrowser / --max-body-bytes 2000`，确认 File Browser health 返回 `status=OK`，WebUI HTML 包含 `File Browser`，D518/D601 通过 provider-gateway 访问节点本机 `4251`；随后在公网 frontend 的 `用户服务 / File Browser` 中确认 D518 为默认目标、可导出截图、iframe 紧凑布局不再有巨大 `folder` 标记遮挡文件名，并可浏览 `/mnt/c`。
- 在 D601 上用 `trans D601 ...` 调试业务仓库和容器，确认 `curl http://127.0.0.1:3254/api/health` 可用；不要把调试服务部署到主 server。
- 在 D601 上用 `trans D601 ...` 调试业务仓库和容器，确认 `curl http://127.0.0.1:18082/health` 和 `curl http://127.0.0.1:18082/api/snapshot` 可用；不要把 Pipeline 调试服务部署到主 server。
- 在 D601 上用 `trans D601 ...` 调试 `~/met_nonlinear`，确认 `curl http://127.0.0.1:3288/health` 可用；最终验收必须回到公网 UniDesk frontend，通过项目库选择、Fork、加入待启动队列和启动队列完成，不要把 MET Nonlinear 后端、Docker build 或训练任务部署到主 server。
- 在 D601 上用 `trans D601 ...` 调试 `~/.agents/skills/claudeqq`，可使用业务仓库 `docker-compose.unidesk.yml` 做本地诊断，但正式部署必须回到 `bun scripts/cli.ts deploy apply --service claudeqq`、k3s Deployment `claudeqq` 和 UniDesk `microservice health/proxy` 验证；不要把 ClaudeQQ 后端或 NapCat 调试服务部署到主 server，也不要把诊断 Compose 当作正式运行态。
- 运行 `bun scripts/cli.ts e2e run`，确认用户服务相关检查 passed；公网浏览器访问、截图和前端断言细则统一见 `$unidesk-webdev`。
- 登录公网 frontend，进入 `用户服务 / 服务目录`、`用户服务 / Todo Note`、`用户服务 / FindJob`、`用户服务 / Pipeline`、`用户服务 / MET Nonlinear` 和 `用户服务 / ClaudeQQ`，确认能看到 NC01 k8s、主 server 与 D601 provider 的受控运行信息、仓库引用、后端私有映射、Todo Note 迁移清单与树形任务、FindJob 指标和岗位预览、Pipeline 组件矩阵、React Flow 控制图、epoch 列表、epoch 甘特图和运行材料索引、MET Nonlinear 队列/GPU/镜像/Project config/训练历史、ClaudeQQ NapCat 容器登录二维码/NapCat 状态/事件订阅/消息推送/最近 QQ 事件；Todo Note 页面必须能创建临时清单、添加任务并删除临时清单，删除前必须按唯一临时清单名称重新选中对应行，禁止用未确认的当前 active 清单执行删除，FindJob 页面必须显示真实数字指标、`HEALTH OK` 和非空岗位预览，Pipeline 页面必须显示 `Pipeline v2 工作台`、`Health OK`、组件数、epoch 甘特图和结构化运行材料索引，MET Nonlinear 页面必须显示 `Health OK`、`Fork Project`、`启动队列`、`当前队列`、最大并发设置和 GPU/镜像面板，ClaudeQQ 页面必须显示 `Health OK`、`NapCat 容器登录`、`QQ 事件订阅`、`消息推送`、`事件缓存` 和私有代理说明，不能只停留在 loading 骨架；页面默认不得出现裸 JSON、JSONL 或逐行日志。
