From 70aa824d7ca7dcb35033925705ef7b204b55c04d Mon Sep 17 00:00:00 2001 From: ReSerendipity Date: Tue, 22 Sep 2026 08:23:15 +0800 Subject: [PATCH] =?UTF-8?q?chore(release):=20=E6=8A=8A=20v2.2.2=20?= =?UTF-8?q?=E7=9A=84=E8=AE=B0=E8=BD=BD=E4=B8=8E=E7=89=88=E6=9C=AC=E4=BD=8D?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E7=9C=9F=E5=AE=9E=E7=8A=B6=E6=80=81=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E7=BB=99"=E7=89=88=E6=9C=AC=E4=BD=8D=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E5=90=8C=E6=AD=A5"=E4=B8=8A=E9=97=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CHANGELOG:[2.2.2] 从"未发布(tag 已撤销)"改为正式发布(2026-09-22),并新增 「发布前置回归(2026-09-21/22)」9 条 —— 这一批(einops/addict 干净环境修复、镜像 apt 索引就绪守卫、CSRF 硬失败 + 0600、IndexTTS 预热并发崩溃、tts-1-hd 500→400、 两处"永远绿的假信号"、依赖下界回实测 + 16 条公告逐条登记、CodeQL 台账刷新)此前 **没有任何 CHANGELOG 条目**(release-please 空转,见下),发版说明会漏掉一个 P0 打包修复。 另在段末如实记下版本口径的偏差:本批 43 个 feat 仍标 2.2.2 是所有者的显式决定, 以及"资产不含 26 GB 分卷、GPG 分离签名不会执行(secret 不存在)"。 - 版本位:`deploy/kubernetes/deployment.yaml` 镜像 tag 2.2.1 → 2.2.2(2 处)、 `version.json` 的 release_date / changelog 文案与"未发布"表述。 `desktop/package-lock.json` 也在 2.2.1,但它在 .gitignore:196 里 —— 不是仓库内的版本位, 改了也不会被跟踪,故 governance §1 补一句区分。 - 新增机器闸:`test_all_version_sites_agree` 比对仓库内跟踪的 9 个版本位, `test_installer_artifact_names_track_the_version_site` 再核 OutFile / VIProductVersion / `version.json.minimum_shell_version` 与 APP_VERSION 同源。 取值函数在**任何一处取不到版本号时直接抛错**,避免"少核一个站点"被当成"全部一致"。 变异自证已做:把 deployment.yaml 改回 2.2.1 → 该测试红且点名是哪一处。 - release-governance:§1/§2/§5 里"发布由 release-please 自动完成""推 tag 会触发发版" 两句是**不成立的**(它设了 skip-github-pull-request 而仓库从无 release PR, 每次 main push 都 `found 0 possible releases` 后成功;还有 5 个 v4 不认的入参被静默忽略)。 改成实际可执行的手工发版步骤,并写明便携分卷与 GPG 签名不在默认路径里。 Signed-off-by: ReSerendipity --- CHANGELOG.md | 38 ++++++++++++++- deploy/kubernetes/deployment.yaml | 4 +- docs/release-governance.md | 38 +++++++++++---- tests/test_version_consistency.py | 79 +++++++++++++++++++++++++++++++ version.json | 4 +- 5 files changed, 149 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2922c666..a6682329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,10 @@ ## [Unreleased] -## [2.2.2] - 2026-09-16(未发布:v2.2.2 tag 已撤销,无 GitHub Release、无便携分卷资产) +## [2.2.2] - 2026-09-22 + + ### Features @@ -43,6 +46,39 @@ * **dist:** 便携依赖钉装(`launcher/requirements-small.txt` 93 项 + `torch==2.13.0+cu132` 系列钉版,`sync_requirements.py --check-small` 校验)(P1-1) +### 发布前置回归(2026-09-21/22,随 v2.2.2 发出) + +* **deps(P0):** `einops` 提为**核心依赖** —— vendored VoxCPM 在模块顶层 + `from einops import rearrange`,而它原先只出现在 `training` extra 里,`funasr`/`modelscope` + 又只在 **extras** 声明它:任何按 `requirements.txt` / `[project].dependencies` 装出来的干净环境 + (镜像、便携包)都起不来默认引擎 `tts-1`。由 docker-smoke 新增的"镜像内引擎导入探针"第一次真跑抓到 + (run 35618578940)。同形状把 `addict` 补进两份钉版集;新增守卫 D5「核心声明必须有钉版」。 +* **docker:** 镜像构建间歇性失败的真因是 `apt-get update` 对「某个索引没抓下来」只打 `W:` 并**返回 0**, + 两行之后才炸成 `E: Unable to locate package python3.12`。两段 RUN 都改成「PPA 索引里真查得到才继续」, + 取不到则重试 5 轮后硬停并给可操作原因。先前记成「`bash-builtins`/man-db 的确定性故障」是**误判**, + 已被探针数据与绿色 run 双重证伪(`docs/DOD.md` 相应段落已更正)。 +* **security:** CSRF 密钥取不出来时**拒绝启动**(原为 warning 后以空密钥继续挂 `CSRFMiddleware`, + 等于静默关掉这道防护);密钥改 `0o600` 落盘、既有宽权限文件收紧;CodeQL 既有告警 #1 + (明文存储密钥)走「真加固 + sink 行带理由抑制」,不整条静音。 +* **engine:** IndexTTS 推理按引擎名加 `threading.RLock`。真因是**预热绕过串行队列、与用户请求并发** + (不是先前记的「第二次引擎切换之后」),device-side assert 会毒化整个 CUDA context 使同进程后续合成全废; + 真机并发条件下字节数与串行一致(2.5 = 336,642 B / 2.0 = 239,674 B)。 +* **api:** OpenAI 口 `tts-1-hd` 不再必然 500 —— 该口没有参考音频通道,改回 400 并指明该走哪个端点。 +* **ci:** 修两处「永远绿的假信号」:`gpu-smoke.yml` 的 job 因缺 secret + 零注册 runner 一直 `skipped` + 而 run 顶层 success(现发 warning 并写进 step summary);镜像内的引擎模块此前**从没被导入过**(新增探针)。 +* **deps:** 依赖下界回到实测能跑的 `transformers>=4.52.1,<4.53` + `tokenizers>=0.21.0,<0.22`; + 4.52.x 的代价按 OSV 实测是 **16 个公告**(其中 8 条上游根本没有修复版本),逐条写可达性判据与理由后 + 在 pip-audit / Trivy 里绑由头豁免;20 条 Dependabot 告警同样逐条 dismiss + (台账见 `docs/SECURITY_DEPENDABOT_TRIAGE.md`)。 +* **security:** CodeQL 存量分诊台账救回并按 09-21 实测刷新:open **110 → 76**、critical **1 → 0**, + 34 条差额逐条对上账(`docs/SECURITY_CODEQL_TRIAGE.md` §6)。 + +> **版本口径如实记一句**:本批含 **43 个 `feat:` 提交**却仍标 `2.2.2`(按 SemVer 应为 `2.3.0`)。 +> 这是所有者的显式决定 —— 本段内容与 2026-09-16 那次被撤销的 v2.2.2 tag 属同一批,沿用该号。 +> 另:仓库只有 1 个 Actions secret(`MANIFEST_SIGNING_KEY_B64`),`GPG_PRIVATE_KEY` 缺席 → +> `gpg-signed-release.yml` 只会 notice 跳过,资产不做分离签名;本次 Release 附源码包 + wheel + +> SHA256SUMS,**不含** 26 GB 便携分卷与增量包(那部分资产未获授权,命令见 `docs/release-governance.md` §2)。 + ## [2.2.1](https://github.com/ReSerendipity/TTS_MultiModel/compare/v2.2.0...v2.2.1) (2026-08-22) diff --git a/deploy/kubernetes/deployment.yaml b/deploy/kubernetes/deployment.yaml index 2b044a75..46953f73 100644 --- a/deploy/kubernetes/deployment.yaml +++ b/deploy/kubernetes/deployment.yaml @@ -37,7 +37,7 @@ spec: # P0-1:subPath 挂载要求 PVC 内子目录预先存在(kubelet 不会自动创建)。 # 用主镜像(离线可用、自带 python)在可写的 data 卷上预建全部运行时目录。 - name: init-dirs - image: ghcr.io/reserendipity/tts-multimodel:2.2.1 + image: ghcr.io/reserendipity/tts-multimodel:2.2.2 imagePullPolicy: IfNotPresent command: ["python3.12", "-c", "import os; [os.makedirs(f'/mnt/data/{d}', exist_ok=True) for d in ['data','outputs','logs','cache','lora','personas']]; print('init-dirs ok')"] @@ -58,7 +58,7 @@ spec: memory: "256Mi" containers: - name: tts - image: ghcr.io/reserendipity/tts-multimodel:2.2.1 + image: ghcr.io/reserendipity/tts-multimodel:2.2.2 imagePullPolicy: IfNotPresent ports: - containerPort: 7869 diff --git a/docs/release-governance.md b/docs/release-governance.md index 1ef5aad2..7039199b 100644 --- a/docs/release-governance.md +++ b/docs/release-governance.md @@ -8,19 +8,37 @@ ## 1. 版本号规范 -- 遵循 SemVer `MAJOR.MINOR.PATCH`。**源码版本 2.2.2(未发布)|已发布最新 = v2.2.1**。 +- 遵循 SemVer `MAJOR.MINOR.PATCH`。**已发布最新 = v2.2.2(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 处 + > 由 `tests/test_version_consistency.py::test_all_version_sites_agree` 逐条比对,漂一处就红 + > (v2.2.2 发版前 `deploy/kubernetes/deployment.yaml` 的镜像 tag 就停在 2.2.1,红得下来)。 - 版本位:`pyproject.toml` + `config.yaml`(release-please 驱动前端缓存参数需人工补齐,见本地 AGENTS.md #9(AGENTS.md 为本地维护、不随仓库分发))+ `CHANGELOG.md`。 -- 发布由 `release-please` 自动生成 GitHub Release 并打 semver tag;该作业若失败**不会**再被吞掉 - (v2.2.2 曾在工作流全绿的情况下既没 tag 迁移也没 Release,原因见 `CHANGELOG.md` 该条标注)。 +- 发布**目前**由人工 `git tag -a` + `gh release create` 完成:**`release-please.yml` 是结构性空转**, + 不会替你发版 —— 它设了 `skip-github-pull-request: true` 而仓库从未产生过 release PR, + 于是每次 main push 都输出 `found 0 possible releases` 后成功;它还传了 5 个 v4 不认的入参 + (`package-name`/`changelog-path`/`draft`/`label`/`prerelease` 全被忽略),且没有 + `release-please-config.json` / `.release-please-manifest.json`。 + 挂在它下面的 `build-release`(sdist/wheel + SHA256SUMS)因 `release_created != true` 同样从不执行。 + (v2.2.2 曾在工作流全绿的情况下既没 tag 迁移也没 Release,原因即此;修法另见待落的 RP 修复 PR。) ## 2. 发布流程 -1. 确认 CHANGELOG `[Unreleased]` 条目完整;release-please 提交后自动收敛版本 -2. `git push` 触发 `release-please.yml` -3. 同步 `config.yaml` 顶层 `version`(release-please 不自动同步,需人工) -4. CI 盯到终态;容器镜像钉 digest 发布,禁止 `:latest` +1. 确认本批内容已进 CHANGELOG(`[Unreleased]` 收敛进 `[<新版本>]` 并改日期); + `tests/test_version_consistency.py` 必须绿(它就是"§5 版本位全部同步"那格闸) +2. 同步 `config.yaml` 顶层 `version` 与 `deploy/kubernetes/deployment.yaml` 的镜像 tag + (都无人自动改;后者红在版本位一致性测试里) +3. 手工发版(RP 目前是空转,见 §1): + `git tag -a v<版本> -m "..." ` → `git push origin v<版本>` → + `python -m build` + `twine check dist/*` + `SHA256SUMS.txt` → + `gh release create v<版本> --notes-file ... <资产>`;核对 `gh release view v<版本>` +4. CI 盯到终态;容器镜像**发布走 `docker-publish.yml`**,上线时按 digest 钉,禁止 `:latest` + +> 便携分卷(core/torch/model,~26 GB)与桌面增量包**不在**第 3 步的默认资产里: +> 需要 `scripts/release_gate.ps1 -ModelDir ... -RuntimeDir ... -TorchWheelDir ...` 真构建 + 单独点头 +> (权重未变时重传 13 个 model 分卷是逐字节浪费)。GPG 分离签名同理 —— +> `GPG_PRIVATE_KEY` 这个 secret 目前不存在,`gpg-signed-release.yml` 只会 notice 跳过。 ## 3. 回滚(详见 `docs/rollback_sop.md`) @@ -35,11 +53,13 @@ ## 5. 发布前检查清单 -- [ ] 版本位全部同步(`pyproject.toml` + `config.yaml` + `CHANGELOG.md`) +- [ ] 版本位全部同步(`pyproject.toml` + `config.yaml` + `CHANGELOG.md`;仓库内跟踪的 9 处已由 + `tests/test_version_consistency.py::test_all_version_sites_agree` 机器核对,这格是**复看**用) - [ ] CHANGELOG `[Unreleased]` 已改版本 + 日期 - [ ] 全量 pytest 通过(门禁实测:非 GPU 回归 0 failed) - [ ] `ruff` 全绿;mypy 遵守 `.ci/mypy_baseline.txt` 棘轮 - [ ] `python scripts/check_spec_refs.py` 退出码 0 - [ ] 镜像 digest 钉版 + Trivy 关键/高危扫描绿 - [ ] 完整性自检 16/16 通过 -- [ ] tag 已推送触发 `release-please.yml` \ No newline at end of file +- [ ] tag 已推送,且 `gh release view v<版本>` 能看到 Release(**推 tag 不会触发自动发版**: + `release-please.yml` 目前结构性空转,见 §1;Release 需手工建 + 手工挂资产) \ No newline at end of file diff --git a/tests/test_version_consistency.py b/tests/test_version_consistency.py index 5c00473c..9c92922a 100644 --- a/tests/test_version_consistency.py +++ b/tests/test_version_consistency.py @@ -72,3 +72,82 @@ def test_no_hardcoded_old_version(): pass assert offenders == [] + + +# --------------------------------------------------------------------------- +# 版本位一致性:release-governance §1 说"版本号出现在 11 处",但 §5 的那格 +# "[ ] 版本位全部同步" 一直是**手工勾选**。v2.2.2 发版前实测就有真漂移(`deploy/kubernetes +# /deployment.yaml` 的镜像 tag 还停在 2.2.1)。手工勾选的清单项在无人复看时会漂,故上闸。 +# 注:`desktop/package-lock.json` 也在 .gitignore 里(第 196 行),不是仓库内的版本位,故不列。 +# --------------------------------------------------------------------------- + +_SEMVER = re.compile(r"\d+\.\d+\.\d+") + + +def _site_versions() -> dict[str, str]: + """从各版本位精确取值;取不到就抛,避免"少一个站点"被当成"一致"。""" + + def grab(rel: str, pattern: str, label: str) -> str: + text = (PROJECT_ROOT / rel).read_text(encoding="utf-8", errors="ignore") + m = re.search(pattern, text, re.M) + if not m: + raise AssertionError(f"{label}({rel})取不到版本号,正则或文件已失效:{pattern!r}") + return m.group(1) + + def grab_json(rel: str, label: str) -> str: + data = json.loads((PROJECT_ROOT / rel).read_text(encoding="utf-8")) + if "version" not in data: + raise AssertionError(f"{label}({rel})没有 version 字段") + return str(data["version"]) + + sites = { + "pyproject.toml": grab("pyproject.toml", r'^version\s*=\s*"([^"]+)"', "Python 包版本"), + "version.json": grab_json("version.json", "更新契约版本"), + "config.yaml": grab("config.yaml", r'^version:\s*"?([^"\n]+)"?', "前端缓存参数版本"), + "desktop/package.json": grab_json("desktop/package.json", "桌面壳 npm 版本"), + "desktop/src-tauri/tauri.conf.json": grab_json("desktop/src-tauri/tauri.conf.json", "Tauri 壳版本"), + "desktop/src-tauri/Cargo.toml": grab( + "desktop/src-tauri/Cargo.toml", + r"\[package\][\s\S]*?^version\s*=\s*\"([^\"]+)\"", + "Rust 包版本", + ), + "desktop/src-tauri/Cargo.lock": grab( + "desktop/src-tauri/Cargo.lock", + r'name = "tts-multimodel-desktop"\nversion = "([^"]+)"', + "Rust 锁内自身版本", + ), + "scripts/installer/setup.nsi": grab( + "scripts/installer/setup.nsi", r'!define APP_VERSION "([^"]+)"', "安装器版本" + ), + "deploy/kubernetes/deployment.yaml": grab( + "deploy/kubernetes/deployment.yaml", + r"image:\s*ghcr\.io/\S+/tts-multimodel:([0-9][^\s\"]*)", + "k8s 镜像 tag", + ), + } + return sites + + +def test_all_version_sites_agree() -> None: + sites = _site_versions() + assert len(sites) >= 9, f"只核到 {len(sites)} 个版本位,本条已失去意义" + bad = {k: v for k, v in sites.items() if not _SEMVER.fullmatch(v)} + assert not bad, f"这些版本位不是 x.y.z 形态:{bad}" + distinct = set(sites.values()) + assert len(distinct) == 1, ( + "版本位互相矛盾(release-governance §5 的『版本位全部同步』没做到):" + + "\n " + + "\n ".join(f"{k} = {v}" for k, v in sorted(sites.items())) + ) + + +def test_installer_artifact_names_track_the_version_site() -> None: + """OutFile / VIProductVersion 与 APP_VERSION 必须同源,否则装出来的包自称一个版本、 + 文件名叫另一个版本(`version.json` 的 min_shell_version 比对就失去意义)。""" + sites = _site_versions() + ver = next(iter(set(sites.values()))) + text = (PROJECT_ROOT / "scripts" / "installer" / "setup.nsi").read_text(encoding="utf-8", errors="ignore") + assert f"TTSMultiModel-Setup-v{ver}.exe" in text, f"OutFile 还没跟到 v{ver}" + assert f'VIProductVersion "{ver}.0"' in text, f"VIProductVersion 还没跟到 {ver}.0" + data = json.loads((PROJECT_ROOT / "version.json").read_text(encoding="utf-8")) + assert data.get("minimum_shell_version") == ver, "更新契约的最低壳版本与本批版本不一致" diff --git a/version.json b/version.json index 6e79afbc..47ec80fa 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "version": "2.2.2", - "release_date": "2026-09-16", + "release_date": "2026-09-22", "minimum_shell_version": "2.2.2", - "changelog": "2.2.2(未发布)桌面分发补全:修复 Tauri 壳 updater pubkey 双重 base64 错误、同步桌面壳版本与完整性清单;尚无便携分卷与增量包资产,安装器/分卷发布需先过 release-gate" + "changelog": "2.2.2 发布:桌面分发与便携打包链路补全 + 发布前置回归(干净环境缺 einops/addict 已修、镜像 apt 索引就绪守卫、CSRF 硬失败与 0600、IndexTTS 预热并发崩溃修复)。资产为源码包 + wheel + SHA256SUMS;不含 26 GB 便携分卷与增量包。" }