2026-09-24 07:51:03 +08:00
|
|
|
|
# S5/S6 落地件 —— 兼容矩阵 · 迁移前结构备份 · 门户三段式 UI
|
|
|
|
|
|
|
2026-10-10 23:13:22 +08:00
|
|
|
|
> **工作区**:`E:\ProgramData\AIProject\ai1net-dsh-server`|**代码基线**:`D:\github\dsh_shenxian`(生产口径 `dshs`)
|
2026-09-24 07:51:03 +08:00
|
|
|
|
> **棒次**:插件投放与分库线 · **第 7 棒 · 执行棒**(automation `9cf7c353-…` · 2026-09-23 08:2x)
|
|
|
|
|
|
> **执行依据**:`接续入口_插件投放与分库线_20260922.md` §0 最新一行 + §5「本轮动作(第 7 棒)」
|
|
|
|
|
|
> **交接单**:`交接单/插件投放与分库线-①共享只读包库与插件数据面.md` §五 S5(含 S5-E)+ §六 总验收
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 一 一句话结论
|
|
|
|
|
|
|
|
|
|
|
|
本轮把 P2「插件更新要有定义」从**散落在正文里的三条口径**收成**一张可判别的矩阵**(并写进 `DB-03 §四·补`),
|
|
|
|
|
|
把第 4 棒记下的**迁移前结构备份缺口补上并真正接进迁移路径**(备份失败 ⇒ 迁移不执行),
|
|
|
|
|
|
把 S6 的**门户三段式 UI** 做出来(用户面「平台共享只读 / 已启用 / 已停用」),并把**端侧边界声明**落进 `DB-03 §八·补`。
|
|
|
|
|
|
|
|
|
|
|
|
⛔ **本轮未碰装配协议**(`file:` vs `link:`,D-h 待拍板项):`plugin-assembly.ts` 零改动,交接单里 `file:` 相关描述零改动。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 二 改了哪些文件(逐个 + 一句话)
|
|
|
|
|
|
|
|
|
|
|
|
### 代码面(`D:\github\dsh_shenxian`)
|
|
|
|
|
|
|
|
|
|
|
|
| 文件 | 改动 |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `src/db/plugin-data/datastore.ts` | **新增** `backupSchemaOnly()`(`pg_dump --schema-only` → `<backupRoot>/plugin-db/<pkg>/<v>-<ts>.sql`)+ `SchemaBackupResult` 类型 + `pgEnvFrom()` 私有辅助;补 `node:child_process` / `node:fs` / `node:path` import |
|
|
|
|
|
|
| `src/web/routes/business-plugins.ts` | **新增** `GET /api/plugins/shared/catalog`(用户面只读清单,白名单投影)|**迁移路径接入**结构备份:非首次建库 ⇒ 先备份,`ok=false` ⇒ 回 500 `schema_backup_failed` 且**不执行**迁移 |
|
|
|
|
|
|
| `src/config.ts` | **新增** `sharedCatalogPublic` 配置项(`DSHS_SHARED_CATALOG_PUBLIC`,**默认 `false`**)+ `Overrides` 字段 + 解析行 |
|
|
|
|
|
|
| `web/portal.html` | 新增 `sec()` / `subHead()` CSS(`.sec-head` / `.sec-title` / `.sec-sub` / `.sec-right` / `.sub-head` / `.sec-note`)|「插件管理」新增第 3 个 tab「用户可见性(三段式)」+ `loadVisibility()` / `renderEnabledDisabled()` / `tableWrap()`|`showTab` 支持 `visibility` 键|初始 tab 读 hash 支持 `#/plugins/visibility` |
|
|
|
|
|
|
| `package.json` | `test` / `verify` 两条脚本各追加 `test/plugin-shared-catalog.test.mjs`(⚠️ 本仓测试是**逐文件枚举**,⛔ 不追加等于没跑) |
|
|
|
|
|
|
| `test/plugin-shared-catalog.test.mjs` | **新增** 5 条:开关默认关 ⇒ 404(⛔ 不是空列表)· 白名单字段(⛔ 不含 9 个内部字段)· 磁盘不存在的条目不算可开通 · 池内无登记回落 id · 稳定排序 |
|
|
|
|
|
|
| `test/plugin-data.test.mjs` | **追加** 3 条结构备份回归:非法库名下发前被拒 · 包名折叠不越出 backupRoot · 失败不留半截 `.sql` |
|
|
|
|
|
|
|
|
|
|
|
|
### 文档面(`dsh-server-docs` + 本工作区)
|
|
|
|
|
|
|
|
|
|
|
|
| 文件 | 改动 |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `数据库/DB-03-插件数据面规范.md` | **新增** `§四·补` 兼容矩阵(6 行场景表 + 三条硬规则 + B 档两条前置 + 4 条可复现验证)|**新增** `§八·补` 端侧边界声明(6 行表 + 现状读数) |
|
|
|
|
|
|
| 本文件 `交付物/S5-S6落地-20260923.md` | 新增 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 三 S5-a 兼容矩阵(P2「要有定义」的兑现)
|
|
|
|
|
|
|
|
|
|
|
|
**落点**:`数据库/DB-03-插件数据面规范.md §四·补`(定稿文档,⛔ 不是只放在本落地件里)。
|
|
|
|
|
|
|
|
|
|
|
|
**核心口径**:两个版本号是两件事 —— `version`(包版本)vs `schemaVersion`(结构版本);
|
|
|
|
|
|
**判"库该不该动"只看 `schemaVersion`**。矩阵行 = 包版本 × `schemaVersion` 的三段关系(≥ / = / <)。
|
|
|
|
|
|
|
|
|
|
|
|
**验证(命令 + 期望 + 退出码)**:
|
|
|
|
|
|
|
|
|
|
|
|
| # | 命令 | 期望 | 退出码 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| 1 | `node --test test/plugin-data.test.mjs` | `# fail 0`,34 条全过 | 0 |
|
|
|
|
|
|
| 2 | `grep -c "四·补" 数据库/DB-03-插件数据面规范.md` | `≥ 1`(矩阵已落定稿) | 0 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 四 迁移前结构备份(第 4 棒记下的缺口)
|
|
|
|
|
|
|
|
|
|
|
|
**落点**:`src/db/plugin-data/datastore.ts` 的 `backupSchemaOnly()` + `…/datastore/migrate` 路由的调用点。
|
|
|
|
|
|
|
|
|
|
|
|
### 三条纪律(都做了)
|
|
|
|
|
|
|
|
|
|
|
|
1. **失败即不执行** —— 路由层拿到 `ok=false` ⇒ 落 `datastore_migrate_backup_failed` 审计 ⇒ 回
|
|
|
|
|
|
`500 schema_backup_failed`,**库一个字节都没改**(在设计件 §七-3 原文「备份失败 ⇒ ⛔ 不执行」上落地)。
|
|
|
|
|
|
2. **`--schema-only`** —— 只导结构,⛔ 不导数据(迁移不改存量数据,导全量会让备份体积与用量成正比)。
|
|
|
|
|
|
3. **凭据不走 argv** —— 口令经 `PGPASSWORD` env 传,⛔ 不写进 `execFileSync` 的 `argv`(`ps` 可见 = 泄密)。
|
|
|
|
|
|
|
|
|
|
|
|
### 两处自决(技术实现项,⛔ 不上抛)
|
|
|
|
|
|
|
|
|
|
|
|
- **只在"非首次建库"时备份**:`plan.items` 里若有 `create_database` ⇒ 库根本不存在的 ⇒ 无结构可备,
|
|
|
|
|
|
强行跑 `pg_dump` 必然失败、会把**首次建库全堵死**。故判据 = `!isFirstCreate`。
|
|
|
|
|
|
- **0 字节视为失败**:空库 `pg_dump` 也会写头部注释 ⇒ 空文件 = **静默失败**。
|
|
|
|
|
|
若不判它,「备份失败即不执行」这条纪律会被绕过(备份"成功"了,但里面什么都没有)。
|
|
|
|
|
|
- **包名路径安全折叠**:`pkgFlat` 先折非 `[A-Za-z0-9._-]` 字符(打断以分隔符为界的穿越),
|
|
|
|
|
|
再逐字折掉连续点(`..` → `_`)⇒ `../../etc/passwd` 不可能越出 `backupRoot`。
|
|
|
|
|
|
|
|
|
|
|
|
**验证**:
|
|
|
|
|
|
|
|
|
|
|
|
| # | 命令 | 期望 | 退出码 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| 1 | `node --test test/plugin-data.test.mjs` | 3 条结构备份用例全过(非法库名不在下发前被拒 / 折叠不越界 / 失败不留 `.sql`) | 0 |
|
|
|
|
|
|
| 2 | 真机:对已建库的插件点「确认执行」,看 `/opt/dsh/backups/plugin-db/<pkg>/` | 出现 `v<N>-<ts>.sql`,`stat -c %s` > 0 | 0 |
|
|
|
|
|
|
| 3 | 反证:把 `backupDir` 指到不可写路径后再点「确认执行」 | `500 schema_backup_failed`;库结构**未变**(`planHash` 与执行前一致) | — |
|
|
|
|
|
|
|
|
|
|
|
|
⚠️ **状态**:验证 1 已在本机跑通(见 §七);验证 2/3 属**真机取证**,
|
|
|
|
|
|
本轮**未部署到 47/106**(见 §八「未做的事」具名原因)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 五 S6 门户三段式 UI + 端侧边界声明
|
|
|
|
|
|
|
|
|
|
|
|
### S6-1(⛔ 不动)
|
|
|
|
|
|
|
|
|
|
|
|
`GET /api/plugins/mine` **保持原样**(`requireAuth` + 无过滤全量),⛔ 未"顺手加过滤"。
|
|
|
|
|
|
|
|
|
|
|
|
### S6-2(做成)
|
|
|
|
|
|
|
|
|
|
|
|
三段式的数据来源**分两路、语义不混**:
|
|
|
|
|
|
|
|
|
|
|
|
| 段 | 数据来源 | 语义 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| ① 平台共享只读 | **新增** `GET /api/plugins/shared/catalog` | 共享层**实况** —— 只有真铺到共享层、用户才装得到的才算数 |
|
|
|
|
|
|
| ② 已启用 | `GET /api/plugins/mine` 的 `enabled:true` | admin 自己实例的 profile 实际启用集 |
|
|
|
|
|
|
| ③ 已停用 | `GET /api/plugins/mine` 且 `enabled:false` **且**在共享层里 | 能装但当前没开 |
|
|
|
|
|
|
| ⋯ 暂不可用(附) | `enabled:false` **且不在**共享层里 | 池里有、没发布 ⇒ 用户装不到,⛔ 不是"用户没开"(混进③会误导 admin) |
|
|
|
|
|
|
|
|
|
|
|
|
🔴 **新路由的三条自我约束**:
|
|
|
|
|
|
|
|
|
|
|
|
- **字段白名单**:只回 `id / name / description / version`;⛔ 不回 `dir` / `path` / `fileRef`(宿主路径)/ 库名 / DDL / `planHash` / 指纹 / 备份路径。
|
|
|
|
|
|
- **名称说明以池内登记为准**,版本以**共享层实况**为准(用户真会装到的是共享层那一份,池里那个可能已漂移)。
|
|
|
|
|
|
- **清单里有、磁盘上没有 ⇒ 跳过**(否则用户会看到一个装不了的插件)。
|
|
|
|
|
|
|
|
|
|
|
|
### 🔴 可见面开关(默认关 · 需 admin 显式开)
|
|
|
|
|
|
|
|
|
|
|
|
`DSHS_SHARED_CATALOG_PUBLIC`,**默认 `false`**。关着 ⇒ **404**(⛔ 不是空列表 ——
|
|
|
|
|
|
空列表会被前端读成"共享层是空的",与"口子没开"混为一谈;前端据此显示明确的开启提示)。
|
|
|
|
|
|
|
|
|
|
|
|
**为什么默认关**:这是**用户可见面**的变化 —— 此前登录用户没有任何"平台共享清单"的口子。
|
|
|
|
|
|
按 `CODEBUDDY.md §1`,「扩大权限或可见面」属**红线门禁第一类** ⇒ ⛔ 不擅自默认开。
|
|
|
|
|
|
**现状**:开关关着 ⇒ 三段式的第 1 段显示「未开启」提示,第 2/3 段照常(它们与开关无关)。
|
|
|
|
|
|
开启动作 = 在 47 的 drop-in 里加 `DSHS_SHARED_CATALOG_PUBLIC=1` + 重生成 `platform.env` + `restart dshs`。
|
|
|
|
|
|
|
|
|
|
|
|
### S6-3 端侧边界声明(落文档)
|
|
|
|
|
|
|
|
|
|
|
|
**落点**:`数据库/DB-03-插件数据面规范.md §八·补`。要点:端侧走**同一个平台 API** ⇒ 只保证「能看见 + 能开通」;
|
|
|
|
|
|
⛔ 不要求端侧本地装包、⛔ 不下发平台级凭据到客户端、⛔ 端侧不落插件数据(数据在服务端库)。
|
|
|
|
|
|
⚠️ 现状读数照实写:端侧投放通路 = 无、客户端载体 = `client-artifact-missing` ⇒ 本声明只定义"该看到什么/不该拿到什么",
|
|
|
|
|
|
⛔ 不在本单造端侧载体。
|
|
|
|
|
|
|
|
|
|
|
|
**验证(命令 + 期望 + 退出码)**:
|
|
|
|
|
|
|
|
|
|
|
|
| # | 命令 | 期望 | 退出码 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| 1 | `node --test test/plugin-shared-catalog.test.mjs` | 5 条全过(含 404 语义与字段白名单) | 0 |
|
|
|
|
|
|
| 2 | `node -e "…检查 portal.html 含 pane-visibility…"` | `✓ 三段式 pane 与 tab 均在` | 0 |
|
|
|
|
|
|
| 3 | `grep -c "八·补" 数据库/DB-03-插件数据面规范.md` | `≥ 1` | 0 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 六 与交接单 §六 总验收的对照
|
|
|
|
|
|
|
|
|
|
|
|
| # | 判据 | 本轮状态 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| 1 | D2 主判据(不复制实体) | 第 6 棒已过;本棒⛔ 未碰装配路径 ⇒ 不受影响 |
|
|
|
|
|
|
| 2 | 共享层不可写 | 第 1 棒已过,本棒未动 |
|
|
|
|
|
|
| 3 | 宿主属主/权限 | 同上 |
|
|
|
|
|
|
| 4 | 跨机(S3-E ①–⑦) | 第 6 棒已过 |
|
|
|
|
|
|
| 5 | P1/D3 库隔离 | 第 4 棒已过 |
|
|
|
|
|
|
| 6 | 归属列强制 | 第 3 棒已过 |
|
|
|
|
|
|
| 7 | **P2(S5-E ①–⑤)** | 🔄 **口径面本轮补齐**(矩阵落定稿);**真机 E2E 属部署后动作** ⇒ 见 §八 |
|
|
|
|
|
|
| 8 | **P4/D1(S6-E ①②③)** | 🔄 **UI 段落地 + 新路由就绪**;**真机 E2E 属部署后动作** ⇒ 见 §八 |
|
|
|
|
|
|
| 9 | 零回归(`npm test`) | ✅ 见 §七 |
|
|
|
|
|
|
| 10 | 沙箱参数逐字复核 | 第 1 棒已过,本棒未动 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 七 本机回归读数(命令 + 原始输出 + 退出码)
|
|
|
|
|
|
|
|
|
|
|
|
| 项 | 命令 | 读数 | 退出码 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| 编译 | `npm run build` | 无输出(tsc 通过) | **0** |
|
|
|
|
|
|
| 全量测试 | `npm test`(Node **22.22.2**) | **447 tests / 445 过 / 0 败 / 2 跳过**(基线 439/437/0/2 ⇒ **+8**) | **0** |
|
|
|
|
|
|
| 分层 | `node scripts/check-layering.mjs` | `✅ 无新增违规`(`added: 0`;现存 5 条均在基线内) | **0** |
|
|
|
|
|
|
| 注入脚本 | `node scripts/verify-inject.cjs lib/supervisor/proxy.js` | `结论:全部合格 ✅` | **0** |
|
|
|
|
|
|
|
|
|
|
|
|
⚠️ 上一棒基线 = **439 / 437 过 / 0 败 / 2 跳过**;本棒新增 5 条(`plugin-shared-catalog.test.mjs`)+
|
|
|
|
|
|
`plugin-data.test.mjs` 追加 3 条(31 → 34 条)⇒ 439 + 8 = **447**(实测一致)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 八 ⛔ 未做的事 / 卡住的(具名原因,⛔ 不许混进成功)
|
|
|
|
|
|
|
|
|
|
|
|
| # | 项 | 原因码 | 说明 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| 1 | **部署到 47 / 106** | `deploy-deferred-to-next-step` | 本轮为**代码 + 文档**交付;`backupSchemaOnly` 与 `/shared/catalog` 都要 `restart dshs` 才生效。**属本线后续棒**(部署 + 真机 E2E),⛔ 未部署 ⇒ 上表第 7/8 项的真机 E2E 未跑 |
|
|
|
|
|
|
| 2 | **S5-E ①–⑤ 真机 E2E** | `requires-deployed-build` | 依赖 #1;本机无法造出"v1 → v2 升级 + 强行降级"的真库场景(需 47 的 PG + 真实插件包) |
|
|
|
|
|
|
| 3 | **S6-E ①②③ 真机 E2E** | `requires-deployed-build` + `requires-visibility-switch-on` | ② 还需先开 `DSHS_SHARED_CATALOG_PUBLIC`(红线门禁项,已备开关,⛔ 未擅自开) |
|
|
|
|
|
|
| 4 | **跨节点内容分发** | `not-in-scope-this-round` | 106 的共享层目前**手工同步**,平台侧无分发通路 ⇒ 需单独立项(入口 §5 已记) |
|
|
|
|
|
|
| 5 | **装配协议 `file:` vs `link:`** | `awaiting-user-decision` | 已拍板项 D-h 的重新取舍,候选与优缺点见 `交付物/S3跨机装配落地-20260923.md §六` ⇒ ⛔ 拍板前不得改(本轮已严格遵守:`plugin-assembly.ts` 零改动) |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 九 回滚
|
|
|
|
|
|
|
|
|
|
|
|
| 改动 | 回滚动作 | 影响面 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `backupSchemaOnly` + 迁移接线 | 删掉 `datastore/migrate` 里的备份块(路由层 3 处)+ 重 build + `restart dshs` | 回到"无备份直接迁移"(⚠️ 会退回第 4 棒记下的缺口) |
|
|
|
|
|
|
| `/api/plugins/shared/catalog` | 开关置 `0`(免重新部署)⇒ **立即 404**;彻底回滚 = 删路由 | 仅用户面清单;`/api/plugins/mine` 不受影响 |
|
|
|
|
|
|
| 门户三段式 UI | `git checkout web/portal.html` 单文件 | 门户「插件管理」回到两个 tab |
|
|
|
|
|
|
| `DB-03 §四·补 / §八·补` | 删该两节 | 纯文档 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 十 收尾
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ 锁:`--claim-exec` → 回归 → `--release-exec`(顺序铁律)
|
|
|
|
|
|
- ✅ 入口 `接续入口_插件投放与分库线_20260922.md` §0/§5 已推进
|
|
|
|
|
|
- ✅ 当日日志 `.workbuddy/memory/2026-09-23.md` 已追加
|
|
|
|
|
|
- ⛔ 未 merge / 未引新依赖 / **未 commit / 未 push**
|