merge(chv-muse-round12): 合入 round-11 修复轮 HEAD 50cae2a (RETEST6 全绿)

按 hunk 合入,不整文件覆盖。冲突 3 处:
- chv-guest/build.sh:取 P4 的 copy-in(cdp-broker/guard/blocklist/
  chv-memory)+ cdp-broker service/enable 三件;取 R11 的 git
  config(去掉 credential.helper store,层 5);tmpfiles 两边
  并存(/run/chv/ask-queue + /run/chv-browser)。
- chv-guest/chv-browser.py:P4 架构保留(CDP broker 客户端 +
  guard 模块);取 R11 两处仍适用的运行期修复:BROWSER_SOCK
  移进 /run/chv-browser(缺陷 4,合入后的 broker 已指该路径)
  与 handle_request 认 op=browser/action 透传;R11 的直连
  Playwright 代码(validate_url/truncate_aria/_load_playwright/
  _page snapshot)由 P4 取代,不带回。
- cloud/tests/test_chv_muse_p2.py:truncate 三测取 P4 的
  GUARD.truncate_a11y(dict 树);FakeCdpBroker 取 P4 版,删
  R11 的 FakePage 系列;干跑夹具取 R11 的 _fixture 调用,并
  给 _fixture 补 P4 四件(否则合入后 build.sh fail-fast)。

合入内移植(R11 修复在 P4 里移了位置,不移植即静默丢失):
- chv-guest/chv-cdp-broker.py:_load_playwright 返回
  sync_playwright()(原 R11 在 chv-browser.py 修过的同形
  bug);回归测试由 test_p2 移植到 test_chv_muse_p4.py。
- test_chv_muse_p2.py:HOST 由模块级 eager 改懒加载(_host()):
  host.py 顶层 import queue 会把 stdlib queue 缓进 sys.modules,
  使同进程后加载的 test_queue_sandbox 取不到 cloud/queue.py
  遮蔽而 ImportError(全量唯一失败)。

未移植(见报告):R11 的 Page.aria_snapshot() 纠正未进
chv-cdp-broker.py(仍调 accessibility.snapshot(),且 guard
的 dict 树 API 须配套改,属 P4 返工,不在本轮合入做)。

全量 cloud 套件:2522 tests OK (skipped=10),基线 a6f9597
为 2474 OK (skipped=12);+48 来自 R11 新增测试(p2 +36、
p3 +7、closeup +3、gaps +1、p4 +1 移植)。
This commit is contained in:
host committed 2026-11-07 18:13:31 +00:00
commit 9da74c2a78
24 files changed
+1949 -217

No files matched your search

