Skip to content

fix: verdict 结构化标记 + 解析失败降级'需人工核对' - #19

Merged
tizerluo merged 6 commits into
mainfrom
fix/review-gate-structured-verdict
Aug 15, 2026
Merged

fix: verdict 结构化标记 + 解析失败降级'需人工核对'#19
tizerluo merged 6 commits into
mainfrom
fix/review-gate-structured-verdict

Conversation

@tizerluo

Copy link
Copy Markdown
Owner

Fixes #16

问题

表头「严重度分布」靠正则猜审查报告正文的 P0/P1/P2 计数,报告格式稍变即误读(真实案例:正文明确「P0 × 0,✅ 可以合并」,表头却渲染「P0 × 2」——疑似把汇总表格行/枚举句的数字当成了计数)或读不到(fallback「解析失败」)。且解析失败一律按 concerns 假红灯,曾致下游指挥 agent 看到表头停工等人工。

修复

mcp-server 侧(标记的生产端):

  • zcode_pr_review prompt 要求报告以严格单行 VERDICT: P0=n P1=n P2=n MERGE=yes|no 收尾
  • 新增 _append_verdict_marker():自尾向头找该行,转写成文末 HTML 注释 <!-- zob-verdict:{...} -->(GitHub 评论里不渲染);模型没按格式输出则原样返回,不编造

review-gate 侧(消费端):

  • parse_severity_counts 优先全文搜索 zob-verdict 标记,无标记退回原有正文正则兜底
  • verdict_from_counts:解析失败 → unresolved(❓ 需人工核对),不再误标 concerns
  • 评论表头三态渲染(pass / concerns / 需人工核对);README 与模块注释同步

测试

  • review-gate +4 例:标记优先于正文 / 标记残缺回退 / 仅标记无 prose / unresolved 渲染不含 concerns;2 处旧断言按新语义重钉
  • mcp-server +6 例:VERDICT 行转写 / MERGE 大小写 / 自尾向头查找 / 幂等 / 空安全 / 无行不编造
  • 两套件全绿 + ruff 通过

自证彩蛋

本 PR 合入后,review-gate 审查带 VERDICT 行的报告将直读结构化标记——修 review 工具的 PR 由 review 工具验证。

表头严重度分布之前靠正则猜报告正文, 格式稍变即误读 (把表头/枚举句数字
当计数, 真实案例正文 P0=0 却渲染 P0×2) 或读不到 (fallback 解析失败)。

mcp-server 侧:
- prompt 要求报告以严格单行 'VERDICT: P0=n P1=n P2=n MERGE=yes|no' 收尾
- _append_verdict_marker(): 自尾向头找该行, 转写成文末 HTML 注释标记
  zob-verdict (GitHub 评论不渲染); 模型没按格式输出则原样返回不编造

review-gate 侧:
- parse_severity_counts 优先读 zob-verdict 标记 (全文搜索), 无标记退正文
  正则兜底
- verdict_from_counts: 解析失败 → unresolved ('❓ 需人工核对') 而非误标
  concerns — 假红灯曾致下游指挥 agent 停工等人工
- 评论表头三态渲染; README/模块注释同步

测试: review-gate +4 例 (标记优先/残缺回退/仅标记/unresolved 渲染) +
mcp-server +6 例 (转写/大小写/自尾向头/幂等/空安全/无行不编造)
tizerluo added a commit that referenced this pull request Aug 15, 2026
PR #18 用 zcode_pr_review 自审发现的跟进修复:

- P1-1 实例互斥: checkout 发生在 bridge 侧文件锁之外, 双实例共享同一
  state/clone_root 时, 第二实例 (守护进程跑长审查期间手工 --once 调试)
  会在 mimosa 扫描中途换掉工作区 (TOCTOU) → 静默扫错代码。
  修法: 启动时对 <state_file>.lock 非阻塞 flock, 拿不到锁 exit 2;
  正常退出释放, 崩溃时 OS 自动回收。
- P2-1 checkout --force 只丢弃 tracked 改动, untracked 残留跨轮存活
  (mimosa 扫工作区文件) → checkout 前补 git clean --force -d -x,
  clone 为 gate 专有目录, 清到严格基线。

P2-2 (README 三态 verdict) 是跨分支审查视角产物, #19 合入后自然消解。
测试: fake git 补 clean 处理; +2 例 (clean 先于 checkout / 实例锁退出码
2 且不跑审查, 释放后可续跑); 77 全绿。
PR #19 用 zcode_pr_review 自审, 报告自身即复现了 P1-1 (正文引用
zob-verdict 字样 → 幂等短路 → 真标记未追加), 据此修复:

- P1-1 标记伪造链 (被审 PR 预埋标记骗表头):
  ① mcp-server 幂等检查改为「文末完整匹配 <!-- zob-verdict:{...} -->」,
     裸串/正文引用不再抑制追加
  ② 转写前对正文完整注释形态标记消毒 ([已消毒的 zob-verdict 引用]),
     唯一可信来源 = 文末追加的真标记
  ③ review-gate 解析只认完整注释形态 + 取最后一个匹配 (真标记恒在文末)
