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

13 KiB
Raw Permalink Blame History

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