# Q1 · `#M23.8.1` 上闸预注册 —— 落点结构问题的答案是「问题不存在」

> ⛔ 写在动手之前。交接把这一条列为「**要棘轮设计**，且落点有个结构问题要先答：
> mockup 规则跑在 live payload 上，基线 key 在 (fileKey,nodeId,ticketId) 上对 live 面意义不明」。
> **本轮先量，结果是那三个断言都不成立。**

---

## 0 交接的三个断言，逐条被读数推翻

| # | 交接逐字 | 实测 |
|---|---|---|
| 1 | 「现取 **55 处**存量无链 ticket ID（来源段 17 · 散文 38）」 | 宽形态下是 **71** 处（40 个不同 TEXT 节点）；而按活源自己给的射程收窄后**只有 3 处无链** |
| 2 | 「⇒ 要**棘轮设计**」 | **不需要** —— 可落那一支的存量是 **7 处**（3 无链 + 4 链错），直接登记为存量比造一套棘轮便宜得多 |
| 3 | 「落点有个结构问题要先答：基线 key 在 (fileKey,nodeId,ticketId) 上对 live 面意义不明」 | **问题随 #2 消失** —— 不需要基线，就不需要 key。⚠️ 但那个顾虑**本身是对的**，理由已量出（见 §3），所以它值得留档：**这个判定面上不能建具名集合棘轮。** |

---

## 1 判据串逐字取自活源（`mockup-conventions.md:1158` 起，§M23.8.1）

**Acceptance**（散文一行，Σ=0，四个分句）：

> 需求来源段列全需求链上所有相关 ticket（Jira issuelinks 核对无遗漏）；`probe →
> getRangeHyperlink(idStart, idEnd)` 对每个 ID 返回非 null URL；交付物内无纯文本 ticket ID；
> 外链（Slack 等）同样带 hyperlink。

**射程从规则自己的「触发」那一行取**（⛔ 未发明任何新格式）：

> **触发**：写 / 改任何含需求溯源的 PRD / 交付卡时强制。**含"来源/出处"字样的 annotation 亦触发（第 5 款）。**

第 5 款逐字给出了那四个词：

> 任何 UX 交付卡 / annotation 一旦标注了「**来源 / 出处 / Source / 讨论结论**」等溯源字样，
> 该出处本身必须是 `setRangeHyperlink` 可点链接

⇒ 判据的射程 = 「**含那四个词的 TEXT**」，闭集**逐字来自活源**。

---

## 2 分母（现取，4 份本机 payload 快照，遍历 305,222 节点）

**全量 ticket range = 217**，先按 M23.8 主条的边界切开：

| 侧 | 数 | 归谁 |
|---|---:|---|
| 在 `Jira requirement` 实例内 | **112** | **M23.8 主条**（已上闸 DS `9834b949`） |
| 实例外，且**在第 5 款射程内**（含四词） | **23** | ⇒ 本闸 |
| 实例外，但**不在**射程内 | 82 | ⛔ 本闸不判，见 §4 |

**本闸射程内那 23 个的三态**：

| 态 | 数 | probe |
|---|---:|---|
| 无 hyperlink | **3** | `M23.8.1-A` |
| 有链但不是 Jira browse 地址 | **4** | `M23.8.1-B` |
| 合规（Jira browse 链） | **16** | 阴性对照 ✅ 非空 |

⇒ 合规率 **70%**（16/23）。存量违例 **7** 处。

---

## 3 🔴 那个「结构问题」的答案 —— 值得留档，因为它对**别的**同族判据仍然成立

交接的顾虑成立且已量出证据：**这个判定面上不能建具名集合棘轮。** 三条：

1. **key 里的 nodeId 是 live payload 的活 id，而 payload 未进 git**（`.gitignore:29`，1.1 GB）
   ⇒ 集合无法从仓内核对，任何「消失 ⇒ 报 FAIL 要求删行」的机制都会因设计师正常改动而误报。
   ⚠️ 这与刚落的 `#M-DISCIPLINE.SOURCE` **根本不同** —— 那一条的判定面是 4 份**进 git** 的 md，
   集合稳定、且 key 就是标识符本身。
2. **存量高度分散、且随文件增长**：宽形态那 71 处分布在 **24 个不同帧 / 10+ 个 page**，
   其中 `< BM-1047 >` 一个 page 占 **35 处（49%）**。每个新 mockup 文件都会带来自己的同类存量
   ⇒ 集合只会无限增长，永远到不了「表空 = 终态」。
