音乐舱怎样统一多组件发布
音乐舱的一次更新涉及播放器、扩展、音频网关、恢复服务、QQ 插件和访问入口。单独看,每个组件都能构建和启动;放到一起,维护者还要回答另一组问题:这次采用了哪些源码和补丁,交付的文件是否完整,哪些实例真正切换了,失败之后应该恢复什么、保留什么?
“三个统一”围绕这些问题建立发布约束:统一版本清单固定输入与身份,统一构建入口形成可校验的发布包,统一部署入口把计划、切换和运行回执连起来。本文先讲它们为什么需要一起工作,再讲怎样验证结果。发布命令见附录 A,网页与手机验收见附录 B;2026 年 11 月 1 日首次生产发布的记录集中放在附录 C,不作为长期有效的健康保证。
一、分散更新留下的确认难题
浏览器中的 Any Listen 负责播放,音乐扩展向 TS 网关请求目录和音频,恢复服务协调受控续期与登录,OpenClaw 插件连接 QQ,入口服务提供访问和管理边界。一次产品更新可能涉及其中几个组件,也可能只改变一个。发布者必须同时知道组件之间的关系,以及本次有权管理哪些实例。
逐个手工更新时,“已经上传代码”“镜像构建成功”“容器启动了”分别描述不同阶段。它们不能互相代替:源码变了,进程未必切换;播放器更新了,扩展仍可能是旧版;镜像标签相同,实际内容也未必相同。问题不仅是操作次数多,还在于输入、产物与运行状态之间缺少可核对的对应关系。出错时,维护者需要重新查文件、镜像和进程,才能判断究竟执行到了哪里。
这次改造要回答的核心问题是:多组件音乐服务怎样让构建输入、发布产物和线上运行状态保持可核对的一致,并在发布异常时安全恢复? 目标包含固定构建来源、核实交付完整性、限制部署影响范围和保存恢复依据;不要求所有组件使用相同版本,也不要求每次发布都替换全部实例。
二、三个统一分别约束发布的哪一段
2.1 统一版本清单,区分输入、产物和运行身份
单个版本号只能给一次发布命名,无法充分说明组成它的内容。音乐舱使用三份职责不同的记录:Git 中的 release/manifest.json 描述构建输入,发布包中的 release.json 描述实际产物,部署产生的 deployment.json 描述运行变化。输入清单不能证明文件已经构建出来,产物清单也不能证明服务器已经切换;三份记录通过源码提交、清单哈希和组件身份建立对应关系。
输入清单固定 Any Listen 上游完整 commit、补丁顺序与哈希、工具链、基础镜像 digest、目标平台和六组件定义。它不写自己的 Git 提交号,避免自引用;构建时从干净工作区取得完整 HEAD。发布清单中的 releaseId 与 sourceCommit 等于这次构建采用的源码完整 SHA,并记录输入清单哈希、平台、实际工具版本和组件构件。构件逐项保存相对路径、字节数与 SHA-256,组件保存构件集合及内容摘要。
运行记录则保留 deploymentId、releaseId、发布清单哈希、时间、状态和逐组件 before/after、检查结果。没有更新的组件继续保留原版本,未知来源明确为 null。统一的是描述和核对规则;如果用同一个提交号填满所有组件,反而会丢掉“哪些改变了、哪些没有改变”的事实。
2.2 统一构建入口,把固定输入变成可检查的交付物
只有输入固定,仍不能排除缺文件、补丁未应用、目标平台错误等产物问题。scripts/build-release.sh 将固定源码、Any Listen 固定上游与仓库内补丁汇成一次构建,输出六类组件的发布包:gateway、recovery、player、extension、plugin、entry。构建和校验完成后才形成完整交付物,不依赖发布者临时在服务器上拼凑文件。
七个补丁依次覆盖 Media Session、诊断、后台预取、手机播放契约、元数据刷新、播放错误和维护者更新提示。缺补丁、顺序错误、前像不符或哈希不同都应让构建失败。播放器的原生模块也必须在 Linux 目标环境生成并验证,不能用 macOS 二进制代替 linux/amd64 产物。
发布包还需独立校验。verifyBundle 拒绝缺失文件、未申报文件、路径越界、外层文件符号链接、哈希不符及不安全归档,解包过程检查嵌套与链接链。这里能证明的是操作者选定发布包的完整性;没有第三方签名,不能据此宣称来源已经得到第三方认证。
2.3 统一部署入口,把变化范围和实际结果留下来
发布包正确仍不等于运行正确。scripts/deploy-release.sh 先依据发布包与私有 target 生成计划,再更新变化组件并记录结果。target 精确声明实例、端口、用户、目录、挂载、env-file 路径与检查项;未声明的旧站播放器和共享入口不随本次部署被顺带替换。
这使“发布什么”与“允许影响哪里”分开。相同包与配置再次检查时,计划应能说明为什么不需要切换;发现漂移时,也应展示逐组件差异,而不是只返回一个成功退出码。发生 SSH 超时或部分切换时,部署结果可能成为 unknown 或 mixed,后续处理依靠原事务与实际状态核对,不能把没有收到成功消息解释成没有执行。
| 统一项 | 关键记录或入口 | 防止哪一种判断失误 |
|---|---|---|
| 版本 | release/manifest.json、release.json、deployment.json |
用一个版本号代替输入、产物和实际运行身份 |
| 构建 | scripts/build-release.sh、verifyBundle |
把源码固定或编译成功当成完整交付物已经可用 |
| 部署 | scripts/deploy-release.sh、私有 target 与回执 |
把包正确、命令退出 0 或超时当成运行状态已确定 |
三个入口需要衔接。统一构建若没有固定输入,无法追溯差异;统一版本若没有部署回执,无法回答线上实际运行什么;部署脚本若不复验发布包,也无法保证计划中的内容就是收到的文件。
三、一次发布怎样从固定输入走到运行回执
发布从干净源码工作区开始,构建器按输入清单获取上游、应用补丁并生成目标平台产物;独立校验通过后,发布者选择该包与对应 target,先查看 plan。只有变化范围和前置条件符合预期,才进入 apply。这个顺序把“准备的内容正确”和“允许切换的对象正确”分别检查,避免在停止服务以后才发现基础条件不成立。
music-cabin 固定源码 + Any Listen 固定上游 + 七个补丁
|
build-release.sh
|
发布包 + release.json
|
deploy-release.sh plan / apply
|
实际运行组件 + deployment.json
|
status / verify + 人工听感验收apply 获取目标锁,复验包与配置,按依赖顺序更新变化组件并写回执。容器可能经历 stop、rename、create、start,因此会有短暂中断,不属于零停机发布。相同包与配置重复 apply 应为 no-op,不过日常检查应优先使用 plan/status,不能靠再次执行变更来证明部署一致。
运行身份要核对实际内容。 镜像至少区分本地构建 config digest、运行时镜像身份与 registry digest。本次生产 Docker 的 containerd image store 暴露 OCI manifest digest,与构建侧 config digest 不一定相同。工具从已校验镜像归档推导对应关系;只比较标签会漏掉内容变化,直接比较不同种类的 digest 又可能制造假漂移。镜像未推仓库时,也不能声称已经取得 registry digest。
入口配置同样存在“文件更新了,运行却未变”的风险。Docker 单文件 bind mount 可能钉住旧 inode,rename 替换宿主文件后容器仍读旧内容。entry 的 caddy-container 适配先 validate/adapt 候选,再原地写宿主配置、重启既有入口,核对宿主文件、容器文件和运行配置;生产采用 admin off 时可读取 autosave。无法取得可靠运行配置就不能签署生效。没有变化时不重启,入口镜像与外层共享 Caddy 也不随配置发布重建。
恢复依靠实际状态,而不是重新执行猜测。 超时后先保留原 target、状态目录和事务信息,再查看 before/after;存在未决状态时不盲目 apply。确认需要回退后,只恢复那次部署改变过的组件,并检查候选代码能否读取当前数据。最新 active、ledger、pendingCommit 和业务卷要保留,因为代码回退不能抹掉发布后发生的合法凭据提交或用户操作。
四、怎样证明统一发布有效,而不把部署成功当成可用
验收需要回答三个不同的问题:运行内容是否与声明对应,服务请求是否按边界工作,用户是否真实完成播放。前一层通过只能支持该层结论。status.inSync 不能证明手机锁屏跨曲,HTTP 206 不能证明播放器持续出声,桌面听感也不能替所有手机和耳机签署结果。
| 验证层 | 要回答的问题 | 应保留的证据与限制 |
|---|---|---|
| 部署身份 | 选定发布包与受管实例是否一致? | releaseId、清单哈希、逐组件身份、drift 与未决状态;不证明用户功能正常 |
| 服务链路 | 入口、鉴权、音频和持久化是否满足检查要求? | 目标 checks、状态码、音频字节与一致读摘要;结论只覆盖相应样本和时间 |
| 用户体验 | 本设备能否真实播放、拖动、跨曲并保留数据? | 实际声音、进度、手机后台与锁屏操作、同站点前后数据;未测设备单独标注 |
部署层通过 plan/status 观察,必要时对指定部署执行 verify。verified 只表示该 target 中要求的检查满足条件:非必需项仍可能为 not_tested,没有写进 target 的功能不会自动被验证。verify 会取锁并更新回执,可能清理本地已收敛状态,不是完全只读;它不切换产品组件。检查时应展开 checks 和 drift,不能截取绿色总状态。
音频层还要区分缓存与上游授权。缓存命中并返回 Range 数据,可以证明缓存读取和传输链路工作,却不能证明当前夸克授权仍有效。严格未缓存验证需要可信缓存未命中证据,再结合该请求的上游访问与音频结果;缺少可信缓存头时,即使返回 206,也应标 not_tested。库存中没有同尺寸对象只作补充证据,不足以独立证明未缓存。不能用改名或清空生产缓存代替验证。
source not found 的既往故障说明了这一区分的价值:账号接口和取下载地址接口可用,但媒体直链拒绝请求,播放器回退又覆盖原始错误。受控刷新恢复了媒体授权,协调器补上账号健康而音频认证失败时触发刷新、失败冷却且不自动升级二维码的处理。因此,“QQ 扫过码”“账号接口 200”“未缓存歌曲可播放”必须分别核实,不能看到聊天中的成功提示就要求无限重登。
用户数据也要按同一站点前后比较。新旧站共用 TS 音频网关,但播放器实例与用户数据独立,两站列表不同不能推断为丢失。服务器侧使用一致读取得的行数和内容摘要,浏览器侧核对收藏、歌单、排序和队列;文件大小与 WAL 哈希不足以证明数据保留,正常播放产生的历史变化和启动产生的备份时间变化需要逐项解释。
五、这个方案的选择与尚未解决的边界
音乐舱将发布交付集中在 music-cabin,继续固定 Any Listen 上游并应用仓库补丁。这个选择沿用现有播放器能力,发布来源仍能追溯;代价是每次调整上游都要维护补丁前像、顺序和目标平台构建。另一种做法是复制播放器长期分叉,获得更大的直接修改自由,但也要自行承担后续上游合入;本次没有采用它。继续逐个手工交付则保留了操作灵活性,却无法解决第一章描述的对应关系问题。
本次也没有引入新的发布平台,镜像通过 tar 分发而非 registry。统一入口先服务于现有实例和受控 target,而不承诺通用平台能力。旧 Go 网关与独立 kuake CLI 已退役,旧 Go 镜像已永久删除,不能作为新发布的运行依赖或可播放回退选项。
仍有几项边界必须保留:入口环境变量轮换需要独立重建,重启既有容器不会重读 --env-file;混合 unknown 状态要依照实际状态处置,工具拒绝盲重放;代码回退不等于完整数据灾备,本次没有实现完整备份恢复;包完整性校验不等于第三方签名认证;真实手机矩阵与严格未缓存证据尚未完整验收,详见附录 C。
三个统一让第一章的问题有了可以查证的回答:输入由清单固定,产物由构建和哈希约束,运行由目标与回执核对,恢复由变化记录和当前数据兼容性决定。它没有消除所有运行故障,却让维护者能够区分“交付内容不对”“切换未收敛”和“部署正确但用户仍不可用”,据此选择下一步,而不是重复上传、重启或扫码。
附录 A:发布、复核与回退操作
本附录供有权限的发布者使用,不适用于只想检查网页播放的普通使用者;只做日常验收时跳过构建和 apply,从步骤 6 的只读命令开始。以下命令是操作说明,本次文章改写没有重新执行生产发布、verify 或回退。公开稿不附生产 target、凭据、服务器地址或原始数据。
A.1 执行前准备与参数
发布工作机需要 Node.js 24、npm 11、锁定的 pnpm、Docker/BuildKit、Git 和目标机 SSH 权限。目标为 linux/amd64;Apple Silicon 可以模拟构建,但原生模块仍在 Linux 目标环境生成。发布者确认包和目标,实例维护者提供私有配置并授权变更;复核者按回执与设备实际结果记录结论。
开始前逐项确认:源码 clean;输出目录在仓库外;target 属于本实例且挂载、env-file 和检查项正确;没有 unknown/applying 未决状态;变更范围得到授权。任一条件不满足就停止。deploy/release/targets/example.json 只是结构示例,不能作为覆盖现有生产的配置。
| 参数 | 含义 | 填写规则 |
|---|---|---|
CABIN_BUNDLE |
本次选定发布包的绝对路径 | 新发布填目标输出目录,复核填已部署的原包,不要拿新包核对旧生产 |
CABIN_TARGET |
私有 target JSON 的绝对路径 | 使用维护者提供的实际配置;不是 token,不公开 target 或 env-file |
CABIN_DEPLOYMENT |
要验证或回退的部署 ID | 取该次实际回执,不沿用文章历史 ID;首次发布示例集中在附录 C |
A.2 按执行顺序完成发布
步骤 0:确定状态与停止条件。 在发布工作机确认上面的前置条件,先保留原部署记录和用户数据基线。出现歌单/历史明显减少、账户绑定变化、凭据提交未决、部署反复重启或 unknown 时停止变更,记录时间与关联 ID,进入 A.3;不要连续 apply 或重登。
步骤 1:设置本次参数并执行独立测试。 执行位置为 music-cabin 仓库根目录。替换路径并设置实际部署 ID,测试应全部退出 0;任一测试失败就停在本步,先定位失败,不能继续构建和发布。
# 执行位置:music-cabin 仓库根目录;按 A.1 参数表替换路径
CABIN_BUNDLE=/absolute/releases/cabin-release
CABIN_TARGET=/absolute/private/target.json
# 复核或回退时填实际回执 ID;新发布完成后再设置
CABIN_DEPLOYMENT=
node --test scripts/release/test/*.test.mjs
npm --prefix services/music-cabin-ts run typecheck
npm --prefix services/music-cabin-ts test
步骤 2:构建并核对输出。 仅在步骤 1 的测试全部退出 0 后,在同一仓库根目录执行下面命令。预期退出 0,输出包含 release.json 与六组件产物。构建器执行编译、补丁门禁和播放器产物校验,不能替代步骤 1 的独立测试。失败时保留日志,修复输入或环境后再构建;已有输出不匹配应拒绝覆盖,有效且相同的输出可以复用。生产包采用默认优化,受限环境显式使用 --player-native-cflags "-O0 -g0" 时会记录进构件,不保证生产性能等价。
scripts/build-release.sh \
--output "$CABIN_BUNDLE" \
--platform linux/amd64步骤 3:独立校验包。 在同一仓库根目录执行,不连接生产。预期 ok: true、errors: [],releaseId 对应所选源码;记录 manifestSha256,后面核对回执的 releaseManifestSha256。失败则停止,重新取得或构建包,不手改哈希。
node --input-type=module - "$CABIN_BUNDLE" <<'JS'
import { verifyBundle } from './scripts/release/manifest.mjs';
const result = await verifyBundle(process.argv[2]);
console.log(JSON.stringify({
ok: result.ok,
errors: result.errors,
releaseId: result.manifest?.releaseId,
manifestSha256: result.manifestSha256
}, null, 2));
process.exitCode = result.ok ? 0 : 1;
JS步骤 4:查看计划。 在发布工作机的仓库根目录执行 plan。它不加载镜像、不停止服务、不改运行状态。读取 ready、preconditionBlockers、每个组件 action/reasons 和未受管理实例;新发布可以有预期 replace,同包复核应是 noop: true 且全 none。ready 为 false 或范围不符时停在本步,核实包与 target,不能仅凭退出码 0 放行。
scripts/deploy-release.sh plan \
--bundle "$CABIN_BUNDLE" --target "$CABIN_TARGET"步骤 5:按已核对计划切换。 只有步骤 4 满足条件且已获得变更授权时执行。预期产生本次回执和逐组件检查结果;保存实际 deploymentId 并填入 CABIN_DEPLOYMENT。失败、超时或出现 mixed/unknown 时保留同一状态目录和事务,停止重放并进入 A.3,不重新生成事务 ID 掩盖未决结果。
# 变更命令;不是日常验收命令
scripts/deploy-release.sh apply \
--bundle "$CABIN_BUNDLE" --target "$CABIN_TARGET"步骤 6:只读复核部署身份。 使用刚部署的原包和同一 target。预期 status 的 inSync: true、pendingUnknown: null、releaseId 正确;plan 的 ready: true、noop: true、action 全为 none。失败时读取 drift 和 before/after,按 A.3 处理,不用再次 apply 代替排查。
scripts/deploy-release.sh status \
--bundle "$CABIN_BUNDLE" --target "$CABIN_TARGET"
scripts/deploy-release.sh plan \
--bundle "$CABIN_BUNDLE" --target "$CABIN_TARGET"步骤 7:复验回执并补用户验收。 在确认可以更新回执时执行 verify,先确保 CABIN_DEPLOYMENT 为实际 ID。展开顶层 ok、receipt.status、checks 和 drift;必需项不满足不能签署通过,非必需未测项原样记录。失败则保留检查结果并进入 A.3。最后按附录 B 做真实设备验收,不能以 verified 代替听感。
# 会取锁、更新回执;不切换产品组件
scripts/deploy-release.sh verify \
--bundle "$CABIN_BUNDLE" --target "$CABIN_TARGET" \
--deployment-id "$CABIN_DEPLOYMENT"A.3 中止分支与受控回退
以下表格用于前面步骤的失败分流。先记录信号、发生步骤、时间与逐组件状态,再按首查动作确定安全位置;未决状态未核实前不执行新的切换。锁冲突也需要确认另一个发布进程,不能直接删锁。
| 信号 | 首先做什么 | 不要做什么 |
|---|---|---|
bundle_invalid、哈希不符 |
停止,核对来源并重新构建/取得包 | 修改 JSON 中的哈希使其通过 |
locked |
核实另一发布进程与目标 | 直接删锁并同时发布 |
unknown、SSH 超时 |
用同一 target 和状态目录执行 status,对比 before/after | 立刻再次 apply、重新生成事务 ID |
| mixed / drift | 保留逐组件实际值,按已核实状态处置 | 把部分切换当作从未执行 |
rollback_incompatible |
检查当前数据 schema 与候选代码兼容性 | 强制退回读不了当前数据的代码 |
| Caddy 候选无效 | 修复候选后重新计划;原配置应保持运行 | 跳过 validate,直接重启 |
| 播放地址获取失败 | 区分扩展注册、代理、网关响应和媒体授权 | 一律归为“需要扫码”,或只改提示文案 |
| 概要 degraded,但各维度正常 | 检查观察时间、player 未验项和恢复账本差异 | 为消除告警伪造验收或改账本 |
参数/包错误通常退出 2,锁冲突 4,未知状态 5,不兼容回退 6,前置拒绝 7;其他失败可能为 1 或验证相关错误。仍必须读取业务 JSON,尤其 plan.ready、status.inSync 和 verify.ok,不能仅看 $?。
回退步骤 1:选定记录并核实兼容性。 确认那次部署记录与恢复资料存在,没有 unknown/applying 未决状态,候选代码兼容当前数据;把该部署 ID 填入 A.1 的参数。任一条件不满足则停止,先由维护者核实,不强制回退。
回退步骤 2:执行并回读实际状态。 在发布工作机的仓库根目录执行以下变更命令。它仅恢复该次部署改变过的组件,保留最新 active、ledger、pendingCommit 和业务卷;入口回退也要验证运行配置。随后执行步骤 6 的 status/plan,依据恢复后回执核对实际身份与剩余 drift,不假定回退后仍与撤销前的包一致;失败进入上表,不盲重试。回退不是完整数据灾备,也不是恢复旧 Cookie。
# 恢复变更命令;仅在已经确认需要回退时执行
scripts/deploy-release.sh rollback \
--bundle "$CABIN_BUNDLE" --target "$CABIN_TARGET" \
--deployment-id "$CABIN_DEPLOYMENT"附录 B:网页、手机与服务边界验收
普通使用者从这里开始,不需要执行 apply、verify 或回退。主入口为音乐舱新站,旧站作兼容回归。准备电脑和实际听歌手机,记录系统、浏览器、网络与时间,先截图同站点的歌单、收藏数和队列。验收过程中主动修改的数据也要记录。
出现收藏或历史明显减少、账户绑定变化、凭据事务未决或服务反复重启时,停止连续刷新、登录和变更,记录提示与公开关联 ID,交维护者按 A.3 处理。不要覆盖另一站点的列表,也不要清空缓存来证明未缓存播放。
B.1 普通使用者的 10—15 分钟验收
按下表做基础检查,并填写本附录末尾的记录。通过条件是可观察的真实结果,不是播放按钮变形或页面显示“成功”。没有执行的项目保留“未测”,不推断为通过。
| 步骤 | 操作 | 通过标准 | 不通过时记录 |
|---|---|---|---|
| 1 | 正常登录新站,打开“我喜欢”和曲库列表 | 能进入页面,原有列表与收藏可见,无反复登录 | 时间、入口、HTTP/页面提示 |
| 2 | 播放原先报错的收藏曲,再选两首不同歌曲 | 能听见声音,进度持续推进至少 30 秒;不能只看按钮变成暂停 | 曲名或公开曲目 ID、错误文案、是否所有曲目都失败 |
| 3 | 暂停后继续,把进度拖到歌曲中段 | 暂停与继续对应真实声音,拖动后从新位置播放,不无限缓冲 | 目标位置、实际位置、等待时长 |
| 4 | 手动下一首,再把一首歌拖近结尾并等待自然结束 | 手动切歌与自然跨曲都成功,队列顺序正确 | 队列、上一首与下一首、是否出现假结束 |
| 5 | 手机播放后切到其他应用,再锁屏等待自然跨曲 | 当前曲继续,锁屏后下一首能自动播放;记录实际等待时长 | 系统/浏览器、锁屏时刻、停在哪一步 |
| 6 | 使用锁屏或耳机控制播放/暂停;若系统支持,再测下一首 | 支持的动作确实改变播放;不支持时标“未支持/未验证” | 控件是否出现、点击后实际声音 |
| 7 | 回到页面、刷新,再重新打开 | 页面曲目和真实播放状态可核对;收藏、歌单、排序仍在 | 页面状态与真实声音是否不一致 |
| 8 | 打开旧站做一次播放与列表核对 | 旧站仍可访问,其自身历史和列表未丢失 | 与该站发布前的差异 |
拖近结尾可以快速验证跨曲,但不能替代较长时间的后台播放。手机中断项目单独记录:有条件时检查来电或系统音频抢占、网络短暂断开与恢复、第二台设备独立播放。页面隐藏和锁屏不应被误当成来电直接暂停;系统明确打断后默认暂停,不应突然自行出声。桌面通过不覆盖 iOS、Android、耳机和跨设备矩阵。
B.2 服务边界与管理页面核对
下面的公开请求不带凭据,只检查入口和鉴权边界,不证明手机或音频已经正常。可以在自己的终端执行,预期依次为 200 / 401 / 404 / 308 / 200;不加 -L,否则会掩盖原始 308。网络不可达和服务返回错误分别记录。
curl --max-time 15 -sS -o /dev/null -w '%{http_code}\n' \
https://test.music.fulequn.online/
curl --max-time 15 -sS -o /dev/null -w '%{http_code}\n' \
https://test.music.fulequn.online/manage/
curl --max-time 15 -sS -o /dev/null -w '%{http_code}\n' \
https://test.music.fulequn.online/api/health
curl --max-time 15 -sS -o /dev/null -w '%{http_code}\n' \
http://test.music.fulequn.online/
curl --max-time 15 -sS -o /dev/null -w '%{http_code}\n' \
https://music.fulequn.online/内部网关 Bearer、私网监听、recovery 健康、凭据版本与持久化由受控 target checks 检查;不要为让公网 /api/health 返回 200 而开放内部接口。业务 revision 对应当前实际凭据版本,不能永远写死为历史值 13,也不能由部署程序制造新凭据版本。音频取得 206 和真实字节只能支持相应样本链路,ID3 只适用于部分 MP3 样本,不作 FLAC/M4A 的统一标准。
管理员通过已有管理入口访问,不在浏览器粘贴服务器 Bearer token。分别核对账号、目录、音频、持久化、观察时间、凭据版本和待处理动作,不只看 healthy/degraded。概览与各维度不一致时保留结果,记录状态展示问题;历史恢复账本与网关版本差异也要保留,不能为变绿而手改账本。
验收记录。
将以下记录对应到具体设备、站点和发布身份;没有权限取得部署 ID 时向维护者确认,不自行猜测。分别给出部署身份、服务链路和本设备听感结论,保留未完成项目及原因。
验收日期与时区:
站点:新站 / 旧站
设备、系统、浏览器:
网络:Wi-Fi / 蜂窝
产品 releaseId:
部署 deploymentId:
原收藏曲播放:通过 / 失败 / 未测
其他两首播放:通过 / 失败 / 未测
暂停、继续、拖动:通过 / 失败 / 未测
手动切歌、自然跨曲:通过 / 失败 / 未测
手机后台与锁屏跨曲:通过 / 失败 / 未测
锁屏/耳机控制:通过 / 失败 / 不支持 / 未测
来电与断网恢复:通过 / 失败 / 未测
歌单、收藏、排序、历史:保留 / 异常
旧站回归:通过 / 失败 / 未测
异常发生时间、公开关联 ID、曲目、操作、实际结果:
未完成项目及原因:
部署身份结论:一致 / 漂移 / 未核实
服务链路结论:通过 / 失败 / 未测
结论:本设备通过 / 有条件通过 / 不通过附录 C:首次发布记录与固定源码入口
本节仅汇总 2026 年 11 月 1 日首次统一生产发布与最终工具版本的已有证据。本次文档重构没有重新运行这些生产检查,以下数量与状态沿用该次记录;它们不是当前每台设备均已通过的声明。
产品包构建后,部署工具补充了生产路径与镜像身份兼容,因此工具提交晚于产品提交,产品包没有因此被修改。deploy/cloud/release.json 已成为退役说明,不再是发布真源;源码仓库 main 在该次发布记录中已推送到最终工具提交。
| 项目 | 首次发布记录 |
|---|---|
| 产品包源码提交 | dcb06c4b94fcac06b5355753ec46d305b5745089 |
| 最终部署工具提交 | ee05387a10e56d4e350f4b23b82a087c1d23ab0d |
| 产品包清单 SHA-256 | a091dada2e8440fcc60062b6af0f30ef7fbac2a68700d47421b98379dcd2ae7a |
| 成功部署 ID | deploy-20261101T021345-7f93be25 |
| 最后记录的回执状态 | verified |
| 项目 | 已有证据 | 结论边界 |
|---|---|---|
| 发布工具回归 | 最终 110 项通过;网关完整测试 1325 项通过 | 自动测试不能替代真实设备 |
| 隔离部署演练 | 真实容器/镜像替换、no-op、兼容回退、卷标记保留、unknown 对账,16 项通过 | 使用隔离目标与模拟账号 |
| 入口激活演练 | 候选校验、单文件挂载、旧→新 HTTP、失败恢复、no-op 等 11 项通过 | 生产这次 entry 无变化,没有重启 |
| 生产发布 | 回执 verified;部署后 plan 全 none;status inSync,无 pendingUnknown | 仅该目标和时间点 |
| 访问与鉴权 | 两站 200、管理无凭据 401、公网内部接口 404、HTTP 308 | 不等于音频或手机验收 |
| 代表音频 | 收藏曲与缓存代表返回 206,人工探针取得 1024 B、ID3 | 严格未缓存项仍 not_tested |
| 用户数据 | 主要列表、曲目、排序、队列与 URL 表对照一致;metadata 仅备份时间变化 | 实时日志/WAL 等有正常写入 |
| 真实手机矩阵 | 未形成完整签署证据 | 由附录 B 的设备验收补齐 |
该次管理证据中账号、目录、音频与持久化正常,player 仍为 not_tested,恢复账本和网关存在历史版本差异。两个普通 Range 检查通过,但 uncached-audio-evidence 为 not_tested,原因是网关没有可识别缓存头;该项若被 target 设为必需,未验证就不能作为通过项发布。
固定版本入口如下,用于追溯实现;其中发布操作说明里的旧“未生产 apply”段落对应集成阶段,该次生产结果以本节记录为准,不将旧段落解释成后来未发布。