交付出件箱

← 返回出件箱

门店手机端视光档案线 · 发生产 MR 清单(调查稿 · 未发版 · v2 已按长官补的三条要求修订)

调查时间:2026-09-17
调查范围:origin/test / origin/main 全部真实 git 历史 + sudo db-prod 只读查生产库 +
scripts/release_main.py status/plan 只读子命令真跑(不写 Git/Codeup)
调查人:执行层(sonnet)· 只调查整理,未执行任何发版/写操作

v2 修订说明:长官补了三条要求 —— ①清单只放 feature/store-mobile-refractive-archive
自己的 MR,root 的 #698 单独标"依赖项"不混进清单,但要 verify 会不会带别人提交 ②对照历史发版
经验逐条排查 ③把 root 9-16 口信要点对进清单。本版已按此重写第1、4节,新增第5、6两节。


1 · test MR 全清单(仅 feature/store-mobile-refractive-archive 自己这条线)

查法:git log --merges origin/test --grep="store-mobile-refractive" -i,命中 11 条,其中 1 条是
Merge remote-tracking branch 'origin/test' into feature/...(把 test 合回功能分支的反向合并,
不是 MR,已排除),剩 10 个真实 MR,按合并时间升序:

MR 编号 合并时间(UTC+8) 合并提交 标题 含哪些 commit(简述)
#693 09-16 04:30:55 5cf89cd5 手机端视光档案(批0-批7b) 批0地基/批1儿童列表/批2档案详情+绑定+趋势图/批2.5补端点/批3录入页骨架+AI识别/批3b录入页收尾/批4查看页三Tab/批7a已读回执后端(含 batch7a_conclusion_read_receipt 迁移)/批7b未读徽章/查看页历次对比色误用修复
#694 09-16 13:11:24 46a888d7 趋势图三指标+AI进度面板+测试补齐+两处缺陷修复 趋势图三指标切换+验光方法字段/AI分析分步进度面板/按风险缺口补7用例/解绑失败兜底+重复建档预警
#695 09-16 15:38:47 ca6530c0 长官真机反馈8处修复(徽章/Y轴刻度/头像/年份/已推送内容/折行/单位/负号) 时间线卡徽章文案改「家长未读」/Y轴固定步长+列表页4处对齐/时间线卡徽章基线对齐/查看页4处缺陷
#697 09-16 16:59:53 ad3bfdfd 徽章压矮+智脑分析五处+趋势图对齐平板端+示范档案姓名订正 时间线卡降高/智脑分析与历次对比5处/demo_profile_data_fix2 迁移(订正示范档案姓名)/回退卡片降高压徽章/趋势图对齐平板端
#700 09-16 18:23:00 2040dd1e 录入页两高危+门店结论布局+AI重试+列表贴边+绑定搜索三路+迁移链修正 徽章对齐/列表贴边+趋势图悬浮框/demo_profile_data_fix3 迁移(订正推送快照旧姓名)/绑定候选席位搜索三路(席位号/昵称/手机号)/录入页7项修复(含2高危)/迁移链修正:demo_profile_data_fix3 改挂 phase81_001(按 root 9-16 口信)
#701 09-16 19:15:07 340af781 tooltip接线+曲线动画恢复+重试按钮对齐平板端 tooltip真接线+恢复曲线动画+重试按钮对齐
#702 09-16 20:00:47 609138d2 列表贴边ml-auto+眼位记法+孤行换行修复 列表贴边改ml-auto+眼位记法+两处孤行换行
#703 09-16 22:31:32 d9d37d75 视功能档位选择器+本次未做对齐+日期范围+推送状态块定稿 视功能档位选择器+两处行为对齐平板端+推送状态块定稿
#704 09-16 23:10:21 9e5ac47e 已读回执补齐家长端接线+录入保存后跳查看页 已读回执补齐家长端接线+录入保存后跳查看页对齐平板端
#705 09-16 23:58:27 61dcf7e5 编辑保存报错修复+三处破坏性操作补二次确认 编辑保存报错修复+三处破坏性操作补二次确认

