AI 编码合并前风险登记:merge 之前要追踪的条目

专门给 AI 生成代码准备的合并前风险登记册:LLM 不会主动提示的七类风险、每个 PR 一行的风险备注、什么情况阻塞合并、什么情况只是警告,以及高风险条目上线前必须写好的灰度与回滚方案。

statusreviewedpublished2026-03-10last-review2026-11-06read10 分钟topic合并前风险登记author编辑部policy编辑政策

为什么 AI 代码需要单独的风险登记

目的不是再多一个模板,而是别再在生产环境里被吓一跳。常见的情形是:某个 AI 生成的 PR 上线了团队里谁都不会手写上线的东西,接着又来一个,直到团队不得不承认一个事实:助手和评审者对"风险"的权衡完全不是同一套。助手优化的是"看起来像样、能编译通过"的代码;合并它的人要优化的是"合并之后晚上还能不能睡着"。

风险登记的做法,是在每一个 AI 协作过的 PR 上写一行风险备注。不是文档,就是一句话:这次改动可能破坏什么,作者为此做了什么。这一句话就是整份登记册。它写在 PR 描述里,被 CI 强制要求,也是评审时最先读的那一行。

模型不会主动标出的七类风险

七个分类。每个 PR 在写风险备注之前,都要对照这七类逐一过一遍:

那一行风险备注到底长什么样

格式就是下面这样。它故意写得很无聊。无聊,才能在赶时间时一眼读完。

Risk: 新增一个接收用户输入 ORDER BY 的 GET 接口——SQL 注入类风险。Mitigation: 对列名做白名单。

就这么一句。一个分类提示、具体可能出什么事、为此做了什么。如果作者写不出这一句,这个 PR 就还没准备好。如果缓解措施写的是"无",这个 PR 同样还没准备好——"无"是一个信号,不是一条捷径。

绿、黄、红:合并门禁矩阵

每个 PR 根据七类风险被打上一个颜色,颜色决定接下来会发生什么:

作者写备注,评审者负责质疑

不可谈判的一条:是谁对着 AI 下的 prompt,就由谁来写风险备注。不是评审者,不是模型,不是机器人。作者才有上下文——他向模型要了什么、模型返回了什么、他又基于信任接受了哪些部分。评审者的工作重心也随之转变:从"挑出 bug"变成"质疑风险备注"。作者是不是漏了一类?缓解措施是真做了,还是只是口头理想?标成黄色的是不是其实该是红色?

这个翻转很关键。从风险备注读起的评审者,比从 diff 读起的评审者抓到的问题更多——因为备注把作者的推理过程暴露出来了。备注写得敷衍,代码通常也敷衍。

让 AI 自己先把风险说出来

先让模型写风险,再让它写实现。prompt 是这样:在写任何代码之前,列出这次改动最高的三个风险,以及你会怎么缓解每一个。它给出的答案质量不稳定,但这个动作迫使模型去审视这次改动,而不是一上来就开始吐代码。把它给的这三行贴在草稿注释里,再对照着写真正的那一行风险备注。

这里有一个陷阱,也正是这份登记册存在的全部理由。AI 生成的代码看起来往往风险很低。变量名温和、注释礼貌、函数短小。人类读者会根据"语气"去模式匹配。这份登记册就是对抗这种偏见的强制机制:不管代码看起来多友好,风险备注照写不误。

示例:没做租户隔离的用户搜索接口

拿一个 PR 来看:它新增了一个 GET /api/users/search?q= 接口。代码写得很干净,测试全部通过,助手用了参数化的 LIKE 查询,看起来挺像样。作者包了一层薄薄的 wrapper 就把 PR 提上来了。

是写风险备注这个动作抓住了它。先是踩中安全,接着是性能,然后是合规——作者在第三类上停下来想了想,才意识到这个查询根本没按租户做作用域隔离。任何登录进任何账号的人,都能搜到系统里的每一个用户。代码严格按照写的那样运行,同时也是一起随时会爆发的跨租户数据泄露。风险备注把它标成红色。修复只有两行。那起没有发生的事故,正是登记册要拦下的东西。

缓解措施就是上线方式:高风险条目的灰度与回滚设计

不是每一条风险都能用两行代码关掉。回填脚本、计价逻辑、新的通知发送方——这类改动代码可以完全正确,发布过程照样能伤到用户。这时诚实的缓解措施是“怎么上线、怎么叫停”,而“我们会密切监控”不算缓解措施。

判断标准很简单:改动只要碰到四件事之一,就按高风险处理——钱、身份、落盘数据、用户触达(邮件、通知)。其他改动通常挂在 feature flag 后面加轻量监控就够了。对这四类,风险备注里的 Mitigation 要指向规格里的上线章节;这四类里被标成红色的条目,在上线章节写出来之前一直是红色。计划的分量要匹配爆炸半径:坏掉的 UI 组件影响一个按钮,坏掉的账单迁移可能影响好几周的收入确认。

上线前检查

这些在部署之前做,任何一项不通过,发布就不走:

分阶段灰度,每一步都有带数字的 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 个灰度退回到仅内部用户。

基线要取“上周同一天”,不要取“上周平均”,否则日内正常波动不是触发假回滚,就是掩盖真问题。阈值也要让产品看过:只由研发写的止损线,可接受的错误率往往不是太保守就是太宽松。

把回滚按类型写清楚

“回滚”在物理层面差别很大,响应时间能差几个数量级:

如果回滚落在最后两档,那就没有回滚,只有 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