# 插件数据面取数口 · **47 真机验收** + 抓出接线缺陷(2026-09-25 · 第 15 棒) > **一句话**:把第 14 棒落地的「运行时取数口」部署到 47 并**真机跑通**(四拒一空 + 回读 **7/7 PASS**), > 过程中抓出**一条会让取数口对全部插件失效的接线缺陷**(已修 + 已复验); > 第 2 件(MCN 移动端视觉验收)**未通过 —— 前提不成立**:移动端修复落在**未投放的包**里。 > **归属线**:插件投放与分库线|**会话**:插件投放与分库线-第15棒|**交接单**:无(本棒为执行棒) --- ## 一、本轮做了什么 | # | 事项 | 结果 | |---|---|---| | 0 | 开工门禁 + 域锁 | preflight 判【D】2 个 → 归类后抢锁成功(域 `aliyun-dsh-server/接续入口_插件投放与分库线_20260922.md` + `aliyun-dsh-server/.workbuddy`) | | 1 | 部署取数口到 47 | ✅ 5/5 md5 逐字一致 + `restart dshs` + 服务 active、门户 200 | | 2 | 数据面取数**真机验收** | ✅ **7/7 PASS**(四拒 + 一空 + insert→find 回读;归属列真值直连核) | | 3 | 抓出并修复**接线缺陷** | ✅ 已修 `src/web/routes/plugin-data.ts` + 重投 + 复验 | | 4 | MCN 移动端视觉验收 | 🔴 **未通过 —— 前提不成立**(修复不在已投放的包里,详见 §五) | | 5 | 夹具清理 | ✅ **零残留**(池 5 / 插件库 0 / 台账 0 / 会话 0 / tgz 已删) | --- ## 二、部署面证据(47 · `/opt/dshs`) | 文件 | 47 与本地 md5 | 说明 | |---|---|---| | `lib/web/server.js` | `fcbbb9b3476499ba2d97841f0e6ebaed` | 与 47 原版**只差 7 行**(就是 `pluginDataRoutes` 的 import + `register` +注释)—— 已逐行 diff 确认 | | `lib/db/plugin-data/runtime.js` | `45daf7c5b2b6163e3d1e1fd110dca36d` | 新增 | | `lib/db/plugin-data/registry.js` | `44d3b5c424f7bc27a930454a945f6f06` | 新增 | | `lib/web/routes/plugin-data.js` | `a03394088f4ad90e571d4b5ede7a5f95` → 修复后 `fac8f4dfe0d5710705de6d614275c77e` | 新增 → 修复 | | `sdk/im-plugin-client/index.mjs` | `4a1a160156a90cc07696332fcc99d0cd` | 新增(落 `/opt/dshs/sdk/`) | - **备份**:`/opt/dsh/backups/lib-p15/47-pre-p15-20260925-074345.tgz`(投放前)。 - **重启**:`systemctl restart dshs` → `active`、`NRestarts=0`、门户 `200`。 - **部署前先做了漂移比对**(避免把别的棒的东西带上线):`business-plugins.js` / `worker/agent.js` / `leased-spawner.js` / `config.js` 四件**与本地逐字一致** ⇒ 只投必要项。 - ⚠️ **106 未部署**(本轮范围 = 47;取数口是 Manager 侧路由 ⇒ 106 不影响本轮验收)。 --- ## 三、真机验收读数(47 · 调用者 = `guest`,插件身份头 = 包名) 夹具 = 一次性探测包 `dsh-plugin-pdprobe`(user 域 + room 域各一表;声明过平台**真校验器** → `level=ok`)。 | 断言 | 期望 | 实测(HTTP 200 + 契约体) | |---|---|---| | ① 写他人 `user_id` | 拒 | `ok:false, reason:bad-args`,detail=`不得写入他人的 user_id(归属列由内核注入)` | | ② room 表缺 `room_id` | 拒 | `ok:false, reason:bad-args`,detail=`房间维度表必须带 room_id` | | ③ 缺主键列 | 拒 | `ok:false, reason:bad-args`,detail=`缺少主键列「probe_key」(它是本表的行标识)` | | ④ 带 `created_at` | 拒 | `ok:false, reason:bad-args`,detail=`审计列「created_at」由内核维护,⛔ 不可自写` | | ⑤ 非成员读 room 表 | **0 行** | `ok:true`,`value:[]` ⇒ **0 行** | | ⑥ `insert` 正常行 | ok | `ok:true` | | ⑦ `find` 回读一致 | 1 行且字段一致 | `ok:true`,`value` 1 行,`note='v1'`、`payload={a:1}` | **归属注入直连核**(不靠响应自证):`p_dsh_plugin_pdprobe_probe_user.user_id` **全部等于** 调用者本人 `4092b965-…`;`probe_room` 行数 = **0**。 **DDL 侧证**(迁移回显):`PRIMARY KEY ("user_id", "probe_key")` ⇒ **`id` = 声明第一列**、`归属列 + 第一列` 复合键 (无代理自增 id)—— 与 `DB-03 §五` 口径一致,**首次在真机上被看见**。 **对照**(负例):给**无声明**的真插件取数 ⇒ `409 plugin-data-unavailable / no-declaration`(不是 500,不是"假装成功")。 --- ## 四、🔴 抓出的接线缺陷(P0 级 · 已修 + 已复验) **现象**:库建好(`state=ready`)、插件身份头合法、鉴权通过 —— 但取数一律 `409 no-declaration`:`插件「dsh_plugin_pdprobe」不在候选池或包内无数据面声明`。 **根因(两个名字空间被混用)**: | 名字空间 | 取值 | 谁在用 | |---|---|---| | **池键 / 台账键** | **原始包名**:`dsh-plugin-pdprobe`、`@dsh-local/storyforge` | `business_plugins.id`、`plugin_datastores.plugin_id`(建库口、门户都用它) | | **`pluginId`(归一化)** | 去 scope + 下划线化:`dsh_plugin_pdprobe` | `pluginIdOf()`;用于**算库名/表名前缀** | 取数口把**归一化后的 id** 交给了 `PluginDataRegistry`,而 registry 内部拿这个值去 `findBusinessPlugin` / `findPluginDatastore` **精确查**这两张表 ⇒ **永远查不到** ⇒ **任何插件**调取数口都只会得到 `no-declaration`(不是这一个夹具的问题)。 **为什么单测没抓到**:`test/plugin-data-runtime.test.mjs` 用**假执行器**,直接构造声明对象, **绕过了路由层的 id 归一化** ⇒ 这条路径只有在真机上才会被走到。 **修法(最小)**:`src/web/routes/plugin-data.ts` 两处 - `resolvePluginPort`:`pluginId: pluginIdOf(pkgName)` → `pluginId: pkgName`(原始包名) - `/plugin-data/status`:查表用原始包名,**对外仍回归一化 `pluginId`**(响应契约不变) **复验**:`npm run build` rc=0|`node --test test/plugin-data-runtime.test.mjs test/im-plugin-client.test.mjs` → **24/24 过** |重投 + 重启 → **7/7 PASS**(同 §三)。 > ⛔ **未 commit / 未 push**(遵守本棒约束);改动留在 `D:/github/dsh_shenxian` 工作树(`plugin-data.ts`)。 --- ## 五、🔴 第 2 件(MCN 移动端视觉验收)—— **未通过:前提不成立** **验收目标**(入口 §2 原文):`D:/dshworkspace/plugin_package/dsh-plugin-mcn/lib/client.js` 的 `mcn-mobile` 样式段 → 375×667 下工作台独占、能退出、无横向溢出、聚焦不缩放、横屏可用。 **真机取证(为什么做不了)**: | 检查 | 结果 | |---|---| | 线上**真身**是哪个包 | 池与共享层里的都是 **`dsh-plugin-mcn-suite` v0.3.13**(`/var/lib/dshs/bundled-plugins/dsh-plugin-mcn-suite`) | | 该真身含修复吗 | **否** —— `lib/client.js` 里 `mcn-mobile` 命中数 = **0**(`mcnNav_split` = 4) | | 本地 suite 含修复吗 | **否** —— 本地 `dsh-plugin-mcn-suite/lib/client.js`(0.3.13 · mtime 09-20 22:12)`mcn-mobile` = **0** | | 修复改在哪个包 | `dsh-plugin-mcn` **v0.1.0**(本地 `lib/client.js` mtime 09-25 05:18)—— 该包**从未投放** | ⇒ **移动端修复改在了「不是线上真身」的包里**。所以"重投插件 + 实例重启"**无从生效**: 线上跑的一直是 suite,它里面没有这段样式。**故本件不做视觉验收,也不宣称通过**(无真机读数不得宣称)。 **整改路径(可机械执行 —— 已核对可移植性)**: `mcn-mobile` 段是一段**运行时注入的 `