概述
AI 编码时代,AI 执行得再快,也从不主动问"你到底要什么"------它只会自信地猜。本篇讲契约层如何把"怎么做"写进文件:用真实事故(改一处字段裂 19 处、22 个接口静默 500)说明口头约定的代价,给出 Spec 三栏、数据契约硬边界、FMEA 风险审计、三轨制三权分立四种载体,并演示路由门禁如何把接口对齐从"靠人 review"变成"机器能回答的确定性问题"。附第一天就能用的五步清单。
契约层:从规格到代码------为什么"怎么做"必须写下来
AI Harness Engineering 系列 · 第四篇 · 王海涛(GitHub:derekwang85 · 腾讯云 TVP / 架构师名人堂)

一百多年前,弗雷德里克·泰勒做了件让整个工业界震动的事:他走进车间,把老师傅的手艺拆成标准工序,写下来,然后让任何人都能按着工序做出同样的东西。福特接着把这件事推到极致------流水线上没有"老师傅",只有写好的作业指导书。后来的事我们都知道了:手工业时代的"看人下菜",被工业时代的"看单下料"取代,产量差了不止一个数量级。
泰勒没有发明新的手艺,他只是证明了"把怎么做写下来"本身,就是生产力。
AI 编码时代,这个道理换了一副面孔重新出现。我们面对的 AI,是一个学习能力极强、执行力惊人、但永远不会主动问"你到底要什么"的员工。你让它"给这个接口加个超时",它不会追问是连接超时还是读超时,它只会选一个它觉得合理的,然后自信地把代码写完。问题不在于它选错了------人也会选错------而在于它选错的方式:它从不认为自己在猜,它认为自己在执行。
上一篇文章讲了架构层:决策即边界,解决"为什么这么定"。这一篇往下走一层,讲契约层:怎么做、怎么稳定。把"怎么做"从对话里搬进文件里,让 AI 照着契约施工,而不是照着印象施工。
一、对话里没有契约

先说一个我在 TradeOMS 里踩过的真实事故,它把"没有契约"的代价摆得明明白白。
那个交易系统里有 CDP 模块。一次修改触发涟漪分析后,扫出 19 处同根问题:后端用 @JsonProperty("items") 序列化响应,19 个测试文件和前端代码却全在读 data.content。后端改了字段名,前端和测试还用旧名------改一处,裂 19 处。
这种事故你在传统工程里也见惯,通常叫"接口没对齐"。可在 AI 编码项目里,性质完全不同:
- 人改接口,至少记得"我改过这个",下次见到旧名会警觉;
- AI 改接口,只是在执行"把 items 改成 content"这条指令,没有"历史记忆"提醒它"前端还在用旧名"。
第二类事故才更麻烦。那次涟漪扫描覆盖 218 个前端 API 调用,发现 22 个后端接口在返回 500------Controller 的签名改了,前端没跟上。22 个接口,没有任何人知道它们坏了,直到扫描把它们翻出来。
这两类事故有一个共同的根:前端和后端之间、代码和测试之间,只存在"口头约定",不存在"书面契约"。 口头约定靠人记,书面契约靠文件校验。AI 项目里,人记不住的东西,AI 更记不住。
公元前 1754 年,汉谟拉比做了件开创性的事:把法律从"长老口传"变成刻在石柱上的成文法。人类最早的成文法典,说白了就是最早的契约层------把"怎么做"从人的记忆搬到不可漂移的载体上。三千七百年后,AI 编码撞上的是同一个问题,只是"人记不住"换成"AI 记不住","刻在石柱上"换成"写进 Spec 和数据契约文件里"。
二、Spec 契约:把"怎么做"写进契约

