Appearance
Brownfield 实战三:验证、Review 与可追责交付
系列第 11 篇
实战节点:review-correction→final
核心问题:测试通过以后,怎样建立足够但不夸大的交付证据
最后核验:2026-07-20
对应视频:第 01 集(第三、四章)《AI 编程实战:让 Codex 从需求走到可验证交付》
实现阶段结束时,订单服务有 16 个测试通过,状态不变量已经移动到 Order.cancel()。但“测试绿了”仍不是交付的全部条件:测试可能断言错对象,Diff 可能包含无关改动,本地 MockMvc 可能没有覆盖真实进程,文档可能暗示已经创建远程 PR,内存实现也不能证明生产并发正确。
可靠交付需要三种活动相互制衡:验证用确定性工具观察行为;Review 尝试反驳实现和测试;交付说明把已验证、未验证、风险和责任写清。Agent 可以扩大检查范围,但不能把它没有观察到的状态写成事实。
阅读路线:先看全局,再进入机制
视频版先展示整章地图,再逐层展开。博客沿用同一判断顺序,但保留更多机制、案例、失败模式和生产边界:
- Diff 审查:从范围、行为、结构和风险逐层缩小审查对象。
- 验证金字塔:组合领域测试、Service 测试、MockMvc 与真实 HTTP 观察。
- 证据链:保留 baseline、红灯、初次实现、审查修正和最终状态。
- 责任边界:区分本地完成、远程 PR、CI、部署和生产事实。
这四步不是目录装饰,而是一条决策链:Diff 审查 → 验证金字塔 → 证据链 → 责任边界。阅读后面的案例时,可以随时回到这条链判断当前问题发生在哪一层。
真实问题:为什么“BUILD SUCCESS”仍可能交付错误代码
测试成功只说明当前测试套件对当前实现没有报告失败。若测试是在实现后照着代码生成,可能同时复制同一个误解;若断言内部方法而非业务结果,重构困难且覆盖虚假;若只运行聚焦测试,Spring 对象图可能已坏;若 Agent 为绿灯删除旧测试,成功反而是风险信号。
Review 也可能退化为格式检查。实现 Agent 已经投入一个方案,同会话自审容易解释自己的选择,而不是寻找反例。更有效的 Review 从任务契约开始,检查范围、状态分支、测试检测能力、安全性能和证据边界,最后才处理命名。
机制:先区分证据等级,再决定能声称什么

“源码已经改”只证明存在变更;聚焦测试证明局部行为;全量测试增加回归证据;启动真实应用并走 HTTP 证明用户路径;部署后健康检查才证明目标环境运行。证据越高,成本和环境影响也越大。
结论必须止于实际观察。本项目完成了本地源码审查、mvn test 和真实进程 HTTP Smoke,因此可以说本地订单创建与取消路径工作;没有远程仓库、PR、数据库和部署,就不能说“PR 已就绪”“线上可用”或“生产验证通过”。
证据等级不是越高越好。一个教程任务不应为了获取远程部署证据擅自创建资源;生产发布则不能用本地单测代替目标环境验证。任务契约和授权共同决定需要到哪一级。
Diff Review 的顺序决定能否先发现高价值问题

推荐顺序:先读任务和非目标;看 git diff --stat 判断改动面;按业务状态和错误路径审核心逻辑;再审测试是否真能失败;检查安全、性能、兼容和并发;最后处理风格。若先陷入变量名,Reviewer 的有限注意力会被低风险问题耗尽。
本例 baseline..final 是 19 个文件、415 行新增、5 行删除,文档和测试占多数。统计本身不判定好坏,但提醒 Reviewer 检查:为什么有两个新异常;接口新增查询影响哪些实现;Controller 是否只做适配;是否真的没有依赖和数据库变化。
高信号命令包括:
bash
git diff --stat baseline..final
git diff baseline..final -- src/main/java
git diff baseline..final -- src/test/java
git status --short
mvn test分开读生产代码和测试有助于比较行为是否一致,最终空工作树证明交付没有遗漏未记录文件。
真测试必须有能力推翻错误实现

一个测试是否有价值,可以问:把实现改成一个合理但错误的版本,它会失败吗?例如将 PAID 也改为 CANCELLED,Domain 和 Service 测试应失败;每次重复取消都保存,保存计数测试应失败;404 映射成 500,Web 层错误测试应失败。
本例还有真实红灯节点:在生产能力不存在时,新增测试编译失败;实现后同一测试通过。这比最终测试数量更强。但仍要检查测试是否遗漏。MockMvc 覆盖首次与重复取消,PAID 的 409 没有通过公开 HTTP 构造,因为系统没有支付入口;对应 Domain 和 Service 行为有覆盖,协议映射代码存在,但真实 PAID HTTP 路径没有端到端观测。交付说明必须保留这一差异。
测试不应只断言 Order.cancel() 被调用,因为错误状态结果仍可能通过;它断言状态、异常、对象身份与保存次数。Web 测试断言状态码和 JSON,而不是 Controller 私有结构。
独立 Review 要主动寻找反证

