Commit Graph
449 Commits
Author SHA1 Message Date
host 3209f59cd6 merge(chv-muse-round11): 合入 P2 nspawn 单元 + broker + 浏览器子代理 (c61484a) 2026-11-06 21:35:53 +00:00
host 55f6d7ad6c merge(chv-muse-round11): 合入 P1 常驻 VM + 气球 + vsock (78098c5) 2026-11-06 21:35:51 +00:00
host 78098c5594 feat(chv-muse-p1): 常驻 VM + WarmPool 扶正 + 气球 + vsock,对齐 Muse
每用户一台常驻 VM(暂停态,resume 到 SSH 5 秒内为待验证目标),
池空/失败回落冷启动;CHV 配置加 virtio-balloon + free-page-reporting
与 vsock notify 最小实现(SSH 仍是命令通道与就绪判定)。
新模块 resident_pool.py / vsock.py + 客内 vsock_notify.py;
_config 增加 CHV_RESIDENT_IDLE_MINUTES / CHV_BALLOON / CHV_VSOCK;
worker_adapter 接常驻孤儿清理 facade;ADR-17 差距表见
docs/chv-muse-p1-gap.md。

恢复收尾(分离后回归,见 gap.md §4):VmDeathTest 死亡模拟改杀
实际跑命令的 VM;两处冷路径旧测试显式关常驻;_balloon_vsock_payload
与 listen_notify 拆到 50 行内;resident_pool.py:208 mypy 修掉。
executor.py out 未注解与 marker_tools.py:142 是基线旧错,未动。

测试:全量 cloud 套件 1934 OK(skipped=8);mypy 在改动文件上
干净(仅基线旧错 4 处)。真机验证待第三棒让出 WSL 测试机。
2026-11-06 21:31:48 +00:00
host c61484aa68 feat(chv-guest): 对齐 Muse P2——nspawn 单元 + broker + 浏览器子代理
- chv-cell:systemd-nspawn 单命令单元,--private-users=1000:65536
  (单元 root → 客机 chv),丢 8 项能力(含 SYS_PTRACE/NET_ADMIN),
  syscall 过滤扣 io_uring,遮蔽 /etc/chv 与 playwright,
  --network-veth(10.200.0.0/24 + NAT),白名单环境透传
- chv-broker:Unix socket + SO_PEERCRED(uid 范围 + pid ns 对照),
  白名单 ping/sync/browser;sync 只调 chv-sync(runuser -u chv)
- chv-browser:单元外独立进程,只暴露 open/snapshot/screenshot/
  close/ping,返回无障碍树快照(截断);evaluate/content 拒绝
- build.sh:装 systemd-container/iproute2/iptables,拷入三脚本,
  建 chv-browser 用户与两个 systemd 单元;vsock_notify 条件拷入
- buildinfo:内容项加 P2 四项,支持可选 extra_contents
- 测试:cloud/tests/test_chv_muse_p2.py 40 项全过;broker/browser
  真实 socket 回路冒烟通过(root 连 broker 被拒)
- 差距表:docs/chv-muse-p2-gap.md;真机验证待测试机空闲(round 11)
  本地做,不推、不合、不部署
2026-11-06 20:53:15 +00:00
host 06c3e172fe fix(chv): restore 路径补 H7 net 校验+登记,seed 暂存文件走 atomic_file
restore_vm 此前既不校验 net 冲突、也不登记 net.json:
已登记 VM 的 tap/mac/ip 会被恢复出的 VM 静默复用,且
恢复出的 VM 自身不登记、后来的 wake 也看不到它,两边
都漏判。现与 wake 同款:_validate_restore 在起 VMM 前
校验冲突(net_tap_conflict + vm_failed 审计),
_restore_boot 在 restore 成功后登记 net.json,登记失败
审计 net_registry_write_failed + 拒绝恢复(P2-19 语义,
stop_vm 清半恢复 VM)。

hostkeys.write_seed/_apply_seed_extras 的 meta-data、
user-data、network-config 改走 atomic_file(mkstemp 0600
→ 写 → fsync → rename):user-data 含 host 私钥明文,
直接写 + 事后 chmod 会在 chmod 前以 umask 权限短暂可读;
半截文件本身到不了 ISO(唯一消费者 genisoimage 只在
write_seed 返回后跑),原子写同时堵住权限窗口与半截残留。

回归测试 6 个:restore 冲突拒起/恢复后登记/登记失败
拒起/无 net_json 不登记;seed 走原子写(0600、无 tmp
残留)/写失败不留半截文件。CHV 全套件 395 测试通过。
2026-11-06 19:32:41 +00:00
host f448ecb74b docs(chv): L4/M6 取舍定案 + 部署核对清单
- L4:保持警告不改硬拒(CHV_NET 是手动直接启动命令透传值,
  与 API 的 CHV_NET_JSON 是两条路径;硬拒会断手动运维,
  误配代价只是 API 模式忽略 CHV_NET);写清兼容影响与
  将来改硬拒的前置条件;部署核对只设 CHV_NET_JSON
- M6:确认声明 + 审计就是本项机制,不假装已校验;部署核对
  加人工项(部署记录写明已核对租户数 = 1 与项目名一致,
  并复核 disk_reuse_experiment_allowed 审计)
- §11.4 加环境变量核对与残留尾巴审计抽查项
  (net_registry_write_failed / dirty_marker_write_failed /
  vm_net_registry_delete_failed 任一出现即先修宿主再启用)
