@nightpainters/dsh-ling 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,51 +2,217 @@
2
2
 
3
3
  本项目遵循语义化版本:`MAJOR.MINOR.PATCH`。
4
4
 
5
- ## 1.6.1
5
+ ## 1.7.0
6
6
 
7
- **3×3 红蓝对抗审计的修复版 · 0 条阻断发布 · 14 条该修全部修完**
7
+ > ⚠️ **工作区候选 · 未发布。这一节记的是工作区里已落地、已被离线测试覆盖的工作,不是已发布状态。** 包版本已抬到 **`1.7.0`**,但**没有发布、没有安装**。宿主界面上的新标签**已在本机真机验收走通**(第五标签、两个图书馆工具、确定性核对、撤下留痕,2026-10-07),**第六标签「结论层」与其工具面已于 2026-10-15 随一次宿主重启实测生效**;对外发布仍需走正常的安装/重载流程(见 README「快速开始」)。
8
8
 
9
- > 一句话:1.6.0 发布前跑了 **红队 3 路 + 蓝队 3 路**的对抗审计(6 个独立审计员),总账 **0 条阻断发布 / 14 条该修 / 10 条观察**;本版把 14 条该修**全部修完**,并修掉 1.6.0 提交后 CI 暴露的一处脆弱断言。
9
+ **图书馆:一个跟记忆分开的库 —— 只搜与核对,认证的是"声明的那一处"。**
10
10
 
11
- > ✅ `package.json` 版本号为 **`1.6.1`**。**无新功能、无 schema 变化**(仍是 v17);⚠️ 修复多落在 **host 侧** ⇒ **需重启宿主**才生效。
11
+ > 一句话:给器灵加一个**能回头对账**的库。它不掺进记忆 —— 既有记忆永不迁进图书馆,图书馆也不进注入面;器灵只有两只手(**搜**、**核对**),**没有维护工具**;核对结论只有 `ok` / `wrong` / `unknown`,而且它**只认证条目声明的那一处比较,不认证解释性正文、也不构成通用真理**。
12
12
 
13
- ### 一、注入面(记忆开场)
13
+ ### 一、独立库:与记忆库并列的第二套表
14
14
 
15
- - **置顶条目现在真的"必进"**:此前 `maxItems` 与字预算**先于**置顶判定生效 ⇒ 置顶数超出上限时,多出来的置顶会被挤掉(穷举 90 组实测**累计丢 117 条**;真机 9 条置顶 + 上限 3 ⇒ 只进 3 条)。修法:两道闸门对 `pinned` **豁免**。复测:9 条置顶 + 上限 3 ⇒ **进 9 条**,穷举 90 组**丢 0**。
16
- - **落选者留痕的 `source` 恒为 `undefined`**:深条目路径读的是 `row.source`,而该列列名是 `src` ⇒ 那一栏一直是空的。同批把 `snapshot.js` 的字段白名单补上 `src/kind/durability/hit/pinned`(否则面板拿不到这几列)。
17
- - **打分理由与公式不符(两条路)**:`why` 串按"加法"报分项,而真实公式是"分项先加、再乘血缘与矛盾系数" ⇒ 改成 `(分项和) × 血缘 × 矛盾 ⇒ 总分`,**概述路与条目路**同时改(第一遍只改了条目路,被断言当场抓住)。
18
- - **概述路的"热度"漏乘类别权重**:同一分数在留痕里被报成两种值(daily 差 0.252、feeling 高出 88%)⇒ 改报**类别加权后**的值。
15
+ - 图书馆有**自己的 `library.db`**(与 `memory.db` 同目录、独立表);**不迁移记忆、不参与开场注入**,两库之间没有隐式通道。
16
+ - 条目形态 = **值 + 出处 + 取数时间 + 核对规格**:`title / body / category / source_kind / source_ref / as_of`(必填),可选 `version / conditions`,以及 `check_spec`(定位 + 期望值 + 比较方式;`null` = 不可核)。
17
+ - **"不可用"与"零命中"是两件事**:打不开 / 未启用 / 运行时不在场 ⇒ 明确的不可用,**绝不回落成"搜到 0 条"**。
18
+ - 一条边界写清:**"不迁移记忆库里的旧数据"不等于"禁止网页端来源的资料"** —— 资料能不能进馆看的是**有没有具体、可追责的出处**,与渠道无关。
19
19
 
20
- ### 二、库与检索
20
+ ### 二、只搜索与核对两个工具
21
21
 
22
- - **`recall` 检索档印的"下一步"用错坐标系**:条目里的 `seq_from` 是**事件流 seq**,而 `recall(conv, from, to)` 收的是**对话轮序号**。实测 274 条可比条目里 **207 条(75.5%)**照那条指引会 out-of-range,偶尔落在范围内的也只能读到不相干的轮次。修法:**不再印 `from`**,改为"先 `recall(conv=…)` 拿对话目录,再按目录里的 `#N` 取原文"。
23
- - **回灌会把网页端会话升格成"主人自己的 DSH 会话"**:`restoreEntry` 原先按 id 形状回推来源(裸 uuid ⇒ `dsh`),而 **1,523 条网页端会话全是裸 uuid** ⇒ 一次回灌就让该会话多出 `source='dsh'` 的原文行,概述器当场认领 ⇒ 同 id 双概述、双份记忆。修法:id 带 `import:` 前缀仍记 `import`,其余用**调用方声明的**来源;并在工具层加**交叉校验**(声明与归档记录不一致 ⇒ `source-mismatch` 拒)。
24
- - **飞书通道里主人自己的原话被当成"注入"**:真人判据原先要求 `rpcId`,而**飞书桥来的原话没有 rpcId**(全盘 436 条无 `rpcId` 的 user 消息里 **55 条是主人真话**)⇒ 这些句子在 `recall` 读档时被折进"(此处折叠 N 条注入消息…)"、目录里印成「(注入)」,**"你说过什么"整段消失**。修法:**会话级分通道**(取 `session` 事件过 `isMemoryEligibleHeader`)。实测:真人轮 **951 → 1001**(+49 飞书真话 +1 探针),**子代理委托种子 387 条零回归**。
25
- - **锁竞争下插件加载失败**:第一抛出点是 `PRAGMA journal_mode=WAL`(而 `busy_timeout` 排在它**之后**一行 ⇒ 撞锁 **23 毫秒**就抛、连等都没等)。修法一:`busy_timeout` 挪到所有可能撞锁的语句**之前**;修法二:构造失败对**锁类错误**(`errcode 5/6`)做 **3 次 × 2 秒有界重试**,仍失败则打印可执行的排查指引后**降级加载**(不注册记忆工具,不影响其它插件)。
22
+ - `library_search({q, offset?, limit?})` 与 `library_check({id})`;**没有**新建 / 修订 / 撤下 / 改配置的模型工具。
23
+ - 两个工具**只接受列出的参数**:多给一个键就拒(`unknown-arg`);代码根目录、URL 白名单这类**登记项不接受模型入参**。
24
+ - 模型可见的文字回执附完整资料或核对凭据 JSON,不只报标题与结论;受限空页不冒充零命中,分页提示不冒充字节超限。
25
+ - 核对把 `exec.signal` 传下去:**被取消的核对不写库**(取消**不算**一次 `unknown` 认证)—— "取消了"与"核完了"在留痕里分得开。
26
26
 
27
- ### 三、工具与写入边界
27
+ ### 三、核对口径:source 一致性,不是真理
28
28
 
