# S3「数据面」落地与三轮回执(第 41 棒 · 执行棒)
> **线**:插件投放与分库线 | **棒次**:第 41 棒(执行棒)| **日期**:2026-09-26
> **会话**:`插件投放与分库线-41`(锁目录实际名 `________________________-41-1377504050`,开始 14:35)
> **执行依据**:规格书 `05-交接单/交接单_包规格书_20260926.md` §〇/§五 A/§五 C | 方案 `交付物/功能打包到基础插件-分步实施方案-20260926.md` **§3.10.5**
> **结论**:🟢 **本棒判据全绿**(建库 → 迁移 → 对账 三轮 + 库名正则 + 负控 + 包内门禁);⚠️ 浏览器级 `v-ui.sh` 未取到(与第 40 棒同因,需客户端线起真壳)。
---
## 一、只读前置(命令 + 原始输出)
| # | 项 | 命令 | 原始输出 |
|---|---|---|---|
| 1 | 锁空闲 | `"$PY" state.py` | `[锁] 空闲 ✅`(+ 入口/收口段;5 条工作线,本条只走插件投放线) |
| 2 | 方案件在位 | `ls -la 交付物/功能打包到基础插件-分步实施方案-20260926.md` | `51364 B` · mtime `2026-09-26 13:29` |
| 3 | 库名形状三处硬编码 | `grep -nE "PLUGIN_DB_PREFIX\|PLUGIN_DB_RE\|PLUGIN_DB_MAX_IDENT" src/db/plugin-data/schema.ts` | `90: PLUGIN_DB_PREFIX='dshs_pl_'`|`100: /^dshs_pl_[a-z][a-z0-9_]{0,40}$/`|`103: PLUGIN_DB_MAX_IDENT=41` |
| 4 | 平台侧建库能力 | ssh 47:`select rolname,rolcreatedb from pg_roles` | `dshs createdb=true`|`postgres createdb=true` |
| 5 | 既有插件库(开工对照) | ssh 47:`select datname from pg_database where datname like 'dshs_pl_%'` | `dshs_pl_dsh_plugin_mcn_suite`|`dshs_pl_storyforge`(**`dshs_pl_ai1net` 不存在** ⇒ 本棒要建) |
| 6 | 台账两表在位 | ssh 47:`to_regclass(...)` | `plugin_datastores` ✅ `plugin_data_audit` ✅(列:`plugin_id,db_name,state,schema_version,plan_hash,last_error,created_at,updated_at`) |
| 7 | 平台 lib 产物 | ssh 47:`ls /opt/dshs/lib/db/plugin-data` | `datastore.js` / `diff.js` / `registry.js` / `runtime.js` / `schema.js`(**可直接 import 复用**) |
| 8 | 门禁与尺子 | `ls 交付物/验证脚本/` | `v-baseline.sh` `v-neg.sh` `v-plugin.sh` `v-ui.sh`(S0 四把,⛔ 本棒未改一行) |
---
## 二、四件事 · 逐条原始输出
### ① 声明数据面(包内)
**载体**(🔴 关键口径):`package.json` 的 `dsh.data.schema` **显式指向** `./dsh.data.yaml`
—— 平台端 `parseDeclFromDir` **不自动探测**(`schema.ts:695` 原文:⛔ 不自动探测 `
/dsh.data.yaml`)。
新增/改动文件(**全在新包内**):
| 文件 | 动作 | 说明 |
|---|---|---|
| `dsh.data.yaml` | 新增 | 8 张表 / 29 列 / 5 个模块段;格式照 `DB-03 §三` |
| `lib/data-limits.js` | 新增 | **插件侧唯一把关人** `COLUMN_MAX_BYTES=16384` + 按类型上限 + 两个纯函数(零 DB 动作) |
| `package.json` | 改 | `dsh.data.schema` + `exports["./data-limits"]` + 🔴 `files` 补 `dsh.data.yaml` |
| `scripts/build.mjs` | 改 | 新增门禁 ①d(数据面声明);组装清单补 `dsh.data.yaml` |
| `README.md` | 改 | 新增「数据面(S3)」章 + 红线自查命令 |
🔴 **`files` 与 `build.mjs` 的组装清单是两处**,漏任一处都是"分发出去少了声明件"的静默故障 ⇒ 已两处同改。
**平台解析器裁决**(ssh 47 · `/tmp/p41/decl`):
```
✅ 声明解析:level=ok origin=yaml-file 库名=dshs_pl_ai1net schemaVersion=1 表=8
── 模块段覆盖(⛔ 不建"无模块段"的表)
✅ M1 多租户(tenant_)2 张:tenant_orgs → p_ai1net_tenant_orgs | tenant_bills → p_ai1net_tenant_bills
✅ M2 模型管理(model_)2 张:model_configs → p_ai1net_model_configs | model_creds → p_ai1net_model_creds
✅ M3 技能插件管理(plugin_)1 张:plugin_registry → p_ai1net_plugin_registry
✅ M4 覆盖网络界面(net_)1 张:net_zone_refs → p_ai1net_net_zone_refs
✅ M5 IM 插件侧(im_)2 张:im_rooms → p_ai1net_im_rooms | im_messages → p_ai1net_im_messages
✅ 库名 dshs_pl_ai1net(长度 14 ≤ 48)
· 库名正则:匹配 ^dshs_pl_[a-z][a-z0-9_]{0,40}$
· 声明结构摘要 declDigest=b46b4983b8da6fc0efa97051ab29793c23eab5a644c4906a96ad18021ba50892
```
`rc=0`
### ② 先建库(平台侧)
**预演**(⛔ 不动任何库):
```
✅ 库存在=false | 计划项=17 | 禁止项=0
· planHash=1e587f9a2f0b06727e17d937fbef6cc3e86dd817618d2bf8b2c73f614238579e
· 计划:create_database dshs_pl_ai1net / create_table ×8 / add_index ×8
```
`rc=0`
**建库**(🔴 **动手前说明**:这是生产变更 —— 在 47(开发环境服务器)建插件库 `dshs_pl_ai1net` + 落台账;
**可逆**:回滚口径 = 改名冻结,⛔ 不 DROP。未重启控制面、未中断任何用户):
```
── 建库(先建库再投放 · 单语句非事务 · 幂等)
✅ CREATE DATABASE dshs_pl_ai1net OWNER dshs ENCODING UTF8 TEMPLATE template0 ⇒ created
✅ 台账已写(state=created)+ 审计一条
```
`rc=0`
### ③ 迁移(单入口 · 按模块分步 · 每模块独立事务)
```
── 迁移(单入口 · 按模块分步 · 每模块独立事务)
· 本棒执行模块:M1 → M2 → M3 → M4 → M5
── M1 多租户:表 2 | 计划 4 | 执行 4 | 失败 0 | 42ms
── M2 模型管理:表 2 | 计划 4 | 执行 4 | 失败 0 | 34ms
── M3 技能插件管理:表 1 | 计划 2 | 执行 2 | 失败 0 | 26ms
── M4 覆盖网络界面:表 1 | 计划 2 | 执行 2 | 失败 0 | 29ms
── M5 IM 插件侧:表 2 | 计划 4 | 执行 4 | 失败 0 | 30ms
── 迁移后现状:exists=true schemaVersion=1 表=9/8
✅ 台账已写(state=ready,plan_hash 保留)
```
`rc=0`(完整逐条输出在 47 的 `/tmp/p41/migrate.out`)
⚠️ **`表=9/8` 的解释**(⛔ 不是异常):9 = 声明 8 张 + 内核台账表 `p_meta_schema` 1 张
(`executePlan` 每次都会建它并写版本号 —— 它是 `diff` 判 `ready` 的依据)。
**「每模块可单独回滚」的取证**(事务内演练 · 零残留):
```
── 单模块回滚演练(M1 多租户)—— **事务内**,结束后 ROLLBACK,库内零残留
· 本模块表:p_ai1net_tenant_orgs, p_ai1net_tenant_bills
· 其他模块表 6 张(演练必须不碰它们)
✅ 事务内改名生效:p_ai1net_tenant_orgs ⇒ p_ai1net_tenant_orgs__rbprobe(命中 1 行 pg_tables)
✅ ROLLBACK 后表集合与演练前逐字相同(9 张)⇒ 单模块 DDL 可独立回滚且零残留
```
`rc=0`
### ④ 对账(`pg_database` 的 `dshs_pl_*` × 控制面台账)
```
── 对账(pg_database 的 dshs_pl_* × 控制面台账)
✅ pg_database=3 台账=3
· 库侧:dshs_pl_ai1net, dshs_pl_dsh_plugin_mcn_suite, dshs_pl_storyforge
· 台账:dshs_pl_ai1net, dshs_pl_dsh_plugin_mcn_suite, dshs_pl_storyforge
✅ 两集合一致(差集双空)
✅ 本棒的库 dshs_pl_ai1net 在 pg_database 里
```
`rc=0`
---
## 三、负控(🔴 本棒的取证核心)
**改一处声明且不回读** —— 选 `model_configs.params_json` 的 `maxBytes: 8192 → 16384`
(**刻意选"改动仍在常量上限内的值"**:这样包内门禁不会红,**只有回读能查出**)。
| 态 | 动作 | 包内门禁 | 平台侧 `readback` |
|---|---|---|---|
| **改坏** | `8192 → 16384`,⛔ 不回读登记 | ✅ **仍绿**(`maxBytes ≤ 16384` 仍成立;只 `DECL_DIGEST` 变 `ccb81776…`) | ❌ **rc=1 红** —— 快照 `b46b4983…` ≠ 现读 `2b28a76b…` ⇒ `声明被改过却没回读登记(负控即命中此条)` |
| **改回** | 恢复 `8192`,回读登记 | ✅ 绿 | ✅ **rc=0 绿** —— 两侧同为 `b46b4983…` |
**改坏/改回的字节级证据**:`dsh.data.yaml` sha256
`460c27d1…6350`(改前)→ `e4973b3e…e3b3`(改坏)→ `460c27d1…6350`(改回,**与改前逐字相同**)
⇒ **结论**:① 回读这一步**真的在起作用**(不回读就查不出)② 包内门禁**不能**替代回读(改坏时它全绿)
③ 声明有**两处表达**(yaml 的 `maxBytes` 意图 × `lib/data-limits.js` 的常量),改任一处都必须回读另一处。
---
## 四、判据总表(退出码即结论)
| # | 判据 | 命令 | rc | 结论 |
|---|---|---|---|---|
| 1 | **建库** | 执行器 `create` | 0 | ✅ 绿 |
| 2 | **迁移**(5 模块分步,0 失败) | 执行器 `migrate --modules M1,…,M5` | 0 | ✅ 绿 |
| 3 | **对账**(差集双空) | 执行器 `reconcile` | 0 | ✅ 绿 |
| 4 | **库名正则 + 总长** | 执行器 `decl` / `plan` | 0 | ✅ `dshs_pl_ai1net` 匹配 `^dshs_pl_[a-z][a-z0-9_]{0,40}$`、长度 14 ≤ 48 |
| 5 | **负控**(改坏不回读 ⇒ 红;改回 ⇒ 绿) | 执行器 `readback` | 1 / 0 | ✅ 命中(见 §三) |
| 6 | **包内门禁** | `node scripts/build.mjs --check-only` | 0 | ✅ 绿(8 表 / 29 列 / 模块段齐 / maxBytes 合规 / 回读摘要) |
| 7 | 红线自查(包内零 DB 动作) | `grep -rEn "CREATE[ ]TABLE\|…" lib scripts dsh.data.yaml package.json` | 1(零命中) | ✅ 通过 |
| 8 | 尺子①`v-plugin.sh 装配`(端A 客户端形态) | 沙箱副本 profile + 本包 | 0 | ✅ 绿 |
| 9 | 尺子①`v-plugin.sh 装配`(端B 平台形态) | `profiles/web` 键位建模 + `link:` | 0 | ✅ 绿 |
| 10 | 尺子②`v-plugin.sh 取数` | 真实 import 本产物 | 0 | ✅ 绿 |
| 11 | 尺子②`v-plugin.sh 回调` | 真实调用 `apply(ctx)` | 0 | ✅ 绿 |
| 12 | 尺子①`v-baseline.sh` | 平台 4 包 / 客户端 4 bundle | 0 | ✅ 绿(与 40 棒对照一致) |
| 13 | 尺子③`v-ui.sh` | 需真壳 | **3** | ⚠️ **未取到**(⛔ 不算绿) |
| — | `v-plugin.sh 运行` | — | — | ⚠️ **不成立**(可用启动日志 mtime 09-25,**早于本包存在** ⇒ 空真,⛔ 不当证据,同 40 棒口径) |
| — | `v-neg.sh` | 靶子固定旧插件 | — | ⚠️ 本棒**未跑**(它只证明"尺子能变红";本棒已自做**针对数据面**的负控,见 §三) |
---
## 五、本棒两处自纠(如实)
包内门禁 ①d 是**新写的扫描器**(正则逐行扫 yaml 文本),首版有两处缺陷,**均已当场修好并复验**:
| # | 缺陷 | 症状 | 根因 | 处置 |
|---|---|---|---|---|
| 1 | 索引行被当列 | 列数报 **34**(平台解析实为 29) | 6 空格缩进的流式映射有**两种**:列行(含 `name:`)与索引行(含 `columns:`) | 加判据:不含 `name:` 的行不计入列 |
| 2 | `default: "{}"` 被截断 | 列数报 **26**(少 3 列) | 正则用 `[^}]*` ⇒ 值里含 `}` 时提前收尾 | 改为 `\{(.*)\}`(贪婪到最后那个 `}`) |
**复验**:`声明可读:8 张表 / 29 列` —— 与平台解析器(`表=8`、列 29)**逐字一致** ✅。
⇒ 教训(写进门禁注释):**"包内自检"与"平台解析"是两个层次**,数字对不上时先怀疑自检扫描器。
---
## 六、发现的文档偏差(⛔ 本棒只报告、不擅改)
| # | 位置 | 文档写的 | 实测 | 影响 |
|---|---|---|---|---|
| 1 | 规格书 §五 A4 · 决策点 D1 | 库名 `dshs_pl_ai1net`(**15 字符**) | **14 字符**(`dshs_pl_`=8 + `ai1net`=6) | 纯笔误;换算与正则判据**均独立成立**,不影响任何判据。⛔ 规格书在文档库、属"冻结规格",本棒持锁域不含它 ⇒ **未改**,留给文档棒/收口会话一支笔 |
| 2 | 规格书 §五 A4 表前缀 `p_ai1net_` | — | 实测一致 ✅(`p_ai1net_tenant_orgs` 等 8 张) | 无偏差 |
---
## 七、未取到 / 不成立 / 刻意不做(⛔ 不折算成"已知")
| 项 | 状态 | 原因 + 取数口 |
|---|---|---|
| **浏览器级 `v-ui.sh`** | ⚠️ **未取到 rc=3** | 需**真壳在跑**(CDP 9330 不可达);启动器 `.workbuddy/_devkit/launch-desktop-dev-017.mts` **属客户端线**,本线 ⛔ 不代启。取数口:客户端线窗口内起壳后跑 `bash 交付物/验证脚本/v-ui.sh` |
| `v-plugin.sh 运行` | ⚠️ **不成立** | 可用启动日志 mtime = **09-25**,早于本包存在 ⇒ "无失败条目"是**空真** |
| **真机投放(两端各起一次)** | ⛔ **刻意不做** | 硬边界「⛔ 不碰起实例 / 取身份 / 把包装进去」;投放属 **S4** |
| **包进平台候选池 / 门户点[建库]** | ⛔ **本棒绕过** | 门户路径要求包先进候选池(从 tgz 解析声明);本棒 ⛔ 不投放 ⇒ **用同一份解析器从包目录解析**(`parseDeclFromDir`)。⚠️ 差异**只在"从目录 vs 从 tgz"**,解析器与执行器**是同一批函数**(`/opt/dshs/lib/db/plugin-data/*.js`)。⛔ 不等于"门户链路已通" |
| **库级回滚** | ⛔ **未执行**(本棒只做单模块事务内演练) | 口径 = 改名冻结、⛔ 不 DROP(方案 §3.10.5);本棒无需动它 |
---
## 八、硬边界自查
| 边界 | 状态 |
|---|---|
| ⛔ 不改官方 dsh 主程序与缓存(R2) | ✅ 未触碰 `@deepseek-ai/dsh` 与官方缓存 |
| ⛔ 不重造分发与更新机制 | ✅ 本棒**不碰**分发;建库/迁移/对账**全部复用** `datastore.js` / `diff.js` / `schema.js` |
| ⛔ 不碰「起实例 / 取身份 / 把包装进去」 | ✅ 一件未碰(无 ssh 起实例、无凭据取用、无 profile 装配) |
| ⛔ 不改客户端工作区(只读引用) | ✅ `dsh-ai1net-desktop` / `dsh-desktop-0.1.7rc2` 文件零改动(端A 用的是 `tmp/p40/` 里的**副本**) |
| ⛔ 包内不写直连平台库的路径 | ✅ 包内零 DB 动作(判据 §四-7 零命中);要写平台库的动作**全在平台侧脚本** |
| ⛔ 不 commit / 不 push | ✅ 未 commit、未 push |
| 红线门禁(重启控制面 / 中断用户 / >10 文件 / 不可逆) | ✅ **未触发**:未重启、未中断任何用户、改动 9 个文件(<10)、库可回滚(⛔ 未 DROP) |
| 平台侧口令 | ✅ **未落盘、未打印**:只在 ssh 会话内由 drop-in 取到 shell 变量并传 env(报道里只给 `url_shape=postgres://dshs:***@127.0.0.1:15432/dshs`) |
---
## 九、产物与落点
| 类别 | 路径 |
|---|---|
| 包内(并入包) | `E:/ProgramData/AIProject/dsh-plugin-ai1net/`:`dsh.data.yaml`(新) · `lib/data-limits.js`(新) · `package.json` · `scripts/build.mjs` · `README.md` |
| 平台侧执行器(⛔ 不属于包) | `E:/ProgramData/AIProject/aliyun-dsh-server/交付物/平台侧-插件数据面执行器-20260926.mjs` |
| 远端(47) | `/tmp/p41/{decl/,p41-exec.mjs,run.sh,migrate.out}`(临时件,⛔ 未落代码根 `/opt/dshs`) |
| 本机读数(⛔ 不入库) | `tmp/p41/`(探针脚本 · run-scales.sh · 尺读数-20260926.txt) |
| 平台侧持久变更 | PG:库 `dshs_pl_ai1net`(8 表+`p_meta_schema`)|控制面:`plugin_datastores` 一行(`state=ready`)+ `plugin_data_audit` 若干条(plan/create/migrate×5/decl_snapshot) |
**回滚**:① 库 → 改名冻结(⛔ 不 DROP)|② 台账行 → `DELETE FROM plugin_datastores WHERE plugin_id='@dsh-local/ai1net'`|
③ 包内文件 → `git`-less 直接还原(⛔ 本棒未 commit,故无历史版本;`dsh.data.yaml` 改回 sha256 已核)|
④ 远端 `/tmp/p41/` → `rm -rf`(纯临时件)。