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 时不留回落审计
(反向对照)。
This commit is contained in:
host committed 2026-11-08 06:01:54 +00:00
1 parent 35d9c3ff3f
commit 7c517f7fcc
3 files changed
+77 -9

No files matched your search

+41
View File
@@ -11,6 +11,8 @@
缺失 fail-closed、git argv 里只有 helper 配置绝无 token、输出先
扫描后返回、block 阻断、flag 标记;
- chv-sync.sh 委托:worker 在时走 worker、不在时走旧路径 + 警告;
回落每次留 stderr 警告 + 一行 JSON 审计(fail-open 保留,见
chv-sync.sh 头注);
- 执行器推理边界审计钩子:只记指纹、不记值、失败不中断。
"""
@@ -653,6 +655,8 @@ class ChvSyncDelegationTest(unittest.TestCase):
self.env["CHV_WORKSPACE"] = self.ws
self.env["CHV_USER_HOME"] = self.tmp.name
self.env["CHV_GITEA_WORKER_CLIENT"] = self.client
self.audit = os.path.join(self.tmp.name, "fallback-audit.log")
self.env["CHV_SYNC_FALLBACK_LOG"] = self.audit
def tearDown(self):
self.tmp.cleanup()
@@ -668,7 +672,42 @@ class ChvSyncDelegationTest(unittest.TestCase):
self.tmp.name, "no-worker.sock")
proc = self._run_sync("status")
self.assertIn("worker 不在", proc.stderr)
self.assertIn("privsep 未生效", proc.stderr)
self.assertNotEqual(proc.returncode, 0)
# 一次回落一行 JSON 审计:动作 + 未委托 + privsep 未生效。
with open(self.audit) as fh:
lines = [line for line in fh if line.strip()]
self.assertEqual(len(lines), 1)
record = json.loads(lines[0])
self.assertEqual(record["event"], "chv_sync_worker_fallback")
self.assertEqual(record["action"], "status")
self.assertIs(record["delegated_to_worker"], False)
self.assertIs(record["privsep_active"], False)
self.assertIn("ts", record)
def test_fallback_audit_accumulates_one_line_per_call(self):
self.env["CHV_GITEA_WORKER_SOCK"] = os.path.join(
self.tmp.name, "no-worker.sock")
self._run_sync("status")
self._run_sync("status")
with open(self.audit) as fh:
lines = [line for line in fh if line.strip()]
self.assertEqual(len(lines), 2)
for line in lines:
self.assertEqual(
json.loads(line)["event"], "chv_sync_worker_fallback")
def test_fallback_audit_failure_does_not_break_sync(self):
# 审计落盘失败只记 stderr,绝不中断同步(与 worker _audit
# 同款口径):同步本身的退出路径不变。
self.env["CHV_GITEA_WORKER_SOCK"] = os.path.join(
self.tmp.name, "no-worker.sock")
self.env["CHV_SYNC_FALLBACK_LOG"] = os.path.join(
self.tmp.name, "no-such-dir", "audit.log")
proc = self._run_sync("status")
self.assertIn("回落审计落盘失败", proc.stderr)
self.assertNotIn("Traceback", proc.stderr)
self.assertNotEqual(proc.returncode, 0) # 假 .git 仓,git 失败
def test_worker_present_delegates(self):
mod = _load_guest_worker()
@@ -692,6 +731,8 @@ class ChvSyncDelegationTest(unittest.TestCase):
self.assertEqual(proc.returncode, 0)
self.assertIn("clean tree", proc.stdout)
self.assertNotIn("worker 不在", proc.stderr)
# 走 worker 时不留回落审计(反向对照)。
self.assertFalse(os.path.exists(self.audit))
finally:
server.shutdown()
server.server_close()