---
title: 设计网「缺口 A」scoping —— mockup conformance 强制自动跑 + 报告防伪
date: 2026-07-10
backlog: INFRA-F55-支柱②-③（设计网 缺口 A）
status: draft
---

> **性质**：只读 scoping 分析，产出 owner 决策就绪的方案骨架 + 工作量估计。**不改任何源文件**（本报告是唯一产出物）。
> **方法**：主线一手 Read/Bash/Grep 交叉验证（本文件内所有引用均标 `file:line`），对照支柱④ `validate-upstream-gate.mjs` + `upstream-gate.schema.json` + PR 模板的既有防伪范式。
> **背景真源**：`docs/internal/full-lifecycle-assessment-2026-07-08.md` §4 支柱②行「设计网（mockup 确定性后验）缺失」+ §8 排期项 3；`docs/internal/backlog.md` §INFRA-F55 待做③。

---

## 0. 结论摘要（先说结论，证据在下面）

1. **I1=pass 粘贴洞是真的，且比预想更深——不是一层洞，是两层**：
   - **洞①（repo 内已知）**：`audit-mockup-handoff-evidence.mjs` 只对 handoff `.md` 做**正则文本存在性**扫描（`/Integrity audit:\s*I1=/`），从不重新执行任何审计脚本、也不校验数值是否反映当前 Figma 真实状态。AI 手打一行 `Integrity audit: I1=pass / I2=... / I3=overflow-count 0 / I4=orphan-count 0` 即可通过。
   - **洞②（本次新证实，更严重）**：这道"洞①"的弱闸**只挂在 `tvu-design-system` 自己仓库的 `.husky/pre-commit`**（`.husky/pre-commit:293-297`）。而 mockup handoff 文档的实际落点是**消费产品仓库**的 `docs/handoffs/`（`skills/tvu-design-mockup/SKILL.md:152`、`templates/consumer-product/docs/handoffs/`），消费产品仓库**从未 scaffold 这道 pre-commit hook**（`grep -rln "audit-mockup-handoff-evidence" templates/consumer-product` 零命中）。而且 `setup-tvu-consumer` 的规范接线步骤（Step 2.6）**只对变体 A（Design+Code）跑，变体 B（Design-only，纯 mockup 消费产品）整段跳过**（`skills/setup-tvu-consumer/SKILL.md:74-77`）。
   - **净效应**：对绝大多数真实 mockup 任务（消费产品里的纯设计交付），**连"查文本"这道假闸都不存在**——唯一存在的强制机制是 Claude-Code-only 的 `post-figma-write.sh` PostToolUse hook，它只**注入文本提醒**（"⛔ 写操作后必跑 audit:mockup-conformance"），不拦截、不校验、且对 Codex/claude.ai/其他工具完全不存在。

2. **chokepoint 现实**：mockup 产出的第一落点是 Figma（经 MCP `use_figma` 写，AGENTS.md 硬规则 #1 明确"Figma 写无机器拦截"），不是 code PR。可挂的机械 chokepoint 只有三层，越往下越弱：
   - **(a) Figma 写本身** —— 无法拦截（同支柱④"竞品分析当场脑补"问题的镜像：produce 环节本身没有确定性介入点）。
   - **(b) 消费仓库 git commit/pre-push（handoff .md 落盘）** —— 变体 A 有 husky 基建（走 code net 蹭上）；**变体 B 完全没有 npm/husky 基建**（`Step 1/2 跳过`），是当前唯一的、真正可新建的强 chokepoint，但要新增基建。
   - **(c) Gitea PR checks** —— 前提该消费产品用 Gitea 托管 + 走 PR；许多 mockup-only 消费产品未必开 PR 流程（纯 Figma 交付可能全程不碰 git）。

