Files
patbond-doc/docs/development/iterations/iteration-3/18-contract-freeze-report.md
T
lixi a611acb358
CI / docs-build (push) Successful in 32s
docs: M3 第二波收口——报告 15~20 入档挂导航
- 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>
2026-09-09 11:38:42 +08:00

13 KiB
Raw Blame History

M3 契约冻结报告(T3-10community/media 域合入正典 v1.3.0

作者:API 契约工程师 日期:2026-09-09 工单:T3-10(契约冻结,第二波收口) 输入:草案 openapi-community-draft.yaml + 11 号草案说明;定型表 13(媒体凭据)/ 15(帖子生命周期)/ 16FeedCard/AuthorSummary/ 17(评论/互动/关注) 结论先行:community/media 域按四份定型表照单全收合入 docs/api/openapi.yaml1.2.0 → 1.3.0:新增 13 路径 / 19 操作 / 27 schemas / 4 参数 / 7 响应组件 / 9 错误码,正典总量 31 路径 / 43 操作 / 72 schemas。草案→冻结修正 26 项逐条对照见 §3;四份定型表间未发现矛盾(两处表面分歧均已由报告自身声明口径,见 §4);草案 10 处 TODO-FREEZE 全部回填删除;YAML 解析、$ref 全解析、operationId 唯一性、mkdocs build --strict 全部通过。api 侧字节级快照同步为本冻结的硬依赖,由后续 api 侧工单执行(§5)。


1. 冻结版本与总量

1.2.0 1.3.0 增量
路径 18 31 +13
操作 24 43 +19
schemas 45 72 +27
parameters 4 8 +4PostIdParam/AssetIdParam/UserIdParam/IdempotencyKeyRequiredHeader
responses 6 13 +7PostNotFound/CommentNotFound/MediaNotFound/UserNotFound/PostAccessDenied/IdempotencyPayloadMismatch/MediaNotReady
错误码 19 28 +940301/40403/40404/40405/40406/40905/42203/42204/42205
servers 3 4 +:8084 patbond-community
tags 6 12 +media/posts/feed/comments/interactions/follows

info 头同步动作:更新履历补 1.3.0 段;错误码表按码位序并入 9 码;新增「Community / Media 域约定」段(幂等域差异、媒体两步上传与签名读语义、防枚举码族、互动面=公开面、ADR-018 裁剪与 /internal 不入契约)——11 号报告 §6-5 要求的「Idempotency-Key 必带 + 比对 hash + ≤128 与 pets 域差异在 info 头显式成文」已落。

2. 冻结端点总表(13 路径 / 19 操作)

# 端点 操作 服务 成功 错误面(HTTP/业务码)
1 /api/v1/media/uploads POST user :8082 201 凭据 400/40000、401/40101
2 /api/v1/media/uploads/{assetId}/complete POST user :8082 200 asset 400/40000、401、404/40405、422/42205
3 /api/v1/posts POST community :8084 201 Post 400、401、404/40401+40405、409/40905、422/42203
4 /api/v1/posts/{postId} GET / PATCH / DELETE community 200 GET401、404/40403PATCH400、401、403/40301、404/40403+40401+40405、409/40902、422/42203DELETE401、403、404
5 /api/v1/me/posts GET community 200 分页 Post 400、401
6 /api/v1/feed GET community 200 分页 FeedCard 400、401
7 /api/v1/posts/{postId}/comments GET / POST community 200 / 201 GET400、401、404/40403POST400、401、404/40403+40406、409/40905
8 /api/v1/comments/{commentId} DELETE community 200 Void 401、403/40301、404/40404
9 /api/v1/posts/{postId}/like PUT / DELETE community 200 LikeState 401、404/40403
10 /api/v1/posts/{postId}/bookmark PUT / DELETE community 200 BookmarkState 401、404/40403
11 /api/v1/me/bookmarks GET community 200 分页 FeedCard 400、401
12 /api/v1/users/{userId}/follow PUT / DELETE community 200 FollowState 401、404/40406PUT 另有 422/42204
13 /api/v1/users/{userId}/follow-stats GET community 200 FollowStats 401、404/40406

全部端点强制 Bearer 鉴权。裁剪不出现(ADR-018):话题端点、关注/粉丝列表、作者主页帖子列表、region/generationJob/visibility=followers|private/internal/users/profiles 为服务间接口,不入公网契约(形态以 16 号报告 §3 为准)。

3. 草案 → 冻结修正项对照(26 项,照单全收)

3.1 媒体域(依据:13 号报告 §3/§4)

# 草案 冻结 依据
M1 读取侧 URL 形态留白(公共读 vs 签名读) 私有桶 + 预签名 GET(TTL 默认 1 小时,配置项);MediaAsset.url / PostMediaItem.url / AuthorSummary.avatarUrl 描述统一注明「时效性、每次响应现签、客户端不得持久化、过期即重取」 13 号偏差 #1 + 用户拍板
M2 complete「校验失败置 failed」一刀切 对象不存在 → 422/42205 保持 uploading 可重试;对象存在但大小/类型不符 → 置 failed 终态 422/42205 13 号偏差 #2
M3 「有 sha256 则一并核」 sha256 照收照存,M3 不核验(字段描述改写;后续经存储侧 checksum 补齐不改契约形态) 13 号偏差 #3
M4 complete 未声明 400 补 400/ValidationError(非 UUID assetId 13 号偏差 #4
M5 purpose 白名单待定 post_image 一项;P6 扩展为向后兼容枚举追加 13 号偏差 #5
M6 mime 白名单与 HEIC 待定 定 jpeg/png/webp不收 HEIC 13 号偏差 #6
M7 byteSize 上限草案 10 MiB 定 10485760(配置项) 13 号偏差 #7
M8 requiredHeaders「键集草案态」 定型为恒且仅 {"Content-Type": <mimeType>} 一键,并入 requiredexpiresAt TTL 10 分钟维持 13 号 §3 定型表

3.2 帖子域(依据:15 号报告 §2/§4)

# 草案 冻结 依据
P1 Post.author 占位争议(T3-04 曾落 authorId 裸字段) Post.author = AuthorSummaryT3-05 回填闭环,authorId 不出现) 15 号偏差 #1 + 16 号偏差 #7(同一事项两端)
P2 status 对作者是否露 hidden 留白 不露hidden/archived 对作者读写一律 404/40403,枚举保持 [draft, published],权限矩阵写入 getPost 描述 15 号偏差 #2
P3 「其余迁移 400/40000」 published→published 为幂等 no-op200version 照常 +1400 只留给 draft/hidden/archived 目标值 15 号偏差 #3
P4 幂等重试撞已删首帖未覆盖 同键同 hash 撞已删首帖 → 404/40403,写入 IdempotencyKeyRequiredHeader 描述 15 号偏差 #4
P5 request_hash 规范化细则未定 「hash 对象是规范化后的创建命令(trim、缺省展开),语义等价即命中」写入头参数描述与 info 头 15 号偏差 #5
P6 PostMediaItem.url required 与降级冲突 维持 required + 运维前提(生产恒配置),描述注明现签与 TTL 15 号偏差 #6(报告建议后者)
P7 PATCH media 整组替换「草案态」 定型确认,删标注:字段出现即删旧插新、[] 清空、缺席不动 15 号 §2.6(草案预设确认)
P8 isCover 全 false「展示层取 position 0」 改为写侧落库置真:库内恒有唯一封面行;PostMediaAttachRequest/PostMediaItem 描述同步 15 号 §2.6
P9 —(草案未列) createPost/updatePost 404 显式声明 40401petId)与 40405asset)双例;Post.updatedAt 注明「互动计数亦推动该值,判编辑以 version 为准」 15 号 §2.3 复用码行为 + 17 号 §3 记录在案行为

