Files
dsh_ai1net_server/交付物/基础插件身份与回滚缺失侧-20260925.md
admin 318430c9e9 chore(工作区): 插件投放与分库线收口入库(第 35 棒 + 22:44 拍板落盘)
范围 = 本线(插件投放与分库线)产物 + 记忆类,共 27 件:
- 接续入口_插件投放与分库线(1 件):§0 新增 22:44 拍板行;§2 第 36 棒范围改 5 步、条件步转无条件
- 交付物(22 件):MCN 数据面接入阶段一/阶段二系列(含 B 案落地与 shadow 读数)、
  P0 修通与移动端真机验收、pnpm-EPERM、两机 lib 差异、共享层台账语义、
  基础插件身份与回滚、插件接入验收、插件数据面取数口、移动端迁包与字号扩面、跨机错误消息
- 记忆(4 件):2026-09-24 / 2026-09-25 日志、MEMORY.md、本棒 automation 执行记录

⛔ 未含他线在途改动(只报告、不代提交):机制层 CODEBUDDY.md / state.py /
.codebuddy/rules/server-ops.md、接续入口_IM线、接续入口_StoryForge验收线、
其余 29 个 automation 目录、docs/规则与载体/、归档/、接续包_*。
2026-09-25 23:06:29 +08:00

11 KiB
Raw Permalink Blame History

第 19 棒交付件 · 基础插件「默认开启+不可关闭」机制 + 回滚缺失侧修复

工作区:E:/ProgramData/AIProject/aliyun-dsh-server|线:插件投放与分库线(入口 接续入口_插件投放与分库线_20260922.md) 轮次:第 19 棒 · 执行棒(2026-09-25 10:2x–10:5x)|代码仓:D:\github\dsh_shenxian(生产口径 dshs) 结论一句话:第 1 件完成并上线;第 2 件机制代码面全落并上线,真机判据 2 ✅ / 1 ✗ / 1 未取证 ⇒ ⛔ 不宣称验收通过;同时把 pnpm exit 255 的根因从"原因不明"推进到有具体错误码指向(未修,交第 20 棒)。


一、第 1 件 · 回滚"缺失侧"(P0)✅ 完成

问题(第 18 棒真机实证):off 撞 pnpm 偶发失败 ⇒ package.json 里声明还在、node_modules/<pkg> 软链已被删 ⇒ 实例启动 Error: dsh: cannot resolve profile bundle "dsh-plugin-mcn-suite" ×5 → crash-loop-circuit-open(冷却 600 s)。旧 restoreProfile() 只做"删多余",不重建缺失。

修法(src/supervisor/plugin-assembly.ts):新增 linkRefsOf(dir) / rebuildMissingDepLinks(dir),接入 restoreProfile()(在 pruneOrphanDepLinks 之后)+ applyProfileChanges() 收尾,并把重建结果作为 repaired[] 回给调用方。

四条不变量(判据,单测钉住):

# 不变量 实现要点
1 只补 link: 声明 遍历 dependencies 里 link:<绝对路径> 的条目;file: / registry 依赖一律不动
2 只补"缺" lstatSync 命中即 continue(已正确 ⇒ kept,不重写)
3 ⛔ 不建悬空链 目标 statSync 不是目录 ⇒ 跳过(宁可缺,不留指向空气的软链)
4 不碰真目录、不跑 pnpm 纯 symlinkSync;子路径 @scope/x 先 mkdirSync(recursive);node_modules 不存在 ⇒ 整体跳过;属主 chownSync 尽力而为(非 root 静默跳过)、⛔ 回滚路径上不许再抛

单测:test/plugin-assembly.test.mjs 第 ⑨ 组 2 例(缺链重建 / 不造半安装态)+ 第 ⑩ 组 2 例(落盘拒绝 / 拒绝码同名);S3 请求体形状 补 essential: false。npm test = 577 过 / 0 败 / 2 跳过(第 18 棒基线 575 ⇒ +2)。


二、第 2 件 · 基础插件「默认开启 + 用户不可关闭」(用户 09:3x 拍板)

2.1 第 0 步:真机确证"dsh 自带插件清单"(⛔ 不许跳的那一步)

