Files
dsh_ai1net_server/交付物/跨机错误消息放宽与两项只读取证-20260925.md
T
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

157 lines
14 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.
# 插件投放与分库线 · 第 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 <代码根>`(投前备份)。