Files
ctms/docs/audits/desktop-release-stabilization-checklist.md
T
2026-07-08 20:46:56 +08:00

221 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CTMS Desktop Release Stabilization Checklist
状态: `active`
适用范围: Web 与 macOS Desktop 统一客户端发布
最后更新: `2026-07-02`
本清单用于第一、二阶段桌面端能力完成后的准发布稳定化。它不引入离线登录、本地业务数据存储、内嵌后端服务或离线同步。
## 1. 发布链路门禁
发布候选提交必须从同一 Git 提交构建 Web 与 Desktop 制品,并完成以下检查:
```bash
cd frontend
npm ci
npm run version:check
npm run release:env:check
npm run runtime:check
npm run desktop:release:check
npm run ui:contract
npm run type-check
npm run test:unit
npm run build
npm run desktop:build:app
```
正式发布还必须确认:
- [ ] `frontend/package.json``package-lock.json`、Tauri 配置、Cargo manifest/lock 版本一致。
- [ ] `VITE_BUILD_CHANNEL=release``VITE_BUILD_COMMIT=<release tag commit>` 由 CI 注入,且 `npm run release:env:check` 通过。
- [ ] 在正式 release tag 和签名环境中执行 `npm run desktop:release-readiness:check`,确认 tag、构建元数据、签名/公证变量、updater 私钥和生产 artifact HTTPS 基址齐备。
- [ ] macOS app 已签名和公证。
- [ ] updater `.sig` 使用组织 CI secret 或密钥库中的私钥生成,私钥未进入仓库。
- [ ] 设置 `TAURI_SIGNING_PRIVATE_KEY``TAURI_SIGNING_PRIVATE_KEY_PASSWORD` 和 Apple 签名/公证变量后,以 `REQUIRE_DESKTOP_SIGNING=true` 再次执行 `npm run release:env:check`,随后执行 `npm run desktop:build:macos-release -- --ci`
- [ ] 正式 updater feed 先执行 `npm run desktop:update-feed:create -- --artifact <CTMS.app.tar.gz> --base-url <versioned-https-artifact-prefix> --output-dir <release-dir>` 生成 `latest.json``SHA256SUMS.txt`
- [ ] 正式 updater feed 执行 `npm run desktop:update-feed:check -- --feed <release-dir>/latest.json --artifacts-dir <release-dir>`,并确认 checksum manifest、updater artifact、`.sig``latest.json` 均通过校验。
- [ ] 不可变制品先上传,`latest.json` 最后原子替换;若 feed 校验未通过,不替换线上 `latest.json`
- [ ] Web 与 Desktop 制品记录同一产品版本、Git 标签和完整提交 SHA。
## 2. 安全边界复审
自动门禁 `npm run desktop:release:check` 覆盖以下静态约束:
- [ ] Tauri bundle 启用 `app``dmg` 和 updater artifacts。
- [ ] updater public key 已配置。
- [ ] CSP 禁止 wildcard source、`unsafe-eval`、宽泛 HTTP API 访问和 `object-src`
- [ ] Tauri capability 不包含 shell 权限、持久文件系统 scope 或宽泛目录读写。
- [ ] 文件系统与 opener scope 只允许 `$TEMP/ctms-desktop/**`
- [ ] 单实例插件先于其他桌面插件注册。
- [ ] Tauri command 白名单仅包含凭据和更新命令。
- [ ] 前端源码不通过 query string 传递 token。
- [ ] `ctms_token` 只允许由 `secureSessionStorage` 处理。
- [ ] 系统通知只能通过 `frontend/src/runtime/notifications.ts` 发送,标题和正文保持通用。
- [ ] 通知 capability 只暴露权限查询、权限请求和发送通知,不使用 `notification:default`
- [ ] opener capability 只允许打开 `$TEMP/ctms-desktop/**` 下的临时文件,不开放 URL 或 reveal 权限。
- [ ] updater capability 不直接暴露给 WebView,自动更新只走受控 Tauri command。
- [ ] 更新弹窗 release notes 过滤 URL、token 查询参数和 Authorization/Bearer 形态文本。
- [ ] CI release 候选 workflow 包含 version/runtime/desktop/ui/type/unit/build/desktop app smoke 门禁。
- [ ] signed macOS release candidate workflow 只允许从 `vX.Y.Z` tag 运行,并包含签名环境检查、Universal macOS 构建、update feed 生成、checksum 校验和 verified release directory 上传。
人工复审还必须确认:
- [ ] token 不出现在 URL、日志、系统通知正文、下载链接或持久化业务缓存中。
- [ ] 桌面端通知正文只显示通用内容,不包含项目、文件或版本详情。
- [ ] 服务端权限、审计和业务数据持久化仍由 FastAPI 后端裁决。
- [ ] Web 运行时不直接导入 Tauri API。
## 3. 端到端回归矩阵
| 场景 | Web | macOS Desktop | 预期 |
| --- | --- | --- | --- |
| 登录与项目恢复 | 必测 | 必测 | 登录成功后恢复可访问项目;401 后重新登录 |
| 30 天免登录 | 不适用 | 必测 | 关闭并重启 App 后复用系统凭据库中的后端在线会话;超过 30 天或 `/me` 校验失败后重新登录 |
| 服务器地址未配置 | 不适用 | 必测 | 自动进入服务器设置,不进入业务页 |
| 服务器地址切换 | 不适用 | 必测 | 清除当前会话和项目上下文,要求重新登录 |
| 服务端不可达 | 必测 | 必测 | 显示可恢复错误,不进入离线模式 |
| 附件上传 | 必测 | 必测 | Web 使用浏览器文件选择,Desktop 使用原生选择 |
| 附件下载/保存/打开 | 必测 | 必测 | 使用 Authorization header;无 `?token=` |
| 临时文件清理 | 不适用 | 必测 | 启动时清理 `$TEMP/ctms-desktop/**` |
| 系统通知开启 | 不适用 | 必测 | 用户主动开启后请求 OS 权限并创建订阅 |
| 系统通知拒绝 | 不适用 | 必测 | 开关回退,提示系统权限未开启 |
| 通知领取与 ack | 不适用 | 必测 | 显示成功后 ack;失败等待租约重试 |
| 单实例重复启动 | 不适用 | 必测 | 恢复、显示并聚焦主窗口 |
| 自动更新检查 | 不适用 | 必测 | release 通道按当前 CTMS origin 派生清单 |
| 更新稍后提醒 | 不适用 | 必测 | 同版本 24 小时内不重复提示 |
| 更新安装失败 | 不适用 | 必测 | 不打断业务录入,显示可排障错误 |
## 4. 桌面体验验收
- [ ] 登录页显示当前桌面服务器地址,长 URL 不撑破登录面板。
- [ ] 30 天免登录仍只保存系统凭据库会话记录,不保存密码,不把 token 写入 URL、日志、通知正文或业务缓存。
- [ ] 服务器设置页显示当前服务器、连接检查状态、HTTP 错误、超时和网络失败原因。
- [ ] 个人中心显示客户端类型、版本、平台、构建通道、提交、服务器和能力状态。
- [ ] 个人中心可复制诊断信息,内容不包含 token 或业务敏感数据。
- [ ] 通知开关显示 OS 权限状态。
- [ ] 手动检查更新能反馈“已是最新版本”、未启用更新或检查失败。
- [ ] 关键弹窗、表单、按钮在最小窗口尺寸 `1180x760` 下不重叠、不溢出。
- [ ] 更新弹窗只显示版本、发布日期和通用 release notes,不展示 token、下载链接或业务详情。
## 5. 不允许项
- [ ] 不实现离线登录、离线浏览、离线队列或离线同步。
- [ ] 不在桌面端保存 CTMS 业务数据副本。
- [ ] 不内嵌 FastAPI、PostgreSQL、SQLite 或本地业务 API 镜像。
- [ ] 不绕过后端做本地权限裁决或本地审计回放。
## 6. 2026-07-01 收尾验证记录
本轮收尾验证在 `/Users/zcc/MyCTMS/ctms-dev/worktrees/ctms-desktop` 的 detached HEAD `c923f887` 上执行,包含当前工作区文档与 CI 门禁调整。
已通过的自动门禁:
- `cd frontend && npm run version:check`
- `cd frontend && npm run release:env:check`
- `cd frontend && npm run runtime:check`
- `cd frontend && npm run desktop:release:check`
- `cd frontend && npm run ui:contract`
- `cd frontend && npm run type-check`
- `cd frontend && npm run test:unit`
- `cd frontend && npm run build`
- `cd frontend && npm run desktop:build:app`
- `cd frontend && node --check scripts/verify-desktop-update-feed.mjs`
验证结论:
- Tauri 运行时边界、release 静态安全门禁、构建元数据预检、版本一致性和 UI 合约均通过。
- Web 生产构建和未签名 macOS `.app` smoke 构建均可重复执行。
- 当前 CI 已补齐 `npm run release:env:check``npm run ui:contract`tag 构建会将 `VITE_BUILD_CHANNEL` 规范为 `release` 并校验 tag 与版本号一致。
- updater feed 校验脚本已完成语法检查;正式 `latest.json` 需要在签名 updater artifacts 生成后执行实物校验。
仍需正式发布前人工确认:
- macOS 签名、公证、Apple Developer 凭据和组织 updater 私钥。
- 签名后的 updater artifacts、`.sig`、checksum manifest 和 `latest.json` 在真实发布目录内通过 `npm run desktop:update-feed:check`
- 不可变制品上传完成后,再原子替换线上 `latest.json`
- Desktop 端到端人工回归矩阵、最小窗口体验验收和系统通知/自动更新真实环境验证。
## 7. 2026-07-02 发布稳定化推进记录
本次推进补齐了发布链路自动化,不改变桌面端产品边界:
- 新增 `npm run desktop:build:macos-release`,封装 Universal macOS `app`/`dmg` release candidate 构建命令。
- 新增 `npm run desktop:update-feed:create`,从签名 updater artifact 和 `.sig` 生成 `latest.json`、复制发布目录文件并生成 `SHA256SUMS.txt`
- `npm run desktop:update-feed:check` 在传入 `--artifacts-dir` 时要求并校验 `SHA256SUMS.txt`
- 新增 `npm run desktop:release-readiness:check`,在正式签名候选构建前检查 release tag、构建元数据、签名/公证变量、updater 私钥和生产 artifact HTTPS 基址。
- 新增 `.github/workflows/desktop-release-candidate.yml`,在 release tag 上执行签名候选构建、feed 生成、feed 校验并上传 verified release directory。
- `npm run desktop:release:check` 已检查上述脚本和 workflow,避免发布链路回退。
仍未自动完成、正式发布前必须人工确认:
- Apple Developer 凭据、证书、签名身份、公证结果和组织 updater 私钥。
- 生产下载源的不可变制品上传和线上 `latest.json` 原子替换。
- 真实环境下的自动更新安装、系统通知、单实例和完整人工回归。
## 8. 2026-07-02 端到端回归优化记录
本轮端到端优化仍保持在线桌面客户端边界,不引入离线、本地业务存储或本地权限裁决。
已完成的自动化收口:
- 服务器地址切换时,桌面设置页调用 `auth.logout({ rememberCurrentStudy: false })`,避免退出时把旧服务器项目记入当前用户的最近项目;随后继续清除当前项目上下文。
- 系统偏好连接页与独立服务器设置页保持同一切换服务器语义,切换时不记忆旧服务器项目并清除当前项目上下文。
- 系统通知轮询在部分通知显示失败或系统通知未实际发起时,先 ack 已成功发起系统通知的通知,再让失败项通过租约重试,贴合“显示成功后 ack;失败等待重试”的回归预期。
- 自动更新管理器新增稍后提醒 24 小时抑制、安装失败可重试、检查失败不打断业务和未启用更新状态的单元覆盖。
- 附件 API 新增 blob 下载、multipart 上传和删除端点单元覆盖,确保下载凭据继续由 axios Authorization header 承载而不是进入 URL。
- 文件任务反馈 helper 新增选择、保存、取消保存和打开的单元覆盖,约束桌面保存/打开继续走 `frontend/src/runtime/` 适配层。
- Keychain/凭据库会话新增旧浏览器 token 迁移、未配置服务器不读取凭据、本地 30 天上限、服务器切换删除旧服务器凭据,以及恢复 token 必须先经 `/me` 校验的单元覆盖。
- 桌面发布门禁新增单实例重复启动处理校验,要求重复启动时恢复、显示并聚焦 `main` 窗口。
- 会话刷新后的跨窗口 token 更新不再写入 `localStorage` fallback,只通过内存态 BroadcastChannel 通知,避免 token 进入明文广播缓存。
- `desktop:release:check` 新增 session broadcast 静态门禁,防止 `TOKEN_UPDATED` payload 回退写入 `localStorage`
- 新增相关单元测试覆盖服务器切换不记忆旧项目、通知权限未授权不领取、部分通知失败时只 ack 成功项、系统通知未发起时不 ack、自动更新失败恢复路径、附件文件流契约、30 天在线会话恢复边界、单实例恢复行为和 token 广播存储边界。
仍需人工或真实环境验证:
- Keychain/凭据库 30 天在线会话恢复。
- 原生附件上传、下载、保存和打开。
- 系统通知拒绝路径的 OS 级交互。
- 单实例重复启动聚焦主窗口。
- 签名 release 构建下的自动更新 feed、验签、安装和重启实物流。
## 9. 2026-07-02 安全边界复审记录
本轮安全复审在端到端自动化收口之后推进,不改变桌面端在线客户端边界。
已完成的安全边界收口:
- Tauri notification capability 从 `notification:default` 收敛为 `notification:allow-is-permission-granted``notification:allow-request-permission``notification:allow-notify`
- `desktop:release:check` 新增 capability 最小化约束,拒绝 `notification:default`、opener URL/reveal 权限和 WebView 直连 updater 权限。
- `desktop:release:check` 将 token URL 检查扩展到 `token``access_token` 查询参数,并扩大日志敏感词检查到 `token``access_token``authorization``bearer`
- 更新弹窗 release notes 增加清理逻辑,过滤 URL、token 查询参数、`access_token``Authorization``Bearer` 形态文本,避免 feed 内容把下载链接或凭据样式文本带入 UI。
- 凭据库 Rust 单测新增带凭据 server origin 拒绝,以及 Keychain/Credential Manager account 不暴露原始服务器 origin 的覆盖。
仍需人工复审确认:
- 真实生产 release notes 内容保持通用,不写入项目、文件、下载链接或敏感业务详情。
- 正式签名、公证和 updater feed 环境继续使用组织 secret,不在日志、artifact 或配置中泄露私钥材料。
## 10. 2026-07-02 桌面体验收口记录
本轮体验收口聚焦登录、服务器设置、个人中心诊断、系统偏好和最小窗口布局稳定性,不改变业务能力边界。
已完成的体验收口:
- 个人中心新增只读客户端诊断信息,展示客户端类型、版本、平台、构建通道、提交、服务器和能力状态,并支持复制诊断信息。
- 登录页在桌面最小窗口附近收紧左右分栏 padding 和卡片宽度,长服务器地址继续在登录面板内换行,不撑破布局。
- 服务器设置页增加面板内滚动和健康检查 URL 换行约束,避免 `1180x760` 下长 URL 或错误信息溢出。
- 系统偏好通知/更新控制区允许换行,长状态说明使用 `overflow-wrap`,避免按钮和权限状态标签在窄视口重叠。
- 个人中心对话框内容区改为内部滚动,诊断值使用强制换行,避免新增诊断信息后超过最小窗口高度。
- 通知权限运行时改为优先读取 WebView `Notification.permission``granted`/`denied` 状态;请求权限返回 `default` 时保持“待授权”,不再误标为“已拒绝”。
- 系统偏好通知页已移除授权成功态的固定标签文案,开启状态只由开关表达;待授权、已拒绝和不可用状态继续显示提示标签。
- 系统偏好通知页新增“测试通知”命令,完整走 `frontend/src/runtime/notifications.ts` -> `@tauri-apps/plugin-notification` 的系统通知发送链路;测试通知文案为固定通用内容,不包含项目、文件、token 或其他敏感业务信息。
- `showSystemNotification` 和测试通知调用会在发送前确认系统通知权限,只有真正发起系统通知时才返回成功;桌面通知轮询据此只 ack 已实际发起系统通知的后端通知。
- 真实 macOS `.app` 已验证系统偏好通知开关可调用系统通知权限并生效。验证截图见 `/Users/zcc/Library/Application Support/CleanShot/media/media_boKznpjOwt/CleanShot 2026-07-02 at 11.07.52@2x.png`
- `Layout.desktop.test.ts` 新增静态契约覆盖个人中心诊断、登录页最小窗口断点、服务器设置滚动和偏好页控制区换行约束。
- `notifications.test.ts` 新增授权、拒绝、待授权、取消授权请求和测试通知发送链路的单元覆盖。
仍需人工体验验收:
- 在真实 macOS `.app` 中以 `1180x760` 检查登录页、服务器设置、个人中心、系统偏好和更新弹窗无重叠、无横向溢出。
- 在真实系统通知权限拒绝流程中确认权限状态提示、开关回退和错误提示与 OS 状态一致。