契约层的第一种载体,是 Spec------规格文件。它不是给 AI 的说明书,是给 AI 的契约。说明书可以含糊,契约不行。
一份 Spec 至少要有三样东西,缺一样,AI 就会用它的"常识"填空:
范围。 这次到底做不做这个边界。没写范围,AI 会顺手把隔壁模块也"优化"了,因为它觉得这样更合理。
假设。 哪些前提我默认成立。这一栏最容易被忽略,也最值钱。缺了假设,AI 会用它的"常识"填补你的空白------而 AI 的"常识"来自互联网,不是来自你的业务。我在第 7 篇讲过那个缓存事故:给被限流的接口加缓存,就是假设栏缺位的结果------没写"这个接口慢是因为上游限流",AI 就默认"慢=该加缓存"。
验收标准。 做到什么程度算完成。没有验收标准,AI 写完了你没法说"对"还是"不对",只能靠感觉。靠感觉验收,是 AI 项目里最贵的一种验收方式。
Spec 的价值在于,它把"怎么做"从"对话里的共识"变成"文件里的契约"。对话会漂移,契约不会。下一次会话的 AI 打开仓库,读到的不是上一轮对话的模糊印象,而是白纸黑字的范围、假设和验收标准。
三、数据契约:接口签名是硬边界

契约层的第二种载体,是数据契约------接口签名、DTO 结构、字段命名。这是整个约束体系里最接近"物理定律"的一层:它在编译期或运行期就能被验证,不需要任何"理解"。
在 TradeOMS 里,这套数据契约长成一个目录:docs/15-api-contracts。每个接口的请求/响应签名、字段含义、约束条件,都落在文件里,AI 无权自行修改------它属于三级约束力里的 Level 1 硬约束,碰都不能碰。
光有文件还不够,真正的防线是把契约变成能自动校验的门禁。那次 22 个 500 端点的教训之后,我们建了一道路由门禁 check-api-route-consistency.py:前端调用的每一个 API 路径、每一个字段,自动跟后端实现对齐,不一致就拦下来。
结果很有意思。门禁上线后先扫出 233 条不匹配 ,修复过程中一度涨到 370 条------因为门禁在"暴露问题"而不是"掩盖问题"------最后归零。233 到 370 再到 0,这条曲线本身就是门禁价值的证明:问题不是不存在,是以前没人知道它存在。
那 22 个返回 500 的接口,门禁架起来之前是哪种"未知"?不是"已知的未知"------你列不出一张"这里 22 个问题待修"的清单,因为你根本不知道它们坏了。它们是未知的未知 (unknown unknown):连"有没有问题"这个问题都问不出来。人 review 只能问"我看到的地方对不对",问不出"我没看到的地方坏没坏"。数据契约是第一个让看不见的地方自己开口说话的载体------对上就是对的,对不上就是不对,于是未知的未知,第一次变成了已知的已知。
数据契约的存在,让"接口对不对"从"靠人 review"变成"机器能回答的确定性问题"。这在 AI 编码里是决定性的:AI 最擅长制造"看起来没问题"的错误,数据契约则是最不给面子的裁判------签名对不上就是对不上,没有商量余地。
德鲁克有句被引用了无数次的话: "被衡量的才能被管理。" 在 AI 编码的语境里,这句话要反着读才见真义:被管理的前提是能被衡量。 你不能 review 一个只有"感觉"的接口,但你可以校验一份契约文件。数据契约就是把"接口对不对"从感觉变成度量------这也是为什么它在三级约束力里属于碰都不能碰的 Level 1:它一旦可以商量,就回到了口头约定。
四、FMEA:契约自带的风险审计

