- 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 产物装配与部署形态。
50 KiB
系统性 Review / 全面测试 / 修复 / 验收 — 第二周期
- 分支:
refactor/optimize-ui,HEADf543e47(本周期结束时工作树仍未提交,见 §6) - 上一轮基线:
docs/acceptance/2026-09-23-gates/final-verdict.md(本文件只记新增内容,不重复其已闭环条目) - 方法:subagent 并行 + 主线逐条源码复核(trust-but-verify)。子代理报告中的每一条断言均由主线用独立探针重测后才采信。
0. 结论(未完成项显式保留)
0.1 验收判定:未达"可判验收通过",阻断点唯一且明确
- 技术上已闭环:本周期新增审查轴上的 11 项 CONFIRMED 缺陷全部按 RED→GREEN 修复,可在最终树上复算的门禁全绿(类型 / 单元 1716 例 / lint 0 error / 真库集成 / 只读 E2E / 安全头 / a11y+perf / axe 四组 / Lighthouse / PR 模板结构),且无一项是靠放宽断言、加 retry、下调阈值或写基线拿到的。
- 验收不能判完成的理由不是代码质量,而是交付形态:344 条改动(含两轮全部修复)仍只存在于工作树,HEAD 未动 ⇒ 没有任何评审者、CI 或下一位开发者能复现本报告结论(§6);同时 10 项决策悬置(§8),其中 D-3/D-4/D-9/D-10 是本轮被授权边界挡住的可测项,不是"已测通过"。
- 因此本文件的主张仅限于:"已测的都真测了,未测的都点名了";"完成验收"待 D-1 入库 + 至少 D-3/D-4 两项授权后重判。
本周期新增一条前几轮从未审查过的代码轴:表现层 / 领域层(src/components/**、src/hooks/**、src/app/admin/content/**)。该轴产出 11 项 CONFIRMED 缺陷,其中 4 项具备数据损毁或整页崩溃后果,已全部按 RED→GREEN 修复。
| 门禁 | 命令 | 结果 |
|---|---|---|
| 类型检查 | npm run type-check |
EXIT=0(最终树复跑,两轮 0 error) |
| 单元 + 覆盖率 | npm run test:coverage |
EXIT=0,137 套件 / 1725 例全通过(同一周期内三次复跑:132/1677 → 136/1716 → 137/1725,末次在 L-1/L-2 修复之后);global 85.85 / 87.35 / 81.1 / 85.85,阈值 82 / 75 / 75 / 75 未下调 |
| Lint | npm run lint |
EXIT=0,0 error / 104 warning(no-console 仍为 55 ⇒ 本周期零新增债;105→104 的唯一归因见 §1.5,且本节更正了本报告早前一版的误归) |
| PR 模板结构门禁 | bash scripts/check-pr-checklist.sh |
EXIT=0:✅ PR 模板结构门禁通过:30 项子项,三节完整(修 §3.2 的假事实后复测,结构判定未受影响) |
| 构建 + 产物装配 | npm run build |
EXIT=0,dist/standalone/{server.js,dist/static,public} 三项齐备(N-9 前提成立) |
| 产物含 Sentry 运行时 | find dist/standalone -name '*instrumentation*' |
dist/standalone/dist/server/instrumentation.js 存在 |
| 真库集成层 | npm run test:integration:real |
4 套件 / 23 例全通过;teardown 守卫实测 prisma/dev.db、dev.db、data.db 的 inode+size+mtime 与运行前完全一致 ⇒ A-1 改动的 items GET 查询契约在真库上无回归,且未污染开发库 |
| 真库未被写入 | sqlite3 -readonly prisma/dev.db |
ContentItem=38、draft=0,与上轮收尾计数一致 |
| 视觉回归只读比对 | npm run test:visual:all -- --update-snapshots=none |
104 通过 / 21 失败 / EXIT=1,20 条失败实测为 A snapshot doesn't exist ⇒ 0 例像素不符,且跑前跑后基线仍为 85 M / 0 新建(详见 §3.4) |
| E2E(生产目标,写库用例已排除) | 7 个只读 spec × 4 project(E2E_TARGET=production) |
115 通过 / 1 失败 / EXIT=1;唯一失败 = mobile-user-journeys.spec.ts:360 UJ-10 firefox,隔离复跑 --retries=0 1 passed (10.4s) ⇒ 判为上轮 §8.3 的并发/墙钟族,非本周期回归;未加 retry、未放宽阈值。全程 prisma/dev.db 复核 draft=0 / items=38 未变 ⇒ 只读边界成立 |
| E2E(生产目标,安全头) | --grep '@security' × 4 project |
EXIT=0 |
| E2E(生产目标,a11y+perf 层) | `--grep '@accessibility | @performance'` × 4 project |
check:a11y |
contrast + headings + brand-token | EXIT=0 |
| 全站 axe 节点计数 | check:axe:routes + check:axe(standalone 产物,专用 :3100) |
EXIT=0 / PASSED=true,34 页,bgMismatch=0、contrastNodes=0、violationNodes=0 |
| Lighthouse | npm run lighthouse |
EXIT=0,9 URL × 3 = 27 次运行断言全通过 |
| 新增动效门禁 | check:motion / check:motion:test |
见 §3.5:自证伪通过,但真实树实测判红 23 处⇒ 故意未并入 check:a11y,登记为独立脚本待决策 |
1. 新增审查轴的发现与修复(表现层 / 领域层)
判据:CONFIRMED = 读过代码与调用点、并能说出具体触发条件。
1.1 后台内容编辑器(P1,含数据损毁面)
| ID | 位置 | 缺陷 | 触发条件 | 状态 |
|---|---|---|---|---|
| A-1 | src/app/admin/content/[modelCode]/[itemId]/page.tsx:306-308 |
以 getItems({page:1,pageSize:100}) + find(id) 定位条目,条目级无 not-found 分支(模型级在 :295-296 有) |
列表页 pageSize=10,第 101+ 条打不开 ⇒ 表单空白;填入标题后保存即把 {title, slug, data:{}} PUT 到真实记录 |
已修 |
| A-2 | 同文件 :251-260、:318-320 |
isInitialMountRef 被自动保存效果的挂载期首次执行消费掉,:319 的置位是死代码 |
仅"打开"一个既有条目即被标为未保存,约 3s 后发出多余 PUT ⇒ version 上涨并覆盖并发编辑者 |
已修 |
| A-3 | 同文件 :239-247 |
自动保存链无 in-flight / 优先级约束,旧 formData 快照可后于新快照落库 | 慢网络下丢编辑 | 已修 |
| A-4 | src/app/admin/content/[modelCode]/page.tsx:59-85 |
search 进 fetchItems deps,无防抖、无序号守卫 |
迟到响应覆盖新查询结果;每字符一发请求 | 已修 |
A-1 的修法经过取证:api/admin/items/** 不存在 by-id 端点(只有 [id]/workflow),adminApi 亦无 getItem ⇒ 给既有 GET 增加 id 过滤(route.ts:86,92),未新建路由、未发明 API。
主线对 A-1 报告的一处口径更正:子代理称"空白表单保存会清库",实测 validate() 对每条保存路径都要求非空标题 ⇒ 未填标题时不可达;填入标题后清库成立。修复仍针对根因(定位窗口 + 早退分支)。
1.2 案例详情页与动效层
| ID | 位置 | 缺陷 | 触发条件 | 状态 |
|---|---|---|---|---|
| C-1 | src/components/sections/case-detail-page.tsx:141,344,565 + src/app/(marketing)/cases/[slug]/page.tsx:31 |
metrics/timeline/services 直接 .slice/.map、challenge.substring,而 validate-content-data.ts:27-30,60 对缺失键刻意跳过不补默认 |
发布一条缺 timeline 的案例 ⇒ TypeError ⇒ 整页错误;缺 challenge ⇒ generateMetadata 崩 |
已修(在 payload 入口按既有 ?? [] 惯例归一,语义与 standalone/[id]/client.tsx:41-46 一致) |
| P-1 | src/hooks/use-swipe-gesture.tsx:106 + :192 |
触摸监听挂在无 className 的空 div(0×0)上,触摸无从冒泡 | 边缘滑动永不触发,而"尝试左右滑动切换页面"提示照常渲染 | 已修(挂 document,边缘阈值逻辑保留并被第三例测试证明中段触摸不误触);附带查明 erp-upgrade-v3 调用点自身零入向引用(死码),standalone/[id] 为活路径(src/app/layout.tsx:125 生成导航) |
| P-4 | src/app/layout.tsx:207 + cases/[slug]/client.tsx:53 + case-detail-page.tsx:618 |
/cases/[slug] 上 ScrollProgress 挂载 3 次,各带独立 scroll/resize 监听与逐帧 setState |
最重页面上 3 个叠层进度条 | 已修(保留 layout 一处,删页面级两处;grep 复核全仓仅剩 layout.tsx:207) |
| P-5 | src/components/ui/scroll-progress.tsx |
scaleX(shouldReduceMotion ? 0 : …) 把降级动效做成了宽度 0 |
prefers-reduced-motion 用户全站失去阅读进度指示 |
已修(实测现为 scaleX(progress/100) + transition: none) |
| D-1 | src/components/detail/detail-trust-section.tsx:32 |
value.replace(/[0-9.]/g,'') 把前导符号搬到末尾 |
种子数据确有 <2h、+113%、<2小时、<1天、<6个月(prisma/seeds/products.ts:76-255)⇒ 渲染成 113+% |
已修(存在真实触发数据,非假想) |
1.3 Sentry 此前为「声明了但从未接线」
@sentry/nextjs@10.67.0 在依赖里、三个 sentry.*.config.ts 在盘上,但无 instrumentation.ts、next.config.mjs 未包 withSentryConfig、src/** 零 import ⇒ 生产错误上报不生效(上轮 §4.1 记为"明确未做")。本周期接线,关键点是这版 Next(16.3.0) 默认 Turbopack,sentry.client.config.ts 根本不会被加载(SDK 自身告警 @sentry/nextjs/build/esm/config/webpack.js:207-213),故客户端初始化移入 instrumentation-client.ts 约定文件,并把旧配置置为空 stub 以防 --webpack 路径二次 init。
无 DSN 时的惰性有码路证据:if (SENTRY_DSN) 跳过 init、captureRouterTransitionStart 无 handler 时空转、captureRequestError 在无 client 时丢弃;CSP 在无 DSN 下逐字节不变(追加域与 worker-src 仅在 DSN 合法时非空,next.config.mjs:17-26)。
1.4 A-1 同类残留已闭合(第二波修复,实测确认为真缺陷)
复核确认 load() 的单个 try 下有三条失败分支(getModels 拒绝、models.find 未命中即 throw new Error('模型未找到')、getItems 拒绝)都只 setError 而 itemNotFound 仍为 false ⇒ 主 JSX 照样渲染 header 与底部两组「保存 / 发布 / 保存并返回」,一次点击即对从未加载成功的记录发 PUT/POST。自动保存侧已由 §1.1 的空快照守卫解除,但手工路径当时未覆盖。
修法为单点结构门而非五处散判:新增 canEdit,仅在 load() 成功填充后(新建=模型就绪 / 编辑=条目+快照就绪)的唯一位置置真;渲染侧 canEdit && 恰好包住那两组动作簇;发射侧 performSave(手工保存、自动保存、发布前保存的唯一收口)在 !canEdit 时直接返回 {ok:false},发布与工作流随之不可达;load() 顶部 setCanEdit(false) 覆盖 params 变更。错误展示(含「模型未找到」)保持不变。
RED 证据(4 例新用例实测失败文本):TestingLibraryElementError: Found multiple elements with the role "button" and name "保存";expect(received).toBeNull() / Received: <button …>发布</button>。
GREEN:npm run test:unit -- admin EXIT=0(14 套件 / 155 例,含 4 例新增)。
同形状兄弟页只记录未动刀:/admin/roles 安全(加载失败 ⇒ isEditable 假 ⇒ 保存隐藏);/admin/users 与 /admin/content/[modelCode] 在加载失败时仍渲染「新建」,但那是创建而非覆盖,属良性变体;/admin/media 失败时显示"暂无媒体文件"而上传仍可用,是误导性空态、无覆盖形状;/admin/zones、/admin/notifications 干净。
1.5 lint 口径的精确归因(105 → 104,本周期零新增债)
以 npx eslint . -f json 为唯一权威口径(458 文件):0 error / 104 warning,分项
no-console 55、no-explicit-any 25、react-hooks/set-state-in-effect 11、no-img-element 10、no-html-link-for-pages 1、no-sync-scripts 1、死抑制指令 0(原 1)。
对上轮记录的 105 条口径,差值全部可归因且均为改善,无一项是本周期引入:
| 分项 | 上轮 | 本轮(最终树 eslint -f json 实测) |
归因 |
|---|---|---|---|
react-hooks/set-state-in-effect |
12 | 11 | §1.1 A-2 删掉了"挂载期消费守卫"的那个效果 ⇒ 105→104 的唯一来源 |
no-console |
55 | 55 | §1.3 的 console.log 经 git show HEAD:sentry.client.config.ts:72 证明是原样搬迁,非新增 |
Unused eslint-disable directive |
1 | 1(no-await-in-loop,仍在) |
未变 |
no-explicit-any / no-img-element / no-sync-scripts / no-html-link-for-pages |
25 / 10 / 1 / 1 | 25 / 10 / 1 / 1 | 未变 |
⚠ 本报告早前一版把差值记成"两处各降 1"(含把死抑制误记为 1→0),已在最终树复测后更正为"仅
set-state-in-effect12→11 一处"。记此更正本身,是因为它正是本仓反复记录的判读纪律:分项相加必须等于总数、且计数须产自最终树——中途树(各代理仍在改文件)测得的 104 与最终树的 104 数值相同但成分不同,不可混用。
⇒ 文档中"实测 105 条"的口径已过期,须以本表为准。
1.6 第三条新审查轴:src/lib/** + Prisma 数据层(无 P0,2 项 P2)
| ID | 级别 | 位置 | 缺陷 | 用户可见后果 |
|---|---|---|---|---|
| L-1 | P2 | src/lib/admin-api.ts:81(加密分支 return JSON.parse(...) as T,无 res.ok 判定;:94 的守卫只管未加密分支)对 src/lib/api-crypto.ts:76,101-127 |
所有后台写路由都被 withCrypto 包裹 ⇒ 400/403/404/500 的响应体同样是加密的 {data:<enc>};客户端解密成功便按 T 返回。另注::86 的兜底在 :76 已 res.text() 消费过 body 之后再 res.json(),二次读取必然失败 ⇒ 连"降级通用错误"也拿不到 |
主线逐点复核后的精确后果(不用子代理的"崩溃或空"含混说法):① 被守卫拒绝的后台写操作静默显示"已保存";② src/app/admin/content/[modelCode]/page.tsx:83-84 写的是 data?.items || [],故 403 时 data 是 {error} 对象(真值)、.items 为 undefined ⇒ 渲染成**"0 条内容"且无任何报错**,不是崩溃。⚠ 与 §1.1 叠加:B-1/N-4/N-5 把发布/状态写收口到 workflow,而那些路径上的 403 今天正是被 L-1 吞掉的 ⇒ 修完 L-1 才真正拿到权限守卫的可观测性 |
| L-2 | P2 | prisma/seed.ts:223-262 对 src/lib/permissions.ts:26(精确相等)+ items/route.ts:208,285、stats:11、models:9、zones:14,38,108 |
种子 permissionMatrix 的 modelCode 过期:授予不存在的 team/legal(真实为 team-page/legal-page),且漏授 team-page/contact-page/legal-page/content-model/content-zone |
非 super_admin(含种子里的 e2e_editor/e2e_reviewer)对法务/团队/联系内容以及后台外壳(模型、专区、仪表盘)一律 fail-closed 拒绝;notifications.ts:135 为这些模型找不到审核人 ⇒ 条目可永久停在 review |
| L-3 | P3 | src/lib/client-ip.ts:14-15 |
头部信任无条件:生产在 nginx 后由边缘覆写故安全,但 node dist/standalone/server.js 直暴露时任何客户端可自报 X-Real-IP |
本地/CI 下 api/contact/route.ts:51 的限流可被完全绕过(N-25 的修复只覆盖 nginx 形态) |
| L-4 | P3 | src/lib/crypto.ts:36 ↔ crypto-server.ts:20 |
客户端密钥由会打进公开 bundle 的 NEXT_PUBLIC_ENCRYPTION_SECRET 派生,且须等于服务端 ENCRYPTION_SECRET |
传输"加密"只具混淆性,无机密性/完整性——不得当作安全控制(Bearer 仍承担鉴权,故非漏洞,但口径要写清) |
| L-5 | P3 | src/lib/media/image-processor.ts:75 + src/lib/media/media-service.ts:8-12,27 |
派生命名原先只有 image-${Date.now()},而原图走带随机后缀的 generateUniqueFileName |
同一毫秒内两次上传算出同名路径 ⇒ saveDerivative 互相覆盖 webp/avif/缩略图。已修(RED 实测:两次调用同为 public/uploads/image-1700000000000-thumbnail.webp;GREEN:src/lib/media 4 套件 / 59 例全通过) |
同轴复核为已真正闭环的项(附证明行):N-20 停用账号(permissions.ts:41-45 读 User.status,且确在 :52 的 super_admin 短路之前)、13 处 requirePermission 调用点全部走 if ('response' in permission) return、N-18 两条 draft 路由共用 isInternalRedirect、layout.tsx:180 的 dangerouslySetInnerHTML 是静态主题脚本而非 CMS 数据、草稿隔离(data-server.ts:39,50 过滤 published,无状态过滤的 getItemById:59 在 src/app/** 无调用者)、schema↔migration 一致(@@unique([modelCode,slug,locale]) 对齐)、.env* 与全部 .db 均未跟踪 ⇒ 无提交密钥。
1.7 L-1 / L-2 的修复落地与反漂移守卫
L-1 已修(src/lib/admin-api.ts:73-97):加密分支改为先解密取 payload,再按 res.ok 拒绝,错误消息优先取服务端的 error → message → 回落 请求失败 (${status});成功路径返回形状与全部 adminApi.* 签名未变。RED 实录:expect(adminApi.createItem(...)).rejects.toThrow('字段校验失败') ⇒ Received promise resolved instead of rejected / Resolved to value: {"message":"字段校验失败"}(3 失败 / 26 通过)。新增 4 例(加密 403、加密 400 带服务端消息、状态码回落、成功仍 resolve)。
顺带修掉主线另行指出的一处缺陷:原先 catch 里在 res.text() 已消费 body 之后再 res.json(),二次读取必然失败 ⇒ 降级路径拿不到任何明文 error;现改为直接按状态码报错并注明原因。
调用点全量审计:src/app/admin/** 下 media / roles / zones / dashboard(page.tsx:108) / notifications / users / 内容列表 / 编辑页每一个都已自带错误路径(try/catch + setError/toast.error 或 .catch)⇒ 让 L-1 抛错不会引入未处理拒绝,故本轮未改任何后台页面。
L-2 已修(源码侧,故意保持潜伏):prisma/seed.ts 的 contentModels 去掉幽灵码 team/legal、补入 team-page/legal-page/contact-page;content-model/content-zone 以新增 shellReadModels 条目只授 read 给非超管(仪表盘 getStats 与编辑器加载 getModels/getZones 需要读,而 zone 的 update/delete 刻意留给 super_admin,注释引用判据行 permissions.ts:24、models/route.ts:9、stats/route.ts:11、zones/route.ts:14,38,108),site-config/navigation 维持超管专属。
未跑 db:seed/db push ⇒ 现网 prisma/dev.db 内的旧权限行仍是漂移状态,修复尚未生效于数据(属 D-1/seed 授权范围,见 §8)。
反漂移守卫:新增 src/lib/seed-permissions.test.ts(4 例),解析种子数组与 CONTENT_TYPE_CONFIGS + 种子自身 modelConfigs 比对,以下任一情形即判红:幽灵码重现、真实模型失去授权、提取匹配为空(数组被改名/删除时抛错而非静默通过)、shell 码泄进全权限数组。
最终树复跑(在此二改之后):npm run type-check EXIT=0、npm run test:coverage EXIT=0 / 137 套件 / 1725 例、global 85.85 / 87.35 / 81.1 / 85.85(阈值未下调)、npm run lint EXIT=0 / 0 error / 104 warning。
2. 主线独立复核(不采信子代理自述的部分)
| 复核项 | 探针 | 结果 |
|---|---|---|
| 文档命令可执行性 | Node 脚本抽出 4 份文档里所有 npm run X 与 package.json 62 条脚本比对 |
documented-but-missing = 0 |
formsubmit.co 资源提示确为死资源 |
grep -rn formsubmit src/ |
唯一命中 src/app/api/contact/route.ts:90(Node 侧调用)⇒ 删除 dns-prefetch/preconnect 安全 |
| 视觉新选择器有真实 DOM 支撑 | grep data-testid src/components/sections/bento-grid.tsx |
bento-grid.tsx:48 确有 data-testid={testId},与 [data-testid^="bento-product-card-"] 对应 |
| 视觉断言未被放宽 | grep -n "count()|isVisible()|try {|catch {|\.skip|soft(" visual-regression.spec.ts |
命中 3 行全部是注释里的实测记录,无活守卫/软断言/skip |
| lint 计数变化归因 | git show HEAD:sentry.client.config.ts |
console.log 位于 HEAD:72,属原样搬迁而非新增;分项 no-console 55 / no-explicit-any 25 / no-img-element 10 / no-sync-scripts 1 / no-html-link-for-pages 1 与上轮口径一致 |
| 产物装配(N-9 前提) | test -e 三项 |
全部 OK |
| 真库完整性 | sqlite3 -readonly |
38 / 0,未变 |
本周期自建的 API 面是否扩大攻击面(自查 A-1 新增的 id 过滤) |
读 src/app/api/admin/items/route.ts:79-93 |
未扩大授权边界:?id= 且省略 modelCode 时 where={id},判权走 'content-item' 幻影码——但改动前省略 modelCode 就已经返回跨全部模型的分页行,我的改动只是把"翻页枚举"变成"按 id 精确取",鉴权关口同一;且编辑器调用始终带 modelCode({modelCode, id, page:1, pageSize:1})。⇒ 结论:本周期变更中立,但N-21 因此从 P2 提到应优先处理(它同时是这条读取路径的唯一闸口) |
3. 测试装配修复(两波)
3.1 N-27 E2E 零断言(第二波收尾)
子代理改用 TypeScript AST 扫描(提取 test 块 + 沿 if/try/catch 祖先判定 expect 可达性),并先用合成探针自证扫描器再采信其计数——这是对上轮"grep 计数"口径的方法升级。排除 visual-regression.spec.ts。
- 计数结论:上一位代理的收尾已把零断言块降到 0;本轮再修掉两处扫描器看不见的真实缺陷。
e2e/uj-08-service-journey.spec.ts:131-141:isVisible().catch(() => false)吞掉失败 ⇒ 改为执行路径上的硬断言await expect(caseLink).toBeVisible()(uj-08:137)。e2e/cms-workflow.spec.ts:345,351:test 3「多角色权限分离」补齐块级afterApprove.status === 'published'与落地 URL 断言(判据取自api/admin/items/[id]/workflow/route.ts、api/cms/revalidate/route.ts、lib/api-response.ts:69、lib/cms/workflow.ts:62)。该 spec 真写库,本周期未执行,仍需 §6 的写库授权。- 剩余 9 处
if (await …)经逐个读源码判为受卫动作(cookie 横幅关闭、面包屑缺失时的 else-goto 兜底、p1:589循环带无条件下限断言),其后均跟无条件断言 ⇒ 不改。 - 实跑:
uj-08-service-journey.spec.ts(只读、不点提交)跨 chromium / chromium-mobile / firefox / webkit 4 通过 / 0 失败;eslint两文件与npm run type-check均 EXIT=0;:3000 起停前后均确认无残留监听。
3.2 门禁工件自身含假事实(本轮新发现,已修)
上轮 N-31/N-33 把 README / CLAUDE / docs-testing / quality-gates 的数值口径做了机械交叉核对,但漏掉了 PR 门禁工件本身 .gitea/PULL_REQUEST_TEMPLATE.md。其「质量门禁」节原写:
npm run test:coverage通过jest.config.js的coverageThreshold(global:branches 30% / functions 25% / lines 32% / statements 30%)
而真源 config/test/jest.config.js:32-38 实为 branches 82 / functions 75 / lines 75 / statements 75(根 jest.config.js 只是 module.exports = require('./config/test/jest.config.js') 的转发)。⇒ 这份模板会���后来者把阈值当成 30%,恰是 AGENTS.md §5 反复警示的"假绿口径",且它出现在验收关口自身的清单上,比文档写错更值得记。已按"先指真源、再附数值"的写法改正,并复核 bash scripts/check-pr-checklist.sh(结构校验:三节完整 + checklist 子项 ≥ 20)仍 EXIT=0,即修正未破坏门禁结构判定。
3.3 N-15 视觉回归装配(上一轮遗留 21 例判红)
修法只动装配(选择器、目标元素),未动参照、未放宽阈值:
- 卡片例:
[class*="card"]在三种视口匹配数恒为 0 ⇒ 改为[data-testid^="bento-product-card-"](实测 7 个匹配,首个bento-product-card-erp可见)。 - 输入框例:
input[type="text"]的.first()命中反垃圾蜜罐(contact-content-v3.tsx:431-439:display:none+tabindex=-1+aria-hidden,isVisible()=false)⇒ 改为form input:visible;蜜罐覆盖没有删除,改为 6 条属性断言 +toBeHidden()显式断言其存在。 - 窄视口按钮例:判为装配缺陷而非产品缺陷——390px 下 4 个匹配仅 1 个可见,
.first()落在nav.hidden md:flex的桌面导航按钮上;真实目标为header.tsx:184-196的data-testid="mobile-menu-button"(44×44 @ (338,10))。
错误类别变化:5 element(s) not found + 6 Received: hidden + 10 缺参照 ⇒ 0 + 0 + 20 缺参照 + 1 参照尺寸不符。即选择器类缺陷清零,剩余全部卡在"写参照需授权"(上轮 §5 第 3 项)。
3.4 最终树上的视觉只读比对实测(本周期修复无像素外溢)
§1 的全部代码修复落地后重跑 npm run test:visual:all -- --update-snapshots=none:104 通过 / 21 失败 / EXIT=1(5.9 分钟),且失败类别实测为 20 条 Error: A snapshot doesn't exist(另 1 条为上轮已记的参照尺寸不符)⇒ 0 例像素不符。
三重守卫同时成立:
git status --porcelain -uall -- e2e/visual-snapshots跑前跑后均为 85 M / 0 个??⇒ 只读比对确实没写任何参照(缺省调用会让 Playwright 静默创建缺失基线,这条必须验而不是假设);- 失败集合与修复前逐类别同构(21 例同因)⇒ C-1/P-1/P-4/P-5/D-1/N-14 六项改动未波及任何有参照的页面;
- 判读边界见 §4:本树被参照覆盖的页面只有 21 个检查点名,"104 通过"仅约束这些点,不构成对 §1.2 那几项的验收凭据。
⇒ 结合 §0 表,视觉门禁在本周期的口径是:已覆盖面无回归(实测);未闭环项是装配之外的授权项(20 例缺参照需 §6 写参照授权;/products/erp-upgrade、/about/brand、/cases/[slug] 三路由从始至终没有基线)。
3.5 新增门禁:动效约束此前无人机械校验(N-29 的落地)
CONTEXT.md §动效设计四原则为权威口径(直读原文,未采用提示里的转述)::76 入场 180–280ms、hover 150ms、反馈 100ms;:77 默认缓动统一 ease-ink = cubic-bezier(0.22,1,0.36,1);:82 禁止超过 700ms 的入场动效;:78 stagger 步进。新脚本 scripts/utils/check-motion-constraints.ts 落 4 条规则,全部可达(无 warn 分支),退出码 0/1/2(2 = 扫描集为空 / 目标缺失 / 解析到 0 个令牌 —— 直接针对本仓 N-22/N-24/A-6 的"恒不成立断言"族)。
自证伪证据(双向对照,非"跑一遍看看绿"):植入违规 fixture ⇒ EXIT=1(一次命中 R2/R3/R4 共 6 处);移除 ⇒ EXIT=0 并打印分母;CSS 令牌 fixture ⇒ token-band + entrance-cap 双双命中;空扫描 ⇒ EXIT=2;配套 npm run check:motion:test 19 例(逐规则正/负对照 + 一条"触发集 == 声明规则集"的元测试)。
真实树实测判红:npm run check:motion ⇒ EXIT=1,23 处 / 13 文件(分母:262 源文件 · 46 条 CSS 声明 · 290 个时长字面量 · 66 个贝塞尔字面量):
| 规则 | 处数 | 明细 |
|---|---|---|
| R1 令牌档位 | 0 | instant/fast/normal 令牌本身合规 |
| R2 入场 ≤700ms | 1 | globals.css:268 --transition-gentle: 1000ms |
| R3 时长须走令牌 | 2 | globals.css:1457 裸 0.6s、design-system.ts:76 裸 0.3s |
| R4 缓动曲线白名单 | 20 | [0.16,1,0.3,1] × 17、[0.25,1,0.5,1] × 3 |
一处必须如实记录的口径张力:R4 的白名单是从 CSS 里真实解析 --ease-* 令牌得来(不硬编码),而 CONTEXT.md 的令牌层刻意保留 5 条其它曲线、且 --ease-out 只是 ease-ink 的别名(globals.css:282)⇒ "所有缓动一律 ink"的粗读法会与 CONTEXT.md 自身冲突。因此这 20 处究竟是漂移还是已接受的例外,属设计决策而非机械缺陷。据此:门禁故意不并入 check:a11y(否则永久判红),先以独立脚本 + 真实红灯存在,等 §6 决策后再定"改代码"还是"改口径"。
3.6 单元测试可信度专项(N-27 的同类审查首次用到 jest 层)
结论与 Playwright 层相反:约 1712 例中仅 3 例(≈0.2%)结构性装饰,其余可失败。扫描器为 TS Compiler API AST,先用合成正/负对照夹具自证 16/16 再采信计数(过程中还抓出扫描器自身两个缺陷:漏认 toBeInstanceOf、把"对字面量数组 for-of"误判为空守卫)。grep 交叉核对 1693 站点 vs AST 1696 块(+8 it.each = 1712 运行时)⇒ 无系统性漏采。
| 类别 | 原始命中 | 真实 | 位置 |
|---|---|---|---|
| 零断言 | 3 | 3 | src/hooks/use-count-up.test.ts:49、src/lib/analytics.test.ts:158、:241 |
| 空守卫 / 恒真 / 自我证明 / 被 mock 掉被测物 / 吞异常 / skip | 3 标记 | 0 | 唯一 if 命中是对非空导入常量 METRIC_BASES 的 for-of(假阳);GlobalErrorTracker.test.tsx:61 的 .catch(noop) 只压 Node 未处理拒绝告警、不吞断言 |
最危险的一处是 use-count-up.test.ts:49 —— 用例标题承诺"最终到达 end 值",主体只 advanceFrames([0..600]) 而不断言任何事,一个"停在 99"的回归会静默通过。本周期已按 TDD 补齐三例的真实判据(expect(result.current).toBe(100)、expect(window.gtag).toBeUndefined() + not.toThrow()),复跑 2 套件 / 40 例全通过 ⇒ 被测实现本就正确,缺的只是判据;同时说明覆盖率 85.83% 并非由空测试撑起,config/test/jest.config.js 的阈值没有被装饰用例喂出来(src/app/** 根本不在 collectCoverageFrom 内)。
3.7 N-24① 收口:axe 门禁现在逐页断言状态码(双向对照实测)
上轮把"axe/标题门禁的 per-route 状态断言"留在决策位(§8.2),本轮查明它不需要决策、只需要正确的期望集。结构缺陷:axe-node-count.mjs 的聚合保险丝是 push(s.okResponses > 0, …),只有全部响应都非 2xx 才炸;而 404/500 页几乎不产生可比对节点 ⇒ violationNodes 仍为 0,s.pages === urls.length 的分母照加 ⇒ 单条路由退化成 404 会被当"干净"计过。
不能简单写"全部必须 2xx":实测 136 行 = 132×200 + 4×404,而这 4 条 404 全部来自 /_not-found——它就是本站 404 页,返回 404 属正确行为(证据 docs/acceptance/2026-09-21-axe/axe-evidence.json)。故实现为期望集感知:EXPECTED_404 = ['/_not-found'],其余路由必须 2xx/3xx,逐页比对并累计 unexpectedStatus;offender 明细直接写进 failures 文本而非只打 stdout(本仓曾因 line reporter 不带 stdout 而在并发失败时"查不到规则名",同一陷阱不能二次踩)。
双向对照(都在装配后的 standalone 产物 :3100 上跑,非推断):
- 正控制(现有 34 路由清单)⇒ POSITIVE_EXIT=0,即收紧后不误伤现状;
- 负控制(同一清单 + 一条故意不存在的
/__axe-missing-route,35 路由)⇒ NEGATIVE_EXIT=1,且逐条点名 4 个组合:chromium|firefox × light|dark /__axe-missing-route status=404 expected=2xx/3xx。
⇒ 这条判据现在可失败且已证明会失败,属于本仓 §「门禁断言有效性口径」的正例样本。
3.8 变异测试首次拿到实测凭据(A-13/A-14 的长期缺口)
上轮把变异测试列为"须第 4 项授权后再跑",理由是 --inPlace 会改写工作树。本轮核实该前提在默认路径下不成立:stryker.config.json 没有 inPlace 键 ⇒ 默认沙箱拷贝到 .stryker-tmp,工作树只读;test:mutation:quick 亦不带该旗标。据此在最终树上跑通(MUTATION2=0):
src/lib/utils.ts变异分数 = 97.06(≥break阈值 50,且高于high阈值 80),34 个 mutant;- 存活样本已定位并如实记录,例如
[Survived] ConditionalExpression:if (timeout) {clearTimeout(timeout)}→if (true) {...}——即"没有 timeout 也要 clearTimeout"这条分支未被测试约束(行为等价类,属可接受的等价变异,但需知道它活着)。
⇒ 口径:这只是 utils.ts 一个目标的凭据,不是全仓变异分数(全量 mutate 覆盖 src/lib/**+src/hooks/**+七个组件目录,成本与当前磁盘余量不允许)。引用时不得把它当作全站分数。
3.9 磁盘耗尽(ENOSPC)事件与恢复证据(环境风险,须记入运行手册)
变异 + 视觉 + 全站 axe + Lighthouse 连跑期间,数据卷冲到 100% / 可用 <1 Gi,直接造成两件事:① lib-prisma-review 子代理以 ENOSPC 失败(非判断错误),② 首轮 test:mutation:quick 与负控制 axe 同时中断。处置与验证:
-
归因:
.stryker-tmp实测 1.7 G(沙箱拷贝),叠加本仓dist/2.5 G;机器级还有~/Library/Caches7.4 G(非本任务产物,未动)。 -
恢复:仅删除本会话自建且已被
.gitignore:280覆盖、git ls-files为空的.stryker-tmp(两次),可用回到 8.1 G;随后再次冲到 3.1 G,故已把"变异测试须单独排期并预留 ≥2 G"记为运行约束。 -
完整性复核(ENOSPC 后必做,否则可能留下半截文件):
git status --porcelain条数与事件前完全一致(358)、node --check通过、npm run type-checkEXIT=0 ⇒ 无损坏;被中断的两项(负控制、变异)均已重跑并出数(见 §3.7/§3.8)。 -
顺带查实:仓库存在三个 SQLite 文件,
DATABASE_URL为绝对路径指向prisma/dev.db(应用实际打开的就是它),根dev.db(Jul 6)与data.db(0 B,Apr 10)是陈旧未跟踪漂移——本周期未删(非本会话创建,删除须授权)。 -
自纠:本周期我自己造成的一次证据损毁(不静默留给下个会话)。
axe-node-count.mjs的证据输出走OUT = repoPath(process.env.OUT || 'docs/acceptance/2026-09-21-axe'),即所有运行(含 CI,Jenkinsfile:390-392只传SITEMAP不传OUT)共用同一个固定目录,且该目录全未跟踪(git status为?? docs/acceptance/2026-09-21-axe/,git ls-files为空)。后果两条:① 本轮波次的check:axe覆盖了上一周期同路径下的axe-evidence.json,无法从 git 恢复;② 更糟的是,我为 §3.7 跑的负控制(35 路由、含故意不存在的/__axe-missing-route、passed:false)随后又把默认路径覆盖成"站点有一条坏路由"的假象证据——实测该文件现内容routeCount: 35 / has bogus route: true / passed: false,任何按日期读到它的人都会去追一条并不存在的路由。 处置:把正/负两次证据分别写入docs/acceptance/2026-09-23-cycle2/axe-positive与.../axe-negative-control(互不覆盖),并用正控制重跑默认路径,使留在旧位置的文件重新等于真实站点状态。已完成的复原(实测):NEG_EXIT=1 / POS_EXIT=0 / DEFAULT_RERUN_EXIT=0;三份工件核对为 dated-negativeroutes=35 passed=false bogus=true(保留作 §3.7 的负控制凭据)、dated-positiveroutes=34 passed=true bogus=false、默认路径routes=34 passed=true bogus=false @ 18:24:54Z⇒ 假象证据已消除。结构性建议(列入 D-11):把默认OUT改为按运行日期分目录,否则"每次验收都在覆盖上一次验收的证据"这一形态会在 CI 上持续复现;本周期未擅自改默认值,因为它同时是 Jenkins 的落盘位置,属共享流水线行为变更。
4. 本周期 UI 修复的视觉覆盖真相(对"跑一遍视觉就够"的否定)
基线清单实测:e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/ 下只有 21 个不同检查点名(about-fullpage、contact-fullpage、home-fullpage、product-erp-fullpage、services-fullpage、solutions-fullpage、solution-manufacturing-fullpage、service-software-fullpage、methodology-fullpage、news-fullpage、news-detail-fullpage、team-fullpage、footer-section、founder-quote-section、header-navigation、theme-dark-main、theme-light-main、button-default/focus/hover、products-fullpage)。按此核对本周期改动:
| 改动 | 会改像素的落点 | 该落点是否有基线 | 结论 |
|---|---|---|---|
P-4 删两个多余 ScrollProgress |
/cases/[slug] |
无(清单里没有任何 cases-*) |
视觉回归测不到 |
| C-1 案例页缺键崩溃 | /cases/[slug] |
无 | 视觉回归测不到(其正确性凭据在单测) |
| D-1 指标前导符号排布 | products/standalone/[id](erp-upgrade-v3 为死码) |
无(product-erp-fullpage 是另一路由) |
视觉回归测不到 |
P-1 滑动监听挂到 document |
交互行为,非静态像素 | 交互不在视觉用例范围 | 视觉回归测不到 |
P-5 scaleX 降级动效 |
全站 layout 进度条 | 有 fullpage 基线,但基线不在 reduced-motion 仿真下拍摄 |
现状口径下不会变,亦即该修复同样未被视觉锁定 |
N-14 删 formsubmit 资源提示 |
<head> 标签,无像素 |
— | 不影响基线 |
⇒ 判读纪律:本周期视觉只读比对即使 104/104 全绿,也只说明"未波及被基线覆盖的页面",不能当作 C-1/P-1/P-4/D-1 的验收凭据;那四项的凭据只有各自新增的单元用例(§1.2 的 RED→GREEN)。把这句话写进报告,是为了防止下一轮用聚合绿灯替局部证据背书。
5. 遗留与待决
5.1 对上一轮结论的一处更正(本周期实测,属在线合规缺口而非潜伏项)
上轮 §8.2 第 2 条把 /products/erp-upgrade 的 99.2% / 40%+ / 从5天到1天 记为"无 basis 结构约束"的数字口径问题,且 3ed3afd 的提交标题自称"删除详情页虚构佐证"。复核发现这些数字在一个活的组件里,不在死码里:
src/app/(marketing)/products/erp-upgrade/erp-upgrade-content-v2.tsx:6340%+、:6999.2%、:77从5天到1天,财务结账效率大幅提升- 该文件由
src/app/(marketing)/products/erp-upgrade/page.tsx:4以import ErpUpgradeContentV2 from './erp-upgrade-content-v2'引入 ⇒ 路由/products/erp-upgrade渲染这些数字 - 同一路由又正是 N-15 中"基线从未存在"的两条之一 ⇒ 内容口径与视觉验证同时无覆盖,这是本周期最值得单独立项的一条交叉风险
因此"删掉即了结"的判断对该页不成立:要么给出真实来源并补 basis,要么按 AGENTS.md §3「零编造」把这些数字降级为明确的目标口径标注。属业务决策,本周期未替业务作答。
5.2 SLA 互斥口径(复核后仍为多条并存,已核实的三处)
prisma/seed.ts:836 与 :840 「工作日 2 小时内快速响应」 / prisma/seeds/services.ts:129 「平均响应时间 <4小时 · 工作日技术支持」 / src/components/sections/case-detail-page.tsx:595 「免费咨询,48小时内给出初步方案建议」。上轮另记的 detail-cta-section.tsx:84 「<2小时 · 7×24」本轮按 小时 + 响应/回复/方案/支持 组合检索未命中,故不再断言其仍在——需决策的是上述三条互斥口径留哪一个。
5.3 其余待决
- P-2(需产品/设计决策,本周期未动刀):
content-types.ts:74-78把 news 正文字段声明为richtext,而后台renderField(…/[itemId]/page.tsx:450-631)无 richtext 分支 ⇒ 落default:单行<input type="text">,读者侧按纯文本渲染。上轮 N-3 已把文档改成"现实描述",故这是能力缺口而非文档谎言:要么补 TipTap 分支,要么改字段声明。 - A-1 同类残留:模型 not-found 路径(
:295-296)仍会渲染带保存按钮的空白表单——本周期已另派修复,结果见 §7。 - N-21 幻影 modelCode:本轮以源码证据推翻上轮定性(上轮记为"省略
modelCode时以'content-item'的 read 权限放行全部模型")。实测链路:route.ts:79造出'content-item'⇒requirePermission(permissions.ts:80-93)⇒checkUserPermission⇒hasPermission(permissions.ts:24-27)要求p.modelCode === modelCode精确相等;而'content-item'在src/与prisma/中只出现在这一行,种子里真实 modelCode 为hero-banner / stat-item / service / solution / case-study / news(prisma/seed.ts:69-107)⇒ 任何非super_admin角色都匹配不到权限行 ⇒ 返回 403,即 fail-closed。所以它不是越权读取,上轮的 P2 安全定性不成立。真实残留风险降为两点:① 该字符串一旦将来被补进 Permission 种子,就会立刻变成跨模型读取的闸口(潜伏陷阱,非现网缺陷);② 唯一省略modelCode的客户端调用点src/lib/admin-api.ts:207 getStats()在src/内无调用者(同名的src/lib/cms/data-server.ts:133是另一个服务端函数),故亦无现实功能故障。⇒ 建议修法仍是"缺modelCode即 400",但优先级按潜伏陷阱处理,不作为本周期阻断项。 - N-23 安全头门禁仍默认打线上:
scripts/utils/check-security-headers.ts:34无--url时返回https://novalon.cn,而package.json:56的test:security:headers正是不带--url调用它,并被test:all(:48)引用 ⇒ 该门禁验的是线上而非本树产物。 check:a11y构成:package.json:52仍为check:contrast+check:headings+check:brand-token三项(动效门禁是否并入见 §7)。- L-3 未修,须与部署协同(本周期刻意不动):把
src/lib/client-ip.ts的头部信任收进TRUST_PROXY开关看似 1 行,但生产由 nginx 注入X-Real-IP,若部署侧没有先设置该开关,限流键会全部塌成127.0.0.1⇒ 真实访客共享同一个限流桶、互相封禁。这是"改一行换来一次线上 DoS"的典型,须与Dockerfile/nginx/compose 的部署约定一起改,属决策项(并入 D 清单)。 - L-4 记为口径而非漏洞:
crypto.ts:36与crypto-server.ts:20的对称密钥由打进公开 bundle 的NEXT_PUBLIC_ENCRYPTION_SECRET派生 ⇒ 该"传输加密"只具混淆性,无机密性与完整性。鉴权仍由Bearer承担故不构成越权,但不得把它当安全控制引用;文档若称"请求体已加密"须限定语义。 - 其余未修纵深项:服务端
version乐观并发(需改 API 契约,界在 §1.1 的 A-2/A-3 之外)、上轮 §4.2 的审核流缺reject/archive入口(条目提交进review后,审批者没有驳回路径)。上轮 N-24①(axe 逐页状态断言)本周期已收口,故不再列于此(见 §3.7)。 - 扩充后的死码清单(删除需授权,本周期未删):上轮 §4.4 的 7 项不完整,另零入向引用者含
sections/hero-section-v2、sections/insight-card、sections/industry-grid、sections/product-card+ui/product-card+detail/product-card、detail/solution-value、detail/service-value、layout/breadcrumb、ui/loading-state、detail/micro-interactions(TiltCard/HoverLink),以及use-swipe-gesture.tsx的PullToRefresh(其handleTouchEnd里await onRefresh()无 catch ⇒ 潜在未处理拒绝)。 @sentry/tracing@7.120.4:全仓零引用、无包经它解析,README 自述已废弃 ⇒ 建议移除,但依赖删除须单独批准,本周期未动package.json依赖。
6. 工作树披露(与上轮同一最高优先事项,仍未解除)
本周期全部修复同样只存在于工作树,git status --porcelain 约 344 条,HEAD 仍是分支起点 f543e47。即上一轮 §7 的"未固化交付"结论对本周期继续成立且累积:两轮的可审交付都无法从任何 commit 复现。提交/推送/PR 仍待授权(上轮 §5 第 4 项)。
6.1 授权第 4 项的真实代价(本轮实测,须先于批准读取)
以本地 ref 实测(未执行 git fetch,因改写历史与共享状态须先授权):
| 探针 | 结果 |
|---|---|
git merge-base --is-ancestor origin/dev HEAD |
NOT ancestor |
git rev-list --count origin/dev..HEAD |
30(本分支独有) |
git rev-list --count HEAD..origin/dev |
31(origin/dev 独有) |
git log -1 %h %s 两端 |
HEAD = f543e47,origin/dev = 040951c,标题完全相同(test(visual): 以规范模式重生成全站视觉基线并补齐 firefox 缺口)而 hash 不同 |
本地 dev ref |
040951c(与 origin/dev 同值) |
⇒ 三点必须让批准者知道,而不是等 rebase 时才发现:
- 这是双向分叉(30 ↔ 31),不是"我们落后于 dev"的单向情形;AGENTS.md §5.1 的
git fetch origin dev && git rebase origin/dev在这里不是平凡的快进整理,会出现同标题异 hash 的重复提交对撞,需逐笔判定取哪一份。 - 同标题异 hash 说明某一侧历史被重写过(amend / force-push / 另一工作树重提)。本地
origin/devref 可能已陈旧,真实现状只有git fetch后才可知——这正是本仓反复记录的"不得用陈旧本地 ref 推断历史事实"。 - 344 条改动一次入库体积过大(上轮已建议按 N 系列分批);结合本条分叉,建议的入库顺序是:先
git fetch origin dev取得真值并出分叉清单 → 由交付决策人裁定重复提交对如何取舍 → 再按"表现层修复 / 后台编辑器 / Sentry 接线 / 测试装配 / 文档口径"分 5 笔提交 → 最后创建 PR。本轮未擅自启动其中任何一步。
7. 收尾回填(本周期已完成)
- §0 全部可自主执行的门禁都在最终树上出数并回填(中途树与最终树的 104/105 区分见 §1.5)
- 视觉只读比对:104 / 21、0 像素不符、未写任何参照(§3.4)
- 生产目标只读 E2E:115 / 1,唯一失败隔离复跑通过,判并发族(§0)
@security、@accessibility|@performance、check:a11y、全站 axe 节点计数、Lighthouse 全 EXIT=0- 真库集成层 23 例 +
prisma/dev.db完整性复核(38 / draft 0) - 动效门禁落地并自证伪(§3.5);单元测试可信度专项(§3.6)
- 文档同步:
quality-gates.md§1 与CONTEXT.md的 lint 口径改为 104 实测;CLAUDE.md补「Error Monitoring (Sentry)」与动效门禁两条架构级说明(此前 Sentry 接线在 CLAUDE.md 中零记载);PR 模板的假阈值已改指唯一真源(§3.2)
8. 交付决策清单(须由交付决策人逐项裁定;本轮未擅自代替裁定)
| # | 事项 | 本轮状态 | 裁定 what |
|---|---|---|---|
| D-1 | 入库:344 条改动、两轮修复全部只在工作树,HEAD 仍 f543e47;且 origin/dev 与 HEAD 双向分叉 30↔31、两端同标题异 hash(§6.1) |
未 fetch、未 commit、未 push | 是否授权按"表现层修复 / 后台编辑器 / Sentry 接线 / 测试装配 / 文档口径"分 5 笔入库;分叉的重复提交对如何取舍 |
| D-2 | 动效门禁 23 处判红(R4 曲线 20 + R3 裸时长 2 + R2 --transition-gentle:1000ms 1,§3.5) |
门禁已存在但故意不并入 check:a11y |
是"改代码收敛到 ink/令牌"还是"承认这些曲线为设计例外、把它们补进 --ease-* 白名单" |
| D-3 | 视觉参照缺口:20 例缺参照恒红 + /products/erp-upgrade、/about/brand、/cases/[slug] 三路由从无任何基线(§3.3、§4) |
只读比对未写参照 | 是否授权 --update-snapshots 写参照(会追认现状,A-9 症结),以及是否为上述三路由补基线 |
| D-4 | 写库 E2E 11 例未执行(cms-workflow.spec.ts、user-journey.spec.ts 各 9 处写调用;本轮已把它们从生产目标集合中显式剔除) |
只读边界经 DB 计数实证 | 是否授权跑完整 npm run test / 完整 test:e2e:prod(真写 prisma/dev.db) |
| D-5 | /products/erp-upgrade 在线数字口径:99.2% / 40%+ / 从5天到1天 位于活组件 erp-upgrade-content-v2.tsx:63,69,77(被 page.tsx:4 引用),且该路由同时无视觉基线(§5.1) |
未替业务作答 | 给真实来源并补 basis,还是降级为目标口径标注,还是删除 |
| D-6 | SLA 互斥:工作日 2 小时 / <4小时 工作日 / 48 小时内 三套并存(§5.2) |
未动 | 留哪一条 |
| D-7 | news 正文富文本能力缺口 P-2:字段声明 richtext 而 renderField 无该分支(§5.3) |
未动(上轮已把文档改成现实描述) | 补 TipTap 分支,还是把字段降级为纯文本并改声明 |
| D-8 | 死码删除:上轮 7 项不完整,本轮扩充为含 hero-section-v2、insight-card、industry-grid、三份 product-card、solution-value、service-value、breadcrumb、loading-state、micro-interactions、PullToRefresh 等(§5.3);其中 loading-state.tsx 正是 check:brand-token 唯一的白名单豁免(N-8) |
未删(删除不可逆且需授权) | 是否授权删除 + 一并收掉白名单豁免 |
| D-9 | Sentry 实投递未验证:接线与惰性有码路证据,但未用真实 DSN 起服务验证事件送达与 bundle 上传 | 未验证项已披露 | 是否提供 DSN 并授权一次线上实测;另 @sentry/tracing@7(零引用、已废弃)是否授权移除 |
| D-10 | 变异测试:上轮把它列为"须第 4 项授权后再跑",理由是 --inPlace 会改写工作树;本轮核实 stryker.config.json 无 inPlace 键 ⇒ 默认沙箱拷贝,前提不成立 |
本周期已执行:src/lib/utils.ts 变异分数 97.06(≥ break 50、达 high 80),1 例存活已定位(§3.8);.stryker-tmp 峰值 1.7 G 曾把磁盘冲到 100%(§3.9),已在收尾删除 |
剩余裁定项是要不要跑全量(mutate 覆盖 src/lib/**+src/hooks/**+七个组件目录,需 ≥2 G 磁盘余量与一段无并发门禁的时间窗) |
| D-11 | 门禁证据落盘路径共用固定日期目录(§3.9):check:axe 的默认 OUT 写死在 docs/acceptance/2026-09-21-axe,CI 与本地、负控制与正控制全部互相覆盖,且目录未跟踪 ⇒ 证据不可恢复、可被后一次运行悄悄改写 |
已用 dated 目录自保并复原默认路径 | 是否授权把默认 OUT 改为按日期分目录(同步 Jenkinsfile:390-392),并决定是否将 docs/acceptance/** 纳入版本控制 |