# S10 缺口总账 · 实测复核版（2026-09-17）

> **为什么有这份文件**：`S10-CONSTRUCTION-HANDOFF-20260917.md` 的行号写于 **3.0 发版之前**，
> 且**缺口 1 的前提被 3.0 的默认值翻转改写了**（原文说"默认 legacy ⇒ 整条链不启动"，已不成立）。
> 本轮逐条 grep 重核，**以本文为准**；原 handoff 保留作施工语境。
>
> **复核方法**：只认「有调用点/有实现」才算已实现。全部结论附当前行号（`D:\dsh-auto-memory`，2026-09-17 复核）。

---

## 0. 一句话结论

**六个缺口里，1 个已被 3.0 发版顺带改掉症状（但根缺陷仍在），5 个仍在。**
其中**白板线的"接线"问题是这批的核心**——不是没写代码，是**线没接完**：写线通了、注入线通了、检索线断了一半。

---

## 1. 逐缺口复核

| # | 缺口 | 原判 | **实测结论** | 现在的严重度 |
|---|---|---|---|---|
| 1 | 锚点写入被 `boardMode` 门控 | P0 默认不生效 | ⚠️ **症状已消失，根缺陷仍在** | P1 |
| 2 | 白板进注入不进检索 | P0 | ❌ **确认仍在，未动** | **P0（最高性价比）** |
| 3 | 索引派生（S10.2） | P1 零实现 | ⚠️ **半对**：派生真在跑，缺契约行格式与两字段 | P1 |
| 4 | lint（S10.3） | P1 零实现 | ❌ **确认零实现** | P1 |
| 5 | 账本跳过用户区保护 | P2 | ❌ **确认仍在** | P2 |
| 6 | 死导出 | P2 | ⚠️ **范围收窄**：多数有测试消费者，真死只有 2 个 | P2 |

### 缺口 1 · 已降级（前提被 3.0 改写）

| 项 | 原文档 | **实测（现在）** |
|---|---|---|
| `boardMode` 默认 | `'legacy'`（`index.js:219`） | **`'graph'`（`index.js:221`）** |
| `handoffEnabled` 默认 | `false` | **`true`（`index.js:312`）** |
| 锚点写入（PLAN） | `:1988` 被门控 | `:1993` 仍被门控（graph 档才写） |
| 锚点写入（账本） | `:2032` 被门控 | `:2037` 仍被门控 |
| sidecar 早退 | `:2076` | `:2081` |

**⇒ 默认配置下全链已激活**（锚点/ sidecar / tag 地图 / expand+trace 工具都活）。
**但"格式契约耦合渲染开关"没修**：用户手动切回 legacy ⇒ 锚点立刻停摆。
建议仍做解耦，但**不再是发版阻断项**，也不用赶。

### 缺口 2 · 白板进"注入"不进"检索"（❌ 确认仍在）

- 注入 ✅ `index.js:4706` `add('whiteboard', s.planText, s.planPath)` → Tier-0 目录（五来源之一）
- 检索词法臂 ❌ `pushL0` 仅四来源：`:5510` 日志 / `:5511` 反思 / `:5512` 项目笔记 / `:5513` 用户级
- 检索语义臂 ❌ `semSources` 仅四来源：`:5709-5712` 同上四类
- **两处都没有白板。** `p.planPath` 变量存在（`:1828` / `:1890` 定义，`:4706` 在用）。

**⇒ 白板每轮被注入，但 `memory_recall` 搜不到它。** 这是全批最小改动、最高收益的一项。

> ⚠️ 改的时候要一并判两件事（原 handoff §缺口 2 已指出，复核确认成立）：
> 1. `tier0-catalog-pre.js:353` 的 `floorLayers: ['whiteboard','user']` 是 **Tier-0 注入侧**的保底配额；
>    **L0 检索侧没有同类保护** ⇒ 补进 `pushL0` 后，白板可能又被 `project` 层挤掉。
> 2. `handoffEnabled=false` 时白板不读不写（`:3586` / `:4160` 等处已解耦）——补检索来源时要同样判这个门，
>    否则会去读一个"按设计不该读"的文件。

