验证(08-10-pwa-cross-version-cache-upgrade-validation): 固化PWA跨版本升级回归
这个提交包含在:
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,85 @@
|
||||
# PWA 跨版本缓存升级验证:技术设计
|
||||
|
||||
## 1. 判定对象
|
||||
|
||||
本任务验证的是一个状态迁移,不是两个独立页面截图:
|
||||
|
||||
```text
|
||||
same loopback origin + same BrowserContext
|
||||
-> serve build A
|
||||
-> A page loads
|
||||
-> A SW ready + controller + precache proven
|
||||
-> close A page, retain context/profile
|
||||
-> atomically switch origin to build B
|
||||
-> revisit same protected route
|
||||
-> real SW update code runs
|
||||
-> document/controller/cache/session converge to B
|
||||
```
|
||||
|
||||
任何直接从空 context 打开 B 的路径都只能是 harness sanity,不参与最终判定。
|
||||
|
||||
## 2. 产物生成
|
||||
|
||||
- runner 在 OS 临时目录创建 `build-a/`、`build-b/`,不覆盖 `client/dist`。
|
||||
- 两次均从当前 checkout 构建,只注入不同的 synthetic 12 位 SHA。构建后读取 `index.html` 与生成资产,断言:
|
||||
- document/runtime 可观察到各自 SHA;
|
||||
- `sw.js` 存在;
|
||||
- A/B hashed JS/CSS 至少有一个差集。
|
||||
- runner 使用 `trap`/finally 精确清理本轮临时目录;失败日志只输出相对路径和计数。
|
||||
|
||||
选择同源码不同 SHA,而不是 checkout 历史 commit,原因是本轮要隔离“SW 缓存版本切换”这个变量。build SHA 会进入 core bundle 并改变 Workbox revision;历史代码差异会同时引入 API、路由、fixture 与依赖变量。
|
||||
|
||||
## 3. Loopback HTTP harness
|
||||
|
||||
Playwright spec 进程内启动只监听 `127.0.0.1` 的 ephemeral HTTP server,持有一个 `activeBuild=A|B` 状态:
|
||||
|
||||
- `/sw.js`、`/index.html` 与 SPA navigation:从 active dist 读取;HTML/SW 使用 `Cache-Control: no-cache`。
|
||||
- hashed JS/CSS/image:从 active dist 读取并使用 immutable cache header。
|
||||
- `/ready`:返回 active synthetic build SHA,`Cache-Control: no-store`。
|
||||
- `/api/auth/refresh`、`/api/me`、navigation、notifications 与 portal 最小接口:返回合成 envelope,不触碰数据库。
|
||||
- 路径解析必须防止越出 active dist;未知静态文件 404,未知 API 返回受控合成结果。
|
||||
- `switchToB()` 为测试进程内同步状态切换,不暴露公网管理端点;同一 server/socket/origin 始终不变。
|
||||
|
||||
请求 ledger 只保留 `method/path/status/build`,并单独统计 JS/CSS 404、refresh/me 与 main-frame navigation。
|
||||
|
||||
## 4. 浏览器 witness
|
||||
|
||||
### Phase A:建立旧 profile
|
||||
|
||||
1. 创建唯一 BrowserContext,显式 `serviceWorkers: "allow"`。
|
||||
2. 安装 synthetic `has_session` cookie;API refresh 返回合成 token,`/api/me` 返回合成员工。
|
||||
3. 访问 `/portal`,确认 document SHA=A、route 保持 `/portal`。
|
||||
4. 等待 `navigator.serviceWorker.ready`;A 阶段允许且仅允许一次显式 `page.reload()`,使首次安装 worker 成为 controller。
|
||||
5. 断言 controller 存在、A precache/cache entry 存在;保存 A-only hashed asset 相对路径集合。
|
||||
6. 关闭 A page,但不关闭 context。
|
||||
|
||||
### Phase B:部署后重访
|
||||
|
||||
1. server 原位 `switchToB()`,同时切换 `/ready` 和所有静态文件。
|
||||
2. 在同一 context 创建新 page,重新访问 `/portal`。
|
||||
3. 从这一刻起禁止测试代码调用 reload/clear/unregister;只等待应用真实 autoUpdate/clientsClaim/更新回调。
|
||||
4. 记录 `controllerchange`,必要时通过 Chromium CDP ServiceWorker domain 对比 A/B active worker versionId;`scriptURL` 预期始终为同 origin `/sw.js`,不得把恒定 URL 误写成版本证明。
|
||||
5. 将 B 阶段 no-cache 获取到的 `/sw.js` 内容 SHA-256 与 build B 文件指纹对账,并联合断言 document SHA=B、B-only chunk 已成功加载、route 仍为 `/portal`、refresh/me 成功、controller 存在。
|
||||
6. 安全记录 `hyb-oa:pwa-update-ready` 触发次数;若 autoUpdate 流程实际进入 `onNeedRefresh`,保留阶段与后续行为证据。
|
||||
7. 读取 Cache Storage 的 URL pathname 白名单,断言 A-only JS/CSS 消失,B asset 已进入或已成功请求。
|
||||
8. 断言无 `/login`、无 JS/CSS 404、无 chunk/preload/page error、导航次数有上限。
|
||||
|
||||
## 5. 不使用 Playwright route mocking
|
||||
|
||||
本 witness 的核心就是观察 Service Worker 拥有网络请求时的真实行为。若用 `page.route` mock API,会重新引入上一任务已证明的 fixture 冲突。因此所有 synthetic API 都由 loopback HTTP harness 提供,浏览器和 worker看到的是同一真实 origin 响应。
|
||||
|
||||
## 6. 结果状态
|
||||
|
||||
| 状态 | 判据 | 后续 |
|
||||
| --- | --- | --- |
|
||||
| `PASS` | A 前置成立,B 在禁止强刷条件下完整收敛,所有负向断言通过 | 固化 test-only harness |
|
||||
| `PRODUCT_FINDING` | A 前置成立,但 B 长期旧 shell、回登录、旧 chunk 404/残留或 reload loop | 停止,另建 PWA 产品修复 task |
|
||||
| `PAUSED` | A controller/precache 无法稳定建立、构建产物不可区分或环境不支持 SW | 记录恢复条件,不猜测产品结论 |
|
||||
|
||||
## 7. 风险与回滚
|
||||
|
||||
- 只新增/修改 QA runner、E2E spec 与当前 task;`client/src/pwa.ts`、`vite.config.ts` 在验证阶段只读。
|
||||
- 所有 server、BrowserContext 和临时目录用 finally/trap 清理;失败后不得留下监听端口或 build 目录。
|
||||
- harness 若证明不了旧 profile 前置,删除/回滚本轮 test-side probe,但保留 PAUSED 证据。
|
||||
- 第一次 B witness 失败即为冻结点;不得通过手工或自动追加 reload/重访改变失败性质。只有先保存安全 ledger、回到规划并获批后,才允许开展新的诊断实验。
|
||||
- 不接触生产、数据库、部署脚本或真实浏览器 profile。
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,47 @@
|
||||
# PWA 跨版本缓存升级验证:执行计划
|
||||
|
||||
## Phase A:Harness 与旧版本前置
|
||||
|
||||
- [x] 在 `scripts/qa/` 增加 loopback-only wrapper:生成两个临时 production outDir,注入不同 synthetic SHA,失败/退出时清理。
|
||||
- [x] 在专用 Playwright spec 中实现进程内静态/API server;同一 origin 支持 A→B 原子切换,禁止 path traversal,缓存头贴近生产。
|
||||
- [x] 显式使用 `serviceWorkers: "allow"`,同一 BrowserContext 建立 synthetic session。
|
||||
- [x] A 阶段断言 SHA、SW ready、controller、precache 与 A-only asset;未满足即 PAUSED,不进入 B。
|
||||
|
||||
## Phase B:同 profile 跨版本重访
|
||||
|
||||
- [x] 关闭 A page但保留 context,原位切 B,再创建 page 重访同一路由。
|
||||
- [x] B 阶段不调用测试侧 reload、clear cookie/cache、unregister 或新 context。
|
||||
- [x] 等待应用自身 SW 更新,断言最终 SHA=B、route/session 保持、refresh/me 成功。
|
||||
- [x] 以 controllerchange/CDP worker version、B `sw.js` 指纹、B document SHA 与 B-only chunk 联合证明接管;恒定 `/sw.js` URL 只作 scope 证明。
|
||||
- [x] 记录 `hyb-oa:pwa-update-ready` 触发次数与阶段;检查最终 cache、asset 请求、404、console/page error 与导航次数,旧-only chunk 不得残留。
|
||||
- [ ] 加一个明确标注的 fresh-B sanity(如确有诊断价值),但不得把它计入 A→B AC。
|
||||
|
||||
## Phase C:结果分叉
|
||||
|
||||
- [x] PASS:固化最小 test-only runner/spec,写入脱敏 ledger,并复跑至少两次确认不是 worker 时序偶发。
|
||||
- [ ] PRODUCT_FINDING:停止,不修改 `pwa.ts`/`vite.config.ts`;记录最短失败阶段和独立修复任务建议。
|
||||
- [ ] 第一次 B 失败:立即冻结现场并停止,不追加 reload、重访或第二次升级尝试。
|
||||
- [ ] PAUSED:记录环境/前置缺口和恢复条件,不声称真实用户升级正常或异常。
|
||||
|
||||
## Phase D:验证与收口
|
||||
|
||||
- [x] focused runner 单 worker、zero retry PASS;重复执行保持同结论。
|
||||
- [x] 若新增 TS test:`cd client && npm run typecheck`。
|
||||
- [x] task/process/governance/sensitive/`git diff --check`。
|
||||
- [x] 评估是否需要扩展 frontend PWA spec;仅在真实 witness 得到稳定结论后沉淀。
|
||||
- [ ] 中文 commit 引用 `08-10-pwa-cross-version-cache-upgrade-validation`;不推送、不部署。
|
||||
|
||||
## Risky Files / Stop Points
|
||||
|
||||
- `client/src/pwa.ts`、`client/vite.config.ts`:本任务验证阶段只读;如需改动,按 PRODUCT_FINDING 停止并回来批准。
|
||||
- 新 QA wrapper:不得覆盖 `client/dist`,不得接受非 loopback origin,不得泄露完整临时路径。
|
||||
- 新 Playwright spec:不得使用 `page.route`/`context.route`,不得 block Service Worker,不得在 B 阶段强制刷新或清缓存。
|
||||
- build A 前置不成立、A/B asset 无差集、server origin 变化或 cleanup 失败:立即 PAUSED。
|
||||
|
||||
## Expected Evidence
|
||||
|
||||
- A/B build SHA、asset 差集与 SW 存在性(仅 synthetic 标签/相对路径)。
|
||||
- A 阶段 controller + precache 证明。
|
||||
- B 阶段同 context 重访、应用驱动更新、最终 document/controller/cache/session 证明。
|
||||
- `/login` 次数、refresh/me status、静态 404、page/chunk error、导航次数的安全 ledger。
|
||||
- 单一最终状态与失败分叉,不用 fresh install 替代 upgrade。
|
||||
@@ -0,0 +1,91 @@
|
||||
# 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 再访问”;失败后的产品修改另行批准。
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# 当前证据:PWA 跨版本升级缺口
|
||||
|
||||
## 已有保护
|
||||
|
||||
- VitePWA:`autoUpdate`、`skipWaiting`、`clientsClaim`、`cleanupOutdatedCaches`。
|
||||
- 应用更新:新 worker installed 时清理 known runtime/precache;非 auth 页面自动 apply,auth 页面提示用户确认。
|
||||
- 旧包检测:页面回前台时用 `cache: no-store` 请求 `/ready`,build SHA 不一致则提示刷新。
|
||||
- chunk 恢复:`vite:preloadError`、window error 和 unhandled rejection 命中 chunk load 模式时,清 cache 后最多 reload 一次。
|
||||
|
||||
## 现有测试证明范围
|
||||
|
||||
- `login-portal-ux.spec.ts` 证明 synthetic update event 和 `/ready` mismatch 时 UI/刷新回调正确。
|
||||
- production build 会生成真实 `sw.js` 与 Workbox precache。
|
||||
- 08-10 会话 fixture 任务证明 Service Worker 在 production runner 中确实取得网络所有权。
|
||||
|
||||
## 未证明范围
|
||||
|
||||
- 没有测试先让 build A 的真实 worker 控制页面并建立 cache,再在同一 origin/profile 切换 build B。
|
||||
- 没有证明重访后 document bundle、controller、cache 与 session 同时收敛到 B。
|
||||
- 没有证明旧-only hashed chunk 被清理、不会产生 chunk 404、reload loop 或 `/login` 回落。
|
||||
- fresh profile 直接打开 B 与上述问题无关,不能填补证据缺口。
|
||||
|
||||
## 规划结论
|
||||
|
||||
需要一个不使用 Playwright route mock 的 loopback production-build harness。验证先于修复;失败时应输出产品 finding,而不是在同一任务中边改 PWA 策略边把测试修绿。
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# 跨版本升级验证账本
|
||||
|
||||
## 最终结论
|
||||
|
||||
`PASS`。本地 Chromium 在同一 loopback origin、同一 BrowserContext 中完成了真实的 build A → build B 升级;没有用 fresh context、新装路径或测试侧强刷替代升级证据。
|
||||
|
||||
## 执行边界
|
||||
|
||||
- 三次执行均由 `scripts/qa/run-pwa-cross-version-upgrade.sh` 生成临时 production build;仓库 `client/dist` 未被覆盖。
|
||||
- synthetic build A/B 分别使用 `111111111111` 与 `222222222222`;两份 `sw.js` 指纹不同,hashed JS/CSS 集合存在双向差集。
|
||||
- HTTP harness 只监听 loopback,同一个 socket/origin 原位切换 active dist;API 响应全部为合成数据,没有连接生产、数据库或真实账号。
|
||||
- Playwright 使用 `serviceWorkers: "allow"`、single worker、zero retry。B 切换后源码没有 `page.reload()`、清 cookie/cache、注销 worker、hard reload 或新建 BrowserContext;唯一一次显式 reload 位于 A 阶段,用于首次安装后取得 controller。
|
||||
- B 首次 witness 直接 PASS,因此未进入失败后重访或“多刷一次”路径。后续执行只在首次 PASS 后用于增强证据与最终质量确认。
|
||||
|
||||
## A 阶段证据
|
||||
|
||||
- document build SHA:A。
|
||||
- controller scope:`/sw.js`;controller 等于 registration active worker,状态为 `activated`。
|
||||
- 浏览器 `no-store` 获取的 `sw.js` SHA-256 与 build A 文件逐字节对账通过。
|
||||
- Workbox precache 存在;A-only hashed JS/CSS 缓存数为 64,B-only 为 0。
|
||||
- `/login` 回落 0,JS/CSS 404 为 0。
|
||||
|
||||
## B 阶段收敛证据
|
||||
|
||||
- 关闭 A page 后保留同一 BrowserContext,再由同一 origin 重访 `/portal`。
|
||||
- document build SHA:B;业务 route 保持 `/portal`。
|
||||
- `controllerchange=1`;最终 controller 等于 registration active worker,状态为 `activated`。
|
||||
- 浏览器 `no-store` 获取的 `sw.js` SHA-256 与 build B 文件逐字节对账通过;A/B 指纹前缀不同。
|
||||
- 加载到 2 个 B-only hashed asset;最终 A-only cache 数为 0。
|
||||
- refresh 200 次数 2,`/api/me` 200 次数 2;`/login` 回落 0。
|
||||
- JS/CSS 404 为 0,page error 为 0,chunk/preload error 为 0;主 frame navigation 次数 3,未形成 reload loop。
|
||||
- `hyb-oa:pwa-update-ready` 触发次数为 0,符合非认证页 auto-update 直接应用新 worker 的预期。
|
||||
|
||||
## 稳定性
|
||||
|
||||
- 首次原始 witness:PASS。
|
||||
- 加强实际 SW 内容指纹、active-controller 与 B-only loaded ledger 后,第二次 witness:PASS。
|
||||
- 将失败冻结路径改为只读既有快照、不在 catch 中追加 SW fetch 后,最终质量执行:PASS。
|
||||
- 三次均在同一测试合同下使用 single worker、zero retry;没有失败后追加刷新。
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"id": "pwa-cross-version-cache-upgrade-validation",
|
||||
"name": "pwa-cross-version-cache-upgrade-validation",
|
||||
"title": "PWA 跨版本缓存升级验证",
|
||||
"description": "在同一 loopback origin 与同一浏览器上下文中,让旧 production build 的真实 Service Worker 先取得控制权并建立缓存,再原位切换新 build,验证重访后无需强刷即可收敛到新 shell、保持会话且不残留旧 chunk。",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": "client PWA production-build cross-version upgrade test harness and local QA runner; product PWA runtime remains read-only unless a separate finding task is approved",
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-08-10",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [
|
||||
"client/vite.config.ts",
|
||||
"client/src/pwa.ts",
|
||||
"client/tests/e2e/login-portal-ux.spec.ts",
|
||||
"client/tests/e2e/pwa-cross-version-upgrade.spec.ts",
|
||||
"scripts/qa/run-pwa-cross-version-upgrade.sh",
|
||||
".trellis/spec/hyb-oa/frontend-react-vite-design.md"
|
||||
],
|
||||
"notes": "本地同 origin、同 BrowserContext 的 production PWA A→B 跨版本 witness 三次 PASS;A controller/precache、B active worker 指纹/document/chunk、会话保持与旧缓存清理均有联合证据,未连接生产或修改产品 PWA runtime。",
|
||||
"meta": {}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户