- docs/acceptance/qa-tracker.md:跨周期缺陷单一真源台账(§7=第五轮)。 - 周期 1/2 + iPhone SE/axe 验收证据目录、ACCEPTANCE_REVIEW 快照入库。 - 同步 README/CONTEXT/CLAUDE/DESIGN/testing/deployment/lessons-learned 口径; next.config/Dockerfile/nginx/Jenkinsfile/docker-compose/sentry/prisma 对齐 standalone 产物装配与部署形态。
245 lines
49 KiB
Markdown
245 lines
49 KiB
Markdown
# QA 问题跟踪台账(系统性质量保障 · 单一真源)
|
||
|
||
- 分支:`refactor/optimize-ui` HEAD:`f543e47`(**本台账所引用的全部修复仍只存在于工作树,未提交**)
|
||
- 工作树状态(2026-09-25 实测):`git status --porcelain` = **363 条**(47 条未跟踪)
|
||
- 上游报告:
|
||
- 周期 1:`docs/acceptance/2026-09-23-gates/final-verdict.md`(发现 N-1…N-33;对 2026-09-21 阻断 A-*/R-*/B-* 的闭环)
|
||
- 周期 2:`docs/acceptance/2026-09-23-cycle2/final-verdict.md`(新增表现层/领域层轴:A-1…A-4、C-1、P-*、D-1(defect)、L-1…L-5、Sentry 接线;决策清单 D-1…D-11)
|
||
- 本台账的作用:把上述**分散编号统一到一处**,作为「问题跟踪机制」的执行面;**不重复**两份报告的证据叙述,只登记**状态 + 归属阶段 + 闭合阻塞项**。
|
||
- 编号消歧:周期 2 的缺陷 `D-1`(detail-trust-section)与决策清单 `D-1`(入库)同名,本台账分别记为 **DEF-D1**(缺陷)与 **AUTH-D1**(决策/授权项)。
|
||
|
||
## 0. 验收判定(对齐 objective 五阶段)
|
||
|
||
| objective 阶段 | 交付物 | 状态 |
|
||
|---|---|---|
|
||
| 1 代码审查 | 两份周期报告 + 本台账 §1 | **已达成**(多轴、subagent + 主线逐条复核) |
|
||
| 2 测试 | 门禁实测表(周期 1 §1 / 周期 2 §0)+ `docs/testing.md` | **已达成**(可自主执行的门禁全部出数;写库/基线/线上/全量变异等需授权项见 §3) |
|
||
| 3 修复 + 跟踪机制 | 本台账(单一真源) | **机制已建立**;修复的**可复现性未达成**(未入库) |
|
||
| 4 优化 | 性能(Lighthouse 9/9=100、a11y 节点计数、限流键、死资源清理)+ 安全(提权/XSS/越权/令牌/开放重定向) | **已达成**,余留项见 §2/§3 |
|
||
| 5 验收确认 | 本报告 + 门禁凭据 | **未达「可判验收通过」**——唯一阻断为交付形态(AUTH-D1 未入库),非代码质量 |
|
||
|
||
> **结论不变**(承接周期 1「有条件通过」/周期 2「未达可判验收通过」):技术面已闭环并有实测凭据;验收不能判完成的**唯一实质原因**是 §3 AUTH-D1(约 363 条改动未提交,评审者/CI/下一位开发者无法复现本台账任何结论)。
|
||
>
|
||
> **2026-09-28 更新**:第三/四/五轮(§4/§5/§7)在此结论上继续累积修复(第五轮再修 S-21/S-22/Q-10②/S-3 两个 P2 + 12 个 P3,全带判别测试与门禁凭据),未提交的工作树现 **394 条**——AUTH-D1 仍是唯一硬阻断。
|
||
|
||
## 1. 本台账建立时的独立复核(trust-but-verify,2026-09-25)
|
||
|
||
对**最高后果**的 12 项「已修」声明做了只读源码复核(读当前工作树,未采信报告自述),结论:
|
||
|
||
| ID | 声明 | 复核结果 | 证据(本次亲读) |
|
||
|---|---|---|---|
|
||
| N-18 | draft/disable 开放重定向防护 | **CONFIRMED** | `src/lib/cms/internal-redirect.ts:8`;`disable/route.ts:13-21` 先校验后副作用 |
|
||
| N-19 | admin/stats 越权读取 | **CONFIRMED** | `src/app/api/admin/stats/route.ts:11` 用 `requirePermission('content-model','read')` |
|
||
| N-20 | 停用账号令牌仍可用 | **CONFIRMED** | `src/lib/permissions.ts:41-45` 读 `User.status`,早于 `:52` 的 super_admin 短路 |
|
||
| N-25 | 限流 XFF 伪造绕过 | **CONFIRMED** | `src/lib/client-ip.ts:14-23` 优先 `x-real-ip`、XFF 取最右;`contact/route.ts:4,51` 消费 |
|
||
| N-22 | 安全头门禁恒不成立 | **CONFIRMED** | `check-security-headers.ts:63-78,101,128-132` cookie 状态按 flag 推导并计入退出码;缺 CSP=fail;HSTS 分 http/https |
|
||
| N-1 | JSON-LD `</script>` 注入 | **CONFIRMED** | `src/components/seo/structured-data.tsx:8-9` `jsonLd()` 转义 `<`/`>`,8 个 sink 全用 |
|
||
| DEF-A1..A4 | 后台编辑器定位/守卫 | **CONFIRMED** | `admin/content/[modelCode]/[itemId]/page.tsx` `id` 过滤 + `itemNotFound`(:691) + `canEdit`(:252,736,917) 收口 |
|
||
| C-1 | 案例页缺键整页崩溃 | **CONFIRMED(边界归一)** | 归一在唯一 importer `(marketing)/cases/[slug]/client.tsx:22,30-35`,恰在未受信任 CMS 强转处;报告所称组件文件为归属笔误,非漏洞(见 §2.2 REG-C1) |
|
||
| L-5 | 媒体派生命名冲突 | **CONFIRMED** | `src/lib/media/image-processor.ts:75` 追加 `Math.random()` 后缀 |
|
||
| L-1 | admin-api 吞掉 4xx/5xx | **CONFIRMED** | `src/lib/admin-api.ts:85-102` 解密后按 `res.ok` 抛错 |
|
||
| N-4/5/6 | 保存/发布状态通道 | **CONFIRMED** | 保存载荷无 `status`;发布走 `runWorkflow`;POST 拒非 draft |
|
||
| DEF-D1 | 指标前导符号错位 | **CONFIRMED** | `detail-trust-section.tsx:26-48` 用 `[^0-9.]` 否定类 + prefix/suffix 分列 |
|
||
|
||
> Sentry 接线亦复核为真:`src/instrumentation.ts`、`src/instrumentation-client.ts` 存在且 import `@sentry/nextjs`;`next.config.mjs:131` `export default withSentryConfig(nextConfig)`。
|
||
> 覆盖扫描(非定点复核):`src/`+`scripts/` 内无条件 `status:'pass' as const` = **0**;`\|\| echo` = 12(均在 CI/部署 shell 的信息性回退,非门禁判据);`e2e/*.spec.ts` 内 `if (.count()>0)` 守卫 = 11 处(周期 1/2 已逐条定性为视口/角色合法分支,见 N-27)。
|
||
|
||
## 2. 缺陷登记(按闭合状态分组)
|
||
|
||
图例 · 状态:`FIXED-UNCOMMITTED`=已修且§1或原报告有 RED/GREEN 凭据,但未入库;`DECISION`=待产品/设计裁定;`AUTH`=待单独授权动作;`RECORDED`=已知、诚实披露、本轮不动。
|
||
|
||
### 2.1 已修复(代码在工作树内,含 §1 复核标记)
|
||
| ID | 级别 | 类别 | 摘要 | 状态 | 凭据 |
|
||
|---|---|---|---|---|---|
|
||
| N-18/19/20/25 | P1 | 安全 | 开放重定向 / 越权读取 / 停用账号令牌 / 限流绕过 | FIXED-UNCOMMITTED | §1 全部 CONFIRMED + 原报告 RED 实测 |
|
||
| N-22 | P1 | 门禁可信度 | 安全头门禁恒不成立断言 | FIXED-UNCOMMITTED | §1 CONFIRMED + 双向对照 |
|
||
| N-1 | P1 | 安全 | JSON-LD 存储型 XSS 面 | FIXED-UNCOMMITTED | §1 CONFIRMED |
|
||
| N-4/5/6/7 | P0/P1 | 功能 | 后台保存必 400 / 发布必失效 / 状态死控件 / 状态回写 | FIXED-UNCOMMITTED | page.test.tsx |
|
||
| N-9 | P0 | 门禁可信度 | standalone 产物缺 `dist/static`(CSS 404) | FIXED-UNCOMMITTED | postbuild 落地 |
|
||
| N-11 | P2 | 性能/BP | `/contact` CSP 违规(Zod eval 探测) | FIXED-UNCOMMITTED | `z.config({jitless})` + Lighthouse A/B |
|
||
| N-12 | P1 | 门禁可信度 | 补测自身把 rAF 变只读致套件静默不跑 | FIXED-UNCOMMITTED | 隔离复跑 |
|
||
| N-17 | P2 | 测试诚实性 | security-headers 裸 `return` 伪装通过 | FIXED-UNCOMMITTED | 68 passed + 4 skipped |
|
||
| N-24②③④ | P2 | 门禁可信度 | 404 即就绪 / 不受支持的 next start / pr-checklist 空转 | FIXED-UNCOMMITTED | 双向对照 |
|
||
| N-27(部分)| P2 | 测试装配 | 零断言用例 / `expect()\|\|expect()` JS bug / 死选择器 | FIXED-UNCOMMITTED | 四 project 实测 |
|
||
| N-30(删兜底)| P1 | 内容零编造 | 两处凭空 `40%` 兜底已删(含反转被锁死的测试) | FIXED-UNCOMMITTED | 23→23 通过 |
|
||
| N-31 / N-33(部分)| P2 | 文档一致性 | 不可执行命令 / 假阈值 / 口径回填 | FIXED-UNCOMMITTED | 机械比对 |
|
||
| A-1..A-15, R-1..R-5, B-1/3/4/7 | P0-P2 | 安全/功能/规范/UI | 2026-09-21 阻断项 | FIXED-UNCOMMITTED | 周期 1 §2 |
|
||
| DEF-A1..A4, C-1, P-1/4/5, DEF-D1, L-1, L-5 | P1-P2 | 表现/领域层 | 后台编辑器数据损毁 / 案例页崩溃 / 滑动 / 进度条 / 前导符号 / 错误吞没 / 命名冲突 | FIXED-UNCOMMITTED | 周期 2 §1;C-1 见 §2.2 REG-C1 |
|
||
| Sentry 接线 | P1 | 可观测性 | 「声明未接线」→ 已接(含 Turbopack 约定文件) | FIXED-UNCOMMITTED | 周期 2 §1.3;§1 复核存在 |
|
||
| N-24① | P2 | 门禁可信度 | axe 逐页状态断言(期望集感知) | FIXED-UNCOMMITTED | 周期 2 §3.7 双向对照 |
|
||
|
||
### 2.2 待产品 / 设计决策(代码侧不该替业务作答)
|
||
| ID | 级别 | 事项 | 入口 |
|
||
|---|---|---|---|
|
||
| REG-C1 | P3(**已复核,非活跃缺陷**) | C-1 归一在 `cases/[slug]/client.tsx:22,30-35`(唯一 importer),位置**正确**:恰在未受信任的 CMS 强转 `item.data as unknown as CaseStudyData` 边界处 `?? ''`/`?? []` 一次归一,`case-detail-page.tsx` 消费已定型 props 可信任(无第二个真实调用方)。周期 2 报告把它记在组件文件属**归属笔误**,非漏洞;**不**在组件内加冗余守卫(违反"不为不可能发生的场景加校验") | 仅更正周期 2 §1.2 的 file:line 归属 |
|
||
| DEC-N21 | P2→潜伏 | `'content-item'` 幻影 modelCode:周期 2 §5.3 以源码推翻「越权」定性,实为 **fail-closed**;真风险是将来被补进种子即成跨模型读取闸口。建议「缺 modelCode ⇒ 400」 | `admin/items/route.ts:79` |
|
||
| DEC-N21b | P2 | `/admin` 无服务端 gating(仅客户端跳转),HTML 外壳无会话也下发;数据 API 已守卫 | 无 `middleware.ts` |
|
||
| DEC-N30 | P1 | 四条互斥 SLA(工作日 2h / <4h / 48h / <2h·7×24)留哪一条 | 周期 1 §8.2 / 周期 2 §5.2 |
|
||
| DEC-D5 | P1 | `/products/erp-upgrade` 在线数字(`99.2%`/`40%+`/`从5天到1天`)在**活组件** `erp-upgrade-content-v2.tsx:63,69,77`,无 basis 无 MetricsBasisNote,且该路由无视觉基线 | 周期 2 §5.1 |
|
||
| DEC-D7 | P2 | news 正文字段声明 `richtext` 而 `renderField` 无该分支(能力缺口) | 周期 2 §5.3 |
|
||
| DEC-P2-motion | P2 | 动效门禁 23 处判红(20 缓动曲线 + 2 裸时长 + `--transition-gentle:1000ms`):改代码 or 承认为设计例外 | 周期 2 §3.5 |
|
||
| DEC-L3 | P3 | `client-ip.ts` 头部信任无条件;直暴露(非 nginx)时限流可被 `X-Real-IP` 自报绕过(须与部署协同,勿单独改) | 周期 2 §5.3 |
|
||
| DEC-L4 | P3 | 对称加密密钥来自公开 bundle 的 `NEXT_PUBLIC_ENCRYPTION_SECRET` ⇒ 仅混淆无机密性,文档口径不得当安全控制引用 | `crypto.ts:36` |
|
||
| DEC-P-2 | P3 | 死码族删除(`loading-state` 是 brand-token 唯一白名单豁免 N-8;另有 hero-section-v2/insight-card/industry-grid/三份 product-card/breadcrumb/micro-interactions/PullToRefresh 等) | 周期 2 §5.3 |
|
||
| DEC-N4-N23 | P1 | 安全头门禁默认打**线上** `novalon.cn` 而非本分支产物(声明与行为相反);改法涉 CI/`test:all` 语义 | 周期 1 N-23 |
|
||
|
||
### 2.3 待授权动作(阻塞验收;须逐项明文批准)
|
||
| ID | 动作 | 命令 | 为何阻塞 |
|
||
|---|---|---|---|
|
||
| **AUTH-D1** | **入库 / PR(验收唯一硬阻断)** | 先 `git fetch origin dev` 取真值 → 裁定双向分叉(周期 1 记 30↔31,两端同标题异 hash)重复提交对 → 按 5 组分笔提交 → Gitea PR(过 `check-pr-checklist.sh`) | 约 363 条改动只在工作树 ⇒ 本台账/两份报告结论均不可复现。**⚠ 分叉非快进整理,须先 fetch 再判** |
|
||
| AUTH-D2 | 重跑 seed | `npm run db:seed` | 使 L-2(seed 权限过期)修复对数据生效;现网 `dev.db` 仍为旧权限行 |
|
||
| AUTH-D3 | 视觉基线写入 | `npx playwright test visual-regression.spec.ts --update-snapshots` | 20 例缺参照恒红 + `erp-upgrade`/`about/brand`/`cases/[slug]` 三路由从无基线;追认现状需人工核看 85 张差异 |
|
||
| AUTH-D4 | 写库 E2E | 完整 `npm run test` / `test:e2e:prod`(差 `cms-workflow`+`user-journey` 共 11 例) | 真写 `prisma/dev.db`;只读边界已实证,此 11 例是唯一未执行的功能层 |
|
||
| AUTH-D9 | Sentry 实投递 | 真实 DSN 起服务验证事件与 source map 上传 | 接线惰性有码路证据已足,但投递未线上验证 |
|
||
| AUTH-D10 | 全量变异测试 | 去 `--inPlace`(config 默认沙箱)跑 `mutate` 全集 | 成本/磁盘(峰值 1.7G 曾致 ENOSPC,周期 2 §3.9);须 ≥2G 余量与无并发门禁窗口 |
|
||
| AUTH-D11 | 门禁证据落盘 | `check:axe` 默认 `OUT` 按日期分目录(同步 Jenkinsfile);`docs/acceptance/**` 是否纳入版本控制 | 现固定目录互相覆盖证据且未跟踪(周期 2 §3.9) |
|
||
|
||
### 2.4 已知不稳定清单(**禁止用 retry / 放宽阈值「修」**)
|
||
- `mobile-user-journeys` UJ-10(firefox,隔离复跑通过)、`website-acceptance:111` 响应式(隔离通过)、`@accessibility` webkit 四例(周期 1 N-32,隔离不复现)。同属**并发 / 墙钟**族(周期 1 N-28:同一代码三轮失败数 8→1→5)。墙钟型用例(`p4:31`、`p4:68`)不该进共享并发门禁。
|
||
|
||
## 3. 提交前强制提醒(承接 memory 纪律)
|
||
- 提交只加自己的路径,**勿 `git add -A`**:`prisma/dev.db-shm`/`-wal` 未被 `.gitignore` 覆盖(周期 1 §7),`git add -A` 会把 SQLite 边车与陈旧 `dev.db`/`data.db`(周期 2 §3.9)扫入。
|
||
- 破坏性 / 共享状态动作(fetch/rebase/commit/push/merge/seed/写库 E2E/写基线/force-push)须**单独明文授权**,不夹带在常规变更里。
|
||
- 会话结束前披露工作树状态(本台账 §0/§2.3 AUTH-D1 即披露)。
|
||
|
||
## 4. 第三轮审查轴(2026-09-25 本会话,subagent 并行 + 主线逐条复核)
|
||
|
||
两条并行 subagent:① 全部 `src/app/api/**` 路由处理器安全/正确性审计;② `src/lib/**`+`src/components/**` 逻辑缺陷狩猎。均要求 CONFIRMED 附 file:line + 触发条件,并给出已知项围栏以防重报。**主线未照抄任何一条子代理结论**:定点复核 → 对拟修复项做 RED→GREEN。
|
||
|
||
### 4.1 主线已复核确认 + 已修 + RED 测试(工作树内,未提交)
|
||
| ID | 级别 | 缺陷 | 触发/影响 | 修复 | RED→GREEN 凭据 |
|
||
|---|---|---|---|---|---|
|
||
| **Q-1** | **P1** | **登出未清除服务端 httpOnly cookie**:`auth-context.tsx` `logout()` 只清 localStorage;但 login 经 `setTokenCookie` 写 `novalon_token`(24h)/`novalon_refresh`(7d),而 `authenticateRequest`(`auth.ts:74`) 回退读 cookie。`/api/auth/logout` 路由**存在却从无调用者** ⇒ 点「登出」后全站受守卫 API 仍凭 cookie 放行至多 24h/7d | 后台「登出」是**假登出**;共享/被盗机器上会话延续 | `logout()` 先 `POST /api/auth/logout`(触发 `clearTokenCookie`),网络失败也清本地并跳转 | 新增 `auth-context.test.tsx`(2 例):断言 fetch 到 `/api/auth/logout`+POST 与网络失败仍清理;**改前 1 failed(fetch 0 calls)→ 改后 2 passed** |
|
||
| **Q-2** | **P2** | **缩略图格式伪标**:`image-processor.ts` thumbnail 声明 ext `'webp'`(`buildDerivative(..., 'webp', ...)`)但缓冲只 `.resize().toBuffer()` 未调 `.webp()` ⇒ PNG/JPEG 上传的 `-thumbnail.webp` 实为原格式字节,Content-Type 与内容不符、零 WebP 压缩收益 | 落盘文件内容 ≠ 扩展名/MIME;`source type` 消费方受影响 | 缩略图管道加 `.webp({quality:80})` | 既有测试只断路径/尺寸、**从不验格式**(覆盖空洞);新增例断 `sharp(thumb.buffer).metadata().format==='webp'`(PNG/JPEG 双输入);**改前 `Received:"png"` → 改后 media 4 套件/60 例全通过** |
|
||
| **Q-3** | **P2** | **登录端点无限流**:`contact` 有 IP 限流但 `POST /api/auth/login` 无任何节流 ⇒ 匿名者可无限驱动 cost-10 bcrypt(CPU DoS)+ 离线式撞库 | 凭据填充 / CPU 耗尽 | 抽出可复用 `src/lib/rate-limit.ts`(时钟可注入 + `reset()`,**放进覆盖率棘轮内**),登录按 IP `max:10/15min` 且**判定在 bcrypt 之前**;contact 重构复用同一实现(消除副本) | 新增 `rate-limit.test.ts` 4 例(边界/独立 key/窗口过期/reset)+ 扩展 `login/route.test.ts` 一例:前 10 次 200、第 11 次 429 且 `verifyPassword` **仍只 10 次**(证限流省掉 bcrypt);**4/4 + 全 login/contact/auth 73 例通过**,`type-check` EXIT=0,改动文件 `eslint --quiet` 0 error |
|
||
| **Q-4** | **P2** | **停用账号令牌在 6 个内联 role handler 仍全权(N-20 同类,绕开已收口的 `checkUserPermission`)**:`admin/users` GET/POST/PUT/DELETE 与 `admin/roles` GET/PUT 用 `authenticateRequest`(仅验签名,不看 `User.status`)+ 内联 `userRole` 检查。⇒ 被停用但持 ≤24h 有效令牌的 super_admin 仍能列/建/改/删用户、重写权限矩阵 | 停用/离职管理员的残留令牌保留完整后台写权 | 抽出可复用 `authenticateActiveRequest(request)`(`permissions.ts`,`prisma.user.count({id,status:1})` 判活),**6 个 handler 全部改用它**并在角色检查/任何写入前 fail-closed | 在 `users/route.test.ts` 与 `roles/route.test.ts` 各加 2 例(停用超管 → 401 且 `user.create`/`user.update`/`$transaction`/`permission.*` 均未触达);既有 30+ 例在默认存活下保持通过 ⇒ **admin users/roles/permissions 3 套件/54 例全绿**,`type-check` EXIT=0,5 改动文件 `eslint --quiet` 0 error |
|
||
| **Q-5** | **P2** | **响应式网格被 `StaggerReveal` 单子吞成单列**:`content/sections.tsx` 的 `TechStackShowcase`/`DataProofSection` 把 `grid md:grid-cols-N` 挂在外层 div,而该 div 的唯一子元素是 `StaggerReveal`(渲染一个 motion.div,再为每卡片包 motion.div)⇒ 网格只有 1 个网格项,卡片在 md/lg 断点堆成单列、`gap`/`space-y` 失效;`FAQSection` 的 `space-y-3` 同理被吞。**这些组件是活的**(`product-detail-content-v3.tsx:335,343,357` 渲染产品详情页)。正确写法见 `l3-signals-slot.tsx:109-119`(网格类挂 StaggerReveal,其注释即记此坑) | 产品详情页技术栈/数据佐证/FAQ 桌面端单列,UX 退化 | 把网格/间距类移到 `StaggerReveal className`,删除多余外层 grid div(FAQ 仅移 `space-y-3`,保留 col-span 外层) | 在 `sections.test.tsx` 加 3 例,断言 `[class*="grid-cols-3"]`/`grid-cols-4`/`space-y-3` 元素的**直接子元素数 == 条目数**(旧结构下恒为 1 ⇒ RED);sections.test **28 例全通过**,`type-check` EXIT=0,`eslint --quiet` 0 error |
|
||
| **Q-7** | **P3** | **列表路由分页参数未校验(NaN→500 + 无界 take)**:`admin/items` GET 用 `parseInt(searchParams.get('page') \|\| '1')` ⇒ `?page=abc`→NaN→`skip:NaN`→真库 Prisma 抛错→500(应安全回落);`?pageSize=99999`→无界 `take`(任何 read 权限者可整表拉取大 `data` JSON)。`admin/users` GET 有 `Math.max/min` 夹取但 `Math.max(1,NaN)` 仍是 NaN ⇒ 同类未愈 | 500 崩溃 + 无界读取 | 抽出可复用 `src/lib/pagination.ts` `parsePagination(sp,{defaultPageSize,maxPageSize})`(NaN/0/负数→默认,超上限→夹取,取整),**items + users 两路都接入**(max 100) | `pagination.test.ts` 6 例(缺省/合法/非数字回退/夹取/取整,含"移除即崩"的核心 NaN 面)+ `items/route.test.ts` 1 例(`?page=abc&pageSize=99999` ⇒ 200 且 `findMany({skip:0,take:100})`,旧码为 `skip:NaN/take:NaN` ⇒ RED)。**pagination 6 + items 38 + users 18 全通过**,`type-check` EXIT=0,改动文件 `eslint --quiet` 0 error |
|
||
| **Q-8** | **P3** | **内容 ID 存在性枚举 oracle**:`admin/items` PUT/DELETE 与 `admin/items/[id]/workflow` POST 都先 `contentItem.findUnique`→`notFound('内容不存在')` **再** `requirePermission` ⇒ 匿名调用者可用「404=该 ID 存在 vs 401/403=权限屏障」枚举有效内容 ID(无写入先于鉴权,纯信息泄露面) | 未鉴权可枚举内容 ID | 三个 handler 均在存在性探针**之前**加 `if (!authenticateRequest(request)) return unauthorized();`(先认证→再 404→再 per-model 权限),关闭匿名枚举;记残留:已登录但无该模型权限者仍能分辨 404/403(把 notFound 与 forbidden 合流需改契约,未做) | `items` 2 例 + `workflow` 1 例:匿名 PUT/DELETE/workflow ⇒ **401 且 `findUnique` 未被调用**(证探针在认证门之后);两测试文件补 `jest.mock('@/lib/auth')` 默认有效会话使既有授权用例不受影响 ⇒ **items+workflow+pagination 3 套件/47 例全通过**,`type-check` EXIT=0,5 改动文件 `eslint --quiet` 0 error |
|
||
| **Q-10a** | **P3** | **`ui/button.tsx` 涟漪 id 用 `Date.now()`**:同毫秒两次点击 ⇒ 重复 React key + 清理 `filter(r=>r.id!==id)` 会一次删掉同 id 的多条涟漪;且 600ms `setTimeout` 卸载时从不清除(setState-after-unmount / 定时器泄漏)。Button 是全站在用组件 | 真实浏览器下重复 key 告警 + 卸载后定时器泄漏 | 涟漪 id 改自增 `useRef` 计数器(保证唯一);定时器收入 `useRef` 数组,`useEffect` 卸载时 `clearTimeout` 全部 | **诚实标注**:真正判别性(重复 key)在本环境不可测——做过 RED 探针(还原 `Date.now()` 后断言仍绿),因 Jest 跑 production React 构建、dev key 告警被剥离,React 18 也不再就 setState-after-unmount 告警。故只补**被改代码路径**的行为用例(点击→涟漪出现→600ms→清除,能捕获"永不清除/不出现"类回归),不谎称它是缺陷锁。**button 21 例通过**,`type-check` EXIT=0,`eslint --quiet` 0 error |
|
||
| **Q-10c** | **P3(隐私/同意 fail-open)** | **`analytics.ts` `getStoredPreferences` 把 `JSON.parse(...) as CookiePreferences` 原样透传,无字段校验**:而 `updateConsentDetailed` 用 `preferences.analytics ? 'granted':'denied'` 判定 ⇒ 被篡改/写坏的 `localStorage`(如 `{"analytics":"yes","marketing":1}`,或数组/数字等非对象)会以真值非布尔**意外授予** analytics/ad 同意 | 同意管理可被污染存储绕过(GDPR/隐私面) | 新增 `sanitizePreferences(raw)`:四个开关严格 `=== true` 才算同意(否则 false),`necessary` 恒真,`timestamp` 仅数字保留;`getStoredPreferences` 走它 | 2 例真判别(未修即 RED:旧码原样返回 `'yes'/1`,断言 `false` 必红):truthy 非布尔→false、非对象载荷→全 false 且 necessary 恒真;既有有效对象用例(`{necessary:true,analytics:false,marketing:true,functionality:true}`)经 `toEqual` 仍通过 ⇒ **analytics 31 例全通过**,`type-check` EXIT=0,`eslint --quiet` 0 error |
|
||
| **Q-10b** | **P3** | **`api-crypto.ts` 用客户端 `content-length` 判定是否有请求体**:`hasBody = contentLength && parseInt(contentLength) > 0`。加密的 POST/PUT/PATCH 若走**分块传输**(无 `content-length` 头,或该头被省略/伪造为 0)⇒ 被判「无 body」跳过解密,密文 `{data:"..."}` 原样交给 handler(`isEncrypted:true` 但体未解),handler `.json()` 拿到信封而非真实字段 | 加密通道在分块/缺头时静默不解密(功能正确性/健壮性) | 改按 **HTTP 方法**判体存在:`method ∈ {POST,PUT,PATCH}` 即尝试读体+解密(空体自然落回原路径),不再信 content-length | 新 1 例真判别(RED 探针实测:还原 content-length 版 ⇒ 该例判红;恢复 ⇒ 通过):加密 POST **无 content-length** ⇒ 断言 `mockDecrypt` 被调用且 handler 收到解密体;既有 26 例(全部 method+content-length 配对)不受影响 ⇒ **api-crypto 27 例通过**,`type-check` EXIT=0,`eslint --quiet` 0 error |
|
||
| **Q-9** | **P3** | **`admin/media` 多文件上传无原子性**:上传循环内 ① 某文件超 10MB 直接 `return validationError`,但**前面文件已 `uploadMedia` 落盘+入库**;② 某文件在 `uploadMedia` 内被 magic-byte/类型策略拒(抛 `UploadPolicyError`,外层 catch 返 400)同样留下前面已写入的文件 ⇒ 请求返回错误却已产生孤儿媒体资产(磁盘 + `MediaAsset` 行),无返回、无清理路径 | 半途失败留下孤儿资产(磁盘/DB 泄漏 + 计费/存储面) | 把尺寸校验提到**任何写入之前**(整批预检);上传循环包 try,失败时对本请求已成功的 `results` 逐个 `deleteMedia` 回滚再返回错误 | 新 2 例真判别(旧码必 RED):① 第二个文件抛 `UploadPolicyError` ⇒ 断言 `mockDeleteMedia('asset-1')` 被调用(旧码无回滚 ⇒ 不调用);② 批次含一个超限文件 ⇒ 断言 `mockUploadMedia` 从未被调用(预检在任何写入前拒整批)。既有单/多文件成功用例仍通过 ⇒ **media route 14 例通过**,`type-check` EXIT=0,`eslint --quiet` 0 error |
|
||
|
||
### 4.2 子代理上报、**主线尚未逐一复核**(登记为待验证,不当作已确认,不据此宣称已修)
|
||
| ID | 级别 | 摘要 | 位置(子代理所引) | 主线状态 |
|
||
|---|---|---|---|---|
|
||
| Q-4 | ~~P2 待复核~~ | ~~6 个内联 role 数组的 handler~~ | — | **已复核确认为真缺陷并已修**(见 §4.1 Q-4):status 维度缺位属实,role 授予逻辑本身仍正确 |
|
||
| Q-5 | ~~P2 待复核~~ | ~~外层 grid/space 被 StaggerReveal 单子吞成单列~~ | `content/sections.tsx` | **已复核确认为真缺陷并已修**(见 §4.1 Q-5):3 处组件为活代码(产品详情页),已把网格类挂回 StaggerReveal 并加 3 例结构断言 |
|
||
| Q-10 | P3 | 四条子代理上报:① `api-crypto.ts` `hasBody` 信任客户端 `content-length`;② `workflow.ts:84` `updateMany`+`auditLog.create` 非 `$transaction`;③ `analytics.ts:33` 同意偏好无字段校验;④ `button.tsx` ripple `Date.now()` 键碰撞 + 卸载不清 timeout | 各处 | **④=Q-10a、③=Q-10c、①=Q-10b 均已修**(见 §4.1,各带判别测试)。**仅 ② 未做**:修法需 interactive `$transaction(async tx=>…)`(update→读回→审计有数据依赖),会牵动 workflow 路由测试的 prisma mock 边界,风险高于其 P3 权重,暂缓并附精确配方 |
|
||
| Q-6 | P2 | `detail-product-value.tsx:73-91` render 体内 `setDisplayValue + requestAnimationFrame`(StrictMode/重渲染派生多串未取消动画) | 组件 `detail-product-value.tsx` | **待复核**:grep 显示该组件仅被 `products/erp-upgrade-v3/page.tsx` 引用,而周期 2 判定 `erp-upgrade-v3` 为**死路由** ⇒ 若确死则非在线缺陷,仅登记;render 副作用面仍宜在组件复用时修 |
|
||
| Q-7 | ~~P3 待复核~~ | ~~`admin/items`/`users` GET 分页未校验~~ | — | **已复核确认为真缺陷并已修**(见 §4.1 Q-7):新增 `parsePagination` 复用件,items + users 两路接入,6+1 例覆盖 |
|
||
| Q-8 | ~~P3 待复核~~ | ~~内容 ID 存在性 oracle~~ | — | **已复核确认为真缺陷并已修**(见 §4.1 Q-8):三 handler 认证前置于存在性探针,3 例证 `findUnique` 不被匿名触达 |
|
||
| Q-9 | ~~P3 待复核~~ | ~~多文件上传中途失败留孤儿~~ | — | **已复核确认为真缺陷并已修**(见 §4.1 Q-9):整批尺寸预检 + 失败回滚已成功文件,2 例真判别 |
|
||
|
||
### 4.3 主线**证伪**的子代理结论(不修,防把假发现写进代码/文档)
|
||
- **Q-FP-1**:子代理称 `detail-product-value.tsx:69` `parseFloat(value.replace(/[^0-9.]/g,''))` 把 `"1,000+"` 变成 `1`(千倍低估)。**不成立**——`[^0-9.]` 会删掉逗号、保留数字与点,`"1,000+"`→`"1000."`→`parseFloat=1000`,正确。故该解析点**不是缺陷**(其 render-副作用面另见 Q-6,属不同问题,仍待复核)。
|
||
|
||
> **本轮验证层级诚实说明**:`type-check` 为**全程序** `tsc --noEmit` 且 EXIT=0(覆盖 Q-1/2/3 全部改动);受影响单元套件(auth/login/contact/media/rate-limit)**73 例全通过**;改动文件 `eslint --quiet` **0 error**。但**全量 `test:coverage`(1725 例)本轮未重跑**,且 Q-1(表现层登出)与 Q-3(登录 API 行为)**不在**任何已跑过的 E2E 子集里 ⇒ 其生产目标凭据待 AUTH-D1 入库 + AUTH-D4 后统一复跑。Q-2(media 派生)真 `sharp` 单测覆盖格式,最直接。**Q-4 的凭据层级**:6 个 handler 的存活判定由 `admin/users`+`admin/roles` 路由级 jest(mock prisma.user.count)端到端覆盖(默认存活 30+ 例仍绿 + 停用 4 例判 401/零写入),属**单测层**,非 E2E;其"移除 helper ⇒ 停用令牌被放行"的 RED 逻辑由"旧码走 `authenticateRequest` 不看 status、新 mock 的 `count` 遂成死参数、断言 401 必红"反证。**验收判定不变**:新增修复同样只在主工作树,AUTH-D1 仍是唯一硬阻断。
|
||
|
||
### 4.4 本会话全量回归证据(2026-09-25,覆盖 Q-1…Q-5/Q-7/Q-8 全部改动之后)
|
||
| 门禁 | 命令 | 结果 |
|
||
|---|---|---|
|
||
| 全量单元 | `npm run test:unit` | **142 套件 / 1760 例全通过,0 失败**(较周期 2 的 137/1725 净增 5 套件 35 例,全为本会话新增 RED 测试;无一处回归) |
|
||
| 覆盖率棘轮 | `npm run test:coverage` | **EXIT=0,阈值全达标**:global **85.96 stmts / 87.57 branch / 81.38 funcs / 85.96 lines**(门限 75/82/75/75 **未下调**);本会话新增 `src/lib/pagination.ts`、`src/lib/rate-limit.ts` 各 **100/100/100/100**(在 `collectCoverageFrom` 的 `src/lib/**` 内,随棘轮受约束) |
|
||
| 真库集成层 | `npm run test:integration:real` | **EXIT=0,4 套件 / 23 例全通过**;一次性 `/tmp/novalon-itest.db`,teardown 守卫实测 `prisma/dev.db`、`dev.db`、`data.db` 的 inode+size+mtime 与运行前完全一致 ⇒ 本会话对 `permissions.ts`(新增 `authenticateActiveRequest`)与 users/roles/items/workflow 鉴权路由的改动在真库鉴权层无回归,且未污染开发库 |
|
||
| 静态 a11y(对比度/令牌) | `npm run check:contrast` + `check:brand-token` | 两者 **EXIT=0**:contrast 44/44 令牌配对满足 WCAG 2.1 AA;brand-token 扫描 406 文件 **0 违规**(1 处已知白名单降级 = `loading-state.tsx` 死组件,见 N-8/D-8)⇒ 本会话改动无对比度/品牌令牌回归 |
|
||
| 生产构建 + 装配 | `npm run build`(含 postbuild 拷贝) | **EXIT=0**,`dist/standalone/{server.js,dist/static,public}` 三项齐备(`ASSEMBLY_OK`)⇒ 本会话对 `api-crypto`/`permissions`/users·roles·items·workflow·media 路由的服务端改动可干净构建(N-9 前提成立);产物 `heading-hierarchy-report.json` 等落盘物均被 `.gitignore` 覆盖,不脏提交面 |
|
||
| 标题层级(产物上) | `npm run check:headings`(自起 standalone 扫 10 页) | **EXIT=0**:10 页全通过、0 问题 ⇒ 与 contrast/brand-token 合起来 **完整 `check:a11y` 三件套在本会话构建产物上全绿**(不再仅沿用周期 1/2 结论) |
|
||
| Lighthouse(产物上) | `npm run lighthouse`(lhci autorun 自起 standalone) | **EXIT=0**:对 9 URL × 3 次运行 = 27 次跑的全部断言通过(含本会话前几轮新加的 `inspector-issues` minScore 断言)⇒ 性能/a11y/BP/SEO 门禁在当前源码产物上仍达标 |
|
||
|
||
| 类型检查 | `npm run type-check`(全程序 tsc) | **EXIT=0** |
|
||
| Lint | `npm run lint` | **0 error / 104 warning**(与周期 2 口径 104 完全一致 ⇒ 本会话新文件 `rate-limit.ts`/`pagination.ts` 等**零新增债**) |
|
||
|
||
> 说明:**本会话已在含全部 Q 修复 + S-1 修复的最终树上重跑并 EXIT=0**:`test:unit`(1758)、`test:coverage`(阈值棘轮)、`test:integration:real`(23)、`check:contrast`/`check:brand-token`/`check:headings`(完整 check:a11y)、`npm run build`+装配、`npm run lighthouse`;`type-check`/`lint` 全程 EXIT=0。**仍未在本会话重跑**:全站 `check:axe`(需自起 standalone + crawl-routes 清单,端口/残留进程需专管)、生产目标 E2E(**注意 `npm run test:e2e:prod` 不排除写库 spec,跑它即触发 AUTH-D4**)、视觉只读比对(Q-5/S 系列改了布局,需 AUTH-D3 重基线)。周期 1/2 的这三项结论仍登记于各自报告。
|
||
|
||
## 5. 第四轮审查轴(2026-09-25,2 路并行 subagent:`src/app/**` 页面 + 余下 `src/components/**`)
|
||
|
||
两条只读 subagent 分别扫前几轮**只 grep 未逐行**读过的面。主线**未照抄任何一条**:S-1 我逐行核实并修复;其余登记为「待主线复核」,其中标注与前序已知项同机制的(Q-5/C-1 新点位)优先,因为修法现成。
|
||
|
||
### 5.1 主线已核实 + 已修(工作树内,未提交)
|
||
| ID | 级别 | 缺陷 | 触发/影响 | 修复 | 凭据 |
|
||
|---|---|---|---|---|---|
|
||
| **S-1** | **P2** | **首页 CMS 数组未逐条守卫 ⇒ 整页 RSC 崩溃**:`home-content-v15.tsx` 的 `ServicesSection` 只在 `:510` 校验 `services[0].subtitle`,随后 `:546` `service.highlights.map`(**无 `?.`/`Array.isArray`**,而 `:552` metrics 却有 `&&` 守卫);`CasesSection` 的 spotlight `:789` 用 `?.` 但 `restCases` `:828` `caseItem.metrics.slice` **未守卫** | 发布任一缺 `highlights`/`metrics` 的服务或案例 ⇒ 流量最高的首页 500 到 error.tsx | 两处改 `Array.isArray(x) ? x : []` 逐条守卫(与既有 `?.` 惯例一致) | `type-check` EXIT=0、`eslint --quiet` 0 error(改动文件);纯渲染守卫,行为对合法数据不变 |
|
||
|
||
### 5.2 子代理上报、待主线逐一复核(**不当作已确认/已修**)
|
||
| ID | 级别 | 位置 | 摘要 | 复核优先级/备注 |
|
||
|---|---|---|---|---|
|
||
| S-2 | P2 | `products/product-detail-content-v3.tsx:178,231` + `solutions/solution-detail-content-v3.tsx:121,168,223,278` | grid/`space-y` 挂在外层 div、唯一子为裸 `StaggerReveal` ⇒ `/products/[id]`、`/solutions/[id]` 桌面端塌成单列(**Q-5 同机制的新点位**) | **已复核+已修(6 处)**:同文件 :132 已是正确写法可佐证。安全修法——把网格/间距类移到 `StaggerReveal className`、外层 `<div>` 留作无类包裹(**不删闭合标签**,规避 Q-5 那次改动风险)。content-v3 三套件 25 例通过 + `type-check` EXIT=0 + `eslint --quiet` 0 error。需 AUTH-D3 视觉重基线确认像素 |
|
||
| S-6 | ~~P3 误报~~ | `cases/[slug]/page.tsx:27-34` | ~~`generateMetadata` 绕过 C-1 归一~~ | **主线核实为误报,未改**:`generateMetadata` 已用 `data.challenge?.substring` / `data.metrics?.map`,对**缺键**的保护与 `client.tsx` 的 `?? []` 同级,并非"绕过 C-1"。唯一残余是**类型错误值**(如 metrics 存成字符串),但那对 client 路径同样会崩,属通用运行时类型校验缺失(非元数据专属崩溃),不在本轮改 |
|
||
| S-3 | P2 | `about/page.tsx`、`contact/page.tsx`、`products/erp-upgrade/page.tsx` | 无 `export const revalidate`,且 `/api/cms/revalidate` 未映射这些模型 ⇒ 构建后发布新内容永不刷新(并含「`revalidateTag` 因 `data-server.ts:37` 用 React `cache()` 而非 `unstable_cache` 实为 no-op」的重断言) | 高但**涉渲染配置/缓存语义**,须逐条核 next 行为再定改否(可能引 ISR/构建变化) |
|
||
| S-4 | P2 | `home-content-v15.tsx:32-43`(用于 :510-516) | `FALLBACK_SERVICES` 兜底含 `40%`/`6个月` 等**无 basis 的数字**,N-30 类第三处残留,违背零编造 | 中:修法=删兜底 metrics(与 N-30 一致);属内容口径,宜确认后改 |
|
||
| S-5 | ~~P2 待复核~~ | `detail/detail-hero.tsx:171-184` + `micro-interactions.tsx:148-153` | `<a href><PressableButton/></a>`,而 PressableButton 渲 `<motion.button>` ⇒ button 嵌进 a(无效交互嵌套) | **已复核+已修**:根因在 `PressableButton` 的 href 分支本就产 `<a><button>`。改为有 href 时渲单个 `motion.a`(承载按钮样式),`detail-hero` 去掉外层 `<a>` 直接传 `href`;`onClick`/样式保留,导航语义不变。新增 `micro-interactions.test.tsx` 2 例真判别(旧码 `querySelector('button')` 非 null ⇒ RED;且发现 `detail.test.tsx` mock 掉了 PressableButton,故测真组件须单列)|
|
||
| S-7 | P3 | `services/[id]`/`solutions/[id]`/`products/[id]`/`news/[slug]` 的 page | `dynamic='force-dynamic'` 与 `generateStaticParams`/revalidate 冲突 ⇒ SSG 产物永不服务,注释与单测给假信心 | 中:涉渲染模式口径,须核 next16 行为再定 |
|
||
| S-8 | ~~P3 待复核~~ | `hero-particle-field.tsx:287-290` | `onVisibility` 未判相交态即 `start()` ⇒ 离屏 canvas rAF 空转 | **已复核+已修**:加 `intersecting` 跟踪(IO 回调置位),`onVisibility` 改为「可见且 `intersecting`」才启动,与 :271 契约对齐。凭现有 hero-particle-field 三套件/36 例(含 reduced-motion 降级、unmount 停环)无回归 + `type-check`/lint 绿;**未加专测**(jsdom 下 visibilitychange+IO 离屏组合难做判别性 RED,诚实标注)。首页活组件(home-content-v15) |
|
||
| S-9 | ~~P3 待复核~~ | `back-to-top.tsx:12-20`;`detail-trust-section.tsx:18-65` AnimatedCounter | 挂载未初始同步;裸 rAF 计数不查 `useReducedMotion` | **两处均已修**:back-to-top effect 内补 `handleScroll()` 初始同步;AnimatedCounter 在 `shouldReduceMotion` 时直接落终值不跑 rAF(`useReducedMotion` 自绘,因 `MotionConfig` 不管裸 rAF)。back-to-top by-construction(jsdom `scrollY` 只读难判别,未加专测,诚实标注);AnimatedCounter 由 detail.test 27 例 + 全量 `test:unit` 142/1760 + `type-check`/lint 绿佐证无回归 |
|
||
| S-10 | P3 | `admin/content/[modelCode]/page.tsx:210` 状态误标(review→「已归档」);`admin/roles/page.tsx:59-79` effect 依赖 `selectedRole` 覆未保存编辑;`admin/users/page.tsx:85` 搜索无防抖/序号守卫(`content/[modelCode]` 已修,此处未);`admin/media/page.tsx:38,131` 单 `fileInputRef` 挂两个 input ⇒ 顶部按钮重选同文件不触发 onChange;`admin/page.tsx:108`/`media/page.tsx:40` 吞错显示「空」非报错;`admin/login/page.tsx` label 无 htmlFor、密码切换钮无 aria-label | 后台多处 P3(错标/竞态/吞错/a11y 标签) | 中:逐条小修,部分与既有模式(防抖/seq)现成可搬 |
|
||
|
||
> 诚实边界:S-2…S-10 为 subagent 上报,**我尚未逐行独立复核**,不作「已确认/已修」主张;S-1 是我本轮亲自核实并修复的唯一项。子代理另排除了 `cta-section`(零 importer,其「30 分钟内响应」是 DEC-N30 的第 5 条 SLA 变体,仅附记)、Radix 包装件、`slider`(零消费者)等——同样待我复核后再采信。
|
||
|
||
## 6. 会话收尾与续工交接(2026-09-25,subagent 架构系统性 QA)
|
||
|
||
> 本文件(`qa-tracker.md`)即 objective 阶段3 要求的「问题跟踪机制」单一真源;§4 为第三轮(API+lib/组件)、§5 为第四轮(src/app 页面 + 余下组件)审查轴。
|
||
|
||
### 6.1 本会话已完成(均在工作树,**未提交**)
|
||
- **已核实并修复的缺陷(含判别性测试):Q-1、Q-2、Q-3、Q-4、Q-5、Q-7、Q-8、Q-9、Q-10a、Q-10b、Q-10c、S-1、S-2(6 点位)、S-5**;**S-8、S-9** 经检查修复(诚实标注:jsdom 无法做判别性 RED,仅 by-construction + 现有套件/类型门禁)。
|
||
- **主线证伪、未改的子代理断言**:`1,000+→1` 解析(实为 1000,正确)、C-1 归属笔误、S-6(metadata 已 `?.` 守卫,非绕过 C-1)。
|
||
- 自纠:一次 Edit 误删既有测试体(已复原);一次写了不能失败的假测试(已换成诚实版);一次 §4.4 表格被打断+留陈旧说明(已合并修正)。
|
||
|
||
### 6.2 本会话门禁证据(最终树,全部 EXIT=0,见 §4.4 表)
|
||
`test:unit` **142 套件/1760 例** · `test:coverage`(阈值未下调,新 lib 100%)· `test:integration:real`(23,`dev.db` 指纹未变)· 完整 `check:a11y`(contrast 44/44 · headings 10/10 · brand-token 0 违规)· `npm run build`+standalone 装配 · `npm run lighthouse`(9 URL×3)· `type-check` · `lint` 0 error/104 warning。
|
||
|
||
### 6.3 工作树披露(最高优先,勿静默留给下一个人)
|
||
`git status --porcelain` = **378 条**(54 未跟踪),HEAD 仍 `f543e47`。⇒ 两轮 + 本会话全部修复**只在工作树**,未提交/未 PR ⇒ 不可审、不可复现、不可 CI 验证。**这是 objective 阶段5「可交付」未达成的唯一实质原因。**
|
||
> **2026-09-28 第五轮后刷新**:`git status --porcelain` = **394 条**(55 未跟踪),HEAD 仍 `f543e47`。本轮净增 16 条改动/新文件(含新测试文件 `src/app/__tests__/cms-page-revalidate.test.ts`)。结论不变。
|
||
|
||
### 6.4 续工:待授权动作(逐项明文批准后才动;见 §2.3 / 周期报告 §5)
|
||
1. **AUTH-D1 入库/PR**(解锁验收):先 `git fetch origin dev` 取真值 → 裁定 `origin/dev`↔HEAD 双向分叉的重复提交对 → 按「表现层/后台编辑器/Sentry/测试装配/文档口径 + 本会话 Q/S」分笔提交 → 过 `scripts/check-pr-checklist.sh` → Gitea PR。**`git add` 按路径,禁 `-A`**:`.gitignore` 未挡 `prisma/dev.db-shm`/`-wal`,`-A` 会带入 SQLite 边车。
|
||
2. **AUTH-D4 写库 E2E**(11 例,真写 `dev.db`;⚠ `npm run test:e2e:prod` 不排除写库 spec,跑它即写库)。
|
||
3. **AUTH-D3 视觉基线**:Q-5/S-2/S-5 改了布局,产品/方案/案例详情页需人工核看后 `--update-snapshots`。
|
||
4. **AUTH-D2 seed**:使周期2 L-2 权限修复对数据生效。
|
||
|
||
### 6.5 续工:待复核/待决策的登记项(不阻断验收)
|
||
S-3(about/contact/erp-upgrade 无 revalidate + revalidateTag no-op,涉渲染配置)、S-7(force-dynamic vs generateStaticParams)、S-4(FALLBACK_SERVICES 无 basis 数字,宜按 N-30 删)、S-10(后台状态误标/搜索竞态/媒体双 input ref 冲突/登录表单 label 关联)、S-9 子项(detail-trust 裸 rAF 未查 reduced-motion)、以及周期1/2 遗留的 D-1…D-11 / DEC-* 决策项。
|
||
|
||
## 7. 第五轮审查轴(2026-09-27…28 会话,4 路并行 subagent + 主线逐条复核/修复)
|
||
|
||
承接 §6.5 的续工清单:S-3/S-4/S-7/S-10/Q-6/Q-10② + 未逐行覆盖的余下面(hooks、lib/cms 渲染器、seo/analytics 组件、marketing 残余页面)。主线未照抄任何子代理结论:逐条亲读源码复核后再修,修复均带判别性测试(RED→GREEN 实测)。
|
||
|
||
### 7.1 主线已复核并修复(工作树内,未提交)
|
||
| ID | 级别 | 缺陷(主线核实后口径) | 修复 | RED→GREEN 凭据 |
|
||
|---|---|---|---|---|
|
||
| **S-21**(=DEC-D7 落地) | **P2** | `renderField` 无 `richtext` 分支 ⇒ news 正文/legal 正文落 default 单行 `input[type=text]`,浏览器 value 消毒剥掉全部 `\n`,编辑一次即**静默损毁段落**(详情页 whitespace-pre-line 渲染) | richtext 走 `textarea`(rows=12);顺带收敛出作用域读写机制 | 新增 3 例读写保真测试;改前 `Expected TEXTAREA, Received INPUT` 判红 → 改后编辑器套件 **22/22** |
|
||
| **S-22** | P3 | array/object 子字段编辑器读写**顶层** `formData[field.name]`:编辑写不进数组,却把 `label/value` 垃圾顶层键混进保存载荷(假成功) | array 改整体 JSON 编辑(复用 JsonEditor);object 子字段以 `scope{parent,write}` 读写父对象并合并回写 | 同上:改前 `getByLabelText('指标')` 无 TEXTAREA / `作者` 值为空 判红;改后载荷断言 `metrics` 回写数组、无泄漏顶层键 |
|
||
| **Q-10②** | **P2** | `workflow.ts` 状态更新→读回→审计三条独立写:审计失败时**状态已落库但无审计**,调用方收 500 误以为流转未发生 | 交互式 `prisma.$transaction(async tx …)` 包住 CAS→读回→审计;通知保持在事务外(不回滚不上抛的既有语义不变) | 测试 mock 增加带快照回滚的 `$transaction` 桩(数组形态直接判红防退化);升级既有「审计失败 fatal」例为回滚断言:改前 storedItem 停在 published 判红 → 改后 28/28;**真库集成层 `test:integration:real` 23/23 EXIT=0(dev.db 指纹守卫通过)** |
|
||
| **S-3** | **P2** | `/about`、`/contact`、`/products/erp-upgrade` 无 `revalidate` 导出且 revalidate 路由无映射 ⇒ 构建后发布的 CMS 内容**永不刷新**(子代理实证:三页在 `prerender-manifest` 为 `static + initialRevalidateSeconds:false`)。附带断言 `revalidateTag` 全程 no-op **成立**(全站无 `unstable_cache`/`'use cache'`/fetch tags 消费点,React `cache()` 仅请求内去重) | 三页加 `export const revalidate = 3600`(与其余 14 页同档)+ 路由补 about-page/contact-page/product→`/products/erp-upgrade` 三条映射(推送即秒级失效,3600 为无推送兜底) | 新增 6 例(3 route 映射 + 3 页 revalidate 导出,新文件 `src/app/__tests__/cms-page-revalidate.test.ts`):改前 6 例全判红 → 改后 25/25;**产物级实证**:重建后 `dist/prerender-manifest.json` 三页 `initialRevalidateSeconds=3600` |
|
||
| **S-10 六条** | P3×6 | ①列表状态列把 review/archived 误标「已归档」②roles 的 `fetchRoles` 依赖 `selectedRole`,切角色 refetch 覆盖未保存编辑 ③users 搜索逐键打后端且无 seq 守卫(乱序响应覆盖新结果)④media 双 input 同挂一个 ref(重选同文件不触发 onChange)⑤admin 首页/media/zones 吞错显「空」(假绿)⑥login/zones/users 弹窗 label 无 htmlFor、密码显隐钮无可访问名 | ①四态 STATUS_LABELS 映射 ②`fetchRoles` 依赖清零 + `setSelectedRole(prev=>prev||…)` ③搬 SEARCH_DEBOUNCE_MS+fetchSeqRef 现成模式 ④全页单 input + 程序化 click ⑤error state + 横幅 ⑥补 id/htmlFor + `aria-label` 随状态切换(实施走 general-purpose subagent,主线逐 diff 复核 + `selectedRoleInfo` 等符号存在性核实) | 改动 8 文件 eslint --quiet 0 error;`content/[modelCode]/page.test.tsx`(防抖+乱序 2 例)通过;全量回归见 §7.4 |
|
||
| S-11/S-12/S-13/S-15 | P3 | zones 吞错显空 / notifications 翻页无 seq 守卫 / roles 矩阵 super_admin 全显「未授权」与「拥有所有权限」文案矛盾 / zones·users label 关联(S-10 同族扩散) | 与 S-10 一并修(error 横幅、fetchSeqRef、super_admin 整行按已授权呈现、htmlFor) | 同上 |
|
||
| **S-23** | P3 | `ui/animated-counter.tsx` `displayValue = start ? count : value`:未入视口直显**终值**,IO 异步首回调后跳回 0 再爬升(先给答案再数) | `displayValue = shouldReduceMotion ? value : count`(useCountUp 未启用期本就停在起点 0) | 新 1 例(IO 桩驱动):改前判红(旧码未相交即显 500)→ 改后 6/6;既有用例改 `startOnView={false}` 语义不变 |
|
||
| **S-24** | P3 | `PerformanceTracker` CLS 累加器在 observer 回调**内**:分批到达的 layout-shift 每批只报增量,GA 收到的永不是会话总 CLS | 累加器提到回调外,每批上报累计值 | 新 1 例双批次断言 [≈100, ≈150](旧码第二批报 50 ⇒ RED 由形状显然)→ 改后 17/17 |
|
||
| **Q-6** | P3 | `detail-product-value.tsx` MetricCard render 体内 setState+rAF:探针实证**每帧 setState 触发重渲染又 spawn 一条从 0 开始的动画链且从不 cancel**,假时钟下大字永远停在 '0';卸载后帧链继续排队 | 动画收入 `useEffect`,cleanup `cancelAnimationFrame`;NaN 回退同 effect | 新 1 例(faithful rAF 桩:cancel 真撤销排队):改前判红(旧实现卸载后 sched 继续增长)→ 改后 28/28。**注**:第一版测试命中 benefits 同名文本属假绿,已被探针推翻重写(诚实记录) |
|
||
|
||
### 7.2 主线复核后的定性变更(不改代码,只改台账口径)
|
||
| ID | 原登记 | 复核结论 |
|
||
|---|---|---|
|
||
| Q-6 路由定性 | 「`erp-upgrade-v3` 死路由 ⇒ 若非活码仅登记」 | **定性修正**:不是死路由,是**零入口孤儿路由**(不在 sitemap、src 内零内部链接,但静态可直达且有真实渲染)。缺陷本身属实已修(见 §7.1),影响面限直达 URL |
|
||
| S-4 | 「FALLBACK_SERVICES 无 basis 数字,N-30 类残留,宜删」 | **降为 DECISION(DEC-S4)**:子代理实证 fallback 与 CMS 数据走同一渲染 map,`:562` 对每张卡(含 fallback)渲染 `<MetricsBasisNote basis={weakestBasis(…)}>`,未声明 basis 一律保守落 `target` 角注 ⇒ **并非裸奔编造**,与 N-30(虚构佐证)不同类。残余问题:硬编码数字本身无出处 + fallback 触发条件(CMS service 缺失/首条无 subtitle)。是否删数字属内容口径,留业务裁定 |
|
||
| S-7 | 「force-dynamic + generateStaticParams 冲突 ⇒ SSG 产物永不服务」 | **CONFIRMED 但判为设计取舍**(子代理引 bundled docs `caching-without-cache-components.md:97` + `prerender-manifest` 实证四路由不在产物中;页内注释表明有意为之:硬 404 + 新内容首请求可见)。不自动改渲染模式,登记为 DECISION(承 DEC-N21b 同级) |
|
||
| S-14 | clipboard 无 catch | 随 S-10 批修(`.catch` + toast.error),SUSPECTED 定级不变 |
|
||
|
||
### 7.3 子代理上报、主线**证伪或不修**(防假发现入库)
|
||
- MetricCard `parseFloat('1,000+')→1` 类解析问题在本组件不成立(`[^0-9.]/` 保留数字与点),与 §4.3 Q-FP-1 同族。
|
||
- hooks 面(use-focus-trap/use-swipe-gesture/use-reduced-motion/useCountUp)、analytics 其余 tracker、structured-data、lib/cms 其余模块:子代理逐文件扫描后**无新缺陷上报**(含监听清理、capture 分工、XSS sink 检查均通过)。
|
||
- 死码新增:`CookieSettingsButton`(零 importer,连带「重开偏好面板不与已存同意同步」的潜在缺陷一并挂账)、`getInitialShowConsent` 三分支恒 false(死但无害)。
|
||
|
||
### 7.4 本轮收尾门禁(2026-09-28 最终树)
|
||
| 门禁 | 结果 |
|
||
|---|---|
|
||
| type-check | EXIT=0(一次 tsc 报错为 Q-6 测试 mock 类型断言丢失 `.mock`,已修后复跑绿) |
|
||
| lint | 0 error / 105 warning(与 AGENTS.md 口径一致 ⇒ 本轮零新增债) |
|
||
| test:coverage | EXIT=0,**143 套件 / 1772 例全通过**(较 §6.2 净增 1 套件 12 例,全为本轮判别测试) |
|
||
| test:integration:real | EXIT=0,4 套件 / 23 例,dev.db/data.db inode+size+mtime 守卫通过(workflow `$transaction` 改动在真 SQLite 无回归) |
|
||
| check:contrast / brand-token / headings | 44/44 · 0 违规 · 10/10 页,均 EXIT=0 |
|
||
| build + standalone 装配 | EXIT=0,ASSEMBLY_OK;`prerender-manifest` 实证三页 ISR=3600(S-3 产物级) |
|
||
| lighthouse | EXIT=0,9 URL × 3 次 = 27 run 全部断言通过(lhci autorun 自起 standalone :3200) |
|