### 缺口 3 · 索引派生：不是没做，是没输出成契约那一行

**家底（复核确认，别重复造）**：`rebuildSidecarIndexPre`（`wb-sidecar-pre.js`，`index.js:2125` 有调用）**真在派生**。
实测本机 `~/.dsh/memory/workspaces/--D--dsh-auto-memory--/handoff/index.json`：

```
entries: 150 | by_tag: 100 个 key | by_cue: 0 个 key | versions: 53 个 key
entry 字段: id, kind, source, section, tags, cues, preview, mtime, criteria, title, cue, chars, ts
```

**缺的三样（实测确认）**：
1. entry **无 `status` 字段**（契约行要有 `status=current`）
2. entry **无 `layer` 字段**（契约行要有 `layer=whiteboard`）
3. 没拼成契约那一行的字面格式（`WB-FORMAT-CONVENTION.md` §1）

> 🔎 **附带发现（不是缺口，是线索）**：`by_cue` 实测 **0 个 key 而 `cue` 字段每条都有** ⇒
> 倒排没能从条目里聚出来。这是**待查疑点**，正好与用户要报的白板 bug 可能相关，先不下结论。

### 缺口 4 · lint 零实现（❌ 确认）

全仓 grep `lintWhiteboard|lintPlan|wbLint`：**命中全在文档里**——
`docs/internal/PLAN-gpt6astra-round2-20260914.md:284`（设计）、`:559`（拟新增）、
`docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md:73`（形态约定）。
**代码侧 0 个函数定义、0 个 export。**

另 `wb-contract-pre.js:261`、`wb-sidecar-pre.js:517` 的注释写着"供 lint 用"，但**没有 lint 本体**。

### 缺口 5 · 账本跳过保护门（❌ 确认，行号已漂）

当前实现（`index.js`）：
```
2317:  if (target !== 'plan' && target !== 'handoff') return { ok: true }
2318:  if (target === 'handoff') return { ok: true }     ← 账本被无条件放行
2319:  if (!beforeText.trim()) return { ok: true }
```
而同函数 docblock（`~:2298` 起）与 `:332` 的模块级注释都声称"**无条件生效**"。
**注释与实现不一致**——影响面有限（账本走 `:2027` 的 `beforeText: ''`），但若将来账本改可覆盖写，
这里就是静默漏洞。

### 缺口 6 · 死导出：范围收窄

全仓（lib+tests+tools）引用计数实测：

| 符号 | 命中 | 判定 |
|---|---|---|
| `WB_STATUSES_PRE_V1` | **1**（仅自身定义） | 🔴 真死 |
| `describeWbReasonPre` | **1**（仅自身定义） | 🔴 真死 |
| `buildByCuePre` | 2 | 🟡 仅 wb-sidecar 内部 |
| `WB_CONTRACT_VERSION` | 4 | ✅ 有用 |
| `collectAnchorIdsPre` | 5 | ✅ index + sidecar + 测试 |
| `computeWhiteboardCardIdPre` | 7 | ✅ 有测试消费者 |
| `checkWriteCriteriaPre` | 4 | ✅ 有测试消费者 |

**⇒ 原判"12+16+7+5 个死导出"过宽。** 真正无消费者的是 **2 个常量/函数**（上表红标），
其余多为"仅测试消费"——那不算死，是**没有生产调用点**，可另判。

---

## 2. 白板线「接线」全景（用户点名的重点）

白板线共 **13 处 `boardMode === 'graph'` 闸门**（实测计数）。逐处判"该不该解耦"：

### 写线
| 位置 | 作用 | 该解耦吗 |
|---|---|---|
| `:1988` | PLAN 基线写入 | — **无条件，已正确**（注释 `:1985` 记录了教训） |
| `:1993` | PLAN 锚点加工 | **该解耦**（格式契约，与渲染无关） |
| `:2037` | 账本锚点加工 | **该解耦**（同上） |
| `:2081` | sidecar 条目写入 | ❌ 不该——sidecar 就是 graph 特性 |
| `:2115` | sidecar 事件追加 | ❌ 不该 |

