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/规则与载体/、归档/、接续包_*。
This commit is contained in:
admin committed 2026-09-25 23:06:29 +08:00
1 parent c1461fd958
commit 318430c9e9
27 files changed
+5649 -72

No files matched your search

@@ -0,0 +1,169 @@
# 插件数据面取数口 · **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` 段是一段**运行时注入的 `<style>`**(`document.head.appendChild`,`data-plugin-css="mcn-mobile"`),
选择器全部是 `.mcnNav_*` / `vvModal` 族。已逐个核对它在 suite 里的存在性:
| `.mcnNav_split` | `.mcnNav_panel` | `.mcnNav_resizer` | `.mcnNav_close` | `.mcnNav_btn` | `.mcnNav_mask` | `.mcnNav_modal` | `.mcnNav_stats` | `.mcnNav_input` | `vvModal` |
|---|---|---|---|---|---|---|---|---|---|
| 4 | 3 | 3 | 2 | 12 | 3 | 10 | 2 | 2 | 2 |
⇒ **类名齐备 ⇒ 可整段平移**(把同一段注入进 suite 的 `lib/client.js`,`dataset.plugin` 改成 suite 名)。
⚠️ 重投前**必须先验**本地 suite 与线上 suite 逐字一致(否则会把别的漂移一起发出去);
⚠️ 版本要自增(`0.3.13` → `0.3.14`)并同步 `lib/skills-installer.js` 的 `SUITE_VERSION` 与 `skills/.manifest.json`(该包「版本号唯一真源」口径)。
---
## 六、第 3 件(可选 · MCN 数据面接入改造)—— **未做**
超本轮范围(`lib/db.js` 红线扫描 21 处、且须「先建库再投放」)。按入口 §2 的口径**留给后续棒**。
---
## 七、清理与零残留(夹具全清)
| 项 | 清理后读数 |
|---|---|
| 候选池 | **5**(回到原值,夹具行 0) |
| 插件库 `dshs_pl_*` | **0** |
| `plugin_datastores` | **0** |
| `plugin_data_audit`(夹具) | **0** |
| 临时会话(`poc-curl2`) | **0** |
| 夹具 tgz / 备份目录 | **已删** |
| 服务 | `active` |
⚠️ 夹具在池内存活约 **2 分钟**(`07:56–07:59` 量级),期间登录用户理论上可在「功能管理」看到它 ——
已删除,无残留。**这是本轮唯一一处对线上可见面的临时触碰**,主动记录。
---
## 八、未闭环 / 下一棒
1. 🔴 **MCN 移动端修复的「迁包 + 重投 + 375×667 视觉验收」**(见 §五)—— 本线唯一排定的下一棒。
2. **跨节点形态**:worker 上的实例 → Manager 侧数据面仍缺「实例→控制面」身份通道
(本轮验收是**同机/本机**形态)⇒ 属既有缺口(`DB-03 §六` 自认),未动。
3. **106 未部署**本轮产物(Manager 侧路由,不影响本轮验收)。
## 九、可复现命令
```bash
# 部署(三件产物 + server.js)
scp lib/web/server.js bt-server:/opt/dshs/lib/web/server.js
scp lib/db/plugin-data/{runtime,registry}.js bt-server:/opt/dshs/lib/db/plugin-data/
scp lib/web/routes/plugin-data.js bt-server:/opt/dshs/lib/web/routes/
ssh bt-server 'systemctl restart dshs'
# 验收(夹具搭建 + 7 条断言 + 零残留清理)
scp tmp/15棒-夹具/dsh-plugin-pdprobe-0.0.1.tgz bt-server:/var/lib/dshs/business-plugins/
scp tmp/15棒-夹具/pd-probe-47b.mjs bt-server:/tmp/ # 或第 15 棒工作区 tmp/15棒-夹具/
ssh bt-server '/usr/local/bin/node /tmp/pd-probe-47b.mjs'
ssh bt-server 'bash -s' < tmp/15棒-夹具/remote-cleanup.sh
```