# 周末作战 · 执行流程图（2026-09-17 定稿 · 待批准）

> 本文件是**执行顺序的唯一依据**。批准后按 底层 L → 中层 M → 表层 S 顺序推进，每步配可失败断言 + 变异演示。
> 上游依据：`S10-GAP-INVENTORY-20260917.md`（缺口复核）· `ROADMAP-20260917-WEEK.md` §3.6（用户报障）。

---

## 0. 一图看懂

```
                        ┌───────────────────────────────────────────┐
                        │            一 个 根 病 根                    │
                        │   职责边界被开关 / 注释侵蚀                  │
                        │   （6 个缺口 + 2 条用户报障 全出自这里）      │
                        └────────────────────┬──────────────────────┘
                                             │
                        ┌────────────────────▼──────────────────────┐
                        │      一 条 判 据  （用它给所有闸门划线）      │
                        │                                           │
                        │   这个门控的是「渲染」还是「写入/取材」？     │
                        └───────┬───────────────────────┬───────────┘
                                │                       │
                    控「渲染」   │                       │   控「写入/取材」
                                ▼                       ▼
                ┌───────────────────────────┐  ┌──────────────────────────┐
                │  不该被产物开关拦住         │  │  归 handoffEnabled 管     │
                │  → 解除 handoffEnabled 依赖 │  │  （保持现状，不动）        │
                │                            │  │                          │
                │  · 看板   index.js:2234    │  │  · 锚点写盘 :1993/:2037   │
                │  · 白板面板 :3729          │  │  · 账本读入 :3590         │
                └───────────────────────────┘  └──────────────────────────┘

                                ▲
                                │ 另外：控「graph 渲染」的只有 boardMode 一族
                                │ （tag 地图 :3675 / GUI 视图 :3823 / 看板本体 :2207）
                                │ → 保持 boardMode 门，因为 legacy 必须逐字节不变
```

### 三层推进（批准后按此顺序）

```
┌──────────────────────────────────────────────────────────────────────────┐
│  第一层 · 底层 L（界限 + 接线）   ← 本轮重点，先全部做完                     │
│                                                                          │
│   L1 看板/面板解耦 ──┐                                                    │
│   L2 提示消费 reason ─┴─→ 修完「用户看得见的 bug①」                        │
│                                                                          │
│   L3 接续预算重构 ─────→ 修完「用户看得见的 bug②」                          │
│                                                                          │
│   L4 白板进检索 ──┐                                                       │
│   L5 锚点解耦  ──┤                                                        │
│   L6 账本保护门 ──┼─→ 收尾 S10 六缺口的「底层三件」                          │
│   L7 死导出    ──┘                                                        │
└────────────────────────────────┬─────────────────────────────────────────┘
                                 │  底层冻结（跑全量回归，0 失败）
                                 ▼
┌──────────────────────────────────────────────────────────────────────────┐
│  第二层 · 中层 M（补齐「只有契约、没有代码」的功能）                          │
│                                                                          │
│   M1 契约行渲染   ← 缺口 3，派生已在跑，只缺 status/layer + 一行格式         │
│   M2 lint ①②④    ← 缺口 4，纯只读函数                                     │
│   M3 GUIDANCE 白板纪律 ← 缺口 4⑤，白板腐烂真因，零新增管线                   │
└────────────────────────────────┬─────────────────────────────────────────┘
                                 │  再跑全量回归
                                 ▼
┌──────────────────────────────────────────────────────────────────────────┐
│  第三层 · 表层 S（外观）   ← 必须等前面冻结，否则返工                         │
│                                                                          │
│   S1 容器结构：记忆窗格挂载点 + 看板升级为容器（作战图 §1.1–1.4）            │
│   S2 视觉对齐 DeepSeek Flow（作战图 §1.5，8 条设计要素 V1–V8）              │└──────────────────────────────────────────────────────────────────────────┘
```

### 依赖关系（为什么是这个顺序）

```
   L1 ─┬─→ L2          （同一处 bug 的后端 + 前端，必须一起改）
       │
   L3 ─┘  独立，可与 L1/L2 并行

   L4 ──→ 依赖 handoffEnabled 判据（L1 先定，L4 才知该不该跟这个门）

   M3 ──→ 依赖 L4/L5（纪律里要引用「白板已进检索」这件事）

   S1 ──→ 依赖 L1（渲染解耦后，容器才知道自己该向谁取数）
   S2 ──→ 依赖 S1（结构定稿才动外观）
```

---

## 1. 判据：一张表定所有闸门归属

**判据**：不看「是不是 boardMode 门」，只看 —— **这个门控的是「渲染」还是「写入/取材」**。

| 位置 | 干什么 | 现被谁拦 | **应该归谁** | 动作 |
|---|---|---|---|---|
| `index.js:2234` | 看板数据 | `handoffEnabled` | **渲染** | 🔧 **解除** |
| `index.js:3729` | 白板面板数据 | `handoffEnabled` | **渲染** | 🔧 **解除** |
| `index.js:1993` | PLAN 锚点写盘 | `boardMode` | **写入契约** | 🔧 **解除** |
| `index.js:2037` | 账本锚点写盘 | `boardMode` | **写入契约** | 🔧 **解除** |
| `index.js:3590` | 账本读入（接续取材） | `handoffEnabled` | 取材 | ✅ 保持 |
| `index.js:2081` | sidecar 条目写 | `boardMode` | graph 特性 | ✅ 保持 |
| `index.js:2115` | sidecar 事件写 | `boardMode` | graph 特性 | ✅ 保持 |
| `index.js:3675` | tag 地图注入 | `boardMode` | graph 渲染 | ✅ 保持 |
| `index.js:3685` | 结构化检索指引 | `boardMode` | graph 渲染 | ✅ 保持 |
| `index.js:2207` | 看板本体 | `boardMode` | graph 渲染 | ✅ 保持 |
| `index.js:2376` | sidecar 结构化检索臂 | `boardMode` | graph 特性 | ✅ 保持 |
| `index.js:3823` | GUI 结构化视图 | `boardMode` | graph 渲染 | ✅ 保持 |
| `index.js:3863` | state 回显 | `boardMode` | 状态报告 | ✅ 保持 |

**⇒ 要动的只有 4 处**（2 处渲染 + 2 处写入契约）。
其余 9 处保持不动 —— 因为 `:3673` 有明文纪律：**legacy 档必须与旧行为逐字节相同**，乱解耦会击穿每轮前缀缓存。

---

## 2. 第一层 · 底层 L（详细）

### L1+L2 · 看板/面板解耦 + 提示消费 reason 　【P0，修用户 bug①】

**问题链**：
```
handoffEnabled=false（用户关掉了白板产物）
        │
        ▼
:2234 直接 return {enabled:false, reason:'handoff-disabled'}
        │  ← 但 handoff/ 里的 PLAN.md 和账本**没被删**，数据还在
        ▼
client.js:1830 丢弃 reason，硬编码一句"需 boardMode=graph 且 handoff 已开启"
        │
        ▼
用户看到：❌「未启用或加载失败」  ← 提示指向错误方向，无法自查
```

**改动**：

| 步 | 文件:行 | 改什么 |
|---|---|---|
| L1a | `index.js:2234` | 删掉 `handoffEnabled` 早退（渲染不该被产物开关拦） |
| L1b | `index.js:3729` | 同 L1a，且**补上 reason**（现在连 reason 都不给） |
| L2a | `client.js:1830` | 按 `reason` 分支：`legacy-mode`→提示切新版／`error`→显示 message／真无数据→提示去写账本 |
| L2b | `client.js:3006` 附近 | 面板侧同样的提示分支 |

