# INFRA-F95 文档体量 enforcement —— 段级形态契约闸（`audit:doc-shape`）设计

> 日期：2026-08-03 · 轨道 B（流程 · 文档治理） · 对应 backlog [[INFRA-F95]] **待做 ①**
> 状态：✅ owner 已审（2026-08-03）→ 下一步转 `writing-plans`
> 数字真源：本文件所有实测值均为 2026-08-03 17:40 前后实跑；**STATUS/backlog 每工作日各涨十几到三十几 KB，引用前先重量**

---

## 1. 问题（不复述 F95，只锁本 spec 要解的那一面）

F95 的诊断是「**缺的只有 enforcement**」：正确做法 `WRAP-UP.md:13`/`:18` 早在 2026-06-09 就写下了，且诊断一字不差，但**纯 L1、无人执行**，3 周内 4 次手动精简全部在 2-4 天内被填平。

本 spec 只解 F95 **待做 ①**：**闸能机械看见什么**。待做 ②（4 处漂移就地修）由后续 plan 承接；待做 ③（`docs/` 278 份 / 6.11 MB 的三大件判定）**明确不在本 spec 范围**，另立。

### 1.1 关键机械事实

增量**全长在既有行内部，行数几乎不动**（STATUS 206→222 行）→ **任何按行数/diff 行的检查结构上必然瞎**。信号只在字节层。

### 1.2 实测靶子（2026-08-03）

`docs/STATUS.md` 全文 60 247 B，可机械切分为四段：

| 段 | 切分判据 | 实测 | 既有规则规定它该长什么样 |
|---|---|---|---|
| **顶部摘要区** | 文件头 → 首个 `^> ### ` 行之前 | **9 359 B** | `WRAP-UP.md:13`：「顶部只放**当日一条摘要 + 指针**」 |
| open 清单区（§一/§二/§三） | 首个 `^> ### ` → 首个 `^---` | 19 021 B | 无明文形态规定（open 项，合法内容） |
| **Active 后续工作 fence** | `## Active 后续工作` 后首个 ``` 块 | **10 914 B**（单行 132 = 10 520 B） | `WRAP-UP.md:18`：「刷新剩余**项数**」；`STATUS §SoT 归属表`：「STATUS 是**计数**镜像」 |
| 其余 | — | 20 953 B | — |

**两个受管段合计 20 273 B = 全文 33.7%**，且**都有明文规定的形态、实物完全不符**。

---

## 2. 阈值的推导（本设计最关键的一节）

F95 ⛔③ 要求「阈值要在稳定态量过噪声底再定」。**这两个文件没有稳定态**（同一天内 backlog ±6 KB）。本设计绕开这个前置——**阈值不从噪声推，从同一文件内既有的合规样本推**：

### 2.1 `TOP_MAX`（顶部摘要区上限）

顶部 9 359 B 逐行拆解：

| 内容 | 行 | 字节 | 性质 |
|---|---|---|---|
| `<details>` 归档块 | 13-23 | **5 256（56%）** | 自称「已归档，展开仅为检索方便」= `STATUS-CHANGELOG` 的**第二副本** |
| 三条「上几轮已归档」指针 | 7 / 9 / 11 | 1 455 | 三条说同一件事，可并成一条 |
| 当日摘要 | 3 | 632 | ✅ 合法常驻 |
| owner 裁定 | 4 | 416 | ✅ 合法常驻 |
| 接手人必须知道的一条 | 5 | 207 | ✅ 合法常驻 |
| §一 编辑判据 | 25 | 310 | ✅ 合法常驻 |
| 并行 session 纪律 | 27 | 1 029 | ✅ 合法常驻 |

**合法常驻实测和 = 2 594 B** → **`TOP_MAX = 3 000 B`**（给「当日一条摘要」留 ~400 B 呼吸空间）。

> `<details>` 块是 owner 2026-08-03 裁定「**唯一真源、别处一律引用**」的直接命中对象：它明说自己是归档件的副本，删掉**零信息损失**。

> ⚠️ **审查期发现：上表把两条「错位的规则」算进了合法常驻，所以 3 000 偏松**。
> 行 25（§一 编辑判据 310 B）与行 27（并行 session / commit 显式路径纪律 1 029 B）**都是规则，不是状态** —— 按 STATUS 自己的 SoT 归属表，「AI 协作硬规则」的家是 `AGENTS.md`。**已实测：`AGENTS.md` 里没有行 27 那条纪律的正本** → 它不是第二副本，是**唯一副本放错了地方**，搬迁是真搬（不能直接删）。
> 剔掉这 1 339 B 后，顶部真正该常驻的只有 **1 255 B**（当日摘要 632 + owner 裁定 416 + 接手人一条 207）。
> **本 spec 保留 `TOP_MAX = 3 000` 不改**（它离现值 9 359 仍有 3× 压缩要求，闸有效），但把这两条列入 §4.2 `top-summary` 豁免的 `fix` 清单；待它们搬走后，`TOP_MAX` 可另议收到 1 500–2 000。**这是 owner 的编辑判断，AI 不自行收紧。**

### 2.2 `ACTIVE_LINE_MAX`（Active fence 行上限）

同一个 fence 里已有三条**合规形态**的实测样本：

```
design-review-queue (3):  …           185 B
Code-leads divergences (3): …         139 B
Deferred            (3):  …            58 B
Backlog (active)  (29):  …         10 520 B   ← 唯一违例
```

**既有合规行最大值 = 185 B**。但真正的下界不是它 —— 是**那条最长的行合规之后会有多长**。

**实测（2026-08-03，不是估算）**：用 backlog `## Active` 里今天全部 **28 个未闭合 ID** 拼出的最简合规行 —— `Backlog (active)  (28):  CANONICAL-F92·…·META-DSYNC-01` —— **实测 321 B**。