29
- - **「只有概述、没有原文」的记忆永远忘不掉**:遗忘前的归档守卫要求"原文轮数 > 0",而这类条目(主脉记忆 / 导入条目,真库实测 3 条)的内容**就在概述里** ⇒ 判据改宽;同批修正 `archive.js` 的"空归档"判据 —— 它原来把**只有元数据空壳**的会话也算成"有内容"(⇒ 真空归档会被放行,而遗忘标记一旦落下就永不召回)。
30
- - **枝名在回执 / 留痕 / 库里三处不一致**(请求 70 字 ⇒ 库里 60 字):统一常量 `BRANCH_NAME_MAX = 80`,并把改名改成**写后读回** —— 回执与留痕一律报库里的真值。
31
- - **`unlink` 的假回执**:连边可被反向重复写入(`link A→B` 后再 `link B→A` 落两行),而删除只删一条 ⇒ 回执说"断开了"、树里还连着。修法:写入侧查反向(但**有向边不合并** —— `prerequisite`/`supersede` 的反向是不同语义,渲染以 `→` / `↔` 区分),删除侧双向删。
32
- - **`weightScale` 传 `null` 会静默写成 0**:`Number(null) === 0`,而 0 在界面上是**合法读数** ⇒ 改成先判类型(`null`/`true`/`[]` 一律拒)。
33
- - **"被采用"的信号按请求窗口加固**:此前按"请求的区间"给条目计数 ⇒ **没被交付的轮次也 +1**;改用**真正交付**的区间。
34
- - **范围限制改为"如实标注"**:宿主没提供会话清单时,检索档开头写明「⚠️ **没能限定范围** —— 下面这份结果**可能含子代理或其它会话**」,不再静默 fail-open。
29
+ - 三态 `ok` / `wrong` / `unknown`:`unknown` 是"核对**跑完了**却判不了"(取不到源、定位不到、编码不对、超时……)。⚠️ **取消不在这三态里** —— 取消是"没跑完",单列在下一条。
30
+ - **取消 ≠ 一次 `unknown` 认证**:核对在飞时取消 / 断开 / 卸载 ⇒ 本次**不写任何核对记录**(既不留 `ok` / `wrong`,**也不留 `unknown`**),只回"已取消"(带 `cancelled`);**只有跑完的核对才留账、才计入"认证过"**。
31
+ - 比较方式只有两种:**`exact`** = 类型严格的**结构比较**(JSON 对象**成员顺序无关**、**数组顺序有关**;文本按字面精确比);**`text-lf`** = 只把 CRLF/CR 归一成 LF 后精确比。**两者都不 trim、不折叠大小写、不模糊匹配**。
32
+ - 定位支持**行段**与 **JSON 指针**:指针**位置不存在 = `unknown`**(不是 `wrong`)。
33
+ - 回执里写死这句话:**它只认证声明的那个比较,不认证解释性正文,也不构成通用真理。**
35
34
 
36
- ### 四、诊断面
35
+ ### 四、修订与撤下:留旧、带哈希
37
36
 
38
- - 补 `dbg.diff_stats`(差分路的丢弃画像:扫过多少 / 写了多少 / 丢了哪些类型)。此前它只在"一条都没写进去"时留一次记录 ⇒ **正常路径上丢了多少完全不可见**(1.6.0 的原文写入排查就卡在这一条上)。
39
- - 同批:`dbg.diff_stats` 与 `dbg.event_stats` 现在**每次重启都清空**(此前前者会跨重启残留,看起来像"刚跑过"),`evStats` 计数与 kv 的归零点对齐。
37
+ - 修订产生**新版本 + 新 id**,旧版留痕指向新版;撤下只**标记**状态(带原因),**不物理删除**;**跑完的**核对各留一行账(源摘要 / 观测值 / 结论 / 理由,含结论为 `unknown` 的),**取消的不留**。
38
+ - 修订与撤下都必须带**观测到的 `content_hash`**,对不上就拒(防陈旧覆盖);`checked_state` / `checked_at` 这类认证字段**只由核对写**,从写面塞进来一律拒绝。
39
+ - 认证只在"条目**仍 active 且内容未变**"时才落 —— 否则只留记录、不采纳。
40
+ - 状态与哈希判定在写事务内进行,防止另一进程在判定后抢先修订,造成两个最新版或抹掉已完成核对。核对留档与是否采纳是两件事;存储/提交失败仍会明确报错。
40
41
 
