- 变更规模:新增 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/ 知识文件,按口径入库)
15 KiB
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(纯临时件)。