2026-11-06 18:41:56 +00:00
host 2ad29eeb1f feat(chv-guest): M4 Chromium 单独钉死 + /app 收紧为最小集
- Chromium 钉死:CHROMIUM_HEADLESS_SHELL_REV(纯数字修订号)
  设即钉死,buildinfo.py 校验读回的 ms-playwright 缓存目录名
  须恰好是该修订号,否则构建失败;generate/check 的 --verify
  也复核;未设时只记录、构建输出明说未钉死,BUILDINFO 记 null
- /app 最小集:build.sh 不再把整个 $REPO_DIR/cloud/ 拷进客机,
  只拷 host.py(只用标准库,不依赖 cloud/);REPO_DIR 预检
  不再要求 cloud/ 存在
- generate_lockfile.py 只读 BUILDINFO.versions 一事已在 to10 轨
  改写,本棒以合成 BUILDINFO 干跑复核(假版本 9.9.9 进 lockfile,
  宿主 git 1:2.43.0 未混入)并补钉死复核;真实 lockfile 产出
  仍待 V-9 真机构建,不编造版本号
2026-11-06 18:41:56 +00:00
host c39715659e fix(chv): P2-20/P2-19/P2-17/P2-18 残留收尾 + NO_PROXY 合并
- P2-20:_guest_env_delivered 加专用锁,读写只走
  _delivered_secrets/_mark/_clear 三个入口,并发 hammer 测试钉死
- P2-19:_record_net_entries 失败发 net_registry_write_failed
  独立审计并返回 False;_boot_vm 见 False 即拒起(wake 随后清半醒 VM)
- P2-17:net.json 删失败发 vm_net_registry_delete_failed 审计;
  _write_dirty_marker 失败发 dirty_marker_write_failed 审计并返回
  False,stop 不抛的契约不变
- P2-18:新增 cloud/chv/atomic_file.py(mkstemp 随机名 + 文件
  fsync + rename + 目录 fsync),net.json/known_hosts/
  snapshots.json/pool registry/脏标记/spill 全部走它,固定 .tmp 清零
- H1 附带:NO_PROXY 实测确有覆盖 bug(env 重复赋值以后者为准,
  调用方 NO_PROXY 被 egress 的 localhost 值静默覆盖),改为合并
  单条发出;egress 已设时调用方代理变量(含小写)一律不发
- 测试:新增 cloud/tests/test_chv_residual.py(23 项),旧 closeup
  两处钉死旧语义的测试按新语义更新
2026-11-06 18:41:50 +00:00
host ba5df980bf fix(chv): known_hosts 22 端口写裸 host 行,修 provisioned 钉死失效
OpenSSH 默认端口只查裸 host 名、非默认端口只查
'[host]:port':known_hosts_line 对 22 端口也写
'[192.168.10.10]:22',ssh 永远匹配不上,报 Host key
verification failed、wake fail-closed 关 VM。keyscan
TOFU 输出裸行,lite9 没暴露。非 22 端口仍用括号形式。
2026-11-06 17:59:53 +00:00
host 4f3a176771 fix(chv): CHV 54 VmConfig 形态——payload.kernel/cmdline 纯字符串 + 磁盘显式 image_type Raw
真机 CHV 54.0.0 实测:旧顶层 kernel:{path}/cmdline:{args}
create 时 204 照放、vm.boot 400 'No bootitem provided';
补 payload 后又报 'Image type required for disk'(raw 盘
无魔数、探测失败),磁盘须显式 image_type,枚举原名
Raw(JSON 写小写 raw 报 unknown variant)。
2026-11-06 17:59:53 +00:00
host 5b6514d019 fix(chv): api_client 接受 204 无 Content-Length 响应
WSL 真机实测(CHV 54.0.0):vm.create 成功回 204 No Content,
且不带 Content-Length、无体;严格校验(P1-6)把合法成功当
失败拒掉,wake_vm 卡在 vm.create。204/304 按 HTTP 语义体长
恒为 0,不要求长度、不读体(keep-alive 下读会等到超时);
其它状态仍严格(无长度/截断/chunked 均拒绝)。
测试:test_chv_cover100 新增 3 项(204 成功、200 无长度仍拒、
_parse_status)
2026-11-06 17:43:54 +00:00
host d7cc482ed5 feat(chv-guest): seed 附加项供客机用户与静态网络(wake 路径前置)
wake_vm 在 WSL 台架上跑通需要客机身份与网络:镜像本身不建
用户、tap 无 DHCP,缺 users 块的 VM 登不进去、缺 network-config
的 VM 拿不到静态 IP。最小补法(复用 write_seed 机制):
- hostkeys.write_seed 新增 user_data_extra(原样追加进
  user-data,供 users: 块)与 network_config(写 seed
  network-config 文件,0600);附加项含 runcmd: 即 ValueError
- config _seed_fragment:CHV_SEED_USER_DATA_EXTRA_FILE /
  CHV_SEED_NETWORK_CONFIG_FILE(只存路径)
- executor._seed_extras 在 wake 起 VMM 前读入,缺失即
  ChvFailure seed_extra_missing;provision_host_key 透传
- README §7 残留更新:guest-env 集成已做
- 测试:test_chv_gitea_sync 新增 5 项(写盘/禁 runcmd/
  配置映射/wake 带附加项/缺失 fail-closed)
