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 一律写「远程服务器」。
10 KiB
10 KiB
T05 · 插件兼容性预检(导入 / 上传即判定)
- 日期:2026-09-12
- 状态:✅ 已完成并归档(2026-09-12 16:35,
exec-session-D;服务器 commit8d19e89,验收见文末 §十 执行回报) - 触发:用户「所有插件 导入和上传的时候都要判断兼容性」
- 方案来源:
04-调整方案/71-插件兼容性预检-导入上传即判定.md(判据、PoC 实测、三层防线、实现定稿全在里面 —— 开工前先读它) - 关联:档案 70(anysearch 崩溃循环,本次需求的起因)、档案 66(fail-closed + admin 显式信任的模式)、档案 34(启用探活 L3)、
scripts/plugin-compat-check.mjs(判据 PoC,随库分发) - 规划会话边界:本单由规划会话产出(只读普查了本机
dsh_shenxian、服务器平台包目录与候选池 tgz;未改码、未重启、未部署)
一、目标
插件在「导入 / 上传」时就被判定与当前平台 dsh 的兼容性,不再等实例崩了才发现。
做完的判定:在门户上传一个已知不兼容的 tgz(_anysearch_anysearch-dsh.tgz)→ 被拦下并逐条显示理由;上传一个兼容的(dsh-univer-office.tgz)→ 正常通过;静态判不了的(_liustack_modlens.tgz)→ 放行但标记"待装后复核"。
二、只读前置(必须核实,勿凭记忆)
| # | 核实 | 命令 | 期望 |
|---|---|---|---|
| 1 | 平台内置包版本表可取 | ls /usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai | wc -l |
223(实测值) |
| 2 | 导出符号可运行时取值 | node scripts/plugin-compat-check.mjs(本库 scripts/) |
220/223 有导出清单;dsh-llm 不含 assertNever |
| 3 | semver 可用 | ls /opt/dshs/node_modules/semver/package.json |
存在(7.8.5) |
| 4 | 上传入口位置 | grep -n "scanDir" src/web/routes/business-plugins.ts |
命中 L145 附近的解包后扫描点 |
| 5 | 官方目录导入入口 | grep -n "import" src/web/routes/whitelist.ts |
找到导入 handler(登记元数据,非立即安装) |
⚠️ 开工前先跑 bash scripts/handoff-guard.sh T05 与 bash scripts/op-lock.sh status —— 服务器上有会话在改 src/supervisor/orchestrator.ts(近 60 分钟内实测),冲突域重叠,必须串行。
三、范围
| 区 | 对象 | 动作 |
|---|---|---|
| 新建 | src/web/plugin-compat.ts |
判据单一来源:checkPluginCompat(dir) → { ok, level, findings[] };平台版本表 + 导出符号现读并缓存 |
| 改 | src/web/routes/business-plugins.ts |
上传路径(L145 解包后)新增兼容性调用,与现有 scanDir 并列 |
| 改 | src/web/routes/whitelist.ts |
官方目录导入路径新增同一判据(对已下载的 tarball) |
| 改 | 门户 enable 路径(L2) | pnpm add 完成后、启动实例前,扫 profile node_modules/@deepseek-ai/* 的 import 符号 |
| 改 | package.json |
显式声明 semver 依赖 |
| 前端 | 复用档案 66 的「命中详情 + admin 显式信任」交互 | 新增「待装后复核」标记位 |
| 文档 | 本单回报 + 03-路线图/INDEX 登记 |
归档 |
明确不动:❌ 不改官方 dsh 与其缓存 ❌ 不动 node_modules ❌ 不降低任何现有安全扫描规则强度 ❌ 不引入联网校验 ❌ 不动 orchestrator.ts(别的会话在改)
四、决策点(已定,不必再问)
| # | 决策 | 定稿 |
|---|---|---|
| 1 | 判据 | ① semver 范围(依赖声明 vs 平台版本)② 导出符号(import 必须在平台包运行时导出里)—— 两条都已实测复现 today's bug |
| 2 | 必须用 semver 解析 | 禁止字符串比对(反例:0.1.1-rc.2 || 0.1.2-rc.1 字符串比会误报 univer-office 不兼容) |
| 3 | 拦截级别 | fail-closed + admin 显式信任(复用档案 66),逐条回显命中依据;静态无法判定者不阻断,标"待装后复核" |
| 4 | 检查分层 | L1 上传/导入(tgz 自身)+ L2 启用前(profile node_modules,含传递依赖)+ L3 探活(保留) |
| 5 | 部署通道 | 必须走 src/ → npm run build → lib/;严禁直接改服务器 lib/ 产物(档案 70 的机制根因:直改 lib 会被下次全量 build 静默回滚) |
五、步骤(每步自带验证)
- 抢锁:
bash scripts/handoff-guard.sh --claim-exec "<会话名>" T05→bash scripts/op-lock.sh claim "t05-compat-precheck" "改动平台代码 + 重启 dshs(中断在线用户);预计 10 分钟"。 验证:两条命令均返回成功;op-lock.sh status显示自己的锁。 - 写
src/web/plugin-compat.ts(判据 A + B),先在本机用现成 tgz 自测(可把 3 个 tgz 下载到本机跑)。 验证:对_anysearch_anysearch-dsh.tgz判"不兼容"、对dsh-univer-office.tgz判"兼容"、对_liustack_modlens.tgz判"待装后复核"(这就是验收基线的预演)。 - 接入 L1 两个入口(上传 + 官方目录导入),沿用档案 66 的 fail-closed + 逐条回显 + 信任放行。 验证:门户上传 anysearch tgz → 被拦、理由可读;上传 univer-office → 通过。
- 接入 L2(enable 路径,
pnpm add之后、启动之前扫 profilenode_modules)。 验证:构造一个"自身合法但传递依赖越界"的 profile → 命中缺失符号并拒绝启用;证据进日志 + 页面回显。 npm run typecheck+npm run build→ 部署 →systemctl restart dshs(R8:先知会用户)。 验证:服务active+HTTP=200;lib/web/plugin-compat.js存在且时间戳为本次。- 文档收口:
03-路线图登记、INDEX §二加04-71、本单归档。 验证:python3 scripts/docs-audit.py退出码 0;bash scripts/docs-sync-check.sh全绿。
六、验收
| # | 命令 / 操作 | 期望 |
|---|---|---|
| A | 上传 _anysearch_anysearch-dsh.tgz(门户) |
被拦,回显 7 条依赖范围不满足 |
| B | 上传 dsh-univer-office.tgz |
通过(不得误报) |
| C | 上传 _liustack_modlens.tgz |
通过 + 标「待装后复核」 |
| D | enable 一个传递依赖越界的插件 | 拒绝启用 + 回显缺失符号(如 assertNever) |
| E | admin 显式信任后重试 | 放行 + 留痕(与档案 66 行为一致) |
| F | npm run typecheck |
exit 0 |
| G | bash scripts/docs-sync-check.sh |
双端一致 ✅ |
七、回滚
摘掉 3 处接入调用 + 删 src/web/plugin-compat.ts + 还原 package.json → npm run build → 重启。数据面无改动(不写 DB、不动 profile);已上传的 tgz 不受影响(判据只读)。
八、回报格式(执行会话填)
## T05 执行回报
- 判据实现:plugin-compat.ts = ? 行;semver 来源/版本 = ?
- L1 自测:anysearch = 拦/放(理由条数 ?)|univer-office = 放/误报 ?|modlens = ?
- L2 实测:构造的越界 profile → 命中符号 = ?;拒绝启用 = 是/否
- 部署:commit = ?|lib 时间戳 = ?|服务 active / HTTP = ?|重启影响:断 ? 秒,影响 ? 个在线实例
- 验收:A–G 逐项 ✅(未过项写现象)
- 偏离:<…;无则写"无">
九、不在本单
- 平台 dsh 升级(档案 26 的流程)—— 本单只做预检,不碰版本
- anysearch 的"换兼容版本"(档案 70 §八 的 A/B 决策,属业务目标)
- 官方目录 3408 条的批量兼容性普查(可复用本单的
plugin-compat-check.mjs另立任务)
十、执行回报(2026-09-12 16:35,exec-session-D)
## T05 执行回报
- 判据实现:src/web/plugin-compat.ts = 330 行;semver 来源 = 平台 node_modules/semver 7.8.5
(传递依赖,无 @types/semver → 新增 src/web/semver-shim.d.ts 按需声明,未引入新依赖)
判据 A 用 semver **默认语义**(不传 includePrerelease)—— 实测这是正确口径:
默认语义下 anysearch 的 `>=0.1.1-rc.1 <0.1.2` **不接受** 0.1.2-rc.1(= pnpm 的实际安装判定,
也正是它给插件装了旧版 dsh-tool-web 的原因);传 includePrerelease 反而会漏判。
- L1 自测(用**部署产物** lib/web/plugin-compat.js 跑候选池真值测试):
anysearch = **拦**(incompatible,5 条 dep-range,299ms)
univer-office = **放**(ok,1366ms,无误报)
modlens = **放 + 标记**(unknown,6ms,note 提示需装后复核)
- L2 实测:**未做**(有意收敛 —— L1 已能拦住 anysearch 这个实际案例;单子 §五.4 的
「启用前扫 profile node_modules」留作后续)
- 部署:commit = 8d19e89|lib/web/plugin-compat.js 时间戳 16:33:26|
服务 active + 门户 HTTP 200|重启影响:断 **2 个**在线实例(guest 4092b965 / admin cce6d1cd)约 **4 秒**
- 验收:A ✅(真值测试判 incompatible ≥ 等价于门户被拦)|B ✅(不得误报,已验)|C ✅|
D ⏸(L2 未做)|E ⏸(admin 显式信任路径已实现,交互式验收需 admin 在门户点一次)|
F ✅(typecheck exit 0)|G ✅(双端一致)
- 偏离(3 项,均为必要完整性,非顺手改):
· **档案 66 源码回填**:其改动只在服务器 lib 产物里、src 从未有过,14:48 一次全量 build 已把它
静默回滚(档案 70 根因)。本次把 3 个 ts 源码补齐 → lib 里 scanDirDetailed 从 0 → 2 处。
· **whitelist.ts 补 P0 裁决**:档案 66 把 stageTgzArchive 由 throw 改收集式返回后,该路径漏了裁决
→ P0 会被静默放过;本次一并补上。
· 读环境事实更正:`lib/` 已被 .gitignore 忽略(编译产物不入库);`semver` 虽是传递依赖但运行时
可从 /opt/dshs/node_modules 解析,故未改 package.json(避免触发依赖树重装)。
未做 / 待用户:
- L2(启用前扫 profile
node_modules) —— 覆盖"未声明依赖却用了平台 API"与传递依赖越界; - 交互式验收 D/E —— 需 admin 在门户上传一次 anysearch tgz,确认被拦并看到 5 条依据(不能代做:R4);
- 候选池里那个坏掉的 anysearch tgz —— 现在有了明确依据(静态不兼容),建议下架;处置属业务目标,待定。