**验收断言（必须能失败）**：
1. `handoffEnabled=false` 且 `handoff/PLAN.md` 存在 ⇒ 看板返回 `enabled:true`（**当前会红**）
2. `boardMode='legacy'` ⇒ 返回 `reason==='legacy-mode'`，前端文案含"切换"而非"未启用"
3. 真实异常 ⇒ 前端展示 `error.message` 原文（不再吞）
4. 变异演示：把 `:2234` 那行加回去 ⇒ 断言 1 必须真红

---

### L3 · 接续预算重构 　【P1，修用户 bug②】

**问题链（算过账）**：
```
parts 拼接顺序 →  :3704  slice(0, 18000) 从尾部砍

  [指令]      ~200
  [白板]      3000
  [账本]      8000
  [近期线程]  14000   ← 20 条 × 700 字
  ────────────────
  小计      ≈25200  > 18000  ⇒ 必然溢出

  [第3层转写路径]  ← ❌ 第一个被砍  ← 模型的"逃生通道"
  [锚点表]         ← ❌ 被砍
  [tag 地图]       ← ❌ 被砍
  [结构化检索指引]  ← ❌ 被砍
```

**改动（两条治本 + 三条加固）**：

| 步 | 改什么 | 收益 |
|---|---|---|
| **L3a★** | **把「第3层转写路径」提到 `parts` 最前**，或给它不可截断的独立额度 | ★★★ 一条修复主因 |
| **L3b★** | **截断发生时显式告知**：写明「因预算截断，未包含 X/Y/Z；完整材料见转写路径」 | ★★★ 模型知道自己拿的不全 |
| L3c | 层间独立预算（白板/账本/线程/导航各一份），不再共用一个池 | ★★ |
| L3d | 白板 `slice(0,3000)` → 按 `##` 小节做**摘要式保留**（保全部小节标题 + 每节首句） | ★★ 图景不丢结构 |
| L3e | 第2层取样：末尾 20 条 **但强制包含**最后一条用户消息 + 最后一条助手结论 | ★★ 避免全取到工具噪声 |

**验收断言**：
1. 材料充足撑爆 18000 ⇒ `carryText` **仍包含转写路径**（**当前会红**）
2. 发生截断 ⇒ 文本中含显式未包含清单
3. 白板超 3000 字 ⇒ 全部 `##` 小节标题都还在
4. 变异演示：把转写路径移回末尾 ⇒ 断言 1 真红

> 注：`weightedTrimHandoffLedgerPre`（`:3628` 已接线的账本按四段权重截断）是**本仓已有的正确范式**，
> L3d/L3e 直接复用它，不新造轮子。

---

### L4 · 白板进检索 　【P0，S10 缺口 2】

**现状**：注入 ✅（`:4706` 无条件）／检索 ❌（两臂都只有四来源）

| 步 | 文件:行 | 改什么 |
|---|---|---|
| L4a | `index.js:5513` 后 | `pushL0` 补白板来源（`p.planPath` **变量已存在**，`:1828`/`:1890` 定义） |
| L4b | `index.js:5712` 后 | `semSources` 同样补一条 |
| L4c | 配额 | **L0 检索侧没有同类保护**（`:353` 的 `floorLayers` 是 Tier-0 注入侧的）⇒ 白板可能又被 `project` 挤掉，需一并判 |
| L4d | 门控 | `handoffEnabled=false` 时**不该读**该文件（`:3586`/`:4160` 已解耦）⇒ 补来源时同样判这个门 |

**验收断言**：
1. 建 PLAN.md 含独特关键词 ⇒ `memory_recall` 能命中白板层（**当前会红**）
2. 白板与日志同关键词 ⇒ 两者都在候选里（不被层内挤掉）
3. `handoffEnabled=false` ⇒ 白板不进检索语料（不读按设计不该读的文件）

---

### L5 · 锚点解耦 　【P1，S10 缺口 1 根缺陷】

**现状**：3.0 已把默认翻成 `graph`（`:221`），**症状消失**；但手动切回 legacy 锚点仍停摆。
**理由**：锚点是**写入格式契约**，看板是**渲染形态**，本不该耦合。

| 步 | 文件:行 | 改什么 |
|---|---|---|
| L5a | `index.js:1993` | 去掉 PLAN 锚点写盘的 `boardMode==='graph'` 门 |
| L5b | `index.js:2037` | 去掉账本锚点写盘的同款门 |
| L5c | —— | ⚠️ **只动这 2 处**，其余 boardMode 门保持（见 §1 表） |

**验收断言**：
1. `boardMode='legacy'` + 写 PLAN ⇒ 正文含 `<!--memory:mem_...-->` 锚点（**当前会红**）
2. `boardMode='legacy'` + 不写白板 ⇒ 其余注入输出**逐字节不变**（保 legacy 兼容）

---

### L6 · 账本保护门 　【P2，S10 缺口 5】

**现状**（`:2317-2318`）：
```js
if (target !== 'plan' && target !== 'handoff') return { ok: true }
if (target === 'handoff') return { ok: true }     // ← 账本被无条件放行
```
与同函数 docblock（`~:2298`）及 `:332` 模块注释声称的「**无条件生效**」**直接矛盾**。

**改动**：二选一，**倾向 (a)** ——
- (a) 修正注释，如实写明「账本走 `beforeText:''`，结构性不适用丢卡判定」
- (b) 补上账本保护门

**验收**：断言注释与实现一致（(a) 改注释后，`:2318` 不再是"静默放行"而是"显式不适用"）。

---

### L7 · 死导出 　【P2，S10 缺口 6】

**复核结论（已收窄）**：真死只有 2 个 —— `WB_STATUSES_PRE_V1`、`describeWbReasonPre`（各 1 命中 = 只有自身定义）。
其余（`computeWhiteboardCardIdPre`=7、`collectAnchorIdsPre`=5、`checkWriteCriteriaPre`=4）**有测试消费者，不算死**。

**改动**：删除 2 个真死导出，或标注为「对外契约保留」。**二者择一，不要沉默留着。**

---

## 3. 第二层 · 中层 M（详细）

### M1 · 契约行渲染 　【缺口 3】　✅ **已完工 2026-09-18**

**家底（别重复造）**：`rebuildSidecarIndexPre` **真在派生**。本机 `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
```

**缺的只有三样**：
| 缺什么 | 契约要求 |
|---|---|
| entry 无 `status` 字段 | 契约行要有 `status=current` |
| entry 无 `layer` 字段 | 契约行要有 `layer=whiteboard` |
| 没拼成契约那一行的字面格式 | `WB-FORMAT-CONVENTION.md` §1 |

**改法（关键设计，已定）**：**不做成独立文件**（那会新增状态源，违反 S10.4「不建状态机」）。
改为**同一次派生、两种渲染** —— 让 `renderCatalogLinePre`（`tier0-catalog-pre.js:316`，已在渲染 `标题·摘要·layer·status·日期`）
在有页面路径时输出**带链接形态**。
⇒ 契约 §8「派生出 index 与 Tier-0 条目一致」**天然成立**，因为就是同一份数据。

**验收**：断言「同一份 entries 走两种渲染，字段一致」+ 变异演示真红。

---

#### ✅ M1 结项记录（2026-09-18）

**实测修正（对上面「缺的只有三样」的补充）**：M1 **不止缺格式**，还缺一个**桥**。
`buildRecordPre` 返回 `{layer,status,date,title,oneLine}`，**根本没有 path/id** ⇒ 光有渲染分支还接不上。
故实际是三处改动（比原计划多一处）。