3.3 Feed / 作者域(依据:16 号报告 §2/§3/§6)

# 草案 冻结 依据
F1 AuthorSummary required [userId, nickname] required 收为 [userId]nickname nullablenull 仅降级/墓碑;正常路径恒非空语义写入描述) 16 号偏差 #1 + 用户拍板
F2 contentPreview「200 字符 + 完整边界截断」 200 Unicode 码点、码点边界截断(增补面字符不劈)、不加省略号 16 号偏差 #2
F3 FeedCard 是否带图列表待定 只带 coverImage + mediaCount0~9);裁剪面(无 content 全文/petId/visibility/version/media 整组/created/updated)写入 schema 描述 16 号偏差 #3
F4 bio/username/墓碑待定 不补 bio、不露 username;墓碑 = id-only 形态({userId, nickname: null, avatarUrl: null}),与降级同形 16 号偏差 #4
F5 封面「isCover 优先→position 0 兜底」 读侧只认唯一 is_cover 行(兜底已在写侧完成),FeedCard.coverImage 描述改写 16 号偏差 #5
F6 likedByMe/bookmarkedByMe「批量查询」 实现为内联 EXISTS——契约无感知,仅在此记录,条文不动 16 号偏差 #6
F7 avatarUrl 示例为公共读稳定 URL 形态 示例删除,描述改为预签名 GET 语义(与 M1 同源) 16 号 §3 + 13 号偏差 #1

3.4 评论 / 互动 / 关注域(依据:17 号报告 §2/§5)