2026-11-06 16:58:27 +00:00
host e7f699c62b feat(chv-guest): Gitea 同步变量经 seed 进 guest-env(执行器集成)
- 新增 cloud/chv/gitea_sync.py:6 键白名单,三源合并
  (宿主 CHV_GITEA_* < 任务 gitea_sync < extra_env),
  必填 4 键缺失即 fail-closed,空即不同步
- ssh_transport:GITEA_TOKEN 进密钥集(argv 拒绝),其余
  5 键进 guest-env 集(split_guest_env)
- hostkeys.write_seed:guest_env 与 secret_env 合并写同一
  条目,仍无 runcmd、0600
- config:_gitea_fragment 产 gitea_sync_env(函数内 import,
  叶子模块顶层只许标准库)
- executor.wake_vm:起 VMM 前合并+校验(gitea_sync_incomplete),
  guest-env 随 seed 投递;provision_host_key 透传 guest_env
- worker:_run_attempt 仅对 CHV 执行器把同步变量并进 gateway_env
- 测试:cloud/tests/test_chv_gitea_sync.py 20 项
- 边界:tasks 表无 JSON 列,DB 往返丢任务 gitea_sync;
  生产主来源是宿主 CHV_GITEA_* 环境变量
2026-11-06 16:55:32 +00:00
host 67ca0b86cd fix(chv-guest): seed user-data 不再自带 runcmd,防顶掉镜像钩子
sync-test 首轮实测:seed 的 runcmd 整体替换了镜像
/etc/cloud/cloud.cfg.d/99-chv-sync.cfg 的 runcmd(cloud-init
不跨配置类型合并,merge_how 救不了 conf.d),chv-sync
boot 钩子静默不跑、启动 clone 没跑。

- hostkeys.build_seed_user_data 不再发 runcmd;chmod 600
  已由 write_files 0600 保证;sshd 重启搬到镜像自己的
  runcmd(cloud-final,sshd 首次启动之后)。
- build.sh:99-chv-sync.cfg 的 runcmd 增加 restart ssh
  一行(在 chv-sync boot 之前)。
- README §5 同步硬性要求;回归测试 2 项(无 runcmd、
  带 secret 仍单 write_files 列表无 runcmd)。
- 针对性 147 项全过(test_chv_to10/gaps/secfix/fix/
  arch95plus)。

本地提交,未推未部署。
2026-11-06 16:35:14 +00:00
host cbd5ab7c0b fix(chv-guest): 首启缺 key 时才生成 host key(B-23)
Minimal 底座 cloud.cfg 不含 ssh 模块,build.sh 又写
ssh_genkeytypes: [] 彻底关掉首启生成:客机无 host key、
sshd -t 失败、ssh.service 连败 5 次,SSH 全程不通(真机
冒烟 131s 未通,console 已到 login、ping 通、tap 正常)。
修正:99-chv-hostkey.cfg 改写首启 bootcmd,只在缺
ed25519 key 时 ssh-keygen -A;ssh_deletekeys: false 保留,
绝不重生成已预置的 per-VM key(bootcmd 跑在 seed
write_files 之前,预置 key 随后覆盖生成值)。README §5
同步修正;新增 BuildScriptTest 回归断言。
2026-11-06 12:05:43 +00:00
host dcb0824f78 fix(chv-guest): 浏览器装系统路径(B-22)
Round 7 构建本身成功,但 BUILDINFO.versions.chromium 为空:
virt-customize 的客机 shell 里 HOME=/,Playwright 默认缓存
~/.cache/ms-playwright 落到根目录 /.cache(实测浏览器在
/.cache/ms-playwright/chromium_headless_shell-1248),而
build.sh 读的是 /root/.cache/ms-playwright(不存在),ls 读回
空文件;buildinfo.py 当时不校验 chromium 非空,静默放过,
事后 check_lockfile / generate --verify 兜底报未覆盖(exit 1)。
改:安装与读回都走 PLAYWRIGHT_BROWSERS_PATH=
/usr/local/share/ms-playwright(agent 运行时也须设同一变量,
否则 chv 用户在家目录找不到浏览器);buildinfo.py 加
playwright_browsers_path 字段,并校验 playwright=1 时
versions.chromium 非空;README 同步。
2026-11-06 10:55:02 +00:00
host 4c8e4eac7b fix(chv-guest): apt 用 --no-install-recommends(B-21)
Round 6 真机构建在 Playwright 步 ENOSPC 失败:--install 默认装
Recommends,python3-pip 连带拉入 python3-dev → gcc/g++/
build-essential 全套,根分区 2.4G 用到 2.2G(95M 空闲),
headless-shell 下到 70% 写满。改走 --run-command apt-get
install --no-install-recommends,必需项显式列出(fontconfig、
openssh-client),两轮 apt 后 apt-get clean + 清 lists 收尾。
buildinfo contents 与 README 同步加 openssh-client/fontconfig。
2026-11-06 10:29:59 +00:00
host 5206dfbc43 fix(chv-guest): gt 用绝对路径调用(B-20)
Round 5 真机构建在 gt 安装步失败:install.sh 已装好
/usr/local/bin/gt,但 virt-customize 的客机 sh 里 PATH 不含
/usr/local/bin,同一 --run-command 链里的 gt version 报
command not found,整个 virt-customize 退出非 0。
改两处为 /usr/local/bin/gt version(安装断言 + 版本读回文件)。
install.sh 自身的 SHA256 校验与二进制下载均已通过,不是下载问题。
2026-11-06 10:09:02 +00:00
host 4d09518338 feat(chv-guest): 轻量镜像 + Gitea 双向同步(停全量版,改 Ubuntu Minimal)
- build.sh 改轻量:Ubuntu Minimal 底座、apt 精简(git/ssh/cloud-init/
  python3/pip/curl/ca-certificates/xz-utils/fonts-wqy-microhei)、
  Playwright + chromium-headless-shell、去 B-16 +12G 扩盘;
  加 git + gt 1.0.0(官方 install.sh + SHA256 校验,GITEA_CLI_VERSION
  钉 1.0.0)与 chv-sync 启动钩子(cloud-init runcmd)