查什么 47 上的实测结果
@deepseek-ai/dsh 的 package.json 不声明任何插件清单 —— 无 dsh.plugins / bundled 字段,只有 configTrees
node_modules/@deepseek-ai/* 是库依赖(dsh-base / dsh-web-app 等运行时 / UI 包),不是"自带业务插件"
@softspark 在 dsh 包内 零命中
可比对的平台语义(唯一) 门户 /opt/dshs/web/admin.html「运行环境」页用 i.removable ? pa.removable : pa.required 渲染,文案 = 「平台必备」/「可移除」 二分,并有"「平台必备」项请勿删"

⇒ 判定:"基础插件"不是 dsh 的既有概念,无"自带清单"可枚举;机制必须数据驱动(台账身份标记),⛔ 不硬编码包名清单。

2.2 落地形态(上线件)

  • 身份存台账:src/db/schema.ts v17 —— business_plugins.essential(SQLite INTEGER NOT NULL DEFAULT 0 / PG BOOLEAN NOT NULL DEFAULT FALSE);types.ts / repo.ts / pg.ts / sqlite.ts / adapter.ts 五件加列读出口与 setBusinessPluginEssential。
  • 门禁两处(双保险):① 路由 POST /api/plugins/mine/apply:对基础插件的 enabled:false 直接拒 ⇒ HTTP 409 {error:'essential_plugin', pluginId, message} + 写 disable_denied_essential 审计;② 装配层 applyProfileChanges():结构性跳过并回 refused: [{id, reason:'essential_plugin'}](⇒ 即便别的调用方绕过路由,也关不掉)。
  • 播种三条路径:审批钩子 POST /api/admin/users/:id/approve(fire-and-forget)/POST /api/plugins/business/essential/sweep(admin,{userIds?} 省略 = 全部公开用户)//mine/apply 顺带。
  • 跨机:remote-spawner.ts 请求体带 essential ⇒ worker/agent.ts 严格解析(只认布尔 true)。
  • 门户 UI:poc/business-plugins/lib/client.js —— 勾选框 disabled(基础插件不给操作入口)、状态徽章改渲染 「平台基础插件 · 不可关闭」(身份标注优先于启停徽章)+ title;版本 0.3.25 → 0.3.26。

2.3 真机判据(47,临时 admin 会话,用完即删)

判据 读数 结论
④ 关闭请求 = 具名拒绝且状态不变 http=409 + {"error":"essential_plugin", …};拒绝前后 /mine 完全一致 ✅ 通过
② 台账标记生效 admin 列表 @softspark/dsh-file-preview essential=True version=2.0.0 ✅ 通过
① 默认播种 ⇒ 该实例默认 enabled sweep 回 {"ok":false,"seeded":1,"failed":1, error:"pnpm_failed"};/mine 仍 enabled=False 🔴 未通过(被 pnpm 挡住)
③ 门户 UI 无关闭入口 0.3.26 tgz 已 pack、未投放 ⇒ 无读数 🔴 未取证(⛔ 无读数不宣称通过)

三、附录 A · pnpm exit 255 根因新证据(⛔ 未修 · 交第 20 棒)

取证命令(第三方可复现):

ssh bt-server "journalctl -u dshs-worker --since '45 min ago' --no-pager | grep plugin-assembly"

读数(Sep 25 10:52:09–10:52:21,12 秒内连续 4 次 ⇒ 在该 profile 上稳定复现,不是"偶发"):

[plugin-assembly] pnpm 失败 code=255 uid=114801 cwd=…/users/<uid>/home/profiles/web args=install --reporter silent stderr=- stdout=-

已排除的两块:① 权限 —— 共享层链 755(/var/lib/dshs 711 → bundled-plugins 755 → 包目录 755),uid 114801 对新旧包目录 package.json A/B 双读均通过;② 残留 —— apply 失败后 dependencies 已回滚干净(无 @softspark/dsh-file-preview 残留)、node_modules 无半安装态。

新证据(指向具体错误码):

事实 读数
profile 记的 store node_modules/.modules.yaml ⇒ storeDir=/var/lib/dshs/users/<uid>/ws/.local/share/pnpm/store/v3(HOME=ws 时代产物,Sep 11)
平台跑 pnpm 时的 HOME src/supervisor/plugin-assembly.ts:499 ⇒ env HOME=${homeDir}(= <user>/home)
该 HOME 下的默认 store /var/lib/dshs/users/<uid>/home/.local/share/pnpm/store/v3 ⇒ No such file or directory
同仓已有的解法 scripts/ensure-biz-plugins.cjs:existingStoreDir() —— 注释明写 ERR_PNPM_UNEXPECTED_STORE(档案 57 实测):pd 记录 store 与解析到的 store 不一致时,pnpm 直接拒绝

⇒ 高度指向 ERR_PNPM_UNEXPECTED_STORE(立即拒绝特征 ≈2 s 与读数吻合);正解 = 平台路径复刻脚本里的 store 口径(读 .modules.yaml ⇒ 传 --store-dir),而非只补 reporter。为什么一直"看起来像偶发":只有 HOME 换代(ws → home)前建的存量 profile 会命中。


四、未取证 / 未修清单(如实)

# 项 状态
1 基础插件判据 ①(默认播种 ⇒ enabled) 🔴 未通过(pnpm)
2 基础插件判据 ③(门户 UI 无关闭入口) 🔴 未投放、未取证
3 pnpm exit 255 🔴 未修(根因已成形,交第 20 棒)
4 npm run verify 尾段 verify-platform-admin-section.mjs ⚠️ 既有缺陷(同文件 require() + top-level await ⇒ ERR_AMBIGUOUS_MODULE_SYNTAX;单独跑同样失败,与本棒无关)
5 @softspark/dsh-file-preview 已进共享层 ⇒ 所有登录用户可见(可自助启用);若"先启用后标 essential"则用户无法自行关闭 ⚠️ 副作用,已上报(撤回口 = …/:id/essential {essential:false})
6 @dsh-local/business-plugins 0.3.26 的 UI 改动 ⚠️ 未投放(需 pnpm 修通后铺装)

五、部署与卫生读数

  • 47 部署 11 件 / 106 2 件,md5 与本地逐字一致;备份 /opt/dsh/backups/lib-p19/47-pre-p19-20260925-104848.tgz、106-pre-p19-20260925-105012.tgz。
  • 47 systemctl restart dshs ⇒ active · NRestarts=0 · 门户 200;v17 迁移已落(max(version)=17),池内 essential 全 f(存量行为零变化)。
  • ⚠️ 部署途中曾把 lib/ 内容误落 /opt/dshs 根(散进 supervisor/ web/ db/ worker/)⇒ 已逐一 rm + rmdir 复原(四目录均系本次误建)后按 -C /opt/dshs/lib 重投;期间 47 未用新代码重启。
  • 清理:临时 admin 会话残留 0;⛔ 未 commit / push;⛔ src/web/routes/im.ts 零改动;⛔ 未手改用户 home 的 profile / .pnpm。

六、补充(2026-09-25 11:0x · 用户口径定案后,本文件第二节结论已更新)

用户原话:「dsh 自带的就是,admin 添加的就不是」⇒ 判定 = 按来源(@deepseek-ai/dsh 自身声明的依赖 = 自带),⛔ 不是按"平台必备"。据此:

  • ✅ 基础插件名单 = 空:业务插件台账全量 5 项(@dsh-local/im-conversation-tabs、@dsh-local/storyforge、@softspark/dsh-file-preview、dsh-plugin-mcn-suite、dsh-univer-office)全属 admin/平台添加;@softspark 在两机的 dsh 安装树 + 全局 npm 根零命中(dsh 包内提到它的文件 0 个)。
  • ✅ @softspark/dsh-file-preview 的 essential 标记已撤销(真机 changed:true;台账复读 essentials 空;用户面仍可见、可自助启停)⇒ 第二节的"判据 ①③"已无主体;门户 0.3.26("平台基础插件"徽章)暂不投放,产物留在 poc/business-plugins/dsh-local-business-plugins-0.3.26.tgz。
  • ✅ 新核实(免掉一次改造):dsh 自带的包本来就关不掉 —— 装配入口对清单里每一项都查台账,不在候选池 ⇒ 404 not_in_pool(src/web/routes/business-plugins.ts:2299-2302)⇒ 绕过 UI 自造请求也关不掉;门户清单又不渲染它们 ⇒ UI 层与接口层同时成立,无需新增代码。
  • ⛔ 仍待办(未变):pnpm exit 255 可见化 + 根因修复 —— 已交第 20 棒(automation 421a8bad-…)。
  • 取证脚本:tmp/p19b-quota.sh(台账全量 → 撤销标记 → 复核 → 用户面清单 → 清理临时会话)。