Commit Graph
59 Commits
Author SHA1 Message Date
host 7784be0657 fix(chv-muse-r12fix3-3): 回落审计 repo 记解析后的仓目录(P6 小项 A)
R12 复测 2 轻微缺口:回落审计行的 repo 字段为空串——fallback_warn
记的是调用方传的原始参数,而驱动与日常调用都是无参调用
(cd 仓 && chv-sync status)。单仓无损,多仓时审计分不清是哪
一仓回落。

修法:cmd_pull/cmd_push/cmd_status 三处都先调 resolve_repo 解析
仓目录,再以解析后的目录调 fallback_warn。解析失败即 die、不
留回落审计——同步本身已失败,没有“回落”可记(此前是先记审计
再解析,解析失败也留一行空 repo 审计)。

测试:ChvSyncDelegationTest——无参回落的 repo 即唯一仓目录
(不再是空串),显式给仓目录时 repo 即该目录。P6 模块 65 项全过。
2026-11-08 09:48:41 +00:00
host 7cba3c2b21 fix(chv-muse-r12fix3-2): worker socket 组权限 + allowed_uids 含 chv(P6 缺口 ②)
R12 复测 2 钉死:即使 worker active,chv 客户端也永远连不上
socket——worker root + 目录 /run/chv-gitea 0700 root + socket
0600 root + allowed_uids=(0,) 三处锁死;chv ping 一律 EACCES
(rc=2),root ping rc=0,privsep_worker_call 恒为 0,全部同步
走回落。

修法:三处配套改,只改一处仍锁死——
1. chv-gitea-worker.py:DEFAULT_ALLOWED_UIDS=(0, 1000)(root +
   chv;round-11 authd 同款;环境变量
   CHV_GITEA_WORKER_ALLOWED_UIDS 可覆盖);socket 用 group 权限
   (目录 0750、socket 0660、组为第一个非 root 放行 uid 的主组,
   _socket_group_gid/_set_group_perms 与 authd 同款实现;gid
   解析不出时回落 root-only 0700/0600,绝不放给全员)。
   拒绝 0666 探针口径。
2. build.sh tmpfiles.d:d /run/chv-gitea 改为 0750 root 1000,与
   新方案一致。gid 写数字 1000 而不用组名 chv——tmpfiles-setup
   跑在 07:23:24,chv 用户由 cloud-init 在 07:23:40 才建,组名
   当时解析不出;1000 是 chv 的 uid/gid(seed users 块未指定
   uid/gid,cloud-init 取首个空闲 1000;authd 同款假设)。
   worker 启动时再按 passwd 重新推导 gid 并重设权限,tmpfiles
   行只是开机存在性保证。审计日志仍 0600 root,不动。
3. 服务单元 ReadWritePaths 不动(/run/chv-gitea 仍在列,存在性
   由 tmpfiles 保证)。

注意:单元内 agent 同样映射为 chv uid 1000,故也能走到 socket,
但只能走类型化协议——操作白名单、仓库 confinement、安全扫描三
处仍在,worker 不持凭证(见 worker 模块注释)。

测试:WorkerSocketGroupPermsTest 新增 7 项——默认白名单含
(0,1000)、环境变量覆盖、gid 可推导时 0750/0660 + 组正确、显式
socket_group 时同样组权限、只有 root 时回落 0700/0600、gid 推导
函数四档、tmpfiles 行 0750 root 1000;WorkerUnitTmpfilesTest 的
/run/chv-gitea 期望改 0750。P6/round12/P2/P4 四模块 205 项全过。
2026-11-08 09:48:16 +00:00
host e31354dc58 fix(chv-muse-r12fix3-1): worker 开机时序竞态:Condition + path 触发,不赌重启预算(P6 缺口 ①)
R12 复测 2 钉死:剩下的 226 只点名 /home/chv/workspace——工作区由
cloud-final 的 chv-sync boot 克隆出来,晚于 multi-user.target 约
9 秒,worker 的 5 次启动预算在工作区出现前耗尽(5 台 VM 2 台
failed,手工 reset-failed + start 后立即 active,二进制无辜)。

修法三选一,选 (a)——让服务根本不在工作区出现前启动:
新单元 chv-gitea-worker.path(PathExists=/home/chv/workspace,
工作区一出现即触发启动)+ 服务加
ConditionPathExists=/home/chv/workspace(开机那次启动在命名空间
搭建之前干净跳过,不 226、不耗重启预算)。两者缺一不可:
Condition 本身绝不重试,没有 .path 它就是一次跳过、服务永远不
起;只有 .path 而无 Condition,开机那次启动仍会在命名空间搭建
时 226 撞上限速。WantedBy=multi-user.target 保留(is-enabled 仍
为 enabled),runcmd 只启动 .path 单元(cloud-final 时工作区尚
未 clone,直接 start 服务会 226);.path 进 buildinfo contents
与两处 lockfile 登记,P6 预检由三件扩为四件。

不用 (b) 放宽 StartLimit:放宽只是把竞态窗口拉大,wake 时间在
负载下已翻倍(R12 复测 2),预算仍可能耗尽,仍是时序赌博。
不用 (c) 把工作区创建提前到 tmpfiles-setup 之前:/home/chv 本身
07:23:40 才出现(cloud-init 建 chv 用户),晚于 tmpfiles-setup
(07:23:24);硬约束:绝不盲目补 tmpfiles 行。ExecStartPre 也不
行——命名空间搭建在它之前,救不了 226。