41
- ### 五、CI
42
+ ### 五、配置:严格、默认空
43
+
44
+ - 新增公开配置 `library: { enabled, codeRoots, officialUrls, limits }`:代码根目录与官方文档 **URL 白名单默认为空**(管理员登记;**精确 URL / 绝对目录**)。
45
+ - 限额在**激活时校验**:非法值**响亮报错**,不静默夹取;短别名只在存储层兼容,**插件配置面只认规范名**。
46
+ - **先审原始 `library` 键再进框架 schema** —— 否则框架的非严格合并会把未知键(含构造期钩子)**保留**下来。
47
+
48
+ ### 六、远程核对与隐私口径(一处订正)
49
+
50
+ - 官方文档类源走 **HTTPS GET 白名单里的精确 URL**:不跟随跳转、不带凭据、私网地址拒。**发出去的是什么要说准**:这个 GET 会把**登记的 URL(连同查询串)**与**一次普通 HTTPS 请求的元数据**(连接来源 IP、Accept/Accept-Encoding 请求头等)交给对方 —— **对方能看到你的 IP 与这个 URL**,但请求**不会附带**条目正文 / 记忆 / 人格 / 任何本机文件;⚠️ **URL 查询串里若写了敏感信息,它就会真的被发出去**。**白名单默认空 ⇒ 图书馆默认不发生外部取源访问。**
51
+ - 因此 README「隐私」一节补了一条精确表述:**它不往外发正文,但会访问你登记的 URL(含查询串),对方由此能看到你的 IP 与该 URL** —— 此前"不上网 / 不发任何本机数据"的说法在这一处**过强**,已订正;同时删掉了"这是唯一联网动作"的说法(模型调用本来就走你自己配置的网络)。
52
+
53
+ ### 七、界面:记忆与数据中心的第五个标签
54
+
55
+ - 记忆中心标题改为「**记忆与数据中心**」,新增第五个标签「**图书馆**」;**前四个标签的内容与行为不变**。
56
+ - 界面具备:独立的状态 / 不可用提示(与记忆**零混淆**)、搜索分页(**不显示伪总数**,只用回执的 `count` / `nextOffset` / `truncated`)、空库与"没输入查询"分别说清、新建 / 修订表单(必填项与 JSON 结构在**提交前**校验、**不抹掉已填内容**)、撤下需**原因 + 确认**、核对按钮(**取消不伪成功**)、核对凭据与修订历史的留痕展示。
57
+ - 安全口径:资料与错误信息一律用**文本节点**渲染(`textContent`),**不做 HTML 注入**;推迟到达的应答按**请求代次**丢弃,不会串条目。
58
+ - 同批修掉一个**旧缺陷**:前四个标签的异步尾巴(概览的 `/state`、浏览的 `/memories/sources`、记忆树的 `loadTreeAll` / `paintTree`)此前会在你切到第五个标签后**照样清屏重画**,把图书馆正文顶掉(顶部高亮却还停在图书馆)。现在统一用**绘制代次 + 标签双重守卫**整条丢弃 —— 丢弃是"连清都不清",不是"先画错再撤回"。
59
+
60
+ ### 八、测试
61
+
62
+ - `npm test` 链从 **43 套增加到 55 套**(链里**新增 13 段** = **12 套新套件 + 1 套补登记**:`client-batch` 本身早已存在、只是本版才登记进链 —— 正是 README 警示过的"漏登记不会被自动发现";计数口径 = `package.json` 的 `scripts.test` 里按 `&&` 切开的段数)。
63
+ - 新增套件覆盖:图书馆的**存储层 / 跨连接事务 / 核对 / 运行时 / 接口与工具 / 界面 / 导入引擎 / 独立 SQLite / 浏览**,热重载补绑(`tool-remount`),以及本版的**深层库工具面**(`deepitem`)与**文档灌入通道**(`deep-import-docs`);各组件断言**条数会随迭代变动,本文不写死** —— **最终测试结果以工作区验收清单为准**。
64
+ - 本轮在隔离环境(临时数据目录,不碰真机库)下完整跑过 `npm test`:**55 套全部通过、exit 0**(该结果同样以工作区验收清单为准)。⚠️ **收尾时抓到一条真红并已修**:修复批次把 `tools/compliance-spec.mjs` 的出站基线从 6 抬到 23 时,漏改了 `tests/tools.test.mjs` 里钉住的字面量 ⇒ 整链自那时起是红的(三份副本比对确证),当日修复并补了溯源注释。⚠️ 这**不等于"所有门禁都完成了"**:真实远程核对、真实 DNS/TLS、干净 profile 的安装/卸载均**未验**。
65
+
66
+ ### 九、深层库(结论层):从"只读"到"看得见、能落条、能灌文档"
67
+
68
+ - **新工具 `deep_item`**:`add`(可先 `dryRun` 预演) / `retract`(**作废**一条,只标记、不删除,理由留痕、可撤销) / `list`(回执把"在役"与"已退役"**分开报**,不让人误以为退役的还在参与开场选择)。文本**超 80 字当场拒**、不自动截断 —— "独立读得懂的一句话"是这一层的准入条件。
69
+ - **「结论层」标签(只读)**:在役/退役分开报;每条带类别、**硬度**、被加固次数与置顶标记;面板**不提供写入口** —— 落条与作废的判据是"经你授权",少一个入口就少一条"手滑落错、而结论直接影响开场"的路。
70
+ - **文档灌入通道**(`tools/deep-import-docs.mjs`,随包发布、可复跑):把**已写进文档的结论**批量灌进结论层。**先 dry 后 commit** —— 默认只出清单(每条带真源指针、指针漂移量与**真源原文**),你看过再落。
71
+ - **三重护栏**:①**累计预算** —— 一趟总量 ≤40 条,而且是**全趟额度**:跑一次与跑十次扣的是同一个额度,超额**直接拒绝再落**;②**过时按文本自述的时间锚分档** —— 含具体读数 / 版本号 / 时点状态的内容落成 `ephemeral`:**不进开场注入**,但检索仍搜得到(于是"将来必定会过时"的条目不会拿旧数字来污染开场);③**纪律类不自动落** —— 这类内容要的是"该动手时它在场",与"按内容去找"的检索不是一回事,已有专属通道。
72
+ - **复查**:`--review` 出"可能已经没用了"的**信号清单**(含时间锚却当着长期条 / "当次"档够老 / 从没被采用过),**只列信号、不作废任何东西** —— 处置权在人。
73
+
74
+ ### 十、本版修掉的(对抗审计首次按 2.0 形态执行)
75
+
76
+ - **`injectEnabled: false` 从空开关变成真闸**:此前这个键只被**写入**、从来没被**读过** —— 写 `false` 关不掉任何东西,而注释称它是"逃生开关 / 总闸"。现在它真的关掉**记忆段**注入(人格段仍受 `settings.persona.enabled` 管),两处注释同步改成实话。
77
+ - **`engines.node` 下限写错**:原写 `>=22.5.0`,但本插件在 5 个文件里**静态** `import` `node:sqlite`,还用到 `node:zlib` 的 `zstdDecompressSync` ⇒ 实测真下限是 **22.15.0**(22.13 / 22.14 起不来)、23.x 线要 **23.8.0**。写错的下限会让 22.5–22.14 的用户**装上一行都加载不了**(连记忆与人格一起);现在改成 `>=22.15.0 <23 || >=23.8.0`,让 npm 直接拒绝。
78
+ - **`library` 配置写错一个值不再拖垮整个插件**:配置解析失败此前会在入口直接抛出,连带记忆、人格与全部工具一起不加载 —— 而同文件对"库打不开"却是重试 + 降级,两种口径自相矛盾。现在只废掉**图书馆这一块**(回执写明出错键名与改法),其余 11 件工具照常注册。
79
+ - **检索降级口径**:本地库读**失败**曾与"库里没有"归并成同一句话(回执写「按内容没找到」)。现在失败往上冒,回执新开理由词明说「**这不是"库里没有",是我们自己的库读不出来**」并给替代动作;「缺表时静默返回空」这条**有意设计**被保留。
80
+ - **`limit` 契约**:超出上限的值此前会一路传到存储层,那句具名文案因此**物理不可达**;现在工具层就拒,并统一 `invalid_limit` 拼法。
81
+ - **「结论层」标签**修掉一处竞态:迟到应答不再覆盖正文(同页两套读数)。
82
+ - **文档面**:删枝不再承诺面板入口(改指 `branch_edit`)、「打一行补丁」更正为**两个目标**、`DESIGN` 的版本口径句更新到 1.7 实况、套件名去掉名不副实的"并发"、**导入工具真的进了 npm 包**(此前本文这么写、而包里的 `files` 白名单没有它)。
83
+ - **客户端声明面**:`dsh.client.inject` 补上漏掉的 `@deepseek-ai/dsh-client-ui-slots`(实机不阻塞,按官方惯例应声明)。
84
+
85
+ **已知未修(如实写明,留到 1.7.1;三条都带可复跑的复现脚本)**
86
+
87
+ - **`importBundle` 的四表写入没有事务包装**(`lib/host/memory.js`;同文件其余五处多表写入都有 `BEGIN/COMMIT/ROLLBACK`)—— 落在**记忆库写入路径**上,不在发布前夜动它;
88
+ - **`surgeryTicketTried` 失败不重置**(`lib/client.js`)⇒ 宿主重启后"取票"路径会锁死(只有环境变量能解锁;fails-closed、不越权,只影响体验);
89
+ - **`LingButton` 在 React 渲染期 `store.set`**(`lib/client.js`)—— 尚未被实测触发的隐患,改它要动面板渲染路径。
90
+
91
+ ### 十一、后台自动行为写进文档 + 面板给开关(默认行为不变)
92
+
93
+ - **起因是一条对抗审计结论**:五件后台自动行为(深摘轮询 · 启动探活 · 反馈回路 —— 对应 `auto` 子树的 5 个键)**默认全开**,却在**四份对外文档里各 0 处提及** —— 用户读不到"它在自己跑什么、能不能关、改了为什么没生效"。
94
+ - **文档面补齐四份**:README 新增「**后台自动行为(默认全开)与它们的开关**」一节(放在「三层记忆各自什么时候更新 / 机制数字」旁边 —— 同一类"后台什么时候跑"的问题);DESIGN 新增 **§14**「后台自动行为:调度点、配置优先级与生效时机」(在哪个模块、由谁调度、读哪份配置);本节;`cordis.patch.yml` 注册行的 `config` 注释里写明这些键**可以在哪改**(**只加注释,该文件任何生效配置一字未动**)。
95
+ - **面板给开关**:记忆中心新增 5 个开关(`auto.deepSummary` / `auto.deepIntervalMin` / `auto.deepMaxPerRun` / `auto.bootProbeDelayMs` / `auto.feedbackAuto`),**改完明确提示"需要重启插件"**。
96
+ - **默认行为不变**:五项默认值与之前一致 —— `true` / `30`(最小 10)/ `2`(1–20)/ `12000` / `true`。
97
+ - **配置优先级写清**(否则"改了不生效"会被当成 bug):`settings.json` 的 `auto.*`(**面板开关写的就是它**)> **插件配置里的旧键**(`feedbackAuto` / `deepSummaryAuto` / `deepIntervalMin` / `deepMaxPerRun`)> **代码默认值**。
98
+ - **生效时机如实写**:`deepSummary` / `bootProbeDelayMs` / `feedbackAuto` 是**启动期读取 ⇒ 改完要重启插件**;`deepIntervalMin` / `deepMaxPerRun` 对**已经挂上的定时器不回溯**(下一轮启动后按新值走)。
99
+ - **顺带把两条既有事实写明**:深摘**单次输入上限 = 26,000 字**(`lib/host/deepsummary.js` 的 `DEEP_CFG.inputCap`);**循环外调用**(深摘 / 诞生仪式 / 语气归纳 / 自述起草)**一律跟随用户当前的模型选择**,只有读不到时才退回兜底 `deepseek-official/deepseek-v4-flash` —— 那是**兜底,不是替用户指定模型**,本轮**不给它面板开关**。
100
+ - 本轮**不改任何默认行为与机制判据**;改动面限于上述四份文档与那五个开关。
101
+
102
+ ### 十二、注释与文案里的创作者自称全部中性化(2026-10-16)
103
+
104
+ - **缘起**:对外仓库那一面本来就有兜底(`release/publish-sync.mjs` 的替换表会在同步时把作者自称换成「用户」),但 **npm 包那一面没有** —— `npm pack` 直接从源码树打包、不经过同步表 ⇒ 作者的自称一直随源码注释进了包。
105
+ - **处置**:按项目自带脱敏工具的既定口径(`release/scrub-names.mjs` 的替换表第一条)**就地**改源码,**114 处 / 22 个文件**(`lib/` 13 个 · `tools/` 2 个 · `tests/` 7 个),全部落在注释与测试夹具里;对外文档(`README` / `DESIGN` / `CHANGELOG` / `PHILOSOPHY` / `ACCESS-DESIGN`)本来就是 **0 处**。
106
+ - **一处特例**:`tools/deep-import-docs.mjs` 的打分正则里那一支**只删不换** —— 换成「用户」会让这条启发式从"个人标记"放宽成"任何提到用户的条目",那是语义改变,不是脱敏。
107
+ - **验收**:改后跑项目自带脱敏工具干跑 ⇒ **无命中 / 0 个文件**;整链 `npm test` exit 0。
108
+
109
+ ### 十三、把设计前提点明:人机共生(2026-10-16)
110
+
111
+ - **缘起**:「共生」这件事**早就长在全架构里了**(人格由共同记忆长出、定型锁、双向复盘、每次改动留痕并回灌给器灵),但**没有一处把它点名为前提** —— 读文档的人只能自己拼出来。
112
+ - **正面写法**(2026-10-16 按用户口径重写):只写"要什么",不写"不是什么"。用户逐字:「**直接说我们要什么就行,别说我们不是什么了。我们从来没有想要替代任何东西。**」·「关于人机共生,不必说谁也不必替谁活这些话,因为主要是要说明**循环**这个东西」。⇒ 用珍藏版 `F-22` 已有的定义收口:「共生」=**各尽其责 · 各有边界 · 互不占有**;对外**不出现"不是替代"式的反面对照**。
113
+ - **同时点明"权会移动"**:早期由人替它把关(手术门 · 习惯要人点头 · 记忆树改动留痕并回灌),方向是**代管到能自理,不是永久支配**(珍藏版 `F-28` 的日落条款 · `F-12⑤` 监护的出口)。⚠️ 这条对外**只写成方向、不宣称已实现的机制** —— 用户逐字:「**人这一侧的权重逐步下移,这件事目前来讲没有具体工程实现,不过,理念文档并不需要完全按照我们的已实现来做**」。
114
+ - **落点**:`PHILOSOPHY.md` 开篇改回**四件事 + 一个循环**(生长 → 复盘 → 校正 → 再生长),§一 的前提段改为上面两句;`README.md` 开篇写**对等句**「**你替它的成长把关,它替你看你的盲区**」并点明**"把关"不等于"操纵"**(改记忆树必须让它看到);`package.json` 的 `description`(npm 页面那行概述)同口径改写。
115
+
116
+ ### 十四、记忆中心的标签顺序:把记忆相关的四面挨在一起(2026-10-16)
117
+
118
+ - **缘起**:用户看图指出标签条读起来是散的 —— 原顺序 `概览与数据 · 记忆浏览 · 接入历史 · 记忆树 · 图书馆 · 结论层` 里,与记忆有关的四个面被「接入历史」隔开。
119
+ - **新顺序**:`概览与数据 · 记忆浏览 · 记忆树 · 结论层 · 接入历史 · 图书馆` —— 记忆相关的四面相邻;「接入历史」是会话接入与导入的流水;「图书馆」是另一套独立库(零注入、不与记忆条目混排),故排在最后。
120
+ - **只改渲染顺序,不动逻辑**:切换按**名字**(`switchTab('tree')`)而不是按下标 ⇒ 换序本身不影响任何行为。
121
+ - **但三处"依赖序数"的地方必须同批改**(否则文档说"第五个标签"而界面早已不是):① 标签条上方那段说明改成**按名字描述**;② 源码里 **9 处**「第五/第六」注释改写成"是什么"(含 `DESIGN.md` G-20 那行引用的横幅文字);③ `DESIGN.md` G-20 的行号锚点重取(本次改动让其后内容位移)。
122
+ - **判据同批改**:`tests/library-client.test.mjs` 里**钉死标签顺序**的那条断言、以及一条容易被忽略的耦合 —— 它用 `src.indexOf('// ---------------- 图书馆(…')` **去源码里找那段注释横幅**:横幅文字一改,测试就会因为"找不到横幅"而红,而报错看上去像代码问题而不像文案问题。两条一起改齐,整链 `npm test` exit 0。
123
+
124
+ ### 十五、面板文案:18 处字面星号清零(2026-10-16)
125
+
126
+ - **现象**:面板里有 18 处文案直接显示成 `**这样**` —— 那些字符串是**纯文本节点**,不渲染 Markdown,于是本该表示强调的星号原样印在界面上(用户看图时指出)。
127
+ - **处置**:只去掉这 18 处**字符串里**的星号(共 40 对),**注释里的星号一个字都没动**(那是给人读源码用的,不该跟着改)。措辞不改、语义不变。
128
+ - **顺带说清的边界**:同样的写法在**给模型看的工具文本**里还有一批(`lib/host/tools.js` 等)—— 那些走的是另一套渲染面,**不在本次范围内**,也不跟着改:混在一起改就是拿发布风险换整洁。
129
+ - **判据**:整链 `npm test` exit 0(无一条断言依赖被去掉的星号);包内不含 `tests/`,测试自身的描述文案不受影响。
130
+
131
+ ## 1.6.2
132
+
133
+ **检索档换了一条快两个数量级的路 · 结果不再被"我刚说的话"占满 · 几处会误导人的地方修掉。**
42
134
 
