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

9.2 KiB
Raw Blame History

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

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"

# 本机:列出仓库里的源码文件,逐个算「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 变更。