AI 编码合并前风险登记:merge 之前要追踪的条目
专门给 AI 生成代码准备的合并前风险登记册:LLM 不会主动提示的七类风险、每个 PR 一行的风险备注、什么情况阻塞合并、什么情况只是警告,以及高风险条目上线前必须写好的灰度与回滚方案。
为什么 AI 代码需要单独的风险登记
目的不是再多一个模板,而是别再在生产环境里被吓一跳。常见的情形是:某个 AI 生成的 PR 上线了团队里谁都不会手写上线的东西,接着又来一个,直到团队不得不承认一个事实:助手和评审者对"风险"的权衡完全不是同一套。助手优化的是"看起来像样、能编译通过"的代码;合并它的人要优化的是"合并之后晚上还能不能睡着"。
风险登记的做法,是在每一个 AI 协作过的 PR 上写一行风险备注。不是文档,就是一句话:这次改动可能破坏什么,作者为此做了什么。这一句话就是整份登记册。它写在 PR 描述里,被 CI 强制要求,也是评审时最先读的那一行。
模型不会主动标出的七类风险
七个分类。每个 PR 在写风险备注之前,都要对照这七类逐一过一遍:
- 安全(Security)。密钥被注入泄露、不安全的
eval、未经校验的输入跑到 shell 里、鉴权被某个辅助函数绕过。模型在本该用 parser 的地方,顺手就伸向了exec。 - 数据丢失(Data loss)。数据库写入、破坏性迁移、没带
WHERE的DELETE、文件覆盖、schema 被 drop 掉。数据是一扇单向门。 - 性能(Performance)。N+1 查询、没有上界的循环、热点路径上的同步调用、没做分页。演示数据集上很快,真实数据集上是灾难。
- 正确性(Correctness)。悄无声息的错误结果。差一错误、正负号写反、舍入规则错、异常被吞掉。代码照样跑,结果微妙地不对。
- 依赖(Dependency)。新引入的包、陌生的包、每周下载量只有十次的包。助手几乎什么都敢 import。
- 合规(Regulatory)。PII、GDPR、HIPAA、PCI,凡是碰到支付的一切。模型并不知道你在哪个司法管辖区。
- 运维(Operational)。新队列、新 cron、新 webhook——任何现在就需要被监控、但目前还没人盯着的东西。
那一行风险备注到底长什么样
格式就是下面这样。它故意写得很无聊。无聊,才能在赶时间时一眼读完。
Risk: 新增一个接收用户输入 ORDER BY 的 GET 接口——SQL 注入类风险。Mitigation: 对列名做白名单。
就这么一句。一个分类提示、具体可能出什么事、为此做了什么。如果作者写不出这一句,这个 PR 就还没准备好。如果缓解措施写的是"无",这个 PR 同样还没准备好——"无"是一个信号,不是一条捷径。
绿、黄、红:合并门禁矩阵
每个 PR 根据七类风险被打上一个颜色,颜色决定接下来会发生什么:
- 绿色。七类里一类都没踩中。在有测试覆盖的模块里做纯重构、改日志、改文档。正常合并。写一行 Risk: none 仍然欢迎。
- 黄色。踩中一到两类,有明确的缓解措施。写好风险备注,走正常评审。大多数真实 PR 都属于这一档。
- 红色。任何破坏性的数据操作、任何新增的鉴权面、任何触碰合规的改动,或者一次性踩中三类及以上。阻塞合并。重新设计、拆分、或者升级上报。红色绝不允许在评审行内直接豁免。
作者写备注,评审者负责质疑
不可谈判的一条:是谁对着 AI 下的 prompt,就由谁来写风险备注。不是评审者,不是模型,不是机器人。作者才有上下文——他向模型要了什么、模型返回了什么、他又基于信任接受了哪些部分。评审者的工作重心也随之转变:从"挑出 bug"变成"质疑风险备注"。作者是不是漏了一类?缓解措施是真做了,还是只是口头理想?标成黄色的是不是其实该是红色?
这个翻转很关键。从风险备注读起的评审者,比从 diff 读起的评审者抓到的问题更多——因为备注把作者的推理过程暴露出来了。备注写得敷衍,代码通常也敷衍。
让 AI 自己先把风险说出来
先让模型写风险,再让它写实现。prompt 是这样:在写任何代码之前,列出这次改动最高的三个风险,以及你会怎么缓解每一个。它给出的答案质量不稳定,但这个动作迫使模型去审视这次改动,而不是一上来就开始吐代码。把它给的这三行贴在草稿注释里,再对照着写真正的那一行风险备注。
这里有一个陷阱,也正是这份登记册存在的全部理由。AI 生成的代码看起来往往风险很低。变量名温和、注释礼貌、函数短小。人类读者会根据"语气"去模式匹配。这份登记册就是对抗这种偏见的强制机制:不管代码看起来多友好,风险备注照写不误。
示例:没做租户隔离的用户搜索接口
拿一个 PR 来看:它新增了一个 GET /api/users/search?q= 接口。代码写得很干净,测试全部通过,助手用了参数化的 LIKE 查询,看起来挺像样。作者包了一层薄薄的 wrapper 就把 PR 提上来了。
是写风险备注这个动作抓住了它。先是踩中安全,接着是性能,然后是合规——作者在第三类上停下来想了想,才意识到这个查询根本没按租户做作用域隔离。任何登录进任何账号的人,都能搜到系统里的每一个用户。代码严格按照写的那样运行,同时也是一起随时会爆发的跨租户数据泄露。风险备注把它标成红色。修复只有两行。那起没有发生的事故,正是登记册要拦下的东西。
缓解措施就是上线方式:高风险条目的灰度与回滚设计
不是每一条风险都能用两行代码关掉。回填脚本、计价逻辑、新的通知发送方——这类改动代码可以完全正确,发布过程照样能伤到用户。这时诚实的缓解措施是“怎么上线、怎么叫停”,而“我们会密切监控”不算缓解措施。
判断标准很简单:改动只要碰到四件事之一,就按高风险处理——钱、身份、落盘数据、用户触达(邮件、通知)。其他改动通常挂在 feature flag 后面加轻量监控就够了。对这四类,风险备注里的 Mitigation 要指向规格里的上线章节;这四类里被标成红色的条目,在上线章节写出来之前一直是红色。计划的分量要匹配爆炸半径:坏掉的 UI 组件影响一个按钮,坏掉的账单迁移可能影响好几周的收入确认。
上线前检查
这些在部署之前做,任何一项不通过,发布就不走:
- 集成测试在接近生产规模的数据量下通过:至少是生产行数的 10%,而不是 100 行的 fixture。七类风险里“演示数据上很快、真实数据上是灾难”的性能问题,只有在真实规模下才会暴露。
- 迁移脚本在生产快照上 dry-run,耗时在维护窗口预算之内。
- 回滚流程至少被非作者的人在 staging 端到端跑过一次。
- On-call 已知道发布窗口,runbook 已经打开放在手边。
分阶段灰度,每一步都有带数字的 gate
Stage 1: 仅内部用户(≤50 个账号) soak 24h Stage 2: 1% 客户流量 soak 6h,检查 SLO Stage 3: 10% 客户流量 soak 12h,检查 SLO Stage 4: 50% 客户流量 soak 2h Stage 5: 100% 监控 72h 后再宣布稳定
每个 gate 都是一个决策点,由一个具体的人(不是“团队”)决定继续、按住还是回滚。很多高风险发布出问题,不是因为没有监控,而是报警响了却没人确定自己有没有权限叫停。所以每一阶段都要写明:灰度人群、看哪个监控面板、谁有权暂停、暂停通知发到哪里、满足什么条件才恢复,以及恢复后是否沿用原来的灰度节奏。这些写进规格,不要只写在发布群里——值班同学不该去翻聊天记录找决策。
止损阈值写成数字
“感觉不对就回滚”这种止损,凌晨三点会被任何想回去睡觉的人推翻。写数字:
| 信号 | 叫停阈值 | 动作 |
|---|---|---|
| 变更端点的 5xx 率 | 连续 10 分钟超过基线 3 倍 | 回滚。 |
| p95 延迟 | 持续 15 分钟超过基线 2 倍 | 回滚。 |
| 受影响漏斗的转化率 | 相对“上周同一天”下降超过 15% | 回滚。 |
| 数据不变量(如订单总额 ≠ 明细金额之和) | 出现任何不一致 | 立即回滚。 |
| 重复副作用(重复扣款、重复发送) | 任何一次确认的重复 | 回滚写路径,开 incident review。 |
| 相关客服工单 | 一小时内 5 个 | 灰度退回到仅内部用户。 |
基线要取“上周同一天”,不要取“上周平均”,否则日内正常波动不是触发假回滚,就是掩盖真问题。阈值也要让产品看过:只由研发写的止损线,可接受的错误率往往不是太保守就是太宽松。
把回滚按类型写清楚
“回滚”在物理层面差别很大,响应时间能差几个数量级:
- Feature flag 开关:秒级,一分钟左右用户回到旧行为。
- 配置变更:1 到 5 分钟,前提是配置能热加载、不需要部署。
- 代码 revert 并重新部署:5 到 20 分钟,取决于 CI。旧代码必须还能兼容新代码已经写入的数据。
- 暂停任务:时间不固定。能停掉新任务,在途任务可能还要善后。
- 数据库回滚或修复:小时级到天级,实际上多半只能 forward-fix。
如果回滚落在最后两档,那就没有回滚,只有 forward-fix,风险备注里要明说。数据库变更用 expand-contract:新列先加成 nullable,回填,再切换读写路径,最后在单独一次发布里删旧列,每一阶段都能单独回退。AI 生成的 diff 如果把迁移和依赖它的代码放在同一个 PR 里,把它拆成这几个阶段本身就是缓解措施。feature flag 要同时覆盖代码路径和数据迁移,否则没法局部回滚。如果这次发布没法描述成一串向前兼容的阶段,就该安排维护窗口。
值班同学凌晨三点能照做的 runbook
规格里链接 runbook,它要不加解读就能回答四个问题:现在怎么判断发布是否正常(具体面板和指标);“健康”是什么数值区间;不健康时第一步做什么(具体命令或按钮);拿不准时 page 谁(具体的人或值班组,带响应时限)。检验标准是新来的 on-call 不用 page 别人就能执行完。回滚还必须演练过,从没跑过的回滚,第一次执行往往就在事故现场,而且总会出岔子。
有些回滚根本不是技术动作。新计价引擎上线后,撤代码撤不掉已经生成的发票,可能需要红冲、审计记录和客户沟通口径。这些要在上线前写好,事故时没人想现场补会计政策和客服话术。发布结束后再补一行结果:是否触发过暂停、哪个阈值最敏感、下次灰度要不要调顺序。
这样一来,高风险条目的风险备注会写成这样:
Risk: AI 生成的回填脚本会改写已有行的 role——数据丢失类风险。Mitigation: 分两次发布做 expand-contract,附 dry-run 行数 diff,flag role_backfill_v2 可暂停任务,任何不变量校验失败即叫停;已写入的行只能 forward-fix。
给登记册本身写 Given/When/Then 验收标准
这份登记册只有作为 CI 检查才管用,作为"礼貌建议"是没用的。可以这样把它规格化:
Given an AI-assisted pull request is opened When the PR description does not contain a line matching "Risk:" Then CI fails with a message pointing to the risk register policy Given a PR is marked red by the risk matrix When the author attempts to merge without a linked redesign issue Then the merge is blocked until a reviewer with the risk-gate role approves Given a PR passes all seven category checks with no trips When the author writes "Risk: none" Then the PR may proceed through normal review at yellow-equivalent speed
这个检查本身故意写得很笨。它只是在 PR 正文里查字面字符串 Risk:。判断本身仍然由人做。这扇门只是拒绝让你跳过做判断这件事。
每周一次横扫:把登记册当成数据集来读
每周固定一次(比如周五)扫一遍本周的所有风险备注——不是 PR,是备注本身。规律出现得很快。如果本周黄色备注里有一半都写了"N+1",那说明团队有的是查询层问题,而不是评审问题。如果"新依赖"反复出现,就该有人来搞一个包选型策略了。如果某个团队的合规类风险从来没出现过,他们要么是运气好,要么就是没在看。这样一来,登记册就不再只是文书工作,而变成了一个弱信号传感器。它的价值也正是在这里兑现——不在任何单个 PR 上,而在聚合视角里。登记册和它所守护的代码遵循同一条原则:如果它不能帮任何人做出决定,它就不该存在。
可以直接放进 PR 模板的风险登记
风险登记不需要长。它只要逼着作者把“哪里可能出事”写成一句可检查的话。AI 生成的 PR 最需要这一格,因为模型通常会解释自己做了什么,却不会主动说自己没验证什么。
AI risk register: - Scope risk: touched files outside src/billing/**? no - Data risk: migration changes existing rows? yes, backfill dry-run attached - Contract risk: public response shape changed? no, schema diff attached - Rollback risk: feature flag billing_proration_v2 can disable new path - Evidence gap: load test not run against production-size invoice table
边界:不要把低风险文案 PR 也塞满风险表。只要改动碰到数据、权限、钱、外部 API、迁移、后台任务或用户可见契约,就必须登记。
可复制产物:AI 编码评审包
在 AI 生成 diff 进入代码评审前使用。它把提示词范围、允许变更和证据要求合并成一个可审查产物。
AI 编码评审包:AI 编码合并前风险登记:merge 之前要追踪的条目 本次要做的决策: - 确认 AI 只在批准范围内生成变更,并为每条验收标准提供证据。 责任人检查: - 产品责任人: - 工程责任人: - QA 或运维评审: 范围边界: - 本次包含: - 本次不包含: - 仍需确认的假设: 验收证据: - 测试或 fixture: - 日志、指标或截图: - 人工复核步骤: AI 边界:生成变更必须留在书面范围内,每条验收标准都要能找到证据。 评审追问: - 没参加需求会的人还会误解哪里? - 哪个证据能证明这次改动足够安全,可以发布?
AI 生成改动的风险登记示例
AI 代码的风险说明要写出模型可能“脑补”的具体行为。“看起来没问题”不是评审证据。
| 风险 | AI 为什么容易漏 | 合并门禁 |
|---|---|---|
| 搜索接口返回跨租户用户。 | Prompt 只写了 admin search,没有写 tenant 边界。 | 使用两个 tenant fixture 的权限测试。 |
| 迁移脚本把 null role 回填成 admin。 | 模型从样例数据里推断了默认值。 | migration dry-run 和 row-count diff。 |
| 重试循环重复发送通知。 | 生成代码重试的是副作用,不是幂等操作。 | replay 测试证明每个 event_id 只发一次。 |
案例:AI 生成的租户数据泄漏
生成代码通过了所有 happy path 测试。风险登记表要求评审人写出哪些数据可能跨账号流动,因此发现了缺失的 tenant 边界。
合并前风险说明: - 改动:给 support console 增加用户搜索 endpoint - AI 生成查询:SELECT * FROM users WHERE email ILIKE $1 - 缺失边界:tenant_id 和 support role scope - 影响面:任何 support user 都可能跨租户搜索 - 必需证据: - cross-tenant denial test - audit log 包含 actor_tenant_id - query plan 显示 tenant_id predicate - reviewer 确认允许的 support roles