Commit Graph
6 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 d4cef29cdb round-12: migrate P8 registries + P12 audit to AppStateStore
- app_state.py: host_state_db_path() (host-level DB, distinct from
  per-VM state_db_path); kv_get/kv_put/kv_delete on meta (namespaced
  keys; kv_put skips the no-secrets guard by design — registry docs
  hold user prompts, documented); audit_trail table (P12 record
  shape + hash chain, seq explicit); counts()/backup() gain
  audit_trail.
- swarm.py / presentation.py / browser_tasks.py: JSON-file backends
  replaced by KV rows (p8-swarm / p8-ledger / p8-browser-tasks);
  legacy JSON imported once on first load (renamed .migrated);
  corrupt row / unreadable store returns empty (fail-safe, same
  semantics as before; next successful save self-heals).
- audit_trail.py: StoreAuditTrail (same record shape, hash chain,
  filters, export as JSONL version, backed by audit_trail table);
  migrate_jsonl_to_store() moves a chain verbatim (broken chain
  refused, divergent seq refused, re-run idempotent). JSONL class
  kept for reading old files.
- P12 dict contracts now run against real P3/P5 code
  (ApprovalQueue.record -> build_approval_card ->
  card_matches_channel; CapabilityIssuer.issue -> describe ->
  PermissionRegistry.record_grant).
- P6->P3 local protocol test: real PrivsepClient + real worker
  server + real git status; worker git argv carries the real P3
  helper + useHttpPath; no token in argv/env/audit. runuser + HTTP
  pull stay real-machine-only (see gap docs).
- Gap docs updated: P8 migration done, guest-side browser follow-up
  explicitly left for the real-machine track (no task_id in the
  browser protocol yet; lifecycle needs a real VM to verify);
  P6 wiring + P12 migration marked done; production-readiness.md
  gains §20 (round-12 integration).
- Tests: new cloud/tests/test_chv_muse_round12.py, 24/24 OK;
  test_chv_muse_p8.py ledger corrupt test rewritten for the KV
  backend (+ legacy-import test). P7+P8: 95+1 tests OK.
2026-11-07 00:36:25 +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