三天内 DSH 连续三次启动闪退,表象各异(缺包 → API 导出缺失 → 同名服务重复注册),是三层不同根因叠加:一次依赖树损坏、一次 dsh 生态 API 版本漂移、一次第三方全家桶插件与官方内置插件的服务冲突。每次都以「报错原文 → 本机实证 → 最小修复」闭环解决;最后一次冲突与 插件冲突管家(P2M) 要治理的领域直接相关,暴露了 P2M 可落地的改进方向——该方向已逐条实施为 E1–E6(见文末落地记录)。
node_modules/.pnpm 被清空,顶层 @deepseek-ai/ 变空壳;lockfile 同步丢失 dsh-settings 实体解析。pnpm install 两次「成功」但崩点不变 → 揭穿 pnpm 状态文件信任快路径。dsh-settings API 版本漂移(settingsNamespace 导出被新版移除)。dsh-settings@0.1.1-rc.2 后,@morlay/session-rdb 终于能加载 → 与官方 JSONL 后端抢注同名服务 sessionPersistence → Cordis 拒绝。证据:.pnpm 仅剩 lock.yaml(mtime 09-05 16:20);顶层 @deepseek-ai/ 空壳;而 pnpm-lock.yaml、package.json、全局 store(758MB)均完好。
为什么越修越糟:pnpm 以 .modules.yaml + lockfile 判断「是否 up to date」,不 stat 磁盘实体 → 普通 pnpm install 永远走快路径跳过,实体永不补齐。
ren node_modules node_modules.bak-…(改名备份而非删除)→ pnpm install 强制重建,231 包从 store 硬链,7.4s 完成。崩溃点:@morlay/session-rdb@0.0.11 按旧 API 写:import { settingsNamespace } from "@deepseek-ai/dsh-settings"。
^0.1.1-rc.2 —— 该版本确有 settingsNamespace(下载 tarball 实证);dsh-settings@0.1.2-alpha.3 —— 该版本已移除该导出(改为 SettingsProvider 等);dsh-settings 的 resolution 条目弄丢(只剩 peer 声明)→ 重装永不安装它。pnpm add @deepseek-ai/dsh-settings@0.1.1-rc.2 → 本地实体 + lockfile resolution 齐备 → Node 解析改命中本地兼容版。经验:报「does not provide an export」≠ 缺包,是解析目标版本错;用 createRequire(...).resolve() 实证解析路径。session-rdb@0.0.13 已适配新 API(peer 改 ^0.1.2-rc.1)——pre-1.0 生态 API 破坏性变更频繁,是此坑的温床。报错:service "sessionPersistence" has been registered at <JsonlSessionPersistence>
| 冲突方 | 身份 | 提供者 | 注册服务 |
|---|---|---|---|
官方 session-persistence-jsonl | dsh-base 内置默认后端 | JsonlSessionPersistence | sessionPersistence(同名 → Cordis 拒绝重复注册) |
web-ui-session-rdb | web-all 全家桶经 @morlay/better-session 引入的 RDB/SQLite 后端 | SessionPersistenceRdb |
关键证据(定位到根因的决定性一步):
cordis.patch.yml,AUTO-GENERATED)末尾自带注释与默认值:# inactive by default: the rows above ship disabled, so the stock persistence
# backend keeps serving sessions until you opt in.
- id: web-ui-session-branch
disabled: true
- id: web-ui-session-rdb
disabled: true
- id: web-ui-conversation-message-actions
disabled: true
morlay RDB 后端出厂即禁用——官方 JSONL 继续服务会话,要换 RDB 需显式 opt-in;cordis.patch.yml 为了开启全家桶 UI,把所有 web-ui-* 整批设为 disabled: false,把这三个休眠条目也唤醒了 → 双后端抢注同名服务。cordis.patch.yml.bak-20260906-113229):把 web-ui-session-branch / web-ui-session-rdb / web-ui-conversation-message-actions 三行改回 disabled: true,其余 20 项全家桶 UI 一概不动。false 并追加 - id: session-persistence-jsonl / disabled: true 停用官方 JSONL 后端,而非两个都开。| 幕 | 表象 | 误导方向 | 破局手段 |
|---|---|---|---|
| ① | 缺包报错 | 「再 pnpm install 一次」 | 读 .modules.yaml,识破状态文件信任快路径 |
| ② | export 缺失 | 「包没装全」 | createRequire.resolve() 实证解析目标 = 全局 CLI 旧版 |
| ③ | 服务重复注册 | 「依赖又坏了」 | 读三方 patch 层,发现「出厂禁用被用户层覆盖」 |
本次事件中两幕(② 版本漂移、③ 同名服务)正是 P2M 声称治理的「插件冲突」范畴,但现有检测面覆盖不到。以下改进点已逐条实施、配套单测与真实冒烟验证,并按「GitHub main + 市场留痕」双端同步,见文末实施记录表。
静态扫描每个 entry 目标包入口的 Cordis 服务注册面(super(ctx, "…") / provide("…"),连带一跳依赖包),检测「不同 entry 提供同名 service」并提前报告——本次 sessionPersistence 双注册在启动前即可被识别;命中官方 known-core 服务(sessionPersistence)时给出「二选一」修复建议。7 个单测全绿;真实冒烟:对 web-ui-session-rdb 扫出注册 sessionPersistence 撞 core 默认后端。
boot 与每次对账记录各 entry 的 require.resolve 实际解析路径与版本(含是否命中 profile 本地);一旦出现「本地缺失 → 爬升到全局 CLI」这类解析漂移,写 resolve-drift incident 并给出钉版本建议,快照持久化到 state.json#lastResolve。5 个单测全绿;真实 profile 4 个 spec 全部 local=true 且版本正确。
启动器拉起 DSH 前调用静态体检:核对每个 entry 声明的 peerDependencies 区间与实际解析版本(内置零依赖 semver 子集,prerelease 按同 [major.minor.patch] 元组规则判定),版本越界即报告/给出钉版本命令,DSH_P2M_PREFLIGHT=block 可拒启;同时暴露 ctx.p2m.preflight()。7 个单测全绿;真实 profile 首跑即抓出 2 条现存漂移(dsh-settings 的 peer brand/invariants 爬到全局 0.1.2-alpha.3)。
boot 自动发现 profile 与各 bundle 的 cordis.patch.yml 分层扫描:检测到「出厂 disabled:true 被用户层 disabled:false 覆盖」时直接输出「二选一」建议(本次就是「全开清单覆盖了出厂禁用」);双 overlay 分歧同样告警;C4 收紧为「同形态重复」,insert+overlay 合法习语(web-all)不再误报。5 个单测全绿;真实扫描 26 → 23 命中且无 error。
C7 类入冲突矩阵(§6.1)与 README 冲突速查表(补「同名服务 / 版本漂移」两行);配置表补 knownCoreServices / c7ScanIntervalMs / preflightOnBoot / scanBootLayers;API 表补 preflight();AGENTS.md 沉淀本次「pnpm 状态文件信任」「export 缺失 ≠ 缺包」「service registered = 出厂禁用被覆盖」「cordis 注册字面量两类」四条排障心法。全仓单测 74 个全绿。
web-all 聚合脚本把 better-session 的 insert 行与「出厂禁用」行拆开生成、导致 opt-in 语义割裂;dsh-settings 破坏性改名无迁移提示。已以 issue / discussion 形式反馈:zhu1090093659/dsh-web#1394(经本机 package.json 元数据锁定上游实为 zhu1090093659/dsh-web,非 @linxin666 个人仓库)、deepseek-ai/deepseek-harness Discussion #5771(该仓库关闭 Issues,改走 Discussion)。
| 条目 | 版本 / 提交 | 核心改动文件 | 验证 | 同步方式 |
|---|---|---|---|---|
| E1 · C7 同名服务预检 | v0.1.3 · 9aa2273 | conflicts.js / policy.js / index.js + test/c7-service-clash.test.mjs(7 例) | 真实扫出 web-ui-session-rdb 注册 sessionPersistence 撞 core | GitHub:push main;市场:2BingLing/dsh-market#127 评论留痕(catalog 每日 06:00 自动刷新) |
| E2 · resolve 快照 | v0.1.4 · dd90b28 | state.js / index.js + test/e2-resolve-snapshot.test.mjs(5 例) | 真实 profile 4 spec 全 local | |
| E3 · preflight 体检 | v0.1.5 · 8faf4bf | 新增 preflight.js / index.js / launcher/dsh-safe.mjs + test/e3-preflight.test.mjs(7 例) | 真实首跑抓出 2 条现存漂移 | |
| E4 · profile 覆盖感知 | v0.1.6 · e9420c3 | conflicts.js / index.js + test/e4-profile-override.test.mjs(5 例) | 真实 patch 层 26 → 23 命中、无 error | |
| E5 · 文档同步 | 309d3f0 | DESIGN.md / README.md / README.en.md / AGENTS.md | 全仓 74 单测全绿 | |
| E6 · 上游反馈 | 本页终稿 | docs/incident-2026-09-06.html | issue / discussion 均已建 | dsh-web#1394 + deepseek-harness Discussion #5771 |
| 对象 | 路径 | 状态 |
|---|---|---|
| 修复后的 profile 补丁 | ~/.dsh/profiles/web/cordis.patch.yml | 三件套已置 disabled:true |
| 改动前备份 | ~/.dsh/profiles/web/cordis.patch.yml.bak-20260906-113229 | 可回滚 |
| 依赖损坏备份 | ~/.dsh/profiles/web/node_modules.bak-20260906-1108/ | 重建后可删(删除需确认) |
| 钉回的兼容版本 | dsh-settings@0.1.1-rc.2(已入 profile deps + lockfile) | 勿用 pnpm install 冲掉 |
| P2M 状态目录 | ~/.dsh/p2m/(guard / usage / incidents / state) | 正常运行 |
复盘依据:本机文件系统实证 + registry tarball 导出面对比 + 三方 cordis.patch.yml 原文;所有结论均有命令级证据,非推测。