feat-chv-closeup:收尾(executor 覆盖到 100%、合并/灰度准备,堆叠于 #264)

- 后端新增 cloud/tests/test_chv_closeup.py 85 项:唤醒失败
  (vm.create/vm.boot/API 超时/guest 就绪超时/host key 不匹配/
  provisioned 缺钉死)、恢复与快照失败、取消与清理(残留
  socket/登记/磁盘/密钥、脏标记)、中途死亡分类、只读
  execute 归类、入口校验、preflight 事后改写、防御性
  except、worker_adapter 剩余分支;executor.py 覆盖 100%。
- 前端新增 src/api/chv.closeup.test.ts 13 项:显式 false、
  undefined、可用性优先级、Workers 旧 bug 文案不复发、
  120 秒边界;控制按钮与新页面仍不做。
- 新增 tools/chv_merge_precheck.py(只读):逐个核 base/head/
  mergeable/CI/链衔接,实测 11 个 PR 全 NOT-READY(CI 全
  FAIL、mergeState 全 UNSTABLE),退出码 1,绝不合并。
- docs/chv-production-readiness.md 新增 §14:审计落盘运维
  runbook(定位/轮转/读取/清理,ADR-14 取舍不变)、mypy
  默认口径 2 错评估(沙箱验证过的标注修法,本轨不改
  其它链文件)、灰度三阶段可执行清单、V-1~V-9 检查表
  (全部未验证)、逐项评分。
- 安全语义不变:egress、key seed 投递、磁盘删除、默认
  provisioned、tap 校验、前端不绕后端白名单均未放宽。
- 只到 PR OPEN,不合并、不部署;CI 账单仍是硬门禁。
This commit is contained in:
host committed 2026-10-31 12:15:56 +00:00
1 parent 2f3ff8b3ec
commit 54f20dfe55
4 files changed
+1759

No files matched your search

+213
View File
@@ -572,3 +572,216 @@
6. 负载测试(V-8)与安全审计结论更新(No-Go → 有条件启用)。
7. 前端 live 点击验证(用户侧 live browser 检查清单见
本 PR 描述;回环地址限制下浏览器任务无法代劳)。
## 14. 收尾轨 feat-chv-closeup(堆叠于 #264 之上)
本节是准备工作,不是启用结论。本轨只到 PR OPEN,不合并、
不部署;生产就绪与动态安全的外部关卡(§13.4)原样未动。
### 14.1 本轨量化结果(实测,非上一轨回执转述)
- 后端:232 项 → 317 项(新增 `cloud/tests/test_chv_closeup.py`
85 项),全过。命令(仓库根目录,cwd 须为根目录——
在 `cloud/` 下跑会撞 `cloud/queue.py` 遮蔽 stdlib queue,
mypy 同理):
`python3 -m unittest cloud.tests.test_chv_arch95
cloud.tests.test_chv_arch95plus cloud.tests.test_chv_arch_maint_fe
cloud.tests.test_chv_fix cloud.tests.test_chv_gaps
cloud.tests.test_chv_optimize cloud.tests.test_chv_secfix
cloud.tests.test_chv_to10 cloud.tests.test_cloud_hypervisor
cloud.tests.test_chv_closeup`
- 覆盖率(同命令 + `coverage run --source=cloud/chv`):
chv 总 87%(1925 行,缺 258)→ 96%(缺 80);
`executor.py` 85%(缺 157)→ 100%(缺 0);
`worker_adapter.py` 82%(缺 16)→ 100%(缺 0)。
本轨基线与上一轨回执的行数口径略有出入(上一轨称
1826 行/缺 242,本轨同命令实测 1925 行/缺 258),
本轨前后对比只用本轨同一命令,不混用上一轨数字。
- 剩余未覆 80 行(按模块,全部在 executor/worker_adapter
之外,本轨未声称覆盖):
`api_client.py` 11 行(socket 层 chunked/超时/异常分支,
需 socket 级故障注入);`compat215.py` 2 行(旧兼容回落);
`config.py` 5 行(非法 env 构造分支);`disk.py` 22 行
(快照登记/剪枝边角与配额异常分支);`experimental_pool.py`
16 行(实验池,非生产路径);`hardening.py` 11 行
(真实 socket 权限与 net 校验边角);`hostkeys.py` 13 行
(ssh-keygen/genisoimage 子进程失败分支)。以上均非死
代码的断言——未逐行证伪,留待后续轨按模块补,不在本
轨凑数。
- mypy:silent 口径 `cloud/chv/` 0 错(不变);
默认口径 `cloud/marker_tools.py` 仍 2 错(见 §14.3,
本轨未改该文件)。
- ruff:`--select F,E9,W,I` 口径 `cloud/chv/` +
本轨新测试 0 错(不变;默认全规则基线不声称通过)。
- 前端:Vitest 23 项 → 36 项(新增
`src/api/chv.closeup.test.ts` 13 项),全过。
### 14.2 审计落盘文件运维 runbook(ADR-14 取舍不变,只补可操作性)
- 定位:`<state_dir>/chv-audit-spill.jsonl` 与轮转文件
`<state_dir>/chv-audit-spill.jsonl.old`。`state_dir`
默认 `~/.local/share/codex-app-server/chv-vms`,
可由 `CHV_STATE_DIR` 改址;以部署的实际值为准,
不要猜。目录本身 0700(见 §13.1 的 H6 门)。
- 触发:只有 `_emit` 的审计回调抛错时才写一行;回调
成功、无回调(只记日志)时不写。触发时宿主日志必有
`chv_audit_failed`,且 `audit_failure_count` +1——
落盘行是“哪次事件丢了”的唯一取证,计数只是数字。
- 格式:JSONL,每行 `{ts, event, kind, detail, task_id}`。
`detail` 沿用 `_emit` 契约,只含 VM 名/路径/URL/kind,
绝不含密钥值;读文件时仍按敏感文件对待(0700 目录内)。
- 轮转:当前文件 >1MB 时,先 `os.replace` 成 `.old`
再写新行;只留最新两代,更早的丢失事件会丢——
这是 ADR-14 已定的取舍,不是 bug,不要指望它当
完整审计账本。
- 读取(宿主侧,只读):
`cat <state_dir>/chv-audit-spill.jsonl`;
按事件过滤:`jq -c 'select(.event=="vm_failed")'
<state_dir>/chv-audit-spill.jsonl`;
两代一起看时先 `.old` 后当前文件(时间顺序)。
- 清理:`.old` 可随时删(删的是已被轮转覆盖的旧代);
当前 `.jsonl` 只有在已人工核对、且确认丢失事件已
无需追查时才删——删了就再也找不回那批事件。
落盘文件不是 DB 替代:任务 timeline 查询看不到它,
不要把它当成审计表去对账;审计完整性本身仍无保证
(见 ADR-14),保证需要 DB 事务级审计写入,那是
worker 全局语义改动,不在本轨。
- 落盘写失败:只记 `chv_audit_spill_failed` 日志,
绝不中断任务;此时连落盘都没有,唯一信号就是这条
日志与计数。
### 14.3 mypy 默认口径剩余 2 错评估(结论:本轨不修)
- 位置:`cloud/marker_tools.py:142`,`try: from zoneinfo
import ZoneInfo / except Exception: ZoneInfo = None`。
mypy 把 import 推断为 `type[ZoneInfo]`,再把 `None`
赋值判为两错(misc + assignment)。
- 仅标注修法(沙箱验证过,未进仓库):在 import 区后加
`from typing import Any` 与 `ZoneInfo: Any`(标注语句
本身不产生绑定),默认口径 2 错 → 0 错;运行时
`ZoneInfo` 仍是 stdlib `zoneinfo.ZoneInfo` 同一对象,
行为不变。验证在 `/tmp` 副本上做,不碰仓库文件。
- 不修的理由(两条,任一条已足够):① 该文件属标记
工具链(git 历史为标记工具提交),不在本链 §13.1
文件清单内,本轨改它就是动其它链的文件,徒增与
仍 OPEN 的审计/标记 PR 的 rebase 冲突面;② CHV 链
自己的门是 silent 口径(`--follow-imports=silent
cloud/chv/`),该口径已 0 错,默认口径的 2 错是
作用域外既有问题。修法留给 marker_tools 的归属轨,
本轨只把配方写在这里,不为清零越界改文件。
### 14.4 合并前检查脚本(只读)
- 脚本:`tools/chv_merge_precheck.py`,用法见文件头。
只读:`gh pr view` / `gh api` 查询 + 本地 `git` 读
remote,不 fetch、不 checkout、不 merge、不改 CI。
退出码:0 就绪、1 未就绪、2 查询失败(结论不可信)。
- 本轨实测(未含本轨新 PR,建好后须追加新 PR 号重跑):
`python3 tools/chv_merge_precheck.py` → 退出码 1,
结论 NOT-READY:11 个 PR 全部 OPEN / MERGEABLE,
链衔接全部 OK,当前 main 头仍 `aebc6238`(与 §13.1
一致),但 11 个 PR 的 CI 全部 `FAIL [verify:FAILURE]`、
mergeState 全部 UNSTABLE。脚本不诊断失败原因,
不把秒级失败改判——账单恢复变绿前结论保持 NOT-READY。
### 14.5 灰度三阶段可执行清单(准备工作,未执行、未验证)
通用停止条件(任一触发即停在该阶段,不进下一阶段):
`vm_failed` 审计激增、脏标记(`dirty_marker`)出现、
`chv-audit-spill.jsonl` 持续增长、残留盘/残留密钥扫描
清出本阶段不应有的项、任一任务静默降级到 Docker/Local。
通用回滚:停止向 CHV 路由新任务(改回 docker/local
执行器选择),已起 VM 走取消/关闭路径(`cancel_vm` /
`shutdown_vm`);代码回滚另议(revert 合并),不与
“停路由”混为一谈。
- 阶段 0(合并后、默认关闭):
1. 按 §14.4 脚本逐项核对,退出码 0 前不合并;合并
顺序即 §14.4 的链序,不得跳号。
2. 合并后确认 CHV 仍默认关闭(无生产任务路由到
`cloud-hypervisor`,任务创建白名单外的执行器值
仍被后端拒绝)。
3. 在 KVM 宿主(须见 `/dev/kvm`,本 VM 与生产宿主
均无 KVM,不能替代)上跑 §14.6 的 V-1~V-9,
全部通过才可谈阶段 1;任一未跑即记未验证。
回滚点:合并本身可 revert;未进生产路由前无任务影响。
- 阶段 1(单租户可信项目):
1. 只对明确指定的单租户项目派发 CHV 任务;复用
实验仍需 `CHV_REUSE_SINGLE_TENANT_EXPERIMENT` 与
`CHV_SINGLE_TENANT_PROJECT` 同时声明,缺一即
preflight 拒绝(代码行为,不放宽)。
2. 检查点:每任务 VM 销毁后 `state_dir` 无该任务
残留盘与 seed;`chv-audit-spill.jsonl` 无新增;
取消路径真关 VM(V-5 口径)。
3. 停止条件见通用条件;回滚:该项目任务改回原
执行器,CHV worker 停接新任务。
- 阶段 2(受限多租户):
1. 放开到受邀项目,连续观察一周:VM 残留盘、
审计落盘、容量门(`CHV_MAX_VMS`,含重启后残留
socket 扫描)、快照配额四项指标。
2. 检查点:每周复核四项指标零异常 + §14.2 落盘
文件复核;任一指标异常即退回阶段 1。
3. 停止条件见通用条件;回滚:收紧到阶段 1 项目集。
- 阶段 3(全量多租户):仅在阶段 2 指标干净 +
动态安全审计结论由 No-Go 改为有条件启用后,才可
讨论;绝不从阶段 0 直跳全量。本轨不给阶段 3 的
时间表,不写成已就绪。
### 14.6 真机验证用例 V-1~V-9 验收检查表(全部未验证)
每行格式:用例 — 验收标准(§13.2 细化)— 证据栏(待填)。
状态栏一律“未验证”,跑过才改,跑过≠通过。
- [ ] V-1 冷启动 — 任务 completed,宿主 state_dir 无该
任务残留盘与 seed。证据:______ 状态:未验证
- [ ] V-2 seed 首启时序 — `sshd -T` 显示
PasswordAuthentication no 等加固项,known_hosts 钉死
校验通过,非 TOFU 回落。证据:______ 状态:未验证
- [ ] V-3 快照恢复 — 恢复后 known_hosts 不变,快照文件
落盘非空且经 `snapshot_not_persisted` 校验。证据:______
状态:未验证
- [ ] V-4 网关真实任务 — 任务完成 + 宿主进程表抽查无密钥
(密钥只走 seed 客内文件)。证据:______ 状态:未验证
- [ ] V-5 取消与配额真杀 — VM 进程消失,任务 interrupted
且原因写回正确。证据:______ 状态:未验证
- [ ] V-6 审计落盘 — 落盘行 event/task_id/detail 与丢失
事件一致,`audit_failure_count` +1,任务不中断。
证据:______ 状态:未验证
- [ ] V-7 动态安全负向 — 直连被 tap/防火墙挡、跨 VM 探活
与 IP 冒用被 SSH 钉死拒、崩溃注入后残留被清理或脏
标记拒起,每项都有“被挡住”的证据。证据:______
状态:未验证
- [ ] V-8 负载 — 4 VM 并发下容量门计数含重启后残留
socket 扫描,死 socket 不占名额、活 VM 不被超发,
无静默降级。证据:______ 状态:未验证
- [ ] V-9 供应链清单 — 构建宿主产出真实 lockfile,
`--verify` 通过且 unresolved 为空,再用
`check_lockfile.py` 对 BUILDINFO.versions 复核。
证据:______ 状态:未验证
### 14.7 本轨逐项评分(目标 10,无一项硬写 10)
- 代码质量 9.8 → 9.8,未达 10:executor/worker_adapter
覆盖到 100%,但 chv 仍有 80 行未覆(非 executor
模块),且默认口径 mypy 余 2 错在作用域外。差:代码内。
- 架构 9.7 → 9.7,未达 10:本轨未改架构,ADR-13/14
取舍不变。差:外部(生产验证后才可复评)。
- 可维护性 9.7 → 9.8,未达 10:新增合并检查脚本与
§14 运维/灰度文档,落盘可操作性补齐;但跨链文件
的 mypy 归属未落。差:代码内(小)。
- 文档 9.8 → 9.9,未达 10:§14 补齐 runbook、灰度
清单、V 检查表、评分表;仍缺真机证据填表。差:外部。
- 测试 9.7 → 9.8,未达 10:后端 317 项、前端 36 项全过,
executor 100%;但 chv 总覆盖 96% 未满,且真机用例
0 项已跑。差:代码内(80 行)+ 外部(真机)。
- 静态安全 9.6 → 9.6,未达 10:取舍不变,只补落盘
运维;lockfile 仍待构建宿主产出,绝不编造。差:外部。
- 前端 9.5 → 9.6,未达 10:只读四态与可用性文案边角
全覆盖(显式 false、undefined、优先级、旧 bug 文案
不复发、120 秒边界);控制按钮与新页面仍不做,
live 点击未验。差:代码内(有意不做)+ 外部(live)。
- 生产就绪 5.0 → 5.5,未达 10:仅因合并/灰度准备
工作(可跑脚本 + 可执行清单)小幅上升;CI 仍
UNSTABLE、未合并、未部署、真机 0 项。差:外部为主。
- 动态安全 2.0 → 2.0,未达 10:本轨无任何动态验证,
V-7 未跑。差:外部(KVM 宿主 + 审计结论)。