3. **报告防伪方案**：复用支柱④"确定层机校 + 理解层人核"双层范式，但**关键差异**——支柱④的确定层校的是**静态可复验事实**（文件存在、URL 可达、字段名匹配），mockup 的确定层要校的是**"审计确实被重新执行过、且针对的是当前 Figma 活体状态"**，这是全新的、支柱④没有的一类防伪需求（"证明干了活"而非"证明写的话结构合法"）。核心机制：把审计脚本改成**产出机器可验证的结构化 report 文件**（而非人工转录文本到 handoff），handoff gate 从"正则找字符串"升级为"验证 report 文件存在 + 时效 + 与当前 file/node 状态哈希匹配"。

4. **推荐**：三步走，`①` 立即可做（不需 owner architecture 决策）、`②③` 需 owner 先拍 chokepoint。

---

## 1. I1=pass 粘贴洞根因（带证据）

### 1.1 现状机制怎么跑

`pnpm audit:mockup-conformance --file <fileKey> [--node <ids>] [--non-blocking]`（`package.json:73`）是真实存在的强闸——`scripts/audit-mockup-conformance.mjs` 是 9 个子审计（integrity/colors/typography-icon/library-origin/binding-fidelity/bilingual-spacing/overlap/connector/library-binding）的总闸，会真的用 `FIGMA_PERSONAL_ACCESS_TOKEN` 打 Figma API 取活体数据、逐条跑、`spawnSync` 聚合 exit code。**这个总闸本身没有防伪问题**——真跑就是真结果。

问题是它的**auto-run 覆盖率 = 0**：

| 挂载点 | 状态 | 证据 |
|---|---|---|
| pre-commit | 未挂 | `.husky/pre-commit` 全文无 `audit:mockup-conformance` / `audit-mockup-conformance.mjs` 字样 |
| CI (Gitea pr-checks) | 未挂 | `.gitea/workflows/pr-checks.yml` 只跑 `vue-tsc`/`vitest`/`prepublishOnly`（`full-lifecycle-assessment-2026-07-08.md:98` 已验证；本次复核未变） |
| prepublish | 未挂 | `prepublishOnly` 20 个 audit 不含它 |
| 唯一自动触发 | `post-figma-write.sh` PostToolUse hook | 只注入提醒文本，不执行、不拦截（`.claude/hooks/post-figma-write.sh:全文`，见 §2） |

### 1.2 "假闸"具体指什么——能不能手打 PASS

能。`scripts/audit-mockup-handoff-evidence.mjs:9-15`：

```js
export function checkHandoffEvidence(md) {
  const text = String(md || '')
  const isHandoff = /<!--\s*mockup-handoff\s*-->/i.test(text)
  if (!isHandoff) return { isHandoff: false, hasConformance: false, hasIntegrity: false, ok: true }
  const hasConformance = /mockup-conformance|conformance summary|audit:mockup-conformance/i.test(text)
  const hasIntegrity = /Integrity audit:\s*I1=/.test(text)
  const ok = hasConformance && hasIntegrity
  return { isHandoff, hasConformance, hasIntegrity, ok }
}
```

这段代码**只对字符串做正则匹配**：只要 handoff `.md` 里出现 `<!-- mockup-handoff -->` 标记 + 一句提到 `mockup-conformance` 的话 + 一行形如 `Integrity audit: I1=` 的文本，就判定 `ok:true`，**无论这几行是真实脚本 stdout 复制粘贴、还是 AI 凭"应该没问题"手打的**。它不：
- 重新执行任何审计脚本；
- 校验 I1/I2/I3/I4 后面的具体数值是否合理（如 `I1=pass` 和 `I1=fail` 在正则层面同样通过，因为只匹配 `I1=` 前缀）；
- 关联到 Figma 文件的当前状态（无 fileKey/nodeId/timestamp 校验）；
- 有任何 replay 保护（同一段文本可以贴到十个不同任务的 handoff 里，逐条都"过闸"）。

这正是 §M-LIFECYCLE 文档里承认的实证（`mockup-conventions.md:1064`）："**实证 2026-06-18：提醒后 AI 仍漏 I3 overflow，目测代脚本**" —— 连"目测"这种更轻的失败都发生过，"直接编造通过文本"这条更省事的失败路径机制上完全没被堵。

### 1.3 更深一层：这道假闸多数时候连挂载的机会都没有

