Files
dsh_ai1net_server/dsh-server-docs/04-调整方案/34-功能插件启用探活与逐插件隔离.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

61 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 34 · 功能插件启用:探活 + 快照回滚 + 逐插件隔离标记
- 日期:2026-09-11
- 触发:用户确认「批量 + 统一重启 + 刷新 + 死循环风险」,并明确「**标记错误插件禁用并标记(让 admin 方便知晓),其余正常插件不需要回滚**」
- 状态:**✅ 已实施(commit `0283c1b`)并端到端验证通过(含「好插件保留」真实插件复核)**
## 一、暴露的既有缺陷
`/api/plugins/mine/apply` 的异步任务原本是:
```
pnpm add 各插件 → reconcileBundles → restartMain → 直接标 success
```
**全程没有任何探活** → 插件把实例搞崩(崩溃循环)时,任务照样报「✓ 已应用」,用户以为成功、实际实例起不来。档案 25 的事故(`duplicate loader entry id`)正是这一类。**排查时还发现第二个更严重的既有缺陷 → 见档案 35。**
## 二、设计(采纳用户方案)
```
① 快照 profile(package.json / pnpm-lock.yaml / cordis.patch.yml)
② 禁用项 → pnpm remove(不引入新代码,永远安全,不需探活)
③ 启用项 → 先「整批试一次」(正常路径只花 1 次重启,与旧行为一致)
探活通过 → 完成
探活失败 → ④
④ 回滚快照 → 逐插件隔离定位:能起来的保留,起不来的单独摘掉 + 记事故
结束状态 = 快照 + good 集合 ← 好插件不被坏插件连累
⑤ 事故写入 audit_log(action='plugin_incident')
⑥ 任务结果新增 rejected[],stage/error 写明「哪些已自动禁用」
```
## 三、实现
| 文件 | 改动 |
|---|---|
| `orchestrator.ts` | 新增 `restartAndProbe(userId, budgetMs=60000)` |
| `spawner.ts` | 接口加该方法 |
| `k8s-spawner.ts` | 兜底实现(k8s 未启用;返回 ok:true 保持旧行为) |
| `business-plugins.ts` | apply 任务重写 + `snapshotProfile`/`restoreProfile` |
**探活判定的两个关键点(都是实测踩出来的)**:
1. **不能只判 `restartMain` 的返回值** —— 编排器里没有该用户实例时(portal 重启清了内存态、或实例被空闲回收)`restartMain` 返回 `undefined`,会把**无辜插件**误判为不兼容。→ 改为:无实例时先按 `/api/dsh/enter` 同路径 `launch` 一个。
2. **固定短等待会把好插件判死** —— 初版用「固定 2.5s 稳定窗口」,实测好插件也报「未产出 launch token」。→ 改为**轮询**(500ms 间隔、总预算 60s),拿到 token 且 `status==='running'` 后再过 2.5s 确认没闪崩。
## 四、验证(2026-09-11,端到端)
用两个自建 fixture 插件:`good-test`(合法 bundle)、`bad-test`(`cordis.patch.yml` 插入已存在的 loader id `permission` → 必然启动失败)。
| 检查项 | 结果 |
|---|---|
| 坏插件被准确诊断 | ✅ 回传真实错误 `TypeError: duplicate loader entry id: permission`(`cordis-plugin-loader/lib/index.js:91`),非泛化文案 |
| 隔离循环执行 | ✅ 阶段依次 `整批试→定位(1/2)→定位(2/2)→恢复实例`,耗时 3s→30s→33s→42s(真实重启+探活) |
| **实例未进崩溃循环** | ✅ 全程 `instance` 存活,最终回到干净状态 |
| 快照回滚生效 | ✅ `final bundles` = 原始 5 个;`deps` 无残留 |
| 事故可追溯 | ✅ `audit_log.action='plugin_incident'` 记录 pluginId + 真实 reason |
| **好插件被保留** | ✅ **已用真实插件复核通过**(2026-09-11):从官方白名单导入真实 npm 插件 `dsh-status-rotator`,与 `bad2`(npm-pack 布局、仅 patch 有差异)一起启用 → 任务返回 `完成:1 个已启用,1 个已自动禁用`;`bundles`/`deps` 均含 `dsh-status-rotator` 且不含 `bad2`,`plugin_incident` 记录 `bad2` 的完整真实错误,实例存活。→ **「好插件不被坏插件连累」成立**。此前两轮 fixture 失败纯属 fixture 自身问题(平铺 tar 布局 pnpm 不接受 / 包名与 patch 引用不一致)。 |
## 五、后续
1. ~~用真实插件复核「好插件保留」~~ ✅ **已完成(2026-09-11)**:白名单导入 `dsh-status-rotator` + `bad2` 一起启用 → 好插件保留、坏插件被摘并标记。
2. **admin 可见性**:事故已入 `audit_log`,但**读取/聚合并未实现** —— 门户「插件管理」页应显示「⚠ N 个用户启用失败」。需要新增一个 audit 读取方法(adapter 目前只有写)。
3. **重启前预检**:应用后、重启前跑 `dsh --profile web --dump-config`,可 0 重启拦住配置类错误(未做)。
4. **前端**:`location.reload()` 与被禁用插件的展示,随批次 3(client bundle 改造)一起做。