- iteration-3 报告 01-08(PM 拆解/后端/Flutter/RC 首个 CERTIFIED/UI/埋点/证据基线/Git) - ADR-016 自托管 MinIO 起步预留迁云(用户确认现无云存储)、ADR-017 patbond-community :8084 + media 归 user + 作者信息跨 schema 只读、ADR-018 范围裁剪(话题剪出/单层评论)、 ADR-019 幂等按域(PUT/DELETE + request_hash)、ADR-020 聚合 feed_viewed/字典 v3/ 北极星不变/队列三项升第一波、ADR-021 Git 修订(main 更正/PR 情形触发/首发布重建 main/ 防泄漏 grep 先行) - mkdocs 挂「第三迭代」导航,build --strict 通过 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 KiB
08 M3 Git 与 CI 工作流核查规划
- 执行人:Git Workflow Master
- 日期:2026-09-08
- 范围:第三迭代(M3 社区)开工前的三仓状态核查、ADR-011 PR 条款实践复盘、发布分支启用规划、对象存储凭证防泄漏、CI 增量评估。本报告只核查与规划,未改动任何代码、工作流或 mkdocs.yml,未执行 commit/push。
1. 三仓当前状态核查(2026-09-08 实测)
| 仓库 | 分支 | 相对 origin | 工作区 | stash | 最新提交 CI 状态 |
|---|---|---|---|---|---|
| patbond-api | dev | 同步(fetch --prune 后确认) | 干净 | 无 | success(64c9b72,CI / backend-test,run 31,5m18s) |
| patbond-flutter | dev | 同步 | 干净 | 无 | success(720865b,CI / flutter-gates,run 37,2m12s) |
| patbond-doc | main | 同步 | 干净 | 无 | success(e68b655,CI / docs-build,run 39,29s) |
CI 状态经 Gitea commit status API 逐仓核实,非转述。未提交内容清单:无(本报告文件本身除外,按波次规则随下一波提交)。M2 收官时的「三仓 commit + push + CI 绿」闭环纪律保持完好。
1.1 历史遗留分支的新发现(比 M2 报告掌握的更严重一档)
M2 报告只记录了「api 本地孤儿 master 上游已删」。本次为发布分支规划做了祖先关系核查,发现:
- patbond-api 的
ff876bc(本地孤儿 master、远端origin/main共同指向的初始 README 提交)不是 dev 的祖先——git merge-base --is-ancestor ff876bc dev判定失败,dev 的根提交是b1252b9(Initialize patbond microservice modules)。即 api 远端默认分支 main 与 dev 是两条不相干历史(unrelated histories)。这直接影响第 3 节「dev→发布分支」怎么做第一次合并。 - patbond-flutter 的
main(030b11f)是 dev 祖先,未来 dev→main 可干净 fast-forward。 - M2 报告建议的「核实后删本地孤儿 master」当时的前提(
ff876bc已被 dev 包含)实测不成立,但结论不变:该提交仅是初始 README,远端origin/main仍保留它,本地git branch -D master无信息损失,可顺手做。
2. M2 工作流实践复盘:ADR-011「高风险走 PR」条款何去何从
2.1 实践事实(git log + Gitea Actions 全量核查)
- PR 使用次数:0。 三仓 M2 期间(09-07 至 09-08)无任何 merge commit,历史全程线性。
- 四类「高风险」全部直推了:Flyway V3/V4(
49299fb,T2-01)、契约冻结 v1.2.0(doc511617b)、事件字典白名单扩充(64c9b72)均直推 dev/main。 - 风险事件清点:零。 具体证据:
- M2 期间三仓 CI 零失败——Actions 全量 run 列表中的 7 次 failure 全部集中在 09-04(M1 末 CI 搭建期),且全是流水线自身配置问题(外部 action 不可达、JDK 安装方式、format 未跑),无一是业务代码直推打红 dev;
- 零 revert(
--grep回退/回滚/revert 无命中); - 迁移不可变规则守住了:V3/V4 文件推送后零修改(
git log --follow各只有一次提交); - 契约冻结守住了:
openapi.yaml在冻结提交511617b之后零改动; - 无 force push 痕迹(线性历史 + 各推送头全绿)。
2.2 为什么直推没出事——机制归因,而非运气
M2 的安全性不是来自 PR 的缺席碰巧无事,而是四道机制已经覆盖了 PR 想防的东西:
- Flyway 迁移:
./mvnw clean test经 Testcontainers 起真库执行完整迁移链,每次 push 都等于迁移演练——PR 合入前 CI 与 push 后 CI 跑的是同一条命令,对串行开发者而言只差「红了是否已在 dev 上」,而两人+AI 模式下红 dev 的传播面就是自己。 - 契约破坏:T2-09 契约一致性测试把破坏性变更变成红测试,比人工 PR review 更机械可靠。
- 波次收尾 compose 实测 + E2E 烟囱兜住了集成层。
- 串行作业:M2 全程实质单线程推进(AI 辅助不产生 git 并发),四类触发条件中真正指向并发风险的「两人并行期」从未发生。
2.3 结论建议(待拍板 #1):条款降级为「按情形触发」,不是纪律失效
判定:这不是纪律失效,是条款的触发条件设计错了——它按「改动类别」(迁移/契约/依赖)触发,而 M2 证明这些类别在串行+CI 全量门禁下并无 PR 才能拦住的残余风险。真正需要 PR 的是「情形」:
- 建议修订 ADR-011 备注:Flyway 迁移、契约变更、依赖升级在串行开发期直推 dev + CI 绿 + 波次实测即为足够实践,不再列为 PR 推荐触发项;
- PR 保留为强制的仅两种情形:
- 两人并行改同一仓库期间(唯一真实的并发冲突风险源);
- 首次 dev→发布分支合并之后,凡影响已发布版本的破坏性变更(不可变迁移的例外处理、已冻结契约的破坏性修订、发布分支 hotfix)——发布后爆炸半径从「自己人」扩大到「装了 App 的用户」,性质不同。
- 三仓 ci.yml 的
pull_request:触发器保留不动(零成本待命);M2 待拍板 #2 的分支保护同理降级为「随首次发布对发布分支启用」,dev 不开(见 3.3 checklist)。
这样条款从「写了但没人执行的推荐」变成「触发即无争议的强制」,规范与实践重新一致。
3. M3 发布分支启用规划
3.1 先解决命名与历史两个前置问题
命名不一致(待拍板 #2):ADR-011 写的是「master 保留为发布分支」,但远端实况是:api 的 origin/master 已删除(默认分支为 main)、flutter/doc 默认分支均为 main,三仓远端今天没有任何一个 master 分支。建议:统一以 main 为发布分支名,修订 ADR-011 措辞(master→main),顺手删除 api 本地孤儿 master(§1.1,无信息损失)。反向方案(重建三仓 master)多一次全员改默认分支操作,无收益。
api 的 main 与 dev 历史不相干(待拍板 #3):origin/main(ff876bc)不是 dev 祖先,首次 dev→main 无法 fast-forward,普通 merge 需要 --allow-unrelated-histories 且会把一条孤儿历史永久缝进发布线。三个选项:
| 选项 | 操作 | 评价 |
|---|---|---|
| A(推荐) | Gitea 仓库设置将默认分支临时切到 dev → 删除远端 main → 从 dev 重建 main → 默认分支按需切回 | 零 force push、历史干净,纯平台操作 |
| B | 一次性 git push --force origin dev:main,在 ADR 中记录为例外 |
结果等价,但破「不 force push 共享分支」戒律,留坏先例 |
| C | merge --allow-unrelated-histories |
永久保留无意义的孤儿历史缝合点,不推荐 |
flutter 无此问题(main 是 dev 祖先,直接 ff);doc 仓 main 即日常分支,不参与发布分支语义。
3.2 何时启用:建议 M3 末做第一次 dev→main 发布(待拍板 #4)
理由:M2 收官已具备「E2E 烟囱脚本 + 全绿测试基线 + 冻结契约」的可发布形态,缺的只是发布动作本身;北极星指标出数(ADR-012,M3 末 A/B 前置目标全绿)需要一个稳定版本承载;再往后拖,发布流程的首次演练会和 M4 首实验挤在一起。M3 末做第一次,把流程走通比版本内容重要。
3.3 发布 checklist 草案(首次发布用,验证后固化进 git-workflow.md)
- 冻结:发布波次收尾,三仓 commit + push + CI 绿(既有纪律);
- 实测:compose 全栈起,跑 M2+M3 两份 E2E 烟囱脚本,全场景 PASS,证据入波次报告;
- 前置一次性项(仅首次):完成 §3.1 的命名统一与 api main 重建;
- 合并:api/flutter 各执行
git checkout main && git merge --ff-only dev && git push origin main(此后每次发布 dev→main 都应 ff-only 可过,过不了说明 main 被绕过 dev 改动,先查明); - 打标:两仓
git tag -a v0.3.0 -m "M3 社区"(版本号待拍板时一并定)并 push tag;doc 仓同点位打同名 tag,三仓互为对照; - 平台侧:Gitea 为 api/flutter 的 main 开启分支保护(禁直推、合并需 CI 状态检查通过)——dev 仍不开,保持直推流;
- 记录:发布说明入 doc 仓(版本、三仓 tag 哈希、E2E 证据链接、已知遗留);
- 发布后:影响 main 的 hotfix 一律走短命分支 + PR(§2.3 强制情形之二正式生效)。
4. 对象存储凭证防泄漏(M2 方案未实施,重新评估)
4.1 现状核查
- M2 报告 §5 的两层纯 shell 方案零实施:三仓均无
scripts/hooks/,core.hooksPath均未设置,ci.yml 均无检查 step。 - M2 没出事的原因和 PR 条款同理:M2 引入的凭证(RS256 密钥对、DB 密码、internal token)全部由
deploy/init-secrets.sh生成且不入库,*.sample占位约定执行到位——但这套卫生依赖「凭证只在本机生成」这个前提。
4.2 M3 威胁面变化:这次不一样,建议先落第二层(待拍板 #5)
M3 的对象存储凭证(ADR-010 剪出项回归:COS/OSS/MinIO 的 AccessKey/SecretKey)与 M2 的密钥有本质区别:它是云厂商控制台签发的长期凭证,泄漏即可被外部直接使用且常绑计费,不是本机自生成的内部秘密。AI 辅助开发下,凭证从「配置文件」流向「示例代码/测试/报告」的路径变多,纯约定不够。重新评估结论:
- 第二层(CI 兜底 grep)从「可选」升为「M3 第一波、对象存储凭证进入任何开发机之前必须上线」。各仓 ci.yml 加一个纯 shell step(零外部依赖,秒级),模式清单在 M2 方案基础上增补云凭证特征:
AKID[A-Za-z0-9]{13,}(腾讯云)、LTAI[A-Za-z0-9]{12,}(阿里云)、(access|secret)[-_]?key\s*[:=]后跟非占位值、40 位以上连续 base64/hex;文件名黑名单增补.env、credentials、*.csv(控制台导出的密钥文件形态)。 - 第一层(共享 pre-commit 脚本)维持推荐;若继续搁置,第二层单独上线也成立(拦「已提交的」比拦「即将提交的」在两人团队更关键——push 即触发,无
--no-verify逃逸)。 - 沿用 M2 结论:不引入 gitleaks 等外部工具;真泄漏的第一动作是去云控制台轮换/禁用密钥,历史清理其后——此条随实施写进 git-workflow.md。
- 实施时顺手核对三仓 .gitignore 对
.env的覆盖(api 仓 compose 依赖.env,规则应已有,实施时以git check-ignore取证)。
5. CI 增量评估
5.1 新模块接入:确认零成本(同仓模块方案下)
patbond-api 是 maven 聚合工程(根 pom <modules> 现有 common/user/auth/pet 四个)。若 M3 沿 ADR-009 模式在 api 仓内新建 patbond-community / patbond-media 模块:根 pom 加一行 <module>,ci.yml 零改动,./mvnw -B clean test 自动覆盖新模块(含其 Testcontainers 测试)。确认零成本,无待拍板。
仅当选择独立新仓(当前无此计划)才有增量:复制既有 ci.yml(三仓模板已统一:手动 checkout + apt/镜像装工具链)+ 仓库设置启用 Actions,runner 是实例级共享的,无需新注册,估计半小时内。
5.2 E2E 烟囱进 CI:技术可行但不建议进 push 门禁(待拍板 #6,倾向不做)
可行性核查(基于 ci-runner-setup.md 与 act_runner 现状):
- docker.sock 已挂进 job 容器(Testcontainers 依赖,实测可用),job 内跑
docker compose up起的是宿主 sibling 容器——技术上通。 - 但有四项实际成本:
- 网络:compose 端口发布在宿主,job 容器内
127.0.0.1:8081-8083不可达,E2E 脚本的 base URL 需改造为可注入,并让 job 容器走宿主网关 IP 或直接加入 compose 网络; - 工具链:job 镜像需补 docker CLI + compose 插件;
- 时长:
mvnw package+ 三个镜像 build + 全栈起 + 11 场景,估计给流水线加 5–10 分钟(现 backend-test 5m18s,翻倍以上); - 跨仓:脚本在 flutter 仓、compose 在 api 仓,任一仓的 push CI 跑它都要 clone 另一仓,触发归属含糊。
- 网络:compose 端口发布在宿主,job 容器内
- 建议:push 门禁维持现状(快、单仓、职责清晰);E2E 保持 M2 已验证的「波次收尾手动跑、证据入档」模式,并作为发布 checklist 第 2 步的强制项。若要自动化,做成独立的
workflow_dispatch手动触发工作流(发布前一键跑),M3 内低优先,不占开工路径。
6. 待拍板事项汇总
| # | 事项 | 推荐 | 见 |
|---|---|---|---|
| 1 | ADR-011 PR 条款降级:类别触发(迁移/契约/依赖)取消,改为仅「两人并行同仓」与「首次发布后影响 main 的变更」两种情形强制 PR;分支保护随之改为只对发布分支启用 | 采纳修订 | 2.3 |
| 2 | 发布分支统一命名为 main(修订 ADR-011 的 master 措辞),顺手删 api 本地孤儿 master |
采纳 | 3.1 |
| 3 | api 远端 main 与 dev 历史不相干的一次性处理:Gitea 平台删除重建(选项 A) | 选项 A | 3.1 |
| 4 | M3 末执行第一次 dev→main 发布,采纳 §3.3 checklist(含版本号定名) | 采纳 | 3.2/3.3 |
| 5 | 防泄漏第二层(CI 兜底 grep + 云凭证模式增补)升为 M3 第一波必做、先于任何对象存储凭证落地;第一层 pre-commit 维持推荐 | 采纳 | 4.2 |
| 6 | E2E 烟囱不进 push 门禁;可选做 workflow_dispatch 手动工作流(低优先) | 不进门禁 | 5.2 |
采纳后需要落实的改动(本报告未执行):ADR-011 修订、git-workflow.md 增补(PR 情形条款、发布流程、泄漏应急)、三仓 ci.yml 加防泄漏 step、api main 重建操作、本报告挂入 mkdocs 导航。