见 §0.1 洞②——handoff `.md` 落在消费产品仓库 `docs/handoffs/`，而这道正则闸只存在于 `tvu-design-system` 自己的 `.husky/pre-commit`。消费产品（尤其变体 B Design-only）commit 自己的 handoff 文档时，**这个 hook 根本不在那个仓库里，不会被触发**。也就是说对绝大多数真实 mockup 产出场景，当前连"能被查文本"的机会都没有——I1=pass 粘贴洞的更准确描述是：**mockup 产出对任何工具都不存在机械后验，唯一存在的文本闸只保护 DS 自己仓库内部的自用 mockup（如给 DS 库本身画的示意图），不覆盖消费产品的真实产品设计交付**。

---

## 2. 强制自动跑要挂哪个 chokepoint

### 2.1 对照支柱④的 chokepoint 选择逻辑

支柱④（`validate-upstream-gate.mjs:9-13` 注释原文）："chokepoint-bound：挂在受控落点（DS Gitea pr-checks / 消费 repo scaffold 下发 CI）。artifact 不存在 → no-op PASS（不是每个 PR 都是产品设计任务）。" 它能这样做，是因为**上游 gate 的产出物本来就是要提交进消费仓库的 markdown 文件**（`upstream-gate.<feature>.md`），产出物形态天然可以躺进一个 PR。

mockup 的产出物形态不同：**第一性产出是 Figma 节点树，不是文件**。handoff `.md` 只是**事后补写的旁路文档**，不像 upstream-gate artifact 那样是"任务本体"。这是两个支柱在 chokepoint 设计上的根本差异，不能直接照搬。

### 2.2 三层 chokepoint 候选（越往下越可靠，也越要新建基建）

| 层 | chokepoint | 可靠性 | 当前状态 | 新增成本 |
|---|---|---|---|---|
| (a) Figma 写本身 | `use_figma` 调用时机械拦截 | 最高（离产出最近，堵不掉就是堵不掉） | **不可行**——AGENTS.md 硬规则 #1 明文"Figma 写无机器拦截"，Figma Plugin API 无法被 repo 侧 git/CI 机制勾住；`post-figma-write.sh` 已是这层能做到的最大值（工具原生 hook 事件），且只对 Claude Code 生效 | 不适用（架构性上限，非工作量问题） |
| (b) 消费仓库 commit/pre-push（handoff `.md` 落盘时） | pre-commit/pre-push hook | 高（可强制阻断本地提交） | 变体 A：有 husky 基建（蹭 code net），**但也没挂 mockup 闸**；变体 B：**零 npm/husky 基建**（Step 1/2 全跳过） | 中——变体 A 只需追加一个 pre-commit 段；变体 B 需先决定"给纯设计仓库塞一套轻量 git hook 基建"是否值得（可能仓库连 `package.json` 都没有，需换成不依赖 npm 的 shell/Python 独立脚本，或强制变体 B scaffold 一个最小 `package.json`+`husky-lite`） |
| (c) Gitea PR checks | CI workflow | 高（同支柱④机制可直接复用 runner） | 只在**该消费产品选择 Gitea 托管 + 走 PR 流程**时适用；不少 mockup-only 消费产品可能是个人 GitHub 镜像甚至纯 Figma 无 repo | 低（若已用 Gitea PR：复用 `audit:upstream-gate` 同款 job 定义即可）；但覆盖面受"是否用 PR 流程"这个前提限制，与 owner 尚未拍板的"owner-direct-master vs PR-for-all"分叉（`full-lifecycle-assessment-2026-07-08.md §7 第2条`）直接相关 |

### 2.3 "裸 claude.ai 无 PR" 场景对照

支柱④文档里对应的降级是"artifact 不存在 → no-op PASS"。mockup 场景的对应降级应该是：**如果消费产品既非 Gitea 托管走 PR、也未 scaffold 任何 git hook（纯 Figma 交付，AI 全程不碰本地 repo）——机械 chokepoint 全部落空，只能靠 owner 人工 spot-check（design-walkthrough）兜底**，这一点应该在方案里**显式承认为已知残余缺口**，不能假装靠"文档要求 AI 自觉贴证据"就补上（那正是现在失败的机制）。