> ⚠️ **初版写的 `ACTIVE_LINE_MAX = 400` 余量只有 79 B ≈ 7 个新 entry**，backlog 一旦长到 35 条，**闸就变成不可满足**（把叙述删光也过不了）。不可满足的闸会被 `--no-verify` 绕过，然后死掉。
>
> → **`ACTIVE_LINE_MAX = 600 B`**（321 B 实测 + 约 25 个新 entry 的余量）。这是审查期改的数，**推翻 §6.1 批准的 400**，理由是实测余量而非偏好。

> 判据按**行**不按段：行级判据直接命中「增量长在既有行内部」这个机械成因，也贴合规定形态（每类一行 `名称 (N): ID 列表`）。

---

## 3. 三条候选路线与取舍

### 路线 A（否）—— 全文字节上限

对 `STATUS.md` / `backlog.md` 各设一个全文字节上限。

- ❌ **是形态代理，且逃逸方向背离目标**：per-file 上限只会把内容挤到 `STATUS-CHANGELOG.md`（已 338 KB）或新建文件，**总量不降**；而「onboarding 预算失真 2.2×」那条已付代价恰恰是**跨文件**的。
- ❌ 阈值无处可推（无稳定态），落地当天必然红 → 逼出 F95 ⛔① 明令禁止的「先删一遍」。
- **重开条件**：若将来出现「L-core 四份合计字节」这个跨文件预算口（把 `AGENTS.md` §Onboarding Gate 与 STATUS §起手必读链路 那两处「~40KB」变成脚本印出的实测值），全文上限可作为该预算口的子项重开。

### 路线 B（否）—— 字节棘轮（shrink-only ratchet）

把今日字节数写进 checked-in baseline，之后只许降不许升。

- ✅ 唯一能**保证**体量真降的形态，且今天就绿（baseline = 现值）。
- ❌ 与真实工作节奏正面冲突：backlog **合法地**要加新 entry；棘轮会把「记录一个新缺陷」变成违规操作。
- ❌ 逃逸同路线 A（挤去别处），且每次 commit 都要更新 baseline = 持续噪声。
- **重开条件**：若形态契约（路线 C）上闸满 4 周后，`S4` report-only 印出的日增速仍未回落到手术前水平（STATUS ≤8.1 KB/工作日 · backlog ≤14.8 KB/工作日），说明形态约束不足以抑制速率，届时按棘轮重开。

### 路线 C（**推荐 · 本 spec 采用**）—— 段级形态契约

**把 `WRAP-UP.md:13`/`:18` 与 SoT 归属表「计数镜像」这三条既有 L1 规则升到 L4+L5**，判据锚在「这一段该长什么样」而不是「文字要短」。

