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

177 lines
12 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.
# 移动端修复「迁包 → 重投 47/106」+ 真机视觉验收(第 16 棒)
> **工作区**:`E:/ProgramData/AIProject/aliyun-dsh-server`|**线**:插件投放与分库线|**日期**:2026-09-25
> **依据**:`接续入口_插件投放与分库线_20260922.md §2「🎯 本轮动作(第 16 棒)」`
> ⛔ 本棒未 commit / push;⛔ 未改 `src/web/routes/im.ts`;⛔ 未做 MCN 数据面接入改造。
---
## 一、结论(先说判定)
| 件 | 判定 | 一句话 |
|---|---|---|
| 第 1 件 · 迁包 | ✅ **完成** | `mcn-mobile` 样式段已从**从未投放**的 `dsh-plugin-mcn` v0.1.0 整段平移进**线上真身** `dsh-plugin-mcn-suite`,38 个引用类名在目标包内**齐备 0 缺** |
| 第 2 件 · 版本自增+重投 47/106 | ✅ **完成** | 0.3.13 → **0.3.14**;**47 + 106 共享层双双为 0.3.14**(106 由节点拉取机制一轮收敛);池内候选同步、回滚素材在位 |
| 第 3 件 · 375×667 真机视觉验收 | ❌ **未通过(未取到真机读数)** | **不是代码问题,是链路上游断了**:已启用用户的实例仍指向**旧副本**,而平台的「重装配」操作**稳定失败**(两次复现)。⛔ 未宣称通过 |
🔴 **本轮最重要的产出 = 一条可复现的新缺陷**:**共享层更新对「已启用」的用户不生效,且平台没有可用的补救路径** —— 详见 §四。
---
## 二、第 1 件 · 迁包(逐字平移,只改包名一处)
| 项 | 值 |
|---|---|
| 源 | `D:/dshworkspace/plugin_package/dsh-plugin-mcn/lib/client.js` 第 216–268 行(`//#region 移动端 / 窄屏兼容`) |
| 目标 | `D:/dshworkspace/plugin_package/dsh-plugin-mcn-suite/lib/client.js`(插在 `var McnNav_module_css_default` 之前) |
| 改动 | 仅 1 处:`el3.dataset.plugin = "dsh-plugin-mcn"` → **`"dsh-plugin-mcn-suite"`** |
| 体量 | 段 3642 B / 53 行 → 剥行尾后 **3595 B**;目标 401936 → **405531 B** |
| 语法 | `node --check` **OK**(Node 22.22.2) |
**三条自证**(脚本 `tmp/p16-port-mobile-20260925.py`,可重跑):
1. **类名齐备**:段内引用类名 **38 个**,目标包内缺失 **0 个** ⇒ 第 15 棒的"可移植性核对表"在**字节级**上被独立复核。
2. **行尾一致**:源段是 **CRLF**、目标包是**纯 LF** ⇒ 已归一(产物 CR = 0)。⚠️ 首版没做归一,产物混入 53 个 CR,**已回滚重做**。
3. **幂等**:目标已含 `data-plugin-css="mcn-mobile"` ⇒ 脚本直接 SKIP。
4. **可回滚**:改前副本 `tmp/p16-backup-20260925/dsh-plugin-mcn-suite-client.js.bak`(401936 B)。
---
## 三、第 2 件 · 版本自增与重投(47 + 106)
### 3.1 重投前对账("别把别的漂移一起发出去")
**改前副本 md5 = `0955f6b8b70f0c1357d29395643fdeb6` = 47 共享层线上 `lib/client.js` md5** ⇒ 本地 suite ≡ 线上真身,**逐字无漂移**。
全包逐文件比对(323 项,按路径建索引,排除 `node_modules`):
| 差异 | 内容 |
|---|---|
| 内容不同 | **仅 `lib/client.js`**(就是本棒的改动) |
| 仅本地 / 仅线上 | `tests/smoke.mjs`(本地有、不进包)/`pnpm-lock.yaml`(服务端安装时生成) |
⇒ **漂移面 = 1 个文件且是本棒的预期改动**。
### 3.2 打包(以池内 tgz 为基底,只替换 3 个文件)
| 项 | 值 |
|---|---|
| 基底 | `/var/lib/dshs/business-plugins/dsh-plugin-mcn-suite.tgz`(1989680 B,322 文件,顶层 `package/`) |
| 新包 | `tmp/p16-pool/dsh-plugin-mcn-suite-0.3.14.tgz`(**1982036 B,322 文件**) |
| sha256 | `3ca65153605b3c48f7de628d4754dc872b6f4fb7b617e9e57e704eb90bbab61c` |
| 新旧差异 | **只差 3 个文件**:`package/lib/client.js`、`package/package.json`、`package/skills/.manifest.json`;**零增删** |
版本自增两处(该包「版本号唯一真源 = `package.json`」,`SUITE_VERSION` 由运行时读它 ⇒ 无字面量可改):
`package.json` `version` → `0.3.14`(+ 变更史前置一条)|`skills/.manifest.json` → `suite: …@0.3.14`、技能 `version: 0.3.14`。
⚠️ `files/bytes/sha256` 三项**未动**(技能载荷未变,只动版本字段)。
🔴 **行尾坑(本棒第二次踩)**:Windows 上用 Python 写文本默认把 `\n` 转成 `\r\n` ⇒ 首版把 CRLF 写进了 `package.json` 与清单,**已回滚重做**(写时显式 `newline="\n"`,产物 CR = 0)。
### 3.3 上传递交(走平台正规口,非手改文件)
`POST /api/plugins/business`(admin 会话)→ **http=200**:`version 0.3.14`、`replaced: true`、`trustedOverride: false`、
兼容性裁决 `level: ok`(`platformPkgs 240`、`sawPlatformRef true`);安全检测**只出 warning 未拦**(`[email protected]` / `network-egress@lib/client.js` / …)。
### 3.4 铺共享层(两段式:先预演、后确认)
* 预演(`confirm` 缺省)→ `conflict: true`,并**顺带暴露一条台账缺陷**(见 §四 P2)。
* 确认 → `action: "replaced"`、`version 0.3.14`、`fileCount 657`、新 `treeSha256 f414df79…`、
回滚素材 `/opt/dsh/backups/plugins/dsh-plugin-mcn-suite/0.3.14.tgz`。
**47 实况复核**:`lib/client.js` md5 = `d387089bd28c91cdbe46056e21288cbd`(= 本地新文件)、`mcn-mobile` 命中 **2 处**、`package.json` = 0.3.14、文件数 **657**(含 `pnpm install --prod` 重建的 `node_modules`)。
### 3.5 106 同步(走节点拉取机制,非手工 scp)
106 上手动触发一轮:`POST http://127.0.0.1:19000/shared-layer/pull`(带 `x-dsh-agent-token`)
→ `{"stale":["dsh-plugin-mcn-suite"],"pulled":["dsh-plugin-mcn-suite"],"failed":[]}`
→ 106 盘上 **0.3.13 → 0.3.14**、`client.js` md5 = `d387089…`。**一轮收敛**。
### 3.6 回滚
| 素材 | 位置 | 说明 |
|---|---|---|
| 上一版(内容 = 0.3.13) | 47 `/opt/dsh/backups/plugins/dsh-plugin-mcn-suite/0.3.9.tgz`(md5 `f47a07b9a2a889770e35315e22156e90`) | ⚠️ **文件名叫 0.3.9、内容是 0.3.13**(见 §四 P2) |
| 本棒投出的版本 | 同上目录 `0.3.14.tgz` | 内容是**新**包(share 时补落的语义错位,见 §四 P2) |
| 回滚动作 | 用上一版 tgz 重走上传递交 + `POST /api/plugins/business/share {confirm:true}` | 共享层目录被整体替换 ⇒ 指向它的用户下一次 reconcile 生效 |
---
## 四、抓出的缺陷(⛔ 均未擅自修,按纪律只报告)
### 🔴 P0 —— 已启用插件拿不到共享层新版本,且平台无可用补救路径
**判据链(全部可复现)**:
1. **实例依赖形态**:admin 实例 profile 的 `dsh-plugin-mcn-suite` 声明为
`file:/var/lib/dshs/users/cce6d1cd-…/home/.dsh-stage/dsh-plugin-mcn-suite.tgz`
—— 是**一份 `.dsh-stage` 副本**(1989680 B = 旧版内容,09-20),**与共享层无关**。
⇒ **共享层升到 0.3.14 对它零影响**,这与第 15 棒"修复落在没投放的包里"是**同一病根的下一层**。
2. **正规补救路径 = 关掉再开**(`apply` 是全量裁决:已启用的项再发 `enabled:true` 判 `noop`,不会重装配)。
3. **该路径稳定失败**:两次全量「关闭」均返回 `taskId`,各约 **52 s** 后 `{"status":"failed","stage":"失败","error":"安装失败,请重试或联系管理员"}`:
* 任务 `0df1ea39ed3026c4`(08:32:38 → 08:33:30)
* 任务 `3eb4b72b73373e07`(08:34:42 → 08:35:34)
`/mine` 复核**仍为 `enabled: true`** ⇒ 平台**没有**把用户切到新版本的可用手段。
⚠️ 已核实**不是**"暂存包缺失"(4 个 `.dsh-stage` 包全部在位)。
4. **失败原因不可观测(第二条缺陷)**:`journalctl -u dshs` 在失败时段**只打了 HTTP 请求行**,pnpm 的 stderr / 装配失败原因**未落任何日志** ⇒ 用户与运维都只能看到"请重试",**无法定位**。本棒的根因**因此未能定位**。
**影响面**:凡是**已经启用**过某插件的用户,都停留在启用当时那份副本上 ⇒ 平台的「插件更新」对存量用户**不成立**;共享层只对"新启用"的用户有效。这条直接命中本线目标 P2("插件更新的情形要有定义")。
**建议落点(下一棒)**:① 给 `applyPlugins` 的 pnpm 失败路径补**可观测性**(stderr 落日志 + 失败原因回传到任务对象);② 定位失败根因(候选:47 的 egress 护栏挡住了平台自身的 registry 解析 / pnpm store 锁竞争);③ 重装配要能把 `file:` 形态换成 `link:`。
### 🟡 P1 —— 移动端真机视觉验收**仍未取得**(第 15 棒遗留,本棒被上面那条挡住)
**"能不能真渲染"这件事本棒已做成判定,且结论是"能"**:
| 探针 | 读数 |
|---|---|
| 47 侧浏览器 | ❌ 无(`chromium/chrome/firefox` 全无、无 playwright/puppeteer 缓存) |
| **本机侧浏览器** | ✅ **有**:`C:/Program Files/Google/Chrome/Application/chrome.exe`(真 Chromium 内核) |
| 平台门户 | ✅ `https://ai1net.com/portal.html` → 200 |
| 实例 UI | ✅ `https://ai1net.com/u/<uid>/dsh/` → **200**,且平台**自动下发实例凭据** `Set-Cookie: dsh-auth-…` |
| 实例地址 | `https://admin.ai1net.com/?token=…`(`/api/dsh/status` 直出,可直接作为渲染入口) |
⇒ **路径已通,只差"实例上跑的是新版本"这一步**。修好 P0 即可直接补做五条读数(无需再做任何环境建设)。
### 🟡 P2 —— 共享层清单与磁盘不同步 + 回滚素材命名语义错
* **清单 vs 盘**:本次预演读到的清单是 `version: "0.3.9"`、`tgzSha256 d14cd5fe…`、`treeSha256 cb1c73fb…`,而**盘上实际是 0.3.13 内容**(`package.json version = 0.3.13`、`client.js` md5 与池内 0.3.13 tgz 一致)。
⇒ `authoritativeShared()` 拿这份 `treeSha256` 做节点对账 ⇒ **节点会永远判 `stale` / 反复重拉**。本棒 put 新版本后该条**自愈**(新 `treeSha256` 是现算的),但机制上的不同步来源未查明。
* **回滚素材命名**:`doShare` 里补落的备份路径用 **incoming 版本号** 命名、内容取 **incoming 的 tgz**
(`backupTgz = <backupDir>/plugins/<flat>/<version>.tgz` 且 `copyFileSync(existing.tgzPath, …)`,而 `existing` = **上传后**的池条目)
⇒ 产出的 `0.3.14.tgz` **是"新包的自拷贝",不构成"回到上一版"的素材**;真正可回滚的那份躺在 `0.3.9.tgz` 里(名字与内容不符)。
⇒ 建议:备份应记**被替换**的那一版,或名字带上"replaced"语义。
---
## 五、复现命令(第三方可照抄)
```bash
# ① 共享层版本与字节
ssh bt-server 'grep -m1 "\"version\"" /var/lib/dshs/bundled-plugins/dsh-plugin-mcn-suite/package.json'
ssh bt-server 'md5sum /var/lib/dshs/bundled-plugins/dsh-plugin-mcn-suite/lib/client.js' # 期望 d387089bd28c91cdbe46056e21288cbd
ssh test106 'md5sum /var/lib/dshs/bundled-plugins/dsh-plugin-mcn-suite/lib/client.js' # 应与 47 同值
# ② 池内候选
ssh bt-server 'ls -la /var/lib/dshs/business-plugins/dsh-plugin-mcn-suite.tgz' # 期望 1982036 B
# ③ 实例依赖形态(P0 判据)
ssh bt-server 'python3 -c "import json;print(json.load(open(\"/var/lib/dshs/users/cce6d1cd-b376-4304-80f0-0e1c58c9ffde/home/profiles/web/package.json\"))[\"dependencies\"][\"dsh-plugin-mcn-suite\"])"'
# 期望输出 file:…/.dsh-stage/dsh-plugin-mcn-suite.tgz ← 即「未指向共享层」
# ④ 重装配失败复现(需要 admin 会话):全量 apply 一条 enabled:false,随后轮询
# GET /api/plugins/mine/task/<taskId> → 约 52 s 后 status=failed / "安装失败,请重试或联系管理员"
```
**本棒用到的临时脚本**(均在 `tmp/`,可重跑):`p16-port-mobile-20260925.py`(迁包)· `p16-build-0314.py`(改版打包)· `p16-hash-list.py` / `p16-cmp.py`(逐文件对账)· `p16-pull-106.sh`(触发节点拉取)· `p16-cleanup.sh`(清临时会话与临时物)。**只读**工具:`p16-reach.sh`(连通性探路)。
---
## 六、清理与合规
| 项 | 状态 |
|---|---|
| 临时 admin 会话 | ✅ **已删**(`DELETE FROM sessions WHERE user_agent='p16-mksess'` ⇒ rowCount 1,复核残留 **0**) |
| 47 `/tmp` 临时物 | ✅ **零残留** |
| 实例半状态 | ✅ **无**:实例 running、profile 依赖**未被改动**(两次失败均未落盘) |
| 47/106 服务 | ✅ 正常(共享层 0.3.14、池 0.3.14) |
| 装配协议 / `plugin-assembly.ts` | ✅ **零改动** |
| 可见面开关 | ✅ 未开;⛔ 未开任何新口 |
| `src/web/routes/im.ts` | ✅ **零改动**(IM 线 D 单判据 2 冻结遵守) |
| commit / push | ✅ 未做 |