- 变更规模:新增 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/ 知识文件,按口径入库)
13 KiB
S5/S6 落地件 —— 兼容矩阵 · 迁移前结构备份 · 门户三段式 UI
工作区:
E:\ProgramData\AIProject\ai1net-dsh-server|代码基线:D:\github\dsh_shenxian(生产口径dshs) 棒次:插件投放与分库线 · 第 7 棒 · 执行棒(automation9cf7c353-…· 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 路由的调用点。
三条纪律(都做了)
- 失败即不执行 —— 路由层拿到
ok=false⇒ 落datastore_migrate_backup_failed审计 ⇒ 回500 schema_backup_failed,库一个字节都没改(在设计件 §七-3 原文「备份失败 ⇒ ⛔ 不执行」上落地)。 --schema-only—— 只导结构,⛔ 不导数据(迁移不改存量数据,导全量会让备份体积与用量成正比)。- 凭据不走 argv —— 口令经
PGPASSWORDenv 传,⛔ 不写进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