a611acb358
CI / docs-build (push) Successful in 32s
- 15~17 社区后端纵切三单(帖子/Feed 作者链路/评论互动关注,226→310) - 18 契约冻结 v1.3.0(31 路径/43 操作,26 项修正照单全收) - 19 快照同步与全仓契约矩阵(173 格零漂移,修 allOf 校验盲区,→325) - 20 收口总表:定型语义汇总(第三波接入依据)与质量事件记录 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
83 lines
7.9 KiB
Markdown
83 lines
7.9 KiB
Markdown
# M3 契约同步报告(api 侧:v1.3.0 字节级快照同步 + community/media 契约矩阵入场)
|
||
|
||
> 作者:Senior Developer(后端)
|
||
> 日期:2026-09-09
|
||
> 工单:契约冻结 v1.3.0 的 api 侧收尾(18 号冻结报告 §5-2 注明的硬依赖工单)
|
||
> 输入:doc 仓 `docs/api/openapi.yaml` v1.3.0(main@f848476,31 路径 / 43 操作 / 72 schemas);patbond-api dev@7f1dd33(310 测试基线)
|
||
> 结论先行:**正典 v1.3.0 已字节级复制为四个模块的 `openapi-v1.3.0.yaml` 快照(md5 与正典逐一比对一致),pet/auth 守卫期望同步升版;community 域 17 操作 64 单元格、media 域 2 操作 8 单元格的契约一致性测试全响应矩阵入场,均零豁免;实现与冻结契约零漂移(64+8 格无一漂移报告);发现并修复框架级校验盲区一处(ContractValidator 不支持 v1.3.0 引入的 `nullable + allOf: [$ref]` 模式,会静默跳过 coverImage/replyToUser 内部校验);mutation 自证两轮通过(普通路径 + allOf 定向路径注毒均红、还原即绿);全套 325 测试全绿(310 + 15),`check-secrets.sh --all` 通过。**
|
||
|
||
---
|
||
|
||
## 1. 快照同步(字节级)
|
||
|
||
| 位置 | 旧 | 新 | 处置 |
|
||
| --- | --- | --- | --- |
|
||
| `patbond-pet/src/test/resources/contract/` | openapi-v1.2.0.yaml | openapi-v1.3.0.yaml | 替换(删旧) |
|
||
| `patbond-auth/src/test/resources/contract/` | openapi-v1.2.0.yaml | openapi-v1.3.0.yaml | 替换(删旧) |
|
||
| `patbond-community/src/test/resources/contract/` | —(新建) | openapi-v1.3.0.yaml | 新增 |
|
||
| `patbond-user/src/test/resources/contract/` | —(新建) | openapi-v1.3.0.yaml | 新增 |
|
||
|
||
- 四份快照 md5 与 doc 仓正典(main@f848476)逐一比对一致(`5b550fabf8e94b715ac1161798cb2738`),满足「字节级复制」纪律。
|
||
- **旧 v1.2.0 快照删除而非保留**:每个模块的 `OpenApiContract.RESOURCE` 常量只认一份快照文件,守卫测试锁 `info.version`,保留旧文件只是死重——历史版本由 git 历史与 doc 仓承载。
|
||
- pet/auth 守卫期望同步升版:`1.2.0/18 路径/24 操作/45 schemas` → `1.3.0/31/43/72`;各域 `operationsTagged` 断言不变仍绿(pets 域 18 操作、auth 域 6 操作在 v1.3.0 中零变化,即 v1.2.0 冻结面未被 1.3.0 触碰的实证)。
|
||
- 契约框架(OpenApiContract + ContractValidator)按既有的模块内复制纪律扩为四份同构副本(pet/auth/community/user),同步纪律注释已改为「四模块各复制一份、各自更新期望」。
|
||
|
||
## 2. 覆盖矩阵规模(本单新增 19 操作 / 72 单元格,零豁免)
|
||
|
||
| 域 | 模块 | 测试类 | 操作 | (操作, 状态码) 单元格 | 豁免 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| community(posts/feed/comments/interactions/follows) | patbond-community | CommunityContractConformanceTest | 17 | 64 | **0** |
|
||
| media(两步上传,属 user 模块) | patbond-user | MediaContractConformanceTest | 2 | 8 | **0** |
|
||
| pets/dictionaries/health-records(既有) | patbond-pet | ContractConformanceTest | 18 | 82 | 1(沿用) |
|
||
| auth/user/analytics(既有) | patbond-auth | AuthContractConformanceTest | 6 | 19 | 0 |
|
||
| **合计(v1.3.0 全部 43 操作)** | 4 模块 | 4 类 | **43** | **173** | **1** |
|
||
|
||
- 机制与 pet 侧 T2-09 完全同构:真实起服务发请求(community 走 MockMvc + postgres:18 Testcontainer 全迁移链;media 走真实 MinIO Testcontainer,直传为真实 HTTP PUT)→ 严格校验器逐字段比对(未声明字段即报漂移)→ 末位全矩阵门禁断言每个声明单元格都被真实响应触发过。
|
||
- community 域覆盖要点:错误码全谱 40000/40101/40301/40401/40403/40404/40405/40406/40902/40905/42203/42204 各至少一格实证;双业务码单元格(POST /posts 404 的 40401/40405、POST comments 404 的 40403/40406)两种业务码分别触发;分页信封 hasMore/nextCursor 两态、coverImage 与 replyToUser 的 null/非空两分支、防枚举合并语义(幽灵 id 与他人 draft 同响应)均在矩阵内。
|
||
- media 域覆盖要点:201 凭据形态、直传后 complete 200(含幂等重复确认)、400(mime 白名单外 + 畸形 assetId)、401、404 防枚举合并(他人 asset 与幽灵 asset 同答 40405)、422/42205(直传前确认)。
|
||
|
||
### 豁免格清单
|
||
|
||
**本单新增矩阵零豁免**——community 域的 409 均为幂等键/乐观锁冲突、422 均为业务规则拒绝,media 域 422 为状态机拒绝,单线程 MockMvc 均可确定性触发。全仓唯一豁免格仍为 pet 侧沿用的 `PATCH /api/v1/care-reminders/{reminderId} 409`(并发条件更新守卫落空,单线程无法确定性构造,行为语义由并发一致性设计文档背书)。
|
||
|
||
## 3. 发现并修复的漂移清单
|
||
|
||
### 3.1 实现 ↔ 冻结契约:零漂移
|
||
|
||
新增 72 单元格全部一次通过严格校验,无字段名/类型/必填/nullable/枚举/格式漂移,无需修实现;未发现语义级冲突。这与第二波「先定型表、后冻结照单全收」的流程预期一致——契约本就是按已定型实现冻结的,本单是对「冻结稿与实现零偏差」声明的全矩阵实证。
|
||
|
||
### 3.2 框架级校验盲区一处(发现并修复)
|
||
|
||
- **问题**:v1.3.0 为表达「可空的 $ref」引入 `nullable: true + allOf: [$ref]` 模式(`FeedCard.coverImage`、`Comment.replyToUser`),而既有 ContractValidator 明文只支持无 allOf 子集——遇到该模式会解析出 `type=null` 而**静默跳过内部校验**:coverImage/replyToUser 里新增泄漏字段或类型漂移将无法被察觉,属校验盲区而非误报。
|
||
- **修复**:四份 ContractValidator 副本同步加入单分支 allOf 展平合并(分支键先入、同级键——如外层 nullable——胜出;冻结契约只用单分支 allOf,浅合并即精确),并以定向 mutation 证明该路径生效(见 §4)。
|
||
|
||
## 4. mutation 自证(注毒应红、还原应绿)
|
||
|
||
| 轮次 | 注毒点 | 预期 | 实测 |
|
||
| --- | --- | --- | --- |
|
||
| 1a | community 快照 `PostMediaItem.required` 注入假必填字段 | 红 | 5 测试失败,`$.data.media[0].fakeContractField: 契约必填字段缺失` |
|
||
| 1b | user 快照 `MediaAsset.required` 注入假必填字段 | 红 | 2 测试失败,`$.data.fakeContractField: 契约必填字段缺失` |
|
||
| 2 | community 快照 `coverImage` 的 allOf 同级注入 `required: [fakeAllOfField]`(定向打 allOf 合并路径) | 红 | `GET /api/v1/feed 200` 漂移:`$.data.items[*].coverImage.fakeAllOfField: 契约必填字段缺失` |
|
||
| 还原 | 四快照 cp 回正典并 md5 复核 | 绿 | 全套 325 测试全绿 |
|
||
|
||
第 2 轮专为 §3.2 的修复自证:假必填字段被报告在 **coverImage 内部**,证明 allOf 合并后校验器确实下钻到了此前静默跳过的分支。
|
||
|
||
## 5. 测试数变化
|
||
|
||
| 模块 | 基线 | 现在 | 增量 |
|
||
| --- | --- | --- | --- |
|
||
| patbond-common | 3 | 3 | — |
|
||
| patbond-user | 96 | 100 | +4(MediaContractConformanceTest) |
|
||
| patbond-auth | 39 | 39 | —(守卫期望升版,数量不变) |
|
||
| patbond-pet | 89 | 89 | —(守卫期望升版,数量不变) |
|
||
| patbond-community | 83 | 94 | +11(CommunityContractConformanceTest) |
|
||
| **合计** | **310** | **325** | **+15** |
|
||
|
||
`JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test` 全绿;`scripts/check-secrets.sh --all` 通过(快照与测试无敏感信息,MinIO 凭据沿用 dummy 占位值先例)。
|
||
|
||
## 6. 遗留与交接
|
||
|
||
- 契约同步纪律自此为**四处复制**:doc 仓正典升版 → 四模块同字节复制新快照 + 各守卫期望更新,任一处忘记同步 CI 即红(守卫锁 `info.version` 与三项计数)。
|
||
- 契约测试框架仍为模块内四份同构副本(与 BearerAuthFilter 同纪律);若第三迭代后副本继续增多,可评估抽入 patbond-common 的 test-jar,此次不动。
|
||
- 本报告(19 号)随波末统一挂导航入档;mkdocs.yml 本次不动;doc 仓 openapi.yaml 本单未触碰。
|