跳转至

SOP:修复 merge-guard 的 allow_fp 漏记

变更日期:2026-10-07  执行人:Agent(用户授权)  对象:merge-guard/merge-guard.py

1. 背景

allow_fp 是全局规则指定的误报唯一机器判据——「被拦后原样重发」即说明该次拦截不成立;它被用来判断「某类强制规则是否该放宽」(如「判据 I 修复后 allow_fp I 应消失,若仍 ≥50% 则降回提醒级」)。

2026-10-07 实测发现该信号系统性漏记:

  • state 文件 merge-guard/state/6ac5932fc1804934d3e7e22a__solo_agent 记录 denied: ['b8e372a101441b3b', '54eaf3ae5cfb723d']、denies: 2 —— 两次 deny 的指纹都入了册;
  • 但 logs/guard_events.jsonl 中这两次(08:34:34、08:37:51)之后都没有 allow_fp 行,两次原样重发均被静默放行;
  • 同一轮收尾自查的 guard 计数为 1/0/1(deny 1、allow_fp 0),现场复现。

根因:allow_fp 的判定被嵌在 if hit: 内,只有「重发时仍命中某条判据」才记录。而原样重发恰恰是「相同目标、快速连续」:

  • 判据 A 要求 len(set(keys)) == len(keys)(目标互不相同)→ 重发必然不成立;
  • 判据 B 要求 separated()(相邻间隔 ≥ STEP_GAP_SEC = 1.5 s)→ 快速重发(<1.5 s)被当作「同一步内并行调用」排除。

于是 hit=None → 走「静默放行且不写日志」。对照样本:08:16:08 deny(A) → 08:16:17 allow_fp(mark=B),两次相隔 9 s,因满足 B 的 separated 才记上。

2. 目标

allow_fp 的判定与本次判据命中解耦:只要本次调用的指纹在 denied 册中(即此前被拦过),就静默放行并记一条 allow_fp。

可验证状态:离线用例 C1 由「失败(allow_fp=0 条)」变为「通过(allow_fp=1 条)」,其余用例(A/B/C 判据、防假记录)全部回归通过;真机表现为 logs/guard_events.jsonl 中 A 类拦截后的原样重发不再丢记录。

3. 前置条件

  • 对 merge-guard/merge-guard.py 有写权限;
  • python3 可用;
  • 无其它会话正在密集触发该 Hook(本脚本按 (session_id, agent_id) 隔离状态,改动不影响在途会话的既有 state)。

4. 影响范围

  • 主机:单机(本机 token_proxy 工程),一个文件。
  • 组件:merge-guard.py 的 PreToolUse 主流程与文档头。
  • 是否停服:否。Hook 脚本同会话下一次请求即生效,无需重启 Trae、不影响代理 ds/glm/mi。
  • 窗口期:无要求。
  • 在跑作业:无影响。

5. 分步操作

步骤 1:备份(高危前置)

cd /home/rain/Documents/services/token_proxy
TS=$(date +%Y%m%d-%H%M%S)
mkdir -p merge-guard-allowfp-20261007/backup/merge-guard
cp -p merge-guard/merge-guard.py \
      merge-guard-allowfp-20261007/backup/merge-guard/merge-guard.py.bak.$TS

预期:backup/merge-guard/ 下出现 merge-guard.py.bak.<TS>(24554 B)。 失败判断:目录不可写或 cp 非 0 → 停止,勿继续改文件。

步骤 2:先复现缺陷(用备份的旧版跑用例)

python3 merge-guard-allowfp-20261007/verify_allowfp.py \
        --script merge-guard-allowfp-20261007/backup/merge-guard/merge-guard.py.bak.<TS>

预期:C1 失败(deny=True,重发输出='',allow_fp=0 条),C2~C5 通过,退出码 1。 失败判断:若 C1 直接通过,说明先前的诊断有误 —— 停下来重新定位,不要继续改。

