Files
dsh_ai1net_server/dsh-server-docs/archive/交接单-已完成/T05-插件兼容性预检.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

10 KiB
Raw Blame History

T05 · 插件兼容性预检(导入 / 上传即判定)

  • 日期:2026-09-12
  • 状态:✅ 已完成并归档(2026-09-12 16:35,exec-session-D;服务器 commit 8d19e89,验收见文末 §十 执行回报)
  • 触发:用户「所有插件 导入和上传的时候都要判断兼容性」
  • 方案来源: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 静默回滚)

五、步骤(每步自带验证)

  1. 抢锁:bash scripts/handoff-guard.sh --claim-exec "<会话名>" T05 → bash scripts/op-lock.sh claim "t05-compat-precheck" "改动平台代码 + 重启 dshs(中断在线用户);预计 10 分钟"。 验证:两条命令均返回成功;op-lock.sh status 显示自己的锁。
  2. 写 src/web/plugin-compat.ts(判据 A + B),先在本机用现成 tgz 自测(可把 3 个 tgz 下载到本机跑)。 验证:对 _anysearch_anysearch-dsh.tgz 判"不兼容"、对 dsh-univer-office.tgz 判"兼容"、对 _liustack_modlens.tgz 判"待装后复核"(这就是验收基线的预演)。
  3. 接入 L1 两个入口(上传 + 官方目录导入),沿用档案 66 的 fail-closed + 逐条回显 + 信任放行。 验证:门户上传 anysearch tgz → 被拦、理由可读;上传 univer-office → 通过。
  4. 接入 L2(enable 路径,pnpm add 之后、启动之前扫 profile node_modules)。 验证:构造一个"自身合法但传递依赖越界"的 profile → 命中缺失符号并拒绝启用;证据进日志 + 页面回显。
  5. npm run typecheck + npm run build → 部署 → systemctl restart dshs(R8:先知会用户)。 验证:服务 active + HTTP=200;lib/web/plugin-compat.js 存在且时间戳为本次。
  6. 文档收口: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(避免触发依赖树重装)。

未做 / 待用户:

  1. L2(启用前扫 profile node_modules) —— 覆盖"未声明依赖却用了平台 API"与传递依赖越界;
  2. 交互式验收 D/E —— 需 admin 在门户上传一次 anysearch tgz,确认被拦并看到 5 条依据(不能代做:R4);
  3. 候选池里那个坏掉的 anysearch tgz —— 现在有了明确依据(静态不兼容),建议下架;处置属业务目标,待定。