docs: Phase 5 acceptance, deployment, operations and phase report

Record the task/quota exception matrix (isolated fault injection) results,
the admin quota concurrency fix, the isolated restore drill (business/CMS/audio,
with the pre-existing missing-audio finding), upstream security launch blocker,
and remaining minor items. Each item marked pass / fail / awaiting-external with
test version and evidence. Do not mark reliability/launch-blocking items complete.
This commit is contained in:
flym 2026-09-10 05:01:27 +08:00
parent 8657f447a1
commit 34c2b436f0
5 changed files with 331 additions and 0 deletions

68
docs/ACCEPTANCE.md Normal file
View File

@ -0,0 +1,68 @@
# 验收记录ACCEPTANCE
日期2026-09-10 · 基线 `9568622` · 本轮修复/部署版本 `8657f44`
状态标记:**通过 / 失败 / 待外部条件**。每项附测试版本与证据。故障注入均在隔离环境(隔离库 `kaotings_p5`、隔离音频目录、伪造上游)进行。
## 1. 任务与额度异常矩阵Phase 3 遗留)
| 编号 | 验收项 | 版本 | 结果 | 证据 |
| --- | --- | --- | --- | --- |
| A-1 | 成功基线(写入/结算/回放/下载) | 8657f44 | 通过 | S1`succeeded`used 2冻结 0回放/下载 200音频文件存在 |
| A-2 | 最后额度并发争用 | 8657f44 | 通过 | S2并发 2 请求 → `codes=[202,409]`,仅 1 个冻结成功used 3 / 冻结 0 / 可用 2 |
| A-3 | 管理员并发调减额度不变式 | 9568622 → 8657f44 | **失败→修复→通过** | S3基线 7/8 轮破坏不变式;修复后 8 轮恒 `limit+adjustment-used-reserved>=0`(最坏可用 10 |
| A-4 | 冻结与调减并发压测 | 8657f44 | 通过 | S4终态不变式成立、冻结归零used 24 / 冻结 0 / 可用 16 |
| A-5 | queued 重启恢复 | 8657f44 | 通过 | S9`queued→succeeded`,重启后音频存在 |
| A-6 | running 租约过期恢复 | 8657f44 | 通过 | S10`failed / WORKER_LEASE_EXPIRED`,冻结释放 |
| A-7 | 音频写入与结算中断(存储失败) | 8657f44 | 通过 | S7目录不可写 → `failed`,冻结释放,无残留文件 |
| A-8 | 上游超时 | 8657f44 | 通过 | S8`failed / UPSTREAM_TIMEOUT`,冻结释放 |
| A-9 | 非音频 / 损坏 / 超大 / 5xx 响应 | 8657f44 | 通过 | S5`UPSTREAM_NOT_AUDIO` / `UPSTREAM_AUDIO_CORRUPT` / `UPSTREAM_RESPONSE_TOO_LARGE` / `UPSTREAM_HTTP_500`,均冻结释放、无文件 |
| A-10 | 跨周期结算 | 8657f44 | 通过 | S6结算落到任务创建时所属上一周期账户本周期 used 不变 |
复现:`services/api/tests/phase5_exception_matrix.py`(退出码 0 = 全通过)。
## 2. 自动化验收固化
| 编号 | 验收项 | 版本 | 结果 | 证据 |
| --- | --- | --- | --- | --- |
| B-1 | 测试工具/依赖版本/运行说明入仓库 | 8657f44 | 通过 | `services/api/requirements.txt`、`requirements-test.txt`、`tests/README.md`、`tests/provision_isolated_env.sh` |
| B-2 | 秘密经环境注入,不落仓库 | 8657f44 | 通过 | `provision_isolated_env.sh` 仅读 `SUDO_PASSWORD`/`P5_DB_PASSWORD` 等环境变量;仓库无秘密 |
| B-3 | 独立环境可重复执行 | 8657f44 | 通过 | 隔离库 + 隔离音频 + 伪造上游多次运行,退出码稳定 |
## 3. 音频运维与恢复
| 编号 | 验收项 | 版本 | 结果 | 证据 |
| --- | --- | --- | --- | --- |
| C-1 | 业务库隔离恢复 | 8657f44 | 通过 | 行数源=恢复users 35 / quota 34 / tasks 28 / audio 26 / usage 61 / grants 10 |
| C-2 | CMS 库隔离恢复 | 8657f44 | 通过 | 14 张表products=1 / users=1 / migrations=1 |
| C-3 | 音频恢复 + 历史↔音频关联 + 额度一致 | 8657f44 | 通过(带发现) | 磁盘 22 文件全部被 `available` 引用(无孤儿);额度↔流水 0 不符;**发现 4 条 `available` 行无文件**(既有,回放 404 优雅降级) |
| C-4 | 容量/保留/清理/备份方案 | — | 待外部条件 | 保留期/容量上限/清理策略需用户确认(见 OPERATIONS.md §3本轮不擅自删除既有音频 |
## 4. 上游安全
| 编号 | 验收项 | 版本 | 结果 | 证据 |
| --- | --- | --- | --- | --- |
| D-1 | 上游传输加密HTTPS/mTLS/专线) | — | **待外部条件(上线阻断)** | 现为公网 HTTP需上游提供加密端点与证书/接入方式 |
| D-2 | 来源限制(限本站出口) | — | **待外部条件(上线阻断)** | 需上游提供允许的来源网段;业务侧保留 Bearer Key |
| D-3 | 外部网络复测 | — | **待外部条件** | D-1/D-2 解除后重跑成功/失败/超时矩阵与不可达核查 |
## 5. 剩余小项
| 编号 | 验收项 | 版本 | 结果 | 证据 |
| --- | --- | --- | --- | --- |
| E-1 | 不新增按 slug REST 路由,统一 `where` 查询 | 8657f44 | 通过 | 公网展示走 `where[status][equals]=published` + `where[slug][equals]=`200 |
| E-2 | 非法 ID 500 不泄露堆栈/内部信息 | 8657f44 | 通过 | `GET /cms/api/products/:slug`(非 UUID) → 500 通用 `{"errors":[{"message":"Something went wrong."}]}`,扫描无堆栈/路径 |
| E-3 | SMTP 未接入维持 V1 限制,受控管理员恢复流程可用 | 8657f44 | 通过 | 验证渠道返回未启用;恢复流程见 `docs/cms-admin-recovery.md` |
## 6. 部署与版本
| 编号 | 验收项 | 结果 | 证据 |
| --- | --- | --- | --- |
| F-1 | 修复提交并部署 | 通过 | 发布 `8657f44`,软链已指向,仅重启 `kaotings-api` |
| F-2 | 版本核对 | 通过 | 线上 `main.py` SHA256 `571037f4…` = 本地 `8657f44``is-active active``/healthz ok` |
## 未通过 / 待外部条件(不得标为完成)
- **D-1 / D-2 / D-3上线阻断**:公网 HTTP 上游未加密、无来源限制、未外部复测。
- **C-4**:音频容量/保留/清理参数待用户确认。
- **C-3 发现**4 条 `available` 音频行无文件,待调查根因(不阻断恢复)。
- 旧 Big-TTS Web 退役前需旧系统负责人提供源端备份证明与替代验收确认(沿用 Phase 3/4

64
docs/DEPLOYMENT.md Normal file
View File

@ -0,0 +1,64 @@
# 部署说明DEPLOYMENT
日期2026-09-10 · 测试机 `192.168.199.22`Ubuntu 24.04LAN
当前上线版本:**`8657f44`**API 修复Web/CMS 代码内容与 `p4` 相同,随发布一并指向 `8657f44`
## 1. 部署模型
- 发布目录:`/home/flym/releases/<发布名>/`,是仓库的**完整拷贝**(含 `app/`、`apps/cms`、`services/api`、`.next`、`node_modules`)。
- 运行通过软链指向当前发布:
- `kaotings-api``releases/<发布>/services/api`
- `kaotings-cms``releases/<发布>/apps/cms`
- `kaotings-www``releases/<发布>`
- 虚拟环境独立:`/home/flym/kaotings-api-venv`(不随发布拷贝)。
- systemd 服务:`kaotings-api`uvicorn `127.0.0.1:8000`)、`kaotings-cms``127.0.0.1:3001`)、`kaotings-web``127.0.0.1:3000``Restart=always`。
- 反代Caddy 提供 `:80/:443``/api`→8000、`/cms`→3001、其余→3000回环端口不对 LAN 开放。
- 秘密:`/etc/kaotings/api.env`、`cms.env`、`cms-admin.secrets`、`p4-*.secrets`root-only 0600**不进 Git/前端/日志**。
- 数据库PostgreSQL `127.0.0.1:5432`(业务 `kaotings`、CMS `kaotings_cms`,独立账号)。
- 音频:私有本地目录 `/home/flym/kaotings-audio`
## 2. 发布一个新版本(本次 `8657f44` 的做法)
1. 本地提交修复,得到提交号 `<sha>`
2. 服务器:`cp -a releases/p4 releases/<sha>`(完整拷贝当前发布)。
3. 将变更文件更新到 `releases/<sha>`(本次仅 `services/api/app/main.py``tests/`)。
4. 应用数据库迁移(如有):`/home/flym/kaotings-api-venv/bin/python -m app.migrate``DATABASE_URL` 走 `api.env`)。
5. 重新指向软链(三个服务一致指向 `<sha>`)。
6. **只重启发生代码变更的服务**(本次仅 `systemctl restart kaotings-api`),避免无谓中断。
7. 版本核对:比对线上关键文件 SHA256 与提交一致;`systemctl is-active``GET /healthz`。
本次核对:线上 `services/api/app/main.py` SHA256 `571037f4…` = 本地 `8657f44``kaotings-api active``/healthz ok`。
## 3. 回滚
- 软链指回上一发布(本次为 `p4``kaotings-api`→`releases/p4/services/api`(如需回滚 web/cms 一并指回)。
- `systemctl restart kaotings-api`(及被回滚的服务)。
- `p4` 保持完好,可随时回滚。回滚前若已应用新迁移,需评估迁移是否可逆。
## 4. 上线阻断:上游安全改造(必须解除后方可正式上线)
现状TTS 上游为**公网 HTTP**`TTS_UPSTREAM_URL`Bearer Key 明文传输。此为正式上线阻断项,未解除前不得判定“可正式上线”。
### 4.1 具体实施条件
| 条件 | 说明 |
| --- | --- |
| 传输加密 | 上游提供 **HTTPS**(受信任或可内部校验的证书),或受控加密通道(内网 VPN/专线、mTLS。 |
| 来源限制 | 上游侧限制仅**本站服务器出口 IP/网段**可访问(防火墙/安全组/WAF 白名单);业务侧保留 Bearer Key。 |
| 外部网络复测 | 加密与来源限制生效后,重跑:成功生成、音频校验、私有回放/下载、超时/失败矩阵;并核查 TTS/DB/Redis/裸端口对外不可达。 |
### 4.2 需上游负责人提供
1. HTTPS 端点与证书(或 mTLS/专线接入方式、CA
2. 允许访问的**来源机器/IP 段**与网络方式(用于双方配置来源限制)。
3. 凭据注入名称、有效期、轮换方式(密钥仍只进 `/etc/kaotings/api.env`)。
4. 加密/来源限制生效时间窗口,便于安排外部复测。
边界:**不擅自修改上游服务**;本轮仅记录条件与所需输入,未改动任何上游配置。
## 5. 上线前外部核查清单
- 外部不可达PostgreSQL 5432、API 8000、Web 3000、CMS 3001、Redis如有均仅回环不对外。
- 上游仅允许本站出口访问且为加密通道§4
- 备份/恢复、Worker 重启、额度对账、音频恢复、回滚:见 `OPERATIONS.md`
- 旧 Big-TTS Web 退役:仍需旧系统负责人提供源端备份证明与替代验收确认。

View File

@ -93,6 +93,8 @@ Phase 1 当前可在本地继续开发,也可在测试机仅部署官网骨架
### Phase 5集成与上线准备
当前状态:任务/额度异常矩阵与管理员并发调减缺陷已修复并在隔离环境验证(`8657f44`);测试工具/依赖/运行说明已固化业务库、CMS 库与音频隔离恢复演练通过(带 1 项既有音频缺失发现);`DEPLOYMENT.md`、`OPERATIONS.md`、`ACCEPTANCE.md` 与 `phase-5-report.md` 已输出。剩余为**上线阻断**:公网 HTTP 上游需加密 + 来源限制 + 外部复测,及音频容量/保留/清理参数待用户确认。详见 `docs/phase-5-report.md`
- 真实环境 HTTPS、反向代理、私网、持久卷和服务凭据。
- 外部验证 TTS、数据库、Redis、对象存储管理端和其他裸端口不可公开访问。
- 备份/恢复、Worker 重启、额度对账、音频恢复和回滚演练。

74
docs/OPERATIONS.md Normal file
View File

@ -0,0 +1,74 @@
# 运维方案OPERATIONS
日期2026-09-10 · 版本 `8657f44`
范围:音频容量/保留/清理/备份/恢复、数据库备份/恢复、Worker 与额度对账、告警。**本轮不擅自删除任何既有音频。**
## 1. 音频存储现状
- 位置:`/home/flym/kaotings-audio`,布局 `{user_id}/{task_id}.wav`(私有本地目录,非对象存储)。
- 元数据:`audio_files``storage_key`、`mime_type`、`size_bytes`、`checksum`、`status`、`expires_at`)。
- 写入:成功结算时先写文件、再登记 `audio_files`(含 SHA256
- 读取:回放/下载按 `storage_key` 取文件;文件缺失返回 404优雅降级
## 2. 备份
| 对象 | 工具 | 位置 | 说明 |
| --- | --- | --- | --- |
| 业务库 `kaotings` | `pg_dump --format=custom` | `/home/flym/backups/kaotings_<ts>.dump` | 含用户/额度/任务/音频元数据/流水 |
| CMS 库 `kaotings_cms` | `infra/scripts/backup-cms-db.sh`root | `/home/flym/backups/kaotings_cms_<ts>.dump` | 独立库/账号 |
| 音频目录 | `tar`/`rsync` 到隔离位置 | 待指定 | 与库备份分开存放 |
> 备份位置、保留期、异地/异机与责任人:**待用户确认**(见 §5
## 3. 容量 / 保留 / 清理(需用户决定的参数集中列出)
| 参数 | 当前值 | 需用户决定 |
| --- | --- | --- |
| 音频保留期 `AUDIO_RETENTION_SECONDS` | 7 天(默认) | 是(产品/隐私/存储预算) |
| 磁盘容量上限 / 告警阈值 | 根盘约 48 GiB 可用,无上限告警 | 是(阈值 + 通知方式) |
| 保留期清理策略 | 未启用定时清理(仅启动时清孤儿) | 是(是否按 `expires_at` 定时删除) |
| 原始文本保留期 | 存于 `tts_tasks.text` | 是(产品/隐私) |
| 备份保留份数 / 周期 | 待确认 | 是(运维负责人) |
- 清理安全边界:
- 启动时 `cleanup_orphan_audio` 仅删除“**无** `available` 记录且 mtime>1h”的孤儿文件。
- 保留期清理在参数确认并实现后启用;**在此之前不删除任何既有音频**。
- 告警:磁盘使用率超阈值、`audio_files` 与磁盘文件数量长期不一致、Worker 长期无 `queued` 消化。
## 4. 恢复(含隔离演练结果)
### 4.1 流程
1. 停止写入(可选,视 RPO`systemctl stop kaotings-api`。
2. 建空库 → `pg_restore --clean --if-exists` 业务库 / CMS 库。
3. 音频:从隔离备份拷贝到 `/home/flym/kaotings-audio`
4. 校验:行数、音频文件↔`audio_files` 关联、`checksum`、额度↔流水一致(见 §4.2 校验 SQL
5. `systemctl start kaotings-api``/healthz` 复核。
### 4.2 校验(隔离演练已执行)
- 业务库行数源=恢复users 35 / quota 34 / tasks 28 / audio 26 / usage 61 / grants 10。
- 额度↔流水:`used` 与 `consume`、`reserved` 与 `reserve-consume-release` 均 0 不符。
- 音频:磁盘 22 文件全部被 `available` 引用(无孤儿)。
- **发现**4 条 `status=available` 且任务 `succeeded``audio_files` 行在磁盘无文件(既有不一致,回放 404 降级)。待调查根因(见 §6
### 4.3 Worker 重启 / 额度对账
- 单 Workersystemd 单进程),原子领取(`FOR UPDATE SKIP LOCKED`+ 5 分钟租约 + 过期 `running` 回收(`WORKER_LEASE_EXPIRED`,释放冻结)。
- 对账:`GET /admin/usage/summary` 汇总 `quota_accounts``usage_records` 口径;`GET /admin/tts/tasks` 查结算状态consumed/released/pending
- 不变式(每账户恒成立):`limit_snapshot + adjustment - used - reserved >= 0`(由 Phase 5 异常矩阵 S3/S4 保证)。
## 5. 待用户确认清单(集中)
1. 音频保留期(`AUDIO_RETENTION_SECONDS`)。
2. 磁盘容量上限与告警阈值、通知方式。
3. 是否启用按 `expires_at` 的定时保留期清理。
4. 原始文本保留期。
5. 备份位置(异地/异机)、保留份数与周期、责任人。
6. 上游 HTTPS/加密端点、允许来源网段、凭据轮换(见 `DEPLOYMENT.md` §4
## 6. 待调查 / 未闭环项(不标为完成)
- **4 条 `available` 音频行无对应文件**:既有 DB/文件系统不一致,待定位根因(文件被外部删除 or 写入路径异常);恢复流程本身通过。
- **上游公网 HTTP**:上线阻断,待加密 + 来源限制 + 外部复测(`DEPLOYMENT.md` §4
- **音频容量/保留/清理参数**待用户确认§5

123
docs/phase-5-report.md Normal file
View File

@ -0,0 +1,123 @@
# Phase 5 集成与上线准备报告
日期2026-09-10
基线代码:`9568622`Phase 4 验收通过)
本轮修复提交:`8657f44`(已部署为发布 `8657f44`
测试环境:`192.168.199.22`,隔离库 `kaotings_p5`,隔离音频目录 `/tmp/kaotings-p5-audio*`,本地伪造上游
原则:只把有证据的能力标为完成;故障注入全部在隔离环境进行,不触碰生产 `kaotings` 库与既有音频。
## 0. 结论摘要
| 项 | 结果 |
| --- | --- |
| 任务与额度异常矩阵Phase 3 遗留) | **通过**(修复后 13 个场景全部通过;见 §1 |
| 管理员并发调减额度不变式 | **发现并修复缺陷**,已验证(见 §1.1 |
| 自动化验收固化 | **通过**(测试工具、依赖版本、运行说明入仓库,秘密走环境变量) |
| 音频运维方案 + 隔离恢复演练 | **通过(带 1 项发现)**:库/音频可恢复、额度与流水一致;发现 4 条 `available` 音频行无对应文件(见 §3 |
| 上游安全改造 | **待外部条件**:公网 HTTP 仍为上线阻断,需上游提供 HTTPS/加密通道 + 来源限制(见 §4 |
| 剩余小项slug 500 / SMTP | **通过(记录在案)**:非法 ID 500 不泄露堆栈/内部信息SMTP 维持 V1 已声明限制 |
上线阻断项:**公网 HTTP 上游**§4未解除前不得判定“可正式上线”。
## 1. 任务与额度异常矩阵(隔离故障注入)
### 1.1 关键缺陷:管理员调减额度与任务并发(已修复)
- 现象(基线 `9568622``POST /admin/users/{id}/quota-adjustments` 计算投影余额时**未**对额度账户加行锁、也未持用户级咨询锁,只依据一次无锁读取判断。并发调减时多个请求读到陈旧的 `used/reserved`,全部通过校验后 `adjustment` 被相对累加,导致 `limit + adjustment - used - reserved` 出现负值(可用额度为负)。
- 隔离复现S3`limit=100` 的账户并发发起 4 次 `-30` 调减,重复 8 轮。**基线代码 7/8 轮不变式被破坏**available 出现负值)。
- 修复(`8657f44`
- `admin_quota`:先 `pg_advisory_xact_lock(hashtext(user_id))`,再 `SELECT ... FROM quota_accounts ... FOR UPDATE`,随后基于加锁后的行重算 `projected`。校验同时覆盖 `used``reserved`(冻结),不只是已用。
- `finish_task_success` / `finish_task_failure` / `recover_expired_tasks`:结算/回收前对额度账户 `FOR UPDATE`,使“冻结、结算、调减”都串行化在同一把行锁上。
- 与 `create_tts_task`(已持咨询锁 + 额度行锁)保持同一加锁顺序,避免死锁。
- 修复后(`8657f44`S3 八轮**不变式全部保持**(最坏 available=10恒 ≥0S4冻结+调减并发压测)终态不变式成立、`reserved` 归零。
不变式定义(每个额度账户恒成立):`limit_snapshot + adjustment - used - reserved >= 0`。
### 1.2 场景矩阵(隔离环境,伪造上游)
被测代码:`8657f44`。每项记录:上游调用次数、任务终态、已用/冻结/可用(当前周期账户)、音频文件结果。
| 场景 | 上游调用 | 任务终态/错误码 | 已用 | 冻结 | 可用 | 音频文件 | 结论 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| S1 成功基线 | 1 | `succeeded` | 2 | 0 | 9998 | 存在,回放/下载 200 | 通过 |
| S2 最后额度并发争用 | 1 | `codes=[202,409]`(仅 1 个冻结成功) | 3 | 0 | 2 | n/a | 通过 |
| S3 管理员并发调减额度 | 0 | 8 轮不变式保持(最坏可用 10 | 0 | 0 | 10 | n/a | 通过(修复后) |
| S4 冻结与调减并发压测 | 3 | 终态不变式成立、冻结归零 | 24 | 0 | 16 | n/a | 通过 |
| S5 非音频 | 1 | `failed / UPSTREAM_NOT_AUDIO` | 0 | 0 | 1000 | 无 | 通过 |
| S5 损坏 WAV | 1 | `failed / UPSTREAM_AUDIO_CORRUPT` | 0 | 0 | 1000 | 无 | 通过 |
| S5 超大响应 | 1 | `failed / UPSTREAM_RESPONSE_TOO_LARGE` | 0 | 0 | 1000 | 无 | 通过 |
| S5 上游 5xx | 1 | `failed / UPSTREAM_HTTP_500` | 0 | 0 | 1000 | 无 | 通过 |
| S6 跨周期结算 | 1 | 结算落到任务创建时所属(上一)周期账户 | 5(上周期) | 0 | 100(本周期未变) | n/a | 通过 |
| S7 存储失败 | 1 | `failed`(音频目录不可写) | 0 | 0 | 10000 | 无 | 通过 |
| S8 上游超时 | 1 | `failed / UPSTREAM_TIMEOUT` | 0 | 0 | 10000 | 无 | 通过 |
| S9 重启恢复-queued | 2 | `queued→succeeded``running` 保持,租约未到期) | 3 | 3 | 9994 | queued 任务音频存在 | 通过 |
| S10 重启恢复-running 租约过期 | 0 | `failed / WORKER_LEASE_EXPIRED`,冻结释放 | 0 | 0 | 10000 | 无 | 通过 |
要点:
- 所有失败/异常路径均**释放冻结**、**不产生残留音频文件**、**不破坏额度不变式**。
- 重启恢复:`queued` 任务在进程重启后被重新领取并结算;`running` 任务在租约过期后被回收为 `WORKER_LEASE_EXPIRED` 并释放冻结。租约 5 分钟、单 Worker、数据库锁为跨进程边界与 Phase 3 一致)。
- S9 中 `running` 任务在重启后仍为 `running` 属预期(租约未到期,不会重复处理,待租约过期由回收逻辑兜底)。
对比基线:`9568622` 下 S3 失败7/8 轮破坏不变式),其余场景与上表一致。**修复后 `8657f44` 全部通过。**
复现方式(隔离,秘密走环境变量):`services/api/tests/phase5_exception_matrix.py`,见 `services/api/tests/README.md`
## 2. 自动化验收固化
- 测试与运行时依赖版本固化:`services/api/requirements.txt`、`services/api/requirements-test.txt`(已验证版本见 `services/api/tests/README.md` 表格)。
- 隔离库准备脚本 `services/api/tests/provision_isolated_env.sh``SUDO_PASSWORD`、`P5_DB_PASSWORD` 等**仅经环境变量注入**,不落仓库。
- 异常矩阵脚本 `services/api/tests/phase5_exception_matrix.py`:自启动隔离 API + 伪造上游,逐项断言并输出结果表;退出码 0 表示全部不变式通过,可重复执行、可作为验收门禁。
- 已在独立环境(隔离库 + 独立音频目录 + 伪造上游)验证可重复执行,不依赖测试机手工安装的工具;浏览器测试的 Playwright 浏览器二进制仍不入库(沿用 Phase 4 约定,`PLAYWRIGHT_BROWSERS_PATH` 指向本地缓存)。
## 3. 音频运维方案与隔离恢复演练
### 3.1 容量 / 保留 / 清理(需用户决定的参数集中列于 OPERATIONS.md §3
- 存储:本地私有目录 `/home/flym/kaotings-audio``{user_id}/{task_id}.wav`),非对象存储。
- 保留期:`AUDIO_RETENTION_SECONDS`(当前默认 7 天)——**待用户确认**。
- 清理:进程启动时 `cleanup_orphan_audio` 删除“无 `available` 记录且 mtime>1h”的孤儿文件保留期清理策略待确认后再启用**本轮不擅自删除任何既有音频**。
### 3.2 隔离恢复演练(业务库 + CMS 库 + 音频)
在隔离库 `p5_restore_biz` / `p5_restore_cms` 与隔离音频目录执行 `pg_dump`→`pg_restore` + 音频拷贝,结果:
- 业务库行数(源 vs 恢复全部一致users 35、quota_accounts 34、tts_tasks 28、audio_files 26、usage_records 61、membership_grants 10。
- 额度↔流水一致性恢复库34 个额度账户,`used` 与 `consume` 流水 0 不符、`reserved` 与 `reserve-consume-release` 流水 0 不符。
- CMS 库恢复 14 张表products=1、users=1、payload_migrations=1结构完整
- 音频:磁盘 22 个 `.wav` 全部可被 `available` 记录引用(无孤儿);**发现 4 条 `status=available` 且任务 `succeeded``audio_files` 行在磁盘无对应文件**(源目录与恢复目录均缺失)。
- 影响:这些历史任务在“音频可用”标记下回放/下载会返回 404优雅降级不崩溃属既有 DB/文件系统不一致,**非本轮引入**。
- 处置:作为待调查项记录,不阻断恢复;恢复流程本身(库 + 音频 + 额度/流水)验证通过。
结论业务库、CMS 库、音频均可在隔离环境完整恢复,历史↔音频关联与额度数据一致(除上述 4 条既有缺失)。
## 4. 上游安全改造(上线阻断,待外部条件)
- 现状TTS 上游为公网 **HTTP**`TTS_UPSTREAM_URL`Bearer Key 走明文传输。这是正式上线阻断项。
- 上线前必须满足(具体实施条件):
1. **传输加密**:上游提供 HTTPS受信任/可内部校验的证书),或受控加密通道(如内网 VPN/专线、mTLS
2. **来源限制**:上游侧限制仅本站服务器出口 IP/网段可访问(防火墙/安全组/WAF 白名单),并在业务侧保留 Bearer Key。
3. **外部网络复测**:加密与来源限制生效后,重新执行成功生成、音频校验、回放/下载、超时/失败矩阵与外部不可达性核查。
- 需要上游负责人提供:
- HTTPS 端点与证书(或 mTLS/专线接入方式、允许的来源网段)。
- 凭据注入名称、有效期、轮换方式(密钥仍只进 `/etc/kaotings/api.env`,不进 Git/前端/日志)。
- 允许的来源机器/IP 段与网络方式,便于双方配置来源限制。
- 边界:**不擅自修改上游服务**;本轮仅记录条件与所需输入,未改动任何上游配置。
## 5. 剩余小项
- **按 slug 的 REST 路由**:不新增。公网产品展示统一走 `where[status][equals]=published` 列表 + `where[slug][equals]=` 筛选(已验证 200
- **非法 ID 导致 500**`GET /cms/api/products/:slug`Payload 将 `:slug``id` 处理)传入非 UUID 返回 500响应体为**通用** `{"errors":[{"message":"Something went wrong."}]}`**未泄露堆栈、源码路径或内部信息**(已扫描确认)。作为 Payload 行为记录在案,公网展示闭环不受影响。
- **SMTP 未接入**:维持为 **V1 已声明限制**(验证渠道返回未启用)。管理员密码恢复依赖本地受控流程,见 `docs/cms-admin-recovery.md`;本轮不接 SMTP。
## 6. 部署与版本核对
- 修复提交 `8657f44` → 发布目录 `/home/flym/releases/8657f44`(自 `p4` 完整拷贝后更新 API
- 软链 `kaotings-api`→`…/8657f44/services/api`web/cms 同步指向 `8657f44`,仅重启了发生代码变更的 `kaotings-api`)。
- 版本核对:线上 `services/api/app/main.py` SHA256 = `571037f4…`,与本地 `8657f44` 完全一致;`systemctl is-active kaotings-api` = `active``GET /healthz` = `{"status":"ok","database":"ok","service":"api"}`
- 回滚:软链指回 `…/p4/services/api` 并重启 `kaotings-api` 即可(`p4` 保持完好)。
## 7. 未通过 / 待外部条件项(不得标为完成)
- **上线阻断**:公网 HTTP 上游未加密、无来源限制§4待上游提供条件并外部复测。
- **既有数据发现**4 条 `available` 音频行无对应文件§3.2),待调查根因;恢复流程本身通过。
- 旧 Big-TTS Web 退役前,仍需旧系统负责人提供源端备份证明与替代验收确认(沿用 Phase 3/4 边界)。