测试:WorkerUnitBootRaceTest 新增 8 项——Condition 在位、
StartLimit 未放宽(修法语义钉)、.path 触发配对(PathExists +
WantedBy、无 Unit= 覆盖)、服务 WantedBy 保留、build.sh 启用
.path + runcmd 只起 .path、绝无工作区 tmpfiles 行、buildinfo 与
lockfile 登记;BuildInfoContentsTest 的 P6 件加 .path;P2/P4 干
跑夹具一并拷入 .path。P6/P2/P4 三模块 174 项全过。
2026-11-08 09:45:48 +00:00
host 7c517f7fcc fix(chv-muse-r12fix2-2): chv-sync 回落显性化:警告 + 每回落一行审计(P6-2)
R12 复测:worker 226 期间 chv-sync 全程静默走直接路径回落,pull/
push/status 表面 rc=0,privsep/safety 一行没跑——回落只有一句 stderr
警告,无事后可数的痕。本提交让一次回落留两处痕:stderr 警告保留
“worker 不在”标记(措辞加“未走 worker,privsep 未生效”,供驱动
识别)+ 追加一行 JSON 审计到
$USER_HOME/.chv-sync-fallback-audit.log(CHV_SYNC_FALLBACK_LOG 可改
位置;事件/动作/仓/时间戳,delegated_to_worker=false、
privsep_active=false)。审计写失败只记 stderr,绝不中断同步(与
worker _audit 同款口径)。gap 文档诚实边界第 4 条同步更新。

取舍:不改 fail-closed,仍是 fail-open。理由:旧镜像(无 worker)
须仍能同步,改 fail-closed 会把所有旧镜像的同步当场 brick,且无
镜像外恢复手段;本轮项 1 已让新镜像的 worker 开机即起,回落只在
worker 真挂 / 旧镜像时发生,不再是常态路径。旧镜像退役后改为无
worker 即 fail-closed(原定方向不变,见 chv-sync.sh 头注)。

测试:ChvSyncDelegationTest 新增——回落审计一行 JSON(字段全)、
两次回落两行、审计落盘失败不中断同步;走 worker 时不留回落审计
(反向对照)。
2026-11-08 06:01:54 +00:00
host 35d9c3ff3f fix(chv-muse-r12fix2-1): tmpfiles.d 预建 worker 运行时路径(P6-1)
R12 复测钉死的根因:chv-gitea-worker.service 用 ProtectSystem=strict
+ ReadWritePaths=/home/chv/workspace /run/chv-gitea
/var/log/chv-gitea-worker.log,systemd 建 mount namespace 时要求每个
列出路径事先存在,否则 226/NAMESPACE crash-loop(NRestarts=12);
交付镜像里 /run/chv-gitea(/run 是 tmpfs)与
/var/log/chv-gitea-worker.log(worker 首次写审计才建)两处都不存在,
worker 从未起来过。

取舍:只用 tmpfiles.d(/etc/tmpfiles.d/chv.conf,加 d /run/chv-gitea
0700 与 f /var/log/chv-gitea-worker.log 0600,与 P4 的 /run/chv 同款
写法、同一文件)。ExecStartPre 不行——命名空间搭建在任何
ExecStartPre 之前,226 发生在它运行之前;改 ReadWritePaths 为已存在
目录(/var/log)会把写权限放大到整个目录。tmpfiles.d 由
systemd-tmpfiles-setup 在 sysinit 跑、早于 multi-user 的服务启动;
不新增进镜像的文件(chv.conf 本就是 build.sh 内联生成),lockfile
无需登记。unit 文件只加注释说明耦合,不改 ReadWritePaths 本身。

测试:WorkerUnitTmpfilesTest 钉住 ReadWritePaths 每一项挥发路径都有
对应的 tmpfiles 行(类型 + 模式)、P4 四行仍在、unit 无 ExecStartPre。
2026-11-08 06:00:58 +00:00
host 0ebcdfb2a7 fix(chv-muse-r12fix-4): detect_checkout 接进 CDP broker(P10)
R12 真机:detect_checkout 在 chv-guest/、host.py、
executor.py 里零引用,结账页无任何拦截。按 P10 gap §4.1
接线:broker 加载 cloud/chv/checkout_detect.py(同目录
优先,仓库路径兜底;缺失时降级关闭检测、写警告,绝不
crash-loop);snapshot() 每次重算 verdict(aria 行拼伪节点,
与 _form_risk 同形),open() 导航前后两道门——落点是结账
页时返回 form_submit ask 并暂停,上一页是结账页时下一次
导航先审批;ask 批过后同 URL 不再重复 ask(批准即放行,
与 form_risk 同口径);verdict 进 snapshot()/status() 回包。
build.sh:checkout_detect.py 条件拷入 /usr/local/bin/(0644,
P3/P6 同款只警告口径,M4 不硬要求 cloud/),
extra_contents 与两处 lockfile 登记补齐。钱包三腿不在本轮
范围,未动。
2026-11-08 04:31:36 +00:00
host 33ca6c75ed fix(chv-muse-r12fix-2): P6 worker 接进镜像(build.sh + BUILDINFO + lockfile)
R12 真机:chv-gitea-worker / chv-sync-worker-client /
privsep.py / safety.py / chv-gitea-worker.service 5 文件全不在
镜像内,build.sh 从未有 P6 拷入行,全链路无法跑。按 gap 文档
§2 接入说明落地:worker 三件硬校验 + 拷入 /usr/local/bin/
与 /etc/systemd/system/(mv 去后缀 + chmod +x + systemctl
enable);privsep/safety 叶子模块条件拷入
/usr/local/lib/chv-gitea/(与 P3 同款口径,缺失只警告);
叶子树 __init__ 用 stub(绝不拷真实 cloud/chv/__init__.py,
round-11 层 1 教训),stub 需 errors.py 故一并拷入;runcmd
在 chv-sync boot 前加 systemctl start chv-gitea-worker。
BUILDINFO contents 列入 worker 三件(无条件)与 privsep/
safety(条件,extra_contents);两处 lockfile 登记补齐 P6
五项,另补 P4 三件自 P4 起缺失的登记。
2026-11-08 04:25:33 +00:00
host b923d0291b fix(chv-muse-r12fix-1): /run/chv 改为组 chv-browser 可写(P4 broker 权限)
R12 真机:/run/chv root 所有 0755,chv-browser 建不了
cdp-broker.sock,chv-cdp-broker bind 报 PermissionError 而
crash-loop,P4 在交付镜像上永远起不来。改为 2775 root
chv-browser(setgid,沿用真机手工修复 chgrp + chmod 2775
后全过的口径);绝不用 0666/0777 放宽,与 round-11 authd
socket 组权限 0660 同款口径。建目录时的 chmod 与 tmpfiles
登记两处同改。
2026-11-08 04:23:41 +00:00
host dafb0c6606 fix(chv-muse-r12fix-0): build.sh 建 /usr/local/share/chv 目录(认领 c3bf9fd)
纯 114b444 建不出镜像:P4 步骤把 browser-blocklist.txt 拷进
/usr/local/share/chv/,脚本里没有任何 --mkdir,virt-customize
报 target is not a directory。R12 真机靠本地-only 提交 c3bf9fd
(1 行,未推未合)绕过;本提交把该行合回分支,此后测试镜像
须从已推分支纯建出来。
2026-11-08 04:23:41 +00:00
host 114b444554 fix(chv-muse-round12): 移植 round-11 的 Page.aria_snapshot() 纠正到 CDP broker
Playwright 1.64.1 没有 page.accessibility(Puppeteer 的 API),
broker 的 snapshot 调它必走异常分支。改调 Page.aria_snapshot()
(字符串);guard 的 truncate_a11y 换成字符串版 truncate_aria
(179a86d 原样),detect_injection/detect_form_risk 加字符串
分支(dict 分支保留);两边 fake 与 P4 文档同步改。a11y-only
契约不变:不读原始 DOM、不走 JS eval。