# 草案 冻结 依据
C1 帖子不可见 404(未提作者草稿) 互动面 = 帖子公开面:作者本人草稿在评论(读写)与 like/bookmark 全部路径同样 404/40403——comments GET/POST、like/bookmark PUT/DELETE 六处描述逐一补「含作者本人草稿」,并入 info 头与 40403 错误表行 17 号偏差 #1
C2 replyToUserId 校验语义未定 目标须为存活用户,不存在/注销合并 404/40406createComment 404 双例:40403/40406 17 号偏差 #2
C3 unfollow 未提自取关 自取关 200 幂等 no-opfollowing 恒 false;42204 只在 PUT,双端描述与错误表行写明 17 号偏差 #3 + 用户拍板
C4 42205 列为「草案新增」 42205 属 T3-03 已启用码,口径修正;对 1.3.0 契约错误码表仍是本次新收录(1.2.0 表中无此码) 17 号偏差 #4
C5 幂等重试撞已删首评未覆盖 404/40404,与帖子域 40403 平行写入 IdempotencyKeyRequiredHeader 描述 17 号偏差 #5
C6 Comment 形态确认 无 updatedAtM3 无评论编辑,schema 描述注明);replyToUser 为完整 AuthorSummaryrequired 收敛沿 [userId],含降级 id-only 形态) 17 号偏差 #6
C7 评论删除权限 **仅评论作者可删——帖主不可删他人评论(D3-7 首版不做)**在 deleteComment 描述显式写明;对可见评论的非作者(含帖主)403/40301 17 号 §2.1 + 用户拍板

3.5 错误码收录裁定(用户拍板全收)

新收录 9 码:40301 / 40403 / 40404 / 40405 / 40406 / 40905 / 42203 / 42204 / 4220542205 在实现侧属 T3-03 既有,但 1.2.0 契约表无此码,故按实际入 1.3.0 表)。40400 不复用:该码已承担四服务 NoResourceFound「路由级资源不存在」兜底语义,关注/回复目标缺失独立取 40406,理由成文进错误码表行。复用既有码(40000/40101/40401/40902/50000/50300)不新增行、语义不动。

4. 定型表间一致性核验(未发现矛盾)

逐对交叉核验四份定型表,两处表面分歧均已由报告自身声明口径,不构成矛盾:

  1. 15 号(draft 对作者可见)vs 17 号(作者草稿在互动路径 404):17 号 §2.3 显式声明为「收窄而非矛盾」——可见性回答「能不能看」,互动门禁回答「能不能社交」。冻结采两者:getPost 描述保留作者可见 draft,互动六端点补「含作者本人草稿」。
  2. 15 号(PostMediaItem.url 未配置降级为 nullvs 草案 required:15 号偏差 #6 自身给出两选项并建议「维持 required + 运维前提」,16 号 coverImage 的同规注记同源。冻结采建议项:url 保持 required,描述注明运维前提。

5. 冻结纪律重申

  1. 本文件即契约1.3.0 起 community/media 域 13 路径进入冻结面——任何字段/语义变更须显著上报、两端同步;错误码只增不改义、永不复用改号;裁剪字段/端点按纯增量补入(ADR-010/ADR-018 先例)。
  2. api 侧字节级快照同步是本冻结的硬依赖patbond-api 现有契约一致性测试持有 v1.2.0 字节级快照(至少 patbond-pet 与 patbond-auth 的 src/test/resources/contract/ 两处复制,13 号报告 §8 亦要求 T3-10 冻结时同步),本仓升版 1.3.0 后,api 侧快照未同步前其快照守卫测试将保持红灯(CI 红)——这是防漂移门禁按设计生效,不是事故。快照同步(连同 community 域契约矩阵测试 T3-11 的入场)由后续 api 侧工单执行,本报告仅冻结契约本体并注明该依赖顺序:先本仓合入推送,再 api 侧同字节复制快照。
  3. 草案文件处置openapi-community-draft.yaml 与 11 号说明保留原地作为过程档案,不再维护;此后一切消费方(SDK/客户端/契约测试)以 docs/api/openapi.yaml v1.3.0 为唯一权威。
  4. 校验通过项YAML 解析、283 处 $ref 全解析、43 个 operationId 无重复、全操作 security 声明齐、草案 10 处 TODO-FREEZE 归零、mkdocs build --strict 通过。

6. 遗留与交接

  • api 侧:快照同步 + community 契约矩阵测试(见 §5-2,后续工单)。
  • 本仓:本报告(18 号)随波末统一挂导航入档;mkdocs.yml 本次不动。
  • 头像上传口子(purpose 扩 user_avatar)与设置昵称端点随 P6 拍板另立工单,届时按「枚举追加 + 新端点」纯增量升 1.4.x,不触碰本次冻结面。