- P2-1 标记 merge=no 时全 0 计数不给 pass (表头'可以合并'与报告结论
  矛盾是 issue #16 误导残余) — verdict_from_counts 增 merge_from_marker
- P2-2 超长截断日志提示尾部 VERDICT 行可能被切; README 记录评论截断
  不含标记的限制

测试: review-gate +3 例 (末匹配胜出/裸串不认/merge 覆盖), mcp-server
+2 例 (裸串不抑制/伪造注释被消毒); 全绿。
二轮自审发现: _RE_ZOB_MARK_ANY.sub 只在命中 VERDICT 行的分支里执行,
模型未按格式输出结论行时 (格式漂移真实存在, issue #16 起因即是) 兜底
return 原样透传 — 被审 PR 预埋的完整注释形态伪造标记直达 review-gate,
'取最后一个匹配' 全信 → 伪造 pass。

- 消毒提到函数入口无条件执行 (review 已验证正则语言覆盖关系: gate 的
  匹配串全部落在消毒模式内), 到达下游的完整形态标记必然出自文末追加
- 幂等改认「VERDICT 行 + 标记行」两行完整追加形态 (_ends_with_appended_marker),
  正文孤立标记 (含文末裸标记) 不再抑制追加/透传

测试 +1 例 (文末裸标记无 VERDICT 行 → 消毒且不追加), 全绿。
@tizerluo

Copy link
Copy Markdown
Owner Author

狗食审查(zcode_pr_review 自审,两轮)

第一轮(8745bda):P0 × 0 / P1 × 1 / P2 × 2,报告自身即复现了 P1-1(正文引用 zob-verdict 字样 → 幂等短路 → 真标记未追加):

  • ✅ P1-1 verdict 标记伪造链:幂等改认「VERDICT 行+标记行」完整形态、消毒正文完整注释形态标记、gate 只认注释形态+最后一个匹配(7dee8a8)
  • ✅ P2-1 标记 merge=no 时全 0 计数不给 pass;P2-2 截断日志提示 + README 限制说明

第二轮(7dee8a8):P0 × 0 / P1 × 1 / P2 × 4,活体验证通过(报告尾部成功带出 zob-verdict:{"P0":0,"P1":1,"P2":4,"merge":true},消毒 1 处引用):

  • ✅ 新 P1-1 无 VERDICT 行的兜底路径不消毒、伪造标记原样透传 → 消毒提到函数入口无条件执行(465eb7f)

标记链路的两轮攻防验证了 issue #16 修复的完整性。

二轮自审的 4 条纵深加固建议全部落地:

- P2-2 位置契约: 结论行只认全文最后一个非空行 (prompt 规定结论收尾),
  其余位置的 VERDICT 形态行一律按引用消毒 — 防'真结论之后的尾置引用
  行'劫持自尾向头搜索, 也防引用行喂下游 prose 正则
- P2-1 幂等一致性: 尾置「VERDICT 行+标记行」形状对但数值不一致 →
  丢弃伪造标记, 以结论行为准重写 (不再是纯形状短路)
- P2-3 正则限界: _RE_ZOB_MARK_ANY 的无界惰性量词 [^>]*? 改
  [^>]{0,200}, 消除最坏 O(n²) 扫描 (MB 级构造文本可停摆单线程审查)
- P2-4 位数钳制: 全链路 (mcp 结论行/标记行 + gate 标记/prose) 统一
  \d{1,9} — ≥4301 位整数串会让 Python ≥3.11 的 int() 抛 ValueError,
  整次审查作废并烧满 gate 侧 5 次重审

测试: mcp 重写 1 例 (位置契约) + 新增 4 例 (尾置引用块/不一致重写/
超长数字×2), gate 新增 2 例 (超长数字标记/prose); 71+81 全绿。
@tizerluo

Copy link
Copy Markdown
Owner Author

狗食审查第三轮(加固后终验,c24dc3d + b281052

结论:P0 × 0 / P1 × 0,可以合并。 "核心安全不变量(防伪造、防 DoS、fail-safe 方向)经推演均成立,未发现可实际利用的新引入 P0/P1。"

活体验证全链路通过:

  • 报告正常以 VERDICT 行收尾 → 真标记追加文末(zob-verdict:{"P0":0,"P1":0,"P2":4,"merge":true}),全文唯一完整形态标记
  • 消毒 1 处正文标记引用(位置契约 + 无条件消毒工作正常)

剩余 4 条 P2 均为提示性质,不需要代码变更:① 幂等早退路径的纵深不对称(last-match 规则下正文残留对 gate 惰性)② gate 防伪造前提依赖配套 mcp-server 版本(同仓同 commit 部署即满足)③ verdict 第三态 unresolved 的下游契约说明(issue #16 的目的本身,README 已记)④ 存量 markdown 注入面(#18 P2-3 同款,已知限制)。

三轮攻防轨迹:P1 伪造链 → P1 兜底透传 → 全 P2 提示——攻击面收敛,标记链路闭环。

@tizerluo
tizerluo merged commit 4c42530 into main Aug 15, 2026
4 checks passed
@tizerluo
tizerluo deleted the fix/review-gate-structured-verdict branch August 15, 2026 21:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

review-gate: 严重度表头解析失灵(正文 P0=0 却渲染 P0×2/「解析失败」)

1 participant