测试:test_chv_muse_p2 + test_chv_muse_p4 114 OK;全量 2523 OK
(skipped=10)。
2026-11-07 18:24:58 +00:00
host 9da74c2a78 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 移植)。
2026-11-07 18:13:31 +00:00
host 50cae2a0af part 6 修复:worktree 根移出 /app——RETEST5 挂载点回归
RETEST5:part 5 的 --bind=<workspace>/.chv-worktrees:/app/work
(adc7bf6,排在 --bind-ro=/app 之后)让所有 cell 启动失败:
后挂的 bind 只有在挂载点已存在时才能盖住,挂载点不存在时
nspawn 在只读 /app 下连建都建不起来(Failed to create mount
point .../app/work: Read-only file system,全量 73 次,
CELL-RC=0 共 0 次),缺陷 1 的 15/15 回退为 0/15。

三选一取 (a) worktree 根移出 /app:
- chv-cell 不再发任何指向 /app 之下的 bind,删
  --bind=...:/app/work 与 worktrees_dir 参数;WORKTREE_DIR
  (/home/chv/workspace/.chv-worktrees)改经
  --setenv=CHV_WORKTREE_ROOT 传进单元——该路径在已可写绑进
  单元的 workspace 之下,挂载点问题按构造即不存在;backing
  仍在客机 workspace 下随磁盘持久化,隔离与落盘语义不变,
  只是单元内路径从 /app/work/host-worktrees 改为
  /home/chv/workspace/.chv-worktrees/host-worktrees。
- host.py 新增 worktree_base():CHV_WORKTREE_ROOT 优先;
  CHV_CELL=1 但变量未设时回落到同一单元内路径(旧版 chv-cell
  / 单元内手动跑 host.py 仍不出 /app);单元外沿用
  HERE/work/host-worktrees,行为不变。make_worktree 与
  dispatch_from_branch(第二个 HERE/work 调用方)同走该函数,
  goal/fork/依赖链一并修到。
- 不选 (b) 默认 --no-worktree:隔离丢了,且 goal/fork 同样
  调 make_worktree,照样撞 Errno 30;不选 (c) 镜像预建空
  目录 /app/work:修法耦合镜像构建,某次重建漏建即静默回归,
  且单测无法钉住镜像内容。
- 根因 1(39084ce)与根因 3(0203f32)代码未动。

单测:删 part 5 钉 argv 顺序的 4 项(该顺序本身已被真机证伪),
新增 CellPart6WorktreeTest 4 项 + HostWorktreeBaseTest 5 项:
argv 里无任何指向 /app 之下的 bind、CHV_WORKTREE_ROOT 经
setenv 进单元、worktree 根只经可写 workspace 绑定可达、
两处同值常量一致、worktree_base 三路选择、make_worktree
真 git worktree 落点、dispatch_from_branch 同根。

全量:cloud/ 下 discover -s tests,2067 项 OK(skipped=9)。
(注:仓库根跑 discover 会把 queue 解析成标准库、致
test_queue_sandbox 导入失败,须在 cloud/ 下跑。)
2026-11-07 14:23:48 +00:00
host 0203f32627 part 5 修复 3:P2-9 degraded_logged=False——降级日志文案漂移
定位结论(已定位,不是时序、不是日志路径变化):part 4 提交
7a22732 把 serve() 从 peer_allowed 切到 peer_allowed_multi 时,
降级分支的 reason 与警告行都被改写成了“无 cell pid 文件”,
“cell.pid 缺失”这个串自此再也没进过 journal——P2-9 驱动
grep 'cell.pid 缺失' 自然命中不了,RETEST4 的
degraded_logged=False 即由此来。RETEST3 同项为 True,是因为
当时跑的还是 part 4 之前的 broker(serve 直接打印含该串的
警告行)。降级 ping 本身一直 ok=True,降级逻辑没坏,坏的是
日志里的认领字符串。

修复:peer_allowed_multi 无 pid 文件时的 reason 改回与单 cell
版 peer_allowed 完全一致的“uid 在范围内(cell.pid 缺失,未做
pid ns 对照)”;serve() 的警告行改走新 helper
_degraded_warning(reason, pid, uid),把 reason 原样打进日志
(不再手写第二份文案,避免两者再次悄悄漂移;今后改文案只改
reason 一处)。journalctl grep 'cell.pid 缺失' 恢复命中。

新测试 cloud/tests/test_chv_muse_p2.py::BrokerMultiTest(2 项):
reason 含“cell.pid 缺失”、警告行含该串且带 pid/uid。
2026-11-07 12:07:32 +00:00
host adc7bf6666 part 5 修复 2:默认 dispatch 在只读 /app 下建不了 worktree——workspace 绑定 /app/work
RETEST4 新发现 2:make_worktree 把 worktree 建在 HERE/work/
host-worktrees,单元内 HERE=/app 而 /app 是 --bind-ro,带 repo
的默认 dispatch 撞 OSError Errno 30(--no-worktree 的同任务
越过此关、跑到真实 turn)。