3. **但常态摩擦不是 71** —— L4 执行点 `.claude/hooks/post-figma-write.sh` 头注释逐字
   「nodeId 从**写操作**捕获」，引擎支持 `--node` 且本规则 `scope: 'node'`
   ⇒ 常态判定面是**本次编辑的节点子树**。⚠️ 如实登记：这**不是零摩擦** ——
   编辑 `card-source` 那个帧会一次报 11 处（宽形态口径）。

### 3.1 为什么这一条 ⛔ 不进决策队列（§1.1 自查逐条过）

- **不是 C4**：不加豁免、不降判据、不扩 agent 权限
- **不是 C2**：存量是量出来的，不需要「遇到得多不多」这个只有 owner 才有的输入
- **不是 C3**：无真源冲突
- **不是 C1**：C1 逐字是「要新增 **blocking** 闸 / 要把 report-only 升成阻塞」。
  本条落在**已经 report-only 的引擎**里 —— `post-figma-write.sh` 逐字 `exit 0`（3 处）、
  后台跑、结果**下一轮**注入 ⇒ 加一条规则的代价是报文多几行，⛔ 不新增任何阻塞面，
  也 ⛔ 不碰 §0 那条「过度上闸 = 训练绕过」
- ⇒ **修法唯一 ⇒ agent 自己拍**

---

## 4 ⛔ 未覆盖的三支，各带理由与重开条件

| 未覆盖 | 为什么 | 重开条件 |
|---|---|---|
| **[2] 无条件的「交付物内无纯文本 ticket ID」**（射程外那 82 个 range） | 🔴 **宽形态在扩大的射程上有假阳，且形态上排不掉**。逐条实测：`Q2-1` **6 处**逐字上下文是「PM 已确认三组推荐方案（**Q2-1 方案D / Q4 方案③ / Q5 方案A**）」= 设计探索的问题编号 · `PID-1001` 逐字「Stop preview for PID-1001?」= 产品 UI 文案里的进程 ID。⚠️ 而**按前缀形态切不开** —— 真 project key 里 `V4`（**74 处**，最大一族）与 `TM3` 自己就含数字，「纯字母前缀」会砍掉它们。⇒ 排假阳只剩两条路：维护一份 project-key 闭集（上一轮已实测闭集会让**分母静默变小**：主条 `FB\|V4\|SEC` ⇒ scanned 109→78 而违例仍 0），或逐个具名豁免（而豁免表会随每个新文件增长 ⇒ 回到 §3 那个结构问题）| 出现一份可信的 Jira project-key 真源（如从 Jira API 拉全 project 列表并落盘）⇒ 届时闭集不再是发明 |
| **[1] 「列全需求链」** | 需 Jira issuelinks（且逐字要求**遍历到二度**）⇒ 判定面是 Jira API，不落盘 | Jira API 凭据进 CI（那是 Q6 `wire-ci` 待拍的事） |
| **[4] create/update 都遵守** | 时序断言（「后续新增关联时 update 必须回补」）⇒ 要两个时点的 payload 才判得了 | — |

⚠️ **[3] Slack 外链那一支本轮也不做**，但理由不同：它**可能可机检**（射程内的 Slack URL 有没有链），
只是本轮没量它的分母。⇒ 登记为「未量，不是不可测」，⛔ 别写成 NMC。

---

## 5 判据形态（⛔ 钉死）

**落点**：`scripts/audit-mockup-source-link-integrity.mjs`（纯函数 `classifySourceLinkIntegrity`）
\+ 模块 `scripts/mockup-rules/source-link-integrity.mjs`，经引擎注册表挂载 ⇒ RULES **15 → 16**。
挂载声明走 **D2**（经父闸挂载，同 `jira-hyperlink`）。

**判据零件全部 import 自 M23.8 主条，⛔ 不抄第二份**（`TICKET_RE` / `linkCovering` /
`hyperlinkUrl` / `JIRA_BROWSE_RE`）—— 那四个零件上一轮各有造故障承重，
且「parity 是结构性的，⛔ 不靠断言两份逐字相同的测试」是本仓已立的纪律。

| probe | 判据 |
|---|---|
| **M23.8.1-A** | 第 5 款射程内的 ticket range 无 hyperlink ⇒ 违反（逐字对应「对每个 ID 返回非 null URL」） |
| **M23.8.1-B** | 有链但 URL 不是 Jira browse 形态 ⇒ 违反（逐字对应「必须 `setRangeHyperlink` **指向真实 Jira URL**」） |

**射程边界（两条都要在报文里印出来）**：
- ⛔ 跳过在 `Jira requirement` 实例内的 range —— 那是 **M23.8 主条**的射程，重复判会双报
- ⛔ 只判含活源那四个溯源词的 TEXT —— 射程外那 82 个**只报个数**，⛔ 不判（照
  `jira-hyperlink` 已有的 `outsideCount` 形态）