| # | 文件 | 改动 |
|---|---|---|
| ① | `lib/tier0-catalog-pre.js` | 新增 `TIER0_ANCHOR_ID_RE = /^mem_[0-9a-f]{32}$/`（**本地定义而非 import**，避免底层模块反向依赖 wb-sidecar；与 `WB_ANCHOR_LINE_RE_PRE_V1` 同源，套件断言两者判定一致以防双源漂移）；`renderCatalogLinePre` 增**两种渲染**分支 |
| ② | `lib/wb-sidecar-pre.js` | `buildSidecarEntryPre` 增 `layer: 'whiteboard'`（模块内硬编码）/ `status`（缺省 `'current'`）；**只增不改**，既有字段顺序语义逐字节不变 |
| ③ | `lib/wb-sidecar-pre.js` | 新增导出 `sidecarEntryToCatalogItemPre(entry)` —— **只做字段映射，绝不复制渲染逻辑**（否则「两种渲染」会退化成「两个渲染器」并长期漂移） |

**产出格式**（与契约 §3 示例逐字符相同）：
```
- [卡片标题](handoff/PLAN.md#mem_60bc075b...) — 一句话摘要 · layer=whiteboard · status=current · 2026-09-14
```

**四条硬约束**：
1. **兼容红线**：无 `path`/`id` 时**逐字节同旧实现**（老套件 3 条断言不变）。
2. **fail-closed**：id 形态非法 / 只有 path / 只有 id / 空值 ⇒ 退回纯文本，**绝不产出指向不存在锚点的坏链接**。
3. **渲染口径只有一处**（桥接函数不渲染）。
4. **不新建文件**（S10.4）。

**证据**：新套件 `tests/smoke/smoke-test-m1-contract-line-pre.mjs` **55/55**；
变异演示 **5/5 全真红** + 还原 SHA256 逐字节一致；
**全量回归 PASS 118 / FAIL 0 / TIMEOUT 0（156.7s）**（117 → 118）。
备份：`lib/tier0-catalog-pre.js.bak-20260918-M1`、`lib/wb-sidecar-pre.js.bak-20260918-M1`。

**踩坑留档（两处，均可复用）**：
- **运算符优先级**：`'a' + x + 'b'.match(re)` 中 `.match` 只绑到 `'b'`（`.` 优先于 `+`）⇒ 得到 `'a' + x + null` 即字符串 `'...null'`。**必须先拼成变量再 `.match()`**。此坑一度被误判为「正则被污染」，追查数轮。
- **源码级断言必须先剥离注释**：JSDoc 里提到函数名/文件名会造成假阳性（与 R3 裸 `join` 断言同源）。已加 `stripComments`（含块注释状态机）+ 一条**反向自检**断言，证明剥离有效、断言非空转。

**未做（有意）**：未把该渲染接入任何注入/检索路径 —— M1 只关闭「契约有、代码无」这一缺口；
真实调用点属后续项，避免顺带改变既有行为。

---

### M2 · lint ①②④ 　【缺口 4，只读纯函数】＋ 【**G3 状态写入（2026-09-18 用户拍板并入）**】

**形态（硬约束）**：`lintWhiteboardPre(entries) → 问题清单`，**只读、只报告、绝不写盘**。
契约 §6 尾注：「任何自动修正都会把白板变成状态机（违反边界）」。

| 类 | 内容 | 做法 |
|---|---|---|
| ① | 孤立条目（无入站引用） | 扫全部卡正文，找是否引用过其 id/标题 |
| ② | 陈旧（`status≠current`，或超阈值仍 current） | 状态 + 日期比较 |
| ③ | 被提及却无独立卡 | ✅ **判据已解除**（见下"并入"） |
| ④ | 缺交叉引用 | ✅ **判据已解除**（见下"并入"） |
| ⑤ | 矛盾检测 | 需 LLM → 走 M3 |

**现状核实**：全仓 `lintWhiteboard|lintPlan|wbLint` 命中**全在文档里**，代码侧 **0 定义 0 export**。
（`wb-contract-pre.js:261`、`wb-sidecar-pre.js:517` 注释写着"供 lint 用"，但**没有 lint 本体**。）

**验收**：三类各造一个正例 + 一个反例，断言只报告不改盘（跑完后文件 mtime 不变）。

#### ★ 并入项：G3 结论层状态写入（2026-09-18 用户拍板）

**用户原话**：「b➕c 应该才是正路」「**只在 `memory_note_pre`（结论层）时比对**」「标错成 superseded 后，要有个撤销通道」「**落到文档里，和 M2 一起做**」。

**完整设计见 `MEMORY-GOVERNANCE-20260917.md` §8**（含代码证据、触发范围、撤销通道、风险约束、验收口径）。要点：

| 项 | 定案 |
|---|---|
| **触发范围** | **只挂 `memory_note_pre`**；`memory_log_pre` **零改动**（日志是流水，append 本正确） |
| **地基现状** | `status` 三值 + L0 版本指纹 + 归档机制**全部已存在**；缺口是「**机制只读不写**」（全仓 0 处写 `superseded`） |
| **撤销通道** | **必需项**（误判代价不对称：漏判=维持现状，误判=有效结论被当废纸）→ 倾向 U1（`restore: [id]` 参数 + 留痕） |
| **③④ 判据** | ✅ **不再阻塞**：改为"主动触发 + 写入时兜底比对"，不必预先穷举判据 |
| **★ 硬约束** | 语义引擎不可用时**退回词法臂**，绝不报错/跳过（铁律：JS 引擎与 Python 引擎可互换、严禁互相依赖） |
| **★ 纪律** | **M2 只报告、G3 才写**。不得把写入能力塞进 lint（报告与写入分离，S10.4 合规：状态归记忆条目，不新建状态源） |

**分界一句话**：M2 说"这条可能过时"，**G3 在下次写入时决定要不要标**。

---

### M3 · GUIDANCE 白板纪律 　【缺口 4⑤，白板腐烂真因】

**实测**：`lib/index.js:143` 的 `GUIDANCE`（每轮注入的记忆纪律，≈845 字符）**逐字检查结果**：
```
白板 no | PLAN no | kind=plan no | 账本 no | handoff no
memory_expand_pre no | memory_trace_pre no
```
⇒ **模型被明确要求"做记忆"，但从未被要求"维护白板"。这是白板腐烂的真因，不是模型偷懒。**

**改法：扩写 `GUIDANCE`，零新增管线**（它本来就每轮注入，加几十字符即可）。

**三条必须守住的**：
1. 写成**条件触发**，不是"每轮都做" —— 否则每轮改白板 ⇒ 噪声 + token 浪费。
   措辞方向：「**当本轮产生与白板既有结论冲突的信息、或完成了白板上的目标时**，用 `memory_note_pre(kind=plan)` 更新」。
2. **必须写明「只报告、不自动删」** —— 契约 §6 尾注为硬纪律。否则模型会把"清理白板"理解成"删卡片"。
3. **白板关闭时不得有副作用** —— `handoffEnabled=false` 时白板不读不写，纪律里须说明「未启用则跳过」。

**验收**：断言 `GUIDANCE` 含三条关键语义（条件触发 / 只报告 / 关闭跳过）+ 变异演示真红。

---

## 3.5 · M2.5 检索质量三改点（2026-09-18 用户批准「先修」）

> **为什么插在 M2 之前**：这三项让 M2/G3 的收益打折——L0 全坏时，语义臂搜不到笔记层（结论层）；
> 排序盲于 layer 时，结论与流水同权竞争。**先修内容，再谈过滤。**
> 用户原话：「需要先修」「修法确实需要考量」「这确实是一个相当大的隐患」。

### M2.5a · L0 抽取质量门（笔记本层废数据）　✅ **已完成 2026-09-18**

**状态**：✅ 已实施并验证 — 全量回归 **PASS 114 / FAIL 0 / TIMEOUT 0**（152.6s）。

**实测（探针 `artifacts/_probe-l0-quality.mjs`，修前 → 修后）**：