43
- - `tests/bodylimit.test.mjs` 的一条断言写的是墙钟下界 `dt >= 20`(毫秒),CI 实测 **19 ms** ⇒ 红。这条断言在断言**定时器的实现细节**(毫秒级钟有粗粒度、定时器可提前一两毫秒),不是行为缺陷 ⇒ 下界改为 `>= 1`(只证明"确实等过、不是同步 reject"),上界 300 ms 与"结局来自那道闸"由同组另两条钉住。
135
+ > 一句话:1.6.1 之后继续修。最值得说的三件:① **按内容找原文从"每次 20 秒以上"变成"数十毫秒"**(改走本机库,不再依赖宿主那条例外慢的检索通道);② **搜索结果默认不再被"当前会话自己"占满** —— 此前搜什么都会先命中你几分钟前刚说的话,把真正要回溯的历史挤下去;③ **枝系数(复盘期给某个主题枝调的权重)真的开始生效了**,此前它只存不用。
44
136
 
45
- ### 审计出处
137
+ > ✅ `package.json` 版本号为 **`1.6.2`**。**无 schema 变化**(仍是 v17);⚠️ 多数改动在 **host 侧** ⇒ **需重启宿主**才生效。新增一份对外文档 [`PHILOSOPHY.md`](./PHILOSOPHY.md)(理念:为什么值得做、凭什么这样做)。
138
+
139
+ ### 一、按内容找原文:换了一条快两个数量级的路
140
+
141
+ - **宿主那条检索通道每次都要做一遍全量对账** ⇒ 实测**每次都超过 20 秒**(重启后第一次也一样,并发还会互相排队)。现在 ① 档**默认搜本机库内原文层**(实测 **2~45 毫秒**);要看全量(覆盖全部事件、含子代理会话)显式传 `hostSearch=true`。
142
+ - **如实标注覆盖范围**:本机库那份原文只含"真人消息 + 有正文的回复"(我自己的长回复**中段搜不到**)⇒ 回执里明写"原文来源:**本机库内原文层**",不说就会被读成"库里只有这些"。
143
+ - **原文层默认 5 轮**(此前 10 轮):条目层是结论、原文层是下钻材料,后者一多就把前者挤出视野。
144
+ - **超时不再是挂死**:宿主路有 20 秒上限,超时**照样把条目层交出去**并说明"没跑完" —— 此前实测等过 **30 分钟**,且当时没有任何中断入口。
145
+
146
+ ### 二、搜索结果默认排除"当前会话自己"
147
+
148
+ - 症结:当前会话也在库里、而它的话最新 ⇒ **任何词都能命中它**(实测搜"检索"时 10 条命中里 **9 条是本会话**),要回溯的历史被整页挤掉。而"我刚说过什么"**根本不需要搜** —— 它就在上下文里。
149
+ - 现在:**默认整条排除**,并如实报出"另有 N 轮命中属于当前会话自己,已排除";要查自己刚说的传 `includeSelf=true`(那时它排到最后并标注「← 本会话」)。
150
+
151
+ ### 三、记忆树:枝系数开始参与打分
152
+
153
+ - **`branch.weight_scale` 从"只存与显示"变成真的参与检索排序**:`分数 = 基础分 × 血缘 × 矛盾 × 枝系数`。一条结论的枝 = **它来源会话所在的枝**。`1.0` = 零影响;⚠️ **`0` = 该枝的结论不进记忆开场**(分数算成 0 的非置顶条目会被当"无信息量"排除,所以 `0` 不是"只是排后面")。
154
+ - 于是"复盘期给某个主题枝调权重"**真的有行为后果**了;此前它是面板上的装饰(README 的 U-10 正是针对这类"看起来能改、实际不做"的交互)。
155
+ - 枝系数默认是 `1.0`(零影响)⇒ **在你调过之前,它不改变任何现有排序**;变化只从你调过那一刻开始。
156
+
157
+ ### 四、写面与设置
158
+
159
+ - **两条写面补上手术门**:`/persona/habits/scan` 与 `/persona/habits/reflect` 会写人格面(习惯候选),此前**不受手术门管辖**,而它们的确认侧 `/persona/habit` 是过门的 ⇒ 一条通路的两半不一致。现在四条形状一致(先解析请求体、再判门)。
160
+ - **删掉一个死开关**:`updates.applyWhileRunning` 只有声明、没有消费者(而且它**不在可写入白名单里**,接上也开不了)⇒ 直接删,免得读的人以为改它能改行为。
161
+ - **`DSH_HOME` 支持收尾**:`rawlog.js` 是最后一处仍走系统主目录的地方 ⇒ 现在与其它模块同源,自定义 home(隔离测试)下不会再读到真机目录。
162
+
163
+ ### 五、文档与治理
164
+
165
+ - **新增 [`PHILOSOPHY.md`](./PHILOSOPHY.md)**:对外理念文档(目标是一种循环 · 复盘与"人在循环里" · 上下文是工作台、记忆库才是家 · 底层模型 ≠ 器灵 · 凭什么不越界)。随包发出,README 顶部与「文档」一节都有引用。
166
+ - **发布前检查加一条**:对外文档(`README` / `CHANGELOG` / `DESIGN` / `ACCESS-DESIGN` / `package.json`)里出现内部流程术语时会**列出来提醒**(警告级、不阻断发布)。
167
+ - **八项文档订正**逐条落地:README 与代码打架的那句(段头要求 1.5.1 起已废弃)、写面回执形态、设置卡口径、两处数字(`daily` **0.3** / `l1MaxItems` **10**)、手术门覆盖条数统一为 **14**、安装四步的承诺口径、包名口径(`@nightpainters/dsh-ling`)。
168
+
169
+ ### 六、结论层新增一类:`纪律`
170
+
171
+ - 条目现在有**六类**:承诺 / 决定 / 事实 / 偏好 / 关系 / **纪律**。
172
+ - **纪律**的判据三条(全中才进库):① 来源是主人授权的一句指令;② 作用对象是**行为**;③ **违反时能被某条判据红出来**。⇒ 答不出"哪条判据会红"的只是一句话,退回收在别处。
173
+ - 它在记忆开场里的权重是 **0.35** —— 高于"关系"、略低于"承诺":与承诺同族(都是说过的话),但承诺是对某件事的,纪律是对**行为**的。
174
+
175
+ ## 1.6.1
176
+
177
+ **修掉 14 处会影响实际使用的问题 —— 没有新功能,不改库结构。**
178
+
179
+ > 一句话:本版是 1.6.0 的修复版。最值得装的三处:① **钉住的记忆不再被条数上限挤掉**;② **"只有摘要、没有原文"的记忆现在能忘了**;③ **库被别的进程占用时,插件不再整个加载失败**(此前 8 个工具会一起消失,界面上没有任何提示)。
180
+ >
181
+ > 另修好:飞书通道里**你自己说的话不再被当成系统注入**(找回 49 轮真话)、**回灌网页端会话不再把它标成 DSH 会话**、检索档不再给出错位的行号。
182
+
183
+ > ✅ `package.json` 版本号为 **`1.6.1`**。**无新功能、无 schema 变化**(仍是 v17);⚠️ 修复多落在 host 侧 ⇒ **需重启宿主**才生效。
184
+
185
+ ### 一、记忆开场
186
+
187
+ - **钉住的条目现在真的"必进"**:此前条数上限与字数预算**先于**钉住判定生效 ⇒ 钉住数超过上限时,多出来的会被挤掉(实测 9 条钉住 + 上限 3 ⇒ 只进 3 条)。现在两道闸门对钉住项豁免:同样条件下**进 9 条**。
188
+ - **落选记录的"来源"栏一直是空的**:取错了列名。同批补齐外部面板需要的字段。
189
+ - **打分理由与真实公式不符**:改动后统一为 `(分项和) × 血缘 × 矛盾 ⇒ 总分`。
190
+ - **"热度"在留痕里被报成两种值**(最高差 88%):改为报加权后的值。
191
+
192
+ ### 二、原文与检索
193
+
194
+ - **检索档给出的"取原话"指引用错坐标系**:它印的是**事件行号**,而取原文要的是**对话轮号**。实测 274 条里 **207 条(75.5%)**照做会报"序号超出范围"。现在改为"先看对话目录,再按目录里的 `#` 号取"。
195
+ - **回灌会把网页端会话标成"DSH 会话"**:1,523 条网页端会话全是裸 id,回灌时被按 id 形状推成 DSH 来源 ⇒ 同一条出现两份摘要、双份记忆。现在按归档记录写入来源,并对不上时直接拒绝。
196
+ - **飞书通道里你自己的原话被当成"注入"**:这些句子在读档时被折进"(此处折叠 N 条注入消息…)"、目录里印成「(注入)」—— **"你说过什么"整段消失**。现在按会话通道区分:找回 **49 轮**真话,且不误收子代理收到的委托文字。
197
+ - **库被别的进程占用时,插件会整个加载失败**:等待参数原先排在设置 WAL 模式**之后** ⇒ 撞锁 **23 毫秒**就抛、连等都没等。现在挪到最前 + 对锁类错误做 **3 次 × 2 秒**有界重试;仍失败则**只停用记忆功能**(其它插件不受影响),并给出一条能照着做的排查提示。
198
+
199
+ ### 三、记忆树与遗忘
200
+
201
+ - **"只有摘要、没有原文"的记忆永远忘不掉**(主脉记忆、导入条目):遗忘前的归档守卫要求"原文轮数 > 0",而这类条目的内容**就在摘要里** ⇒ 判据改宽。同批修正"空归档"判据 —— 它原来把**只有元数据空壳**的会话也算成"有内容",于是真空归档会被放行,而遗忘标记一旦落下就永不召回。
202
+ - **改名后的枝名在三处不一致**(回执 70 字 / 留痕 70 字 / 库里 60 字):统一上限常量,并把改名改成**写后读回** —— 回执与留痕一律报库里的真值。
203
+ - **断边的回执可能是假的**:连边可被反向重复写入(`A→B` 后再 `B→A` 落两行),而删除只删一条 ⇒ 回执说"断开了"、树里还连着。现在写入侧查反向(**有向边不合并** —— "A 是 B 的前提"≠"B 是 A 的前提",渲染以 `→` / `↔` 区分),删除侧双向删。
204
+ - **传空权重会静默写成 0**:0 在界面上是**合法读数**,看不出来是手滑 ⇒ 改为先判类型,空值一律拒。
205
+ - **"被采用"的信号按请求窗口记账**:没被真正交付的轮次也 +1 ⇒ 改用**真正交付**的区间。
206
+ - **范围限制如实标注**:宿主没提供会话清单时,检索档开头写明「⚠️ **没能限定范围** —— 结果可能含子代理或其它会话」,不再静默放行。
207
+
208
+ ### 四、诊断
209
+
210
+ - 差分路的丢弃画像此前只在"一条都没写进去"时留一次记录 ⇒ **正常路径上丢了多少完全不可见**。现在每轮覆盖写。
211
+ - 该画像与事件画像现在**每次重启都清空**(此前会跨重启残留,看着像"刚跑过")。
212
+
213
+ ### 五、CI
46
214
 