- ✅ **不发明新标准**——**判据形态**全部来自仓库已有的明文规定。（⚠️ 精确说：具体**数值**仍是新东西，规则说的是「一条摘要」不是「3 000 B」；所以 §6.1 仍需 owner 批准阈值。这里省掉的是「多大算大」的**主观争论**，不是省掉批准本身。）
- ✅ **阈值有实测依据**（§2），绕开「无稳定态量不了噪声底」的死结。
- ✅ **密度规避无效**：约束的是「该段只许放摘要/计数」，把散文写得更密仍然超；而写作者被迫把内容挪去 `STATUS-CHANGELOG`（顶部）或 backlog entry（Active 行）——**逃逸路径收敛到规则本来规定的去处**。
- ✅ 落地形态可**直接照抄** `audit:rule-inventory` 的具名带日期 shrink-only 豁免表，无需新造机制。
- ⚠️ 代价：只管两个段（33.7%），其余 66.3% 只做 report-only 观测。**这是有意的 YAGNI**——其余段没有明文形态规定，硬加约束就是发明新标准。

---

## 4. 设计：`audit:doc-shape`

- **脚本**：`scripts/audit-doc-shape.mjs` · **script 名**：`pnpm run audit:doc-shape`
- **Enforcement 层级（meta-rules 触发器 K 要求显式声明）**：**L4**（pre-commit，staged 命中 `docs/STATUS.md` / `docs/internal/backlog.md` 时条件触发）+ **L5**（`prepublishOnly` 链，与 `audit:status-consistency` 同位；Gitea `pr-checks.yml` 无 `paths` 过滤，自动跟随）

### 4.1 判据

| 码 | 判据 | 阻塞 | 依据 |
|---|---|---|---|
| **S1** | 顶部摘要区字节 ≤ `TOP_MAX`(3 000) | ✅ FAIL | `WRAP-UP.md:13` |
| **S2** | Active fence **每行**字节 ≤ `ACTIVE_LINE_MAX`(600) | ✅ FAIL | `WRAP-UP.md:18` + SoT 归属表「计数镜像」 |
| **S3** | open 清单区的**条目标题段**不得标记为已完成 | ✅ FAIL | owner 2026-07-30 判据「不保留已完成的任务，**加 `✅` 留着也不算合规**」（STATUS:25 已写死） |
| **S4** | 印出 STATUS/backlog 全文字节 + 四段分布 + 各豁免的余量 | ❌ report-only | F95「无任何闸看体量」 |

#### S3 的判据收窄（设计期实测推翻了初版）

「编号条目」的机械定义 = open 清单区内匹配 `^> \d+\. ` 的行（STATUS 的条目是**单行**结构）。

初版判据「条目行含 `~~` 或 `✅`」实测 **9 条命中，其中 4 条是误报**——条 5 / 9 / 17 / 19 的 entry 本身仍 open，划掉的是 entry **内部的子项**（如条 5 的 `~~冒烟闸只覆盖 exports['.']~~`）。收窄为**只看条目标题段**：

| 子判据 | 正则 | 今日命中 |
|---|---|---|
| **S3a** 标题被整条划掉 | `^> \d+\. ~~` | **5 条**（§一 1/3/4/10 + §三 18） |
| **S3b** 标题段（`^> \d+\. ` 后 120 B 内）有 `✅` 但无划线 | `^> \d+\. .{0,120}✅` 且非 S3a | **0 条** |

> **S3a 的 5 条与 F95 entry 独立记录的「§一 那 5 条『已完成但保留一句』的常驻事实（5 697 B）」逐条吻合** —— 两个独立来源对上，是判据正确的实证，不是巧合。
>
> **S3b 今日 0 命中**：它堵的是 owner 判据里「加 `✅` 也不算合规」那条路，当前无实例，属**防御性判据**。⚠️ 零命中的判据最容易是空判据 —— §5 验收表必须为它单独造一次故障，证明它真能变红。

**S3 与 `audit:status-consistency` C4 不冲突**：C4 只看 `backlog.md ## Active` 的 `### ` heading（闭合 = heading 加 `~~`），S3 只扫 `STATUS.md` 的 open 清单区，**扫描面不相交**。

### 4.2 豁免表（shrink-only，照抄 `audit:rule-inventory` 范式）

落地当天两个段都超阈值。**不以「先删一遍」起手**（F95 ⛔①），改为：

- 每条豁免 = `{ file, section, ceiling(当日实测值), date, fix(修法方向) }`，具名、带日期、带修法方向
- **上涨即红**：实测 > `ceiling` → FAIL
- **修完由闸自己宣布**：实测 ≤ 该段阈值 → FAIL 并要求**删除这行豁免**（不再命中却还挂着 = 违规）
- **豁免表空着是终态，不是待办**

落地当天**三条**豁免（三条判据各一条）：