| 来源 | 修前 | 修后 |
|---|---|---|
| log | 449 条 0 废 | 452 条 **0 废**（无回归） |
| reflection | 21 条 0 废 | 21 条 **0 废**（无回归） |
| **project-notes** | **16/16 废（100%）** | **17 条 0 废** |
| **user-notes** | **46/50 废（92%）** | **50 条 0 废** |
| 合计 | 62/536 废（12%） | **0/540 废（0%）** |

**根因（附代码证据）**：`l0-extract-pre.js:179-185` 规则① **假设「有标题 ⇒ 标题即摘要」**，
且 **无任何质量检查**（`minChars` 只用在规则②的 `growToMin`）。
日志标题是 `## 主题（12:02）`（去时间后缀后=真摘要）；笔记标题是 **`## 2026-09-17`（纯日期）**
⇒ 去后缀无效 ⇒ **整条 L0 = 一个日期**。样例：2888 字符结论 → `"2026-09-17"`。

**实际改动（三条）**：
1. **规则① 加质量门**：标题命中「退化形态」⇒ 不采信，继续下探到规则②（首个 `- ` 条目首句）。
2. **规则③ 压平前先剥标题行**：否则退化标题被压进正文（修前 user-notes 见 `## 2026-09-09 ## 2026-09-09 …`）。
3. 判据常量 `DEGENERATE_HEADING_RES` + `isDegenerateHeading()` 集中定义，可独立断言。

**★ 实施中被既有套件抓出的真错（已修，教训固化）**：
初版判据含 `if (t.length < 4) return true`（"过短即退化"）。
⇒ **误伤 `主题甲`（3 个 CJK 字符，是合法标题）** ⇒ 标题被拒 ⇒ 落到规则②
⇒ **把正文里的隐私标记当成了 L0** ⇒ `smoke-test-l0-index-pre.mjs` 的
「索引不得含记忆原文」断言**真红**（15/15 → 14/15）。
**教训**：**中文标题短而有效是常态，长度不是质量信号**；判据只按**形态**判。
纪律：**宁可漏判，不可误伤**（漏判=退化标题照旧被当摘要，回到现状；误伤=合法内容被改变行为路径）。

**验收（全部达成）**：
- ✅ 探针复跑：笔记层废 = 0 且 **log/reflection 无回归**
- ✅ 新增套件 `smoke-test-m25a-l0-qualitygate-pre.mjs` **39/39**（含 8 组正例/反例成对断言）
- ✅ 全量回归 **PASS 114 / FAIL 0 / TIMEOUT 0**
- ✅ 三处受影响套件复验：`l0-extract` 18/18、`l0-index` 15/15、`handoff-anchor` 42/42

**单点修、五处全覆盖**（`buildL0IndexPre` → `extractL0Pre` 是唯一出口）：
| 调用点 | 用途 |
|---|---|
| `index.js:5834` | 检索语料（`pushL0` 四来源） |
| `index.js:6026` | 语义臂 corpus |
| `index.js:3972` | Tier-0 目录（`maxChars: 48`） |
| `l0-index-pre.js:247/277` | 持久化向量索引（Python worker） |
| `l0-index-sync-pre.js` | 索引同步 |

**副作用（可接受）**：L0 文本变 ⇒ `l0Hash` 变 ⇒ `computeL0IndexVersionPre` 指纹变 ⇒
下游缓存 fail-closed 重建。**首次会触发笔记层全量重嵌**；之后自愈。

**备份**：`lib/l0-extract-pre.js.bak-20260918-M25a`

**⚠️ 需用户重启宿主**才在实机生效（agent 不重启 3080）。

---

### M2.5b · RRF 加 layer 权重臂（排序层盲于 layer）

**实测不一致**：
| 环节 | 是否考虑 layer |
|---|---|
| 注入编排 `tier-layer-inject-pre.js:58` | ✅ `TIER_LAYER_ORDER_PRE_V1 = ['project','whiteboard','user','reflection','log']` |
| **检索排序 `recall-fusion-pre.js`** | ❌ 签名只有 `{memoryId, dense, lex, temp}` —— **完全没有 layer** |

⇒ 注入时「项目笔记优先、日志垫底」，**但召回排序不知道 layer 存在**；
语义相近时一条日志与一条项目结论**同权竞争**。

**修法：layer 做成第四条臂**（**照抄现有时间臂 `temp` 的写法**，`:62-69`）：
- 与既有架构同构、增量小、可回滚
- **不做「拆两个 function」**：成本翻倍，且拆完仍需再融合 ⇒ 那正是 RRF 第四条臂在做的事

**⚠️ 时序**：**必须排在 M2.5a 之后**。L0 全是日期时，笔记层向量彼此无差别，
**排序手术改不动一个内容为空的语料**。先修内容，再用真实召回判断权重是否值得。

---

### M2.5c · ~~miv 两公式疑点~~ ❌ **已撤销（2026-09-18 假警报）**

> **撤销依据**：`docs/internal/R1-DEGRADE-AUDIT-20260918.md` §2。
> 追查结论：`index.js:5865` 注释所指对照对象是 **`index-sync`**（不是 `context-host-pre:537`）；
> `index-sync-pre.js:62` **只接收 miv、不自己计算**（只校验 `idx_pre_` 前缀）；
> `context-host-pre.js:537` 属**另一个域**（技能/procedures，注释 `:526` 已明示"miv=技能集指纹"），
> 消费者是**直接调用** `_jsSemanticRank`，自洽。
> ⇒ **两条公式服务两个不同消费者，不存在"跨侧不一致"。**
>
> **教训（已固化）**：注释里的「context-host」指**模块族**而非某一个文件；
> 我最初把 `context-host-pre.js:537` 当成注释所指的对照点 —— **推断当结论，已更正。**

**影响**：M2.5a 的"硬前置"**解除**，可立即开工，无阻塞。

---

### M2.5 执行顺序与验收（已按 R1 结论修正）

| 序 | 项 | 前置 | 验收 |
|---|---|---|---|
| ~~1~~ | ~~M2.5c 验证 miv~~ | — | ❌ **已撤销** |
| **1** | **M2.5a** L0 质量门 | 无（前置已解除） | 探针复跑：笔记层废 = 0 且 **log/reflection 无回归**；变异演示真红 |
| **2** | **M2.5b** layer 臂 | M2.5a | 断言 layer 进融合输入；无 layer 时行为**逐字节同旧**（退化安全） |
| **3** | 全量回归 | 1–2 | 维持 **PASS 113 / FAIL 0 / TIMEOUT 0** |

**共同纪律**：改前备份 `*.bak-YYYYMMDD-M25<tag>`；改后 `node --check`；
**含正则字面量/`$` 的探针一律写 `.mjs` 文件运行，不要 `node -e` 内联**
（本仓已踩：PowerShell 吞 `\d`/`$` ⇒ 探针报 0% 废数据的**假绿**）。

---

## 3.6 · 降级留痕线（R 系列 · 2026-09-18 用户批准的第四批）

> **用户原话**：「可以，r1之后，先做 m2.5a，再仔细查静默降级，之后加降级留痕层：统一一个
> `degrade(kind, reason)` 通道，写进可查询的位置（面板/状态文件），让所有降级可见（然后就可以改了）」
> **用户定性**：「这可能是一个相当大的隐患」

### 背景：静默降级普查（已做）

探针 `artifacts/_probe-silent-degrade.mjs` 全仓扫描结果：

| 类别 | 数量 |
|---|---|
| `catch` + diag/console 但**不抛出** | **70 处** |
| 其中**文本自述为「降级/回退/中性/跳过」** | **10 处** ★ 高危 |

**10 处高危名单**（含 4 处**检索链路上的独立臂**）：

