Files
dsh_ai1net_server/交付物/移动端修复迁包与投放-20260925.md
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

12 KiB
Raw Permalink Blame History

移动端修复「迁包 → 重投 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"语义。

五、复现命令(第三方可照抄)

# ① 共享层版本与字节
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 ✅ 未做