坑:用例必须在隔离目录跑(verify_allowfp.py 会把被测脚本复制到临时伪工程结构,state 与 guard 日志都落在临时目录)。若直接对生产脚本跑而不隔离,会污染 logs/guard_events.jsonl 与 merge-guard/state/。

步骤 3:改判定位置(三处必须成套)

把「原样重发」判定移出 if hit:,以 item["fp"] in denied 为条件:

  1. 新增外层 if item["fp"] in denied: 分支,记 allow_fp(mark 取本次判定、无则记 -);
  2. 原 if hit: 改为 elif hit:;
  3. 删除内层已冗余的 item["fp"] not in denied 条件与旧的 elif mark in ENFORCE_MARKS and item["fp"] in denied: 分支;
  4. 文档头「强制与放行」段补一句「放行与记账都只看指纹、与本次是否命中判据无关」。

坑:四处必须成套改。只加外层不删旧 elif,会造成同一调用记两条 allow_fp;只删旧 elif 不改外层,则 hit=None 时仍漏记(等于没修)。

python3 -c "import ast; ast.parse(open('merge-guard/merge-guard.py', encoding='utf-8').read()); print('语法通过')"
grep -n 'in denied\|allow_fp' merge-guard/merge-guard.py   # 预期:外层判定 + 文档头说明各 1 处

步骤 4:(无)本次不涉及其它文件。

6. 验证

层级 命令 预期
语法 python3 -c "import ast; ast.parse(open('merge-guard/merge-guard.py').read())" 无异常
用例(5 项) python3 merge-guard-allowfp-20261007/verify_allowfp.py 全部通过,退出码 0
关键项 C1 同上 A 类 deny 后快速重发 → allow_fp=1 条(修复前为 0)
回归 C2~C5 同上 连续只读仍 deny(A);同文件连续 Read 仍 deny(B);同文件连续 Edit 仍提醒(C);未拦截调用不产生 allow_fp
真机 观察 logs/guard_events.jsonl A 类 deny 后的原样重发出现 action=allow_fp 行;wrapup.py 的 guard 计数中 allow_fp 不再恒 0

实测(2026-10-07):修复前 C1 失败(allow_fp=0)、修复后 C1 通过(allow_fp=1),C2~C5 全程通过。

7. 回滚

备份位置:merge-guard-allowfp-20261007/backup/merge-guard/merge-guard.py.bak.20261007-084025(登记见 backup/README.md)。

cd /home/rain/Documents/services/token_proxy
cp -p merge-guard-allowfp-20261007/backup/merge-guard/merge-guard.py.bak.20261007-084025 \
      merge-guard/merge-guard.py
python3 merge-guard-allowfp-20261007/verify_allowfp.py \
        --script merge-guard/merge-guard.py        # 回滚后预期:C1 重新变为失败

回滚后验证:C1 恢复失败(allow_fp=0)即确认已回到旧版行为。回滚触发量化:真机出现「同一指纹被反复 deny(死锁)」或「同一次调用记两条 allow_fp」→ 立即回滚。

8. 风险说明

风险点 缓解措施
误删/漏删旧分支导致「fp 在册却仍被 deny」(死锁) 三处成套改 + 离线用例 C1 覆盖「拦后重发必放行」
同一次调用记两条 allow_fp 旧 elif ... in denied 分支已删除;C1 断言 allow_fp=1 条(非 ≥1)
未拦截调用被误记 allow_fp 外层条件严格为 item["fp"] in denied;用例 C5 专测该项
行为差异 唯一差异:非强制判据(C)且该指纹已在册时,旧版会提醒、新版静默放行(同一指纹此前已放行过,不再重复提醒)。属边缘且更合理
污染真实 state / 日志 用例强制在临时目录隔离运行

来源:/home/rain/Documents/services/token_proxy/merge-guard-allowfp-20261007/SOP.md | 最后更新:2026-10-07 08:41(由 build/sync.py 同步,请勿在站点侧修改)