| 位置 | 降级内容 | 臂 |
|---|---|---|
| `index.js:5877` | 语义臂择优降级 | **语义臂** |
| `index.js:5897` | evidence-agg 降级为中性 | **importance 臂** |
| `index.js:5908` | temporal-parse 降级（无时间臂） | **时间臂** |
| `index.js:8795` | l0-index sync 降级 | **L0 索引同步** |
| `index.js:3873` | buildPrevSessionPack error → null | 接续材料 |
| `index.js:1941` | config migrate failed | 配置迁移 |
| `index.js:9154` | agent lifecycle | 生命周期 |
| `client.js:2825` | session rename 失败 | 会话重命名 |
| `index.js:8797-8798` | （同上 8795 的 catch 主体） | — |

**核心判断**：**fail-soft 本身是对的**（记忆插件不得拖垮会话），
**缺陷在「降级不可见」** —— 四条臂可**同时静默失效**，使用者只看到「检索结果不太对」。
与 M8/M9 两次「静默恒假」事故同源。

**证据：终端用户实测报障**
```
[dsh-auto-memory] evidence-agg 降级为中性(impMap 空):
ENOENT: no such file or directory, scandir 'C:\Users\Administrator\.dsh\memory\evidence\events'
```
- 该路径**不是拼错**：`release.mjs:93` 有重命名 `['memory/evidence-pre','memory/evidence']`（发布版），
  故 `evidence\events` 是 **npm 3.0.0 的正确路径**；`C:\Users\Administrator\` 是该用户的 home。
- **用户报的是「功能异常」，不是贴日志** ⇒ 说明**用户可感知**，非纯噪音。
- 待查：**写入侧（`context-bridge-pre`）是否真的创建过该目录**
  —— 若从未创建，则**所有用户每次 recall 都会走 ENOENT**，importance 加权**出厂即死**。

### 执行顺序（用户批准，不得颠倒）

| 序 | 项 | 性质 | 前置 | 状态 |
|---|---|---|---|---|
| **R1** | **只查不改**：10 处高危逐个追到底 | 只读 | 无 | ✅ **完成**（`R1-DEGRADE-AUDIT-20260918.md`） |
| **M2.5a** | L0 质量门（见 §3.5） | 改 | R1 完成 | ✅ **完成**（PASS 114） |
| **R2 深查** | 静默降级再细查 | 只读 | M2.5a | ✅ **完成**（`R2-EVIDENCE-DEEP-AUDIT-20260918.md`） |
| **R2-E1** | evidence 读侧守卫（契约对齐） | 改 | R2 完成 | ✅ **完成**（PASS 115） |
| **R3 留痕层** | 统一降级台账 | 改 | R2 完成 | 🔄 **核心完成**（PASS 116）；面板可见性待接 |

### R3 实施纪要（2026-09-18）

**设计定稿**：`docs/internal/R3-DEGRADE-LEDGER-DESIGN-20260918.md`

**① 前置检查（已证实可新建，无重复造轮子）**：

| 查什么 | 实证结果 | 判定 |
|---|---|---|
| `_lastIndexDegrade` | `context-host-pre.js:112` 单值兼容投影（供旧读取方） | ❌ 非收集器 |
| `debugView()` | 存在 **7 个 host**，各返回**本域局部状态** | ❌ 非跨臂台账 |
| `createDegrade*` / `recordDegrade` | 全仓 grep **零命中** | ✅ 无重复 |

**边界**：`diag()` = 过程日志／`debugView()` = 各域局部状态／**`degrade` = 跨臂统一台账**。

**② 新建 `lib/degrade-pre.js`**（零依赖、有界收集器）：
- `createDegradeSinkPre({cap,now})` → `record / snapshot / countOf / isEmpty / reset`
- `deriveArmsHealthPre(states)` → 臂健康快照（E-3 落地）
- 常量：`DEGRADE_SCHEMA_PRE_V1='degrade_pre_v1'`、`DEGRADE_CAP_PRE_V1=200`、
  `DEGRADE_REASON_MAX_PRE_V1=200`、`ARM_STATES_PRE_V1=['active','no-input','degraded','disabled','unknown']`

**③ ★ 最高纪律（R1/R2 得出）—— 必须区分两类，绝不能一律报**：

| 类别 | 定义 | 实例 | 处置 |
|---|---|---|---|
| **预期内分支** | **合法业务状态** | 无证据事件、查询无时间表达 | **静默，不记** |
| **预期外失败** | **不该发生** | 引擎抛错、目录异常、worker 拒绝 | **记** |

若一律报 ⇒ 每次 recall 都刷「无时间臂」⇒ **噪音淹没信号**。

**④ 元规则**：留痕失败**绝不可导致二次失败** —— `record()` 内部整体 try/catch；
已用**变异演示**断言（`now()` 抛错时 `record`/`snapshot` 均不抛，快照仍返回结构性合法对象）。

**⑤ 接线（3 处，最小集）**：

| 位置 | kind | 判定 |
|---|---|---|
| `index.js:5883` | `semantic-arm` | 预期外（引擎/worker 抛错） |
| `index.js:5922` | `evidence-arm` | 预期外（目录不存在已被 E-1 守卫拦下，不记） |
| `index.js:8831` | `l0-sync` | 预期外（同步失败 ⇒ 索引长期陈旧） |
| **`:5908` temporal 段** | **刻意不动** | **预期内分支**（无时间表达是正常路径） |

**观察点**：`:5908` 那个 catch 把「返回 null」与「抛错」**混在一起** ——
若日后区分开，**抛错那支应记**（预期外）。记为待办，不在本次范围。

**⑥ 其他性质**：
- **有界**：cap=200；淘汰数 `evicted` **可见**（不静默丢数据）；`counts` 不受环淘汰影响
- **隐私**：只存 kind/reason/时间，reason 截断 200（与既有 diag 同口径）
- **不新增配置键**：默认开启（观测面，非业务功能）
- **fail-closed**：臂状态非法值归 `'unknown'`，**绝不当正常**

**⑦ 验收**：新增套件 `smoke-test-r3-degrade-pre.mjs` **53/53**；
全量回归 **PASS 116 / FAIL 0 / TIMEOUT 0**（153.6s）。**备份**：`lib/index.js.bak-20260918-R2ev`

**⑧ 待办**：面板可见性（`<dshHome>/memory/degrade-pre/latest.json` 落盘 + GUI 呈现）——
本次只做**核心台账**，状态文件与面板尚未接线。

**R3 设计要点（待 R2 后定稿）**：
- 与**白板 lint「只读 + 留痕」**纪律同源 —— 降级不是错误，但**必须可查**
- 覆盖 §3.5 的 M2.5c（miv）疑点：若 miv 不一致导致语义臂静默退化，**留痕层应能直接暴露它**
- **不得**把留痕做成新状态机（S10.4）：是**观测面**，不是业务状态源

**共同纪律**：`degrade` 只记录、不阻断；任何降级路径**不得因留痕失败而二次失败**（留痕自身必须 fail-soft 到静默）。

---

## 3.7 · 检索区分度与配额治理（R4 · 2026-09-18 用户批准立项）

> **完整设计见 `docs/internal/R4-RECALL-QUOTA-PLAN-20260918.md`**（含全部行号级证据）。
> **用户原话**：「区分度有点问题，更加有含金量的结论和每一次都有的日志会混在一起」
> 「配额这个问题也困扰我很久……**科学的（测量），不能拍脑子**」

**病根（实测）**：语义臂内部**完全不分层**（`index.js:6069` 纯按分数 `sort`），
且检索输出**不显示 layer**（`:6093` 只有 label）⇒ 结论与流水在模型眼里完全同级。
融合层的层次优势（≈0.001）被流水层的数量优势（本机 449 vs 16）碾压。

⇒ **不对称**：**注入侧早就有分层配额**（`tier0-catalog-pre.js:391 TIER0_QUOTA_DEFAULTS`，project 保底 60%），
**检索侧没有**。这是本项要消除的不对称。

### 四个子项（用户已逐条批准）

| 子项 | 内容 | 风险 | 用户态度 |
|---|---|---|---|
| **R4-B** | **分层展示**：检索输出按层分组（结论层/流水层），**不改排序只改呈现** | 极低 | 「分层显示可以先做」 |
| **R4-留痕** | 语义臂降级链 `py→C2→词法` **每一跳都 record**（现在 `:5882` 只有 diag） | 低 | 「记得及时在日志里报告」 |
| **R4-A** | 作废条目**返回但标记**「✗ 已作废」+ 指向最新结论（需 `supersededBy` 指针） | 中 | 「明确告诉 AI 这是过时的结论」 |
| **R4-C** | 检索侧**层次保底配额** | 高（改排序） | 「可以单独评估」 |

### ✅ R4-配额锁死（2026-09-18 完工，**原"节流锁死"推断已被实测证伪**）

**用户原话**：「我尝试把其中一个固定额度加大成两倍的时候，**还遇到了一个锁死的问题**」—— 报障成立，但**机制不是节流**。

**⛔ 原推断作废**（~~整理一次成功后 10 分钟内任何再写入都被 `throttled` 拒绝~~）：
1. **`reason:'throttled'` 是死分支** —— 全仓 0 生产者，只有 `budgetRefusalTextPre` 里的文案 ⇒ 不可能有人被它拒绝；
2. **节流只挡"再跑一次 AI 折叠"**（`allowFold` 只喂给昂贵的折叠分支），**不挡写入**；实测窗口内第二次写入**成功**。

**真实缺陷（两处，都在 legacy 路径 —— 而 `memoryAnchorEnabled` 出厂默认 `false` ⇒ 这是默认可达路径）**：
```
:4621 capacityLimit   <500 视为脏值回落默认 24000
:4655 ensureBudget    超限 → 整理循环（最多 3 轮）
:4772 compactLegacyLayer
  ├─ 缺陷 1  keepBudget = max(PROTECT, limit - deficit - 1)
  │           展开 = 2*limit − curChars − add − 1 ⇒ 保留目标随额度**递增**（方向反了）
  │           且 PROTECT 由**软**下限变**硬**下限 ⇒ 额度落进死亡区间被拒、更小/更大却可写 = **非单调**
  └─ 缺陷 2  缺回写护栏 ⇒「AI 不可用」分支把刚归档的老段落原样写回
              （实测 1729 → 1730 chars，不降反升）⇒ 归档了却没腾空间 ⇒ 三轮后 still-over-capacity