**分母 0 ⇒ `unverified`**：一次交付里没有任何带溯源字样的 annotation 是**正常**的。

⚠️ **要同步更正主条那段注释**：`audit-mockup-jira-hyperlink.mjs` 的模块里逐字写着
「另有 N 个名字像 issue-key 但不在 `Jira requirement` 实例内的 TEXT —— ⛔ 本闸按 §M23.8 的
字面射程不判它们，只报个数」⇒ 落地后要补一句「**其中含溯源字样的那些已由 `source-link-integrity`
判**」，否则读者会以为那批仍无人看。

---

## 6 造故障清单 + 预测

⚠️ 本轮已两次栽在同一处 ⇒ 固定动作：**先列出这个故障会改变哪些测试 helper 的输出，再数用例。**

| # | 造什么 | 预测 |
|---|---|---|
| **F-T1** | 拆掉「第 5 款射程」这个约束（改成扫所有 TEXT） | 真面分母 **23 → 105**、违例 **7 → 75**（含 `Q2-1`/`PID-1001` 那 7 处假阳）；单测侧转红 = 射程阴性对照 |
| **F-T2** | 拆掉「跳过 Jira requirement 实例内」 | 真面分母 **23 → 135**（+112 主条射程）⇒ 与主条双报；单测侧转红 = 那条边界对照 |
| **F-T3** | 拆掉 probe B（只判无链） | 真面违例 **7 → 3**；单测侧转红 = B 的正向用例 |
| **F-T4** | 删掉 `covers-acceptance` 自陈 | `[S2] unclassified 37 > BASELINE 36` + 闸自印「⛔ 别改 BASELINE 放行」 |

⛔ 注入先验落盘 + 先验能 `import`（上一批 F-S5 第一版把语法弄坏过）。

## 7 必跑闸 + 基线

`acceptance-gate-coverage`（棘轮 37 → **36**）· `stale-anchors` · `rule-inventory` · `doc-shape` ·
`gate-mount-declaration` · `rule-load-map` · `gate-ci-parity`（⚠️ 上一批被它抓过，这批列进清单）
\+ 全量 `pnpm test`（基线 **3346 passed**）。

⚠️ ⛔ 不改规则正文一个字 ⇒ Σ 零变化。

## 8 ⛔ 落地后不能说的话

- ⛔ 不许说「`#M23.8.1` 已守住」—— 四个分句只覆盖第 2 个的**一部分**（第 5 款射程内那一段）
- ⛔ 不许说「交付物内已无纯文本 ticket ID」—— 射程外那 82 个 range 里仍有 **64** 个无链/链错，⛔ 无人看
- ⛔ 不许说「存量 55」或「需要棘轮」—— §0 已推翻
- ⛔ 不许说「7 处存量是闸抓到的」—— payload 未进 git ⇒ 那是**本机读数**

---

## 9 ✅ 落地回填（2026-09-08）

### 9.1 实测读数

| 项 | 预测 | 实测 |
|---|---|---|
| `unclassified` | 37 → 36 | **37 → 36** ✅（`gated` 46 → **47**） |
| 引擎规则条数 | 15 → 16 | **16 条**，`source-link-integrity` 在列 ✅ |
| 真面 `scanned` / A / B / 合规 | 23 / 3 / 4 / 16 | **23 / 3 / 4 / 16** ✅ |
| 真面 `outOfScope` / `insideMain` | 82 / 112 | **82 / 112** ✅ |
| 八条必跑闸 | 全 0 | 全 **EXIT=0**（含 `gate-ci-parity`，上一批被它抓过所以这批列进清单） |
| 全量 `pnpm test` | 3346 + 16 = 3362 | **3362 passed** / 0 failed ✅ |
| Σ 变化 | 0 | **0** ✅ |

### 9.2 造故障四次（单测侧预测全中）

| # | 预测 | 实测 |
|---|---|---|
| **F-T1** 拆掉第 5 款射程 | 单测 1 条 · 真面 23→105 / 违例 7→75 | **恰好 1 条**；真面 **23→105** · 违例 **7→75**（A 3→71）✅ 逐字对上 |
| **F-T2** 拆掉「跳过 Jira 实例内」 | 单测 3 条 · 真面 23→135 | 单测 **恰好 3 条**（正是预测那 3 条）；⚠️ **真面预测错**，见 9.3-① |
| **F-T3** 拆掉 probe B | 单测 1 条 · 真面 7→3 | **恰好 1 条**；真面违例 **7→3**（B 4→0）✅ |
| **F-T4** 删自陈 | `[S2] 37 > BASELINE 36` | `EXIT=1` · 逐字 `[S2] unclassified 37 > BASELINE 36（+1）` + 闸自印「⛔ 别改 BASELINE 放行」 |