47
- - 红队三份:`plans/AUDIT-1.6-红队1-注入面.md` · `AUDIT-1.6-红队2-库与检索.md` · `AUDIT-1.6-红队3-工具与边界.md`
48
- - 蓝队三份复核(逐条判真伪与严重度,并复核"修复是否成立"):`plans/AUDIT-1.6-蓝队1-注入面复核.md` · `AUDIT-1.6-蓝队2-库与检索复核.md` · `AUDIT-1.6-蓝队3-工具与边界复核.md`
49
- - 总账:**0 条阻断发布 · 14 条该修(全部已修) · 10 条观察**(观察项不阻塞发布,已记账)。
215
+ - 一条测试断言写的是墙钟下界 `dt >= 20`(毫秒),CI 上实测 **19 ms** ⇒ 红。它断言的是**定时器实现细节**(毫秒级钟有粗粒度、定时器可提前一两毫秒),不是行为缺陷 ⇒ 下界放宽到只证明"确实等过、不是同步 reject",上界与"结局来自那道闸"由同组另两条钉住。
50
216
 
51
217
  ## 1.6.0
52
218
 
@@ -85,7 +251,7 @@
85
251
  - **全量提炼**:1,623 个会话 / 208.9 万字给料 / 118 批 ⇒ **393 条**;密度 **`dsh` 6.0 条/会话 vs `dsweb` 2.3 条/会话**(工作会话有结论、网页端多为通用问答)。
