Files
dsh_ai1net_server/交付物/数据面-S3落地与三轮回执-20260926.md
T
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

226 lines
15 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.
# 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` 原文:⛔ 不自动探测 `<dir>/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`(纯临时件)。