dsh-compat-guard
兼容性治理插件包:升级前置闸门 + 存储格式指纹 + 自动备份 + 会话迁移 + profile 级锁文件 + 插件×DSH 兼容矩阵。一个 npm 包,六个能力,全部零运行时依赖(纯 Node ≥ 20)。
dsh-guard status # 当前版本 / dist-tags / 存储指纹 / 锁文件状态
dsh-guard preflight # 升级前检查(闸门):插件兼容 × 存储格式 × 自动备份
dsh-guard upgrade # 闸门 + 执行 dsh 升级 + 事后复检
dsh-guard upgrade-plugins # 闸门 + 更新 profile 插件 + 重写锁文件
dsh-guard snapshot # 备份 $DSH_HOME + 写入 dsh.guard.lock.json
dsh-guard restore/rollback # 一键回滚(先自动做安全快照)
dsh-guard verify # 与提交的锁文件比对(团队漂移检测)
dsh-guard migrate # 会话数据迁移(sqlite -> zstd JSONL,先备份)
dsh-guard dsh <args...> # 透传模式:dsh plugin add/update 前自动过闸门
四个缺口的对应设计
缺口 1 — 升级前置检查(闸门)→ preflight
一次 dsh-guard preflight 回答三个问题,任何一个是"破坏性"就 exit 1 拒绝升级(有警告则 exit 2):
- 插件兼容:对目标 DSH 版本,逐个已装插件查
- 兼容矩阵注册表(机器跑出来的
compat.json,缺口 2 的输出) - 作者元数据(插件 package.json 里的
dsh.compat.tested/requires) - 都没有 →
untested警告,不硬拦
- 兼容矩阵注册表(机器跑出来的
- 存储格式破坏性变更:对
$DSH_HOME做指纹(见下),与lib/formats.json+ 注册表里目标版本的事实比对。格式不同 → BLOCKED(这正是 rc.8 会话全丢事故的闸门)。 - 自动备份:闸门通过时先拍
$DSH_HOME快照(tar + sha256 + manifest)。
为什么闸门只能靠 wrapper + 引导期探针,而不是插件树内钩子? 这是读源码后的事实约束:
dsh plugin是 launcher 里的薄 pnpm 转发器(bin.js的 switch 分支),没有前置钩子——没有任何插件能挂进pnpm update之前。- 树内插件解析命令行的路也被堵死:
dsh-web-app的web-startup行无条件调用parseCmdline,commander 拒绝未知命令,第二个解析行在同 profile 里必然炸掉整个启动树。
所以设计是:
- 真正的工作在 独立 bin
dsh-guard(不 boot 任何 profile,纯文件检查 + spawn)。 cordis.patch.yml里只挂一个被动行(guard-drift):每次 boot 记录 dsh 版本到$DSH_HOME/.guard-state.json,发现版本变了就打印一行"你没过闸门就升级了"的告警。所有逻辑 try/catch,绝不 fail-loud。- 日常纪律用别名:
alias dsh='dsh-guard dsh'(PowerShell 里包一层 function)。dsh-guard dsh plugin add/update/install先过闸门再转发真实dsh。
缺口 2 — 自动化兼容矩阵 → compat/
把"作者有空才写文章"变成机器数据,三层:
- 元数据契约:插件在 package.json 声明
dsh.compat:"dsh": { "bundle": { "patch": "./cordis.patch.yml" }, "compat": { "requires": ">=0.1.1-rc.1", "tested": ["0.1.0-rc.7", "0.1.1-rc.1"], "storageFormats": ["zstd-jsonl"], "kind": "tooling" } } - CI 矩阵:
compat/compat-matrix.yml(可复制到任意插件仓库或中央调度仓库)+compat/report.mjs。每个 job 在全新 profile(隔离DSH_HOME)里npm i -g @deepseek-ai/dsh@<ver>→dsh plugin --profile ci add <plugin>→dsh --profile ci --dump-config(boot 冒烟:树能组装出来就是过了)→ 输出一行 JSON。collector 合并成compat.json并提交。 - 注册表 + 徽章:
compat.json按compat/schema.json组织,托管在 Blue-Whale-Harness 的 compat/ 目录 (已推送,2026-08-25 首版含 8/8 实测数据),CDN 源cdn.jsdelivr.net/gh/Shizuku-keop/Blue-Whale-Harness@main/compat/compat.json(lib/registry.js默认,含 raw + GitHub API base64 回退)。徽章用 shields.io dynamic JSON 直接指 CDN 文件。preflight 消费同一份数据—— 货架上的"保质期标签"。
首版实测数据(2026-08-25,compat/local-matrix.ps1,隔离 DSH_HOME + pnpm 11):
| 插件 \ DSH | 0.1.0-rc.7 | 0.1.0-rc.8 | 0.1.1-rc.1 | 0.1.1-rc.2 |
|---|---|---|---|---|
| dsh-better-sidebar 0.15.2 | ✅ pass | ✅ pass | ✅ pass | ✅ pass |
| dsh-mnemon 0.2.16 | ✅ pass | ✅ pass | ✅ pass | ✅ pass |
注意:矩阵验证的是插件 API 兼容(安装 + mount)。rc.8 的存储格式变更 (社区报告的数据丢失事故)在数据层——注册表
storageFormats里 rc.8 仍是unknown,升级闸门靠存储指纹拦截,不依赖插件 pass。
注册表条目示例:
{ "schema": 1, "updated": "2026-08-25T03:00:00Z",
"plugins": { "dsh-better-sidebar": { "0.1.1-rc.2":
{ "status": "pass", "testedAt": "2026-08-25T03:00:00Z",
"by": "run 1234", "evidence": "dsh-install:0 plugin-install:0 boot:0" } } },
"storageFormats": { "0.1.1-rc.2": { "sessionFormat": "zstd-jsonl", "projcacheVersion": 3 } } }
缺口 3 — 会话数据迁移 → migrate
安全优先管线:detect → backup → transform → verify → checkpoint。
detectLegacy扫描$DSH_HOME/sessions/**的文件头:zstd(28 B5 2F FD)/ sqlite(SQLite format 3)/ gzip / 未知。不知道的格式拒绝转换,只备份——绝不猜。- sqlite → zstd JSONL:读用 Node ≥ 22.5 内置
node:sqlite(零原生依赖),写 zstd 帧用外部zstdCLI 或可选fzstd;两个都没有就拒绝(裸.jsonlDSH 读不了)。 - 原文件在验证通过后才改名
.legacy.bak,新文件先写.migrating再原子改名。 - 诚实边界:每版 DSH 的 session JSONL 记录 schema 必须对照目标版本读文件确认——
lib/formats.json里逐版本登记,没登记就是 unknown(preflight 会因此警告,不会静默放行)。
缺口 4 — profile 级锁文件 → snapshot / verify / rollback
profiles/<name>/dsh.guard.lock.json(随团队仓库提交):
{ "schema": 1, "profile": "web",
"dsh": { "version": "0.1.1-rc.2", "integrity": "sha256:…" },
"plugins": { "dsh-better-sidebar": { "version": "0.15.2", "integrity": "sha256:…", "bundle": true } },
"storage": { "sessionFormat": "zstd-jsonl", "sessionCount": 74, "projcacheVersion": 3 },
"configHash": { "cordis.patch.yml": "sha256:…", "pnpm-workspace.yaml": "sha256:…", "settings.yaml": "sha256:…" },
"backup": "backups/2026-08-25T03-00-00-000Z/snapshot.tar" }
dsh-guard snapshot:拍快照 + 写锁文件(锁里记录备份路径)。dsh-guard verify:把本机实况与锁文件比对——插件版本、内容完整性、配置 hash、存储格式逐项 diff,输出"你跑得了我跑不了"的具体差异。rollback/restore:解 tar 回写,恢复pnpm-lock.yaml后自动pnpm install --frozen-lockfile;恢复前先做安全快照(永远有回头路)。- 快照默认排除凭据文件(
.credentials.yaml、pet.json、.gh_*、.env),--include-secrets显式开启——备份是可交给同事的东西,不是泄露源。
关键技术事实(源码核实)
| 事实 | 影响 |
|---|---|
dsh plugin = 薄 pnpm 转发器,launcher 无前置钩子 |
闸门只能 wrapper/别名 + 引导期探针 |
dsh-web-app 无条件 parseCmdline,commander 拒绝未知命令 |
同树内不能有第二个解析命令行的插件 → CLI 必须独立 bin |
sessions = session-<uuid>/session.jsonl.zstd(zstd 魔数 28 B5 2F FD,本机实测) |
格式指纹 = 魔数扫描,廉价可靠,不用解码 |
storages/session_projcache.json 带 unit.version(本机 = 3) |
缓存格式版本号可进指纹,版本变化 = 警告(会重建,非数据丢失) |
bundle 插件 = npm 包声明 dsh.bundle.patch,main 导出 {name,inject,apply},loader 取 exports.default |
插件包可同时是 CLI + 被动 cordis 行(default 导出插件,命名导出库 API) |
$DSH_HOME = $DSH_HOME 环境变量 → ~/.dsh(dsh-home-paths 源码) |
路径解析完全对齐官方 |
版本号权威来源 = launcher package.json(dsh --version);dist-tags 每周在变 |
永远运行时解析 next/latest,绝不硬编码(本文档引用的 rc 号已经过时) |
安装与使用
# 作为 CLI(不装进 profile 也能用)
npm i -g dsh-compat-guard # 或 pnpm add -g
# 装进 profile(可选:获得 boot 期漂移探针)
dsh plugin --profile web add dsh-compat-guard
# 日常纪律:把 dsh 包一层
# bash: alias dsh='dsh-guard dsh'
# pwsh: function dsh { dsh-guard dsh @args }
已知边界(诚实声明)
- 闸门不是强制性的——launcher 没有钩子,纪律靠别名/团队约定;探针只能事后告警。上游要根治需给
dsh plugin加 pre-hook,本包是社区侧能做的全部。 - 注册表已托管:默认指向 Blue-Whale-Harness 的 compat/(jsDelivr CDN,多源回退),
lib/registry.js的DEFAULT_REGISTRY_URL可换;离线时用$DSH_HOME/.guard-cache/缓存并降级为"只警告"。 lib/formats.json是种子数据:本机只实测过0.1.1-rc.2(zstd-jsonl / projcache v3)。rc.7/rc.1 的存储布局必须有人实测登记(或等注册表storageFormats补上)——未知 = 警告而非静默放行。- 迁移的 JSONL schema 必须对照目标版本读文件确认;工具对未知格式只备份不转换。
verify的 integrity 是 sha256(插件 package.json)——检测内容漂移够用,不是 npm integrity 的替代。
开发
node --test test/ # 单元测试(node:test,零依赖)
node lib/cli.js status # 本机实况(只读)
node lib/cli.js preflight --target next # 对真实 $DSH_HOME 干跑(会备份!)
License
MIT
No comments yet. Be the first to write one.