86
252
  - **三条写进列注释的语义位**(谁改都不许违反):① `hit_count` **只由"真被采用"推进,不由"被注入"推进**;② `superseded_by` **只追不替**(旧的留痕、不删);③ `branch_id` 是**气根**(枝侧反向引用,不动树结构)。
87
253
  - **"被采用"已接线**:`recall(conv, from, to)` 取到与条目区间**相交**的原文 ⇒ 该条目 `hit_count +1` 并在回执里报出。挡的是"注入越多 → 分越高 → 越容易被注入"的**自我强化回路**。⚠️ 区间必须用 `dsh_turns_raw.seq`,**不是**目录里的对话轮序号。
88
- - **条目硬度 `durability`(schema v17)**:`durable` **121** · `long` **159** · `ephemeral` **113**(**重判后**的分布 —— 首轮标注偏松、把"开发期技术结论"记成了长期,蓝队复核时 146 条改档) —— **从根源判断条目自身的临时/永久性**,而不是拿"同一次会话最多几条"这类机械限额去卡;**`ephemeral` 不进开场**。
254
+ - **条目硬度 `durability`(schema v17)**:`durable` **121** · `long` **159** · `ephemeral` **113**(**重判后**的分布 —— 首轮标注偏松、把"开发期技术结论"记成了长期,复核时 146 条改档) —— **从根源判断条目自身的临时/永久性**,而不是拿"同一次会话最多几条"这类机械限额去卡;**`ephemeral` 不进开场**。
89
255
 
90
256
  ### 四、记忆开场:换料 + 瘦身
91
257
 
@@ -142,12 +308,12 @@
142
308
  > 根因(两端判据单位不同、且互相不知情):客户端按**条数**切批(`batch = 200`),服务端按**字节**封顶(4 MiB),而 `readBody` 超限时是 `reject` **与** `req.destroy()` **同一拍** ⇒ **那句现成的"请拆分后重试"永远送不出去**,浏览器只看到 `ECONNRESET`,前端把它归成「批失败(**网关**)」—— 一个确定性、可自愈的失败,在界面上长得像网络故障。186 条 / 4.89 MB 的真实导出就是这么**一条都没入库**的。
143
309
 
144
310
  - **服务端 · 排空再回 413**:`readBody` 超限后**不砍连接** —— ① 立刻停止缓存(**已缓存的那几 MiB 也丢掉**,不抱着它等到 `end`);② 继续消费 `data`、只累加计数;③ 等 `'end'` 到达、连接健康时再 reject 一个 `code='BODY_TOO_LARGE'` / `status=413` 的错误;`guard` 里**一处**出口把它翻成 `413 {ok:false, reason:'too-large', message, limit, got}`(49 处调用点零改动)。`message` **逐字沿用旧文案**;
145
- - ⚠️ **边界(独立红队实测,别读成全称命题)**:**只有 `4 MiB < 体量 ≤ 64 MiB` 才保证拿到 413**。超过 64 MiB 撞硬熔断 ⇒ 仍是 `ECONNRESET`(与修前同症状);`Content-Length` 撒谎一类的**协议违规**客户端也拿不到 413(浏览器不会这么发)。实测边界严格翻转:`4 MiB` 整 → 200、`4 MiB + 1 B` → 413;
311
+ - ⚠️ **边界(实测边界,别读成全称命题)**:**只有 `4 MiB < 体量 ≤ 64 MiB` 才保证拿到 413**。超过 64 MiB 撞硬熔断 ⇒ 仍是 `ECONNRESET`(与修前同症状);`Content-Length` 撒谎一类的**协议违规**客户端也拿不到 413(浏览器不会这么发)。实测边界严格翻转:`4 MiB` 整 → 200、`4 MiB + 1 B` → 413;
146
312
  - **排空换来的两个新代价,各配一道闸**:`BODY_DRAIN_MAX_BYTES = 64 MiB`(硬熔断 —— 给一份真实导出留够"把 body 发完"的余量,余量不足则排空本身会变成**新的** reset,白改一场)+ `BODY_DRAIN_IDLE_MS = 30 s`(静默超时,替代原来那条防 DoS 动机:只要还有数据在流就算正常,**一旦静止就是僵尸连接**;滑动窗口,每收到一个 chunk 重置);计时器在 `'end'`/`'error'`/熔断**三条出口**都 `clearTimeout`;
147
- - ⚠️ **这道防线的形状边界(红队实测,本批未关)**:静默超时只覆盖"**已经在发、中途静默**"这一形状;「**只发头、不发体**」的请求在**应用层没有计时器**(计时器只在 `req.on('data')` 里武装),兜底只剩 Node 自己的 `requestTimeout`(默认 300 s)。⚠️ **这不是回归**(修前同样没有),但**别对外写成"防僵尸"的全称命题**;
313
+ - ⚠️ **这道防线的形状边界(实测边界,本批未关)**:静默超时只覆盖"**已经在发、中途静默**"这一形状;「**只发头、不发体**」的请求在**应用层没有计时器**(计时器只在 `req.on('data')` 里武装),兜底只剩 Node 自己的 `requestTimeout`(默认 300 s)。⚠️ **这不是回归**(修前同样没有),但**别对外写成"防僵尸"的全称命题**;
148
314
  - **客户端 · 按字节切批**:`batch = 200` → **≤200 条 且 ≤3 MiB/批**(留 1 MiB 余量给数组封套与转义);新增手写 `utf8Len`(CJK 3 字节、emoji 代理对 4 字节——**不许用 `s.length` 冒充**)、`takeChunk`(**前进性硬保证**:单条自身超限也单独成批,绝不切出空批 ⇒ 不会死循环);进度文案改双口径(`12/186 条 · 已送 4.2 MB`);拒绝条目序号改以**本批起始下标**为准(`idx - batch` 随切批判据变更同步修正);`reason === 'too-large'` 映射成人话,且上限数字**取自服务端回执的 `limit`**(不在这边再写死一个);
