92 行
8.5 KiB
Markdown
92 行
8.5 KiB
Markdown
# PWA 跨版本缓存升级验证
|
||
|
||
## Goal
|
||
|
||
在纯本地、同一 origin、同一浏览器 profile 的真实 production PWA 路径中验证跨版本升级:旧 build A 必须先完成 Service Worker 安装、控制与预缓存,随后服务端原位切换为 build B,浏览器再次访问时应通过应用自身更新机制收敛到 B,而不是依赖测试强刷、清缓存或新建浏览器上下文。目标是回答“老用户跨部署是否会长期停留旧 shell、出现旧 chunk/新服务混用或意外回到登录页”,不是证明新装用户能打开最新版本。
|
||
|
||
## Confirmed Facts
|
||
|
||
- `client/vite.config.ts` 当前使用 `VitePWA`,配置为 `registerType: "autoUpdate"`、`skipWaiting: true`、`clientsClaim: true`、`cleanupOutdatedCaches: true`;navigation fallback 排除 `/api/`,静态 runtime cache 使用 `StaleWhileRevalidate`。
|
||
- `client/src/pwa.ts` 在新 worker installed 且已有 controller 时清理 runtime/precache cache;非认证页 `onNeedRefresh` 自动应用更新,认证输入页改为用户确认刷新;build SHA 不一致时还有 `/ready` freshness prompt。
|
||
- 现有 `login-portal-ux.spec.ts` 只通过 synthetic event 和 mocked `/ready` 验证提示、输入保持与刷新回调,没有让真实旧 worker 控制页面后切换新产物。
|
||
- 08-10 会话回落任务证明 production build 的 Service Worker 会改变 `page.route` mock 的网络所有权;那项 `FIXTURE` 结论不等于真实用户跨版本更新已验证。
|
||
- Vite dev 不生成等价 PWA worker;直接启动一个新 preview、新 context 访问 B 只能证明 fresh install,不能作为本任务证据。
|
||
|
||
## Scope
|
||
|
||
- 新增一个 test-only 本地跨版本 runner/harness,生成 A/B 两份 production build 并在同一 loopback origin 原位切换。
|
||
- 使用真实 Service Worker、Cache Storage、页面重访和应用更新代码;API 由 loopback HTTP harness 返回合成响应,不使用 Playwright route mocking。
|
||
- 记录 build、controller、cache、导航、refresh/me、静态资源状态的脱敏证据。
|
||
- 对当前行为给出 `PASS`、`PRODUCT_FINDING` 或 `PAUSED`;仅在 PASS 时把 harness 固化为回归资产。
|
||
|
||
## Requirements
|
||
|
||
### R1 旧版本前置必须真实成立
|
||
|
||
- build A 与 build B 必须由同一候选源码、两个不同且合法的 12 位 synthetic `VITE_BUILD_SHA` 生成;两份 `index.html`、`sw.js` 与至少一个 hashed JS/CSS 资产集合必须可区分。
|
||
- A 阶段必须在 loopback origin 上访问 production build,等待 `navigator.serviceWorker.ready`,并在一次 A 阶段 reload 后断言 `navigator.serviceWorker.controller` 存在、document build SHA 为 A、Workbox precache 含 A 资产。
|
||
- 未满足 controller + A precache 时立即 `PAUSED`;不得继续切 B 后把 fresh install 当升级证据。
|
||
|
||
### R2 原位部署切换与同 profile 重访
|
||
|
||
- harness 只能在同一 HTTP origin 内原子切换 active dist 与 `/ready` build SHA;不得换端口、换 hostname 或启动第二个 origin。
|
||
- A 已受控后关闭旧页面但保留同一 BrowserContext,再切换 B 并新开页面访问同一路由,模拟老用户重访。
|
||
- B 阶段测试代码禁止调用 `page.reload()`、`context.clearCookies()`、`caches.delete()`、`registration.unregister()`、新建 BrowserContext 或 DevTools hard reload;更新/刷新只能由应用的真实 SW 回调触发。
|
||
- BrowserContext 必须显式 `serviceWorkers: "allow"`;禁止用上一任务的 `serviceWorkers: "block"`。
|
||
|
||
### R3 会话、shell 与缓存收敛
|
||
|
||
- 使用 synthetic `has_session` 与 loopback HTTP API 合同维持一个合成已登录 portal;不使用真实账号、真实密码、生产 cookie 或生产 API。
|
||
- B 重访必须在有限时间内达到:document build SHA 为 B、仍位于原业务路由、refresh 与 `/api/me` 成功、Service Worker controller 存在。
|
||
- `/sw.js` URL 在 A/B 间恒定,不能单独证明 B 已接管;必须联合证明 active worker version/controllerchange 已变化、B 阶段获取的 SW 内容指纹等于 build B 产物、document 使用 B SHA 且加载 B-only chunk。
|
||
- 不得出现 `/login` 回落、无限 reload、JS/CSS 404、`ChunkLoadError`、`vite:preloadError` 或未处理 page error。
|
||
- 最终 Workbox precache/runtime cache 不得保留 A-only hashed JS/CSS;必须包含 B 资产或由 B shell 正常请求证明新产物已接管。
|
||
|
||
### R4 证据真实性与失败边界
|
||
|
||
- 安全 ledger 只记录 synthetic build 标签、pathname、请求类型/status、controller/cache 布尔状态、相对 hashed asset path 与导航次数;不记录 cookie 值、token、完整账号、原始 payload 或完整本地路径。
|
||
- 必须明确区分“fresh B context sanity”与“same-profile A→B witness”;前者即使通过也不能满足 AC。
|
||
- 若当前产品路径失败,先输出 `PRODUCT_FINDING` 与最短失败阶段,停止在独立 PWA 修复任务批准前;本任务不得顺手修改 `client/src/pwa.ts` 或 `client/vite.config.ts` 来让 witness 通过。
|
||
- 第一次 B witness 失败后立即冻结 ledger、controller/cache/request 现场并停止;禁止追加一次 reload、再次访问或第二次升级尝试来“看看会不会恢复”。
|
||
- 安全 ledger 额外记录 `hyb-oa:pwa-update-ready` 是否触发及次数;在 `autoUpdate` 路径中若出现该回调,必须解释触发阶段,不能静默忽略或直接判缺陷。
|
||
- 若构建、浏览器或 worker 生命周期不稳定而无法定性,输出 `PAUSED`,不得把未复现写成 PASS。
|
||
|
||
## Acceptance Criteria
|
||
|
||
- [x] AC1:A/B 两份 production build 的 synthetic SHA、SW 与 hashed asset 集合经 runner 独立核验,且不改写仓库 `client/dist`。
|
||
- [x] AC2:A 阶段现场证据同时证明 build A、active controller 与 A precache;缺任何一项均不进入 B。
|
||
- [x] AC3:同 origin、同 BrowserContext、关闭旧页后重访 B;B 阶段没有测试侧 reload、清 cookie/cache、注销 SW 或新 context。
|
||
- [x] AC4:B 重访自动收敛到 build B;新 active worker version/controllerchange、B SW 指纹、B document SHA 与 B-only chunk 形成联合证据,同时保持原业务路由和 synthetic 登录态;refresh/me 成功,零 `/login` 回落、零 chunk/static 404、零 page error、零 reload loop。
|
||
- [x] AC5:最终 cache ledger 无 A-only hashed JS/CSS,并有 B shell/asset 接管证据。
|
||
- [x] AC6:仅输出一个最终状态 `PASS`、`PRODUCT_FINDING` 或 `PAUSED`;若非 PASS,不修改 PWA 产品代码并明确恢复条件或独立修复任务边界。
|
||
- [x] AC7:focused runner、typecheck(若 TS test 有改动)、task/process/governance/sensitive 与 `git diff --check` 全部通过;未连接生产、未部署、未使用真实凭据。
|
||
|
||
## Out of Scope
|
||
|
||
- 公网、生产部署、真实设备/企微 WebView、真实用户账号与生产 Service Worker 清理。
|
||
- 网络中断、弱网、CDN 多节点不一致、非原子文件同步或发布中间态;本轮只验证已完成部署后的缓存升级。
|
||
- iOS Safari/企业微信内核专项兼容;本地 Chromium PASS 不替代真机验收。
|
||
- 离线业务队列、IndexedDB 数据迁移、账号切换数据生命周期和一般 PWA 性能优化。
|
||
- 验证失败时直接修复更新策略、改 cache handler 或放宽断言。
|
||
|
||
## Risks and Deferred Items
|
||
|
||
- 同源码只改变 build SHA 仍会生成不同的核心 chunk/revision,足以验证 SW 版本接管;它不覆盖真实业务代码大改造成的所有 chunk graph 变化,后续可增加历史 commit 对照,但不作为本轮必要复杂度。
|
||
- `skipWaiting + clientsClaim` 可能让 controller 先更新而旧 document 仍执行 A bundle,因此最终必须同时看 document SHA、route、asset/cache,不以 controller 单点判 PASS。
|
||
- custom loopback HTTP harness 需模拟生产缓存头:`sw.js`/`index.html` no-cache,hashed asset immutable;若偏离会使结论失真。
|
||
|
||
## Test Plan
|
||
|
||
- build/runner:任务新增的跨版本 QA runner(两个临时 outDir,trap 清理,loopback-only)。
|
||
- focused Playwright:真实 SW allow 的 A→B single-worker、zero-retry witness。
|
||
- `cd client && npm run typecheck`(若新增 TS spec)。
|
||
- `python3 ./.trellis/scripts/task.py validate 08-10-pwa-cross-version-cache-upgrade-validation`
|
||
- `node scripts/qa/check-process-contract.mjs --baseline --json`
|
||
- `node scripts/qa/check-trellis-governance.mjs --json`
|
||
- `node scripts/qa/check-sensitive-content.mjs`
|
||
- `git diff --check`
|
||
|
||
## Open Questions
|
||
|
||
无。用户已明确升级路径必须是“旧 build 建立缓存 → 部署新 build → 同 profile 再访问”;失败后的产品修改另行批准。
|