Files
dsh_shenxian/dsh-server-docs/04-调整方案/93-兼容判定收口-伞包版本与prerelease语义.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

132 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 93-兼容判定收口:伞包版本 + prerelease 语义(2026-09-14 落地)
- 日期:2026-09-14 | 状态:✅ **已上线**(平台后端已构建重启;服务器 `ci.sh` 转绿)
- 触发:用户原话 ——「**能都把这些问题都解决,同步项目到仓库**」(接档案 92 结尾上报的两个问题)
> **TL;DR**
> ① **"单测长期红"根本不是代码问题** —— 本机 `48 通过 / 0 失败`,是**服务器源码落后于 git**
> (服务器 `test/db.test.mjs` 还是旧版断言)⇒ 同步该文件后**服务器 CI 立即转绿**。
> ② **伞包版本收口**:`@deepseek-ai/dsh` **不在自己的 `node_modules` 里** ⇒ 原查询对它恒判 `null`,
> 被当作「平台没这个包,不算不兼容」而放行;收口到 `dsh-install.platformPackageVersion()`,
> 闸门与列表**共用同一份版本表**。
> ③ ⚠️ **收口带出一个必须一并修的副作用**:闸门判据 A 用 semver **默认语义**,而平台版本是 prerelease
> ⇒ **声明 `*`(任意版本)都会被判成不兼容**。实测抽样 300:`real-newer 3 | narrow-pin 22 |`
> `**prerelease-artifact 117** | match 98` —— 即闸门原本约 **39% 假阳性**
> (`dshmarket`、`dsh-context` 这些**在平台上跑得好好的**都在其中)。
> 只收口不修语义 = **让假拦截变多**,所以同轮加**prerelease 兜底**:先按默认语义判,
> 不满足时再看 `includePrerelease` —— 容忍下能满足的**只计数不阻断**。
---
## 一、问题①:单测"长期红"的真相(服务器源码落后)
| 事实 | 判据 |
|---|---|
| 本机单测 | `node --test` 五个文件 → **49 tests / 48 pass / 0 fail**(1 skip) |
| 本机 `test/db.test.mjs` | 断言已是新口径:`档案 87:条目口径从「互斥单选」改为「各自开关、可同时启用」⇒ 老断言整体改写`,期望 `every entry stays enabled`(5 个) |
| 服务器同文件 | 仍是旧断言 `exactly one key enabled`,实际 `5 !== 1` ⇒ **失败** |
| 服务器是否落后 | 用 `git ls-files src test`(60 个文件)做 **LF 归一化后的 sha256 逐文件比对**:**只有 2 个文件不同** —— `test/db.test.mjs`(服务器旧)与 `src/supervisor/orchestrator.ts`(**别人在途**,见 §四) |
⇒ **修法 = 同步该文件**(不是改代码)。同步后服务器 `bash scripts/ci.sh` → **CI OK ✅(48 pass / 0 fail)**。
> 📌 **教训(本库已在 `CODEBUDDY.md` 写过,这次又踩)**:「服务器能跑」≠「服务器源码 == git」。
> 这次的具体形态更隐蔽:**你没改的东西,服务器就是旧版** —— 所以"某个单测红"未必是代码坏了,
> 先做一次**逐文件哈希比对**再动手。方法见 §五。
## 二、问题②:伞包版本收口(含"共用版本表")
### 病根
`plugin-compat.platformPkgDir(pkg)` = `<dsh 包根>/node_modules/@deepseek-ai/<短名>`,
而**伞包 `@deepseek-ai/dsh` 不在自己的 `node_modules` 里** ⇒ `platformVersion('@deepseek-ai/dsh')` 恒 `null`
⇒ 判据 A 那句 `if (v === null) continue // 平台没这个包:不算不兼容(插件自带)` 把它放行。
**代价**(实测):官方目录里那 3 条"要求更高版本"的声明**全部只写在伞包上**
(`dsh-zotero` `^0.1.5-rc.2`、`dsh-any-background` `>=0.1.5-rc.2`、`dsh-md-notes` `>=0.1.5-rc.2 <0.1.6`)
⇒ 一条都认不出来。
### 修法(单一来源)
- **新增** `src/web/dsh-install.ts` → `export function platformPackageVersion(pkg)`:
伞包读 `<dsh 包根>/package.json`,子包读 `<scope>/<短名>/package.json`;进程内缓存;
`resetInstallPathsCache()` 一并清缓存。
- **消费方两处改为共用**:`plugin-compat.platformVersion()` 与 `plugin-dsh-compat.platformVersionOf()`
⇒ 两处不再各写一份版本表(原先 `plugin-dsh-compat` 有自己的 `versionTable`、`plugin-compat` 有自己的 `versionCache`)。
## 三、问题③(收口带出):闸门判据 A 的 prerelease 假阳性
### 为什么要一起修
只把伞包接进判据 A,会让**声明 `*` 的插件**第一次被拦(`satisfies('0.1.5-rc.1', '*')` 在**默认语义**下为 `false`)
—— 即"修好一处,制造一批新假拦截"。所以必须把语义一并处理。
### 实测分布(抽样 TOP300,`@deepseek-ai/*` 全量声明逐条判)
| 分类 | 数量 | 例 | 处置 |
|---|---|---|---|
| `match` | 98 | `dsh-vision-router` | 满足 |
| `none` | 60 | `@linxin666/*` | 没声明平台依赖 |
| **`real-newer`** | **3** | `dsh-zotero` `dsh-any-background` `dsh-md-notes` | **真不兼容,必须拦** |
| `narrow-pin` | 22 | `dsh-univer-office` `@nanmicoder/dsh-agent-teams` | 声明里只列了更旧具名版本;**仍拦**(有 trust 逃生口) |
| **`prerelease-artifact`** | **117** | `dshmarket` `dsh-context` `dsh-find-plugin` | **平台效应,不该拦** |
### 修法
判据 A 逐条改成两步(`plugin-compat.ts`):
```ts
ok = semver.satisfies(v, range) // ① 默认语义(与 pnpm 一致)—— 保持原判据
if (!ok && semver.satisfies(v, range, { includePrerelease: true })) {
ok = true; prereleaseOnly = true // ② prerelease 兜底:**只计数不阻断**
}
```
`CompatResult` 新增 `prereleaseOnly?: number` —— **不计入 `findings`、不影响 `level`**,只留可观测计数。
> ⚠️ 这一改**放宽**了上传闸门(少拦 ~39%),是本轮唯一一处"安全网变松"的改动,
> 依据是**实测数据**(那 117 条里包含本平台正在运行的 `dshmarket`);判据 B(平台包导出符号缺失)
> **未动**,真正的 API 破坏仍会被拦。**若认为该保持严格,把第二节的兜底删掉即可回退**(锚点在 `verify-dsh-compat.mjs`)。
## 四、⚠️ 未动(属别人 lane,仅上报)
- `src/supervisor/orchestrator.ts`(**在途**):`BASE_MEM_MB 160→448`、`MIN 384→512`、`MAX 1024→1536`、
`dsh-plugin-mcn-suite 128→256`(注释标注 2026-09-14、为 0.1.5 调);而 `poc/business-plugins/lib/client.js`
的对应常量**还是旧值** ⇒ 本机 `verify-mem-model.mjs` **4 项红**(它正是为「两份事实漂了」建的断言)。
**我没有动任何一边**:这组数字直接决定实例 cgroup 配额,是**产品决策**。
⚠️ 注意:**提交后仓库树两边都是旧值 ⇒ 该断言在 HEAD 上自然一致**(红只出现在当前工作树)。
- 文档库 `CODEBUDDY.md` / `BRIEF.md` / `DEPLOY-本部署.md` / `skills/dsh-*` / `04-调整方案/{76,82}` 等
仍是**别人的在途改动**,本轮提交**未包含**。
- 文档库 `scripts/docs-archive-index.py` 在 Windows 上会把 `INDEX.md` 的行尾 **CR 翻倍**
(见档案 91 §八 的完整记录)—— 该脚本是别人新增的,**未改**。
## 五、方法沉淀:判断"服务器源码是否等于 git"
```bash
# 本机:列出仓库里的源码文件,逐个算「LF 归一化后的 sha256」
git ls-files src test > files.txt
# 服务器:同一份清单,逐个算(tr -d '\r' 抹平行尾差异)
cd /opt/dshs && while read f; do [ -f "$f" ] && printf "%s %s\n" "$(tr -d '\r' <"$f" | sha256sum | cut -c1-16)" "$f"; done < files.txt
# 两边 sort 后 diff ⇒ 差异**精确到文件**(本轮 60 个文件里只 2 个不同)
```
> ⚠️ 两个坑:① 两侧都要**行尾归一化**(服务器 LF / 本机可能是 CRLF);
> ② `diff <(a) <(b)` 的**进程替换在本机 Git bash 里不可用**(`/dev/fd` 不存在)⇒ 落临时文件再 diff。
## 六、验证
| 层 | 手段 | 结果 |
|---|---|---|
| 本机测试 | `node --test` 五文件 | ✅ 48 pass / 0 fail |
| 本机 verify | `npm run verify` | ⚠️ 仅剩 §四 的 4 项(别人在途),其余**全绿** |
| 本机断言 | `verify-platform-admin-section.mjs`(**本轮更新**:旧断言断的是档案 91 已替换掉的 UI) | ✅ 全绿(含模拟点击两个新增入口 + 选中目录厂家) |
| 本机断言 | `verify-dsh-compat.mjs`(**新增 4 条锚点**:共用版本表 / 伞包单独解析 / prerelease 兜底 / 只计数不阻断) | ✅ 全绿 |
| 服务器 CI | `bash scripts/ci.sh` | ✅ **CI OK**(48 pass / 0 fail)—— 修前 1 项红 |
| 服务器功能 | 造临时插件目录**真跑闸门** | ✅ 13 项全绿:伞包/子包"要求更高"→`incompatible`;`*`/`^0.1.0-rc.7` → 不拦截且 `prereleaseOnly` 计数正确;满足→`ok`;未声明→`unknown` |
| 服务器接口 | 重启后复验白名单 | ✅ `dshVersion=0.1.5-rc.1`、默认不含 `older`、`newer` 2 条、`dshCompat=all` 时 `older` 出现 |
**铺发**:tar(LF)→ `/opt/dshs` → `npm run build` / `bash scripts/ci.sh` → `systemctl restart dshs`(重启后门户 200)。
备份:`/opt/dsh/backups/pre-93-20260914-210458/`(含 `test/db.test.mjs` / `src/web/{dsh-install,plugin-compat,routes/whitelist}.ts` / `package.json` / `poc/business-plugins/{lib/client.js,package.json}`)。
## 七、回滚
- 后端:`cp -a /opt/dsh/backups/pre-93-*/` 覆盖回 `/opt/dshs/` → `npm run build` → `systemctl restart dshs`。
- 单点回退"闸门放宽":删 `plugin-compat.ts` 判据 A 里的 prerelease 兜底分支(`verify-dsh-compat.mjs` 会立刻报警)。
- 无数据迁移、无 DB 变更。