Skip to content
dsh.fish
Bundle

dsh-compat-guard

Compatibility governance for DeepSeek Harness: upgrade pre-flight gate, storage-format fingerprinting, $DSH_HOME backup, session migration, per-profile lockfile with rollback, and a machine-readable plugin x DSH compatibility matrix + CI workflow.

Source
Shizuku-keop
stars
1 stars
License
MIT
Updated
Updated 13 days ago

Readme

# 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):

1. **插件兼容**:对目标 DSH 版本,逐个已装插件查
   - 兼容矩阵注册表(机器跑出来的 `compat.json`,缺口 2 的输出)
   - 作者元数据(插件 package.json 里的 `dsh.compat.tested/requires`)
   - 都没有 → `untested` 警告,不硬拦
2. **存储格式破坏性变更**:对 `$DSH_HOME` 做指纹(见下),与 `lib/formats.json` + 注册表里目标版本的事实比对。格式不同 → **BLOCKED**(这正是 rc.8 会话全丢事故的闸门)。
3. **自动备份**:闸门通过时先拍 `$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/`

把"作者有空才写文章"变成机器数据,三层:

1. **元数据契约**:插件在 package.json 声明 `dsh.compat`:
   ```json
   "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"
     }
   }
   ```
2. **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` 并提交。
3. **注册表 + 徽章**:`compat.json` 按 `compat/schema.json` 组织,托管在
   [Blue-Whale-Harness 的 compat/ 目录](https://github.com/Shizuku-keop/Blue-Whale-Harness/tree/main/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。

注册表条目示例:
```json
{ "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 帧用外部 `zstd` CLI 或可选 `fzstd`;两个都没有就拒绝(裸 `.jsonl` DSH 读不了)。
- 原文件在**验证通过后**才改名 `.legacy.bak`,新文件先写 `.migrating` 再原子改名。
- 诚实边界:每版 DSH 的 session JSONL 记录 schema 必须对照目标版本读文件确认——`lib/formats.json` 里逐版本登记,没登记就是 unknown(preflight 会因此警告,不会静默放行)。

### 缺口 4 — profile 级锁文件 → `snapshot` / `verify` / `rollback`

`profiles/<name>/dsh.guard.lock.json`(随团队仓库提交):

```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 号已经过时) |

## 安装与使用

```bash
# 作为 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 }
```

## 已知边界(诚实声明)

1. **闸门不是强制性的**——launcher 没有钩子,纪律靠别名/团队约定;探针只能事后告警。上游要根治需给 `dsh plugin` 加 pre-hook,本包是社区侧能做的全部。
2. **注册表已托管**:默认指向 Blue-Whale-Harness 的 compat/(jsDelivr CDN,多源回退),`lib/registry.js` 的 `DEFAULT_REGISTRY_URL` 可换;离线时用 `$DSH_HOME/.guard-cache/` 缓存并降级为"只警告"。
3. **`lib/formats.json` 是种子数据**:本机只实测过 `0.1.1-rc.2`(zstd-jsonl / projcache v3)。rc.7/rc.1 的存储布局必须有人实测登记(或等注册表 `storageFormats` 补上)——未知 = 警告而非静默放行。
4. **迁移的 JSONL schema** 必须对照目标版本读文件确认;工具对未知格式只备份不转换。
5. `verify` 的 integrity 是 sha256(插件 package.json)——检测内容漂移够用,不是 npm integrity 的替代。

## 开发

```bash
node --test test/        # 单元测试(node:test,零依赖)
node lib/cli.js status   # 本机实况(只读)
node lib/cli.js preflight --target next   # 对真实 $DSH_HOME 干跑(会备份!)
```

## License

MIT

Install

dsh plugin --profile web add github:Shizuku-keop/dsh-compat-guard

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source