---

## 3. 报告防伪方案（对照支柱④范式）

### 3.1 支柱④的双层范式回顾

`upstream-gate.schema.json` 给每个字段标 `x-gate-layer`：
- `deterministic`（机校）：`sources_read` 锚点存在性、`competitive[].url` 可达性、`data_feasibility.fields` 是否 ⊆ product-context 已声明字段——**这些字段的"对不对"是可以脱离语义、纯结构/纯事实判断的**。
- `human`（人核）：`understanding`（复述对不对）、`persona_ia`、`mvp_scope`——**这些是判断题，机校会沦为"假装机校"（validator 注释原话）**。
- `deterministic+human` 混合：`competitive` 的 URL 可达性机校、但"是不是真的同类竞品"人核。

validator（`validate-upstream-gate.mjs:runGate`）明确只做确定层，且每次跑完都打印一行"人工层由 owner PR review 验收"，PR 模板（`.gitea/PULL_REQUEST_TEMPLATE.md:54`）里也写死"以下人工层由 owner review 验收，不可自 ack 伪造"。

### 3.2 mockup 场景的差异：多一类"证明真的跑过"的防伪需求

支柱④的确定层校验对象是**静态、可反复重算的事实**（URL 今天可达就是可达，跟"谁、什么时候访问"无关）。但 mockup conformance 的核心防伪诉求不一样——**不是"这句话字面对不对"，而是"这次审计真的针对当前 Figma 活体状态跑过一次，而不是编的/抄的/过期的"**。这是一类新的确定层校验，命名为 **"执行真实性"（execution-authenticity）层**，支柱④没有对应机制，需要新设计：

**核心思路：审计脚本自己产出机器可验证的 report artifact，handoff 文档从"人工转录数值"降级为"引用 report 文件路径"，gate 验证 report 文件本身，而不是验证 handoff 里的文本。**

具体设计（骨架）：

1. **`audit:mockup-conformance` 加 `--report <path>` flag**：跑完把结构化 JSON 写到固定位置（如 `docs/handoffs/.conformance/<fileKey>-<node-hash>-<timestamp>.json`），内容含：
   - `fileKey` / `nodeIds`（本次 `--node` 覆盖的节点，§M-LIFECYCLE 要求"必须覆盖全部触碰节点"，这个字段本身也可被 §3.3 的 diff 校验用上）
   - 每个子审计的 `{key, exitCode, findingsCount}`（原样来自 `results` 数组，`audit-mockup-conformance.mjs:112` 已有这个内存结构，只是现在只 console.log，不落盘）
   - `timestamp`（ISO，跑的时刻）
   - `figmaLastModified`（若 Figma API 返回该文件/节点的 `lastModified` 字段，一并记录——用来判断"这份 report 是不是针对*这次*编辑跑的，还是引用了旧一次编辑之前的 report"）
2. **handoff `.md` 只需引用 report 文件路径**（而非手打数值）：如 `Conformance report: docs/handoffs/.conformance/abc123-n456-20260710T120000Z.json`。
3. **`audit-mockup-handoff-evidence.mjs` 升级为**（这是本方案唯一要改代码的地方）：
   - 不再用正则找 `I1=` 字符串，改为**解析 handoff 里的 report 路径引用**；
   - 校验该 JSON 文件**存在** + **可解析**；
   - 校验 `exitCode` 全部为 0（或允许的 `--non-blocking` 语义）——这一步就是"确定层"：不判断 finding 内容对不对（那是人工层），只判断"report 说过了"；
   - **时效窗口**：`timestamp` 距 commit 时刻不超过 N 小时（防止贴一份几天前跑的、早已过期的 report 敷衍今天的改动）；
   - **（进阶，可选）活体比对**：若 token 可用，gate 可现取 Figma 该文件/节点当前 `lastModified`，与 report 里记录的 `figmaLastModified` 比对——不同则说明"跑完 report 之后 Figma 又被改了"，判失败（要求重跑）。这一条是本方案里"确定层"能做到的**最强防伪**，直接对应支柱④"URL 可达性"这类"脱离语义可判定"的检验。