二选一取 (a) 给 worktree 一条单元内可写路径:客机 workspace
下的 .chv-worktrees 以可写 --bind 挂到单元内 /app/work 上
(build_nspawn_argv 新增 worktrees_dir 参数,默认
WORKTREE_DIR=/home/chv/workspace/.chv-worktrees;run_cell 起
单元前 _ensure_bind_source 建好该目录并 chown 回 chv)。
取舍:
- 不选 (b) 执行器默认 dispatch 带 --no-worktree:worktree
  隔离就丢了——任务改动直接落进 repo 本身,多任务同仓互撞,
  merge/discard/seed-dir 语义全坏;且只修 dispatch,goal/fork
  同样调 make_worktree,照样撞 Errno 30。
- (a) 内不选可写 tmpfs:tmpfs 随单元退出即丢 worktree,跨
  cell 的 run/merge 续跑找不着工作区;workspace 绑定随客机
  磁盘持久化,worktree 语义、隔离与落盘位置(仍是
  /app/work/host-worktrees/<任务>)全都不变,也不碰 P2 的
  /usr 只读约束。
- bind 必须排在 --bind-ro=/app 之后(nspawn 按顺序挂载,后挂
  的才盖住只读子树下的子路径),单测钉住该顺序。

未决(须 RETEST5 真机验证):nspawn 对 --bind 目的路径的
处理(目的不存在时是否自动建挂载点)与挂载叠加顺序只能在
真机上验证,单测只能钉住 argv 形状;本轮不建镜像、不跑真机。

新测试 cloud/tests/test_chv_muse_p2.py::CellPart5WorktreeTest
(4 项):可写 bind 在位且在 workspace 下、顺序在
--bind-ro=/app 之后、自定义目录参数生效。
2026-11-07 12:07:01 +00:00
host 7a227323f2 part 4 缺陷 3:并发 cell 撞毁——唯一身份 + run_cell 接线
RETEST3 缺陷 3:固定 ve-chv-cell 名 + 固定 10.200.0.1/24,
第二个 cell 在 nspawn 建 veth 时即 File exists/Address
already assigned。
- CellNet + allocate_cell_net():每次 run_cell 在 flock 锁下
  (/run/chv/.cell-alloc.lock,30 秒超时)分配唯一身份:
  machine chv-cell-<xx>(11 字符)、宿主 ve-chv-cell-<xx>
  (14 字符,接口名 15 字符上限内)、10.200.<xx>.0/24;
  扫描活接口 + 地址 + machinectl 在册名,避让注销滞后的下标。
- 配网命令表(host/cell/cleanup)与 build_nspawn_argv 接受
  net/cell_net 参数;None 时仍用旧常量(argv 子命令与旧测试)。
- run_cell 重写:分配身份 -> 生成专属文件 -> 起步门 payload
  -> 等 veth(20 秒)-> 宿主配网 -> 轮询 init pid(10 秒)
  -> 写 pid 文件 -> 单元内配网 -> 开门 -> 等退出码;
  nspawn 已死返回其退出码;finally 清 NAT/pid/专属文件。
  共享网络模式跳过门与身份(无配网可等)。
- broker:pid ns 对照改多 cell 版(peer_allowed_multi +
  _read_cell_pids:cell-<machine>.pid glob + 兼容旧 cell.pid;
  对端 ns 与任一活单元同 ns 即放行;残留 pid 文件只跳过,
  全部读不到才拒绝,不降级)。
新测试:CellAllocTest、CellPart4NetworkTest、
CellPart4ArgvTest(cell_net 两法)、BrokerMultiTest。
2026-11-07 10:30:30 +00:00
host 2be69af92c part 4 缺陷 2:volatile 空根缺 /app 与 /etc 关键文件
RETEST3 缺陷 2:--volatile=yes 下单元根是基本空的 tmpfs,
/app、/etc/resolv.conf、/etc/hosts、/etc/ssl/certs 全无,
执行器单元路径一律 ENOENT、DNS/TLS 不可用。
- build_nspawn_argv:固定只读绑 --bind-ro=/app 与
  --bind-ro=/etc/ssl/certs;新增 extra_ro_binds 参数
  (单元专属 resolv.conf/hosts 由 run_cell 传进来)。
- prepare_cell_files():按客机有效 DNS 生成单元专属
  resolv.conf/hosts(/run/chv/resolv.conf.<machine>、
  hosts.<machine>,0644——单元 root 映射为客机 uid 1000,
  0600 的 root 文件单元内读不到;DNS/hosts 不含秘密)。
  客机 resolv.conf 可能是 systemd-resolved stub
  (127.0.0.53),单元内无 resolved,直接绑 stub 即 DNS
  全灭,故过滤 stub 后备选 /run/systemd/resolve/resolv.conf,
  两处都没有即大声失败。nsswitch.conf 故意不绑(客机版
  可能含 resolve 模块;glibc 默认 files+dns 正是单元需要的)。
新测试:CellFilesTest、CellPart4ArgvTest(绑定两法)。
run_cell 接线在缺陷 3 的重写里做。
2026-11-07 10:29:31 +00:00
host fc33eed262 part 4 缺陷 1:cell 启动竞态——起步门 + init pid 轮询
RETEST3 缺陷 1:run_cell 里配网与 payload 零同步,快 payload
(run -- true)在配网完成前退出即撞墙,11 次只成 6 次。
本提交先落 helper(接线在缺陷 3 的 run_cell 重写里做):
- gate_wrapper_command():payload 包一层 sh 起步门,等
  /run/chv/.cell-gate-<machine> 出现再 exec(绝对路径
  /usr/bin/sh:volatile 单元根里 /bin 未必有)。
- find_cell_init_pid() 改带超时轮询(timeout 默认 0.0 = 单次,
  旧调用行为不变;缺目录不再直接回 None;proc_alive 回调让
  nspawn 已死时立刻回,不空等)。
