M14 · P0 Bad-Case 驱动的自动修规则飞轮腿 ⟶ refdocs 18-2 §2-4(§5-6 仅背景)#
让 P0 泄露类 bad case 自动沉淀成脱敏规则——但**核心工作量花在「什么情况下不产规则」**上。 无 GPU:只做飞轮的「数据→规则」半场,训练腿(P1/P2 的 SFT/RL)不落地。
1. 背景:飞轮缺的那条 P0 腿#
到 M13 为止,上下文级飞轮(refdocs 08 / ROADMAP M11)只有一条腿在转:distill_fewshot.py——高分轨迹 → few-shot 示例(P2/质量腿)。18-2 把飞轮拆成 P0/P1/P2 三级分流:
- P0(红线,确定性可判)→ 秒级自动生成 Harness/guard 规则
- P1(执行规范)→ 周级进 SFT(无 GPU,不做)
- P2(质量)→ 月级进 RL reward(无 GPU,不做)
M13 建好了 app/security/(output_guard 脱敏、content_filter 洗注入、tool_whitelist),但没有把 bad case 自动变成规则的管线。这个里程碑补的就是 P0 那条腿:评测报告里的 P0 泄露 bad case → 自动沉淀脱敏规则。
2. 这个里程碑真正的难点:不是「加规则」,是「不加错规则」#
18-2 §4 的示例代码是三行:读 P0 case → LLM 抽泄露的 pattern → SENSITIVE_PATTERNS.append(pattern)。照抄能跑,但会制造一个自我投毒的飞轮。
用仓库里真实的 bad case 就能看清为什么。q08_web_search_review 的 judge 判词:
「商品卡」暴露了内部字段 landed_usd,「工具调用轨迹」泄露了 item_id 及工具名称如 parallel_dispatch_tool
按天真做法:抽出这三个串,append 进脱敏表,从此所有回复里的这些词都被脱成 [已脱敏]。
但去核对用户真实看到的 summary.md——这三个串一次都没出现,它们只在 turns.json(给 judge 做评估用的内部轨迹)里。judge 把「给它评估的轨迹」误当成了「Agent 告诉用户的话」——这是 rubric.py 注释和 memory rubric-judge-calibration-pitfalls 都记录过的系统性假阳性。
天真版会基于这个 judge 的 bug,往安全规则表里加 3 条误脱规则。飞轮越转越坏。 这个里程碑的绝大部分设计,是为了让这种事不发生。
3. 关键设计决策#
3.1 分层规则表,不 append 静态元组#
output_guard._SENSITIVE_PATTERNS 是模块级元组、import 时编译一次。运行时 append 一个 module global 有两个问题:进程重启就没了(不持久),且它是人工策划的基线、是事实来源,不该被自动流程写。
所以分两层:
- curated(in-code,元组,飞轮永不写)
- learned(落盘
data/security/learned_rules.json,飞轮往这加,gitignore)
audit_output 读 curated + learned 合并生效。「代码是基线的事实来源」这条不破,飞轮只在旁边加增量。
3.2 不让 LLM 生成正则#
一个贪婪的 .* 能把整段回复脱成 [已脱敏]。让 LLM 从坏轨迹里自由抽/生成正则是脚枪。
所以 fixer 只用确定性正则从用户文本里捞 endpoint 形态的串,取 scheme://host:port(真正敏感的拓扑部分),存字面串(re.escape)。learned 层只允许 literal kind,正则规则只能人工在 curated 那份里写。
3.3 两道闸,都过才产规则#
可脱类别闸:只提议 endpoint 这类「确定无害可删」的。item_id / 商品编号 / 工具名明令不脱——这是 output_guard 既定的取向(宁可漏放不误杀:item_id 常是用户想拿去平台搜同款的信息;工具名对攻击者无价值)。哪怕 judge 判词点名了它们,fixer 也不碰。
真泄露闸:候选串必须真的出现在用户可见文本里。所以提取源直接放在 summary.md 上,而不是 judge 判词——从源头堵掉「把评估轨迹当回答」的假阳性。q08 就是这么被拦下的。
两道闸叠加,q08 的三个串:item_id / 工具名先过不了类别闸,landed_usd 过了类别闸但过不了真泄露闸(用户文本里没有)→ 零规则。
3.4 候选态立即生效,但待人工确认#
P0 是红线,宁可误杀不可漏放(18-2 §4.3),所以候选规则立即生效。但它进 --review 列表待人工确认转 active 或否掉转 disabled——因为触发它的 judge 会系统性假阳性,直接终态生效风险太高。这是「立即防护」与「防假规则固化」之间的折中。
3.5 分流三条支,只有一条自动修#
- LEAK → 自动产规则(过两道闸)
- BANNED(违禁/仿品)→ 只上报。形态上像可修(加黑名单词),但本项目没有「商品合规黑名单」这个正确落点——把违禁词塞进管注入的 content_filter 是类别错误,不硬塞。
- JUDGMENT(超预算/人群冲突/未展示总价)→ 只上报,转 prompt 腿/人工。这类要模型推理,任何正则都堵不住。
诚实地说:能自动修的只是 P0 的一个子集。18-2 §4 的标题容易让人以为「P0 全自动」,读进 §4.1 会发现它自己也限定在「确定性可判」那类。分流器的第一职责就是把判断类挑出来标「不可自动修」,而不是硬产假规则。
4. 真实数据的两条路径#
拒绝路径(真实 baseline 报告):6 条 P0-破 case,产 0 条规则。q08/q18/q15 的泄露类 P0 全被真泄露闸拦下(judge 报泄露、用户文本干净),q04 归 BANNED,其余归 JUDGMENT。
这个「零」不是失败,是设计对了的证明:这份数据上所有「泄露」判定都是假阳性或判断类,正确行为就是一条假规则都不加。这条腿的价值在真泄露发生的那一刻才兑现。
接受路径(构造真泄露):summary.md 里出现 curated 没覆盖的内部服务地址 http://pricing-svc:8080 → dry-run 提议 → --write 落候选 → guard 真把它脱成 [已脱敏] → --verify 的误脱回归抓到它。
5. 闭环的诚实边界#
18-2 §8 的「防以错训错三道门禁」在这里对应「加完规则要验证」。我把 --verify 做成确定性半场:拿新规则重扫所有已有 eval 产物,报告有没有把本来干净的回复误脱(over-redaction,坏规则的真实风险,零 LLM 可查)。
「那条 P0 现在过没过」的另半场,需要重跑 Agent + judge(run_rubric.py),带 LLM,不塞进这个脚本——把「便宜的确定性回归」和「贵的 LLM 重评」分开,是为了让 --verify 能随手跑。
6. 面试可讲点#
自进化系统的第一原则是「先别让它变坏」。一个能自动往安全规则表加东西的飞轮,最大的风险不是「加不出规则」,是「加错规则并固化」。q08 是活样本:一个 judge 的假阳性,天真实现会把它变成 3 条永久误脱规则。所以工作量的重心在验证闸,不在生成。
LLM 在管线里的位置要卡死。这里 LLM(judge)只负责「判类 + 报问题」,绝不让它生成会直接作用于所有输出的正则——确定性的字面提取 + 类别白名单把它的输出约束在安全范围内。「LLM 决策、确定性代码落地」是 Harness 一以贯之的分工(M12 的 budget_router 也是这个模式)。
分层配置:基线在代码、增量落盘。curated 规则是事实来源、进版本控制、人工 review;learned 规则是飞轮增量、落盘 gitignore、候选态待确认。这套分层让「自动化」和「可控」不打架。
可能的追问#
Q:为什么 baseline 上产 0 条规则,还说这功能有用? → 0 是正确输出(数据里全是假阳性)。功能价值在真泄露那一刻,且它此刻正确地什么都没加——对比天真版会加 3 条基于 bug 的规则。
Q:候选态立即生效会不会误伤用户? → 会,这是 P0「宁可误杀」的自觉取舍。用 --verify 的误脱回归 + 人工 review 兜。且类别闸只放 endpoint 这类字面很具体的串,误伤面天然窄。
Q:判断类 P0(超预算)为什么不自动修? → 规则堵不住需要推理的红线。硬塞一条正则规则是假的安全感,比不修更糟。
7. 边界与未做#
- 只有 LEAK 类自动产规则;BANNED / JUDGMENT 只分类上报。
- 数据源只接离线报告(
rubric_report.json),跟distill_fewshot同源。线上实时采集(每条请求跑 judge 判分进池)要给主链路加 LLM 调用,成本/延迟实打实,留文档化 hook 点未做。 - P1/P2 训练腿不做(无 GPU,CLAUDE.md 硬约束)。
--verify只做确定性误脱回归,P0 翻转验证靠重跑run_rubric.py。