- B-19(新发现):dpkg freeze 的 ${Package} 会被客机 shell 展开
  成空,284 行全空白;加反斜杠转义,探针实测已出真实版本
- buildinfo.py:轻量 contents、chromium-headless-shell、gt_version
- generate/check_lockfile.py:gt/chv-sync 声明来源、headless-shell 映射
- chv-sync.sh(新):boot/pull/push/status,分叉/脏树失败关闭,
  凭证走 seed 盘 guest-env,不进镜像
- README §1/§2/§3/§4 改轻量口径,新增 §7 Gitea 双向同步
- tests:ChvSyncTest 6 项(本地 bare 仓当远端);针对性测试 144 项全过
2026-11-06 09:47:54 +00:00
host 9e60d5dfaa fix(chv-guest): B-18 virt-get-kernel -o 是目录不是文件
- 第三轮真机构建:virt-customize 全程通过(Playwright 步已过),
  随后 virt-get-kernel -a disk.raw -o $OUT/vmlinux 失败;手动
  查 --help 确认 -o 是 output directory——旧写法会在 OUT 里
  建出 vmlinux 目录,后面的 test -s 文件校验必然失败,即使
  下载成功构建也会失败。
- 修复:-o 改为 $OUT,下载 vmlinuz-<ver> 与 initrd.img-<ver>
  后把 vmlinuz-* 移为 vmlinux 文件;initrd.img-* 留在 OUT 里
  作配套产物(与任务产物清单一致)。
- 同轮里 virt-get-kernel 首跑另报过一次 "file receive
  cancelled by daemon",同一盘手动重跑即成功(瞬时故障),
  此处失败重跑一次,与 B-05/B-06 重试口径一致。
2026-11-06 09:01:20 +00:00
host b5e75f156d fix(chv-guest): B-17 Playwright 安装加 --break-system-packages
- 第二轮真机构建实测:单次 virt-customize 在 playwright 步
  失败,pip3 install --no-cache-dir playwright 在 Ubuntu 24.04
  上报 PEP 668 externally-managed-environment 而拒绝安装;
  随后 python3 -m playwright 报 No module named playwright。
  此前 PLAYWRIGHT 默认 0,该步从未在默认构建里跑过,缺口
  被默认开启暴露出来。
- 修复:playwright 的 pip 安装与第一处 pip 安装同款口径
  (--break-system-packages,失败回落无 flag 再试),且只有
  pip 成功才跑 python3 -m playwright install --with-deps
  chromium(括号分组 + &&,失败即停,不再以 ; 带病续跑)。
- 第二轮中间证据:B-10 步已过(sshd -t 完成、99-chv.conf 与
  cloud-init hostkey 配置已写入、客机 /etc/ssh 无残留 host
  key;dpkg/pip freeze 已写 876/92 行)。
2026-11-06 08:34:14 +00:00
host 368bccd16e fix(chv-guest): B-16 真机构建扩根分区(云镜像根分区仅 2.4G 会写满)
- 首轮真机构建实测:单次 virt-customize 在 playwright
  --with-deps 装 g++/make 时报 No space left on device,
  根分区 /dev/sda1 仅 2.4G、已用 97%。
- 修复:qemu-img resize +12G 后 sgdisk 只扩 1 号分区
  (起始扇区实测取,不写死;14/15/16 不动),再 guestfish
  resize2fs;实测根分区 2.4G→14G、可用 12G、编号仍为 sda1。
- 不用 virt-resize:实测它重写分区表、把根分区从 1 号重编号
  为 4 号,而执行器默认 cmdline 是 root=/dev/vda1,重编号后
  无法启动。工具检查清单加 sgdisk、guestfish。
2026-11-06 07:59:29 +00:00
host 3b41f864e8 test(chv-guest): 构建脚本测试夹具跟进 B-09 新契约
- 假 codex 二进制须报出版本号(codex --version 核对)
- 假仓库改为真 git 仓库,REPO_COMMIT 取其实测短 hash
  (旧 test123 非 hex,B-03 格式校验本应拒绝)
- 干跑行补 BUILDINFO.json 字样(断言依赖)
另:79ba56b(并行轨)已把 Playwright 改默认开启、单次
virt-customize、补 fonts-noto-cjk,本提交只补测试与干跑
文案,不动构建逻辑
2026-11-06 15:34:06 +08:00
host 79ba56bd51 feat(chv-guest): Playwright 默认开启、单次 virt-customize、补 fonts-noto-cjk
- virt-customize 3 次调用合并为 1 次(VC_ARGS 数组按序执行;
  PLAYWRIGHT=0 时仍可显式关掉,freeze 步骤保留兜底)。
