Agent 改 API 的契约兼容门:阻断对消费者的隐性破坏
服务端测试通过不代表消费者仍可用。本文把规范差分、消费者契约、跨版本回放和迁移窗口组成兼容门,约束 Agent 修改 REST、事件与消息结构。
本文要点
- 主题聚焦:AI Agent、TypeScript、DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
Agent 接到“给响应加一个字段”时,常会同时重命名旧字段、收紧枚举、调整空值或重新生成客户端。服务端单元测试可能全部通过,新服务也能处理新请求,但旧移动端、合作方脚本和异步消费者已经被破坏。兼容门必须从消费者可观察契约判断改动,而不是从提供方是否成功构建判断。
REST、GraphQL、事件和 Protobuf 的规则不完全相同,但证据链相似:冻结基线契约,识别结构与语义差异,运行真实消费者契约,用旧新版本交叉验证,再决定兼容发布、迁移窗口或拒绝。协议选型背景可参考REST、GraphQL 与 gRPC API 设计,本文关注改动之后谁会坏。
契约基线必须来自已发布事实
基线不是 Agent 开始工作时顺手生成的文件,而是当前生产版本的 OpenAPI、GraphQL schema、Protobuf descriptor、事件 schema 及其 digest。CI 从制品库按 BASE_RELEASE 获取,再从 HEAD 构建候选契约,记录生成器版本、源文件哈希和命令。下面的两个 api:* 名称是仓库必须在 package.json 中实现的脚本契约,不是 pnpm 内置命令;前置检查会在脚本缺失时立即失败。
node --version
pnpm --version
pnpm install --frozen-lockfile
mkdir -p .artifacts
node -e 'const p=require("./package.json"); for (const n of ["api:schema:build","api:contract:diff"]) if (!p.scripts?.[n]) throw new Error(`missing package script: ${n}`)'
pnpm run api:schema:build
sha256sum .artifacts/openapi-base.json .artifacts/openapi-head.json
pnpm run api:contract:diff -- \
.artifacts/openapi-base.json .artifacts/openapi-head.json
示例环境固定为 Node 22.18.0、pnpm 10.14.0;diff 与代码生成工具都使用 lockfile 中的精确版本。报告绑定 base release digest、head SHA、规范版本和生成命令。若规范由运行时代码生成,CI 还要在干净容器中启动服务并对导出结果做确定性比较,避免未提交状态影响契约。
OpenAPI 差分先阻断明确破坏:删除路径、方法、响应字段或枚举值;把可选改为必填;收紧长度与数值范围;改变状态码、内容类型、鉴权要求或字段类型。增加响应字段通常兼容,但如果已知消费者使用严格反序列化,也可能破坏,因此自动规则不能替代消费者证据。API 版本和弃用窗口可结合REST API 版本管理制定。
让消费者声明真实期望
每个活跃消费者发布带版本的契约,包含会发出的请求、依赖的字段、允许的枚举与错误行为。提供方 CI 用候选服务验证最近稳定版本和仍在支持窗口内的旧版本。契约工件必须签名或带 digest,来源、消费者提交、环境和生成时间可追溯;过期超过 30 天的契约不能默认为兼容,只能触发负责人确认。
契约用例应验证语义,不只是 schema。例如旧客户端未发送新字段时仍获得原默认行为;重复幂等键不产生第二次副作用;未知响应字段被忽略;错误码和重试提示保持一致。事件消费者还要验证旧消费者能读取新生产者消息、新消费者能读取发布窗口内的旧消息,并处理乱序、重复和缺失可选字段。
Protobuf 等演进需要遵守字段号不复用、删除字段保留编号、不要把已有字段从 repeated 改为 singular 等规则。即便 wire format 可解析,单位从毫秒改成秒、时区语义变化或默认值改变仍是语义破坏,必须由场景测试阻断。
建立跨版本兼容矩阵
合并门运行四个方向:旧消费者对候选提供方、新消费者对基线提供方、候选生产者到旧事件消费者、旧生产者数据到候选消费者。每项记录镜像 digest、种子数据、请求与响应摘要、退出码。涉及 SDK 时,至少构建当前稳定 SDK 和仍在支持期的最老 SDK,不可只重新生成 HEAD SDK 再自测。
通过条件为:静态差分无未批准 breaking change;所有活跃消费者契约通过;兼容矩阵无失败;弃用字段仍在承诺窗口;迁移说明与监控已就绪。高风险接口还需人工确认。代码审阅循环可参考编程 Agent 的 PR 审阅闭环,但 reviewer 看到的应是具体消费者和版本,不是一句“API tests passed”。
失败不能靠更新消费者契约消失
契约失败后,Agent 优先恢复向后兼容,例如保留旧字段并新增字段、接受旧枚举、用版本协商引入新语义。禁止在同一补丁里把消费者期望改成提供方当前输出,然后声称通过;契约更新需要消费者仓库独立提交和 owner 批准。
如果业务确实需要破坏性变更,状态不是继续自动合并,而是进入迁移流程:分配新版本或协商机制,列出消费者负责人,提供双写或适配层,定义最后使用日期、调用量归零门槛和回滚路径。部署期间监控旧字段使用量、反序列化错误、未知枚举、4xx/5xx 与重试风暴,达到门槛后才能移除旧契约。
工具超时、基线制品缺失、消费者契约不可下载或测试环境启动失败都属于证据缺失,默认阻断。允许人工豁免时,记录范围、原因、负责人和最迟到期时间,且不能覆盖支付、鉴权或数据删除接口。补丁其他层的验证仍遵循编程 Agent 的改动验证门。
上线前用故障样本校准:删除可选字段、收紧枚举、改变默认时区、复用消息字段号、把 404 改为 200 空对象,确认至少一个门会失败。兼容门的目标不是让规范文件更整齐,而是让 Agent 每次改接口都回答清楚:哪些真实消费者、哪些仍受支持的版本,已经用可重跑证据证明不会被这次改动悄悄破坏。
Related