ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes "A1","R65 执行合同建档与范围冻结","创建并校验 R65 Session 稳定性与 409 资源错误修复执行合同 CSV,冻结范围:1) 诊断并修复静态资源 409/缓存策略问题;2) 增强 auth bootstrap refresh 容错;3) 增强 PWA/chunk 更新恢复;4) 不新增 Prisma 迁移,不新增 npm 依赖,不改登录/refresh API 协议。","issues/2026-04-16_r65-session-stability.csv 列完整、Dependencies 为 JSON 数组字符串、A2-A6 范围与当前方案一致,且 Notes 明确无迁移、无新依赖、不改 auth API 协议与回滚方式。","命令:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-04-16_r65-session-stability.csv。业务:检查 1) 12 列完整;2) A2-A6 依赖链合理;3) Notes 明确无 Prisma 迁移、无新依赖、不改 auth API 协议与回滚路径。","manual","DONE","DONE","DONE","issues/2026-04-16_r65-session-stability.csv","[]","回滚路径:删除本次 CSV 不影响业务。" "A2","PWA/chunk 更新恢复与 409 诊断修复","在 client/src/pwa.ts 与 client/src/main.tsx 中补充 Service Worker 更新接管和 chunk 加载失败的一次性自恢复逻辑,以降低旧 HTML/SW 与新 chunk hash 竞态导致的 409/加载失败。","1) 保留现有 Workbox 基础配置;2) 检测到 SW 更新时会清理运行时 cache 并立即接管;3) 对 ChunkLoadError、Loading chunk、Failed to fetch dynamically imported module 触发一次性清缓存 + reload;4) sessionStorage 护栏避免无限刷新;5) 不修改 deploy/trialrun/nginx-hyb-oa.conf。","命令:export PATH=/home/shunli/.nvm/versions/node/v22.20.0/bin:$PATH && cd client && npm run typecheck && npm run build。业务:1) /portal 或其他懒加载页面遇到旧 chunk 失配时仅自动刷新一次;2) 新 SW 接管后旧 precache 不继续引用旧 hash 资源;3) 不引入新依赖。","manual","DONE","DONE","DONE","client/src/pwa.ts|client/src/main.tsx|client/vite.config.ts","[""A1""]","回滚路径:移除 SW 更新接管与 chunk 恢复逻辑,恢复为现有 PWA 注册行为。无 Prisma 迁移,无新依赖,不改 API 协议。" "A3","Auth bootstrap refresh 容错增强","在 client/src/auth/AuthContext.tsx 中增强 bootstrapByRefresh 错误分类,仅在确定 refresh token 失效时清理会话;对网络/瞬时错误增加一次延迟重试,避免误跳 /login。","1) SessionExpiredError 或 ApiError.status===401 时立即 clearSession;2) 其他异常等待 1 秒后只重试 1 次;3) 第二次失败时若离线且存在 offline snapshot 则继续走离线快照;否则再 clearSession;4) 保持 has_session hint、refreshRequestRef 去重、离线快照逻辑和后端 auth 协议不变。","命令:export PATH=/home/shunli/.nvm/versions/node/v22.20.0/bin:$PATH && cd client && npm run typecheck && npm run build。业务:1) 刷新 token 首次瞬时失败、第二次成功时,访问 /portal 不应被误跳 /login;2) refresh 返回 401 时应进入未登录 ready 态;3) 离线且已有快照时仍可走离线访问分支。","manual","DONE","DONE","DONE","client/src/auth/AuthContext.tsx|client/src/api/http.ts|client/src/router/guards.tsx","[""A1""]","回滚路径:移除 bootstrap refresh 重试与错误分类,恢复为单次失败即 clearSession。无 Prisma 迁移,无新依赖,不改 auth API 协议。" "A4","服务端静态资源缓存头修复","在 server/src/app.js 的 @fastify/static 注册中增加 setHeaders,确保 HTML、SW、manifest 走 no-cache,带 hash 的 assets 与 workbox 资源走 immutable 长缓存,减少旧 HTML 指向旧 chunk。","1) /、index.html、sw.js、manifest.webmanifest、registerSW.js 返回 Cache-Control: no-cache, no-store, must-revalidate;2) /assets/* 与 workbox-*.js 返回 Cache-Control: public, max-age=31536000, immutable;3) 保留现有 SPA fallback;4) 不新增自定义 409 路由逻辑,不改 API 路由。","命令:export PATH=/home/shunli/.nvm/versions/node/v22.20.0/bin:$PATH && cd server && node --check src/app.js。业务:1) 直连 127.0.0.1:3000 请求首页时响应头为 no-cache;2) 从首页解析出的 hash JS 文件返回 200 且为 immutable;3) 非 API HTML GET 仍回到 index.html。","manual","DONE","DONE","DONE","server/src/app.js","[""A1"",""A2""]","回滚路径:移除静态资源 setHeaders,恢复 Fastify 默认静态缓存头。无 Prisma 迁移,无新依赖,不改 API 协议。" "A5","测试与回归验证","补充服务端静态缓存头单测与前端 Portal 登录稳定性 E2E,执行 R65 相关语法、单测、构建与 E2E 验证,确认 session 与静态资源修复无回归。","1) 新增 server/tests/unit/app.static-cache.test.js 覆盖首页、sw、manifest、assets、workbox 缓存头;2) 扩展 client/tests/e2e/login-portal-ux.spec.ts 覆盖 refresh 首次失败后重试成功时 Portal 不误跳登录;3) client typecheck/build 通过;4) 服务端静态缓存头测试通过。","命令:export PATH=/home/shunli/.nvm/versions/node/v22.20.0/bin:$PATH && cd server && node --check src/app.js && npm test -- tests/unit/app.static-cache.test.js && cd ../client && npm run typecheck && npm run build && E2E_BASE_URL=http://127.0.0.1:5173 npx playwright test tests/e2e/login-portal-ux.spec.ts。业务:1) 预置 has_session=1 且 mock refresh 首次失败、第二次成功后打开 /portal,仍留在 /portal;2) 首页与 hash 资源缓存头符合预期。","manual","DONE","DONE","DONE","server/src/app.js|server/tests/unit/app.static-cache.test.js|client/src/pwa.ts|client/src/main.tsx|client/src/auth/AuthContext.tsx|client/tests/e2e/login-portal-ux.spec.ts","[""A2"",""A3"",""A4""]","回滚路径:若测试或构建失败,暂停提交并回退本轮 session/cache 修复代码。无 Prisma 迁移,无新依赖。" "A6","部署、状态回填与提交收口","在所有验证通过后部署 trialrun,检查 /ready、首页与静态资源响应头、Portal 刷新后的登录态稳定性,并回填 CSV 状态与提交代码。","1) bash deploy/trialrun/deploy.sh 执行成功;2) /ready 可用;3) 首页与 sw.js 响应头为 no-cache;4) 从首页解析出的 /assets/*.js 返回 200 且缓存头为 immutable;5) issues/2026-04-16_r65-session-stability.csv 状态按实测结果回填;6) git commit 成功,提交信息为 R65 A1-A6:Session 稳定性与 409 资源错误修复。","命令:1) python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-04-16_r65-session-stability.csv;2) export PATH=/home/shunli/.nvm/versions/node/v22.20.0/bin:$PATH && cd server && node --check src/app.js && npm test -- tests/unit/app.static-cache.test.js && cd ../client && npm run typecheck && npm run build && E2E_BASE_URL=http://127.0.0.1:5173 npx playwright test tests/e2e/login-portal-ux.spec.ts;3) bash deploy/trialrun/deploy.sh;4) ssh deploy@118.89.77.218 'curl -fsS http://127.0.0.1:3000/ready';5) 远端检查 /、/sw.js、/assets/*.js 响应头与状态码。业务:部署后硬刷新 /portal,不应因 bootstrap 瞬时失败误回 /login。","manual","DONE","DONE","DONE","issues/2026-04-16_r65-session-stability.csv|server/src/app.js|server/tests/unit/app.static-cache.test.js|client/src/pwa.ts|client/src/main.tsx|client/src/auth/AuthContext.tsx|client/tests/e2e/login-portal-ux.spec.ts|deploy/trialrun/deploy.sh","[""A5""]","回滚路径:若部署后头部或登录态验证失败,则不保留最终状态并回退本轮 session/cache 修复。commit message:R65 A1-A6:Session 稳定性与 409 资源错误修复。无 Prisma 迁移,无新依赖,不改 auth API 协议。"