chore(qa): 验收台账与证据入库 + 构建/部署配置同步

- 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 产物装配与部署形态。
This commit is contained in:
2026-09-28 10:48:09 +08:00
parent 6bb7c557ee
commit a0328a623f
128 changed files with 22455 additions and 504 deletions
+30 -2
View File
@@ -140,7 +140,7 @@ Novalon 的 6 个核心产品,互为互补关系,常以组合形式出现在
当前状态:近期有规划,需在本次重构中预留完整的模板体系
### 特效组件 (Effects)
`src/components/effects/` 目录下的 24 个视觉特效组件。当前状态:**大部分未被首页使用**,属于技术债务。
**该目录已不存在**(口径核对 2026-09-23):`src/components/effects/` 在提交 `37296b5`(2026-05-10「三轮视觉改造与页面过渡动画」)中被**整体删除 25 个文件**,`git ls-tree -r HEAD` 与该目录的 `ls` 均为空。本节原文("24 个视觉特效组件……大部分未被首页使用,属于技术债务")是**重构前的现状盘点**,只作历史保留,不得再当作可寻址的目录引用。动效能力现在的落点是 `src/components/ui/`(`scroll-reveal.tsx`、`animated-counter.tsx`、`brand-visuals.tsx`)与 `src/components/sections/`(`hero-particle-field.tsx` + 其引擎)。
### Design Tokens
`.impeccable.md` 中定义的设计令牌系统,包含颜色、排版、间距、卡片系统、Section 背景交替规则。当前状态:**文档已定义,代码中 globals.css 有对应 CSS 变量,但组件层未完全落地**。
@@ -191,7 +191,35 @@ Novalon 的 6 个核心产品,互为互补关系,常以组合形式出现在
| 品牌主口号 | **智连未来 · 成长伙伴 —— 您的数字化转型同行者**(整句全站统一采用)。品牌叙事内核(定位声明 / 承诺 / L0 价值主张 / L1 三支柱 / 语气语调)见 `docs/brand-narrative-core.md`,作为全站文案统一唯一锚点 | ✅ 2026-08-19 确认 |
| 品牌叙事 CMS 化 | 品牌叙事内核全部下沉到 CMS `site-config` 模型(`slogan` / `valueProposition` / `positioningStatement` / `brandPromise` / `toneOfVoice` / `pillars`),后台可编辑;代码侧 `COMPANY_INFO` 与 `BRAND_NARRATIVE`(`src/lib/constants/company.ts`)作为未配置 CMS 时的兜底唯一真源,`layout.tsx` 读取后经 `SiteConfigProvider` 注入全站 | ✅ 2026-08-19 确认 |
| 结构性文案 CMS 化 | 新增 `page-copy` 内容模型承载各业务页面章节标题/眉标/描述/CTA/空状态,覆盖首页 + 服务/方案/产品/案例/新闻列表页,seed 写入 6 条(home/services/solutions/products/cases/news)。实现为「CMS 优先 + 硬编码兜底」:CMS 未配置时回退 `TRUST_SIGNALS`/`EARLY_ACCESS`/`NARRATIVE_ACTS` 等常量,保证任何情况不白屏。数据层新增 `getPageCopy(pageCode)` / `getHomePageCopy()`,各 `page.tsx` 读取后以 `pageCopy` props 传入组件。详情页小节标题、页面内嵌业务常量(服务流程/合作模式/产品组合)、Header/Footer 等 UI 外壳按 UI 职责保留代码。数据实体(服务/产品/方案/案例/新闻/指标/Hero)仍由 `data-server` 从 Prisma 读取 | ✅ 2026-08-19 确认 |
| 首页对标埃森哲重构(scene#16) | **视觉基调:局部深色 Hero(推荐项)**——保持浅色咨询风为底,仅 Hero 升级为全出血深色画布(近黑底 `bg-ink` + 白色 Logo `/logo-white.svg` + 品牌红 #C41E3A 单电压 ≤10%),制造埃森哲式戏剧感;**实施范围:Phase A+B 全量**——①内容支柱补齐:新增 Insights 行业洞察(2×2 gap-px 卡片网格 + 方法论/观点/共创类型标签)、FounderQuote 创始人观点(深红区块 + 白色点阵 + 大引言)、News 新闻动态(最近 3 条 + 查看全部)三个对标埃森哲思想领导力支柱的区块;②Hero 深色化;③CTA 签名符号:全站 Button 组件 `rounded-md` → `rounded-full`(Pill 胶囊化)+ ArrowRight 签名箭头。零编造内容原则:公司 2026-01-15 成立,Insights 兜底 4 条均为可验证方法论内容(不虚构研究报告/人名),FounderQuote 署名兜底「Novalon 创始团队」不虚构具体人名。实现:`home-content-v15.tsx` 替换 v14(9 区块顺序 Hero→Trust→Narrative→Insights→Services→FounderQuote→Cases→News→CTA),CMS 优先 + FALLBACK_INSIGHTS/NEWS 常量兜底;seed page-copy 新增 insights*/founderQuote*/news* 字段(upsert 幂等)。验证:tsc 0 errors / eslint 0 errors / Jest 128 套件 1621 通过 / Playwright 视觉回归 22 passed(macOS 11 限制下用 Chromium 117 兼容配置生成快照) | ✅ 2026-08-31 确认 |
| 首页对标埃森哲重构(scene#16) | **视觉基调:局部深色 Hero(推荐项)**——保持浅色咨询风为底,仅 Hero 升级为全出血深色画布(近黑底 `bg-ink` + 白色 Logo `/logo-white.svg` + 品牌红 #C41E3A 单电压 ≤10%),制造埃森哲式戏剧感;**实施范围:Phase A+B 全量**——①内容支柱补齐:新增 Insights 行业洞察(2×2 gap-px 卡片网格 + 方法论/观点/共创类型标签)、FounderQuote 创始人观点(深红区块 + 白色点阵 + 大引言)、News 新闻动态(最近 3 条 + 查看全部)三个对标埃森哲思想领导力支柱的区块;②Hero 深色化;③CTA 签名符号:全站 Button 组件 `rounded-md` → `rounded-full`(Pill 胶囊化)+ ArrowRight 签名箭头。零编造内容原则:公司 2026-01-15 成立,Insights 兜底 4 条均为可验证方法论内容(不虚构研究报告/人名),FounderQuote 署名兜底「Novalon 创始团队」不虚构具体人名。实现:`home-content-v15.tsx` 替换 v14(9 区块顺序 Hero→Trust→Narrative→Insights→Services→FounderQuote→Cases→News→CTA),CMS 优先 + FALLBACK_INSIGHTS/NEWS 常量兜底;seed page-copy 新增 insights*/founderQuote*/news* 字段(upsert 幂等)。验证:tsc 0 errors / eslint 0 errors / Jest 128 套件 1621 通过 / Playwright 视觉回归 22 passed(macOS 11 限制下用 Chromium 117 兼容配置生成快照) | ✅ 2026-08-31 确认(署名口径已被 2026-09-21 行修订)|
| FounderQuote 兜底署名改回法定主体名(修订上行) | 上行的「Novalon 创始团队」与 `p1-brand-visual-audit.spec.ts:30` 的 `FORBIDDEN_TEXT=/novalon/i` 冲突,且对外可见文案的署名应是法定主体「四川睿新致远科技有限公司」的简称而非英文品牌名。裁定:**改文案不改测试**——`FALLBACK_FOUNDER_QUOTE.name`(`home-content-v15.tsx:115`)与 `prisma/seed.ts:961` 同步为「睿新致远创始团队」,单测 `home-content-v15.test.tsx:181` 随动。DB 侧 `page-copy/home` 的 `founderQuoteName` 实测为空,页面走兜底分支,无需授权写库;`curl localhost:3000/` 复核渲染值为「睿新致远创始团队」且 0 处 "Novalon 创始团队"。零编造原则不变:仍不虚构具体人名 | ✅ 2026-09-21 确认(验收 §7-4 / 放行条件② 留痕)|
| 装饰大字对比度口径 | 装饰性大字号文字(序号、客户名首字占位)不享有对比度豁免:axe-core 4.11.4 的 `color-contrast` 只看 DOM 可见文字颜色,`aria-hidden` / `role="none"` / CSS `opacity` 均不豁免(变体矩阵实测,仅 SVG `<text>`、CSS mask、`::before` 等非文本编码可绕)。全站统一用大字档令牌 `text-text-hint`(#7C8CA5 / 深色 #94A3B8,对各级卡片底色 ≥3:1,满足 WCAG 1.4.3 大字 AA),不再用 `text-text-muted/10` 之类低 alpha 淡化;口径由 `scripts/utils/check-color-contrast.ts` 的「大号装饰文本 × 各级卡片底色」契约组固化 | ✅ 2026-09-21 确认(验收 A-12 清零)|
| 触摸目标口径 | 硬门禁 = WCAG 2.2 **AA** SC 2.5.8 **≥24×24px**(含 Spacing/Equivalent/Inline 例外,正文内联链接按 `display:inline` 排除);SC 2.5.5 的 44×44px 属 **AAA**,在 `e2e/touch-targets.ts` 中只作为建议清单打印、不使构建失败。此前测试把 44px 标为 AA 是标准档位错用 | ✅ 2026-09-21 确认 |
| GA4 SPA pageview 标题时序 | Next.js App Router 的 `<title>` 在路由 commit 之后 2–5 帧才写入(生产实测 chromium-mobile 第 2 帧、桌面 chromium 第 3–5 帧),因此 `GoogleAnalytics.tsx` 必须带帧预算(`MAX_TITLE_SETTLE_FRAMES = 10`)轮询等待标题变化后再 `gtag('config')`;超预算兜底发送(不丢计数),新导航前补发未送出的上一条 pageview(不送上一页标题)| ✅ 2026-09-21 确认(验收 A-17)|
| iPhone SE 复核口径(放行条件⑥) | 375×667 @2x + `colorScheme` 强制深/浅 × 同意条 pending/dismissed × 首页/新闻详情/新闻列表/联系页 = 16 组,证据(32 图 + `measurements.json` + 探针脚本)落在 `docs/acceptance/2026-09-21-iphone-se/`。实测:16/16 `scrollWidth == clientWidth == 375`(无横向溢出)、`footerBottomGap = 64`(验收 R-1 记录为 192–226 的双安全区空带)、滚到底后 `footerBottomVsViewport = -128` 一致;同意条在 pending 态以 `bannerTop=461` 覆盖页脚——这是 `fixed bottom-16` 浮层的设计行为,可关闭后消失。**边界**:这是浏览器仿真,不等同真机走查,真机确认仍需人工 | ✅ 2026-09-22 确认(验收 §8-⑥)|
| `-[var(--color-*)]/<alpha>` 死样式族的收口边界 | 编译产物实测:src 下 22 处 / 17 个唯一类在 CSS 中 0 产出(Tailwind v3 不能对纯 hex 变量套 alpha,见 `tailwind.config.js:12-15`)。按**渲染证据**分级后只修 2 处「活组件 + 仅 hover」:`NewsDetailClient.tsx:49` → `hover:bg-brand-soft`、`service-card.tsx:33` → `hover:border-brand/20`(hover 不入基线,零像素影响,已确认三条规则在编译 CSS 中产出)。注意点亮后的合成底色须重算对比度:`hover:bg-brand/20` 在浅色主题下使 `text-brand-ink` 降到 4.18:1(破 AA 4.5:1),且 axe 忽略 `:hover`、`check:contrast` 只断言令牌配对,两道门禁均测不到,故取 12% 的 `brand-soft`(浅色 4.80:1 / 深色 6.60:1)。**不做**:`/privacy`、`/terms` 首屏 `via-[var(--color-brand)]/80` 一旦被"点亮"会在已审批基线里画出半透明暗坑,属设计判断;3 处需先在 `globals.css` + config 补 `--color-bg-section-rgb` / `--color-brand-bg-rgb` 通道;其余 14 处位于无任何路由渲染的 barrel-only 死代码。是否扩 `check-brand-text-token.ts` 到全族由用户定范围 | ⏳ 2026-09-22 待裁定(残留项,不属验收 §8 六条放行条件)|
| 英文 wordmark 的受控口径(续上上行 §7-4) | 全仓唯一的字面 `NOVALON` 位于 `public/logo.svg:72`、`public/logo-white.svg:72`、`public/logo-calligraphy.svg:72` 的 `<text>` 元素,且各自行前注释 `<!-- NOVALON - 英文 -->` 自述为设计意图,三处 `<text>` 实测内容恰为 `NOVALON`(`letter-spacing="4"`,仅 `fill` 随底色不同:#0A0E14 / #FFFFFF)。提交 `8d3bd72` 另把书法人名统一到 header/footer/品牌页(新增 `BrandCalligraphyName`,改 `public/logo.svg`)。裁定:**wordmark 属图形资产内的受控内容,不是文案回归**;对外可见**文本**署名一律「睿新致远」(:195),`src` 下其余 `novalon` 串为技术上下文(`novalon.cn` 域名、`novalon_admin_token` 等存储键、PBKDF2 盐、管理端 `Novalon CMS` 界面)。据此把 `e2e/p1-brand-visual-audit.spec.ts` 从"零断言"改为三条真不变量:①两张 logo 资产经 `page.request.get` 取回后,每个 `<text>` 内容须恰为 `NOVALON`(改名、夹带或漏字即失败);②header/footer/首页的可见文本、`alt`、`title`、hover 后文本零 `novalon`(技术上下文除外);③凡"遍历后无命中即通过"的用例补 `expect(checked/hovered/withAlt).toBeGreaterThan(0)`,避免选择器失配伪装成绿灯。因 wordmark 以 `<img>` 加载、不成 DOM 文本,`:173-177` 的首页 Hero 豁免分支在生产 HTML 上当前不触发(改为内联 SVG 时会生效)——保留但不依赖。放行条件② 的「修订 `CONTEXT.md` 或测试」两条路径此处**同时**满足 | ✅ 2026-09-22 确认(验收 §7-4 / 放行条件②)|
| 边界页(404 / 根错误兜底)的可测口径 | ⑤ 的扫描分母从 `sitemap.xml`(29 条)扩为 **sitemap ∪ 预渲染产物 ∪ 站内链接 BFS = 35 条**后,首轮即 `passed=false`(`docs/acceptance/2026-09-21-axe/axe-evidence.json`,双引擎双主题共 8 条 bad rows):`/_not-found` 的 h1「404」用 `text-brand-ink` + **`opacity-20`** ⇒ 有效 alpha 20%,`color-contrast` 必破;`/_global-error` 因项目无 `global-error.tsx`,由 Next 内置文档壳顶替 root layout ⇒ `<html id="__next_error__">` 无 `lang`,且本站主题是 `html[data-theme='dark']` 属性驱动(`globals.css:396` 明示非 `.dark` class),内置壳读不到令牌,深色落到浏览器默认 `rgb(10,10,10)`。裁定:**边界页属全站范围,不豁免**——404 与根错误页是每个访客都可能落到的页面,且是唯一的"无 CMS 数据兜底路径"。修复:404 标题改 `text-brand-ink/80`(120px 属大号文本,AA 需 3:1;沿用 §5.22 `/products/erp-upgrade` 水印数字的同一口径与同一档位),新增 `src/app/global-error.tsx` 自带 `<html lang="zh-CN">` + `<title>`(错误边界是 Client Component,不支持 `metadata` 导出)+ 与 `globals.css` 同值的 OS 主题内联样式(CSP `style-src 'self' 'unsafe-inline'` 允许)。**约束**:①可见文本一律不得用 `opacity-*` 暗化,装饰性大字用可编译的主题令牌 + `/80` 档;②新增 `app/` 顶层边界文件须自带文档壳与主题样式,且本版本 Next 的 `global-error` props 是 **`retry`**(`node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/error.md`),不是旧 API 的 `reset`;③任何"全站 = 0"型门禁的分母必须是三源并集清单,`sitemap.xml` 只覆盖"希望被收录的页",单独使用即为口径错误。配套回归:`src/app/global-error.test.tsx`(3 例:`lang`、`retry` 接线、digest 展示) | ✅ 2026-09-22 修复并复扫通过:chain5/chain6 最终树 34 路由 × chromium/firefox × 深/浅 的 `contrastNodes / violationNodes / themeMismatch / bgMismatch` 全为 0、`rows=136`、`passed=true`(`docs/acceptance/2026-09-21-axe/axe-evidence.json`,详见 §5.24 与 `docs/acceptance/2026-09-21-gates/final-tree-results.md` ⑤)|
| refresh token 的轮换/吊销:本轮**有意延后**(需 schema 变更 = 需授权动作) | `src/app/api/auth/refresh/route.ts` 只验签名即用旧 payload 重签,意味着失窃的 refresh token 在到期前可无限换取 access token。**闭合它需要 `jti` / `tokenVersion` 一类的服务端状态,即 Prisma schema 迁移**——按 `AGENTS.md` §5.1「破坏性/共享状态动作须单独取得授权」,DB 迁移不在常规变更里夹带,故本轮明确延后并留痕(源码注释同处 `:33-34` 已写明 backlog)。**已随车交付的缓解**(不需迁移):①按 `payload.userId` 实查 `User`,账号不存在或 `status === 0` 一律 401,堵住「被禁用/删除账号仍能在 7 天有效期内换新令牌」(B-7);②角色不按令牌里的旧 claim 沿用,而是走 `permissions` 层的同源查询 `UserRole` 关联表后**重签**,无关联角色的历史账号回落到与 login 同源的 `User.role` 标量,避免降权后仍持旧角色。边界:这两条只保证「账号状态实时」,**不**构成令牌吊销——已签发且账号仍启用的旧 refresh token 依旧可用到过期 | ✅ 2026-09-23 确认(延后为有意决策,非遗漏;轮换/吊销待授权迁移)|
| CMS 写入侧字段校验的边界(承接「数字口径 basis 结构强制」行) | 出路由 `src/lib/cms/validate-content-data.ts` 承载,挂在 `POST /api/admin/items`(`:132`)与 `PATCH`(`:215`)上,**只执行 `ContentModel.fields` 已声明的约束**:`options` 即视为枚举(`select`/`dropdown` 与带 `options` 的 `text` 在 `content-types.ts` 同义)、`min`/`max`/`pattern`;违规返回 `validationError('内容字段校验未通过', { fields })` 并附每条 `path`。刻意保持宽松的两处:**未声明的键一律放行、未填的键不判必填**,以免改变既有载荷形态(seed 与页面组件依赖宽松结构);`loadDeclaredFields` 在模型缺失或 `fields` 不可解析时回落 `[]`,即**fail-open**(无声明 = 无约束),因此「后台有校验」不等于「值一定合法」,新增约束的正解是补 `ContentModel.fields` 而非在路由里加特例。`basis` 的强制仍在那一行的口径里:渲染端 `metrics-basis-note.tsx:11` 缺省回落 `target`,`metrics-basis.test.ts` 机械校验 seed 全量条目显式声明并禁止无据自称 `verified`;字段定义改动须重跑 seed 同步 `ContentModel.fields` 后 admin 才出现输入项 | ✅ 2026-09-23 确认(写入侧只判已声明约束 + fail-open 为有意取舍)|
| 边界页(`/_not-found` / `/_global-error`)**不带站点 Header/Footer** | 这是结构结果而非疏漏,本轮核实并固化为口径:`src/app/not-found.tsx` 位于 `app/` 根、**不在 `(marketing)` 路由组内**,而 Header/Footer 只挂在 `(marketing)/layout.tsx:14,22`,故 404 页由 root layout 提供公共外壳(主题内联脚本、GA、CookieConsent、MobileTabBar)但**无导航与页脚**;`NotFoundContent` 自带恢复动线(返回首页 CTA + `history.back()` + 产品/方案/关于/联系四枚入口)。`src/app/global-error.tsx` 更彻底:按 Next 契约它**自带 `<html lang="zh-CN">` + `<title>` + 与 `globals.css` 同值的 OS 主题内联样式并顶替 root layout**(`node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/error.md:163,165` 明写"必须自带文档壳、不含全局样式、`data-theme` 到不了它"),因此两页都**不能**指望复用 Header/Footer 或品牌红规则;错误边界是 Client Component,`metadata` 导出无效,标题只能用 React `<title>`。**约束**:新增 `app/` 顶层边界文件须自带文档壳与主题样式,且本版本 props 是 `retry` 不是 `reset`;给这两页补导航/页脚需先决定是否把 root layout 变成可复用外壳,属设计判断而非机械改动 | ✅ 2026-09-23 确认(口径固化,配套回归 `src/app/global-error.test.tsx`)|
| lint warning 的处置口径:**0 error 判门禁,warning 不判红也不清零** | 现行裁定:`npm run lint` 通过标准是 **0 error**;warning 属**已知存量**并只登记分布、不阻塞合入(数量口径的**版本库内权威记录位**是 `docs/development/quality-gates.md` §1:2026-09-23 **第二周期**最终树复跑 `npm run lint` ⇒ **104 problems / 0 errors / EXIT=0**,分项 55 `no-console` + 25 `no-explicit-any` + **11** `set-state-in-effect` + 10 `no-img-element` + 各 1 `no-sync-scripts`/`no-html-link-for-pages`/死抑制(同日第一周期为 105,差值唯一来自后台编辑器"挂载期消费守卫"那个效果被删)。⚠ 不要再指向 `AGENTS.md` §5——该文件被 `.gitignore` 有意排除、由 `next dev` 再生,不可审也不可回滚,见验收 N-16)。分级由 `eslint.config.mjs` 声明而非随手 `--fix`:`@typescript-eslint/no-explicit-any`(`:52`)、`no-console`(`:54`,`allow: ['warn','error']`)、`react-hooks/set-state-in-effect`(`:61`)为 `warn`;`react/no-unescaped-entities`、`eqeqeq`、`prefer-const` 等是 `error`。按路径降档只有三处 override(`e2e/**/*.ts` 关 `no-console`、`**/*.test.{ts,tsx}` 与 `**/__tests__/**` 关 `no-explicit-any`、`tests/performance/*.js`),**`prisma/seed.ts` 不在其中**——它的进度日志是"有意容忍的 warning",不是配置豁免。**禁止的处置方式**:为凑绿把规则调成 `off`、加行内 `eslint-disable` 而不写理由、把 `Unused eslint-disable directive`(死抑制)当噪声忽略——死抑制本身按 §「门禁断言的有效性口径」第 1 条归入"什么都没测"。需要静音时须附来源注释 | ✅ 2026-09-23 确认(warning 存量制,error 零容忍)|
| `duration-300` → 动效令牌 与 死样式收口:**本轮为 in flight,不得当作已完成** | 两条已知残留:①`duration-300` 一类 Tailwind 预设时长绕过 `--transition-fast/normal/slow`(`globals.css:255-258` 的 180/280/450/700ms)且 300ms 超出动效四原则「入场 180–280ms」的档位;②`-[var(--color-*)]/<alpha>` 静默不产出 CSS 的家族(见上方 §5.20 与「死样式族的收口边界」行)。**2026-09-23 文档同步时不写结论**:另一执行体正在同一批 `src/**/*.tsx` 上作业(写入瞬间 `src` 下 `duration-300` 命中 13 处、死样式族命中 8 处,均在实时变动中),任何计数此刻都不可引用。**约束(替换时才成立)**:动效时长走令牌、点亮死样式属设计判断、hover 合成底色须重算对比度(axe 与 `check:contrast` 都测不到 `:hover`)。复算命令:`grep -rn "duration-300" src` 与 `grep -rno "\-\[var(--color-[a-z-]*)\]/[0-9]*" src` | ⏳ in flight(2026-09-23 观察:他人正在编辑相关文件;未落地,不作为已确认决策引用)|
| E2E 目标与 skipped 口径 / `check:axe*` 的落地状态(2026-09-23) | ①**口径**:`npm run test`(含 `test:all` 里的 `test:e2e:fast`)的 webServer 是 `npm run dev`(`e2e/playwright.config.ts:130`),GA4 的 4 个 `@critical` 用例 × 4 project = **16 个 skipped**(`.env.production` 独占 GA ID → dev 下不注入 gtag → 用例内 `test.skip`);产物口径只有 `npm run test:e2e:prod`(CI 在 `Jenkinsfile`「🌐 E2E 测试」仅 main 分支执行,且须先 build)。裁定:**引用 GA4/生产头覆盖必须指名 prod 目标**,"全绿"不等于"全断言"。②**静态 a11y 门禁已成型**:`check:a11y` = `check:contrast` + `check:headings` + `check:brand-token`,并已并入 `test:all`。③**已闭合(2026-09-23 同日落地)**:`package.json` 的 `check:axe` / `check:axe:routes` 指向的 `scripts/accessibility/{axe-node-count,crawl-routes}.mjs` 现已存在(由 `docs/acceptance/2026-09-21-axe/` 的一次性 harness 平移而来,最终树 34 路由 × 4 组合 `passed=true`,证据 JSON 仍留原目录),判定改为退出码(0 通过 / 1 判红 / 2 清单缺失或 0 条),并由 `Jenkinsfile`「♿♿ 全站 axe 节点计数」阶段(仅 main,服务用 `node dist/standalone/server.js`,按记录 PID 收服)自动执行 —— axe 节点计数自此**是**自动化门禁;引用其结果时须同时给出 `axe-evidence.json` 的 `routeCount` 与分母断言,只报 `passed=true` 不报分母即为口径错误。④文档同步另纠正三处:`npm run preview` 实为 `next start -p 3000`(非 `npx serve`,standalone 下会打警告)、渲染模式措辞由「静态导出」改「standalone 混合渲染」、`docs/testing.md` 的 dev 指示器机制改回 `e2e/fixtures.ts` 的真实实现 | ✅ 2026-09-23 确认(③已于同日落地为常驻门禁,④为该轮文档纠正;③的漂移记录见 `docs/lessons-learned.md` §5.26,其修复标注在同一行)|
#### 门禁断言的有效性口径(chain6/chain7 新增)
验收期间发现 `config/test/lighthouserc.json` 有 3 条 `warn` 级断言(`autocomplete-valid` / `presentation-role-conflict` / `svg-img-alt`)**在 Lighthouse 里根本没有对应审计项**:把 chain6 全部 21 份报告的 `audits` 键取并集(175 个 id)比对 62 个非 `categories:` 断言键,只有这 3 条缺席;`@lhci/cli@0.15.1` 用的是它自带的 `lighthouse@12.6.1`(不是顶层的 13.4.1),其 `core/audits/accessibility/` 64 个审计文件里没有这三个。它们每轮稳定打印 `"…" is not a known audit. found: 0`,因为是 warn 级永不判红 —— 于是"57 项 a11y 断言全过"里混进了 3 项什么都没测的条目。
**约束(今后一律适用)**:
1. **门禁里的每一条断言必须映射到真实存在的测量项**。删除或新增断言都要留下"这项由谁兜底"的说明;`warn` 级、恒 0、无对照的断言按"假绿"处理,不得当作覆盖。
2. **拆门之前先证明覆盖还在别处**。用与 ⑤ 门禁同款的 `runOnly: {type:'tag', values:['wcag2a','wcag2aa','wcag21a','wcag21aa']}` 做正/负对照(`docs/acceptance/2026-09-21-axe/probe-axe-rule-coverage.mjs`,每条规则各造一个必然违规的最小节点):`autocomplete-valid`、`svg-img-alt` 确在 axe 通道内(各抓到 1 node);但 `presentation-role-conflict` 属 `best-practice` tag,**axe 门禁的 tag 集合根本不跑它**。因此处理不是"删了事",而是删死断言 + 在 `axe-contrast-evidence.mjs`(现 `scripts/accessibility/axe-node-count.mjs`)增加第二次 `runOnly: {type:'rules', values:EXTRA_RULES}` 运行。
3. **规则级通道也要断言分母**:每页 `extraRulesChecked === EXTRA_RULES.length`(计 `extraRulePagesUnderCovered`)+ 违规节点 0。axe 对未知 rule id 会抛错,这一条同时防"将来某条规则被 axe 改名后静默消失"——与本节第 1 条是同一种病。
4. **异常快的绿灯必须自证**。chain6 `type-check` 只花 2s,用注入 `const probe: number = "not a number"` 的正对照确认 `tsc` 能报错(冷缓存 11.1s)、删除后 2.9s 干净,成因是 `tsconfig.compilerOptions.incremental=true` 命中 `.tsbuildinfo` ⇒ 门禁有效。**"很快/很慢"只是怀疑的起点,能造正对照就不要靠推断背书**(与记忆「绿灯≠达标」同源)。
5. **文档里的数字要被复算,否则就从证据退化成传说**:该口径本身成立,但**它的示范当时写歪了**——原文称「`AGENTS.md` §5 记的 lint『实测 105 条 warning』在 chain4/chain6 实测均为 **106**,已按实测改文并注明分布(56 `no-console`/25/12/10/其余 3 条零散)」,而 2026-09-23 本树复跑 `npm run lint` 实为 **105 problems / 0 errors / EXIT=0**、`no-console` **55** 条,且"改文"落在未被 git 跟踪的 `AGENTS.md` 上(验收 N-16:由 `next dev` 再生,随时可丢)。裁定:**warning 计数按轮次漂移,不作为常量引用**;权威分布记录位改到 `docs/development/quality-gates.md` §1,并在其后附可复制的复算命令(含分项管道)。本条教训从"文档数字落后于实测"升级为"**文档数字被写进了不可审的载体**"。
来源:`docs/acceptance/2026-09-21-gates/final-tree-results.md` ④ 与「Lighthouse 的 3 条死断言」小节、`docs/lessons-learned.md §5.25`。
## 页面类型与四层映射