156 lines
14 KiB
Markdown
156 lines
14 KiB
Markdown
# 插件投放与分库线 · 第 23 棒(执行棒)落地件
|
||||
|
|
|
|||
|
|
> **工作区**:`E:/ProgramData/AIProject/aliyun-dsh-server` | **日期**:2026-09-25 12:1x–12:4x
|
|||
|
|
> **代码基线**:`D:/github/dsh_shenxian`(生产口径 `dshs`)/HEAD `e6207aa`
|
|||
|
|
> **三件**:① §5-12 跨机错误消息截断放宽(小改,已投两机)② §5-18「清理前必查」只读取证 ③ §5-22 本机 `lib` ↔ 部署面 3+7 差异定性
|
|||
|
|
> **锁**:`插件投放与分库线-第23棒`,域 = `src/supervisor` / `test/remote-spawner.test.mjs` / `aliyun-dsh-server/接续入口_插件投放与分库线_20260922.md`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、第 1 件 · §5-12 跨机错误消息截断放宽(200 → 1200)
|
|||
|
|
|
|||
|
|
### 1-1 改了什么(一件文件 + 一个测试文件)
|
|||
|
|
|
|||
|
|
| 文件 | 改动 |
|
|||
|
|
|---|---|
|
|||
|
|
| `src/supervisor/remote-spawner.ts` | 新增导出常量 `AGENT_ERROR_TEXT_MAX = 1200`(带口径注释:只放宽**保留长度**,⛔ 不动重试次数/退避,⛔ 不改 4xx 不重试语义)|两处 `text.slice(0, 200)` ⇒ `text.slice(0, AGENT_ERROR_TEXT_MAX)`(行 299/303 两个分支:4xx 具名抛与 5xx `lastErr`) |
|
|||
|
|
| `test/remote-spawner.test.mjs` | 新增 **T6/T7/T8**(+3 例) |
|
|||
|
|
|
|||
|
|
### 1-2 单测钉住的不变量
|
|||
|
|
|
|||
|
|
- **T6** 4xx:973 字符正文**逐字完整**进 Manager 侧 `message`(旧口径只留前 200)——正对第 17 棒的现场(真根因只能从 worker journald 补)。
|
|||
|
|
- **T7** 边界:**恰好 1200 字符一个不少**;**多 1 个字符即截到 1200**(钉住"上限还在,不是关了限制")。
|
|||
|
|
- **T8** 5xx 分支:走**同一上限**,且 `calls === 4`(钉住"5xx 仍重试 4 次"这条既有语义**未被本次改动碰到**)。
|
|||
|
|
- ⚠️ T6 首发因**夹具自身超上限**(1453 > 1200)被断言挡住 ⇒ 已改 973 字符夹具;这正是该断言的价值(夹具错了会被抓出来)。
|
|||
|
|
|
|||
|
|
### 1-3 门禁与部署(✅ 判据 ①②③ 全绿)
|
|||
|
|
|
|||
|
|
| 判据 | 读数 |
|
|||
|
|
|---|---|
|
|||
|
|
| ① 放宽到 ~1200 | ✅ `AGENT_ERROR_TEXT_MAX = 1200`,两处分支同用 |
|
|||
|
|
| ② 单测 | ✅ `node --test test/remote-spawner.test.mjs` = **8/8 通过**(原 5 例 + 新 3 例) |
|
|||
|
|
| ③ 门禁 | `npm run build` **rc=0** | `npm test` = **592 tests / 590 过 / 0 败 / 2 跳过**(第 21 棒 589/587/0/2 ⇒ **+3**)|`npm run check:layering` **rc=0 ✅ 无新增违规**(未归类 1 条 = `src/platform-paths.ts`,属既有基线) |
|
|||
|
|
|
|||
|
|
**部署(两机同一文件)**
|
|||
|
|
|
|||
|
|
| 项 | 47 | 106 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 目标 | `/opt/dshs/lib/supervisor/remote-spawner.js` | `/opt/dshs-cluster/lib/supervisor/remote-spawner.js` |
|
|||
|
|
| 投前 md5 | `269d3d20ffceb726298434972a349d56`(20564 B) | **同左(逐字一致)** |
|
|||
|
|
| 投后 md5 | `e9ab05d92a8666459aa486e6893e1bf5`(21529 B) | **同左** |
|
|||
|
|
| 三方一致 | ✅ 本机 = 47 = 106 = `e9ab05d9…` | ✅ |
|
|||
|
|
| 备份 | `/opt/dsh/backups/lib-p23/iZrj99af19cibck1ge93tqZ-pre-p23-20260925-122854.tgz`(sha256 `0089585e6f4e910e…`)+ 同名 `.manifest` + 单文件副本 | `/opt/dsh/backups/lib-p23/VM-0-8-opencloudos-pre-p23-20260925-122855.tgz`(sha256 `d6f71f98efd5191d…`)+ `.manifest` + 单文件副本 |
|
|||
|
|
| 生效 | ✅ `restart dshs` ⇒ `active` · `NRestarts=0` · 门户 **HTTP 200** · journald `dshs listening on http://127.0.0.1:3080` | ⛔ **未重启**(中断在线用户 ⇒ 待窗口)⇒ **待重启面 47 → 48 件** |
|
|||
|
|
|
|||
|
|
- ⛔ **未重启 106**(约束遵守);⛔ 未 commit / push;⛔ `src/web/routes/im.ts` 零改动;⛔ 未手改任何用户 `home` 下的 profile / `.pnpm`。
|
|||
|
|
- 47 重启时旧进程**停止阶段**有一次 `dshs.service: Failed with result 'timeout'`(优雅退出超时),新进程随即 `Started` 并监听 3080、门户 200、`NRestarts=0` ⇒ 判为**生效**,如实记录。
|
|||
|
|
- `dshs-worker`(47)**未重启**:`lib/supervisor/remote-spawner.js` 在 worker 模块图内(`cli.js worker` 静态 import `web/server.js`),但 `RemoteSpawner` 的**唯一实例化点** = `lib/web/server.js:38` 的 Manager 侧装配 ⇒ 对 worker 行为零影响。
|
|||
|
|
- ⚠️ **本棒无"长错误消息"真机读数** —— 该路径需要一次真失败的跨机调用才可观察。⛔ **不宣称运行面验收通过**;本件判据 = 代码面 + 单测 + 部署面三方 md5 一致,均已取到。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、第 2 件 · §5-18「清理前必查」只读取证(⛔ 未执行清理)
|
|||
|
|
|
|||
|
|
**目标 profile**:`/var/lib/dshs/users/cce6d1cd-b376-4304-80f0-0e1c58c9ffde/home/profiles/web`(已启用 `dsh-plugin-mcn-suite` 的那台 admin 实例;运行身份 `dsh-cce6d1cdb376430480f0`,uid 114801)
|
|||
|
|
⛔ 全程只读:未删/未改任何软链、未改 profile、未动 `.pnpm`。
|
|||
|
|
|
|||
|
|
### 2-1 判据① 命中文件与命中处(含行号)
|
|||
|
|
|
|||
|
|
| # | 命中处 | 形态 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 1 | `.pnpm/@dsh-local+business-plugins@file+..+..+.dsh-stage+business-plugins-0.3.25.tgz/node_modules/@dsh-local/business-plugins/lib/client.js:56` | `var React = require("react")` |
|
|||
|
|
| 2 | `.pnpm/@dsh-local+portal-entry@file+..+..+.dsh-stage+portal-entry-0.5.6.tgz_…/node_modules/@dsh-local/portal-entry/lib/client.js:67` | `var React = require("react")` |
|
|||
|
|
| 3 | `.pnpm/@[email protected]_…[email protected]/node_modules/@deepseek-ai/dsh-client-ui-renderer/lib/client.js:10` | `let react = require("react")` |
|
|||
|
|
| 4 | 包自身内部(`react`/`react-dom` 的 `cjs/*`、`[email protected]`、`[email protected]`、`@deepseek-ai/dsh-client-ui-primitives/lib/index.js:8-9`、`@softspark/dsh-file-preview/lib/types/client/*.js:2`) | 这些消费者在**自己的 `.pnpm/<pkg>/node_modules` 自带 react** ⇒ 不经兜底 |
|
|||
|
|
|
|||
|
|
全树剥离"自身包内部 / `dist/assets` 浏览器包 / `lib/client.js`"后**剩余 51 条命中,全部属上述四类**;**四个包的 `main`(`lib/index.js`,服务端半边)零命中**。
|
|||
|
|
|
|||
|
|
### 2-2 判据② 「包自己声明的依赖」 vs 「顶层兜底」——**判别器实测**
|
|||
|
|
|
|||
|
|
Node 解析优先级:① `.pnpm/<pkg>/node_modules` ② `.pnpm/node_modules`(提升层)③ `<profile>/node_modules`(**顶层兜底所在**)。以该实例 uid 运行 `require.resolve`(只解析、不执行模块):
|
|||
|
|
|
|||
|
|
| 解析起点 | `react` 落点 |
|
|||
|
|
|---|---|
|
|||
|
|
| `<profile>` 本身 | `<shared>/dsh-plugin-mcn-suite/node_modules/.pnpm/[email protected]/node_modules/react/index.js` |
|
|||
|
|
| `<profile>/node_modules/dsh-plugin-mcn-suite` | ↑**同一落点**(自己的 store) |
|
|||
|
|
| `<shared>/dsh-plugin-mcn-suite` | ↑**同一落点**(自己的 store) |
|
|||
|
|
| `@dsh-local/business-plugins` | ↑**同一落点(⇒ 本次解析经顶层兜底链)** |
|
|||
|
|
| `@dsh-local/portal-entry` | ↑同(经兜底) |
|
|||
|
|
| `@deepseek-ai/dsh-client-ui-renderer` | ↑同(经兜底) |
|
|||
|
|
|
|||
|
|
- **包自己声明的依赖**:共享层 `dsh-plugin-mcn-suite/package.json` 第 **32–34** 行声明 `react` / `react-dom` / `xlsx`,其 `.pnpm` 树内自带三件 ⇒ 解析**不经过**顶层兜底。
|
|||
|
|
- **顶层兜底**:只有那 3 个 `.pnpm/<pkg>/node_modules` 里**没有** `react` 的消费者(① 优先级落空、② 提升层 `.pnpm/node_modules` 里三件**都没有**)⇒ 解析落到 `<profile>/node_modules/react` ⇒ **兜底链确实在解析路径上**(实测三处落点全部 = 兜底链的实解目标)。
|
|||
|
|
- 兜底目录自身现状:`.dsh-module-fallback/node_modules/` 内**只剩 3 条链**(`xlsx`/`react`/`react-dom` → 共享层 `dsh-plugin-mcn-suite/node_modules/*`),另有 4 个**空目录**(`@deepseek-ai`/`@shikijs`/`@types`/`@ungap`,各 0 项)= 历史残留。
|
|||
|
|
- 三条链的字节级 mtime = **09-25 10:14:57**(⛔ 非 §5-18 记的 09-08;09-08 是**目录** mtime)⇒ 近两日内被平台装配动作刷新过(真值以本行实测为准)。
|
|||
|
|
|
|||
|
|
### 2-3 判据③ 结论句
|
|||
|
|
|
|||
|
|
> **结论 = 有真依赖,但依赖方全在「浏览器半边」;Node 运行期无消费者。**
|
|||
|
|
|
|||
|
|
依据:3 个命中者全是 `lib/client.js`(浏览器半边)——
|
|||
|
|
- `@dsh-local/business-plugins/lib/client.js` 头部原文:`dsh client bundles 以普通脚本形式执行,向 window.__ModuleLoader__ 注册 factory;首次 import 时惰性物化 factory(require)`
|
|||
|
|
- `@deepseek-ai/dsh-client-ui-renderer/lib/client.js` 首行:`window.__ModuleLoader__.load({ id: …, factory: (require) => { … } })`
|
|||
|
|
|
|||
|
|
⇒ 它们的 `require("react")` 由**浏览器侧模块表(`window.__ModuleLoader__`)**满足,**不读 `node_modules` 磁盘**。加一条反向证据:`grep -rln "__ModuleLoader__" /opt/dshs/lib` = **0 文件**(平台服务端代码里根本没有这个加载器)⇒ 服务端不解析 client bundle 的 `require`。
|
|||
|
|
|
|||
|
|
**边界(哪些没判到)**
|
|||
|
|
1. 只核了**这一个 profile**(`profiles/web`)与共享层 4 个包;另一台 profile `9f3a1c47-…`(uid 100010)**没有**这三条链(`ABSENT`),故不在讨论范围。
|
|||
|
|
2. ⛔ 未展开 `@deepseek-ai/dsh` 官方包的客户端宿主实现(只读未做)——若它在服务端预解析 client bundle 依赖,结论需修正。
|
|||
|
|
3. 结论对**当前这一版 profile 内容**成立;profile 重建 / 插件版本升级后需重测。
|
|||
|
|
4. ⛔ 未做"删掉链再跑"的 A/B(会改实例解析面,属清理动作)。
|
|||
|
|
|
|||
|
|
### 2-4 给拍板的输入(⛔ 本棒不清理)
|
|||
|
|
|
|||
|
|
清理的**实际影响面** = 只动 3 个浏览器半边包在 **`require.resolve` 语义**上的可见性;Node 运行期无消费者 ⇒ 风险**低**,但非零(见边界 2、3)。另:这三条链目前每个装配周期都要被 `quarantineSharedLayerLinks` 临时摘一次(第 20 棒的修法),清掉可省这一步 —— 但**该修法本身已保证装配不受影响**,所以"省一步"不构成必须清理的理由。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、第 3 件 · §5-22 本机 `lib` ↔ 部署面 3+7 差异定性(⛔ 未投任何件)
|
|||
|
|
|
|||
|
|
> 本机 `lib` = **09-25 12:23 构建**(本轮 `npm run build` 产物)/两机部署面 = **IM 版 09-23 20:59**(两机逐字相同)。
|
|||
|
|
|
|||
|
|
### 3-1 ① 那 7 件是否被引用 ⇒ 死件 / 活件
|
|||
|
|
|
|||
|
|
| 件 | 两机现状 | 部署面引用面 | 判定 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `im/gateway-token.js` `.d.ts` `.js.map` | **三件全缺** | `grep -rn gateway-token <lib>/**/*.js` = **0 命中** ⇒ 部署面的旧版 `web/routes/im.js` 不引用它 | **死件**(当前无引用者)✅ **不是活缺口** |
|
|||
|
|
| `web/essential-plugins.d.ts` `.js.map` | 缺(**`.js` 两机在**:6606 B / 09-25 10:45:57) | 两个引用者已能解析:`web/routes/admin.js:10`、`web/routes/business-plugins.js:20` (`from '../essential-plugins.js'`) | **纯声明/映射边车**(`runtime` 零影响) |
|
|||
|
|
| `web/routes/plugin-data.d.ts` `.js.map` | 缺(**`.js` 两机在**:15523 B / 09-25 08:04:57) | 引用者已能解析:`web/server.js:55` (`from './routes/plugin-data.js'`) | 同上 |
|
|||
|
|
|
|||
|
|
🔴 **一条必须记住的耦合**(本件新出):**本机** `lib/web/routes/im.js:42` 已 `import … from '../../im/gateway-token.js'` ⇒ 新版 `im.js` 与 `im/gateway-token.*` **必须同批投**;⛔ 单投 `im.js` 会让实例在启动期 `ERR_MODULE_NOT_FOUND`。当前两机构成"旧 `im.js` + 无 `gateway-token`"的自洽态,**没有人处于半投状态**。
|
|||
|
|
|
|||
|
|
### 3-2 ② 3 件运行时 `.js` 谁更新
|
|||
|
|
|
|||
|
|
| 件 | 本机(12:23 构建) | 两机(09-23 20:59) | 谁更新 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `im/backends/gateway.js` | 16241 B | 12077 B | **本机** |
|
|||
|
|
| `im/connection-backend.js` | 4154 B | 3342 B | **本机** |
|
|||
|
|
| `web/routes/im.js` | 47262 B | 36738 B | **本机** |
|
|||
|
|
|
|||
|
|
- 两机三件**逐字相同**(md5 相同、mtime 均 `2026-09-23 20:59:31`)⇒ 两机部署面 = **同一版 IM 构建**(scp 保留源 mtime,故该时间 = **源构建时间**,非投放时间)。
|
|||
|
|
- 代码仓 `git log` **无法作区分判据**:IM 面整体落在同一 commit `e6207aa`(`chore(仓库对齐): 文档库结构治理 + IM/插件线落地`)⇒ **以构建时间 + 体积为判据**。
|
|||
|
|
- 差异成因 = 本机含 IM 线 09-23 之后的改动,含本件新出的 `gateway-token` 抽取。
|
|||
|
|
|
|||
|
|
### 3-3 ③ 结论表与建议
|
|||
|
|
|
|||
|
|
| 归类 | 件 | 本线可否补 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **IM 线未投放件**(⛔ 不归本线) | `im/gateway-token.{js,d.ts,js.map}`(3 件,须与新版 `im.js` 同批) | ⛔ **不补**(单投即事故;同批属 IM 线投放面) |
|
|||
|
|
| **IM 线未投放件**(⛔ 不归本线) | 3 件运行时 `.js` 的新版本 | ⛔ **不补**(`src/im/**` + `src/web/routes/im.ts` 属 IM 线 lane,且 `im.ts` 有冻结口径) |
|
|||
|
|
| **声明/映射边车(死件)** | `web/essential-plugins.{d.ts,js.map}`、`web/routes/plugin-data.{d.ts,js.map}`(4 件) | 可补但**无收益**:两机运行面为纯 node,**无 TS 消费者**;`.js.map` 只影响调试栈 ⇒ **建议不投**(投 = 制造 4 件永远用不上的文件) |
|
|||
|
|
|
|||
|
|
> **本线无"部署面活缺口"** ⇒ 第 3 件**无需任何落地动作**;后续若要投,只能由 **IM 线**按"`im.js` + `gateway-token.*` 同批"排棒。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、卫生与未闭合项
|
|||
|
|
|
|||
|
|
**卫生(逐条遵守)**:⛔ 未 commit / push |⛔ `src/web/routes/im.ts` 零改动 |⛔ 未重启 106 |⛔ 未手改用户 `home` 下 profile / `.pnpm` |⛔ 未清 `.dsh-module-fallback` 软链(只取证)|⛔ 未改 `D:/github/dsh_shenxian/lib` 之外的别线产物 |✅ 47 `/tmp` 无残留(探针脚本已 `trap` 清理且已复核)|✅ 取证脚本全部落在本工作区 `tmp/`。
|
|||
|
|
|
|||
|
|
**未闭合**
|
|||
|
|
1. 🔴 **106 待重启面 = 48 件**(原 47 件 + 本轮 1 件)⇒ 生效待用户给窗口。
|
|||
|
|
2. ⚠️ 第 1 件**无真机长错误消息读数**(需一次真失败的跨机调用)⇒ 未宣称运行面验收。
|
|||
|
|
3. ⚠️ 第 2 件结论有 4 条边界(见 §2-3),其中"官方客户端宿主是否在服务端预解析依赖"未展开。
|
|||
|
|
4. ⚠️ 第 3 件两机 `lib` 树里的 **33 条部署备份残留**仍未处置(第 22 棒记,属"部署卫生"独立议题)。
|
|||
|
|
|
|||
|
|
**复现命令**:`tmp/p23-item2-probe{,2,3,4,5}.sh`(第 2 件五批)|`tmp/p23-item3-probe.sh`(第 3 件,两机同一脚本)|`tmp/p23-deploy-backup.sh <代码根>`(投前备份)。
|