docs: M2 第二波收口——报告 13~21 与契约草案入档挂导航
CI / docs-build (push) Successful in 1m19s

- 13~18 后端纵切六单报告(T2-03~08,测试 95→182)
- 14 + openapi-pets-draft.yaml 契约起草档案
- 19 契约冻结报告(v1.2.0,22 项草案修正对照)
- 20 契约一致性测试(快照机制 + 1 漂移修复)
- 21 第二波收口总表(定型语义汇总,第三波接入依据)
- mkdocs build --strict 通过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-08 10:40:27 +08:00
parent 511617be55
commit 222990e587
11 changed files with 2654 additions and 0 deletions
@@ -0,0 +1,119 @@
# T2-09 起草报告:pets 域 OpenAPI 契约草案
> 作者:API 契约工程师
> 日期:2026-09-07
> 状态:**起草态(DRAFT)——未冻结、未并入 docs/api/openapi.yaml**
> 草案文件:`docs/development/iterations/iteration-2/openapi-pets-draft.yaml`(可独立 YAML 解析:12 路径 / 18 操作 / 33 schema$ref 全部可解析)
> 冻结条件:T2-03 权限/错误语义定型 + T2-08 聚合字段定型,由主会话协调执行冻结合并。
---
## 1. 范围与依据
| 依据 | 用途 |
| --- | --- |
| iteration-2/01 §1.1 端点表 + T2-03~T2-08 工单描述 | 端点清单、cursor 分页、乐观锁、Idempotency-Key 要求 |
| iteration-2/02 §4 资源设计草案 + 错误码扩展段 | 40300/40401/40402/40902 语义、P7 防枚举裁决 |
| patbond-api V3 迁移(`V3__pet_health_baseline.sql`) | 字段名、长度、枚举值、CHECK 约束、状态机(**唯一正典**) |
| 既有契约 `docs/api/openapi.yaml` 1.1.0 | 信封、错误组件、camelCase、ISO 8601、securityScheme 风格 |
| ADR-010 | certificate/provider/booking/avatar 不开放写入 |
| ADR-015 | owner/caregiver/viewer 三角色权限模型,邀请流程后置 |
## 2. 起草的端点(12 路径 / 18 操作)
| # | 端点 | 操作 | 对应工单 |
| --- | --- | --- | --- |
| 1 | `/api/v1/pets` | GET / POST | T2-03 |
| 2 | `/api/v1/pets/{petId}` | GET / PATCH | T2-03 |
| 3 | `/api/v1/breeds` | GET | T2-03 |
| 4 | `/api/v1/pets/{petId}/weights` | GET / POST | T2-04 |
| 5 | `/api/v1/vaccine-catalog` | GET | T2-05 |
| 6 | `/api/v1/pets/{petId}/vaccinations` | GET / POST | T2-05 |
| 7 | `/api/v1/vaccinations/{vaccinationId}` | PATCH | T2-05 |
| 8 | `/api/v1/pets/{petId}/health-events` | GET / POST | T2-06 |
| 9 | `/api/v1/health-events/{eventId}` | PATCH | T2-06 |
| 10 | `/api/v1/pets/{petId}/care-reminders` | GET / POST | T2-07 |
| 11 | `/api/v1/care-reminders/{reminderId}` | PATCH | T2-07 |
| 12 | `/api/v1/pets/{petId}/summary` | GET | T2-08 |
`DELETE /api/v1/pets/{petId}`(软删)**未起草**:02 号评估标注"是否进 M2 契约冻结时定",且 PM 决策 D2-7 建议首版仅归档。归档经 `PATCH status=archived` 已覆盖,软删端点留待冻结时裁决(记入 TODO-FREEZE 清单第 11 项)。
## 3. 设计决策(起草者裁量,冻结评审时可推翻)
1. **子资源 PATCH 走顶层短路径**`/api/v1/vaccinations/{id}` 而非 `/api/v1/pets/{petId}/vaccinations/{id}`):记录 ID 全局唯一(UUID),短路径避免冗余 petId 校验歧义(path petId 与记录归属不一致时如何报错)。与 02 号评估的 `PATCH .../vaccinations/{id}` 写法在语义上一致,仅路径层级不同——**冻结评审时需拍板**(列入 TODO-FREEZE)。
2. **提醒资源名用 `care-reminders`**(与表名 care_reminders 对齐),02 号评估用的是 `reminders`——冻结时统一。
3. **疫苗目录路径用 `/api/v1/vaccine-catalog`**02 号评估用 `/api/v1/vaccines`——冻结时统一。
4. **新增错误码 40903(疫苗剂次重复)、42200(品种互斥)、42201(状态机违反)**:延续既有编号段追加,不与 40900/40901/40902 冲突。T2-05 验收标准要求"同系列同剂次重复登记返回冲突"与"状态机非法迁移被拒绝并返回稳定错误码",用 40902 一码多义会让客户端无法区分"重试可解"(版本冲突→刷新重提)与"业务性冲突"(剂次已存在→改剂次)。42200/42201 用 422 区分"参数格式合法但业务规则违反"与 40000 的"参数格式错误"。**此三码为草案新提,需后端确认后进 ErrorCode 枚举**。
5. **cursor 分页信封形态**`data: { items, nextCursor, hasMore }`。既有契约无分页先例,此形态为 pets 域首次定义,将成为全 API 的分页正典——按"一次定死、处处一致"原则,weights 与 health-events 完全一致。
6. **Idempotency-Key 定为可选头**01 号拆解 T2-04 要求"写接口支持 Idempotency-Key",但 02 号评估指出开发计划 6.1 的强制名单不含 pets。草案折中:weights/vaccinations/health-events 三个 POST 声明可选头,语义为"带则幂等去重"care-reminders 与 pets 创建不声明(低重复风险,乐观锁与唯一约束兜底)。**两份输入存在张力,冻结时需拍板**。
7. **响应字段 ID 命名**:资源自身 ID 用类型化名(petId/weightId/vaccinationId/eventId/reminderId),与既有契约 Me.userId 的先例一致,避免裸 `id` 在嵌套结构中歧义。
8. **PetDetail.myRole**:详情返回调用者角色(02 号评估"详情含调用者自己的 role"),供前端决定编辑入口显隐。列表 Pet 不带 role(避免 N 次 join 语义进列表,前端列表页不需要)。
9. **金额一律 `amountCents` 整数分**int64、非负),日期区分 `date`birthDate/plannedOn 等,数据库 date 列)与 `date-time`timestamptz 列),与 V3 列类型一一对应。
10. **UpdateCareReminderRequest 无 version**care_reminders 表**没有 version 列**V3 确认),状态流转 pending→completed/dismissed 天然幂等,不做乐观锁。其余三个 PATCHpets/vaccinations/health-events)均强制 version。
## 4. 与 V3 约束的对照表
| V3 约束 | 契约体现 |
| --- | --- |
| `ck_pets_species` (dog/cat/other) | species enum,三处字典/宠物一致 |
| `ck_pets_sex` (male/female/unknown) | sex enum |
| `ck_pets_status` 5 值 | Pet.status enum 全 5 值;UpdatePetRequest 只开放 4 值(deleted 不开放写) |
| `ck_pets_breed` breed/custom 互斥 | 请求描述 + 422/42200 错误分支 |
| `ck_pets_name` 164 | name minLength/maxLength |
| `ck_pet_weight` >0 且 ≤500 | weightKg minimum 0.01 / maximum 500numeric(6,2),最小正两位小数值) |
| `ck_pet_weight_source` 3 值 | source enum (manual/clinic/device) |
| `ck_vaccination_status` 3 值 | status enum (scheduled/completed/cancelled) |
| `ck_vaccination_dates` 状态-日期联动 | createVaccination/updateVaccination 描述 + 422/42201 |
| `uq_pet_vaccination_dose`(非 cancelled 唯一) | 409/40903 错误分支 |
| `ck_vaccination_dose` >0 | doseNo minimum 1 |
| `ck_health_event_type` 6 值 | eventType enum 与 V3 逐字一致 |
| `ck_health_event_title` 1160 | title 长度约束 |
| `ck_health_event_amount` ≥0 或 null | amountCents minimum 0, nullable |
| `ck_care_reminder_type` 4 值 | reminderType enum |
| `ck_care_reminder_status` 3 值 | status enum (pending/completed/dismissed) |
| `ck_care_reminder_completed` 联动 | UpdateCareReminderRequest 描述 + 422 分支 |
| version 列(pets/vaccinations/health_events | 三资源响应必含 versionPATCH 请求必填 version |
| care_reminders 无 version 列 | CareReminder 响应无 versionPATCH 无乐观锁 |
| `ix_pet_weight_pet_measured` (measured_at DESC, id DESC) | weights 分页排序描述与索引对齐 |
| `ix_health_events_pet_time` (occurred_at DESC, id DESC) | health-events 分页排序与索引对齐 |
| ADR-010 剪出列(avatar/certificate/provider/booking | 全部请求体不含;Pet.avatarAssetId 只读回显、疫苗/事件的 provider/booking/certificate 字段响应中**不出现**(见 §5 注) |
注:`certificate_asset_id``provider_id``provider_name_snapshot``booking_id` 在草案的响应 schema 中**整体未列出**(而非标 readOnly)——M2 无任何写入路径,值恒为 null,列出只会诱导客户端建模死字段;M5/媒体迭代时按"新增可选响应字段"作纯增量扩展,无破坏性。`pets.deleted_at` 同理不出现(软删语义未开放)。
## 5. 与既有契约(1.1.0)风格一致性自查
| 检查项 | 结论 |
| --- | --- |
| 统一信封 `{code, message, data}`,成功 code 恒 0(enum [0] | 一致,每资源独立 XxxEnvelope,与 MeEnvelope 等先例同构 |
| ErrorEnvelope 结构(code integer / message / data nullable | 逐字段一致 |
| ValidationError / AccessTokenInvalid 复用组件 | 与既有 components/responses 同名同构,合并时直接去重 |
| camelCase、UUID 字符串(format: uuid)、ISO 8601 date-time | 一致 |
| securityScheme bearerAuthhttp/bearer/JWT | 逐字一致 |
| 错误码不复用不改号,追加式扩展 | 40300/40401/40402/40902 取自 02 号评估;40903/42200/42201 为新提追加 |
| 中文 summary/description、错误响应带 code 注释 | 一致 |
| openapi 3.0.3、tags 分组 | 一致 |
| 与既有契约的偏差 | 仅两处有意偏差:创建返回 **201**(既有 auth 全 200,但 02 号评估明确"返回 201",且 pets 域为资源创建语义,属域内新约定不破坏旧端点);分页信封为新增形态(既有无先例) |
## 6. TODO-FREEZE 清单(11 项)
草案 YAML 内以 `# TODO-FREEZE:` 注释标注 10 处,加上本报告第 11 项:
| # | 位置 | 等待 | 内容 |
| --- | --- | --- | --- |
| 1 | info.description 权限模型段 | T2-03 | 每端点权限规则逐条定死(owner/caregiver/viewer 读写矩阵)与错误示例 |
| 2 | GET /pets | T2-03 | 列表是否分页(建议不分页) |
| 3 | GET /pets/{petId} | T2-03 | 不可见宠物 404/40401 vs 越权 403/40300 的最终边界(P7 建议已按防枚举写入,待实现确认) |
| 4 | PATCH /pets/{petId} | T2-03 | caregiver 是否可改档案(建议仅 owner) |
| 5 | GET .../vaccinations | T2-05 | 疫苗列表分页策略(量小或可不分页) |
| 6 | POST .../vaccinations | ADR-010 后续 | provider/booking/certificate 字段的未来开放方式(纯增量) |
| 7 | POST .../health-events | ADR-010 后续 | 同上(provider/booking |
| 8 | GET .../care-reminders | T2-07 | 分页与 status=pending 过滤参数形态 |
| 9 | GET .../summary 端点描述 | T2-08 | 聚合字段命名、月度边界时区口径、进度分母口径、下次接种取值优先级 |
| 10 | PetSummary schema | T2-08 | 全 schema 为占位,逐字段待定 |
| 11 | 本报告 §2/§3 | 冻结评审 | DELETE 软删端点是否入 M2;子资源 PATCH 路径层级;`care-reminders`/`vaccine-catalog` 资源命名与 02 号评估用词统一;Idempotency-Key 可选 vs 强制;40903/42200/42201 三个新错误码后端确认 |
## 7. 冻结前禁止事项(自我约束声明)
- 本草案**未合入** `docs/api/openapi.yaml`(仍为 1.1.0 / 6 端点,未做任何修改)。
- 未修改 mkdocs.yml、未 commit/push、未改动任何代码仓。
- 冻结时的合并动作:去重 componentsErrorEnvelope/两个 responses/securityScheme)、版本号升 1.2.0、错误码表并入 info.description、消除全部 TODO-FREEZE——由主会话在 T2-03/T2-08 定型后协调执行。