# 发布记录(常设) > **定位**:跨迭代常设文档——每次 `dev → main` 发布在此追加一条记录:版本号、三仓 tag 与哈希、门禁证据、已知遗留。 > **维护约定**:按[发布 checklist](iterations/iteration-3/08-git-workflow-plan.md)(§3.3) 执行,完成后在此登记。最新版本在最上。 > 发布分支为 `main`(ADR-011 原写 master,ADR-021 更正);日常开发直推 `dev`。 --- ## v0.4.0 — M3.5 体验补齐(2026-09-14) **首次经 PR 流程发布**(v0.3.0 为直推 main,分支保护启用前)。 ### 三仓 tag | 仓库 | tag | 提交 | 内容 | | --- | --- | --- | --- | | patbond-api | `v0.4.0` | `3cd8005` | 用户资料读写 + 宠物头像 + 社区统计;379 测试;契约 v1.4.0 快照 | | patbond-flutter | `v0.4.0` | `fbcd734` | 中文本地化 + 日期录入收口 + 资料页/编辑页 + 头像接线;597 测试;四份 E2E 脚本 | | patbond-doc | `v0.4.0` | 本记录所在提交 | 契约 v1.4.0 + M3.5 报告 01~08 + 安全事件复盘 + 服务器暴露面清单 | ### 版本内容(相对 v0.3.0 的增量) - **用户资料**:`GET /me` 补 `nickname`/`avatarUrl`;新增 `PATCH /me`(三态:键缺省=不改 / 显式 null=清空 / 给值=设置) - **头像**:用户与宠物头像上传(复用 M3 的 MinIO 两步上传),`purpose` 白名单 +`user_avatar`/+`pet_avatar` - **社区统计**:`GET /me/community-stats`(获赞总数 + 作品数,读侧实时聚合,无新冗余列) - **客户端体验**:中文本地化(此前从未配置 `flutter_localizations`,Flutter 静默回退英文)、日期录入收口共享层(7 处调用点 + 手输 + 「今天」快捷)、资料页 demo 退役、花费卡显示实际月份 - **契约**:openapi.yaml **v1.4.0**(32 路径/45 操作/75 schema),**对 v1.3.0 纯增量**(无字段删改/类型变更/必填收紧,已写入 `info.description`) - **零 Flyway 迁移**(ADR-022):所需列 V1/V3/V5 早已存在,`purpose` 白名单为配置项 - **决策**:ADR-022 ### 发布门禁证据 | 门禁项 | 结果 | 证据 | | --- | --- | --- | | 全量测试 | ✅ | api **379** / flutter **597**,双绿 | | **E2E 回归(四份,同环境串行)** | ✅ | M1 7/7 + M2 11/11 + M3 14/14 + **M3.5 10/10** = **42/42 场景、234 断言零失败** — [08 号报告](iterations/iteration-3.5/08-release-e2e-regression.md) | | 契约偏差 | ✅ **0** | 逐场景对照 v1.4.0;契约计数机械复核 32/45/75 | | 契约向后兼容 | ✅ | v1.3.0→v1.4.0 纯增量,v1.3.0 客户端无需改动 | | 契约一致性矩阵 | ✅ | 173→**181 格**,11 格「未声明字段即漂移」的红全部转绿,mutation 三处注毒自证 | | **零迁移在存量库上实证** | ✅ | compose 起于既有 `pgdata` volume,Flyway 日志 `Current version: 5 / No migration necessary.` | | CI(push + **pull_request**) | ✅ | api 5m31s / 6m42s;flutter 3m46s / 3m44s;doc 2m58s | | 凭证防泄漏 | ✅ | `check-secrets.sh --all` exit 0 | ### 发布操作记录 1. **首次走 PR 流程**(分支保护生效后):两仓各建 `dev → main` PR(标题 `v0.4.0 M3.5 体验补齐`),等 `pull_request` 状态检查转绿后以 **Fast-forward only** 合并 → **main == dev、零合并提交、历史保持线性**。 2. **流程本身也被验证了**:v0.3.0 是直推 main,「PR + 状态检查」这套机制从未真实走过。本次确认:`pull_request` 触发器会跑、分支保护确实要求该检查通过、PR 会自动跟随 dev 新提交重新检查(发布途中 E2E 脚本提交推入 dev,PR 随即重跑)。 3. **CI 从安全事件恢复后的首次完整验证**:见下方「期间事件」。 ### 期间事件:Gitea gitconfig 注入(已闭环) 本迭代期间发生一次安全事件,CI 全面中断约一天。完整复盘见 [07 号报告](iterations/iteration-3.5/07-security-incident-20260911.md),纪律固化见新建的[服务器暴露面清单](server-exposure.md)。要点:攻击**未达成代码执行**、三仓代码经核对**未被篡改**、**无系统层入侵**;处置后外部验证四项通过。 ### checklist 变更(下次发布适用) - **回归清单由两份改为四份**:M1/M2/M3/M3.5。原则是「每个引入对外端点的迭代都应有对应的 E2E 脚本,并在此后每次发布回归」。 - 发布前若 dev 仍有提交推入,PR 检查会重跑——**以最终 HEAD 的检查结果为准**,不要用旧的绿色状态放行。 ### 已知遗留 **真机验证**:M2 两项 + M3 四项 + M3.5 新增(头像上传弱网、头像缓存)——步骤全部备齐在[真机验证清单](device-verification.md),待设备到位。 **功能遗留**:「我的收藏与草稿」列表页(后端就绪,缺 2 页面)、月份网格选择器、`SegmentedButton` 主题债、`widthPx/heightPx` 恒 null、`eventVersion` 口径未定型、uploading 超时清理任务、429 限流、话题/关注列表/作者主页(ADR-018 剪出)。 **跨迭代技术债**:access token 黑名单、`/internal` 改 mTLS。 **待评估**:文档站公开可访问是否加访问控制(见服务器暴露面清单)。 ## v0.3.0 — M3 社区(2026-09-10) **首次正式发布**,发布流程首次演练。 ### 三仓 tag | 仓库 | tag | 提交 | 内容 | | --- | --- | --- | --- | | patbond-api | `v0.3.0` | `8089c06` | 五模块(common/auth:8081/user:8082/pet:8083/community:8084),334 测试 | | patbond-flutter | `v0.3.0` | `0e87413` | 502 测试 + 三份 E2E 烟囱脚本(M1/M2/M3) | | patbond-doc | `v0.3.0` | 本记录所在提交 | 契约 v1.3.0 + 三迭代全部报告(20+30+30 份) | ### 版本内容 - **M1 认证纵切**:JWT RS256、refresh 轮换、多设备会话、登录锁定 - **M2 宠物健康档案**:宠物 CRUD + 三角色权限 + 体重/疫苗/健康事件/提醒 + 档案聚合(18 操作) - **M3 社区**:图片媒体上传闭环(自托管 MinIO,ADR-016)+ 帖子草稿/发布/删除 + 公共 Feed 游标分页 + 单层评论 + 点赞收藏幂等 + 关注(13 路径/19 操作) - **契约**:openapi.yaml **v1.3.0 冻结**,31 路径/43 操作/72 schema;契约一致性测试矩阵 173 格、43/43 操作零漂移 - **部署形态**:docker compose 六容器(postgres:18 + MinIO + auth + user + pet + community),应用容器无状态(ADR-007) - **数据库**:Flyway V1~V5(identity/media、platform 埋点、pet_health、字典种子、community) - **决策**:ADR-001~021 ### 发布门禁证据 | 门禁项 | 结果 | 证据 | | --- | --- | --- | | 三仓 CI 绿 | ✅ | Gitea commit status API 直查 success | | 全量测试 | ✅ | api 334 / flutter 502,`mvnw clean test` 与 `flutter test` 双绿 | | E2E 回归(M2+M3 同环境) | ✅ | **M2 11/11 + M3 14/14**,各连跑 3 轮零 flake,契约偏差 0 — [30 号报告](iterations/iteration-3/30-release-e2e-regression.md) | | M3 验收标准逐条取证 | ✅ | 四条全过 — [28 号报告](iterations/iteration-3/28-e2e-smoke-report.md) | | 契约向后兼容 | ✅ | v1.2.0→v1.3.0 结构化比对:paths/schemas **removed 与 changed 均为 NONE**(严格增量) | | 共享代码面回归分析 | ✅ | M3 全区间 `patbond-pet/src/main/` 0 文件变更;`patbond-common` 仅 ErrorCode +9 行纯新增 | | 凭证防泄漏 | ✅ | `check-secrets.sh --all` 三仓 exit 0(9 规则两层检查,ADR-021) | ### 发布操作记录(首次一次性项) 1. **命名统一**:ADR-011 的 `master` 更正为 `main`(ADR-021);api 本地孤儿 master 已删。 2. **api main 重建**(方案 A,用户拍板):远端 main 原为建仓自动生成的单提交 `ff876bc "Add README"`,与 dev **无共同祖先**,无法 ff 也不宜缝合孤儿历史。操作:Gitea 默认分支临时切 dev → 删除远端 main → `git push origin dev:refs/heads/main` 重建 → 默认分支切回 main。结果:main 41 提交、与 dev 同点位、零 force push。原孤儿提交保留本地备份 ref `refs/backup/old-main-ff876bc`。 3. **flutter main 快进**:main 本就是 dev 祖先,用 `git push origin dev:main` 完成——**较 checklist 第 4 步的 `checkout main && merge --ff-only` 改进**:不切换工作区(当时有 E2E 脚本正在该工作区运行),且非快进推送会被 git 自动拒绝,等于内建 ff-only 保护。 4. **分支保护启用**(checklist 第 6 步,Gitea 平台):api 与 flutter 的 `main` 均已启用,经 Gitea API `GET /repos/{owner}/{repo}/branches/main` 核实: | 仓库 | protected | 推送 | 状态检查上下文 | 所需批准 | | --- | --- | --- | --- | --- | | patbond-api | `true` | 禁用直接推送 | `CI / backend-test (push)` | 0 | | patbond-flutter | `true` | 禁用直接推送 | `CI / flutter-gates (push)` | 0 | | patbond-doc | 未启用 | —— | —— | —— | 两仓 `dev` 均保持 `protected=false`(直推流,ADR-021 分层策略);doc 仓 main 即日常分支、不参与发布分支语义,按规划不设保护。**状态检查上下文显式填写而非留空**:留空时 Gitea 语义为「所有上报的检查都通过」,若某次工作流未触发则空集为真反而放行;写死检查名消除该歧义。 ### 已知遗留(不阻塞发布) **真机验证四项挂起**(步骤已备齐在[真机验证清单](device-verification.md)):媒体上传弱网、乐观更新手感、Feed 图片加载、社区事件落库;另有 M2 两项(Android 事件落库、SessionTracker 30min)。桌面/脚本不可替代——`platform=linux` 埋点整批 400 属契约内行为。 **功能遗留**:完整草稿列表与自动保存、大图下滑关闭手势、`widthPx/heightPx` 恒 null(单图帖回落 4:3)、`eventVersion` 口径未定型、uploading 超时清理任务、429 限流(连带客户端 Retry-After 分支)、话题/关注列表/作者主页(ADR-018 剪出)。 **跨迭代技术债**:access token 黑名单(退出后已签发 access 在剩余 ≤15 分钟内仍有效)、`/internal` 改 mTLS。 ### 发布后生效的纪律 - **`main` 已禁止直接推送**——影响 `main` 的一切变更(含发布本身与 hotfix)一律走 PR,CI 状态检查通过方可合并(ADR-021 强制情形之二正式生效)。`dev` 保持直推流不变。 - **⚠️ 下次发布的姿势与本次不同**:本次首发用 `git push origin dev:main` 直推(当时 main 尚未保护);保护启用后该命令会被拒绝。**此后发布流程为**: 1. 完成 checklist 第 1~2 步(三仓 CI 绿 + E2E 双份回归 PASS); 2. 在 Gitea 上创建 PR:`dev` → `main`(标题写版本号,正文贴门禁证据链接); 3. 等 PR 的 CI 状态检查转绿(即上表的 `status_check_contexts`); 4. 在 Gitea 上合并 PR——因 dev 与 main 无分叉,合并应为快进; 5. 继续 checklist 第 5、7 步(打 tag、写本页记录)。 故 checklist 第 4 步的本地 `merge --ff-only && push` 写法**仅适用于首次发布**(保护启用前),后续版本以上述 PR 流程替代;第 3、6 步为一次性项,不再重复。 - 若某次 PR 显示 `dev` 与 `main` 有分叉(无法快进),说明 main 被绕过 dev 改动过,**先查明原因再合并**,不要用合并提交掩盖。