```

**已修（2026-09-18）**：① `keepBudget = Math.max(0, curChars - deficit - 1)`（因 `ensureBudget` 仅在
`cur+add>limit` 时整理 ⇒ `deficit≥1` ⇒ 恒有段可回收）；② 补上锚点路径同款回写护栏
（`reclaimed - deficitHere`，`deficitHere` 由 `cur.length - keepBudget` 还原 —— 该函数签名无 `deficit`）。

**验收**：探针 `artifacts/_probe-budget-lockup.mjs`（10/0）· 套件
`tests/smoke/smoke-test-r4-budget-lockup-pre.mjs` **25/25** · 变异 `artifacts/_mutate-r4b.mjs` **5/5 真红**
+ SHA256 逐字节还原 · 全量回归 **PASS 121**。

> **★ 可复用教训**：看到「时间戳 + 节流常量 + 一个只在文案里出现的 reason」很容易推出"被节流拒绝"，
> 但**判断一个 reason 是否为活分支，要 grep 它的生产者，而不是读它的消费者文案**；
> 且 `allowFold` 这类标志只控制"要不要再花一次钱"，与"能不能写"是两件事。

### ★ R4-配额测量闭环（用户要的「科学测量」）

**关键发现**：`index.js:5099 state.tier0Meta` **已经在采集** `{ tokens, items, candidates, dropped, perLayer }`
⇒ **"因配额丢了多少条"这个数据早就有了，只是没被用来调参**。

**闭环设计（零新建状态源，复用 R3 degrade 台账）**：
```
采集：每轮把 tier0Meta.dropped/perLayer 追加到 degrade 台账（通道已有）
观察：面板展示「近 N 轮各层 dropped 趋势」
判定：长期 dropped>0 ⇒ 配额偏小；长期 dropped=0 且远未用满 ⇒ 偏大、浪费 token
调整：按观察结果改默认值，不拍脑袋
```
> 用户痛点原文：「有的时候配额太少，效果完全没有，或者有些大条目可能就被过滤掉了；
> 有的时候配额多了，我又怕浪费 token，不知道对 token 的影响有多大」

### 执行顺序（用户裁定）

```
第 1 批（低风险）：R4-B 分层展示 + R4-留痕补齐          ✅ 完工 2026-09-18（套件 99/99 · 变异 9/9）
第 2 批：R4-配额锁死复现与修复 + R4-配额测量闭环        ✅ 完工 2026-09-18（25/25 + 59/59 · 变异 5/5 + 6/6）
第 3 批（随 G3）：R4-A 作废标记 + supersededBy          ⏸ 待用户拍板 2 个边界
第 4 批（单独评估）：R4-C 层次保底配额                  ⏸ 未开工（会改排序，需先评估）
```

### 待用户确认的边界

- **`supersededBy` 是否违反 S10.4「不新建状态源」？**
  **倾向不算** —— 它是 `status` 的**伴生属性**（同为条目字段），非平行账本。但需明确。
- **R4-A 的可见后果**：作废条目从「静默剔除」改为「返回但标记」——
  与 G3 的「剔除 vs 降权」是**同一个决策的两面**，需一并定。

---

## 4. 第三层 · 表层 S（详细）

### S1 · 容器结构 　【作战图 §1.1–1.4】

**痛点**：左下角太挤（记忆按钮 + 浮层 + 欢迎卡 + 更新卡都在争 `left:10px; bottom:64px`）。
**用户思路**：把记忆窗格挪到看板上集成，**看板升级为"承载面"，只把看板当其中一个功能**。

| 步 | 内容 |
|---|---|
| S1a | 定架构：功能清单 → 每个功能的"可挂载"改造点 → 位置配置键设计 |
| S1b | 看板升级为容器（Tab / 分区），记忆窗格作为其中一个功能页 |
| S1c | 位置设置项 + **即时回显**（用户硬规则） |
| S1d | 旧位置（左下角）保留为可选（共存，非替换） |

**⚠️ 待用户澄清（§1.3 老问题）**：「类比 literature 插件的理念」指
(a) 同一功能多处可挂载，还是 (b) 设置内切换"功能归属面板"？
> 补充：`dsh-literature` **确实已安装**（`~/.dsh/profiles/web/node_modules/@a9i5k4/dsh-literature`，mtime 08-30），可随时读取参照。

### S2 · 视觉对齐 DeepSeek Flow 　【作战图 §1.5】

8 条设计要素 V1–V8（见 §1.5 表），其中 **V5「连线分支标签」最值得吸收**：
本仓账本 tag（`type:dead-end`/`topic:*`）天然能画成边上的分支标签。

**硬约束**：
- **禁止凭印象拼 CSS 令牌**，只用 `label-*` / `bg-layer-*` / `border-l*` / `state-*`
- 只动视觉层，不得改变行为与开关语义
- 主题切换**必须即时回显**

**验收（不接受"看着像"）**：
1. 明暗两主题各一条断言（断言取色**来自令牌族**而非硬编码）
2. 「缩放」「显示全图」「独立滚动」三交互各一条断言
3. 按本仓真实账本量级（92 账本 / 417 小节卡）跑一次流畅性
4. 视觉改完后**全量回归维持 0 失败**

---

## 5. 执行纪律（每步都适用）

| # | 纪律 |
|---|---|
| 1 | **改前备份** `*.bak-YYYYMMDD-<tag>` |
| 2 | **改后 `node --check`**（`lib/index.js` 是 **CRLF** 大文件，`edit` 的 `replace_all` 要核命中数） |
| 3 | **每步配可失败断言 + 变异演示**（变异后必须真红，再按 SHA256 逐字节还原） |
| 4 | **跑全量回归**：基线 **PASS 106 / FAIL 0 / TIMEOUT 0**（`node tools/run-smoke.mjs`，约 183s） |
| 5 | **无 BOM**；大文件分块写 |
| 6 | 代码留 **pre 线**，未获明确同意不 commit/push |
| 7 | host 代码改完**只告知用户自行重启**，严禁 Stop/Start-Process |
| 8 | 结论附代码/日志证据；**推断显式标注** |

### 每步的收尾动作（固定）
```
备份 → 改码 → node --check → 写新套件 → 跑该套件(看红)
     → 变异演示(确认真红) → 还原 → 跑全量回归(确认 0 失败) → 留痕