新测试:CellGateTest、CellInitPidPollTest(5 个)。
2026-11-07 10:29:13 +00:00
host 179a86d655 fix(chv-muse-r11fix): browser 快照改 Page.aria_snapshot()(复测新发现 2)
Playwright 1.64.1 的 Page 根本没有 accessibility 属性(那是
Puppeteer 的 API),open/snapshot 在真实库上必报
'Page' object has no attribute 'accessibility'。原生快照是
Page.aria_snapshot()(返回 ARIA 快照字符串),a11y 字段仍叫
a11y,值改为字符串;截断改字符串版 truncate_aria(200 节点
行/64KB)。FakePage 同步改真形状(旧 fake 自带 .accessibility
与真实库分叉,是与 sync_playwright bug 同款模式)。
wheel 1.64.1 实测:sync_api/_generated.py 无 def accessibility,
Page 与 Locator 均有 aria_snapshot() -> str。
2026-11-07 07:02:37 +00:00
host 581e850eb7 fix(chv-muse-r11fix): cell 只遮 /usr 下路径(复测新发现 1)
--volatile=yes 下容器根是基本空的 tmpfs(只挂进只读 /usr),
nspawn 按容器根解析 --inaccessible 路径,/etc/chv 与
/home/chv/.git-credentials 在容器根里根本不存在,解析失败即
Failed to resolve 而起不来;两条 /usr 旗可解析,保留。
凭证文件按构造即不可达(容器根里无 /home、无 /etc/chv),
遮蔽不再是必须项,安全属性不变。
探针证据:celldiag2/3/4——去 inaccessible 后完整 argv 0.4s/rc=0,
单元内 $HOME/.cache 可写,单元内 ls /etc/chv 本来就不存在。
2026-11-07 07:02:37 +00:00
host 04287f6053 fix(chv-muse-r11fix): chv-browser 补 sync_playwright 调用括号 + 接受 broker 的 browser op(复测新发现)
复测(RETEST.md §browser-defect、§broker-proto):
1. _load_playwright 返回裸 sync_playwright 函数(未调用),
_ensure_browser 对它调 __enter__ 即 AttributeError,open
永远失败。改返回 sync_playwright()(调用后的上下文管理器),
与单测假工厂的形状一致。回归测试用 stub 的 playwright 模块
钉住“返回调用结果、不是函数本身”。
2. broker 按设计原样转发 {op: browser, …},chv-browser 白名单
却不认 browser。在 chv-browser 的 handle_request 里归一化:
内层 op 在 action 里;无 action 但带 url 时默认 open(驱动
只发 url);action 同样受 FORBIDDEN_OPS 约束。broker 保持
原样转发不动(其 docstring 与单测均以此为契约)。
附:本提交含 test_chv_muse_p2.py 里 CellArgvTest 的配套更新
(c1d00b3 的 --volatile=yes 断言,同文件无法拆分提交)。
2026-11-07 05:24:42 +00:00
host c1d00b3620 fix(chv-muse-r11fix): cell 改 --volatile=yes(复测纠正缺陷 2)
复测(RETEST.md §cell):--ephemeral 在客内 ext4 根盘(2.4G、
已用 1.8G、可用 593M)上做整盘快照拷贝,空间不够即 ENOSPC,
全部 cell 调用失败(每次约 10.5s veth 超时后 rc=1)。改
--volatile=yes:根为基本空的 tmpfs、/usr 只读挂进,单元内除
bind 出去的目录外一律可写、用完即弃——与 --ephemeral 的设计
意图一致,但不拷盘、不占空间(systemd-nspawn 手册口径)。
--volatile=state 不行:整树只读、仅 /var 为 tmpfs,codex 写
$HOME/.cache 等非 bind 路径会失败。探针已证实 yes/state 均可
启动(0.4s,rc=0),取 yes 保可写性;真机 codex 入 cell 由
第二轮复测验证。测试:CellArgvTest 断言新值、--ephemeral
不再出现。
2026-11-07 05:24:30 +00:00
host 4a7ca25334 fix(chv-muse-r11fix): browser.sock 移进 chv-browser 自有目录(缺陷 4)+ vsock 自动启动
缺陷 4:/run/chv 是 0755 root root,chv-browser 在 bind 时
EACCES 而无限 crash loop(restart 21)。browser.sock 从
/run/chv/browser.sock 移到 /run/chv-browser/browser.sock
(broker 与 browser 两端常量同改);build.sh 建目录 + tmpfiles
登记(0755 chv-browser 所有)。不用组可写 /run/chv——那会给
chv-browser 对 broker.sock/cell.pid 的 unlink 能力。
vsock 自动启动:此前 build.sh 只拷 vsock_notify.py、不建服务,
vsock 通道从未自动启动。按 vsock_notify.py 的 docstring 补
vsock-notify.service(ExecStart=/usr/local/bin/vsock-notify %H,
Restart=no,After/WantedBy=multi-user.target)并 enable;
printf 格式串里 %H 写 %%H。测试:干跑针 /run/chv-browser;
vsock 在场时干跑含服务与 enable 语句。
2026-11-07 04:01:24 +00:00
host f093558c0d fix(chv-muse-r11fix): authd 层 5 去掉 store helper,chv-sync 回落改 fail-closed
round-11 真机测试证实:镜像 /etc/gitconfig 的 [credential]
helper = store 把 authd 释放的长效 token 写进了客内
/home/chv/.git-credentials(0600)。改动:build.sh 不再设
store helper;chv-sync boot 在 authd socket/helper 缺失时直接
die,不再把 token 写进 .git-credentials(旧落盘回落废止);
authd helper 是唯一凭证 helper,且自身绝不落盘。旧镜像上 boot
现在会直接失败,须用含 authd 的新镜像。测试:无 authd 时 boot
失败且 .git-credentials 不产生、干跑不再含 store helper;P3
差距表 §3.5 更新为已关闭。
2026-11-07 04:00:04 +00:00
host 9f593d62ef fix(chv-muse-r11fix): authd 层 3 socket 用 group 权限、层 4 裸 .git 当 pull
层 3:AuthdServer 不再 0700/0600 一刀切。socket 共享组取显式
socket_group 或第一个非 root 放行 uid 的主组(build_server 透
传;生产默认 allowed_uids=(0,1000) 派生 chv 主组),目录 0750
+ socket 0660 + chgrp;解析不出(uid 无 passwd 条目)回落
root-only,绝不放 0666(探针 fixprobe8 用的 0666 不上生产)。
层 4:_operation_for_path 加裸 .git → GET/pull;Sentinel
gitea 规则加 (GET, suffix, .git),POST 裸 .git 仍拒。语义已定:
裸 .git 一律当 pull(只读默认),push 只走显式 git-receive-pack。
测试:Sentinel 允许 GET/拒绝 POST 裸 .git、helper 端到端裸路径
取凭证、组 gid 派生与回落、显式组落地 0750/0660。
2026-11-07 03:58:45 +00:00
host 2d12179b9a fix(chv-muse-r11fix): authd 层 1 叶子树补 errors.py + stub __init__、层 2 seed 补三键
层 1:真实的 cloud/chv/__init__.py import executor/orphans,拷进
authd 最小叶子树(/usr/local/lib/chv-authd)会让 chv-authd 在
import 时 ModuleNotFoundError 而 crash loop——先是 errors.py
缺失,补上后又在 executor 上失败(探针 fixprobe3)。新增
chv-guest/authd_init_stub.py(仅导出 ChvFailure),build.sh 拷
errors.py + stub 作叶子树的 __init__.py,不再拷真实包 __init__;
P3_LIBS_PRESENT 条件加 errors.py;EXTRA_CONTENTS 加 errors.py 与
__init__.py;两 lockfile 脚本的 DECLARED_SOURCES 同款登记。
层 2:_seed_env_entries 把 guest-env 里的
GITEA_URL/GITEA_USER/SYNC_REPO 抄进 authd-seed(build_server
要求四键齐全,只 GITEA_TOKEN 即 ValueError);没有不补、不编
默认值。测试:seed 三键落盘/缺键不补、干跑夹具含新文件、lockfile
合成清单含新两项。
2026-11-07 03:56:24 +00:00
host e02828c590 fix(chv-muse-r11fix): nspawn 255 拒裸 --no-new-privileges 与 --directory=/(缺陷 2)
systemd 255 把裸 --no-new-privileges 当作带必填参数的选项,吞掉
下一个参数后报错(p2.log 实测);--directory=/ 也被拒,须
--ephemeral 或 --volatile。改 --no-new-privileges=yes,
--directory=/ 后加 --ephemeral:根快照 + overlay,单元内除 bind
出去的目录外一律可写、用完即弃,与 P2 共享客机 rootfs 设计一致;
--volatile 会把根置只读,codex 写 $HOME 下非 bind 路径会失败。
测试:CellArgvTest 断言新值、裸值不再出现。
2026-11-07 03:52:34 +00:00
host d324d87529 fix(chv-muse-round11): lockfile DECLARED_SOURCES 补 P3/vsock 六项
717efc4 镜像真机实测:BUILDINFO.contents 含 chv-authd 三件套、
cloud/chv/authd.py、cloud/chv/sentinel.py、vsock_notify,但两
脚本 DECLARED_SOURCES 没有它们——generate 记 6 项 unresolved,
verify 与 check 双双失败(contents 未覆盖)。V-9 的 912edef
没暴露:它的 contents 里根本没有这 6 项。

