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.
11 KiB
11 KiB
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,恒 ≥0);S4(冻结+调减并发压测)终态不变式成立、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 走明文传输。这是正式上线阻断项。 - 上线前必须满足(具体实施条件):
- 传输加密:上游提供 HTTPS(受信任/可内部校验的证书),或受控加密通道(如内网 VPN/专线、mTLS)。
- 来源限制:上游侧限制仅本站服务器出口 IP/网段可访问(防火墙/安全组/WAF 白名单),并在业务侧保留 Bearer Key。
- 外部网络复测:加密与来源限制生效后,重新执行成功生成、音频校验、回放/下载、超时/失败矩阵与外部不可达性核查。
- 需要上游负责人提供:
- 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.pySHA256 =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 边界)。