- PLAYWRIGHT 默认 0→1:浏览器常驻复用;最终 freeze 在
  Playwright 安装之后(B-08),BUILDINFO.versions.pip 含
  playwright,chromium 目录名照常记录。
- apt 清单加 fonts-noto-cjk(浏览器 CJK 渲染字体,不是 LaTeX;
  xelatex 仍不装)。
- 针对性测试:to10+gaps+secfix 94 项,91 过;3 项失败为
  7f18c2d 已有 fixture 陈旧(REPO_COMMIT=test123 非 hex),
  与本次改动无关,改前改后一致。
2026-11-06 07:25:15 +00:00
host 7f18c2d13b fix(chv-guest): 客机镜像构建缺口补齐(B-02/B-03/B-04/B-05/B-06/B-07/B-08/B-09/B-10/B-13/B-15)
- B-10(实测):virt-customize chroot 里 sshd -t 必败,双因:
  无 host key(exit 1)+ 缺 /run/sshd(exit 255);修为
  mkdir -p /run/sshd + ssh-keygen -A 临时生成,断言通过后
  立删临时 key,镜像仍不带 host key(已在 WSL 真机预演验证)
- B-08:Playwright 装在第二次定制,须重写 pip freeze 否则
  BUILDINFO 里无 playwright;Chromium 以 ms-playwright 缓存
  目录名记录进 guest-versions-chromium.txt
- B-09:codex --version 须含 CODEX_VERSION;REPO_COMMIT 须
  等于 HEAD 短 hash;工作树脏即拒绝构建
- B-02/B-13:BUILDINFO 生成抽成 buildinfo.py(可单元测试),
  补 disk.raw/vmlinux sha256+大小、ubuntu 镜像 sha、cmdline、
  playwright 开关、image_sha256 字段
- generate_lockfile.py 改写:版本唯一来源改为
  BUILDINFO.versions(客机读回),codex 三件套取
  codex_version、/app/host.py 取 repo_commit、chromium 取
  versions.chromium;不再查构建宿主 dpkg/pip(旧方法不成立)
- check_lockfile.py 补强:条目格式、contents 全覆盖、冻结
  清单双向比对
- B-03/B-04 输入格式校验、B-05 curl 重试、B-06 哈希不符
  删文件重下、B-07 磁盘预检、B-15 set -euo pipefail
- 同步改 test_chv_to10.py:generate 不再查宿主,改断言客机
  版本来源 + 不回落宿主