与 9f929b1 的 P2 三件套同款修法:六项皆仓库内脚本,版本随
repo_commit。合成 BUILDINFO 清单扩到与 717efc4 真实 contents
一致(另补 systemd-container 进 dpkg 假值),反向对照已验:
只撤修复时 LockfileTest 4 测里 2 测失败。

附带教训:经 ssh->wsl.exe 的命令内层退出码一律被吞成 0
(python3 -c 'sys.exit(1)' 回显 RC=0),此前三项验证只看
RC 会把失败判成通过;此后一律以输出文本为准绳(须见
'lockfile 校验通过' 行且无 unresolved 警告)。
2026-11-07 01:08:28 +00:00
host 5d5041bb7c round-12: merge round-11 head 717efc4 (V-9 real-machine fixes)
Round-11 moved on Gitea after round-12 branched (29d3a5a -> 717efc4):
V-9 guest-env fixes must be in any image built from this branch, or
the round-12 image would ship the known defects V-9 found (guest-env
never created when owner=chv:chv; wake reporting ready ~30s before
the runcmd chown lands). Contents, all from 717efc4:
- build.sh: runcmd chowns /etc/chv/guest-env to chv:chv after
  'start chv-authd', before 'chv-sync boot'
- check/generate_lockfile.py: DECLARED_SOURCES gains P2 trio
  (chv-cell/broker/browser -> repo_commit)
- executor._wait_guest_env_usable: with secrets delivered, wait for
  guest-env readable by the SSH user before reporting ready; timeout
  stops the VM and raises guest_env_not_ready; called from _boot_vm
- hostkeys.py: V-9 root-cause comment (owner always root:root)
- test_chv_gitea_sync.py: 5 new tests

Conflict: build.sh runcmd line only. Resolved as union: 717efc4's
runcmd line (with chown) kept; HEAD's P4 trio + P7 chv-memory
copy-in lines (absent on the 717efc4 side) kept. executor.py
merged cleanly.
2026-11-07 01:05:51 +00:00
host 717efc4688 feat(chv-muse-round11): 移植 V-9 真机修复(9f929b1/912edef/9fb0f4b)
- lockfile 两脚本 DECLARED_SOURCES 加 P2 三件套 chv-cell/broker/
  browser -> repo_commit(9f929b1)。
- build.sh 99-chv-sync.cfg runcmd 在 chv-sync boot 之前加 chown
  行:guest-env 由 write_files 以 root:root 写(建用户之前),
  须在 final 阶段改归 chv(912edef;本分支 runcmd 多一行
  start chv-authd,chown 插在它之后、boot 之前)。
- executor._wait_guest_env_usable:密钥已投递时等客内 guest-env
  对 SSH 用户可读再报 ready,超时关 VM 抛 guest_env_not_ready
  (9fb0f4b;runcmd chown 比 SSH 就绪晚约 30 秒)。
- hostkeys.py:本分支 _guest_env_entry 本来就写死 owner
  root:root(P3 重构时已无 ssh_user 分支),912edef 的
  hostkeys 改动无内容可移,只补 V-9 根因注释防回退。
