# Cloud Hypervisor 执行器架构决策记录(ADR) 范围:cloud/chv/ 执行器链(#216 → #221 → #230 → #232 → #234 → #239 → #246 → 本轨)。 本文件只记录已落代码的决策,不记录未验证为已验证;真机、压测、动态安全 仍见 docs/chv-production-readiness.md §11。 ## ADR-1 SSH 目标:客机 IP 发现(已选) vs vsock / 纯 NAT(未选) - 决策:显式 CHV_SSH_HOST > CHV_NET_JSON 首个带 ip 的 net 条目 > 显式 CHV_SSH_FORWARD=1 宣称下的回环;三者皆无报 ssh_target_undeclared,绝不无条件假设 127.0.0.1:2222 能通。 - 未选 vsock:需客内 agent + 宿主代理两套新代码,当前客机镜像 无此组件,写进代码仍是假保证。 - 未选纯 NAT:DNAT 是宿主防火墙外部状态,执行器代码既不能 创建也不能校验它,写进代码仍是假保证;宿主防火墙见就绪 清单 §11.4 部署清单,代码门 + 防火墙缺一不可。 ## ADR-2 网关 key 走 seed 投递(已选) vs argv / 宿主环境(已弃) - 决策:provisioned 模式下密钥子集随 seed 写进客内 /etc/chv/guest-env(0600),远程命令 source 它;传输层密钥 进 argv 直接 ValueError;keyscan 模式有密钥即 fail-closed。 - 放弃 argv:宿主 ps / /proc//cmdline 可读;放弃宿主子进程 环境:Docker 线 -e 只传名、值仍在 CLI 子进程环境,CHV 比它 更严(值连宿主子进程环境都不进)。 - 残留:seed 随 stop 删,私钥在 seed 盘建好后即删, cleanup_stale_secrets 扫崩溃残留;删失败 fail-fast 或脏标记拒起。 ## ADR-3 磁盘 copy 默认,reuse 只作单租户实验(已选) - 决策:CHV_DISK_MODE 默认 copy,每任务独立 disk.raw; reuse 须同时满足 CHV_REUSE_SINGLE_TENANT_EXPERIMENT=1 与 CHV_SINGLE_TENANT_PROJECT 声明,且 wake 时审计放行。 - 边界:代码无法验证数据库多租户状态,声明 + 审计即机制, 不假装已校验;生产多租户下 reuse 一律不启用。 ## ADR-4 restore 不重预置(纠正,见 #232 审计) - 纠正:审计原说法“restore 须把 seed 接入 VmConfig”是错的—— restore 不走 vm.create,无 VmConfig 可挂盘;快照内已固化原 VM host key,重预置反而破坏钉死。 - 正确不变量:provisioned 下复用已钉死 known_hosts、缺失 fail-fast(host_key_not_pinned),绝不 keyscan 回落。 ## ADR-5 WarmPool:实验性,生产 dispatch 永远冷启动(本轨定案) - 决策(二选一的 B 项):把 WarmPool 从生产路径宣称里摘掉, 明确标为实验/停用,不再留“半接线”悬空状态。 - 原因:预热 VM 的磁盘是干净快照盘,不是任务盘;dispatch 前 查池若要真正接线,必须在派任务前给已暂停 VM 重新供给任务 盘(热换盘 / 二盘挂载 / host.py 磁盘语义三者都未实现), 把预热盘直接当任务盘用会击穿任务盘隔离不变量。 - 落地:WarmPool.wired_to_dispatch 恒为 False;_run_with_vm 绝不查池,dispatch 永远冷启动;acquire 内 resume 只保留为 实验接口;worker 启动/关闭仍调 reconcile_orphans()(上一世 残留清理是池子保留的唯一生产价值)。 - 重启条件:将来要重新接线,必须先实现任务盘重供给并补 “任务盘来源”不变量测试,否则不得把 wired 改成 True。 ## ADR-6 Docker goal 解析分叉:共享正典 + 同步守卫(本轨定案) - 决策:Local/CHV 共用 parse_goal_result 正典;Docker goal_task 内联版保留,但与正典的 8 个真实标记用例(met/unmet/ interrupted 标记/无结论异常退出/永久错误/无结论 rc0/ goal-resume 拒绝/已存在撞名)由 test_chv_arch95.py 的 DockerSyncGuardTest 行为测试钉住状态全等,任一实现改了 分叉测试即 fail,不再只靠文档清单。 - 注意:Docker 的超时证据路径仍与内联解析绑在一起,那是 有意保留的第二处差异,不在 8 用例的状态等价范围内。 ## ADR-7 #215 未合并时的复用边界(不复制模块) - 现状:#215(timeouts/gates/quota/sandbox_policy)未合并, 本链 base 里无其模块,本轨不复制其代码。 - 纠正(feat-chv-arch95plus,核对过 #215 远程分支真实 函数后):上一轨 ADR-7 的“超时→timeouts、配额→quota” 泛化映射是错的。#215 的 timeouts 收敛的是审批/队列 超时(None/0=不过期),quota 是按月美元配额,与 CHV 的 api/ready 秒数(0 无效)和 VM 数/磁盘 GB/快照数 单位与语义都不同,硬套会改行为,故这两项无映射。 - 真实映射(已落代码,见 cloud/chv/compat215.py): gates.resolve_inside(base, rel) 目录门 → CHV 快照/磁盘 路径白名单已在 hardening 里使用,合并后改为 import; sandbox_policy.tmpfs_isolated → CHV 客机 /tmp 落客机 磁盘、一律不隔离(fail-closed),合并后改为 import。 其余 #215 函数(审批、控制门、合并状态门、worker 寻址) 与 CHV 无交集,不映射。替换时须重跑本链测试,不得改语义。 ## ADR-8 WarmPool 生产隔离:facade 而非类导入 - 决策:生产路径(worker 启动/关闭)只调 cloud/chv/pool.py 的 reconcile_orphans_for_executor() facade,该函数内部才构造 WarmPool 并只调 reconcile_orphans();生产 dispatch 绝不调 prewarm/ acquire/release,executor.py 也不 import pool。 WarmPool 类与 sandbox re-export 保留仅为兼容(旧测试 import 路径),新代码不得经类入口进实验接口。 - 测试钉住:facade 测试断言 acquire/prewarm 绝不被调; executor 不 import pool 由模块依赖检查覆盖。 ## ADR-9 审计失败取舍:记日志 + 计数,不中断任务 - 选项 A(失败即中断任务):审计完整性最强,但 DB 短暂 不可用会把一次审计失败放大成任务失败,且与 worker 全局 best-effort 语义冲突,CHV 单独硬中断是不一致的。 - 选项 B(失败记日志 + 计数 + 不中断任务):已选。 executor._emit 失败时记宿主 error 日志并累加 audit_failure_count(宿主可观测,非任务状态),任务 继续;审计完整性本身没有保证,文档与就绪清单明写。 - 边界:本决策只针对 VM 生命周期审计;VM 启动/关机等 门禁失败仍按原有分类失败处理,不受本条影响。 ## ADR-10 长函数抽不动的耦合点(本轨后已无 >50 行函数) - 本轨后 cloud/chv/ 全部函数均 ≤50 行,上一轨的 7 个 长函数全部拆完:stop_vm(进程清理/文件清理/脏标记三 helper)、wait_guest_ready(provisioned 守卫/不匹配检查)、 restore_vm(校验/启动段)、cleanup_stale_secrets(单 VM 清理/活 VM 判定)、cleanup_stale_vms(死 socket 扫描/ 登记清理)、_unix_socket_api(读头/解析长度/读体/解析 响应)、validate_net_entries(归一化/去重/冲突检查)、 build_chv_config(组装段纯移动)。 - 残留耦合点(已拆但仍需注意):stop_vm 的三块清理共享 同一脏标记,任一块失败即整 VM 标记脏(有意,不拆成 三个标记);_unix_socket_api 的读满语义仍由四个 helper 共同保证,改任一 helper 须重跑截断/超时测试。 ## ADR-11 只读 VM 字段边界:chv_vm_dead 不是 live 状态 - 决策:server 任务序列化只在 CHV 任务且 worker 已把 VM 死亡写进 interrupted_reason 列时返回 chv_vm_dead=True, 其它情形字段不存在;worker 终局时把执行器的 vm_dead 原因写回该列(见 worker.py 终局块),否则该字段在 生产里永远不出现。 - 边界:该字段是“已报告死亡”,不是“VM 活着/死了”的 live 状态——server 进程没有 VM 登记表,live 状态编不 出来;前端无该字段时不显示 VM 状态,不猜测。VM 控制 按钮仍不做(后端无控制端点且安全审计 No-Go)。