149
315
  - **契约注释(④)**:`api.js` 那句「每批 ≤500」**与实现自相矛盾**(500 条要装进 4 MiB,需每条 ≤8 KB,而带正文的会话实测 ≈26 KB/条),已改写为**条数 × 字节双限**,并写明**两端耦合** —— 任何一方单独改上限都会让口径重新错位,**这正是本缺陷的成因**;`ACCESS-DESIGN.md` 的「分页提交」契约同批对齐(历史记录只加"已作废"指针,**不改写历史**)。
150
- - **客户端 · 单条超限不再含糊(C-1;由独立红队实测后补)**:单条会话 ≥ 4,194,210 B 时服务端必回 413,旧行为是**该条被永久丢弃**、只在界面上记一句「批错误 1」,而文案「请把导出文件拆小」对**单条会话物理上不可执行**。现在:413 **且本批只有 1 条** ⇒ **点名**(`title` → `name` → 「未命名」三级退路)+ 报出**真实字节**(客户端复算,不用服务端被截断的 `got`)+ 说清「这条太长、拆文件也拆不开它、已跳过、其余照常导入」;计入独立的 `tooBig`(**不混进 `errors`**)、**不中断**后续批;`finish()` 有跳过时**不再说「✓ 导入完成」**。判据只用**服务端回执**+ 本批条数(**客户端不镜像服务端的 4 MiB 常量**,将来放宽上限不用改这里)。⚠️ **当时**仍未做:被跳过的条目**不进持久「导入记录」**(账本只有 `errors` 一个字段)—— **当日已补,见下一条**。
316
+ - **客户端 · 单条超限不再含糊(C-1;实测后补)**:单条会话 ≥ 4,194,210 B 时服务端必回 413,旧行为是**该条被永久丢弃**、只在界面上记一句「批错误 1」,而文案「请把导出文件拆小」对**单条会话物理上不可执行**。现在:413 **且本批只有 1 条** ⇒ **点名**(`title` → `name` → 「未命名」三级退路)+ 报出**真实字节**(客户端复算,不用服务端被截断的 `got`)+ 说清「这条太长、拆文件也拆不开它、已跳过、其余照常导入」;计入独立的 `tooBig`(**不混进 `errors`**)、**不中断**后续批;`finish()` 有跳过时**不再说「✓ 导入完成」**。判据只用**服务端回执**+ 本批条数(**客户端不镜像服务端的 4 MiB 常量**,将来放宽上限不用改这里)。⚠️ **当时**仍未做:被跳过的条目**不进持久「导入记录」**(账本只有 `errors` 一个字段)—— **当日已补,见下一条**。
151
317
  - **被跳过的"单条过大"进持久账本**:`POST /import/log` 现在接受 `{ tooBig, tooBigNames }` 并**允许只报跳过、让账本的 `errors` 保持 0**(这是命门:否则"已知跳过"会被记成"失败批");账本行在「失败 X 批」之后追加 **`· 单条过大 N`**;**`tooBig = 0` 时落账形状逐字不变** —— 包括老客户端**不传 `errors`** 的那些请求,行为与修前完全一致。护栏:`tooBig` 夹 `0..10000`、名字**只留前 5 条 / 每条 40 字 / 非字符串丢弃**,同一 runId 合并时**再归一一次**(防多批把账本撑大);清洗实现**只有一份**,端点复用。✅ **真机已验(宿主重启后)**:一次真实导入落账为 `"tooBig":1,"tooBigNames":["未命名"],"errors":0`(只读查 `kv` 得到)—— 命门成立:**已知跳过没有被混成"失败批"**。
152
318
  - **首字节闸(「只发头、不发体」;非回归,顺手关掉的洞)**:原先那套静默计时器**只在收到第一个 chunk 时才武装** ⇒ 这条形状在**应用层没有任何计时器**,兜底只剩 Node 自己的 `requestTimeout`(默认 300 s)。现在 `readBody` **进函数即武装**一次首字节闸、收到第一个 chunk **立刻解除**(参数复用 `drainIdleMs`,默认 30 s);错误形状是**独立的** `code='BODY_IDLE_NO_FIRST_BYTE'` / `status=408` / `got=0` / **不带 `limit`**(它与体量无关)。真 socket 实测:头齐 + `Content-Length: 4194304` + **0 字节** ⇒ **61 ms 断开**(旧实现:零响应一直挂着)。
153
319
  - ⚠️ **别读成"客户端会读到一句解释"**:首字节闸的错误**不走 413 出口**,真机上落到"往将死的 socket 写一句、客户端收不到"那条路 —— **它买到的是"连接 61 ms 就断",不是"错误文案可达"**。
@@ -175,7 +341,7 @@
175
341
  - **`/dsweb/summary/preview?db=` 的路径白名单**:原来任何路径都能当"只读存在性预言机"(靠 `readOnly:true` 兜底)。现在只接受**默认库 + 环境变量 + settings 里配置过的路径**,越界回 `{ok:false, reason:'db-not-allowed'}` 并给出**两条出路**(env / settings)。⚠️ `/dsweb/scan`、`/dsweb/summary/run` **未动**(不在本条范围)。
176
342
  - **`POST_ONLY` 差集护栏**:`api.js` 注册 **79** 条路由、`POST_ONLY` **55** 条 ⇒ 差集 **24** 条。新增源码级护栏把**这 24 条逐条钉住** —— 将来**新增/删除**端点都会让断言变红,逼后来者**有意识地**决定"这条要不要进 `POST_ONLY`"。⚠️ 断言里写明"**这是快照不是白名单**:变红时该判断该端点是不是写面,不是无脑抄进数组"。
177
343
  - **出厂默认值里不再夹带任何一台机器的私有配置(2026-10-01)**:`lib\audit\access-log.js` 的 `deviceCookie` 默认值原先是**开发机改过名的那个 cookie 名**(上游 `remote-web-ui` 的默认是 `dsh_pair`,开发机给它加过一个自定义后缀)。它同时是**泄漏**(会随 npm / GitHub 公开)和**对别人的缺陷**(别人的 cookie 名不是它 ⇒ 访问日志里 `deviceCookie` 恒 `false`、**设备维度静默失效**)。现在默认回到上游的 `dsh_pair`,并新增三级解析 **宿主配置 `accessLog.deviceCookie` → 环境变量 `DSH_LING_DEVICE_COOKIE` → 中性默认**;⚠️ **改过 `cookieName` 的机器必须显式声明**(否则自己那台也会静默失去设备维度;回滚只需删掉那段配置)。