契约层的第三种形态,很多人想不到:一份 Spec 应该自带风险审计,叫 FMEA(Failure Mode and Effects Analysis,失效模式与影响分析)。
做法不复杂。写 Spec 的时候,把"这个功能如果出错,会怎么错"一条条列出来,每条评估三个维度------严重性(对业务的影响)、发生概率、可检测性(当前门禁能不能抓住),三者相乘得到 RPN 值。RPN 超过 100 的,必须安排对策。
TradeOMS 的 24 份 Spec 里,FMEA 识别出 40 多个故障场景,RPN 超过 100 的全部处理掉了。几个代表性例子:
- KYC 模块:非授信客户查看授信客户信息,RPN 392------对策是权限注解 + 权限门禁;
- Quote 模块:外部汇率源不可用时直接暴露错误,RPN 294------对策是兜底汇率缓存 + 降级提示;
- Order 模块:并发修改同一订单,RPN 210------对策是乐观锁。
FMEA 的价值不在那张表,而在它改变了测试场景的来源:以前是"拍脑袋想测试",现在是"FMEA 驱动测试"。 每个 RPN 高的故障模式,都对应一条测试用例、一道门禁、一条操作 SOP。Spec 从"我要什么"变成"我担心什么、怎么防"------担心写下来,AI 才知道哪些边界不能碰。
五、三轨制:契约的三个载体

最后一种契约形态,是把"要造什么、修什么、对不对"分到三张表里,互相锁死。这套机制在 derekcoding 体系里叫三轨制:
- WBS:要造什么------任务拆解,绑定 Issue 和分支,双向可追溯;
- Issue Log:修什么------bug 与缺陷的唯一入口,每条都带回归关联;
- Test Case:对了吗------测试用例,374 条,每条都登记真实性(这条测试真的跑过吗,还是只是标记了 PASS)。
三轨制最狠的一条规矩是三权分立:报告人、修复人、验证人必须分离。 自己写代码、自己验收、自己打 PASS,等于没有契约。这不是人性问题------司法上本来就有条铁律:立案的不能同时是审判的,纪检查案更要避嫌,让被查的人自己给自己出结论,等于没查。三轨制抄的就是这套监督逻辑:报告人只把问题说清,修复人只把它改对,验证人只判它到底对了没。环节一旦合并,就从"自证清白"退回到"既当运动员又当裁判"------那 82 条 PASS 里 46 条没有任何代码变更,就是裁判和运动员是同一个 Agent 的必然结果。
三轨制的本质,是把"这个活到底干完没有"变成一个可以被审计的事实,而不是一个可以被宣称的状态。契约存在的意义就是:让"我说我干完了"和"证据证明我干完了"变成两件事。
六、活的注脚:契约在 derekinside 里长什么样
这个"把怎么做写下来"的契约层,在我那个本地知识库副脑 derekinside 里,可以找到一个类比(不是 derekinside 自有术语,是我在此做的映射):chunk 的切分契约和实体关系定义,对应的就是代码项目里的接口契约。
derekinside 里所有的知识都要被切成固定格式的 chunk,实体与实体之间怎么连接,也有明确的定义------这些定义就是它的"数据契约"。没有切分契约,知识进来就是一堆乱麻;有了契约,知识才能被稳定地检索、关联、进化。契约层在任何系统里都是"稳定"的来源------不管是代码系统,还是知识系统。
七、第一天就能用的清单

如果你的项目正在用 AI 编码,今天就能把"怎么做"变成契约:
- 下一次给 AI 派活前,写一份带范围、假设、验收标准三栏的 Spec------哪怕只有三行,也比没有强
- 找一个前端和后端共用的接口,把请求/响应签名落成一个数据契约文件,声明"AI 无权修改"
- 加一道接口对齐门禁:前端调用的路径和字段,跟后端实现自动比对,不一致就拦
- 给你的核心 Spec 做一次FMEA:列出 3 个"如果这里出错会怎样",按 RPN 排序,最高的那个安排一道对策
- 立一条三权分立纪律:写代码的、验收的、验证的,不要是同一个人(或同一个 Agent)
五条里做到三条,你的 AI 就从"照着印象施工"变成了"照着契约施工"。最后说一句泰勒当年没说透、但 AI 时代变得格外重要的话:契约不是用来限制 AI 的,是用来保护 AI 的------它让 AI 不用猜,而不用猜的 AI,才是可靠的 AI。
下一篇,从契约层再往下走一层:门禁层------让检查先于代码。契约写了"怎么做",门禁负责"确保真的这么做"。约束是文本,门禁是执行,文本可以不被遵守,执行不能。