### 注入线
| 位置 | 作用 | 该解耦吗 |
|---|---|---|
| `:4706` | 白板 → Tier-0 目录 | — **无条件，已正确** |
| `:3675` | 白板 tag 地图注入 | ❌ 不该（`:3673` 注：legacy 必须与旧行为**逐字节相同**，否则击穿前缀缓存） |
| `:3685` | 第 3 层唤醒句 | ❌ 不该（同上理由） |

### 检索线
| 位置 | 作用 | 该解耦吗 |
|---|---|---|
| `:5510-5513` | `pushL0` 词法臂 | ❌ **不是解耦问题，是缺来源** ⇒ 缺口 2 |
| `:5709-5712` | `semSources` 语义臂 | ❌ 同上 ⇒ 缺口 2 |
| `:2376` | `searchHandoffCorpus` 结构化臂 | ❌ 不该（读 sidecar，graph 专属） |

### 工具线 / GUI 线
| 位置 | 作用 | 该解耦吗 |
|---|---|---|
| `:9146` | `memory_expand_pre` 注册 | ❌ 不该 |
| `:9150` | `memory_trace_pre` 注册 | ❌ 不该 |
| `:2207` | 看板数据 `kanbanDataPre` | ❌ 不该（这就是看板本体） |
| `:2235` | 看板 enabled 门 | ❌ 不该 |
| `:3823` | `handoffPanelData` 结构化视图 | ❌ 不该 |
| `:3863` | state 报告里的 boardMode 回显 | ❌ 不该 |

**⇒ 接线结论（⚠️ 已修正，见下方补注）：13 处 boardMode 闸门里，真正该动的只有 2 处（`:1993` + `:2037` 锚点写入）。
其余 11 处本质属"graph 特性"，解耦反而会破坏 legacy 逐字节兼容纪律。**

真正的问题不在闸门，在**缺口 2 的两处缺来源**。

> ### ⚠️ 补注（2026-09-17 终端用户报障后修正）：本节结论**划窄了范围**
>
> 本节只枚举了 **`boardMode === 'graph'` 这一族闸门**，**漏掉了并行的 `handoffEnabled` 族闸门**。
> 实测：`kanbanBoardData`（`:2234`）与 `handoffPanelData`（`:3729`）**先被 `handoffEnabled` 拦，再看 boardMode**。
> ⇒ 「只该动 2 处」对 boardMode 这一族成立，**对全局不成立**。
>
> **正确的划界判据**：不看"是不是 boardMode 门"，而看 —— **这个门控的是「渲染」还是「写入/取材」**。
> - 控**渲染**的（看板、面板、GUI 视图）**一律不该**被产物开关（`handoffEnabled`）拦住；
> - 控**写入/取材**的（锚点写盘、账本读入）才归 `handoffEnabled` 管。
>
> 现已确认需追加解耦：`:2234`（看板数据）、`:3729`（白板面板数据）。
> 详见 `ROADMAP-20260917-WEEK.md` §3.6.1。

---

## 3. 保护门 / 纪律层

### `GUIDANCE` 对白板零覆盖（实测确认，逐字扫过）

`lib/index.js:143` 的 `GUIDANCE`（模型每轮读到的记忆纪律，≈845 字符）**逐字检查结果**：

```
白板 no | PLAN no | kind=plan no | 账本 no | handoff no
memory_expand_pre no | memory_trace_pre no
```

（它提到了「接续」——但那是在描述 GUI 面板页签，不是白板维护纪律。）

**⇒ 模型被明确要求"做记忆"，但从未被要求"维护白板"。这是白板腐烂的真因，不是模型偷懒。**