实现 Agent 容易对自己的方案形成承诺。独立 Review 可以由另一个会话、另一个 Agent 或人完成,输入只给任务契约、仓库规则、基线与 Diff,让它重新构建问题。目标不是“检查代码有没有问题”这种宽泛指令,而是提出可反驳检查:状态规则是否被所有调用者共享?幂等是否包含副作用?异常层次是否反向依赖?接口演进是否漏掉实现者?
这次 Review 找到一个非测试失败问题:初次实现把取消状态判断放在 Service。行为成立,但规则可能被其他用例绕过,于是移动到 Order.cancel() 并增加三个 Domain 测试。这说明 Review 的价值不只是找编译 Bug,也是在现有证据下改善责任边界。
独立不代表盲从第二意见。Reviewer 可能提出超范围抽象或误解业务,最终由 Owner 根据风险和契约决定接受、拒绝或继续实验。保留理由比机械采纳更重要。
订单取消的完整证据链

本次证据按时间连接:
text
baseline e081137
8 tests pass,证明起点稳定
failing-tests 4328c1f
10 个测试编译错误,证明新契约缺失
initial-implementation 9e4a379
13 tests pass,证明第一版行为成立
review-correction 0d3a019
Domain 拥有不变量,16 tests pass
final 48582bb
真实 HTTP、文档、风险与本地边界记录证据链的价值是可追溯:为什么相信测试、为什么修改设计、最终结论从何而来。单个最终提交只能显示结果,不能证明测试曾经发现问题。
若团队不希望主分支保留教学式提交,也可以在工作分支保存节点,合并时 Squash;验证记录仍应引用可复现的 CI Run、测试报告或临时标签。
PR 描述应是团队理解变更的接口

一份有效 PR 描述说明为什么改、怎样解决、改了哪些职责、运行了什么、还有哪些限制、怎样撤回。它不是把 Diff 翻译成“新增若干文件”。本项目的本地 PR 草稿写明:增加 PAID/CANCELLED、Domain 不变量、查询与应用编排、取消路由、404/409;验证为 16 测试和 HTTP Smoke;限制是内存存储、无并发 CAS、无认证和支付入口;回滚到 baseline。
Agent 可以生成初稿,但 Owner 必须核对所有事实。例如“所有场景已覆盖”不准确,因为没有真实 PAID HTTP;“向后兼容”需要限定为现有创建接口与测试,而非未知外部客户端。把限制写清不会削弱交付,反而让后续决策建立在真实边界上。
本项目没有远程 PR,所以文件名是 docs/pr-description.md,状态明确为本地草稿。语言应避免让读者误以为外部动作已发生。
CI、Review Agent 和人类分别擅长什么

CI 擅长可重复的编译、测试、格式、扫描和构建;Review Agent 擅长扩大代码搜索、发现候选遗漏和比较规则;人工 Reviewer 判断业务语义、架构权衡、风险接受和组织上下文;分支保护强制流程;Owner 承担最终批准。
不要让 Review Agent 成为合并授权者,也不要让人类重复阅读机器已经稳定检查的格式。将确定性问题前移到 CI,让 Reviewer 把注意力放在状态、兼容和风险。Agent 的评论同样需要证据和优先级,不能以数量衡量价值。
对于高风险变更,可让实现和 Review 使用不同上下文或模型,再由人类处理分歧;但第二个 Agent 不会自动独立,如果它读取了第一份结论,也可能形成相同确认偏差。
失败应该回到产生问题的阶段

CI 编译失败通常回到实现;Review 发现状态模型错误要回到计划甚至任务契约;需求变化要重新确认验收;本地与 CI 环境不一致要修复可复现性。所有失败都追加一句 Prompt,会在错误上下文里继续打补丁。
回退不等于清空工作。Git 节点让我们能回到最后可信状态,保留失败证据,再用更小实验前进。若 Review 认为 Domain 设计错误,可以从 initial-implementation 比较修正,而不必丢失已经通过的行为测试。
完成条件也应允许“受阻”结果。如果需要生产凭据、远程授权或业务决策,Agent 应报告当前证据和缺口,不应伪造一个完成叙事。
本地验证与远程事实必须明确分界

本地启动端口 18081 后,实际观察到:创建返回 201、Location: /api/orders/{uuid} 和 CREATED;首次取消返回 200/CANCELLED;重复取消仍返回 200/CANCELLED;随机不存在 UUID 返回 404/ORDER_NOT_FOUND。进程随后被正常停止。
这些结果比 MockMvc 多证明了一层:打包后的应用能启动,真实网络栈和 JSON 组合成立。但它仍使用内存 Repository 和本机环境,没有 TLS、网关、认证、数据库和并发流量。Smoke 不是负载测试,也不是部署证明。
计划明确不创建远程仓库和不发布,因此最终状态是 local-only。需要发布时应另行授权,并建立远程 CI、环境健康、迁移、回滚和监控证据。
最终责任链不会因为代码由 AI 生成而消失