4. **人工层保留给 owner**（同支柱④范式）：report 里的 finding 是否是"能接受的已知限制"而非真 bug（如某些 overlap 是设计意图的叠层）、是否需要重新走查——这些交给 `design-walkthrough` 走查环节人核，不强行机校。

### 3.3 为什么"贴 report 文件路径"比"贴文本数值"更防伪

- **不可手打**：report 是脚本 `writeFileSync` 产出的 JSON，AI 可以伪造一个假 JSON 文件，但成本和"直接跑真审计"基本相当（尤其一旦加上 §3.2 第4点的 Figma `lastModified` 活体比对，伪造需要猜中或伪造一个会被交叉验证识破的字段）——**把"编造 6 个字符的字符串"升级成"编造一个会被结构化+时效+活体三重校验的产物"**，性价比逆转，AI 更可能选择"干脆真跑一次"。
- **可审计留痕**：report 文件本身进 repo（或至少 gitignore 但本地留痕+CI 里临时生成上传 artifact），日后复盘能看到"哪次改动到底有没有真跑过"，不像现在文本证据无法反查。
- **和支柱④范式同构**：都是"结构化 artifact + 确定层 validator 脚本化校验 + 人工层留给 owner"，降低团队认知负担（同一套心智模型套两个支柱）。

---

## 4. 推荐 enforcement 骨架 + 工作量 + owner 前置决策

### 4.1 推荐顺序（三步，第①步不需要 owner 先拍 chokepoint）

**① 报告防伪机制本身（最高杠杆，独立可做）**——不管 chokepoint 最终挂在哪一层，"审计产出可验证 report + gate 验 report 而非验文本"这件事本身值得先做，因为它同时堵住洞①（哪怕暂时还挂在 DS 自己仓库）。
- 改 `scripts/audit-mockup-conformance.mjs` 加 `--report` flag（落盘结构化 JSON）。
- 重写 `scripts/audit-mockup-handoff-evidence.mjs`：从正则改成"解析 report 引用 + 校验文件 + 时效"。
- 更新 `mockup-conventions.md §M-DISCIPLINE.SYNC`（`mockup-conventions.md:2437` 附近）把"贴 Integrity audit 文本行"改成"贴 report 文件路径引用"。
- **工作量估计**：中——两个脚本改动 + 单测（沿用现有 vitest 覆盖 `checkHandoffEvidence` 纯函数的模式）+ 一处文档措辞更新。**不涉及新基建、不依赖 owner 架构决策**，可以直接排期。

**② chokepoint 落地——需 owner 先拍 §4.2 的架构分叉**：
- 变体 A（Design+Code）：在 Step 2.6 已有的 pre-commit 接线里追加一段 mockup-handoff-evidence 检查（复用/移植 code net 已验证的"按 staged 文件类型条件触发"模式，`.husky/pre-commit:239-247` 的 `staged_md` 逻辑本身就是范本，直接照抄迁移到消费仓库模板）。**工作量：小**（有范本可抄）。
- 变体 B（Design-only）：需先决定"要不要为纯设计消费产品新建 git hook 基建"。若要，需最小化方案（不依赖完整 npm 生态，例如一个不需要 `pnpm install` 的独立 Node/Python 脚本 + 极简 `.git/hooks/pre-commit` 手工链接，或强制 scaffold 一个只装 husky 的最小 `package.json`）。**工作量：中——需要新设计"零依赖 git hook for design-only repo"这个此前不存在的基建形态**。
- Gitea PR chokepoint：若消费产品用 Gitea + PR，直接复用支柱④ `pr-checks.yml` job 定义模式新增一个 job 跑 `audit:mockup-conformance`（需要 CI runner 能访问 Figma token，同支柱④ competitive URL 检查一样需要网络出口）。**工作量：小**（有范本）。