> ⛔ **`ceiling` 必须在闸落地那一刻现量，禁止照抄下表里的数**。下表是 2026-08-03 17:40 的快照，而**本 session 自己的 wrap-up 就会改 STATUS 顶部当日摘要 → 顶部字节必然已经不是 9 359**。照抄 = 闸落地当天即红，而且红的原因是记错了基线、不是真违规。

| file | section | 判据 | ceiling | fix |
|---|---|---|---|---|
| `docs/STATUS.md` | `top-summary` | S1 | 9 359 B | `<details>` 归档块（5 256 B）搬 `STATUS-CHANGELOG`；三条「已归档」指针并成一条 |
| `docs/STATUS.md` | `active-line:Backlog (active)` | S2 | 10 520 B | 降回 ID 列表 + 数字，叙述回各自 backlog entry |
| `docs/STATUS.md` | `completed-entries` | S3a | **5 条** | 按 owner「唯一真源、别处引用」裁定各归各家；F95 已实测三份真源副本**都已在位**（`DEPLOY.md` / `check-dist-wc-freshness.mjs` 头注释 / `RELEASING.md` Step 4）→ 删第二副本不丢信息 |

> S3 的豁免 `ceiling` 是**条数**不是字节，shrink-only 同理：6 条即红，降到 4 条要更新 ceiling，降到 0 条则闸要求删除该行豁免。
> ⚠️ **S3b（标题段有 `✅` 无划线）不开豁免** —— 今日 0 命中，任何新出现的实例应当**立即红**。这正是「具名豁免只赦免存量、新缺陷照样红」的形态。

### 4.3 Fail closed（`new-gate-acceptance-three-questions`）

以下一律 **exit 1**，不得当 PASS：

- `docs/STATUS.md` 读不到 / 为空
- 切不出顶部摘要区（无 `^> ### ` 行）
- 找不到 `## Active 后续工作` 或其后的 ``` fence
- 受管段实测为 0 字节（形态被整体删掉 ≠ 合规）
- 豁免表里的 `file`/`section` 在当前文件里已不存在（stale 豁免）

**参数形态**：本闸不接受任何 CLI 参数；`pnpm run audit:doc-shape -- <任意>` 收到多余参数时 exit 1（防 `pnpm -- ` 透传把 `--` 当参数的假绿）。

### 4.4 输出契约（反模式 #4：下游怎么消费）

下游消费者 = **wrap-up 时的 AI**。FAIL 信息必须含「该搬去哪」，否则重蹈 `WRAP-UP.md:13` 覆辙（写了没人执行）：

```
❌ audit:doc-shape FAIL
  [S1] docs/STATUS.md 顶部摘要区 4 210 B > 上限 3 000 B（超 1 210 B）
       规定形态：当日一条摘要 + 指针（WRAP-UP.md:13）
       超出内容搬去：docs/internal/STATUS-CHANGELOG.md 顶部
  [S2] Active fence 行 "Backlog (active)" 1 780 B > 上限 600 B
       规定形态：计数镜像 = ID 列表 + 数字（WRAP-UP.md:18 / SoT 归属表）
       超出内容搬去：docs/internal/backlog.md 各自 entry