Agent 负责提出和执行候选动作;测试、Git 和 HTTP 工具产生确定性记录;Reviewer 尝试发现错误;Owner 接受风险并批准;组织规定数据、权限、合并和审计政策。模型无法成为法律或业务责任主体,代码生成来源不改变仓库所有者的义务。
这也意味着“Agent 说完成了”只是一个待验证事件。Harness 可以把停止条件绑定测试和 Diff,但最终交付仍需 Owner 判断任务是否完整、证据是否足够、限制是否可接受。高自治系统更需要清晰责任,而不是更少。
审计记录应能回答:谁发起任务、Agent 获得什么权限、改了哪些提交、运行哪些检查、谁 Review、谁批准、是否发布。没有这条链,事故后只能复盘自然语言聊天,难以定位控制失效。
完成定义是一组多维门禁

本例的完成定义包括:四类取消行为与创建兼容;无数据库和无关依赖;16 个测试通过且红灯可复现;本地 HTTP 主要路径通过;Diff 责任清楚;并发、持久化与远程状态写明;最终 Git 工作树干净。每一项都可以由证据或 Review 判断。
不同项目门禁不同。UI 功能需要截图和交互;性能任务需要基准与环境;数据库迁移需要前后兼容和回滚;安全修复需要攻击复现与扫描。通用的不是固定命令,而是“声明必须与观察对应”。
失败模式
失败一:只报告测试数量
数量不说明测试语义、红灯和覆盖层级。说明哪些行为、失败前后和未覆盖边界。
失败二:同一 Agent 在同一上下文自我确认
实现解释会压制反例。使用新上下文从任务和 Diff 独立重建判断。
失败三:Review 先挑风格
优先检查范围、状态、错误、测试真实性和风险,再处理命名格式。
失败四:本地成功写成“生产可用”
测试、真实 HTTP、远程 CI 和部署是不同等级。只陈述实际执行的最高等级。
失败五:PR 描述隐藏限制
把并发和持久化边界写清,避免下游在错误假设上批准。
失败六:Agent 自动创建远程资源
代码任务不默认授权建仓、提 PR、部署或发布。外部副作用需要明确请求和审计。
失败七:测试失败后改掉期望
除非任务契约改变,否则优先修实现;任何删除或放宽旧测试都需要理由和 Review。
生产边界
本地教程验证不包含身份、Secret、供应链扫描、持久化事务和远程环境。生产交付必须根据风险增加 SAST/SCA、权限、数据库集成、并发测试、可观测性、灰度和回滚。不能因为 Agent 快速完成代码而跳过组织门禁。
Review Agent 读取外部 Issue、代码和日志时仍面临 Prompt Injection;它的评论是候选意见,不是安全证明。高风险批准应由具备业务权限的人完成,并保留审计。
实践清单
- [ ] 按源码、聚焦测试、全量测试、真实路径和部署区分证据等级。
- [ ] 从任务契约和 Diff 范围开始 Review,而不是从风格开始。
- [ ] 检查测试在错误实现下是否真的会失败。
- [ ] 保存基线、红灯、初次实现、Review 修正和最终节点。
- [ ] 用独立上下文尝试反驳实现和测试。
- [ ] PR 说明包含问题、方案、范围、验证、风险和回滚。
- [ ] 将 CI、Review Agent、人工 Reviewer 和 Owner 的责任分开。
- [ ] 根据失败类型回退到任务、计划、实现或环境阶段。
- [ ] 清楚标记本地、远程和已发布状态。
- [ ] 最终确认 Git 状态、证据文件和未验证边界。
读完之后,你应该能完成什么
- 能判断测试是否因正确语义通过。
- 能组织与风险匹配的验证层级。
- 能写出可被第三方复核的 PR 证据。
- 能准确声明已验证与未验证的交付边界。
如果只能复述概念,却不能完成上述动作,说明还没有把内容转化成工程能力;可以回到对应机制图、失败模式和实践清单重新核对。
小结
可验证交付不是在代码生成后加一行“测试通过”,而是建立一条能被第三方复查的信任链:基线证明起点,红灯证明测试有效,绿灯证明当前行为,Review 挑战设计,真实路径扩大证据,交付说明限定结论,Owner 承担决定。Agent 可以让这条链更快形成,却不能替代证据或责任。
实战资料
- 最终代码:
48582bb/final - 自动测试:16 tests,0 failures,0 errors,0 skipped
- 本地 HTTP:201、200/CANCELLED、重复 200/CANCELLED、404/ORDER_NOT_FOUND
- 验证记录:
demo/order-service/docs/verification-evidence.md - PR 草稿:
demo/order-service/docs/pr-description.md