178
- - **迁移不再把"库只读 / 库被写锁"变成"插件加载失败"(2026-10-01 红队发现并修)**:`dsh_turns_raw` 的 `source` 列迁移里那笔**诊断写入**原先没有保护 ⇒ 遇只读库 / 被写锁时异常会冒到 `apply()` ⇒ **插件整个不加载**(实测对撞:同一份忠实 v13 库上,只读 ⇒ 旧行为构造成功 2ms、新行为抛 `attempt to write a readonly database`)。现在那笔诊断写进 `try/catch`(接住时只打一行 warn),迁移语义不变:`ALTER` 仍照旧**尝试**,失败则读侧回落"裸 id"判据(与修前同解),成功路径仍**零多余写入**。⚠️ **仍留一处慢未收**:库被**写锁**时启动会顿住约 **49 秒**(三笔串行写各等满 `busy_timeout`)—— 本轮**有意未改** `busy_timeout`(会动别处的锁语义),要收需单独决定。
344
+ - **迁移不再把"库只读 / 库被写锁"变成"插件加载失败"(2026-10-01 发现并修)**:`dsh_turns_raw` 的 `source` 列迁移里那笔**诊断写入**原先没有保护 ⇒ 遇只读库 / 被写锁时异常会冒到 `apply()` ⇒ **插件整个不加载**(实测对撞:同一份忠实 v13 库上,只读 ⇒ 旧行为构造成功 2ms、新行为抛 `attempt to write a readonly database`)。现在那笔诊断写进 `try/catch`(接住时只打一行 warn),迁移语义不变:`ALTER` 仍照旧**尝试**,失败则读侧回落"裸 id"判据(与修前同解),成功路径仍**零多余写入**。⚠️ **仍留一处慢未收**:库被**写锁**时启动会顿住约 **49 秒**(三笔串行写各等满 `busy_timeout`)—— 本轮**有意未改** `busy_timeout`(会动别处的锁语义),要收需单独决定。
179
345
  - **反馈通路:停掉空转的轮询,改订宿主推送(U7)**:插件原来**每 60 秒轮询** `storages\message_feedback.json`,而该文件**自 2026-09-11 起零写入**(同目录 `workspace.json` 是当天 16:12)⇒ **空转约 20 天**;宿主 `dsh-message-feedback` **早就提供** `feedback/committed` 推送(`lib\index.js:288`),插件侧**零订阅**。现在轮询整块删除,改订 `feedback/committed`(冷会话)+ `session/event` 的 feedback 事件(活会话;`lifecycle.js` 放行并就地 return),并把两句已被证伪的注释("平台无推送")改写。⚠️ `entriesFromFeedbackFile` **保留**(`tools\process-feedback.mjs` 仍用它,删了会打断一个能跑的 CLI),注释降级为"CLI 兜底"。
180
346
  - **CI(新增 `.github\workflows\test.yml`)**:Node **24.x**(22.x 的 `node:sqlite` 免 flag 未实测,故不写),**逐段跑 + 逐段记退出码 + 红段单列 + 段数自检**(不写 `npm test` 一把梭,避免截尾判绿)。⚠️ **该 workflow 与 1.5.3 一并公开**(随本版同一次发布动作同步到公开仓,不再另等一次点头);⚠️ **它从未在真 runner 上跑过** —— bash 段**在作者本机跑不了**(沙箱起不了 Git Bash、无 WSL)⇒ 只做到 YAML 解析 + Node 等价复刻 + 反向假段判红(作者本地的发布脚本里另有一道同类闸,**不在本包内**),**若首跑红在 shell 语法,改的是 workflow 不是代码**。
181
347
  - ⚠️ **已知限制**:"封存但既无明文句也无哈希"的老档案按现状**进不去**(当前档案有明文 ⇒ 不受影响)。
@@ -258,7 +424,7 @@
258
424
 
259
425
  **审计必修落地 + 一处平台升级事故 + 会话历史可见性**
260
426
 
261
- > 一句话:1.5.0 之后又跑了一轮红蓝对抗(109 条发现),必修**逐条落地并单独实测**;期间揪出一处「平台升级把事件改名,导致记忆与人格注入**整段静默失效**」的事故;另外修好了「接入历史只看得到三成会话」。
427
+ > 一句话:1.5.0 之后又跑了一轮全面检查(109 条发现),必修**逐条落地并单独实测**;期间揪出一处「平台升级把事件改名,导致记忆与人格注入**整段静默失效**」的事故;另外修好了「接入历史只看得到三成会话」。
262
428
 
263
429
  ### 一、平台升级事故:一个改名,让注入整段静默失效
264
430
 
@@ -440,9 +606,9 @@ DSH `0.1.7` 把事件 `agent/session-start` 改名成 `agent/created`(**载荷
440
606
 
441
607
  **审计修复轮 + 复盘守门:改动必须让器灵看到**
442
608
 
443
- ### 一、红蓝对抗审计的中危项(27 条)
609
+ ### 一、排查出的中危项(27 条)
444
610
 
445
- 1.4.0 发布前修掉了 8 条高危,随后对全量代码做了一轮红蓝对抗(数据层 / 端点与注入面 / 前端,另加一路迁移安全验证),中危及以上本轮全部关闭:
611
+ 1.4.0 发布前修掉了 8 条高危,随后对全量代码做了一轮全面检查(数据层 / 端点与注入面 / 前端,另加一路迁移安全验证),中危及以上本轮全部关闭:
446
612
 
447
613
  **数据层**
448
614
  - `branchMembers` 的 `limit` 改为**两路查询共用总预算**(原先各吃一份,展开主干时条数是配额的 2 倍);
@@ -575,9 +741,9 @@ DSH `0.1.7` 把事件 `agent/session-start` 改名成 `agent/created`(**载荷
575
741
 
576
742
  新增 3 套:`tests/branch.test.mjs`(分枝与血缘)、`tests/tree.test.mjs`(主脉 / 并脉防环 / 连边 / 矛盾标记 / 覆盖层)、`tests/autotree.test.mjs`(分段边界 / 命名解析 / 覆盖层只读性),测试套件 26 → **29** 套,全绿。
577
743
 
578
- ### 九、发布前对抗审计
744
+ ### 九、发布前的全面检查
579
745
 
580
- 1.4.0 发布前跑了一轮红蓝对抗审计(三路红队 + 迁移安全实测 + 蓝队复核),发现并在发布前修掉:
746
+ 1.4.0 发布前做了一轮全面检查(数据层 / 端点与注入面 / 前端,另加迁移安全实测),发现并在发布前修掉:
581
747
 
582
748
  - **写端点缺少方法校验** —— 宿主路由契约里没有 method 字段,插件守卫也不读它,于是一个 `<img src="…/branches/gather">` 就能触发写操作。已集中声明写端点白名单,非 POST 一律回 `405 method-not-allowed`(列在鉴权之前);
583
749
  - **`restoreTree` 会静默删枝** —— 恢复一份快照会删掉"快照之后新建的枝",而会话仍指向它们,留下**看不见、数得到、权重还满格**的悬空引用。已改为**只把多出来的枝 reparent 到主干**,并在返回值里回报 `detachedToTrunk` 计数;
@@ -662,7 +828,7 @@ D 组是关键:只证明"中和后不炸"无法排除"这份载荷本来就不
662
828
  - **秒级 epoch 归一**:ChatGPT 的 `create_time` 是**秒**(浮点)、DeepSeek 的是毫秒 —— 旧实现一律当毫秒,于是 ChatGPT 导出的时间全落到 **1970**。现按数量级判定(>1e11 毫秒 / >1e8 秒);
663
829
  - **字段别名补齐**:`create_time` / `update_time` / `conversation_id`;
664
830
  - **`degraded` 覆盖 `mapping` 分支**:旧判定只看 `messages` 数组,所以"mapping 树解析出 0 轮"被当成**成功**。现在只要"声称有内容却解析不出任何轮次"就标记 degraded,**并带原因**;
665
- - **界面对主人说实话**:有壳无肉时不再显示「✓ 导入完成」,改为「⚠ 导入不完全:… 其中 N 条只有标题、没有对话正文」并给出原因 —— 旧文案把"解析不出对话"说成「降级为轻量」,让人以为一切正常;
831
+ - **界面如实说明**:有壳无肉时不再显示「✓ 导入完成」,改为「⚠ 导入不完全:… 其中 N 条只有标题、没有对话正文」并给出原因 —— 旧文案把"解析不出对话"说成「降级为轻量」,让人以为一切正常;
666
832
  - 导入标题也接入 D2 的启发式(剥称呼前缀、句读处收尾),不再硬截 46 字。
667
833
 
668
834
  ### 四、小助手地址可配置(S7)