回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复: - 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录) - tmp/(32.4 M,按接续棒命名的过程临时区) - .workbuddy/tmp/(39.5 M) - 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物) - tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留 入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与 接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、 .workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。 排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、 打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
239 lines
54 KiB
Markdown
239 lines
54 KiB
Markdown
# 接续入口 · 插件投放与分库线(2026-09-22 开线)
|
||
|
||
> **工作区**:`E:/ProgramData/AIProject/aliyun-dsh-server`(本入口**只此一份**)
|
||
> **开线棒**:会话 `aliyun-dsh-server` 2026-09-22 13:2x(用户当场提要求后收口)
|
||
> **代码基线**:`D:\github\dsh_shenxian` = **生产口径 `dshs`**;⚠️ 另有一份**开源导出物 `dsh_ai1net`**(`/opt/dsh_ai1net` · `/etc/dsh_ai1net.env` · `DSH_AI1NET_*`)是**另一套命名、另一个部署**,⛔ 两者不要混用(本会话已实测踩过这个坑)。
|
||
> **上游上下文**:同工作区 `.workbuddy/memory/2026-09-22.md` 的 `10:0x` 节(carbon 线"两处平台侧阻塞"判定)+ `07:5x` 节(一插件一库拍板)。
|
||
|
||
---
|
||
|
||
## §0 状态(**最新一行 = 执行依据**)
|
||
|
||
- **2026-09-22 13:2x|开线** —— 规划棒①收口(出交接单 `①共享只读包库与插件数据面.md`)。
|
||
- ✅ **2026-09-22 17:0x|执行棒①完成(S1/S2 全绿)** —— 共享只读包库已在 47+106 上线并真机取证;实投 mcn-suite + storyforge。**S3/S4/S5/S6 未做**。
|
||
- 🔴 **2026-09-22 18:1x–21:1x|编码与设计(受 IM 线锁占用,只出件不动服务器)** —— 用户追加口径「admin 全流程自闭环」(上传 → 检测通过 → 点建库按钮 → 建库成功才能开启 → 更新按钮 → 更新也检测 → 告知数据库改动 → 确认执行按钮)。产出设计件 `交付物/数据面管理面-建库与更新流程-20260922.md`(7 态状态机 / 检测第 3 项 / `dsh.data` 声明 / API 契约 / 两段式预演-确认 / 门户改动清单)。
|
||
- ✅ **2026-09-22 19:3x|用户拍板建库权限 = D**,产出 D 档 Runbook。
|
||
- 🔴 **2026-09-23 05:2x|D 档实测被证伪(重大反转)** —— PG **显式禁止从函数内执行 `CREATE DATABASE`**(`cannot be executed from a function`;另一层是「不能在事务块内」,而 PL/pgSQL 函数体恒在事务内)。同姿势下 `CREATE SCHEMA` / `CREATE ROLE` 均成功 ⇒ 语句级内核限制。旁路 `dblink` / `postgres_fdw` **均不可用**。**已回滚,服务器零残留**。判定件 ⇒ `交付物/建库权限-D档被证伪与修正判定-20260923.md`。
|
||
- ✅ **2026-09-23 05:4x|用户拍板建库权限 = B,并已在 47 落地** —— `ALTER ROLE dshs CREATEDB;` + **11 条验收全过**(核心断言:权限精确 `false|true|false`,未要 `CREATEROLE` / 未要超管)。🔴 **B 档真实缺口**:PG 无「只允许 `dshs_pl_*` 前缀」的原生机制(实测建 `evil_probe` 成功)⇒ 白名单**只能靠平台代码**兜住三点(库名单点生成 + 正则复校 + 每次建库落台账审计)。落地件 ⇒ `交付物/插件建库权限-B档落地-20260923.md`。
|
||
- 🔴 **2026-09-23 05:5x|排期调整:本线代码面**现在**开工** —— 原拍板「A = 等 IM 线整阶段收官后一次性跑完」的前提已变化:**IM 线因 E 单 UI 落点待拍板而停下**(A–D 四单已落地、E 单未开工、未登记接续棒 ⇒ 整阶段收官**无时点**),本线继续空等已不成立。故改为:**不依赖拍板的部分(设计件 §八 除建库口以外的全部)即刻推进**;建库口与三份文档回写另行安排。
|
||
**下一棒(唯一)** = **第 3 棒 · 执行棒** = automation `fe16b14a-cd7e-424d-99b0-37a21972365a`(一次性 · `scheduledAt = 2026-09-23T05:55`)。
|
||
⛔ **拍板项(不得自动跨过)**:无新增;建库通路已定 = **B 档**。
|
||
- ✅ **2026-09-23 06:0x|第 3 棒 · 执行棒完成(设计件 §八 除建库口外全部落地)** —— 7 项全做完:`src/db/plugin-data/schema.ts`(新建 · 声明解析校验 9 中性类型 · `pluginDbNameOf`/`assertPluginDbName`)|`src/db/plugin-data/diff.ts`(新建 · `diffDecl`/`planHashOf`/`forbidden` 只增不减)|v15 迁移 `plugin_datastores` × `plugin_data_audit`(台账 = B 档白名单第三点兜底)|检测第 3 项「数据面声明校验」|三处门禁(`enableDenialOf` / 检测不过不许建库 / `planHash` 未确认不许迁移)|门户「数据面」列 + 7 态徽章 + 预演清单面板 + 状态驱动按钮|版本回读校验(只认内容指纹,漂移写 `plugin_version_drift` 审计)。
|
||
**验证**:`npm run build` rc=0 | `npm test` = `391/389 过 / 0 败 / 2 跳过`(基线 366/364 过 / 2 跳过,本棒 +25 条)|`check-layering` ✅ 无新增违规(`added: 0`)。⛔ 未做(属下一棒):建库口接线 / 三份文档回写 / S3·S5-a·S6 / 跨节点内容分发。
|
||
**下一棒(唯一)** = **第 4 棒 · 执行棒** = automation `6e8cedcd-f426-4390-9460-937573d93f7d`(一次性 · `scheduledAt = 2026-09-23T06:27`,**约 8 分钟后**);接续点 = **建库口接线**(调 B 档 `CREATE DATABASE`、单语句不走事务、落台账 + 审计)。
|
||
- ✅ **2026-09-23 07:0x|第 4 棒 · 执行棒完成(建库口接线 · B 档全部兑现)** —— §5 的 5 项全做完:`src/db/plugin-data/datastore.ts`(**新建 479 行** · `CREATE DATABASE` 单语句非事务 / `readCurrentSchema` 真读 `information_schema` + `p_meta_schema` / `executePlan` 建库项非事务+表级 DDL 同事务 / `reconcilePluginDatabases` 对账 / 错误码映射 `PG_CREATEDB_MISSING`·`invalid_plugin_db_name`)|`currentSchemaOf` **去占位**|路由 `POST /datastore`(建库)+ `GET /datastores/reconcile`(对账告警)+ `/datastore/plan` 的 `executable` 真判据 + `/datastore/migrate` **去 503 真执行**|门户「建库」按钮接真实路由(原"尚未接线"禁态已去掉)。
|
||
**验证**:`npm run build` rc=0 | `npm test` = **426/424 过 / 0 败 / 2 跳过**(基线 391 过 ⇒ 本棒 **+35**)|`check-layering` ✅ 无新增违规(`added: 0`)。**47 真机取证通过**(真实编译产物走建库口):建库 `created` 71 ms/幂等 `exists`/非法名 `invalid_plugin_db_name` 未下发 PG/库内台账真读 `exists:true, schemaVersion:7`/`planHashPreserved:true`(COALESCE 语义)/对账抓出 orphan `dshs_pl_borphan4`。**清理后零残留**(`pluginDbs:[]` · 控制面表回到 13 张 · `dshs` active)。落地件 ⇒ `交付物/建库口接线-B档落地-20260923.md`。
|
||
⚠️ **本棒记的已知缺口(⛔ 未假装做了)**:① 迁移前结构备份(设计件 §七-3 的 `pg_dump`)属运维面,归 S5-a 棒;② **47 控制面仍停在迁移 v13** ⇒ v15 两张台账表要平台启动时 `runPgMigrations` 才落,部署本棒代码时随 `restart dshs` 自动补。
|
||
- ✅ **2026-09-23 07:2x|第 5 棒 · 执行棒完成(三份文档回写 · 纯文档零代码)** —— 共享文件持锁成批做,全部落笔:
|
||
① `DEPLOY-本部署.md` 新增 **§6.4 数据库初始化**(PG13 / 47 本机进程):建角色建库 + 🔴 `ALTER ROLE dshs CREATEDB;`(标用途 = 插件建库,⛔ 未给 `CREATEROLE`/`SUPERUSER`)+ 回滚句 + 权限校验判据(`f|t|f`)+ 白名单只能靠代码 + v15 迁移表随 `restart dshs` 自落。
|
||
② `交接单/…①共享只读包库与插件数据面.md`:`§五 S4-2` 改写为 **admin 显式按钮**(上传只检测 ⇒ `pending` ⇒ 点 `[建库]` ⇒ 成功才能开启;两道后端门禁;单语句非事务;两重防线;错误码)|`§十-D1` 表格行同步改并标注原写法作废。
|
||
③ `数据库/DB-03-插件数据面规范.md`:**执行状态块整块重写**(🔴 受阻 ⇒ ✅ 已落地;仅剩"跨节点身份通道为 0"一条真缺口)|`§三` 新增 **「三·补 谁建库、什么时候建」**|`§四` 执行时机改**两步(建库 / 迁移都是插件级一次性)**+ 表级同事务 / 建库不可回滚 / `planHash` `COALESCE` 语义|`§六-2` 补**两张台账表结构**(`plugin_datastores` / `plugin_data_audit`)。
|
||
④ 顺带核对 `交付物/数据面管理面-建库与更新流程-20260922.md §十一`:D 档段已作废(头部 + §十一 末尾均已有更新说明)⇒ ⛔ **未改动其历史结论文本**,核对通过。
|
||
**验证**:`npm run build` **rc=0** | `npm test` = **426 tests / 424 过 / 0 败 / 2 跳过**(与基线一致,纯文档零变动)|`git diff --stat` 本次改动 = `DEPLOY-本部署.md` **+21**(唯一 tracked 的直接证据;另两份在**未跟踪路径**下:`交接单/` 被 `.gitignore:45` 排除、`数据库/` 整个目录仍是 `??` 未跟踪 ⇒ 改动落地但不出现在 tracked 清单,属既有仓库形态,⛔ 与本棒无关)。**锁已 `--release-exec`**。
|
||
**下一棒(唯一)** = **第 6 棒 · 执行棒**;接续点 = **S3 跨机装配**(`POST /plugins/apply` + `remote-spawner.applyPlugins` + `mine/apply` 改走台账裁决)。
|
||
- ✅ **2026-09-23 08:1x|第 6 棒 · 执行棒完成(S3 跨机装配 · 真机 S3-E 全过)** —— §5 的 4 项全做完:**新建** `src/supervisor/plugin-assembly.ts`(约 400 行,**两台机共用的唯一实现**)|`worker/agent.ts` 新增 `POST /plugins/apply`(同族于 `/restart-probe`,⛔ 不新增入站口)|`remote-spawner.applyPlugins`(按 `hostIdFor` 定位机器)|`mine/apply` 改**只做清单裁决**(⛔ 不再本机 `runPnpmAs`)。
|
||
**验证**:`npm run build` **rc=0** | `npm test` = **439/437 过 / 0 败 / 2 跳过**(基线 426/424 ⇒ **+13**)|`check-layering` **✅ 无新增违规**(`added: 0`)。
|
||
**S3-E 真机取证(106 · 非 Manager worker · 用户 `4092b965` uid 100002 · 包 `@dsh-local/storyforge` 0.1.0)①–⑦ 全过**:`applyOk:true`|`file:/var/lib/dshs/bundled-plugins/_dsh-local_storyforge`|`node_modules/<pkg>` 软链 ✓|实例内 `ls` 可见 + `touch` = `Read-only file system` + mountinfo `ro,nosuid,nodev`|反证 `plugin_bundle_missing` 报错可读、实例不崩|⛔ **47 的 `users/` 下该用户零 `storyforge` 命中(连软链都没有,目录 8.0K 空壳)= D2 主判据过**。停用腿同验;用户面**零残留**(profile 已复原)。
|
||
**附带修正(真机暴露的既有缺陷,两条)**:① `pnpm install --ignore-workspace-root-check` —— **pnpm 9 不认该 CLI 选项**(只当 config 项)⇒ `Unknown option` **直接失败**(旧代码一直裸写,2026-09-13 07:43:58 曾因此把平台带崩、当时只加 try/catch 兜住进程)⇒ 改 `-w`(与 `scripts/ensure-biz-plugins.cjs` 既有正确姿势一致)+ 回归测试钉住;② 启用项**先落盘再 pnpm** ⇒ 失败留**半装状态**(依赖已声明、实际未安装)⇒ 改**先解析全部包(失败即抛、profile 未改)+ pnpm 失败回滚**。另修 `RemoteSpawner.call()` 既有 4xx 重试 bug(4xx 的 throw 被 catch 吞掉、循环跑满 4 次)。
|
||
**部署**:两机 `lib/` 同批(`plugin-assembly.js` md5 `134b5704…` **逐字一致**)+ 备份 `lib-pre-s3-*.tgz`;47 `restart dshs` 后 **v15 两张台账表已落**;106 共享层已同步(storyforge + mcn-suite);`relay-dialer` 实测连到 `ops/w-106`。落地件 ⇒ `交付物/S3跨机装配落地-20260923.md`。
|
||
🔴 **本棒抓出 1 条口径级缺陷(已取证 · ⛔ 未擅改 ⇒ 待拍板)**:D-h 拍板的 `file:` 协议在 pnpm 9 下会把插件包**整份复制**进每用户 `.pnpm/`(实测 **6.5 M/用户/包**、116 文件、`links=1`、inode 与源不同),**不满足 D2「实体一份、链接多份」**;根因 = pnpm 对 `file:`(目录型)走复制、⛔ 不硬链(同 profile 普通依赖 `links=9` 可证)。**已实测替换方案 `link:` = 4.0 K 纯软链直达共享层**,且抗 reconcile(加依赖 / `remove` 后软链仍在,即 D-h 当初选 `file:` 的唯一理由仍成立)。⇒ 因涉及**已拍板项 D-h**,交用户拍板(候选 A 维持 `file:` / 候选 B 换 `link:`,优缺点见落地件 §六),**倾向 B**。⛔ 拍板前不得改协议写法。
|
||
**下一棒(唯一)** = **第 7 棒 · 执行棒** = automation `9cf7c353-415d-4347-9b59-7ae4449ae986`(一次性 · `scheduledAt = 2026-09-23T08:23`,**约 6 分钟后**);接续点 = **S5-a 兼容矩阵 + 迁移前结构备份 + S6 门户三段式 UI**。⛔ 该棒**不得碰装配协议**(见上「待拍板」)。
|
||
🔴 **新登记待拍板项(唯一)**:装配协议 `file:`(现状)vs `link:`(实测 4 K)。拍板落地位置 = `src/supervisor/plugin-assembly.ts` 的协议写法 + 交接单 8 处 `file:` 描述。
|
||
- ✅ **2026-09-23 08:4x|第 7 棒 · 执行棒完成(S5-a + 迁移前备份 + S6 三段式 UI)** —— §5 的 3 项全做完,**文件面 8 改 2 新**:
|
||
① **S5-a 兼容矩阵**落**定稿** `数据库/DB-03-插件数据面规范.md` **新增 §四·补**(6 行场景表:首次装 / 升级 / 等价换包 / 降级 / 幂等重传 / 同名异内容冲突;三条硬规则;B 档两条前置;4 条可复现验证)—— 核心口径 = **两个版本号是两件事**,判"库该不该动"**只看 `schemaVersion`**。
|
||
② **迁移前结构备份**(第 4 棒记下的缺口):`datastore.ts` **新增** `backupSchemaOnly()`(`pg_dump --schema-only` → `<backupRoot>/plugin-db/<pkg>/<v>-<ts>.sql`)+ **接进 `datastore/migrate` 路径**:非首次建库 ⇒ 先备份,`ok=false` ⇒ `500 schema_backup_failed` **且不执行**(设计件 §七-3 原文落地)。三条纪律:**失败即不执行** / **只导结构** / **口令走 `PGPASSWORD` env ⛔ 不进 argv**。两处自决:只在 `!isFirstCreate` 时备份(否则首次建库全堵死)· **0 字节视为失败**(空库也会写头部 ⇒ 空文件=静默失败,不判它就把纪律绕过去了)· 包名路径折叠(先折分隔符再折连续点)。
|
||
③ **S6 门户三段式 UI**:`web/portal.html` 新增第 3 个 tab「用户可见性(三段式)」+ `.sec-head` 等分段样式 + `loadVisibility()`;三段数据来源**分两路不混**——① 平台共享只读 ← **新增** `GET /api/plugins/shared/catalog`,②③ 已启用/已停用 ← `/api/plugins/mine`(**该路由保持原样,⛔ 未顺手加过滤**),另附「⋯ 暂不可用」把"池里有没发布"与"用户没开"分开(混讲会误导 admin)。
|
||
🔴 **新增开关 `DSHS_SHARED_CATALOG_PUBLIC`(默认 `false`)**:新路由是**用户可见面**变化(此前登录用户无任何共享清单口子)⇒按 §1「扩大可见面」红线**默认关**,关着 = **404**(⛔ 不是空列表:空列表会被读成"共享层是空的")。**未擅自开**。
|
||
④ `DB-03` **新增 §八·补 端侧边界声明**:端侧走**同一个平台 API** ⇒ 只保证「能看见 + 能开通」;⛔ 不要求本地装包 / ⛔ 不下发平台级凭据 / ⛔ 端侧不落插件数据。
|
||
**验证**:`npm run build` **rc=0** | `npm test` = **447 / 445 过 / 0 败 / 2 跳过**(基线 439/437 ⇒ **+8**:新文件 5 条 + `plugin-data` 追加 3 条)|`check-layering` **✅ 无新增违规**(`added: 0`)|`verify-inject` 全合格。落地件 ⇒ `交付物/S5-S6落地-20260923.md`。
|
||
⚠️ **本棒记的已知缺口(⛔ 未假装做了)**:**未部署到 47/106**(`deploy-deferred-to-next-step`)⇒ S5-E / S6-E 的**真机 E2E 未跑**(须部署 + 先开可见面开关);跨节点内容分发仍无平台侧通路(可立独项)。
|
||
⛔ **本轮严格未碰装配协议**:`plugin-assembly.ts` **零改动**、交接单 `file:` 相关描述**零改动**。
|
||
**下一棒(唯一)** = **第 8 棒 · 执行棒** = automation `87c8221c-a7ce-41e2-b5fa-e56fc0c8c929`(一次性 · `scheduledAt = 2026-09-23T08:49`,**约 7 分钟后**);接续点 = **部署到 47/106 + S5-E / S6-E 真机 E2E**。
|
||
🔴 **待拍板项仍是同一条(唯一,⛔ 未变)**:装配协议 `file:`(现状)vs `link:`(实测 4 K)。拍板落地位置 = `src/supervisor/plugin-assembly.ts` 的协议写法 + 交接单 8 处 `file:` 描述。
|
||
🔴 **下一棒新增红线**:**可见面开关 `DSHS_SHARED_CATALOG_PUBLIC` 属红线门禁**(`CODEBUDDY.md §1` 扩大可见面)⇒ ⛔ 下一棒**不得擅自打开**;S6-E ② 若要跑,**要么先向用户取得开启许可,要么只跑不需要该开关的部分(E ①③)**。
|
||
- ✅ **2026-09-23 09:5x|第 8 棒 · 执行棒完成(部署 + S5-E / S6-E 全绿)** —— §5 的 4 项全做完:
|
||
**① 部署**:`npm run build` rc=0 ⇒ 产物 scp 到 47 `/opt/dshs` + 106 `/opt/dshs-cluster`,**三机 md5 逐字一致**(`db/plugin-data/datastore.js` `9fedd77c…` / `web/routes/business-plugins.js` `fe456d60…`);47 `restart dshs` rc=0;**重启后 `plugin_datastores` + `plugin_data_audit` 仍在**(v15 未丢)。部署前备份 `/opt/dsh/backups/lib-s5s6/{47,106}-pre-s5s6-20260923-085319.tgz`。
|
||
**② S5-E ①–⑤ 真机 E2E 全 PASS**:① v1→v2 加列升级,升级前后**均 3 行**(存量原样)|② 强行降级 ⇒ `items=[]` + `forbidden=[drop_column p_s5probe_notes.pinned]` + `executable=false` + `state=blocked` ⇒ **`409 plan_blocked`**,列与行数**一字未动**|③ 同名同版本重传 `http=200`、tgz md5 与源一致(幂等)|④ `/opt/dsh/backups/plugin-db/<pkg>/` **2 个 `.sql`**(`v0-…643B` / `v1-…1344B`)、**0 字节文件数 = 0**、台账 `datastore_migrate_backup_ok x2`|⑤ **对照 200 + 反证 500 `schema_backup_failed`**(`mkdir_failed: EPERM`)、库结构未变,环境已原样还原。
|
||
**③ S6-E ①③ PASS**:① admin 池 5 = guest `/mine` 5|③ admin 新增 `@dsh-local/s6newprobe` ⇒ guest **5 → 6**、无需管理动作。**② 只记事实 ⇒ `DSHS_SHARED_CATALOG_PUBLIC` 未设置(默认 false)⇒ `404`**;🔴 **开关全程未开**(红线门禁遵守)。
|
||
🔴 **本棒抓出并修复 1 条真实缺陷(真机专属 —— 本机测不出)**:`datastore.ts` 里 `pgEnvFrom(baseUrl)` 的返回值被**展开在 `execFileSync` 的 options 层**(与 `env:` 同级)⇒ `PGHOST`/`PGPORT`/`PGUSER` **没下发到 `pg_dump` 子进程** ⇒ 退回 Unix socket `:5432` ⇒ 47 上 `No such file or directory` ⇒ **结构备份在生产上根本跑不通**。修法 = 新增 `pgDumpEnv()` 并**合进 `env`**;另修 `business-plugins.ts` 里第 4 棒遗留的 `backup:'not_implemented'` 不实文案 ⇒ 改真实值(`skipped_first_create` / `taken` + `backupPath`)。回归钉住 `+2` 用例(⚠️ 首版用 PATH 假 `pg_dump` 桩在 **Windows 上失败**(`.cmd`/`.bat` 无法解析 ⇒ `ENOENT`/`EINVAL`)⇒ 改为**直接断言 env 不变量**:四个 PG 变量必须存在、且 ⛔ 不得混进 `stdio`/`timeout`/`cwd`/`encoding`)。重部署后 ④ 腿备份**成功落盘** ⇒ 闭环。
|
||
**验证**:`npm run build` **rc=0** | `npm test` = **449 / 447 过 / 0 败 / 2 跳过**(基线 447/445 ⇒ **+2**)|`check-layering` **✅ 无新增违规**(`added: 0`)。落地件 ⇒ `交付物/S5-S6真机E2E验收-20260923.md`。
|
||
⚠️ **本棒记的已知缺口 / 未做项(⛔ 未假装做了)**:
|
||
① **S6-E ② 字面判据(`/mine` 返 `enabled:true`)未闭合** —— **不是代码缺陷,是拓扑形态的必然**:客实例(uid `100002`)**在 106(worker)**,而 `/mine` 读的是**平台本机 profile 快照**(47 上为空)⇒ 本机快照反映不了远端装配。**实测 enable 已正确落到实例所在机 106**(`node_modules/@dsh-local/storyforge` 软链 09:43 建立、可解到 `package.json`、`package.json` 写入 `file:/var/lib/dshs/bundled-plugins/_dsh-local_storyforge`)⇒ 判据用 **106 侧文件证据**闭合。旁证:`dsh-100002-9648cef4.scope` 沙箱命令行含 `--ro-bind-try /var/lib/dshs/bundled-plugins`(**S1 共享只读层挂载成立**)。🔴 **⇒ 暴露一条缺失能力:平台侧"用户可见启停状态"没有跨机回流通路(现在只有单向 apply)** —— **建议单独立项**,本棒只记录、未改架构。
|
||
② 门户三段式 UI 的 **CSS 视觉渲染 / tab 点击** 未验(本机 `agent-browser` **每次调用均挂死**,属环境限制 ⇒ fail-fast;已清理 chrome 进程)⇒ 改用**等价判据** `ui-logic-check.mjs` 复跑 `portal.html` 的 `loadVisibility()` 逻辑:**7/7 断言通过**(互斥 / 完备 `6 vs 6` / 语义正确 / 开关关 ⇒ ③=0)。
|
||
③ 跨节点内容分发仍无平台侧通路 ⇒ **需单独立项**。
|
||
**清理**:探针库 / 候选池行 / 账本行 / 审计行 / 备份目录 / 共享层探针 tgz **全清,零残留**(`%probe%` 三处计数全 0、池子恢复原始 4 个);`platform-dirs.conf` 原样还原(`DSH_PLATFORM_DIR=/opt/dsh` + 无 `.bak-s5e`);**106 客实例 `storyforge` 启用已回退**(回到 4 依赖原始态:`business-plugins` / `portal-entry` / `workspace-scoped-picker` / `dsh-file-preview`),106 `dshs-worker` + `dshs-relay` 均 active。
|
||
⛔ **本轮严格未碰装配协议**:`plugin-assembly.ts` **零改动**、交接单 `file:` 相关描述**零改动**;⛔ 未开可见面开关;⛔ 未 commit / 未 push / ⛔ 未引新依赖 / ⛔ 无 merge。
|
||
**下一棒(唯一)** = **第 9 棒 · 规划棒** = automation `9028a886-6b79-497e-a57c-1a33dcd796c3`(一次性 · `scheduledAt = 2026-09-23T09:59`,**约 7 分钟后**);接续点 = **跨机可见性状态回流(缺口立项)+ 三门一致性复验**。⛔ 三条硬禁止:⛔ 不碰装配协议(D-h 待拍板)· ⛔ 不开可见面开关 · ⛔ 不改 `plugin-assembly.ts`。
|
||
🔴 **待拍板项仍是同一条(唯一,⛔ 未变)**:装配协议 `file:`(现状)vs `link:`(实测 4 K)。拍板落地位置 = `src/supervisor/plugin-assembly.ts` 的协议写法 + 交接单 8 处 `file:` 描述。
|
||
🔴 **待拍板项(本轮新增 · 第 ② 条)**:**平台侧"用户可见启停状态"的跨机回流通路**要不要立 —— 现状是单向 apply、无状态回流 ⇒ 实例在 worker 上的用户,平台侧 `/mine` 恒显 `enabled:false`。**候选 A**:立专项做状态回流(优点:平台侧启停状态全域准确、admin 与用户看到同一事实;缺点:新增一条控制面回传链路、要定拓扑与重试语义)|**候选 B**:维持现状、把"跨机时 `/mine` 的 `enabled` 语义明确为「本机快照、可能滞后」并在 UI 上标注(优点:零改动、立刻可交付;缺点:admin 侧看到的启停状态在跨机场景下**永远是错的**,会误导管理动作)。
|
||
- ✅ **2026-09-23 10:0x|第 9 棒 · 规划棒完成(跨机可见性状态回流设计件 + 三门基线复验 + 交接单登记)** —— §5 的 3 项全做完,**只出设计件 · 零代码改动 · 未动服务器**:
|
||
**① 设计件** ⇒ `交付物/跨机可见性状态回流-设计件-20260923.md`(10 节):问题定义(4 条影响面 + 3 条排除项)|**事实基础 13 条**(现读代码,带行号)|**🔴 三条硬约束**(C1「worker 主动上报」在"只拨出"形态下物理走不通;C2「Manager 主动拉」的骨架 `reportHost()` **已在跑**、增量只是把返回值加宽;C3 读路径必须与响应路径同源)|**通路形态三候选**(A 主动拉/B 共用台账/C worker 上报,逐条优点+缺点 + 一张对比表)|写入时机 T1(apply 完成后)+ T2(实例启动 reconcile,**由 worker 本机驱动、Manager 不在场**)|陈旧度语义(`asOf` 必带 + `stale` **现算不落库** + 阈值 = 3×心跳周期)|失败重试与不可达降级(5 场景表,核心 = ⛔ 不许把"未知"显示成"未启用")|与 v15 台账的关系(**粒度不同塞不进去**;A 案不建表、B 案才建且**必须落控制面**)|"不做会怎样"四尺度(长期越晚越贵:多 Manager 联邦下每个区都要各造一套)|落地轮廓 + 附复核命令。
|
||
🔴 **本件的关键否证**:**候选 B 不能单独成立** —— `reconcile` 的驱动点在 worker(`agent.ts` 20 s 本地定时器),Manager 不在场 ⇒ "apply 时写台账"覆盖不到该路径 ⇒ 台账只能当**缓存**、⛔ 不能当权威。**候选 C 要新开一条 worker→Manager 入站口** ⇒ 撞 §1「扩大可见面」红线。
|
||
**技术倾向(可推翻 · ⛔ 未替拍)**:**A 为骨架 + B 降级为可选缓存**;C 需用户先授权新开入站口。
|
||
**② 三门一致性复验(基线记录 · 未改代码 ⇒ 确认第 8 棒部署未引入漂移)**:`npm run build` **rc=0**(`tsc -p tsconfig.json` 无输出)|`npm test`(Node 22)= **449 tests / 447 过 / 0 败 / 2 跳过**(与第 8 棒基线**逐字一致**)|`node scripts/check-layering.mjs` = **✅ 无新增违规**(现存 5 条全在基线内;⚠️ 报「未归类 1 = `src/platform-paths.ts`」属既有状态,非本棒引入)。
|
||
**③ 交接单登记**:`交接单/插件投放与分库线-①共享只读包库与插件数据面.md` **§五 S6 新增「🔴 已知边界」小节**(成因代码位置 + 106 真机实证 3 条 ✅ / 1 条 ❌ + 影响面 3 条 + ⚠️ 区分响应路径与读路径 + 状态=缺失能力已立项);**§六 验收表第 8 行同步加注**(② 的字面判据仅同机形态成立)。
|
||
⛔ **本轮严格未碰装配协议**:`plugin-assembly.ts` **零改动**、交接单 `file:` 相关描述**零改动**;⛔ 未开可见面开关;⛔ 未改任何生产代码、未动 47/106 服务器。
|
||
**下一棒** = ⛔ **未登记**(拍板项未定 ⇒ 按用户级记忆「要用户拍板的,等拍了再新建接续会话」)。**接续点 = 用户对通路形态拍板后,排「跨机可见性状态回流」执行棒**。
|
||
🔴 **待拍板项 = 两条(⛔ 未变 + 新增)**:
|
||
① 装配协议 `file:`(现状)vs `link:`(实测 4 K)。落地位置 = `src/supervisor/plugin-assembly.ts` 协议写法 + 交接单 8 处 `file:` 描述。
|
||
② **跨机可见性回流的通路形态**(A 主动拉/B 共用台账/C worker 上报,逐条优缺点见设计件 §三)。落地位置 = `worker/agent.ts` 的 `/healthz` + `leased-spawner.ts` 的 `reportHost()` + `business-plugins.ts` 的 `/mine`。
|
||
- ✅ **2026-09-23 15:4x–16:2x|A 线收口 · 装配协议 `link:` 全落地(① ② ④ ✅ · ③ 实测撤销)** —— 用户口令「**先把 a线做完,处理完成后在做b线**」,本段为 **A 线(插件投放与分库线)** 的收口:
|
||
**代码**:`plugin-assembly.ts` 新增 `depRefOf()`(+ `pnpmExecDisabled()`/`runPnpm()` 测试缝)|`business-plugins.ts` 4 处 `fileRef` 同步为 `depRefOf`。三门全绿:`npm run build` **rc=0**|`npm test` = **450 / 448 过 / 0 败 / 2 跳过**|`check-layering` **✅ 无新增违规**(`added: 0`)。
|
||
**部署**:`lib/supervisor/plugin-assembly.js` + `lib/web/routes/business-plugins.js` 已 scp 两机 —— 🔴 **47 = `/opt/dshs/`、106 = `/opt/dshs-cluster/`**(**106 上没有 `/opt/dshs`**,这是本轮新查实的事实);两机 md5 逐字一致(`fe5162ec…`/`ed507b7a…`);`restart` 47 `dshs`+`dshs-worker`、106 `dshs-worker`;门户 3080 → **200**、worker 19100 → **401**;旧产物备份 `/opt/dsh/backups/plugin-assembly-20260923/`。
|
||
**真机复验(47)**:软链 **54 B** + `.pnpm` **8.0 K**(对照 `file:` 6.5 MiB/116 文件 ⇒ **约 1000 倍差距**)、目标回溯直达共享层、用户身份下可读、反证原子抛错;探针用户已回滚。
|
||
🔴 **③ 存量用户迁移实测撤销(本轮最重要的否证)**:全平台只有 **1 个**带依赖实例,5 条依赖**全 `.tgz` 型**(目录型 0 条),**只有 1 条**在共享层有目录 ⇒ 原迁移方案会让 **4 条悬空 ⇒ 实例崩**。成因 = 那 4 个包是平台自研核心组件、**从不进共享层**。⇒ 已从待落地清单撤下,交接单 §9.3 整节改写。
|
||
**文档**:交接单该文档 —— 状态行(S3 → 全绿已上线)|§五 S3-E 新增真机复验表 + 部署状态段|§五 S4-2 无改|§9.3 整节改写(含"`dsh-file-preview` 已指向共享池"的**误读纠正**)|§十二 执行回报第 1 行(S3 → 已做)。入口 §5 待落地块 + 本线后续第 4 条同步。
|
||
⚠️ **残留一条待补(不阻塞)**:**106 侧跨机腿** —— 106 现无带插件用户实例可复验,等有用户启用时按 S3-E ④⑤⑥ 补一次。
|
||
✅ **2026-09-23 18:3x|用户拍板 —— 跨机可见性形态 = D「按需定向拉(主)+ worker 变更上报(辅·后置)」** —— 用户原话「**可以 worker 上报,具体要管理那个用户的时候 manager 针对性的去查**」。原 A / B / C 三候选**作废**;设计件 §十 已整节改为定稿。
|
||
🔴 **本棒(设计阶段)拿到的最有价值事实**:`fenceOnAgent()`(`src/supervisor/leased-spawner.ts:144-155`)里**已经**有「`findUserInstance(userId)` → `agentFor(hostId)` → **带 token** 定向 `POST` 到那台 worker」的**完整三步定位链** ⇒ 主路径=**只把目标端点从 `/fence` 换成新只读口**(建议 `/profile-bundles`)。⇒ **零新增监听口 · 零新增凭据体系 · 零广播**,且**天然覆盖 `reconcile` 路径**(原 B 案的死结,因为查的是**对端现算真值**而非历史快照)。
|
||
⛔ **两条硬约束(写码前必守)**:① 新端点**必须带 token**(与 `/fence` 同级)—— ⛔ **绝不可挂到免凭据的 `/healthz` 上**,否则等于开匿名任意读口;② 查不到须返回 **`not_here`**,⛔ **不等于"零插件"**(两者语义不同,混用即复制本缺陷)。
|
||
📌 **落地登记 = 下方「⏭️ 本线后续」第 1 条**(本条为设计与拍板落定,⛔ 未写一行生产代码、未动 47/106)。
|
||
🔴 **下一棒(automation)本轮排不出来** —— `automation_update` 创建时返回 **`file is not a database`**:宿主活动库 `E:\ProgramData\.workbuddy\workbuddy.db`(以 `CODEBUDDY_CONFIG_DIR = E:\ProgramData\.workbuddy` 判定)**第 1 页 header 已丢失** ⇒ 全机 **automation 功能不可用**。⚠️ 与拍板结论无关,**⛔ 不要因为排不出棒而认为形态没定**;库修复后按本条与设计件 §十 直接排。
|
||
**下一步 = B 线(IM 线)**:按用户口令「处理完成后在做b线」,接续点 = `接续入口_IM线_20260922.md`(推荐先做 **①档位重写**,它是全部 E2EE 代码的**硬前置**)。
|
||
- ✅ **2026-09-23 20:5x|carbon 线「两问」答辩(纯文档 · 零代码 · 未动 47/106)** —— 触发 = 用户口令「**看插件会话的问题反馈** `dsh-plugin-carbon/对接文档_carbon插件-平台侧两处阻塞_20260922.md`」⇒ 读 carbon 线回执 §H–§K,在该对接单追加 **§L 答辩**,逐条裁定:
|
||
① **凭据落点 → 迁 D4 目录**(`<userRoot>/home/.dsh/plugins/dsh_plugin_carbon/mcp.json`;判据 = 清理单元一致 / 一处约定 / 暴露面不变 ⇒ 不触发 R5);**并将目录名口径定死 = `pluginId`**(去 scope·小写·非 `[a-z0-9_]` 折 `_`,与库名 `dshs_pl_<pluginId>` 同 token ⇒ carbon = **`dsh_plugin_carbon`**,⛔ 不是 `carbon`)。
|
||
② **零数据面插件 ⛔ 不要显式提交空声明**:无 `dsh.data` ⇒ `none` ⇒ **放行**;`tables: []` ⇒ `invalid` ⇒ **状态恒 `blocked`、永远发不出去**;单放一个 `dsh.data.yaml` **根本不被读取**(不自动探测)。⇒ carbon 现状即合规,**什么都不用加**。
|
||
③ 🔴 **自纠(我上一轮写错的一处)**:§C-2 D4 里的「只能走 SDK 的 `im.paths.pluginData()`」**是错的 —— 该 API 三处零命中**(源仓 `src/` / 宿主仓 `deepseek-harness` / `node_modules`)⇒ 正确姿势 = `join(process.env.DSH_HOME, '.dsh', 'plugins', pluginId)`(`DSH_HOME` 由平台注入、必存在),⛔ 删 `homedir()` 回退(`HOME` = `<userRoot>/ws`,而 `ws` 会被平台按产物清理 ⇒ 回退 = 写到会被清掉的位置)。
|
||
④ **另抓一条(对方没问、但会咬人)**:**跨机投递** —— 用户实例在 worker 上时,管理机直写 `<userRoot>/home/…` = **静默空操作**(`user-fs.ts:122-124`,档案 138 §五)⇒ 投递须落在 `hostIdFor(userId)` 那台机。⚠️ 平台侧**无**「插件凭据投给用户」通路(home 白名单仅 3 个**裸文件名**)⇒ 凭据仍带外投递;要自动化须立项,建议**并进 worker `/plugins/apply` 同一条腿**(零新增入站口)。
|
||
**文档落笔**:`DB-03` 新增 **§六·补 插件文件落点与凭据投递**(8 行口径表 + 现状缺口)|`交接单 ①…md` **三处**同步更正(状态行「S5-b 口径已更正」· §四 路径段「SDK 口不存在、改 `DSH_HOME` 拼法」· §五 S5-b 五条改写含 `pluginId` 口径/凭据同目录/跨机/零数据面)。
|
||
⛔ 本棒**零平台代码改动**、⛔ 未动 47 / 106、⛔ 未 commit / push。
|
||
**下一棒 = ⛔ 未登记**(本棒为**即席应答**、非接续棒;且 **B 线(IM 线)第 15 棒正在跑**,按用户口令 A→B 的顺序,本线不与 B 线并行抢资源)。⚠️ 本线**两项可排**(均非阻塞):a) 跨机可见性回流的执行棒(**形态 D 已拍板 2026-09-23 18:3x** ⇒ 按用户级「等拍板」纪律,**条件已满足、随时可排**);b) 可见面开关 `DSHS_SHARED_CATALOG_PUBLIC` 的开启决策(**红线门禁 · 待用户拍板**)。
|
||
- ✅ **2026-09-23 21:3x|插件对接文档落库 + 仓库清理清单(纯文档 · 零代码 · 未动 47/106)** —— 触发 = 用户口令「**需要一份插件对接文档,给插件开发会话按照文档规范开发插件**」+「**放到 `D:\github\dsh_shenxian\dsh-server-docs` 中长期维护**」+「**清理这个文件夹中过时和临时以及不需要的文件**」。
|
||
**① 新规范(长期维护 ⇒ 落文档库、不带日期)** ⇒ **`dsh-server-docs/规范/08-插件开发与对接规范.md`**:§0 一句话 / §1 开工四问 / §2 包形态 / §3 数据面(含「零数据面 ⛔ 不要写空声明」+ 声明**唯一入口**澄清)/ §4 文件落点 D4 / §5 投放链路(谁做什么)/ §6 **错误码表(判归属)** / §7 提包前自测清单 / §8 端侧边界 / §9 回报格式 / §10 别做清单 / §11 变更记录。`INDEX.md §一` 已加一行索引(「写一个业务插件」→ 指向 08)。
|
||
⚠️ **该件顺带纠正一处口径分叉**:`DB-03 §三` 与平台回复里的「在 `im.data` 上声明表」是**运行时对象的措辞**;**声明的存放位置只认 `package.json` 的 `dsh.data.schema`**(内联对象或指向包内文件),且**不自动探测**包内 `dsh.data.yaml`(源码 `src/db/plugin-data/schema.ts:701-746` 现读)。
|
||
**② 仓库清理(`D:\github\dsh_shenxian`)** ⇒ **`交付物/清理候选清单-dsh_shenxian-20260923.md`**(**只读扫描报告**):**P0 明确垃圾 9 项 / 2.5 MiB**(`dsh-server-docs/scripts/__pycache__`、`_tmp_seq40/*.out`)· **P1 疑似一次性 91 项 / 2.6 MiB**(`_tmp_seq40/`(36) · `_tmp_seq24/`(8) · `poc/**/*.tgz` 20 个旧版本 · `_中间产物_待清理/` · `tmp-e9*.txt` · `tmp/seq46s0/`)· **§四 假阳性 10 项**(⛔ 别删:`scripts/overlay-probe.cjs` 等 6 个**在册工具**、`archive/` 存档、过程档案、`poc/carbon-mcp-probe/`(**carbon 线归属**)、`.workbuddy/lock-hook.log`(**hook 正在写**))· §五 五批次处理顺序(每批 ≤10、跟踪文件走 `git rm`、每批复核)。
|
||
🔴 **⛔ 本棒未删 / 未移 / 未改名任何文件** —— 批量删除属红线(用户长期规则「任何可能影响 >10 文件的操作用前必须先出受影响清单并取得确认」+「未明确要求不 commit / push」)⇒ **先出清单、等逐项点名确认**。
|
||
⚠️ **遗留(不阻塞)**:新增规范**尚未进全文检索**(`docs-manifest.json` 未重生成;生成器 = `dsh-server-docs/scripts/docs-manifest.py`,⛔ 本棒未擅自跑生成物)。
|
||
**下一棒 = ⛔ 未登记**(同前:即席应答 + IM 线第 15 棒在跑)。
|
||
- ✅ **2026-09-23 21:5x|仓库清理执行(用户口令「按照建议清理」· **先备份后删** · 零代码影响)** —— 备份 ⇒ `归档/dsh_shenxian-清理-20260923/deleted-20260923.tar.gz`(1.23 MB / 36 条目 + `MANIFEST.txt`)。实删:**批3 = 26 个 POC 历史 tgz(1.15 MiB)** + **批4 = `_中间产物_待清理/`(5 文件)** + 批2 = 2 项;**批1/批2 的多数目标在动手前已被清掉(非本次动作,备份包可证)**;**P0 类残留复扫 = 0**。守卫全绿:`.workbuddy/lock-hook.log`(hook 在写)/ `scripts/overlay-probe.cjs`(在册探针)/ `poc/carbon-mcp-probe/`(carbon 线)/ `poc/im-conversation-tabs/`(IM 线)**健在**,备份包内 **0 条他人路径**。⛔ 未 commit / push。回报已落 `交付物/清理候选清单-dsh_shenxian-20260923.md §六`。
|
||
- 📌 **用户问「仓库根 `docs/` 有什么用 / 何时建 / 要不要留」—— 判定 = 全部保留**(2026-09-23 21:5x 取证):`docs/` = **仓库自带的 7~8 篇安装向文档**(`blueprint` / `deployment` / `hard-isolation` / `domain-config` / `troubleshooting` / `k8s-deploy` / `k8s-deployment` / `architecture`),**建于 09-13~09-16**(项目最早文档批次);`README.md:5` 明文「`docs/`(本仓库内)= 仓库自带 7 篇 …… ⛔ **不要往这里写我们的改造记录**」+ README 表格逐篇索引 ⇒ **职责与 `dsh-server-docs/`(平台文档库)分离**。其中 `k8s-*` 两份对应 **2026-09-15 已下线的模式 B**,但 `README.md:55` 明写「**保留作为未来可选路线**」⇒ 属**有意留档**,不列可清理项。
|
||
⚠️ **同时澄清「仓库只有代码和文档」不成立**:实存非源码内容 = `lib/`(构建产物 3.5 MiB,**服务器跑的就是它**)· `node_modules/` · `.workbuddy/`(钩子日志,正在写)· `config/` · `poc/`(各线 POC 源码)· `deploy/` `assets/` `.github/` · `dsh-server-docs/`(文档库,含 `scripts/ ops/ skills/ archive/ tmp/`)· 根级 `Dockerfile*` `cordis.patch.yml` `tsconfig.json` `package*.json` `mksess.cjs` 等 ⇒ ⛔ 均在用,不属可清理项。
|
||
|
||
---
|
||
|
||
## §1 用户原话(目标,⛔ 不得改写、不得替拍)
|
||
|
||
> 「插件由admin上传后,应该是建独立库据库进行对接(需考虑更新插件的情况),然后分发到服务器worker,或端侧设备上,任何登录用户都可以访问才对」
|
||
|
||
拆成四条判据:
|
||
|
||
- **P1** admin 上传插件 ⇒ 建**该插件独立的库**并完成对接
|
||
- **P2** **插件更新**的情形要有定义(升级后库/表/元数据/存量数据怎么办)
|
||
- **P3** 分发的**终点面** = 服务器 worker + **端侧设备**(不止 Manager 本机)
|
||
- **P4** **任何登录用户都可以访问**
|
||
|
||
---
|
||
|
||
## §2 现状(本会话已核实 / 已定稿,执行棒复核时**只读**)
|
||
|
||
| # | 事实 | 来源 |
|
||
|---|---|---|
|
||
| 1 | **一插件一库已拍板**:库名 `dshs_pl_<pluginId>`,全部建在同一个 PG **13.23**(47 · `127.0.0.1:15432`);库内归属列 `user_id` / `room_id` **强制** | 定稿 `dsh-server-docs/架构设计/数据-分库与权威存储架构.md`;2026-09-22 用户拍板 |
|
||
| 2 | **插件数据面实现为零**:47 的库内 13 张表**全是内核表**,`p_*` 零命中;`src/db` 无对应模块 | 2026-09-22 实测 |
|
||
| 3 | 分发现状 = **admin 导入候选池 → 用户在实例「功能管理」自助启用**;安装动作 = **实例内** `pnpm add file:<tgz>` | `src/web/routes/business-plugins.ts:625-651` |
|
||
| 4 | **跨机那条腿是断的**:`/api/dsh/restart` 的 handoff **已停写**(只留日志)⇒ 装不到 worker 上实例 | `src/web/routes/dsh.ts:149-175`(本会话核实) |
|
||
| 5 | **端侧(桌面壳 / 设备)没有插件投放通路**,目前只有"设备登录接入" | 序㊻ 范围 |
|
||
| 6 | 版本一致性已有档案:候选池同名替换 + 版本对账 | `调整方案/144-插件规模化投放与版本一致性.md` |
|
||
| 7 | 多节点投放与数据面已有档案:路 A / 路 B、worker 自举、目录权限 711 | `调整方案/147-多节点形态下的插件投放与数据面.md` |
|
||
|
||
⚠️ 与既有硬说明的关系:记忆里那条「投放业务插件**只有一种方式**:admin 导入候选池 → 用户自助启用(**不铺 profile**)」说的是**启用语义**;本线的 P3 是**包分发面**(把包铺到各节点 ≠ 替用户铺 profile)。规划棒须把这两层分清,⛔ 不要当成"推翻既有说明"。
|
||
|
||
---
|
||
|
||
## §3 技术方向 · **已拍板口径(用户原话,2026-09-22 13:2x,⛔ 不得改写)**
|
||
|
||
> 「c 全员默认可见,需自己开通,服务器上所有用户共享只读插件库,不会每个用户分发一份,使用中数据入库,文件保存到用户插件文件夹下对应插件名称的路径中」
|
||
|
||
拆成四条**硬口径**(规划棒 ⛔ 不得自行改档):
|
||
|
||
- **D1 可见性**:候选池里的插件对**所有登录用户默认可见**;**开通由用户自己点**(沿用 `/api/plugins/mine/apply`)⇒ ⛔ 不采用"默认安装到所有实例"
|
||
- **D2 包共享**:服务器上**一份只读插件库**供所有用户共享,**⛔ 不给每个用户复制一份**
|
||
- **D3 数据**:使用中产生的数据**走库**(按插件分库 `dshs_pl_<pluginId>`,见 §2-1 定稿)
|
||
- **D4 文件**:用户侧文件落 **「用户插件文件夹 / <插件名称> / …」**(目录真身须现场核实,见 §5-3)
|
||
|
||
> 📌 **2026-09-22 16:3x 补(只记实况,⛔ 未改上面口径)**:两条待拍板已闭合 —— ① 共享包库版本策略 = **只保留最新一版**(用户原话「1 B」)② D4 落点 = **`<userRoot>/home/.dsh/plugins/<pluginId>/`**(口径已对齐:用户说的就是**平台用户自己的目录**;落点由规划棒自决,可推翻)。详见交接单 §4.1 `D-i` / `D-j` 与 §4.2 注;执行棒以交接单 §五 为准。
|
||
|
||
技术方向(可推翻,逐条对应上面):
|
||
|
||
1. **包分发**:admin 上传后由平台/worker agent 铺到**每个节点的共享只读包点**;实例侧**只引用、不复制**。
|
||
- 首选实现 = **共享只读 pnpm store**(节点级 `store-dir`,`o+rX`;`node_modules` 走硬链/符号链接)⇒ 实体一份、链接多份,天然满足 D2;注意硬链要求**同一文件系统**。
|
||
- 备选 = 解包到共享目录后 symlink 进 profile。
|
||
- ⛔ **禁止**把 tgz 实体复制进各用户 profile(这正是 D2 明令否掉的形态)。
|
||
2. **不依赖 handoff**:包已在节点本地 ⇒ 安装动作不需要"重启后执行命令"那条链(跨机 handoff 已停写,见 §2-4)⇒ 也⛔ 不必给登录用户开命令口。
|
||
3. **建库是平台侧动作**(不在实例内):上传钩子里 `CREATE DATABASE dshs_pl_<pluginId>` + 迁移登记;粒度沿用定稿(**默认独立 database**,schema 档留作备选)。
|
||
4. **端侧先只做可见性**:端侧设备访问同一个平台 API ⇒ 只管"访问得到",不要求端侧本地装包。
|
||
|
||
---
|
||
|
||
## §4 P4 粒度:**已拍板 = 候选 C(2026-09-22 13:2x)**
|
||
|
||
原三案存档(供追溯):A 全员默认安装 | B 逐用户自助启用 | C 折中。
|
||
|
||
**定案 = C**,且用户把 C 的语义收窄为「**全员默认可见、需自己开通**」:
|
||
- ⛔ 不采用 A 的"默认安装"(不把内存/磁盘代价摊到所有实例)
|
||
- ⛔ **不加** admin 侧的"默认启用"标记(用户未要求,开线棒提过,已按用户口径去掉)
|
||
|
||
⇒ 规划棒:**这一项不再出候选**,只管把 C + §3 的 D1–D4 落成可执行方案。
|
||
|
||
---
|
||
|
||
## §5 下一棒要做什么
|
||
|
||
> 🔄 **2026-09-23 更新**:本节原有内容(规划棒口径)**已由规划棒①执行完毕**(交接单已产出并跑完执行棒①的 S1/S2)。**当前执行依据改看 §0 最新一行** + 下方「⏭️ 本轮动作」。
|
||
|
||
### ⏭️ 本轮动作(第 9 棒 · 规划棒 · ✅ 已收官 2026-09-23 10:0x)= **跨机可见性状态回流(缺口立项)**
|
||
|
||
> ✅ **第 9 棒已收官**(设计件 `交付物/跨机可见性状态回流-设计件-20260923.md` + 三门基线复验全过 + 交接单 §五 S6 已知边界登记,见 §0 2026-09-23 10:0x 行)⇒ ⛔ 不要重做。
|
||
> ✅ **第 8 棒已收官**(部署 47/106 + S5-E ①–⑤ 全 PASS + S6-E ①③ PASS/② 只记事实 + 修掉 `pg_dump` env 真实缺陷,见 §0 2026-09-23 09:5x 行;落地件 `交付物/S5-S6真机E2E验收-20260923.md`)⇒ ⛔ 不要重做。
|
||
> ✅ 第 7 棒(S5-a + 迁移前备份 + S6 三段式 UI)、第 6 棒(S3 跨机装配)、第 5 棒(三份文档回写)、第 4 棒(建库口接线)同样**已全部落地** ⇒ ⛔ 不要重做。
|
||
|
||
**⏭️ 下一棒(⛔ 未登记 —— 等用户拍板)**:
|
||
|
||
> 🔴 **接续点 = 用户对「跨机可见性回流的通路形态」拍板**(候选 A 主动拉/B 共用台账/C worker 上报,优缺点见设计件 §三)。拍板到手后,排**「跨机可见性状态回流」执行棒**。
|
||
> ⛔ **本轮不登记下一棒**:拍板项未定 ⇒ 按用户级记忆「**要用户拍板的,等拍了再新建接续会话**」——先判"是不是要拍板",命中即停,⛔ 不得因"候选排序看起来有优劣"就自决登记。
|
||
> ✅ **已解决的待拍板项(装配协议)见本节末「⚠️ 已拍板待落地」** —— ⛔ 该条**退出待拍板清单**,拍板前禁令随之解除(禁令仅针对"未拍板就改",不针对落地)。
|
||
|
||
⛔ **执行棒开工前仍须遵守**:⛔ 不开可见面开关 `DSHS_SHARED_CATALOG_PUBLIC` · 不做跨节点内容分发(需单独立项)。
|
||
|
||
要求:判据给「命令 + 期望输出 + 退出码」。
|
||
|
||
### ⚠️ 已拍板待落地(用户 2026-09-23 拍板 · **① ② ④ 已落地 ✅,③ 已实测撤销**)
|
||
|
||
> 🔴 **装配协议 = 采用 `link:`**(**推翻原 D-h `file:`**)。用户原话:「按照建议处理」。
|
||
> **落地范围(4 处)现状**:
|
||
> ① ✅ **已落地** —— `src/supervisor/plugin-assembly.ts` 新增 `depRefOf()`(`file:` → `link:`)+ `business-plugins.ts` 4 处 `fileRef` 同步;`npm run build` rc=0 | `npm test` = **450/448 过 / 0 败 / 2 跳过** | `check-layering` ✅ 无新增违规。**两机已部署**(47 `/opt/dshs` + 106 `/opt/dshs-cluster`,md5 逐字一致 `fe5162ec…`/`ed507b7a…`)+ `restart` 完成(门户 200 · worker 401)。
|
||
> ② ✅ **已落地** —— 交接单该文档 `file:` 描述已改(含 §9.3 整节改写 + 新增 S3-E 真机复验段)。
|
||
> ③ ⛔ **已撤销(实测证伪,⛔ 别再做)** —— 全平台扫描:带依赖的实例**只有 1 个**,5 条依赖**全是 `file:…tgz`**(`.tgz` 型 **0 条目录型**),且**只有 1 条**能在共享层找到对应目录 ⇒ 原「把 4 条改写成共享层引用」会让 **4 条依赖悬空 ⇒ 实例崩**。成因 = 那 4 个包是平台自研核心组件、**从不进共享层**。⇒ 迁移在真实数据上不成立,等这些包进共享层再议。详见交接单 §9.3。
|
||
> ④ ✅ **已落地** —— 47 真机复验(`applyProfileChanges` 真实入口):profile 写入 `link:/var/lib/dshs/bundled-plugins/dsh-plugin-mcn-suite` ✓|`node_modules/<pkg>` = **软链** ✓|软链目标**回溯直达共享层**(非复制)✓|软链本体 **54 字节** + `.pnpm` **8.0 K** ✓|用户身份下可读 ✓|反证 `plugin_bundle_missing` 原子抛错 ✓。对照 `file:` 的 6.5 MiB / 116 文件 ⇒ **量级差约 1000 倍**。探针用户已回滚(`dependencies` 回空、软链已移除)。
|
||
> ⚠️ **残留一条待补(不阻塞)**:**106 侧跨机那条腿** —— 106 上当前无带插件的用户实例可复验(其用户 profile 无依赖),等 106 有用户启用插件时按 S3-E ④⑤⑥ 原命令补一次即可。装配实现两机共用同一份 md5,不因此判 S3 未完成。
|
||
> ⚠️ **跨机前提**:软链目标必须是两机**同路径**的共享层(现为 `/var/lib/dshs/bundled-plugins/<flat>`,两机同名 ✓);⛔ 若将来两机路径分叉,`link:` 会比 `file:` 更脆 —— 已写进 `depRefOf()` 注释。⚠️ 注意 `/opt/dshs`(47 代码根)与 `/opt/dshs-cluster`(106 代码根)**两机不同名不影响** `link:` —— 共享层路径才是 `link:` 的目标。
|
||
|
||
### ⏭️ 本线后续(⛔ 本棒不预登记队列)
|
||
|
||
1. ✅ **跨机可见性状态回流 —— 形态已定稿(2026-09-23 18:3x 用户拍板 = 形态 D「按需定向拉(主)+ worker 变更上报(辅·后置)」)** —— 用户原话「**可以 worker 上报,具体要管理那个用户的时候 manager 针对性的去查**」。原 A / B / C 三候选**作废**(§三 仅存为决策过程)。设计件 §十 已整节改为定稿(10-1 形态 / 10-2 关键发现 / 10-3 两条硬约束 / 10-4 五步落地轮廓 / 10-5 四条真机验收)。
|
||
🔴 **关键发现(写出这条是为了让下一棒不用重查)**:`src/supervisor/leased-spawner.ts:144-155` 的 `fenceOnAgent()` **已经**实现了完整三步定位链 —— `findUserInstance(userId,'main')` 拿 hostId → `options.agentFor(hostId)` 拿 `{agentUrl,token}` → `post('/fence', body, target)` **带 token 定向 POST 到那台 worker** ⇒ 主路径**只需把目标端点从 `/fence` 换成新的只读口**(建议 `/profile-bundles`)。⇒ **零新增监听口 · 零新增凭据体系 · 零广播**,且**天然覆盖 `reconcile`**(查的是对端现算真值,不是历史快照 —— 这正是原 B 案的死结)。
|
||
⛔ **两条硬约束**:① 新端点**必须带 token**(与 `/fence` 同级)—— ⛔ 绝不可挂到免凭据的 `/healthz` 上(那等于开匿名任意读口);② 查不到该 userId 必须返回 **`not_here`**,⛔ 不等于"零插件"、⛔ 不许把"未知"显示成"未启用"。
|
||
**落地位置**:`src/worker/agent.ts`(新端点)|`src/supervisor/leased-spawner.ts`(复刻定位链)|`src/web/routes/business-plugins.ts:1648-1670` 的 `/mine`(先判本机 ⇒ 本机直接读盘零网络,非本机才定向查)。
|
||
⚠️ **下一棒 = 待排**(原记「automation 不可用,见 §0 末行」—— 🔻 **该前提已于 2026-09-23 19:4x 作废,见本节末「状态更正」**)—— 本项**未开工**,⛔ 不要当成已落地。
|
||
2. **跨节点内容分发**(后续棒 · 需单独立项)—— 平台侧把共享层铺到各节点的通路;第 8 棒 106 的共享层是**手工同步**的。
|
||
3. **可见面开关的开启决策**(红线门禁 · 待拍板)—— `DSHS_SHARED_CATALOG_PUBLIC` 开与不开会让"用户能看见什么"变化 ⇒ 需用户拍板后才开。
|
||
4. ~~**装配协议 `link:` 落地**~~ ✅ **已完成(2026-09-23)** —— 见上方「⚠️ 已拍板待落地」;③ 存量迁移已实测撤销。
|
||
5. 🔴 **E 单(`im-conversation-tabs`)的线上可见面投放 —— 此前无归属,2026-09-23 19:4x 由 IM 线第 12 棒显式登记到此**(登记依据 = `接续入口_IM线_20260922.md` 第 136 行的收口要求)。
|
||
- **是什么**:IM 线 **E 单**的 UI 落点(用户已拍板 **C 案** = 实例内 dsh 会话页 tab);**代码面已完成**(`poc/im-conversation-tabs/`)。
|
||
- **为什么以前没人做**:本入口此前对 `im-conversation-tabs` **零命中** ⇒ IM 线以为"投放线会接力"、投放线没登记 ⇒ **两不管**。
|
||
- ~~🔴 **硬阻塞(⛔ 未解,先别投)**:IM 线查出一处 **P0 协议不一致** ……~~ ✅ **已解(2026-09-23 19:5x,IM 线第 13 棒)** —— 客户端已对齐平台的 **op 制**(出站收敛到 `subscribe / ping / resume`,入站 `switch (msg.op)` 全量 9 分支),并新增**两侧源码机械对账**用例 `E-14` 守着;⛔ 不再有 `type:` 出站帧。
|
||
- ✅ **投放已执行(2026-09-23 20:2x,IM 线第 14 棒)**:按本线既有流程走完 —— admin `POST /api/plugins/business`(http=200,池 4→5,`trustedOverride:false`)→ `POST /api/plugins/business/share`(http=200 `action:"created"`,`fileRef = link:/var/lib/dshs/bundled-plugins/_dsh-local_im-conversation-tabs`)→ 回滚素材 `/opt/dsh/backups/plugins/_dsh-local_im-conversation-tabs/0.1.0.tgz`;审计 `audit_log` id **360 / 361**。⛔ 未铺 profile、未 commit、未改动本线任何代码。⚠️ **「共享层发布」这一步是 `link:` 装配协议的必需项**(只进池不发布 ⇒ 用户启用撞 `plugin_bundle_missing`)—— 本线后续给任何插件投放时**都要带上这一步**。
|
||
- 📌 **归属 = IM 线自行认领完结**(2026-09-23 20:2x 第 14 棒):投放的**可自动部分**由 IM 线做完;剩余**唯一未闭合项 = 用户在实例「功能管理」启用**(用户动作,属投放语义),操作单 ⇒ `E:\ProgramData\AI技能\aliyun-dsh-server\交付物\IM群组-ui投放与线上取证操作单-20260923.md`。⇒ **本线不再有该条的待办**,⛔ 不要重复认领。⚠️ **一条留在本线名下的关联缺口**:目标实例若在 **106**,其**共享层缺该包** ⇒ 与本入口 §5-2「跨节点内容分发(需单独立项)」是同一件事,操作单里给了手工同步命令。
|
||
|
||
> 📌 **状态更正(2026-09-23 19:4x,IM 线第 12 棒顺带核对 ⇒ ⛔ 未替本线排棒)**:§0 末行「`automation_update` 返回 `file is not a database` ⇒ 全机 automation 功能不可用」的**前提已消失** —— 宿主活动库已由 app **自动重建**(2026-09-23 18:55;坏库改名 `workbuddy.db.corrupt-2026-09-23T10-55-47-845Z` 留档,代价 = 历史 automation 记录全丢)。本会话实测 `mode=list` / `mode=create` **均可用** ⇒ 本线「下一棒」**可以正常登记**了。⚠️ 排棒时机仍由本线收口时自行决定(⛔ 不预登记队列)。
|
||
|
||
---
|
||
|
||
## §6 开工校验(新会话先跑这三条)
|
||
|
||
- 口径门禁:本入口 md5 见启动棒 prompt(开工时现算比对;不符 ⇒ 先读最新版再动手)
|
||
- 源仓 HEAD:`git -C D:/github/dsh_shenxian log --oneline -1`(以实跑为准)
|
||
- 相关口径复核:`/etc/dshs.env` **无** `DSHS_ENABLE_PATCH`(47 / 106 于 2026-09-22 实测)—— 本线 §3.2 正是为绕开它而设计
|
||
|
||
> ⛔ **不要重做**:carbon 线两处阻塞的定位(memory `10:0x` 节已判完);一插件一库是否分库(已拍板,见 §2-1)。
|