修法（原 handoff §三 已设计，复核确认方向成立）：**扩写 `GUIDANCE`，零新增管线**。三条必须守：
1. 写成**条件触发**（"本轮产生与白板既有结论冲突的信息、或完成了白板上的目标时"），不是每轮都做；
2. 必须写明「**只报告、不自动删**」（契约 §6 尾注：任何自动修正都会把白板变成状态机）；
3. 白板关闭时**不得有副作用**（`handoffEnabled=false` 时跳过）。

### 保护门现状（复核）
- `checkHandoffCriteriaPre` / `checkPlanCriteriaPre`：`:2307-2308` 真接线，两咽喉 `:2025`（账本）、`writePlanSnapshot` 内均过同一入口 ✅
- `validateMutationBoundaryPre`：`:2325+` 实现存在，但账本在 `:2318` 提前放行（= 缺口 5）

---

## 4. 与本轮用户报告的 bug 的关系（待用户补充）

用户 2026-09-17 表示「**这个白板目前有一些 bug，稍后汇报**」。基于本轮实测，先列**观察点**（皆非结论）：

| # | 观察点 | 证据 |
|---|---|---|
| O1 | `by_cue` 倒排为空，而每条 entry 都有 `cue` 字段 | index.json 实测：`by_cue` 0 key / `cue` 字段 150 条都有 |
| O2 | `index.json` 单文件 186KB / 150 条，每次写入**整体重写** | `_writeSidecarIndexPre`（`:2100`）每次 `JSON.stringify(fresh)` 全量落盘 |
| O3 | 白板检索不到（用户可能感知为"记忆不灵"） | 缺口 2 |
| O4 | 切回 legacy 档后锚点消失（用户可能感知为"白板坏了"） | 缺口 1 根缺陷 |
| O5 | 契约文档说"自动进检索语料"，实际只兑现注入那一半 | 缺口 2 + 文档过度声称 |

**等用户报具体现象后再定位，不在此提前下判断。**

---

## 5. 建议动手顺序（复核后重排）

| 步 | 内容 | 难度 | 变化 |
|---|---|---|---|
| 1 | **缺口 2** 白板进检索（`pushL0` + `semSources` 各补一条 + L0 侧配额保护） | ★ 很低 | 不变，仍第一 |
| 2 | **缺口 4⑤ + 白板纪律**（扩写 `GUIDANCE`） | ★ 低 | ⬆️ **提前**（原在最后；理由：它是白板腐烂真因，且不依赖 3/4） |
| 3 | **缺口 3** 索引派生两渲染（补 `status`/`layer` 字段 + 契约行） | ★ 低 | 不变 |
| 4 | **缺口 4①②④** lint 只读纯函数 | ★★ 中低 | ③ 判据待用户定 |
| 5 | **缺口 1** 锚点解耦（只动 `:1993` / `:2037` 两处） | ★★ 低 | ⬇️ **降级**（症状已被 3.0 覆盖） |
| 6 | **缺口 5/6** 注释与实现对齐 + 2 个真死导出 | ★ 低 | 不变 |

> **顺序变更理由**：原顺序把「白板纪律」放最后，是因为它要引用前面做出来的 index/lint。
> 复核后确认**纪律本身不依赖 3/4**（它只要求模型"条件触发地维护白板"），
> 而它是唯一能**阻止白板继续腐烂**的一项 ⇒ 提前。

---

## 6. 实施纪律（沿用，不重复展开）

1. 改前备份 `*.bak-YYYYMMDD-<tag>`；无 BOM；大文件分块写
2. 改后 `node --check`（`lib/index.js` 是 **CRLF** 大文件，`edit` 的 `replace_all` 要核命中数）
3. 全量回归基线：**PASS 106 / FAIL 0 / TIMEOUT 0**（`node tools/run-smoke.mjs`，3.0 发版后实测 182.8s）
4. 每个缺口配**可失败断言** + **变异演示真红**
5. 代码留 **pre 线**，未获明确同意不 commit/push
6. host 代码改完**只告知用户自行重启**，严禁 Stop/Start-Process
