# dsh-auto-memory 白皮书

> **版本**：3.0.0 · **日期**：2026-09-20 · **许可**：BSD-3-Clause
>
> **本文是什么**：一份**约束性文档**。它把「这个插件到底建了什么、它有哪些已知边界」
> 讲清楚，并把一条**核心不变量**显式成文——因为**约束不落纸，就一定会再被违反一次**。
>
> **本文不是什么**：不是宣传材料（宣传看 README），不是 API 文档（看 `FEATURE-INVENTORY.md`），
> 不是论文（看 `M7-RESEARCH-PAPER.md`）。

---

## 0. 一句话

> **同一份记忆字节，同时喂给三个消费者。任何一处写入，都要同时满足三套约束。**

这是本插件全部工程复杂度的来源，也是历史上绝大多数 bug 的来源。

---

## 1. 核心不变量

### 1.1 三个消费者

记忆在磁盘上是纯 Markdown 明文。但同一份字节被三个**语义不同**的消费方读取：

| # | 消费者 | 怎么读 | 对字节的要求 |
|---|---|---|---|
| ① | **注入面** | 每轮自动注入进上下文 | 必须**干净、短、有结构**；乱码与超长行会污染上下文 |
| ② | **检索面** | 词法 + 语义双路召回 | 必须有**可切分的锚点结构**；锚点被破坏则召回精度下降 |
| ③ | **语义面** | digest 供语义模型编码 | 必须**可重复生成**；同一输入必须产出同一 digest |

### 1.2 约束

> **一处写入，三处生效。** 写入方必须同时保证：
> 1. 注入面拿到的字节在**预算内且无污染**；
> 2. 检索面能按锚点**正确切条**；
> 3. 语义面的 digest 能**正确失效并重建**。

### 1.3 为什么这条不变量危险

因为它**大多是静默降级**：

- 注入进了一段乱码 → 模型读到噪音，**不报错**；
- 锚点被写坏 → 召回少了几条，**不报错**；
- digest 没失效 → 语义召回用了旧向量，**不报错**。

**不爆的时候，完全看不出来。** 这就是为什么必须有一条显式约束 + 一批针对性断言。

---

## 2. 一次真实事故（本不变量的代价）

这是本项目最典型的一类 bug，值得完整记录。

### 2.1 现象

用户在面板「日志」页签看到**乱码**；同时记忆文件侧也出现异常内容。

### 2.2 根因

**两条写入通路，只修了一条。**

- 该插件有**两条互不相干**的写入通路：`procedure`（技能）线与 `fact`（事实）线；
- 早先的清洗器只接进了 `procedure` 线；
- `fact` 线的三个入口（`crossFeed` 的 fact 分支 / `factCandidateFromRow` / `hubFlushTick` 的回写）**从来没有过清洗器**；
- 于是脏数据既进了 `facts.json`，又经回写污染了 `MEMORY.md`。

> **教训**：「修了一条通路」不等于「另一条受保护」。
> 只修**具名入口**是治标；把清洗**下沉到写原语**才是治本。

### 2.3 处置

- **当时**：在三个已知入口设防，补清洗器 + 新增判据族；
- **根治（在途）**：把 `sanitizeForWrite` 下沉到写原语（`appendText` / `writeFull`），
  使**全部写入路径自动受保护**——不再依赖「记得给新通路接上清洗器」；
- 存量脏数据按**用户拍板**采用 `skip` 并留痕，**不做「清洗后照写」**。

> **为什么不做清洗后照写**：那等于悄悄改数据，并丢失「曾出现脏 fact」这一诊断事实。
> 本项目纪律是 **fail-soft 必须留痕、不得静默改写**。

---

## 3. 系统的实际形态（截至 3.0.0）

### 3.1 规模（全部经代码自核）

| 项 | 数量 | 说明 |
|---|---|---|
| 模型工具 | **17** | 15 个基础 + 2 个条件注册（`memory_expand_pre` / `memory_trace_pre` 仅在 `boardMode=graph` 时注册） |
| HTTP 路由 | **49** | 全部 loopback-only；插件层外部访问返回 403，宿主层为 401——**两者都是预期行为** |
| 设置键 | **98** | 其中 **60** 个可在设置页写入 |
| 面板页签 | **12** | 概览 / 日志 / 唤起回顾 / 记忆中枢 / 存储管理 / 笔记 / 白板 / 反思 / 接续 / 日历 / 检索 / 工作区 |
| 插槽注册 | **6** | 侧栏入口 / 浮层面板 / 弹窗 / 接续卡 / 设置分区 / 会话页看板 |
| 运行时依赖 | **0** | 本插件自身不引入任何 npm 运行时依赖 |
| 浏览器侧 | 单文件 | `lib/client.js`，纯 ESM，**无构建步骤** |

### 3.2 两条线

- **pre 开发线**（本仓）：profile 以 `link:` 挂载 ⇒ **开发树就是活的宿主代码**
- **REL 发布线**（`_publish_dsh-auto-memory`）：发布时做 `_pre` 后缀擦除与残留闸门

> ⚠️ **「PR merge ≠ 修复落地」的结构性根源**：仓内裸名 `lib/*.js` 是历史遗留的**互引孤岛**
> （零活引用，唯一例外 `ws-overview-rank.js`），宿主只 import `-pre.js` 版本。
> **改错副本 = 改了不生效。** 这是本仓最易踩的坑，已写入约束清单。

---

## 4. 已知边界（诚实清单）

### 4.1 特性默认值（★ 3.0.0 起有翻转，旧文档口径已失效）