- 测试(5 个新测试,8 个运行):owner 恒 root:root、build.sh
  chown 位置、chv 用户下 wake 后 owner 仍 root、等到可读、
  永不可读抛 guest_env_not_ready。

全量 cloud 套件 2019 OK(skipped=8)。
2026-11-07 00:48:25 +00:00
host 320cd36921 merge(chv-muse-round12): 合入 P7 状态与记忆分离 (29fa0c8)
冲突按并集解:build.sh 保留 P2+P3+P4 检查/拷入 + P7 的
chv-memory 检查/拷入(P7 的重复 runcmd 行删掉——同一文件
写两次会盖掉 authd 启动行);buildinfo.py 加 chv-memory 一项;
production-readiness.md P7 一节顺延为 §18。P7 测试 46 项全过。
2026-11-07 00:02:54 +00:00
host b0b9f5ed9d merge(chv-muse-round12): 合入 P6 privsep worker + 确定性安全检查层 (81e4eaf)
唯一冲突是文档 §15 撞号:P6 一节顺延为 §17,基线说明更新
(P3 已在树里,接线走真实代码)。chv-sync.sh 自动合入正确:
P6 的 worker 路径与 P3 的 authd 路径(useHttpPath)都在。
P6 测试 43 项全过(skipped=1 是 root 下 uid 否定测试)。
2026-11-07 00:02:22 +00:00
host c24cc0ba92 merge(chv-muse-round12): 合入 P4 浏览器完全对齐 (1c8fc3f)
冲突按并集解:build.sh 保留 P3 authd 检查/拷入/runcmd(含
start chv-authd)+ P4 三件套检查/拷入(P4 的重复 runcmd 行
删掉——同一文件写两次会盖掉 authd 启动行)+ P4 的
cdp-broker.service/tmpfiles(含 ask-queue)+ P3 叶子模块条件
拷入;buildinfo.py 两边内容项并集;chv-browser.py 取 P4 重构
版(broker 客户端);production-readiness.md P4 的 §15 顺延为
§16。P2 的 BrowserTest 改走假 CDP broker(断言不变),两路
干跑夹具补齐并集文件集。
2026-11-07 00:01:35 +00:00
chv 29d3a5a474 feat(chv-muse-round11): build.sh 补 P3 authd 进镜像 + gt 脚本哈希轮换
- authd 三件套 copy-in(chv-guest/authd.py 与 git-credential-chv-authd
  -> /usr/local/bin/ 并 chmod +x,chv-authd.service ->
  /etc/systemd/system/),云端叶子模块 cloud/chv/authd.py 与
  sentinel.py + 两个 __init__.py -> /usr/local/lib/chv-authd/,
  systemctl enable chv-authd;99-chv-sync.cfg runcmd 在 chv-sync
  boot 之前加 systemctl start chv-authd;缺件即 fail-fast
- buildinfo BASE_CONTENTS 登记新增件;P2 干跑夹具补 P3 文件并断言
  authd 步骤出现在干跑输出里
- GT_INSTALL_SH_SHA256 cf82... -> 49ffe090...:上游 2026-11-07
  06:44 轮换,V-9 已核实(WSL fresh curl 与失败镜像内残留逐字节
  一致;新脚本全文已读,只从 dl.gitea.com 取 1.0.0 二进制并验 hash)
- vsock_notify 条件拷入在合入后生效(cloud/chv/guest/vsock_notify.py
  已在仓里,干跑不再报缺失警告)
- 定向单测 P1/P2/P3/chv_fix 172 项全过;全量套件仍在跑
2026-11-06 23:08:40 +00:00
host 29fa0c8813 feat(chv): 对齐 Muse P7——状态与记忆分离
- 每 VM 一个 SQLite 状态库(cloud/chv/app_state.py):
  state_dir/<vm>/app-state.sqlite(0600,WAL),表 tasks/goals/
  sessions/audit(只追加)/meta;写操作过无凭证守卫(键名 +
  值模式);备份用 sqlite3 在线备份 + manifest(sha256),恢复
  先验 manifest、先留 pre-restore 副本;DDL 用 SQLite 与
  Postgres 共通受限子集,换 Postgres = 换连接工厂。
- 记忆文件接线(cloud/chv/memory_files.py):宿主镜像读/列/
  写/追加(路径穿越守卫、写审计);MemorySystemClient 调真实
  记忆系统 CLI(search/get/explain/forget-plan/forget-confirm/
  backup/restore/export),不重造引擎;forget = plan → confirm
  → 本地清行 → search 验证查不到;pull_memory_home 经 SSH +
  tar 拉取客机记忆家目录。
- 客内脚本 chv-guest/chv-memory.py(纯标准库,list/read/
  append/forget/export),家目录 /home/chv/memory(workspace
  之外,不同步到 Gitea);build.sh 拷入 + buildinfo 登记。
- 新测试 cloud/tests/test_chv_muse_p7.py 46 项全过(含真 CLI
  write/search/forget/backup/restore 实测、假 ssh 拉取、否定
  测试);全量 cloud 套件 1913 项 OK(skipped=8)。
- 存储选型:SQLite 不是 Postgres(见差距表 §4;轻量 VM 跑
  Postgres server 太重,宿主共享 Postgres 破坏每用户 VM
  隔离);忘记是尽力删除 + 验证,不是密码学擦除。
- 真机待验证(未做,等测试机空闲):见
  docs/chv-muse-p7-gap.md §6 清单
2026-11-06 22:39:04 +00:00
host 1c8fc3f7c8 对齐 Muse P4:浏览器完全对齐(CDP broker 单元外 + a11y-tree-only + 确定性分类器)
- chv-guest/chv-cdp-broker.py(新):单元外 CDP broker,用户 chv-browser,
  独家 chromium.launch(headless=True, args=LAUNCH_ARGS);启动参数白名单
  无 --remote-debugging-port、无 devtools;导航前 URL 校验 + 拦截名单;
  快照恒带 untrusted_input 与 injection_flags;高风险表单在页时 open
  即 ask 入队;观测到导航 POST 自动暂停;命中拦截规则的下载取消 + 暂停
  + ask;全部回包与错误经 sanitize_error + response_leaks 检查。
