Files
lixi 222990e587
CI / docs-build (push) Successful in 1m19s
docs: M2 第二波收口——报告 13~21 与契约草案入档挂导航
- 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>
2026-09-08 10:40:27 +08:00

120 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 定型后协调执行。