2026-11-06 15:13:23 +08:00
host 8c8d62f70d test(chv): 补齐 chv 模块剩余覆盖到 100%(生产与实验分开计分) 2026-11-01 07:00:21 +00:00
host e718f0f326 test(chv): 修复 isWorkerAlive 边界测试抖动(精确边界改确定性字段测) 2026-11-01 00:44:12 +08:00
host 54f20dfe55 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 账单仍是硬门禁。
2026-10-31 12:15:56 +00:00
host 2f3ff8b3ec feat-chv-to10:往 10 分方向提升(类型/lint 清零、worker 适配抽取、审计落盘、lockfile 生成脚本、前端四态与统一文案、就绪清单 §13) 2026-10-31 10:20:42 +00:00
host df8c64bd64 feat(chv): 架构/可维护性/前端只读侧推进——生产实验分离、配置片段化、VM 只读展示
架构:WarmPool 从 cloud/chv/pool.py 移到 experimental_pool.py,
生产残留清理彻底分文件到 cloud/chv/orphans.py(facade 直接读
registry 文件清理,不构造池、不碰 prewarm/acquire/release);
cloud/chv 与 cloud/sandbox 不再导出 WarmPool,旧路径一律
ImportError(CHV 未合并、无生产用户,兼容保留已无保留理由);
worker 改走 orphans facade,不再持有 _warm_pool;#215 对齐从
接口形状推进到 REPLACEMENT_POINTS 替换清单(R1/R2 可机械替换、
R3 永不替换,#215 模块当前不存在由测试钉住)。

可维护性:build_chv_config 拆成 base/ssh/network/quota/disk/
timeout 六片段(组装只做 dict 合并,键冲突即报错;38 字段集
与取值不变,单项非法报错文案逐字一致);ADR-12 模块职责图
与禁 import 清单(AST 钉住);cloud/chv/ 公开 API 全量类型
标注、无 Any 逃逸,并修正三处旧标注错误;死代码扫一遍
(删实验池已无调用的 _quarantine 委托与未用 _LOGGER)。

前端(只读侧,不做控制按钮):chv.ts 新增纯逻辑谓词与文案
(执行器精确匹配、附件守卫、VM 状态文案、列表徽标);列表
行新增 VM 已报告死亡徽标;详情对全部 CHV 任务显示 VM 行
(已报告死亡/未报告死亡,不猜 live);WorkersSection 与成本
卡沿用已有数据,不新开池管理/成本页;服务端未新增第二个
生命周期字段(持久化事实只有三项,chv_vm_dead 已是完整派生),
只把派生抽成 _chv_vm_dead_from_row 并补 goal 回落形态测试。
2026-10-31 06:55:26 +00:00
host 42de20da68 feat(chv): 架构与代码质量 9.5+ 提升——长函数清零、WarmPool facade、#215 对齐、审计计数、只读 VM 字段、ADR-8~11 2026-10-31 06:20:12 +00:00
host c908a0147a feat(chv): 架构与代码质量提升——WarmPool 定案、长函数提取、严格测试替身、Docker 同步守卫、配置统一校验、类型注解与 ADR
堆叠于 #246(feat-chv-secfix),只到 PR OPEN,未合并、未部署。
安全语义不变:egress、key seed 投递、磁盘删除、默认 provisioned、
tap 校验一律保留;前端零改动。
- WarmPool 定案为实验性(ADR-5):dispatch 永远冷启动,
  wired_to_dispatch 恒 False,acquire/resume 只保留实验接口,
  worker 仍调 reconcile_orphans 清理残留。
- 长函数提取:preflight 126→31、wake_vm 135→36、__init__ 69→31、
  build_chv_config 159→58;错误文案逐字不变。
- 严格测试替身:fake API 校验 VmConfig(任务盘+seed 盘、net
  无重复),fake SSH 校验 known_hosts 钉死;新增 5 项测试。
- Docker 同步守卫:8 个真实标记用例下 Docker 内联版与正典
  parse_goal_result 状态全等,改分叉即 fail。
- 配置统一校验:preflight 以 config_invalid 统一兜底端口/配额/
  超时/快照/host-key 模式(构造时已校验正常值,preflight 兜底
  事后改写);CHV_NET 与 CHV_NET_JSON 双设仍警告不硬拒。
- 类型与 lint:chv 叶子模块公开 API 补类型注解;stdlib 扫描
  揪出 executor 内 1 个真死 import(host_key_paths)并删除。
- ADR:docs/chv-architecture.md(ADR-1 至 ADR-7)。
测试:CHV 163 项全过;cloud 全量 1593 项三跑结果为 8 errors
(首跑详细日志因 tail 截断丢失)、全过、1 失败(浏览器代理
时序测试,与 CHV 无关),全量本身存在时序偶发,CHV 套件
确定性全过。
2026-10-31 05:50:56 +00:00
host baf26beb78 feat(chv): 安全审计修复 High/Medium/Low(堆叠于 #239)
审计报告 ~/workspace/chv-security-audit/REPORT.md(No-Go,
Critical 0 / High 7 / Medium 8 / Low 5)代码层修复,mock
测试覆盖,真机动态验证一律未做、只列清单(见
docs/chv-production-readiness.md §11)。

High:
- H1 egress 代码门:代理地址校验、直连显式允许+审计、
  每 VM 独立 tap/mac/ip 校验、net 字段白名单;宿主防火墙
  列为部署清单,代码门+防火墙缺一不可。
- H2 网关 key 移出 argv:provisioned seed 盘客内文件
  /etc/chv/guest-env(0600)投递,传输层密钥进 argv 直接
  ValueError;keyscan 模式密钥投递 fail-closed。
- H3 任务盘删除:stop/recycle/cleanup 删 disk.raw(保留期
  0)+ 残留盘 GC,删失败审计+脏标记。
- H4 默认值改 provisioned:keyscan 只作显式 opt-in。
- H5 seed.iso 生成即 0600;宿主私钥 seed 盘建好后即删;
  崩溃残留扫描 cleanup_stale_secrets。
- H6 state_dir 默认出 /tmp(~/.local/share/.../chv-vms,
  preflight 保 0700);api-socket 组/其它可写即 fail-fast。
- H7 tap 隔离校验:net.json 登记+stop 释放,共享即拒绝审计。
Medium:M1 shlex 远程转义;M2 清理失败即拒起(脏标记);
M3 配额默认 50GB + 快照数/总量/保留期上限;M4 SHA256 必填
+ BUILDINFO versions 实测记录(事前 lockfile 列清单);
M5 host key/ preflight/失败审计补齐;M6 reuse 须声明单租户
项目;M7 容量计数只认连通 socket;M8 sshd 加固进 build.sh
断言。Low:L1 链接不跟随;L2 路径白名单;L3 失败审计;
L4 双变量警告+字段白名单;L5 预热盘/任务盘对应关系定义。
旧测试:实验室默认值与生产默认值区分标注;行为已变的旧
测试按新语义更新(容量死 socket、快照白名单、磁盘路径
白名单、网关 argv、reuse 租户声明)。
测试:新 test_chv_secfix 39 项;旧 117 项 + 2 项新增断言 =
158 项;worker 相关 129 项;前端 vitest 9 项;cloud 全量
1588 项(skipped 4)。
2026-10-31 03:02:10 +00:00
host 003ea357f9 feat(console): CHV production-grade frontend — worker availability, attachment guard, queue hint, build:console fix, vitest 2026-10-31 02:35:38 +00:00
host 036323e620 feat(console): CHV executor option in dispatch/env selectors + experimental note in detail/list
- DispatchSection: add cloud-hypervisor option (labelled experimental,
  default off); default stays docker; hint states a KVM-host CHV worker
  must claim the task (otherwise it stays queued; attachments are
  rejected by the backend at creation) and that the frontend only passes
  the executor value through, never bypassing the backend allowlist.
- EnvSection: same option, because the backend env-create allowlist
  (local/docker/cloud-hypervisor) does allow it; note states env
  allowlist excludes cloud.
- TaskListSection: add cloud-hypervisor to the executor filter so CHV
  tasks are findable; list rows already showed executor.
- TaskDetailPanel: pure display note for cloud-hypervisor tasks
  (experimental, default off, no VM controls); detail already showed
  executor from the task API.
- README: document the CHV console entry, experimental marking, and
  verbatim backend-error display.
- Rebuilt cloud/console.html from src (tsc + vite single-file build);
  no backend change: executor is already returned by _task_to_dict.
2026-10-31 02:25:21 +00:00
host 445376743d feat: CHV 审核修复 P2(范围恢复为全做)
P2-1:preflight reuse/CPU/内存校验块小提取为
_preflight_config_checks(纯移动,深耦合处如实停)。
P2-2:build_vm_command 降级为私有 _build_vm_command;
Docker goal 内联解析顶部加正典分叉说明 + 同步检查清单。
P2-3:gaps grep 文本断言改成真实 Worker._run_attempt
行为断言。P2-4:README/build.sh 口径对齐(章节顺序、
BUILDINFO 名字列表、Node.js 不装写实)。P2-5:stop_vm
删除失败记审计。P2-6:CPU/内存校验统一进 preflight。
测试:原 84 + 新增 33 = 117 项,mock,无真 VM。
2026-10-31 02:01:03 +00:00
host 9771ed9e8c feat: CHV 审核修复(P0+P1,堆叠于 #230)
P0:seed 磁盘 CIDATA ISO 挂进 VmConfig 第二块只读盘;
restore provisioned 下复用钉死 known_hosts、缺失 fail-fast
绝不 keyscan;preflight 拦截 CHV_DISK_MODE=reuse(显式
实验二次确认 + 审计放行);SSH 目标选客机 IP 方案(显式
host > net 静态 ip > 显式转发宣称回环,三者皆无 fail-fast)。
P1:网关 extra_env 经 SSH env 前缀注入、worker 守卫收窄到
Local/CHV;取消/清理/看门狗接 CHV 通用路径;WarmPool
acquire 内 resume;容量门持久化计数;磁盘配额总量+余量
与已存在磁盘校验重拷;API 按 Content-Length 验完整性;
快照 file:// 验落盘再 shutdown;execute 死端口归类
vm_not_running。
P2 全部排除未做(用户范围收窄令,留待以后一轨)。
测试:原 84 + 新增 29 = 113 项,mock,无真 VM。
2026-10-31 01:54:13 +00:00
host 22b83f118a feat: CHV gap fill — worker breakpoint wiring, pool reconcile, per-VM host keys, egress validation, guest build script
Stack: #216 -> #221 -> this. Worker breakpoint branch now calls any
executor providing goal_resume_breakpoint_task (Local keeps its
original path, gateway tasks still loud-fail); WarmPool
reconcile_orphans is called on CHV worker startup and close with
audit-on-failure; per-VM SSH host keys are provisioned before VMM
start via cloud-init NoCloud seed (keyscan kept only as weak
fallback, CHV_HOST_KEY_MODE=provisioned|keyscan); CHV_NET_JSON is
validated in preflight and explicit direct egress is audited;
chv-guest/build.sh is now real steps with --dry-run and fails fast
without /dev/kvm. Tests: new test_chv_gaps.py (18), cloud full
1514 OK (skipped=4). No real VM verification: no KVM host.
2026-10-31 01:30:52 +00:00
host fde7bf4c3e feat: CHV 执行器优化(拆模块/去重复/SSH 钉死/池持久化/中途死亡分类)
- 拆模块:CHV 从 cloud/sandbox.py 巨类拆到 cloud/chv/(executor/
  pool/config/api_client/ssh_transport/disk/errors),sandbox 只留
  兼容 re-export,旧导入路径与新路径是同一对象
- 去重复:goal 解析、host 顶层参数、runner 超时抽成共享 helper,
  Local 与 CHV 在矩阵输入下结论全等(Docker goal-resume 变体
  有意保留内联版,见 PR 描述)
- 安全:SSH host key 按 VM 钉死(state_dir/<vm>/known_hosts,
  ssh-keyscan 首次捕获 TOFU),废除 no + /dev/null;CHV_SSH_USER
  空串与 CHV_SSH_PORT 越界直接拒绝,不静默 fallback
- execute 语义:只读已运行 VM,未运行报 vm_not_running,不为
  status 起 VM
- 预热池:registry 落盘、重启载入只当孤儿、reconcile 清理、
  损坏 quarantine;条目仍一次性,绝不复用脏 VM
- 长任务:VM 中途死亡判 interrupted(断点可续跑);中途心跳
  经评估不做(无客内进度信号可采)
- #215 未合并:只列复用清单,不复制模块

测试:原 42 项 + 新 24 项全过;相关旧套件与 cloud 全量 1496 项
全过(skipped=4)。验证仍是 mock + 干跑 + 单元测试,未做真机、
压测、安全审计;堆叠于 #216 之上,base 为 feat-cloud-hypervisor。
2026-10-30 19:45:33 +00:00
host c371bbdf1e feat: Cloud Hypervisor 执行器生产级 v1(cloud-hypervisor)
单任务单 VM:API 唤醒(preflight→vmm.ping→vm.create→vm.boot→SSH 就绪)、
暂停/快照/休眠等价/关闭/回收、预热快照池、每任务磁盘 copy、容量与磁盘配额门、
受控 egress fail-closed、VM 事件审计与分类失败;worker/server/autoscaler 接线
(executor=cloud-hypervisor,任务内 LLM 不可自选);默认关闭,不进生产路由。
未做真实 VM 验证:生产主机与本 VM 均无 /dev/kvm、无嵌套虚拟化,验证为 mock +
干跑命令 + 单元测试;真 VM、快照恢复、压测、安全审计见
docs/chv-production-readiness.md。CI 账单故障仍是硬门禁,本提交只到 PR OPEN。
2026-10-30 18:54:16 +00:00
openqj aebc6238a6 Merge pull request #201 from openqj/feat-platform-auto-resume
feat: auto-resume platform-interrupted non-goal tasks
2026-10-30 15:51:43 +08:00
host 4428a721a6 feat: auto-resume platform-interrupted non-goal tasks
Deploy restarts kill running tasks: the worker marks them
interrupted (worker_shutdown on graceful exit, sweep on stale
reclaim), and auto_resume_interrupted used to accept goal tasks
only, so every non-goal task killed by a deploy stayed
interrupted forever unless a human retried it (the 2026-10-30
demo task died this way).

Now worker_shutdown/sweep interruptions are re-queued for
non-goal tasks too: the platform killed the task, so the
platform re-queues it. The re-queue is a fresh run, not a
breakpoint resume: goal_resume_pending is forced to 0 and any
stale resume_message is cleared, so the new run cannot be
hijacked by the goal-resume path or an old continue message;
files in the same worktree persist. All existing guards stay:
user pause, coordinator orphans, quota (still goal-only),
backoff, the 3-strike stall guard (non-goal tasks have no
checkpoints, so each platform re-queue counts as no-progress),
and the auto_resume_max cap. Other non-goal reasons (NULL,
quota, user cancel) still do not auto-resume.
2026-10-30 15:39:55 +08:00
openqj d6369154c2 Merge pull request #198 from openqj/feat-automation-edit
feat(console): automation panel edit and richer list
2026-10-30 15:38:59 +08:00
host 2d644f901b feat(console): automation panel edit and richer list
- AutomationPanel rows now show title, prompt content (160-char preview with expand/collapse), repo_url, last/next run, run_once badge and cancelled time.

- Active automations get an inline edit form (name/prompt/interval/repo) that PATCHes only changed fields via the existing automation_update endpoint; backend semantics unchanged (no last_run_at reset, cancelled stays terminal).

- Rebuilt cloud/console.html with tsc + vite (build green); automation python tests 18/18 OK.
2026-10-30 15:31:21 +08:00
openqj e2ce04ce4f Merge pull request #197 from openqj/feat-stream-waiting
feat: browser stream waits through round gaps instead of disconnecting
2026-10-30 15:29:17 +08:00
openqj 92157ef421 Merge pull request #195 from openqj/feat-shared-home-mcp-sync
feat: sync shared-home MCP scripts on control-plane startup
2026-10-30 15:19:26 +08:00
host 962bb50a1c feat: browser stream waits through round gaps instead of disconnecting
Stale bridge state used to end the stream on the first stale read,
so every round gap and every worker restart killed the watch (the
2026-10-30 demo watch died this way when a 15:03 deploy restarted
the worker). Now a stale state switches to a waiting heartbeat
every 5s while the task is still alive (running/queued/paused/
interrupted), and frames resume when the next round's MCP starts
polling. The stream ends only on a terminal task state, a missing
task row, or 10 minutes of continuous staleness. The panel shows
a waiting status and keeps the last frame. Also force-close the
SSE connection on stream end: HTTP/1.1 keep-alive otherwise left
terminal streams hanging with no EOF.
2026-10-30 15:14:37 +08:00
host b977a9dee8 feat: sync shared-home MCP scripts on control-plane startup
租户 config 的 mcp_servers.* 指向共享 home 路径,但宿主共享
home 里原先没有 MCP 脚本,宿主 app-server 启动 MCP 即文件
不存在、握手断掉,面板 mcp-servers-list 三服务器 toolsError
全是 handshake failed;14:43 的手工拷入能修一次,但下次
MCP 脚本改动后面板又会跑旧版。

本批只在服务端 main() 启动时同步一次:清单唯一定义在
cloud/shared_home_mcp.py(7 文件:playwright_mcp/browser/
remote、websearch_mcp/websearch、imagegen_mcp/imagegen,含
同目录兄弟模块),解析优先用 host-config-host.json 的
codex_home,兜底用 tenants_root 父目录/codex-home;缺失
拷入、不同刷新(原子替换)、相同不动;源缺失/目标不可写
只记 warning,绝不阻塞启动。create_server() 不做同步,
避免单测写向生产共享路径。

被否决的落点:worker _tenant_dirs(只在下个任务派发时跑,
面板要等下一个任务才更新,且不修共享路径本身);deploy
host_deploy.sh(会在 shell 里复制第二份清单,且重启不
部署时不刷新);面板请求时同步(把读请求变成写请求,
失败会把面板读数变成 502 风险)。
2026-10-30 07:08:51 +00:00
openqj 671b4bedef Merge pull request #194 from openqj/feat-project-tree
feat: project directory tree + first-round snapshot
2026-10-30 15:00:11 +08:00
openqj 4f035f0449 Merge pull request #186 from openqj/feat-console-llm-status
前端:任务详情 LLM 进度与失败原因标识
2026-10-30 14:46:24 +08:00