**③（如 owner 认可）活体比对进阶防伪**——§3.2 第 4 点的 Figma `lastModified` 交叉校验，把防伪从"结构完整"升到"时效+活体一致"。**工作量：中**（需要多一次 Figma API 调用，且要处理 token 不可用时的降级，同支柱④"网络不可达 → UNVERIFIED 不判死"的降级哲学）。

### 4.2 需 owner 先决策的架构分叉（不拍这些，②③无法排期）

1. **设计产出入口在哪**（已在 `full-lifecycle-assessment-2026-07-08.md §7` 第1条列出，本次 scoping 认为这是设计网 chokepoint 选择的**直接前置**，不是独立分叉）：mockup 交付主要走"消费仓库 git commit"还是"纯 Figma 无 repo touch"？决定 §4.1②的三选项里先做哪个/是否需要全做。
2. **变体 B 是否值得新建 git hook 基建**：如果绝大多数 Design-only 消费产品从不 `git commit` handoff（只是聊天里给 owner 看/贴 Figma 链接），那么再强的 pre-commit 闸也堵不到，这条 chokepoint 投入可能打水漂——需要 owner 确认"现在 Design-only 消费产品的 handoff 文档实际落地方式"（是否已经在被 commit）。
3. **是否要求 mockup 任务也走 PR**：同支柱② `full-lifecycle-assessment-2026-07-08.md §7` 第2条"owner-direct-master vs PR-for-all"分叉，如果答案是"需要"，则 chokepoint (c) Gitea PR checks 变成最优先选项（可直接复用支柱④基建，边际成本最低）；如果答案是"不需要"，则只能押注 (b) 消费仓库 pre-commit/pre-push。

### 4.3 一句话给 owner

**先做①（不需要等架构决策，独立堵住"文本可编造"这个最锋利的洞）；②③排期前，请先回答 §4.2 三个问题——尤其第2条，因为如果 Design-only 消费产品的 handoff 现在根本不进 git，那么无论 pre-commit 闸设计得多严密都是在给一个空仓库上锁。**

---

## 附录：本次一手验证事实清单

- `scripts/audit-mockup-handoff-evidence.mjs:9-15`（`checkHandoffEvidence` 纯函数，正则文本匹配，无脚本重跑/无 fileKey 校验）
- `.husky/pre-commit:290-297`（mockup handoff evidence gate 挂载点，仅在 staged `.md` 存在时触发，只存在于本仓库自身）
- `scripts/audit-mockup-conformance.mjs`（9 子审计总闸，`--file`/`--node`/`--non-blocking`，真实调 Figma API，本身无防伪问题，问题是覆盖率=0）
- `package.json:73`（`audit:mockup-conformance` npm script 存在，`grep pre-commit/CI/prepublish` 均未命中）
- `.claude/hooks/post-figma-write.sh`（PostToolUse 提醒 hook，只注入文本，Claude Code 专属，不拦截、不校验）
- `templates/consumer-product/docs/handoffs/`（handoff 文档实际落点，消费仓库内）；`grep -rln "audit-mockup-handoff-evidence" templates/consumer-product` 零命中
- `skills/setup-tvu-consumer/SKILL.md:74-80`（变体 A/B 定义；Step 2.6 规范接线仅变体 A；变体 B 无 npm/husky 基建）
- `scripts/validate-upstream-gate.mjs` 全文 + `scripts/upstream-gate.schema.json` 全文（支柱④双层范式：`x-gate-layer: deterministic/human/deterministic+human/machine-written`）
- `.gitea/PULL_REQUEST_TEMPLATE.md:54-60`（人工层 checklist，"不可自 ack 伪造"）
- `.gitea/workflows/pr-checks.yml:43`（`pnpm audit:upstream-gate` 已接 CI，mockup 对应闸未接）
- `docs/internal/ds-enforcement-scenarios-spec.md`（G4 `audit:mockup-conformance` 总闸建成史 + 场景感知 blocking/non-blocking 设计，佐证"闸本身够格，缺的是 auto-run 接线"）
