Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

## [Unreleased]

## [2.2.2] - 2026-09-16(未发布:v2.2.2 tag 已撤销,无 GitHub Release、无便携分卷资产)
## [2.2.2] - 2026-09-22

<!-- 2026-09-16 那批内容原挂在「未发布」的 v2.2.2 标题下(tag 曾建后撤销);2026-09-22 正式发出,
并追加「发布前置回归」一节记录 09-21/22 这一批。资产范围见本节末尾的说明。 -->

### Features

Expand Down Expand Up @@ -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)


Expand Down
4 changes: 2 additions & 2 deletions deploy/kubernetes/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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')"]
Expand All @@ -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
Expand Down
38 changes: 29 additions & 9 deletions docs/release-governance.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 "..." <SHA>` → `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`)

Expand All @@ -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`
- [ ] tag 已推送,且 `gh release view v<版本>` 能看到 Release(**推 tag 不会触发自动发版**:
`release-please.yml` 目前结构性空转,见 §1;Release 需手工建 + 手工挂资产)
79 changes: 79 additions & 0 deletions tests/test_version_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, "更新契约的最低壳版本与本批版本不一致"
4 changes: 2 additions & 2 deletions version.json
Original file line number Diff line number Diff line change
@@ -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 便携分卷与增量包。"
}
Loading