四次还原后 `cmp` 均 **EXIT=0**；每次注入都先验落盘**并先验能 `import``（上一批栽过语法坏掉那次）。

### 9.3 🔴 预注册没预见到的

**① F-T2 的真面预测错了（预测 23→135，实测仍 23）—— 而那个错抓出一个更好的读数。**
我按「拆掉跳过 ⇒ 主条那 112 个 range 会涌进来」算，漏了**射程是 AND 条件**：
那 112 个 range 还必须落在「标了溯源字样的 TEXT」里才进分母。
实测它们**一个都不带**那四个词（`Jira requirement` 实例内的 issue-key TEXT 的 `characters`
就是纯 ticket ID）⇒ **两条闸在今日真面上不只互补、而是正交**：双报风险实测为 **0**。
⚠️ 判据仍显式跳过（前向 fail-closed）—— 哪天有人把「来源：」写进那个实例内的 TEXT，
跳过那一支就开始承重。⇒ 这条的牙由**单测**那 3 条承担，⛔ 不由真面读数。

**② ~~量出一条本轮之前没人量的缝：标了溯源字样的 TEXT 有 296 个，而本闸只在 23 个 range 上有牙。~~**
~~`markedTextCount` 现取 **296**（实例外、含那四个词的 TEXT），其中带 ticket 引用的只有少数
⇒ 第 5 款逐字要求的「**该出处本身必须是 `setRangeHyperlink` 可点链接**」（Slack 线程 /
Confluence / 外部文档）那一半，**分母比 ticket 那一支大一个数量级，且今天几乎全无人看**。
⇒ 这不是本闸的缺陷（本闸的射程从没声称覆盖它），但它是**下一批的具体抓手**：
比继续啃 [2] 那 82 个假阳混杂的 range 划算得多。⚠️ 预注册 §4 那句「[3] 本轮只是没量分母」
现在有数了：**296 个候选 TEXT**。~~

🔴 **2026-09-09 就地更正 —— 上面这一整段是错的，⛔ 别照它动手。**
下一轮（本轮）去量了那 296 个，读数落盘在
[`2026-09-09-q1-m23-8-1-clause5-denominator-readings.md`](2026-09-09-q1-m23-8-1-clause5-denominator-readings.md)。
「分母大一个数量级 + 几乎全无人看」两半都不成立：

- **296 不是分母** —— `Source` 在 TVU 同时是**产品第一术语（信号源）**，那 296 个里绝大多数是
  `Source Switcher`（26 处）· `Source From`（8 处）· `Selected Source:` · `IP Source` ·
  `No Source` 这类 UI 文案。真溯源标记只有个位数，**且全部已有链**。
- **「几乎全无人看」指向的那个缺陷今天不存在** —— 「溯源 annotation 里有裸 URL 文本却没加链」
  实测 **0 处**。我当时想象的画面（设计师把 Slack 链接贴成纯文本）在真面上没有实例。
- **形态与语种两个方向都切不开** —— `Selected Source:` 与真溯源标记 `Source: BM-603` 同型；
  英文 `Source:` 里有真溯源、中文「数据来源 DC 服务」里是产品语义。
  试过用既有 `ANNOTATION_RE` 当第二个 AND：假阳只砍一半，真阳漏掉 6/11。

⇒ 那一支判**不上闸**（⛔ 不是 NMC —— 判定面在 payload 里），重开条件两条，见读数文件 §2.2。

🔴 **这一段的成因值得单独记住：那句假抓手的来路是我自己印在报文里的一个数。**
模块第 50 行当时裸印「标了溯源字样的 TEXT 296 个」，读起来像「296 个溯源 annotation 里
只有 23 个有牙」。⇒ 本轮真正的产出是**改掉那个读数**（DS `d9cd4aa5`）：报文现在同时印
它到底是什么，且新加一条用例把「`markedTextCount` 不是分母」钉成断言。
⚠️ 与本仓已登记的 `verify-handoff-literal-before-executing` 第七问逐字同族 ——
**这份「交接」是我自己写的，是也一样要回活源核。**

**③ 交接的三个断言全被推翻，这本身值得当成一条读数留档。**
「55 处存量」「要棘轮设计」「落点 key 在 (fileKey,nodeId,ticketId) 上意义不明」——
第 3 条的顾虑**是对的**（§3 已量出证据：这个判定面上确实不能建具名集合棘轮），
但它的前提（需要棘轮）不成立 ⇒ **一个正确的顾虑挂在一个错的前提上，会把落地卡在一个
不存在的问题上**。⇒ 与本仓已有的「照交接字面动手前先推演」同族，且这次推演的对象是
**「这件事真的需要那个机制吗」**，而不只是「那个机制怎么建」。