```

### 4.5 扩展点（反模式 #5：将来扩展改哪里）

受管文件与段的定义集中在脚本顶部 `MANAGED_SECTIONS` 一张表，加新受管段 = 改这一处。

⚠️ **F95 ⛔④ 的连带义务**：本 spec **不拆、不移动任何规则文件**，因此 `audit:rule-inventory` 的 S4/S6 硬编码扫描面**无需变更**。若后续 plan 决定搬迁规则文件，必须同步扩扫描面 + 造故障验证真被扫到。

### 4.6 C4 语义改动的连带效应（§6.2 批准后新增 · 必须编成一个原子步）

owner 批准把 backlog 闭合约定从「heading 加 `~~删除线~~`」改成「**完成即删除 entry**」。**这不是一处改动，是三处，漏一处闸落地当天自己就红**：

1. `audit:status-consistency` C4 的计数逻辑：现在 `open = 总 heading − 删除线 heading`。移除删除线语义后，**现存 2 条删除线 heading 会被重新算成 open**，实测 open 从 **28 → 30**
2. 那 2 条已闭合的 entry 必须**同批真删掉**（否则 C4 的 30 与 STATUS 声明的 28 对不上，闸红）
3. STATUS 行 132 的 `Backlog (active) (N)` 数字同步

**顺序不可交换**：先删 entry → 再改 C4 → 最后同步 STATUS 数字。任一步单独提交都会让 master 上出现一个红闸窗口。

### 4.7 覆盖面自印（F87 教训：别据此宣称「已守住」）

闸每次运行必须自印覆盖面，且 spec 在此显式声明：

- **`docs/STATUS.md`**：S1 / S2 / S3 三条**阻塞**判据
- **`docs/internal/backlog.md`**：**只有 S4 report-only，无任何阻塞判据** —— 本闸**不守 backlog 体量**（207 363 B、日增 32.5 KB 的那一面无人管，仅被观测）

⛔ 不得因本闸绿而宣称「文档体量已受控」。backlog 侧的约束是否要加，取决于 §3 路线 B 的重开条件是否触发。

---

## 5. 验收（造故障 → 看红，缺一不可）

| # | 注入 | 期望 |
|---|---|---|
| 1 | 顶部塞 1 KB 文字 | S1 红，且印出超出字节 + 搬迁目的地 |
| 2 | Active fence 某合规行加 300 B | S2 红，点名该行 |
| 3a | §一 新增一条 `> N. ~~标题~~` 形态的条目 | S3a 红（存量 5 条已豁免，第 6 条即红） |
| **3b** | §一 新增一条 `> N. **✅ 已完成** …` 形态（有 ✅ 无划线） | **S3b 红** —— 该判据今日 0 命中，**不造这次故障就无法排除它是空判据** |
| 3c | 阴性对照：在条 5 那种仍 open 的条目**内部**再加一处 `~~子项~~` | **仍 PASS**（证明收窄判据没退回初版的 4 条误报） |
| 4 | 删掉 `## Active 后续工作` heading | exit 1（fail closed，非 PASS） |
| 5 | `docs/STATUS.md` 置空 | exit 1 |
| 6 | `pnpm run audit:doc-shape -- --foo` | exit 1，不假绿 |
| 7 | 把豁免的 `ceiling` 手动调到实测值以下 | 红（上涨即红路径） |
| 8 | 把顶部真降到 3 000 B 以下但豁免行还在 | 红并要求删该行豁免（自宣布路径） |
| 9 | 阴性对照：不改任何文件 | PASS（三条豁免在位时应绿） |
| **10** | **`git add docs/STATUS.md` 后真跑一次 commit** | **pre-commit 输出里必须出现本闸的执行行** —— 不是「脚本能跑」，是「**闸真的挂上了**」 |
| **11** | 改一个与 STATUS/backlog 无关的文件后 commit | 本闸**跳过**（条件触发正确，不拖慢无关提交） |
| 12 | C4 改动后：删掉那 2 条删除线 entry 前先跑 `audit:status-consistency` | 应红（证明 §4.6 的三步顺序不可交换，不是我推测的） |

> **验收 10 是本条 spec 最不能省的一项**。F95 的病根就是「规则写了、没人执行」（`WRAP-UP.md:13` 写于 2026-06-09，无人执行）—— 闸写好但没真挂上 pre-commit / CI，就是**同一个病换个形态复发**。反模式 #7：声明 ≠ 被消费。

**验收纪律**（memory `regression-pass-needs-fault-proof`）：先断言故障态成立，再看闸转红；全 PASS 前必须有一条**不带修复的致败探针**证明测试不是空过。

---

## 6. Owner 决定（2026-08-03 已批准）

1. **阈值批准**：`TOP_MAX = 3 000` / `ACTIVE_LINE_MAX = 400`。按 §2 的同文件合规样本推导落地。
2. **backlog 闭合约定批准改为「完成即删除 entry」**：implementation plan 必须同步修改 `audit:status-consistency` C4，移除「heading 加 `~~删除线~~` 即合法闭合」的旧语义，并覆盖故障注入与 fail-closed 验证。

---

## 7. 不做（YAGNI）

- ❌ 不给 open 清单区（§一/§二/§三）设字节上限——那是 open 项，无明文形态规定，加约束 = 发明新标准
- ~~❌ 不动 `audit:status-consistency` C4~~ → **已被 §6.2 owner 裁定推翻**：C4 必须同步改，连带步骤见 §4.6
- ❌ 不碰 F95 待做 ③（`docs/` 三大件）——独立 scope，另立
- ❌ 不和 [[INFRA-F58]] 残余② 规则三层化混做（F95 ⛔②：已测出器械与目的不匹配）
- ❌ 不碰任何 `.css`/`.vue`（无需 `VISUAL_COMMIT_APPROVED`）