- chv-guest/chv-browser.py(P2 版重构):改为 broker 客户端,不再直连
  Playwright(本文件无 playwright import),保留 URL 校验 + 快照截断
  作为第二层防线;暂停状态与 broker 同步。
- chv-guest/chv_browser_guard.py(新):纯 stdlib 共享规则——validate_url、
  拦截名单加载/匹配、快照截断、错误消毒(去 HTML、截断 200 字符、凭据形
  key=value 与 sk- 形 token 打码)、注入启发式(中英文各 6 条)、表单风险
  扫描、下载扫描(可执行扩展名)、文件型 ask 队列(pending/allowed/denied,
  与 P3 ApprovalQueue 同语义)、启动参数与回包泄露检查、脚本注入面
  禁止清单(FORBIDDEN_PAGE_ATTRS/FORBIDDEN_OPS)。
- chv-guest/browser-blocklist.txt(新):静态拦截名单;首版只有 RFC 2606
  .test 自测条目,生产条目由运维构建时填入,无在线更新。
- chv-guest/build.sh/buildinfo.py:P2 三脚本 + P4 三文件纳入镜像与
  buildinfo;新增 chv-cdp-broker.service 与 /run/chv/ask-queue tmpfiles。
- docs/chv-muse-p4-gap.md(新):现状 vs Muse 官方要求差距表、改动、
  诚实边界、P2 合入约定、真机待验证清单(round 12)。
- docs/chv-production-readiness.md:补 §15(P4 一节;编号合入时若与
  P2 的 §15 撞,顺延)。
- cloud/tests/test_chv_muse_p4.py(新,36 项):guard 单元、AST 钉住
  禁止属性、假 Playwright broker 测试、假 broker agent 测试、真 Unix
  socket 联调、buildinfo/build 干跑断言;含否定测试(evaluate 被拒、
  DOM 外泄被拒、DevTools 不可达、私有地址被拒、高风险表单被拦、
  注入文本被标注)。

测试:P4 新测试 36/36;全量 cloud 套件 1903 项,两次连续全过
(skipped=8)。附记:第一次全量跑出 1 个失败但输出未保留、后两次
均未复现,疑为时序抖动,未追认。
真机验证未做:排队等 round-11/第三棒释放 100.65.51.33,清单见 gap
表 §5。本地提交,未推未合未部署。
2026-11-06 22:36:56 +00:00
host 81e4eaf4db feat(chv): 对齐 Muse P6 privsep worker + 确定性安全检查层
- cloud/chv/privsep.py(新):Gitea worker 注册表(凭证白名单只含
  GITEA_TOKEN)+ 类型化请求/响应(request_id 绑定、64KB 上限)+
  Unix socket 客户端(超时/崩溃 -> 类型化失败 WorkerRefusal)+
  客户端白名单校验
- cloud/chv/safety.py(新):UntrustedData 来源标记 +
  InjectionScanner 确定性规则(block/flag/allow,中英)+
  redact_secrets + AuditLogger(只记指纹,不记值)+
  audit_inference_request/response
- chv-guest/chv-gitea-worker.py + .service(新):systemd 沙箱化
  worker(SO_PEERCRED 只放 root,仓限 ~/workspace,git 只配 authd
  helper,绝不持 token;输出先扫描再返回)
- chv-guest/chv-sync-worker-client.py(新):agent 侧薄 CLI
- chv-guest/chv-sync.sh:pull/push/status 有 socket 时走 worker,
  否则旧路径 + stderr 警告(过渡兼容)
- cloud/chv/executor.py:_run_guest 前后加推理边界审计钩子
  (best-effort)
- docs/chv-muse-p6-gap.md(新):差距表 + 覆盖边界 + 真机待验证清单
- docs/chv-production-readiness.md:§15
- cloud/tests/test_chv_muse_p6.py(新):43 项

测试:新测试 43/43;全量 cloud 套件 1910 OK(skipped=9);mypy
在新增文件上干净(剩余 4 处为基线旧错 executor.py:1396/1401、
marker_tools.py:142,未动)。
真机验证:未做,排队在 round-11 之后,清单见
docs/chv-muse-p6-gap.md §4。
2026-11-06 22:31:54 +00:00
host 304707b372 merge(chv-muse-round11): 合入 P3 authd 替身凭证 + Sentinel 出口审批 (a90cf78)
冲突解决(3 处,均为并集保留,双方改动无一丢弃):
- cloud/chv/executor.py _copy_config_fields:P1 的 balloon/
  vsock/vsock_cid 与 P3 的 sentinel_allow_hosts 合并进同一元组
- cloud/tests/test_chv_arch_maint_fe.py EXPECTED_FIELDS:P1 三字段
  + P3 sentinel_allow_hosts;len 断言 41/39 → 42(并集)
- cloud/chv/config.py 自动合入:P3 sentinel_allow_hosts(网络
  片段内)+ P1 _balloon_vsock_fragment 均保留;仅更新
  build_chv_config docstring 注明字段集含 P3 字段
2026-11-06 21:36:56 +00:00
host a90cf78d84 feat(chv): 对齐 Muse P3 收尾——authd 替身凭证 + Sentinel 出口审批
- 新测试 cloud/tests/test_chv_muse_p3.py 30 项:替身过期、真 token
  泄露面、SSRF 挡截、deny/ask 路径、authd-seed 拆分、wake 预审、
  客内 authd 守护进程、git credential helper 端到端(真 git fill)
- 8 个旧测试按新 authd 行为改写(未删测试、未放宽断言):
  GITEA_TOKEN 改走 authd-seed,build_ssh_command 不再 source 进
  codex 环境;wake 测试补 Sentinel 白名单 + 假解析器
- 7 处超 50 行函数拆到 50 行内(不改行为);补 authd/sentinel
  公共 API 注解,mypy 在改动文件上干净
- 真问题修复:git 默认不把 path 发给 helper,chv-sync boot 加
  credential.useHttpPath=true,否则 authd 路径每次都拒绝
- 全量 cloud 套件 1897 项 OK(skipped=8)

真机待验证(未做,等第三棒让出 WSL 测试机):见
docs/chv-muse-p3-gap.md §3.6 清单
2026-11-06 21:21:55 +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 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 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 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