```

---

## 6. 里程碑与验收口径

| 里程碑 | 判据 |
|---|---|
| **L 层冻结** | L1–L7 全绿 + 全量回归 0 失败 + 用户 bug①② 的可复现步骤已不能复现 |
| **M 层冻结** | M1–M3 全绿 + 全量回归 0 失败 |
| **S 层冻结** | S1–S2 全绿 + 明暗主题断言过 + 92 账本量级流畅 + 全量回归 0 失败 |
| **最终交付** | 三份文档（缺口总账 / 作战图 / 本流程图）+ 全套新断言 + 回归全绿；**host 改动需用户重启后生效** |

---

## 7. 需要用户拍板的事

| # | 事项 | 影响 | 状态 |
|---|---|---|---|
| 1 | **lint ③④ 的判据** | 不定则 M2 只做 ①②④，③⑤ 挂起 | ✅ **已解除**（改为「主动触发 + 写入时兜底比对」，无需预先穷举） |
| 2 | **S1.3 literature 理念**：(a) 多处可挂载 / (b) 设置内切换归属 | 决定 S1 的架构形态 | ⬜ **待定** |
| 3 | **缺口 5 走 (a) 改注释 还是 (b) 补保护门** | — | ✅ **已按 (a) 实施**（L6） |
| 4 | **G3 自动标 `superseded` 的置信门槛** | 决定误判风险 | ✅ **用户拍板：(a) 只认显式声明** |
| 5 | **`superseded` 的可见后果** | 决定「标错」的代价 | ✅ **已拍板 2026-09-18**：**「返回但标记是正确的」**；且**`retracted` 也不过滤**——用户 2026-09-18 再修正：「并不是挡，我感觉是**备注**……不记住这个教训还会再踩」（见 §10） |
| 6 | **`supersededBy` 是否违反 S10.4「不新建状态源」** | 决定能否实现「指向最新结论」 | ✅ **已拍板 2026-09-18**：「**肯定不算新建状态源啊**，即使他是的话，我觉得也是**有必要增加的**。因为这个对效果真的非常有作用」 |
| 7 | **`procedurePromotionEnabled` 出厂默认** | 决定普通用户能否用上技能层 | ✅ **已解除 2026-09-18**：**不是默认值问题**——是**有意临时关闭**（Hermes 遗留未修，用户原话「所以才先关掉的，改完自然就能打开了」）⇒ **待办改为修 Hermes 那套的问题** |
| 8 | **「教训」是否自动进 procedure** | 决定「踩过的坑」能否自动变成 skill | ✅ **已拍板 2026-09-18**：「**教训肯定得进 C 啊，它不自动晋升，但是可以形成候选，模型也可以通过搜索搜索到**」⇒ 进候选 + 可检索 + **永不自动晋升** |
| 9 | **注入侧是否也一律返回** | 与检索侧一致 vs 维持过滤 | ✅ **已拍板 2026-09-18**：**过滤侧问题随 I5 修正一并解决**（见 §10）；注意 skill 的注入路径已澄清为**本插件语义召回**（§7 第 11 项已撤销） |
| 10 | **Hermes 遗留问题修复** | 打开 `procedurePromotionEnabled` 的前提 | ⬜ **新识别依赖项**（§8 第 4 条） |
| ~~11~~ | ~~`act.skill` 段在官方接管后的定位~~ | — | ❌ **已撤销**：用户澄清 **skill 是相对独立的系统**，晋升成功后**会被本插件正常注入**（经语义召回）⇒ `act.skill` 段是**实现本体，保持不变** |

> 前 3 件**不阻塞 L/M 层**。第 4–6 件**已全部拍板**（G3 + R4-A 的前置已解除）。
> 第 7–9 件与 §8 第 4/5 条相关，不阻塞当前批次。

---

## 8. 远期调研清单（Long-term research backlog）

> 归口约定：本节条目**不进 L/M/S 执行顺序**，不参与层级冻结；只在用户明确点名或排期时才启动。
> 每条必须写清：**现状 → 痛点原话 → 待回答的问题**。
>
> **★ 2026-09-19 修订（教训）**：本约定曾导致**用户已明确提出的痛点被无限期搁置**——
> 用户 2026-09-18 提出「长期记忆表述看不懂」（下表第 5 条同族），到 09-19 追问时才发现
> **它从未进入任何一批的执行序**（因为被归进了本节）。
> ⇒ **新纪律**：本节的条目**一旦被用户点名，必须立即移出本节、并入
> `PRE-FRONTEND-CHECKLIST-20260919.md` 的执行序**，并在原位置留一行「已并入」的指针。
> （下表第 4/5 条已按此纪律并入该清单的 ⑪ / ⑩，见下。）

| # | 条目 | 现状 | 用户痛点原话 | 待回答 |
|---|---|---|---|---|
| 1 | **自建白板/记忆可视化形态** | 已吸收 dsh-graph 的**可视化形态**（列式泳道 + 状态徽章 + 卡片），数据源仍是自己的 `handoff/index.json` | — | 是否需要更深的图结构（依赖图/时间轴） |
| 2 | **面板侧配额可视化** | `tier0Meta` 已采集 `dropped/perLayer`，但**没有趋势图** | 「配额这个问题也困扰我很久」 | 见 R4 §4 测量闭环 |
| 3 | **多窗口/多会话的记忆竞争** | 未调研 | — | 并发写入是否有覆盖风险 |
| 4 | **教训/攻关/重复事件 → skill 式固化** | `procedure-store-pre.js` + `memory-hub-pre.js` 三层已存在（episodic/fact/procedure），证据制晋升闸门齐全；但 `procedurePromotionEnabled` 出厂 **false**，且**「教训」无自动通路** | 「这个教训和这个攻关完成的项目，和多次重复的事件，应该都可以上升为 skill 式的。这个长期记忆就是 procedural memory」 | 见 `PROCEDURAL-MEMORY-AND-APPROVAL-DESIGN-20260918.md` §2；4 个待拍板项在 §4<br>**★ 已并入 →** `PRE-FRONTEND-CHECKLIST §2 ⑪`（skill 导出层） |
| 5 | **审批界面可读性**（"看不懂只能盲确认"） | 记忆中枢页只显示 `title`/`stage`/`riskLevel`/`evidence 计数`；**审查所需三要素结构性缺失** | 「用户都说看不懂……太抽象了，名字也不知道，流程是啥也不知道，没法审，只能盲确认通过」 | 展示证据链 + 预览"晋升后会注入什么"（`renderChecklist` 已存在）+ 内部枚举给中文解释 + 可撤回<br>**★ 已并入 →** `PRE-FRONTEND-CHECKLIST §2 ⑩`（长期记忆可读性）+ §4（前端重构的审批界面组） |
| 6 | **首次启动页重构 + 赞助商/中转站展示** | 未做 | 「之前接到的赞助商和中转站，需要在首次启动页面进行展示。但是，这个首次启动页面也该重构了，而且风格也要变化」 | **归口前端重构**；用户要求「放到后面一点，等做前端的时候再提醒我」 |

---

## 9. 外部 issue 修复批次（2026-09-18 新增 · 用户裁定「前端重构之前一起改」）

> **贡献方**：`Minervaowl7`（**合作方**，非陌生用户）——已确认后续会持续提 issue，并在机制摸索完成后再提 PR。
> **核验**：4/4 全真，报告见 `docs/internal/ISSUE-55-58-VERIFICATION-20260918.md`。
> **用户裁定**：「先把这 4 个记录等到**前端重构之前**一起改」。

| # | 位置 | 缺陷 | 修法 | 风险 |
|---|---|---|---|---|
| **#55** | `lib/storage-manage-pre.js:119` | `readSidecarPrev` 引用未定义的 `docStore`（只在 `:135`/`:169` 局部定义）⇒ 恒抛 ReferenceError 被 `:127` 吞 ⇒ 恒返 null ⇒ **「修复」按钮把 fresh 证据全翻 stale（功能反向）** | 函数内补 `const docStore = docStoreOf()`；补 smoke：repair 后 epoch/version 应继承 | 极低 |
| **#56** | `lib/evidence-store-pre.js:119` | `_appended.add(id)` 在写盘**之前**；写失败无 delete ⇒ 一次瞬时失败后同 id 重试恒被拒 = **证据静默丢失** | 写入成功后才 `add`；或失败时 `delete`（需确认 `BoundedIdSet` 有无 delete） | 低 |
| **#57** | `lib/episodic-store-pre.js:292` | `current = data.current \|\| null` **零形状校验**（对比 `:222` `validateEpisodePre`）⇒ `current={}` 时每次 consolidate 抛 TypeError ⇒ **巩固链路静默停摆** | 补形状校验（`sessionRef` 字符串 / `startedAt` 有限 / 三数组），不合格置 null（**丢弃优于卡死**） | 低 |
| **#58** | `lib/activation-host-pre.js` | ① `disposeRuntime/disposeSession` **全仓零调用方**（仅 `index.js:10869` disposeAll）② `:352` 写入键 `sessionId\|ws:workspaceKey` 与 `:510` 删除键 `String(runtimeKey)` **错配** ⇒ 即使接线也是空操作 | ① `dispose(agent)` 补调用 ② 键格式统一（**须同时核对全部键读写点**） | 中 |

**★ 本批的共同特征（值得记为判据）**：4 条**没有一条**会被现有 smoke 抓到，也**没有一条**在正常使用中报错——
3 条是 **fail-soft 的 catch 吞掉了本该炸的错误**，1 条是"删除键写错 ⇒ 删除静默失败"。
⇒ **fail-soft 是本仓铁律，但代价在这里集中兑现**：catch 把"代码 bug"降级成"无明显症状"。
⇒ **结论：静态审计（标识符作用域 / 缓存登记时机 / 键格式一致性）是与 R1「降级点普查」并列的独立证据面。**
（用户已确认合作方后续会继续提，本条作为我方自查的常态化补充分支。）

**执行纪律**：4 条**当前均零测试覆盖** ⇒ 每条**必须先写"能失败"的套件**；
#58 修完须用**变异演示**确认键格式真的对齐（正是"改了一处、另一处还是旧键"的典型）。

### 9.1 上游缺陷登记：`scope='sessions'` 迁移失败（2026-09-19 发现）

| 项 | 事实 |
|---|---|
| 现象 | `memory_recall_pre(scope='sessions')` 返回 `session-search persistence observation failed: subagent/descriptor 5 uses unsupported descriptor version 2` |
| 触发文件 | `~/.dsh/sessions/--D-dsh-auto-memory--/01740c44-…/session.jsonl.zstd`（48KB，mtime **2026-08-19 15:41**，v0 旧格式） |
| 报错层 | DSH 上游 `dsh-session-query-sqlite/lib/index.js:739` → 内层 `dsh-session-format-v0-to-v1/lib/index.js:1586` |
| 根因 | v0→v1 迁移器只接受 `subagent/descriptor` 的 `version === 3`（`:1584`）；该文件里是 **2** ⇒ 抛 `SessionFormatUnsupportedMigrationError` ⇒ **整个 `scope='sessions'` 查询失败**（不是跳过该文件，是直接抛错） |
| **影响面（关键）** | **仅 `scope='sessions'` 分支**。`_sessionQuery` 全仓只有两处引用（`index.js:9369` 赋值 / `:2707` 唯一使用点），scope 路由是三条互斥分支（`:5869` handoff / `:5876` sessions / `:5913` all）⇒ **默认 `all` 走本地语料 + 语义臂，完全不受影响**（用户实测：日常 recall 从未报错） |
| 归属 | **上游缺陷**（DSH 自带模块），非本插件；本插件唯一可做的是"失败时不把上游错误原文抛给模型"（已登记 `TODO-BACKLOG.md` §B） |
| 处置 | 用户裁定：「**我现在就是大版本施工，你给我把这个问题排在这个作战计划里面**」⇒ 登记为**大排期归口项**，与「跨会话跨 Agent 检索（A4b/P2-⑦）」合并处理 |
| 已裁定前提 | `TODO-BACKLOG.md` §B 原话：该特性「完全外挂 DSH 的 opt-in 特性（出厂 `openAt: never`）」，且**已拍板"等大排期一起定"，暂不动工** ⇒ 本次**不修**，仅登记 |

> **⚠️ 表述纪律（agent 已犯一次，2026-09-19 03:0x）**：不得把它描述成"影响日常 recall"——
> 它**只影响显式 `scope='sessions'`**。agent 曾据此说"卡住了工作"，属**夸大**，已就地更正。

---

## 10. ★ I5 语义再修正（2026-09-18 用户裁定 · **推翻 agent 上一版方案**）

**agent 上一版方案**：`superseded` 放行+标记，`retracted` **继续硬过滤**。
**用户裁定**（原话）：

> 「首先，我觉得这个 **reacted 不是过滤掉**。因为比如说你之前踩过 3 次的那个坑，
> 如果你不记住这个教训的话，你还会再踩。所以**并不是挡，我感觉是备注**。
> 如果遇到这个，后面应该说明现在**更正的内容在哪个哈希值里面**，方便 AI 去搜索。」

**为什么用户是对的**：agent 的分法来自**检索视角**（过时的别干扰判断）；
用户的分法来自**学习视角**（**做错的事恰恰最该被记住**）。
对记忆系统而言后者才是目的——`retracted` 不是垃圾，它是**一条教训**。
藏起来 = **系统性遗忘自己的错误**，正是"还会再踩"的成因。

**修正后的语义**：

| status | 呈现 |
|---|---|
| `current` | 原样（**逐字节向后兼容**） |
| `superseded` | `⚠已作废（已被 mem_<hash> 取代）` |
| `retracted` | `⚠已撤回（原因：<reason>；更正见 mem_<hash>）` |

**三态一律返回**；`isRetrievablePre` 只对**未知值 fail-closed**（防未来新增枚举时静默放行）。
**指向哈希**：id 就是既有 `mem_<32hex>` 锚点 id（**不新建 ID 体系**，守 S10.4）；
标记内嵌 id ⇒ AI 可直接拿它去 `memory_recall_pre`/grep 追最新结论。
**注入侧维持过滤**（常驻目录仅 800 token，装过时条目会挤掉现行结论；检索是按需的）。
⇒ **I5 的"两处"要求被拆开**：检索侧由"过滤"改为"标注"，注入侧不变。