> **警告**：本表在 3.0.0 有过一次**批量默认值翻转**。任何沿用 3.0 之前口径的文档
> （包括早期 README 与第三方说明）都会写错。以下数值**经代码自核**
> （`tools/verify-defaults.mjs` / `tools/verify-experimental.mjs`）。

| 特性 | 键 | **当前默认** | 备注 |
|---|---|---|---|
| 交接白板 + 账本 | `handoffEnabled` | **`true`（开）** | ★ 3.0.0 由 `false` **翻转为 `true`** |
| 白板看板（5 泳道） | `boardMode` | `'graph'` | 默认新版看板 |
| 水位测量窗口 | `waterLevelWindowTokens` | `0` | `0` = 自动沿用官方窗口容量 |
| 水位阈值 | `waterLevelThreshold` | `0.75` | — |
| 水位提示 | `waterLevelAdvisory` | `true` | — |
| 水位自动写账本 | `waterLevelAutoHandoff` | `true` | — |
| **自动接续** | `autoContinueEnabled` | **`false`（关）** | 与白板**已解耦**，各自独立 |
| 自动固化 | `autoConsolidate` | `true` | — |
| Tier-0 常驻目录 | `tier0CatalogEnabled` | `true` | 预算 400 tok / 占比 25% |
| 注入总开关 | `injectEnabled` | `true` | 预算 8000 字符 |
| 记忆中枢 | `memoryHubEnabled` | `true` | — |
| **机械流程切片** | `hubMechanicalProcedureFeedEnabled` | **`false`（关）** | T10 关停；向导内有标「不推荐」的回退开关 |
| 主动联想 | `associativeMemoryEnabled` | `false`（关） | 需显式开启 |
| 无人值守 | `unattendedMode` | `false`（关） | — |
| 容量上限 | `noteCapacityChars` / `userCapacityChars` | 各 **24000** | 2026-09-18 由 12000 上调 |

**白板默认翻转的理由**（代码注释原文）：README 与用户手册早已对外声称「交接默认开启」，
而代码默认是关——**文档与实现长期不一致**；且白板/账本是 3.0 的招牌能力，
出厂关着等于新用户看不到它。

> **这条本身就是「约束不落纸」的又一个样本**：文档写「默认开」、代码写「默认关」，
> 双方各自都以为自己是权威，一直没人发现。

### 4.2 有测试、无接线

项目内发现**一套协议库三件套**（acceptance / ledger-criteria / state-commit）
被 smoke 测试养着，但 `index.js` **未引用**。属「有测试、无接线」——
留作后续整合，当前不影响功能。

### 4.3 两条路由无 UI 消费者

`/subagent-gc` 与 `/activation-inbox-pre`：**能力在、UI 未接**。

### 4.4 前端回归网极薄

146 个测试套件里，**仅 3 个**断言 React 组件行为。
这意味着**前端改动几乎不受回归保护**——重构前需先补特征化测试。
（这也是「前端交给社区共创」的前提条件之一，见 `FRONTEND-CO-CREATION.md`。）

### 4.5 授权边界

| 资产 | 许可 | 说明 |
|---|---|---|
| 本项目代码 | BSD-3-Clause | — |
| 角色设定「溟月」 | **CC BY-NC-SA 4.0** | 原作者 **上善无形**；**非商业 + 相同方式共享**，比代码许可更严格 |
| 参考项目（RhineLabUI 等） | MIT | **仅覆盖其程序代码**，不覆盖 3D 模型 / Blender 工程 / 原片素材 / 游戏品牌 |

> **角色许可与代码许可是两件独立的事，不可合并成一句「本项目采用 XX 许可」。**

---

## 5. 写给后续写入者（约束清单）

任何新增**写入路径**的人（包括 AI 自己）请逐条自查：

- [ ] **三面同时满足**：注入面干净、检索面锚点完好、语义面 digest 正确失效
- [ ] **走写原语**：不绕过 `appendText` / `writeFull` 的校验与事务
- [ ] **fail-soft 必须留痕**：禁止静默吞异常（本项目有 `DEGRADE_RING` 降级台账）
- [ ] **不得静默改写**：脏数据 `skip` 并留痕，不要「清洗后照写」
- [ ] **改对副本**：`-pre.js` 才是活代码，裸名文件是孤岛
- [ ] **配置写入唯一出口**：`saveConfigPatch` → `POST /config`
- [ ] **开关解耦**：单开关不得顺带改变其它功能行为
- [ ] **新旧并存**：已发布的 3.0.0 有真实用户 ⇒ 必须保留开关回退路径

---

## 6. 本白皮书与其它文档的分工

| 文档 | 回答什么 |
|---|---|
| **本文** | 「有哪些**不能违反**的约束、哪些**已知边界**」 |
| `FEATURE-INVENTORY.md` | 「有哪些功能、住在哪一行」 |
| `ARCHITECTURE-FOR-ZCODE-20260920.md` | 「代码结构是怎样的」 |
| `M7-RESEARCH-PAPER.md` | 「为什么这样设计、实验数据如何」 |
| `FRONTEND-CO-CREATION.md` | 「外部贡献者能改什么、怎么改」 |

---

## 7. 结论

本项目的「完工」有两层含义：

- **功能层面的完工**：功能已稳定，3.0.0 已发布，全量回归 PASS。
- **认知层面的完工**：**本文** —— 把约束与边界写下来，让后续任何人（含 AI）
  不必靠口口相传就能避开已知的雷。

> **约束不落纸，就一定会再被违反一次。**

这句话是本文存在的全部理由。
