范围 = 本线(插件投放与分库线)产物 + 记忆类,共 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/规则与载体/、归档/、接续包_*。
11 KiB
第 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 未取证 ⇒ ⛔ 不宣称验收通过;同时把 pnpmexit 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.tsv17 ——business_plugins.essential(SQLiteINTEGER NOT NULL DEFAULT 0/ PGBOOLEAN 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 棒(automation421a8bad-…)。 - 取证脚本:
tmp/p19b-quota.sh(台账全量 → 撤销标记 → 复核 → 用户面清单 → 清理临时会话)。