From 08a0d3deb1219e55b6c4c2ce362b438855b01d7d Mon Sep 17 00:00:00 2001 From: ReSerendipity Date: Tue, 22 Sep 2026 21:42:18 +0800 Subject: [PATCH 1/2] =?UTF-8?q?docs(release):=20=E6=8A=8A"=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8F=91=E7=89=88=E8=B7=AF=E5=BE=84=E4=BB=8A=E5=A4=A9?= =?UTF-8?q?=E8=B5=B0=E9=80=9A=E4=BA=86"=E5=86=99=E6=88=90=E5=8F=AF?= =?UTF-8?q?=E5=A4=8D=E7=94=A8=E7=9A=84=E6=93=8D=E4=BD=9C=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E9=92=89=E4=BD=8F=E7=AC=AC=2010=20=E5=A4=84?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v2.2.5 是本仓第一条由 release-please 自己发出去的版本(tag 9125a3e、Release + 4 个资产由 build-release 挂上)。这轮踩到的三件事都写进 §1,否则下次还是靠人记: 1. RP 只抬 5+1 处自动位,5 类手工位要人在 release 分支上补一条提交 —— 而且**补完必须紧接着合**: 任何一次 main push 都会让 RP 重写那条分支,把补齐提交冲掉(今天真冲掉过一次 1c2597a)。 2. DCO 曾让 release PR 结构上不可合(RP 的提交作者是 github-actions[bot],签不出 Signed-off-by), #135 已按提交作者豁免。 3. release PR 拿不到 CI(#134 的 release-gate 补了可见信号;合并前判据仍要本地跑 scripts/check_release_readiness.py)。 同时把一句散文纳入闸:§1 开头的"已发布最新 = v"。它 RP 不碰,于是 v2.2.5 发完后 这里还停在 v2.2.4 —— 现在 test_all_version_sites_agree 把它当第 10 处版本位核对。 变异复验:把该行改回 v2.2.4 → 那条闸红;改回来 → 8 passed。 test_release_readiness_gate 的 fixture 同步搬这个文件(否则它会以"取不到版本号"失败而不是判出漂移)。 16 passed(版本位 8 + 发布条件闸 4 + DCO 豁免 4);ruff check / format --check 通过。 Signed-off-by: ReSerendipity --- docs/release-governance.md | 30 ++++++++++++++++++++++------ tests/test_release_readiness_gate.py | 5 +++++ tests/test_version_consistency.py | 7 +++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/docs/release-governance.md b/docs/release-governance.md index 5d4eb7d..d52ba50 100644 --- a/docs/release-governance.md +++ b/docs/release-governance.md @@ -8,7 +8,7 @@ ## 1. 版本号规范 -- 遵循 SemVer `MAJOR.MINOR.PATCH`。**已发布最新 = v2.2.4(2026-09-22)**。 +- 遵循 SemVer `MAJOR.MINOR.PATCH`。**已发布最新 = v2.2.5(2026-09-22)**。 版本号出现在 11 处(`version.json`/`pyproject.toml`/`config.yaml`/`desktop/*`/`scripts/installer/setup.nsi`), 但只有 tag + GitHub Release 同时存在才算发出去;核对:`gh release view v<版本>`。 > 其中 `desktop/package-lock.json` 在 `.gitignore` 里(不是仓库内的版本位);仓库内跟踪的 9 处 @@ -28,17 +28,35 @@ > `scripts/installer/setup.nsi` 的 `OutFile`/`APP_VERSION`/`VIProductVersion` > (NSIS 注释符是 `;`,用不了 `generic` 要求的 `# x-release-please-version` 行内标记)、 > `deploy/kubernetes/deployment.yaml` 的镜像 tag(要跟 ghcr 上真存在的标签走)。 + > **第 10 处是散文**:本文开头那句"已发布最新 = v" —— RP 不碰它,而它真会漂 + > (v2.2.5 发出去之后这里还停在 v2.2.4),所以已被 `test_all_version_sites_agree` + > 当版本位钉住,补齐时顺手改一行。 > `version.json` 里 RP 只抬 `$.version`,**`changelog` 与 `release_date` 也是手工位** > (壳的 `updater.rs` 会把 changelog 显示给用户,只抬版本号会发出"自称 2.2.4、说明写着 2.2.3"的包)。 - > 所以下一条 release PR 上,红在这几处是**预期行为**,补一个 commit 即可 —— + > 补齐这几处的那条提交要加在 release 分支上、**并且紧接着就合**:任何一次 main push 都会让 + > RP 重写那条分支,把你补的提交冲掉(2026-09-22 真被冲掉过一次 `1c2597a`)。 > 失败信息里会逐条标注哪处是『RP 自动』、哪处是『手工同步』, > 并由 `test_extra_files_entries_are_all_actionable` 钉住"每条 extra-files 今天确实能命中"。 - 版本位:`pyproject.toml` + `config.yaml`(release-please 驱动前端缓存参数需人工补齐,见本地 AGENTS.md #9(AGENTS.md 为本地维护、不随仓库分发))+ `CHANGELOG.md`。 - **发版有两条路,别同时走**: - 1. 自动:合入 release PR(RP 在 main push 后自动开/刷新,如 #120)并等它自己打 tag 发 Release; - 2. 手工:`git tag -a` + `gh release create`(v2.2.2/v2.2.3 走的就是这条)。 - 手工发版之后 RP 会在下一条 release PR 里把版本号再抬一格(它按 manifest 算), - 所以手工发完要把 `.release-please-manifest.json` 一起抬到刚发的版本,否则两边在版本号上互踩。 + 1. 自动:合入 release PR(RP 在 main push 后自动开/刷新)并等它自己打 tag 发 Release。 + **已于 2026-09-22 走通一次**:v2.2.5 的 tag `9125a3e`、Release 与 4 个资产 + (`SHA256SUMS`、`SHA256SUMS.scripts`、wheel、sdist)都是 RP 自己产出/挂上的。 + 走这条路必须知道三件事: + - **它只抬 5+1 处自动位**,剩下 5 类手工位(`config.yaml`、`Cargo.lock`、 + `setup.nsi` 三处、k8s 镜像 tag、`version.json` 的 `changelog`/`release_date`) + 要你在 release 分支上**另加一条提交**补齐;而**任何一次 main push 都会让 RP 重写那条分支**, + 把你补的提交冲掉(今天冲掉过一次:`1c2597a`)。所以顺序是"补齐 → 立刻合",中间别合别的。 + - **DCO 曾经让它结构上不可合**:RP 的提交作者是 `github-actions[bot]`,永远签不出 + `Signed-off-by`,而分支保护要求 DCO —— 已按"提交作者"豁免(#135),并留下 + `tests/test_dco_bot_exemption.py` 钉住"豁免不是后门"。 + - **release PR 拿不到 CI**(见下面那段),所以合并前的判据是本地跑 + `python scripts/check_release_readiness.py --root `, + 结论也会以 commit status `release-gate` 打在 PR head 上(把它加成必需检查是 owner 的一键决定)。 + 2. 手工:`git tag -a` + `gh release create`(v2.2.2 / v2.2.3 / v2.2.4 走的就是这条)。 + 手工发版之后 RP 会在下一条 release PR 里把版本号再抬一格(它按 manifest 算), + 所以手工发完要把 `.release-please-manifest.json` 一起抬到刚发的版本,否则两边在版本号上互踩。 + —— 反过来也成立:走自动路径时别顺手再手工打同版本的 tag。 > **走第 1 条时,release PR 上看不到任何 CI 检查**(实测 #120:`gh pr checks 120` 是空的)。 > 原因是 GitHub 的固定行为:由 `GITHUB_TOKEN` 产生的提交不再级联触发 workflow, > 而 release PR 的提交正是 bot 用 `GITHUB_TOKEN` 推的。 diff --git a/tests/test_release_readiness_gate.py b/tests/test_release_readiness_gate.py index 57e448f..b73bda1 100644 --- a/tests/test_release_readiness_gate.py +++ b/tests/test_release_readiness_gate.py @@ -40,6 +40,7 @@ # 判据里有两条要读它(extra-files 的类型白名单与标注一致性),漏搬就会以 FileNotFoundError # 崩在"判据未能执行"上 —— 那正是脚本设计上要区分开的那一档(崩 != 漂移,但同样不能放行)。 "release-please-config.json", + "docs/release-governance.md", ) @@ -116,6 +117,10 @@ def _complete_hand_sites(root: Path) -> None: f'OutFile "TTSMultiModel-Setup-v{NEW}.exe"\n!define APP_VERSION "{NEW}"\nVIProductVersion "{NEW}.0"\n', encoding="utf-8", ) + gov = root / "docs" / "release-governance.md" + gov.write_text( + gov.read_text(encoding="utf-8").replace("已发布最新 = v" + CUR, "已发布最新 = v" + NEW), encoding="utf-8" + ) (root / "version.json").write_text( json.dumps( { diff --git a/tests/test_version_consistency.py b/tests/test_version_consistency.py index e5a2902..ff0bacd 100644 --- a/tests/test_version_consistency.py +++ b/tests/test_version_consistency.py @@ -146,6 +146,13 @@ def grab_json(rel: str, label: str) -> str: r"image:\s*ghcr\.io/\S+/tts-multimodel:([0-9][^\s\"]*)", "k8s 镜像 tag", ), + # 第 10 处:文档里那句"已发布最新"。它以前只是散文,于是会这样漂 —— + # v2.2.5 由 release-please 发出去之后,这行还停在 v2.2.4(RP 不碰散文,手工补齐时也没带上它)。 + "docs/release-governance.md": grab( + "docs/release-governance.md", + r"\*\*已发布最新 = v(\d+\.\d+\.\d+)", + "治理文档声称的最新已发布版本", + ), } # 安装器**内嵌**的那份版本:`setup.nsi` 的 `File "version.json"` 取的是 # `scripts/installer/version.json`,而它是 .gitignore:436 明写的"装配中间物" From c38165470bc9fcc811077f3ffbf6439d320b3aed Mon Sep 17 00:00:00 2001 From: ReSerendipity Date: Tue, 22 Sep 2026 21:51:43 +0800 Subject: [PATCH 2/2] =?UTF-8?q?docs(release):=20=E8=AE=B0=E4=B8=8B?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8F=91=E7=89=88=E8=B7=AF=E5=BE=84=E4=B8=8D?= =?UTF-8?q?=E5=90=83=E9=95=9C=E5=83=8F=E8=BF=99=E4=B8=80=E6=AD=A5=EF=BC=88?= =?UTF-8?q?RP=20=E7=9A=84=20tag=20=E5=90=8C=E6=A0=B7=E4=B8=8D=E7=BA=A7?= =?UTF-8?q?=E8=81=94=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v2.2.5 发完之后核对下游:`gh run list --workflow docker-publish.yml` 上 v2.2.2 / v2.2.3 / v2.2.4 各有一条 `ev=push br=vX.Y.Z`,**v2.2.5 一条都没有**;`gpg-signed-release.yml` 同理 (三条 `ev=release`,2.2.5 缺席)。原因是同一条 GitHub 固定行为 —— bot 用 `GITHUB_TOKEN` 产生的事件不级联,而 RP 打的 tag 和建的 Release 正是它产的。#128 只写过"release PR 拿不到 CI" 这一面,没写它会连带吃掉 tag/release 两个下游触发,于是本次实测才发现。 后果是具体的:`deploy/kubernetes/deployment.yaml:40,61` 手工同步到了 `ghcr.io/reserendipity/tts-multimodel:2.2.5`,而 ghcr 上今天没有这个标签。两处都不能退回 2.2.4 消红(`test_all_version_sites_agree` 会把版本位一致性判红),所以唯一的自洽修法是补跑 `gh workflow run docker-publish.yml --ref v2.2.5` —— `metadata-action` 的 `type=semver` 在 tag ref 上就能出 `:2.2.5`,工作流不用改。这一步按"Release 侧动作交回 owner"的约定留给所有者点头。 顺带修一处已经过期的话:§1 末尾那段还在说"要把它变成机器闸就得配一个能报 commit status 的作业 —— 那是权限决策,不在现状里",而 #134 已经把它建出来了(报 `release-gate`、不拦截)。 GPG 那一格现在不痛(`GPG_PRIVATE_KEY` 未配 → 作业进 skip 分支只出 notice),但密钥配上之后 RP 发的每个版本都要手工补跑,这条也写进 §1 第 4 条,免得下次又当成"自动路径会替我做"。 验证:`pytest tests/test_version_consistency.py tests/test_release_readiness_gate.py tests/test_docker_cache_budget.py` → 14 passed(basetemp 换掉才跑起来: `pytest-of-Doro/pytest-current` 有残留目录导致 WinError 5,与本改动无关)。 `已发布最新 = v2.2.5` 一行未动,第 10 处版本位仍与 9 处代码位一致。 Signed-off-by: ReSerendipity --- docs/release-governance.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/release-governance.md b/docs/release-governance.md index d52ba50..9ed7934 100644 --- a/docs/release-governance.md +++ b/docs/release-governance.md @@ -42,7 +42,7 @@ 1. 自动:合入 release PR(RP 在 main push 后自动开/刷新)并等它自己打 tag 发 Release。 **已于 2026-09-22 走通一次**:v2.2.5 的 tag `9125a3e`、Release 与 4 个资产 (`SHA256SUMS`、`SHA256SUMS.scripts`、wheel、sdist)都是 RP 自己产出/挂上的。 - 走这条路必须知道三件事: + 走这条路必须知道四件事: - **它只抬 5+1 处自动位**,剩下 5 类手工位(`config.yaml`、`Cargo.lock`、 `setup.nsi` 三处、k8s 镜像 tag、`version.json` 的 `changelog`/`release_date`) 要你在 release 分支上**另加一条提交**补齐;而**任何一次 main push 都会让 RP 重写那条分支**, @@ -53,6 +53,18 @@ - **release PR 拿不到 CI**(见下面那段),所以合并前的判据是本地跑 `python scripts/check_release_readiness.py --root `, 结论也会以 commit status `release-gate` 打在 PR head 上(把它加成必需检查是 owner 的一键决定)。 + - **它不会替你发出镜像**:"bot 的 `GITHUB_TOKEN` 不级联"这条规则不止吃 release PR 的 CI, + 也吃**它自己打的 tag 和它自己建的 Release** 这两个下游事件。实测对照(2026-09-22): + v2.2.2 / v2.2.3 / v2.2.4 三次手工 `git tag` 各留下 `docker-publish.yml` 一条 + `ev=push br=vX.Y.Z` 和 `gpg-signed-release.yml` 一条 `ev=release`; + v2.2.5 这两个工作流上**都是 0 条**(`gh run list --workflow docker-publish.yml` 复算)。 + 于是合完 release PR 后要立刻补一手 `gh workflow run docker-publish.yml --ref vX.Y.Z` —— + `metadata-action` 的 `type=semver` 在 tag ref 上就能出 `:2.2.5`,**不用改工作流**。 + 不补的后果是具体的:`deploy/kubernetes/deployment.yaml` 指着 ghcr 上不存在的标签, + 而且这不能靠"把镜像 tag 退回上一版"消红 —— 那会让 `test_all_version_sites_agree` + 把版本位一致性一起判红(2026-09-22 的 v2.2.5 就卡在这一点上,见 §2 第 5 步)。 + GPG 签名那一格现在不痛(`GPG_PRIVATE_KEY` 未配,作业进 skip 分支只出 notice), + 但 owner 配上密钥后,RP 发的每个版本都要把 `gpg-signed-release.yml` 一起手工补跑。 2. 手工:`git tag -a` + `gh release create`(v2.2.2 / v2.2.3 / v2.2.4 走的就是这条)。 手工发版之后 RP 会在下一条 release PR 里把版本号再抬一格(它按 manifest 算), 所以手工发完要把 `.release-please-manifest.json` 一起抬到刚发的版本,否则两边在版本号上互踩。 @@ -62,10 +74,12 @@ > 而 release PR 的提交正是 bot 用 `GITHUB_TOKEN` 推的。 > 后果很具体:§1 上面列的那些**手工同步位**在 release PR 上不会变红 —— > `test_all_version_sites_agree` 要到合并进 main 之后才红,那时 Release 已经发出去了。 - > 所以合 release PR 之前**必须本地跑**:`.venv/Scripts/python.exe -m pytest tests/test_version_consistency.py` - > (外加 §2 第 1–2 步的手工位补齐)。要把它变成机器闸,就得给 release PR 配一个 - > 由 `workflow_dispatch`/`push` 触发、能对 release 分支的 head SHA 报 commit status 的作业 —— - > 那是权限决策,不在本文档的"现状"里。 + > 所以合 release PR 之前**必须本地跑**:`python scripts/check_release_readiness.py --root <检出>` + > (外加 §2 第 1–2 步的手工位补齐)。机器闸已经补上了一半(#134):`release-please.yml` 会在 + > 开/刷新 PR 之后跑同一个脚本,并把结论以 commit status `release-gate` 报在 release 分支的 + > head SHA 上 —— 它是**可见信号不是拦截**,把它加成必需检查仍是 owner 的一键决定。 + > 而且要注意它只覆盖"版本位/可发布性"这一类判据:pytest 全量、真机验收这些在 release PR 上 + > 永远不会跑,那是本地路径的活。 历史上 `release-please.yml` 曾是**结构性空转**(`skip-github-pull-request: true` + 缺 config/manifest + 5 个 v4 不认的入参 → 每次 main push 输出 `found 0 possible releases` 后绿, v2.2.2 因此三次绿 run 都没 Release);已修,现在它真的会开 PR。挂在它下面的 @@ -85,6 +99,11 @@ 4. 手工发完把 `.release-please-manifest.json` 的 `"."` 抬到刚发的版本并推 main, 否则 RP 会在下一条 release PR 里把版本号再抬一格(两边互踩) 5. CI 盯到终态;容器镜像**发布走 `docker-publish.yml`**,上线时按 digest 钉,禁止 `:latest` + - **v2.2.5 起这条要自己踩**:走自动发版路径时 RP 用 `GITHUB_TOKEN` 打的 tag 不级联, + `docker-publish.yml` 不会因此运行,`:2.2.5` 这个标签在 ghcr 上就不存在(见 §1 第 4 条)。 + 补法:`gh workflow run docker-publish.yml --ref v<版本>`,跑完用 + `gh run list --workflow docker-publish.yml --branch v<版本>` 确认有了一条 completed/success, + 再回头核 `deployment.yaml` 指的那个 tag 真的存在 —— 顺序反了就是"清单自称能上线、实际拉不到镜像"。 > 便携分卷(core/torch/model,~26 GB)与桌面增量包**不在**第 3 步的默认资产里: > 需要 `scripts/release_gate.ps1 -ModelDir ... -RuntimeDir ... -TorchWheelDir ...` 真构建 + 单独点头