Files
dsh_ai1net_server/交付物/S5-S6落地-20260923.md
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

202 lines
13 KiB
Markdown
Raw Permalink 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.
# S5/S6 落地件 —— 兼容矩阵 · 迁移前结构备份 · 门户三段式 UI
> **工作区**:`E:\ProgramData\AIProject\ai1net-dsh-server`|**代码基线**:`D:\github\dsh_shenxian`(生产口径 `dshs`)
> **棒次**:插件投放与分库线 · **第 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**