+36 -1
View File
@@ -26,7 +26,7 @@ CDP 由单元外 broker 管理。
| 单元内 codex 沙箱 | `host.py` 默认 `sandbox="workspace-write"`(bwrap) | 单元本身就是边界 | 单元内 codex 用 `danger-full-access`:bwrap 的 `--proc /proc` 挂载在嵌套命名空间里是已知走不通的形状(见 AGENTS.md 内层 proc 定位),单元内再套 bwrap 只会假隔离。执行器侧改 `--sandbox` 透传是 P1 的文件,本轨只定义接口(见 §4),不改 `executor.py` |
| 单元网络 | 无(与客机共享网络命名空间) | 单元自有虚拟网络接口 | `--network-veth`:宿主侧 `ve-chv-cell`(10.200.0.1/24)、单元侧 `host0`(10.200.0.2/24,默认路由经 10.200.0.1),客机侧 NAT 出站(MASQUERADE 走客机默认路由的上行口,动态发现,不写死口名)。单元内配网经 nsenter 进单元 init 的 netns 做(`chv-cell.py` 内实现)。veth 建不起来即大声失败,不静默回落共享网络;调试逃生口 `CHV_CELL_NETWORK=shared` 须显式设置、打日志,绝不默认 |
| 单元内外 IPC | 无(SSH 直达,host key 钉死) | Unix socket + SO_PEERCRED + 对端 ACL 的最小权限 IPC | `chv-broker`(客机侧、单元外):Unix socket `/run/chv/broker.sock`(bind 进单元)。SO_PEERCRED 校验:对端 uid 须在单元映射范围 1000–66535,且对端 pid 的 pid ns 须与 `/run/chv/cell.pid` 记的单元 init 同 ns(cell.pid 不存在时只做 uid 检查,并打日志)。操作白名单:`ping`、`sync pull|push|status`(仓目录只允许 `~/workspace` 下,`chv-sync` 以 `chv` 身份跑,不进 shell)、`browser …`(转发给浏览器子代理)。其余一律拒绝 |
| 浏览器 | Playwright headless-shell 由 agent 在客机内直接跑,能读原始 DOM、能跑 JS | 浏览器任务由专门浏览器子代理完成,只见无障碍树快照,不见原始 DOM,不能在页面上下文跑 JS,CDP 由单元外 broker 管理 | `chv-browser`(客机侧、单元外独立进程,用户 `chv-browser`):Unix socket `/run/chv/browser.sock`,操作只有 `open|snapshot|screenshot|close`。`open`/`snapshot` 返回标题、URL 与 Playwright `accessibility.snapshot()` 的无障碍树(截断到 200 节点/64KB);`screenshot` 落盘到 `/run/chv/shots/`(bind 进单元,单元只读路径取图)。**不暴露** `evaluate`、`content`(原始 DOM)、DevTools。单元内 playwright 包与浏览器二进制用 `--inaccessible` 遮掉,单元内跑不起自己的浏览器,只能走 broker |
| 浏览器 | Playwright headless-shell 由 agent 在客机内直接跑,能读原始 DOM、能跑 JS | 浏览器任务由专门浏览器子代理完成,只见无障碍树快照,不见原始 DOM,不能在页面上下文跑 JS,CDP 由单元外 broker 管理 | `chv-browser`(客机侧、单元外独立进程,用户 `chv-browser`):Unix socket `/run/chv-browser/browser.sock`,操作只有 `open|snapshot|screenshot|close`。`open`/`snapshot` 返回标题、URL 与 Playwright `Page.aria_snapshot()` 的 ARIA 快照字符串(截断到 200 节点行/64KB);`screenshot` 落盘到 `/run/chv/shots/`(bind 进单元,单元只读路径取图)。**不暴露** `evaluate`、`content`(原始 DOM)、DevTools。单元内 playwright 包与浏览器二进制用 `--inaccessible` 遮掉,单元内跑不起自己的浏览器,只能走 broker |
| 凭证 | `/etc/chv/guest-env`(0600 root)+ `~/.git-credentials`(0600 chv),codex 在客机内直接可读后者 | 凭证由 authd 保管,代理只见替身令牌 | 本轨只做“单元看不见”:`/etc/chv` 与 `~/.git-credentials` 都 `--inaccessible`,同步走 broker 的 `sync` 操作(broker 在单元外跑 `chv-sync`,token 不进单元)。替身化本身是 P3,不在本轨冒充已做 |
| 客机启动同步 | `chv-sync boot` 由 cloud-init runcmd 以 root 跑,clone 到 `~/workspace` | — | 不动:boot clone 仍在单元外做;单元内只经 broker 做 pull/push/status |
@@ -125,3 +125,38 @@ CDP 由单元外 broker 管理。
只做 uid 检查并打日志——该降级须在日志里可见)。
- 性能记录(只记录、不设门):单元启动到 codex 就绪耗时、veth+NAT 下
模型调用往返、qcow2 实际占用(systemd-container 与新脚本的增量)。
## 6. round-11 part 4 补记(2026-11-07,RETEST3 的 3 个 cell 新缺陷)
RETEST3 证实 `--volatile=yes` + 只遮 /usr 的形状本身生效(遮蔽经
mount/ls 实测有效),但引出三个阻塞缺陷,本轮按报告方向修:
1. **启动竞态**:`run_cell` 原顺序是 Popen → 配网 → 单次找 init pid,
快 payload 在配网完成前退出即撞墙(`run -- true` 11 次只成 6 次)。
修:payload 包一层 sh 起步门(等 `/run/chv/.cell-gate-<machine>`
出现再 exec),配网完成后才开门;`find_cell_init_pid` 改带超时
轮询;nspawn 已死时返回它的退出码而不是抛错;veth 等待放宽到
20 秒。验收:连续 15 次 `run -- true` 计数(cellrace4)。
2. **volatile 空根缺文件**:单元根是基本空的 tmpfs,`/app`、
`/etc/resolv.conf`、`/etc/hosts`、`/etc/ssl/certs` 全无,执行器
单元路径一律 ENOENT、DNS/TLS 不可用。修:`--bind-ro=/app` 与
`--bind-ro=/etc/ssl/certs` 固定绑;resolv.conf/hosts 由 run_cell
按客机有效 DNS 生成单元专属文件(`/run/chv/resolv.conf.<machine>`、
`hosts.<machine>`,0644)再只读绑——客机 resolv.conf 可能是
systemd-resolved stub(127.0.0.53),单元内无 resolved,直接绑
即 DNS 全灭。不绑 nsswitch.conf(客机版可能含 resolve 模块,
glibc 默认 files+dns 正是单元需要的)。P2-8 的 example 腿换
v4 目标(gitea.tose.top)。
3. **并发 cell 撞毁**:固定 `ve-chv-cell` 名 + 固定 10.200.0.1/24,
第二个 cell 在 nspawn 建 veth 时即 `File exists`。修:每次
run_cell 在锁下分配唯一身份(CellNet:machine `chv-cell-<xx>`、
`ve-chv-cell-<xx>`、10.200.`<xx>`.0/24;machine 名 11 字符,
ve- 前缀后 14 字符,符合接口名 15 字符上限;扫描活接口 + 地址
+ machinectl 在册名避让注销滞后)。broker 的 pid ns 对照改成
多 cell 版(`cell-<machine>.pid` glob + 兼容旧 `cell.pid`,对端
ns 与任一活单元同 ns 即放行;旧单文件仍由 run_cell 写最新单元,
P2-9 驱动仍读它)。
另:执行器 `_boot_vm` 的 vm.create/vm.boot 失败消息现在带截断后
的响应体(`_truncate_api_body`,500 字符;只留 status 即无法定位
间歇 500 的根因,两轮已提)。
+10 -4
View File
@@ -106,10 +106,16 @@ authd 保管、代理只见替身令牌;所有连接器动作与网络出口
凭证前评估),不是网络层强制。客内任意进程的直连仍由 Tinyproxy
白名单 + 宿主防火墙(第三棒)兜底;eBPF cgroup 拦截 + LSM 污点传播
是后续工作,不得写成“出口已全部经 Sentinel”。
5. **过渡兼容留了旧路径**:`chv-sync boot` 在 authd socket 不存在时仍走
旧的 `.git-credentials` 落盘(只多一句 stderr 警告),为的是 lite9/
lite10 旧镜像不被一次改断。旧镜像退役后应删掉该回落,改为无 socket
即 fail-closed;删除前不得声称“旧路径已关闭”。
5. **过渡兼容已关闭(round-11 真机修复,层 5)**:此前 `chv-sync
boot` 在 authd socket 不存在时仍走旧的 `.git-credentials` 落盘
(只多一句 stderr 警告),为的是 lite9/lite10 旧镜像不被一次
改断。round-11 真机测试证实该回落把 authd 释放的长效 token 写
进了客内磁盘,修复轮已改为无 socket 即 fail-closed(`die`),
同时镜像 `/etc/gitconfig` 的 `credential.helper store` 也已删
除——authd helper 是唯一凭证 helper,且自身绝不落盘。旧镜像
(lite10 及更早)上 boot 现在会直接失败,须用含 authd 的新
镜像;真机复测须验证跑完一次完整 Gitea 链后客内
`~/.git-credentials` 不存在。
6. **无真机验证**:本轮全部是代码 + 本地单测(含真 git helper 端到端,
但 authd 守护进程本身未在真 VM 里跑过)。真机待验证清单:authd 在
lite11 镜像里启动时序(须在 `chv-sync boot` 之前就绪)、SO_PEERCRED