这 10 个是本次发版清单的全部主体,共 34 个非 merge commit(release_main.py plan 实跑核实,
见第5节)。

🔴 依赖项(不属于本清单,但发布时必须一并点名)

MR #698(root · feat/phase81-refractive-4l-grounding 分支 · 09-16 17:41:54 合并 502764dd,
标题「Phase 81.8 示范档案 4L 新口径冻结迁移 + 平板 UAT 截图存证」)——只带一个纯数据迁移
phase81_001_demo_profile_refresh.py(冻结示范档案6条AI分析文案为4L新口径)+ 文档/截图,
不含任何业务代码改动。它被夹在我们的 #697 和 #700 之间,原因是我们 #700 里的
demo_profile_data_fix3 迁移的 down_revision 改挂到了 phase81_001(见 #700 那行的"迁移链修正")
—— 这条依赖关系是我们自己代码里写死的,不是 root 强加的。

时间窗内 root 同一分支还有 #692(09-16 00:54)和 #696(09-16 16:36)两个 MR,均无迁移文件、
纯代码/prompt 改动 —— 这两个不在依赖链上,不需要跟着发(第6节详述 root 口信落点)。


2 · 迁移文件落在哪个 MR(逐条 git merge-base --is-ancestor 验证,非凭印象)

先物理确认迁移链的 revision/down_revision(注意 revision 标识与文件名不完全一致,root 9-16 提醒
过这个坑:文件名 batch7a_conclusion_read_receipt.py 但 revision 是 batch7a_read_receipt):

migrations/versions/batch7a_conclusion_read_receipt.py   revision=batch7a_read_receipt      down_revision=demo_profile_data_fix1
migrations/versions/demo_profile_data_fix2.py             revision=demo_profile_data_fix2    down_revision=batch7a_read_receipt
migrations/versions/phase81_001_demo_profile_refresh.py   revision=phase81_001               down_revision=demo_profile_data_fix2
migrations/versions/demo_profile_data_fix3.py              revision=demo_profile_data_fix3    down_revision=phase81_001

链是单线不分叉(已用 grep -rl 'down_revision = "xxx"' 逐个反查,每个 revision 只有唯一下游,
demo_profile_data_fix3 是当前链尾,无人以它为 down_revision)。

用 git merge-base --is-ancestor <引入迁移的 commit> <各 MR 合并提交> 逐个验证「首次落在哪个 MR」:

迁移 revision 引入 commit 首次落在 验证方式
batch7a_read_receipt 5e99e8b2 #693(本线) is-ancestor 对 #693/#694/#695/#697/#698/#700-705 全为真,#693 是最早一个
demo_profile_data_fix2 e21ea8b3 #697(本线) is-ancestor 对 #697 起为真,#693/#694/#695 为假
phase81_001 c41a1042 #698(root · 依赖项) is-ancestor 对 #698 起为真,#693-#697 为假
demo_profile_data_fix3 f3f60460 #700(本线) is-ancestor 对 #700 起为真,#693-#698 为假

3 · 生产现状核对

3.1 迁移文件是否已在 origin/main

查法:git grep -l '^revision = "<id>"' origin/main -- migrations/(锚定行首,避免误命中其他文件的
down_revision 引用同名字符串——root 明确提醒过"别用文件名 cat-file 猜"这个坑)。

对照组(证明查法有效):demo_profile_data_fix1 已知在 main(它是上一轮 MR#687 发布内容),
查询返回 origin/main:migrations/versions/demo_profile_data_fix1.py,命中 → 查法有效。

revision 在 main 上?
demo_profile_data_fix1(对照组) ✅ 命中
batch7a_read_receipt ❌ 未命中
demo_profile_data_fix2 ❌ 未命中
phase81_001 ❌ 未命中
demo_profile_data_fix3 ❌ 未命中

3.2 生产库真实 alembic 版本(sudo db-prod 只读查)

SELECT version_num FROM alembic_version;
      version_num
------------------------
 demo_profile_data_fix1
(1 row)

与 git 层面核对完全一致:生产真实停在 demo_profile_data_fix1,下一环正是
batch7a_read_receipt,链条无缺环、无需要跳过的中间版本。这也印证了 root 口信里
"代码新、数据旧"的说法(见第6节)——生产的 alembic 版本号确实还没往前走一步。

3.3 内容层面追加核对(#698 迁移安全性)

phase81_001_demo_profile_refresh.py 及另外三条迁移全部只用 op.execute 操作物理表,
grep -n "^import app\|^from app" 对四个文件均 0 命中——不依赖任何业务代码运行时。
这意味着即便 root 那条分支的代码改动(#692/#696)是否已完整上生产存疑,也不影响
phase81_001 这条纯数据迁移的可发布性——它只改 6 行示范档案的 ai_analysis_text,
幂等 + 三重 WHERE 限定(固定 UUID + is_sample=true + 值不同才更新)。


4 · 最终发版清单 + 风险提示

4.1 要点名的 MR 编号(按合并时间升序;工具 9-15 起会自动按 test 合入顺序重排,书写顺序不敏感)

--mr 693 --mr 694 --mr 695 --mr 697 --mr 698 --mr 700 --mr 701 --mr 702 --mr 703 --mr 704 --mr 705

共 11 个 MR(本线10个「见第1节」 + root 依赖项 #698「见第1节末尾」)。

4.2 会不会把别人夹在 test 里的提交带上生产?—— 已 verify,结论:不会

结论先行:用真实 release_main.py plan(只读,不写 Git/Codeup)对这 11 个 MR 号跑了一遍,
逐 MR 的 commit_ids 与 source_branch 精确对应各自的分支和提交,没有出现属于第三方分支的
提交或文件。理由分三层:

① 时间窗地毯式核查:git log --merges origin/test --since="2026-09-15 23:00" --until="2026-09-17 01:00"
把这个时间段内所有合入 test 的 merge 全部列出(不限定分支关键词),结果只有两条分支活跃:
我们的 feature/store-mobile-refractive-archive(10个MR)和 root 的
feat/phase81-refractive-4l-grounding(#692/#696/#698 三个MR)。没有第三个会话的 MR 夹在这个窗口里。

② 工具机制本身按 MR 隔离,不按时间/分支整体拉平:读了 scripts/release_main.py 源码
(build_release_plan 函数),它对每个点名 MR 只取
git rev-list --no-merges <该MR合并提交的第一父>..<该MR合并提交的第二父>——即“合并前的 test 状态”
到“功能分支尖端”之间的非合并提交,天然排除了已经在 test_parent 里的所有其他人的提交。
且工具显式检测“嵌套合并”(功能分支中途合回 test 解冲突产生的 merge):只有当该嵌套合并的所有
非首父提交都是 test_parent 的祖先时才放行展开,否则直接 ReleaseGuardError 中止——这道判定专门
防"合并回 test 时顺带夹带了外来提交"。我们的 #700 范围内确实有一个
Merge remote-tracking branch 'origin/test' into feature/store-mobile-refractive-archive
(67593559,09-16 18:16),已验证它合入的 origin/test 内容(含 #698)在 #700 合并时已经是
test_parent 的祖先,没有触发 foreign_merges 报错。

③ 真跑 plan 子命令拿到的实证(只读,build_release_plan 不写 Git/Codeup):

$ python3 scripts/release_main.py plan --repo <worktree> --test-ref origin/test --main-ref origin/main \
    --mr 693 --mr 694 --mr 695 --mr 697 --mr 698 --mr 700 --mr 701 --mr 702 --mr 703 --mr 704 --mr 705 --json

mr count: 11
commit count: 39
changed files count: 47
migration files: [batch7a_conclusion_read_receipt.py, demo_profile_data_fix2.py,
                   demo_profile_data_fix3.py, phase81_001_demo_profile_refresh.py]
693 → feature/store-mobile-refractive-archive
694 → feature/store-mobile-refractive-archive
695 → feature/store-mobile-refractive-archive
697 → feature/store-mobile-refractive-archive
698 → feat/phase81-refractive-4l-grounding    ← 与 root 说法一致,且工具自动认出这不是同一分支
700 → feature/store-mobile-refractive-archive
701 → feature/store-mobile-refractive-archive
702 → feature/store-mobile-refractive-archive
703 → feature/store-mobile-refractive-archive
704 → feature/store-mobile-refractive-archive
705 → feature/store-mobile-refractive-archive

旁证(证明"漏掉依赖项会被拦,不是理论推演"):把 #698 从点名列表里去掉重跑 plan,
结果 仍然 exit 0(plan 本身不校验迁移祖先链,只是纯计算),但 migration files 变成只有
3 个(phase81_001_demo_profile_refresh.py 消失),而 demo_profile_data_fix3.py
(down_revision = phase81_001)依然在列——这正是 assert_migration_ancestors_on_main
设计要拦的场景。该守门只在 publish 阶段(建 release 分支/MR 之前)才会触发,plan/status
阶段不会报错,发布前必须靠人工核对迁移链(已在第2节做完),不能只跑 plan 看 exit code 就放心。

4.3 历史经验对照 → 本次是否适用(逐条,详见第5节完整表格)

4.4 其余风险提示

  1. #698 不属于本功能线分支,必须与本线 10 个 MR 一并点名,否则 demo_profile_data_fix3
    建分支阶段会被 assert_migration_ancestors_on_main 拦下(已用第4.2节的"去掉#698重跑"实测坐实)。

  2. root 同一分支的 #692、#696 不在本清单里,原因见第6节——它们与本次迁移链无依赖关系,
    是否需要跟着发属于另一条决策,不能默认"发了我们这11个就等于把 root 那条线发全了"。

  3. git log origin/main 的实际 HEAD(d0428e78,MR#699"release: MR 696 选择性晋级 main",
    合并时间 09-16 18:09:56)与本任务开始时的 worktree 快照信息里的旧 HEAD(b282c143,MR#687)
    不一致 —— origin/main 已经比对话开始时的快照更新,发版前请以 git fetch 后的 origin/main
    为准。

  4. MR#699 用的是"选择性晋级"(cherry-pick 到新 release/* 分支),产生的是新 commit SHA,
    不是原 origin/test 上 96638b64 的祖先 —— 用 git merge-base --is-ancestor 直接查原始
    合并提交会误判为"不在 main"(本次已验证到这个坑,改用文件内容 diff 比对绕过,见第6节)。

  5. 生产部署每轮跑 alembic upgrade heads(复数),会把 main 上所有头待跑的迁移一并执行 ——
    本次点名的 11 个 MR 只带 4 个迁移且构成单线链(第2节已验证无分叉),没有夹带其他游离头的风险。


5 · 历史经验对照表(逐条读 memory · 判断本次是否适用)

来源 memory 坑/经验 本次是否适用 怎么避 / 已怎么避
prod-release-ledger.md 含 migration 的 MR 要单独标红报长官 ✅ 适用 4 个迁移已在第1/2节单独标出,均为纯 op.execute,无 nginx conf 改动、无需 reload
prod-release-ledger.md 合并 MR = 立即发生产,合并前是最后检查点 ✅ 适用 本报告就是"合并前检查",严禁在本任务内跑发版命令
release-verify-methodology.md §2 断言远端状态前必先 git fetch ✅ 已做 开工先 git fetch origin --quiet
release-verify-methodology.md §3 run 号必须实查不许顺延猜 ⏸ 不适用(本任务不发版) 留给真正执行发版的会话,发完按此条验证
release-verify-methodology.md §7 有迁移的发版要真库排练 + 读流水线 alembic 输出 ⏸ 部分适用 本次已用 sudo db-prod 查证生产真实版本号 = 迁移链上一环,链路对得上;流水线侧留给发版会话
release-verify-methodology.md §8 隔离 cherry-pick 让 main/test SHA 永久分叉是常态,别用"内容不同"就判断没发过 ✅ 已避坑 判"是否已发"用 revision 锚点 grep + db-prod,不用内容 diff
release-prod-lessons-20260907.md 教训1 main..test 比对法误判(merge commit 永远不消失) ✅ 已避坑 全程未用此法;改用 revision 锚点 grep(§3.1)+ db-prod 真查(§3.2)
release-prod-lessons-20260907.md 教训2 功能跨多分支只查一条分支必漏 ✅ 已避坑 §4.2① 做了时间窗地毯式扫描(不限分支关键词),确认只有本线+root线两条分支活跃,没漏第三条
release-prod-lessons-20260907.md 教训3 git show --name-only <merge> 对合并提交返假0 ✅ 已避坑 全程用 ^1..^2 区间或 plan 工具输出取文件/提交列表,没用这个会返假0的写法
release-prod-lessons-20260907.md 教训4 生产部署跑 alembic upgrade heads(复数),会带上所有头 ✅ 已核查 4 个迁移构成单线链(§2 已验证无分叉),main 上没有额外游离头会被夹带
release-prod-lessons-20260907.md §6 判断"是否已发生产"三种查法两种错,唯一准的是 main 提交消息里的挑取标记 ✅ 已避坑 本次用 revision 锚点 grep(非 is-ancestor、非内容diff)+ db-prod 双重交叉,规避了这三种查法里错的两种
release-prod-lessons-20260907.md §11 发版先跑 status 只读子命令拿"待发编号" ✅ 已真跑 见第4.2节③,status 输出 11 个 MR 全部"未发",与 §3 的 git/db 核对完全一致
how-to-run-release-to-prod-correctly.md 真发版要用 --yes --auto-merge,禁止 printf 管道喂 y ⏸ 不适用(本任务不发版) 留给真正执行发版的会话
pitfall-release-branch-name-and-script-version.md 坑一 MR 号超长拼分支名 → Codeup 建 MR API 500,阈值约15个 ⚠️ 接近阈值 本次11个MR,建议真发版时主动带 --branch release/短名,已写入§4.3
pitfall-release-branch-name-and-script-version.md 坑二 手里的 release_main.py 可能是旧版 ✅ 已核查,新版 grep -c _API_MERGE_SUBJECT_RE scripts/release_main.py = 2(新版);worktree 版与可信副本 /usr/local/lib/release/release_main.py md5 均为 fd4d976526d280b4487416c4dcf6396e,逐字节一致、无漂移
release-tool-upgrades-20260915.md MR 自动按 test 合入顺序排序;空提交不再误判冲突;部署实证加认迁移失败 ✅ 已知悉 §4.1 已注明书写顺序不敏感;本次11个MR均为正常提交非空提交,不涉及该分支
pitfall-yunxiao-all-pipelines-down-since-0828.md 判部署成败别看云效流水线颜色,生产走自建 cron 拉取;判据是 sudo db-prod 查 alembic_version + 前端 chunk 指纹 ⏸ 不适用(本任务不发版) 留给真正执行发版后的验证会话按此判据核实

6 · root 口信落点(9-16 · 逐条对我们清单的影响)

root 说的 核实结果 对清单的影响
他的代码线 #696 已上生产(main 到 d0428e78,18:25 部署成功) origin/main HEAD 确实是 d0428e78(MR#699"release: MR 696 选择性晋级 main",合并时间 09-16 18:09:56,与"18:25 部署成功"时间线吻合)。内容层面抽查:app/services/four_l_summary_prompt.py 与 #696 引入版本 diff 为 0 行,吻合"代码已发"。refractive_record_service.py diff 706 行(未细究原因,可能是后续其他改动叠加),不影响本清单判断 不影响本清单——#696 无迁移文件,已确认与本次迁移链无依赖
数据线 #698 没发 已用 revision 锚点 grep 确认 phase81_001 不在 origin/main;sudo db-prod 查生产 alembic_version = demo_profile_data_fix1,链条上还没走到 phase81_001 那一环 与本次调查完全吻合,#698 作为依赖项列入待发清单
生产现在是"代码新、数据旧":新分析走新口径,示范档案6条历史文案还是老的 逻辑自洽:代码(#696)已上但数据迁移(phase81_001,#698)没上,示范档案是 _patient_is_sample 短路不会自动重算的静态展示数据,所以文案仍是老口径 说明 #698 这条数据迁移发布紧迫性较高(现网示范档案文案与新代码口径不一致,属于可见的用户体验问题)
迁移链:demo_profile_data_fix2 → phase81_001(他) → demo_profile_data_fix3(我们),另 batch7a_read_receipt 也没进 main 物理链核实为四环:demo_profile_data_fix1(main已有) → batch7a_read_receipt → demo_profile_data_fix2 → phase81_001 → demo_profile_data_fix3,root 描述的三环是完整链的后三环,batch7a_read_receipt 是他额外提醒的第四环,两者合起来与本报告第2节一致 确认清单第2节链条完整,4个迁移一个不漏
"你们这边儿一并发布" = 发布权在我们,发我们时把他的 #698 带上 已按此口径处理:清单主体是我们10个MR,#698 作为"依赖项"单独标注,一并点名 第1节末尾、第4.1节均已体现
判某迁移在不在 main,别用文件名 cat-file 猜(文件名与 revision 不一致),要 git grep -l '^revision = "xxx"' 锚行首 + 对照组 本报告第3.1节完全按此方法执行,并带对照组(demo_profile_data_fix1) 已落实,无偏差

附:关键命令留痕(供复核)

# 1. 找本线 MR
git log --merges origin/test --grep="store-mobile-refractive" -i --pretty=format:"%H|%ad|%s" --date=iso

# 2. 时间窗地毯式扫描(不限分支关键词,核实无第三方MR夹带)
git log --merges origin/test --since="2026-09-15 23:00" --until="2026-09-17 01:00" --pretty=format:"%H|%ad|%s" --date=iso

# 3. 迁移落点(以 demo_profile_data_fix2 为例)
git merge-base --is-ancestor e21ea8b3947cce602c2882ec0f74eebe53125de4 ad3bfdfdbd9428f3fc7205ebdabaa7b1fa5b34b1  # 对 #697,exit 0=真

# 4. 生产现状(锚定行首 + 对照组)
git grep -l '^revision = "demo_profile_data_fix1"' origin/main -- migrations/   # 对照组,命中
git grep -l '^revision = "batch7a_read_receipt"' origin/main -- migrations/    # 目标,未命中

# 5. 生产库真实版本
sudo db-prod "SELECT version_num FROM alembic_version"

# 6. 只读发版计划(不写 Git/Codeup)—— 验证"不带别人提交"
python3 scripts/release_main.py plan --repo <worktree> --test-ref origin/test --main-ref origin/main \
  --mr 693 --mr 694 --mr 695 --mr 697 --mr 698 --mr 700 --mr 701 --mr 702 --mr 703 --mr 704 --mr 705 --json

# 7. 只读待发状态(不写 Git/Codeup)
python3 scripts/release_main.py status --repo <worktree> --test-ref origin/test --main-ref origin/main \
  --mr 693 --mr 694 --mr 695 --mr 697 --mr 698 --mr 700 --mr 701 --mr 702 --mr 703 --mr 704 --mr 705

# 8. 版本/漂移自查
grep -c "_API_MERGE_SUBJECT_RE" scripts/release_main.py     # 期望非0 = 新版
md5sum scripts/release_main.py /usr/local/lib/release/release_main.py  # 期望一致 = 无漂移

本报告只调查整理,未执行任何 release-to-prod / git 写操作 / 生产库写操作。