Files
dsh_ai1net_server/接续入口_插件投放与分库线_20260922.md
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 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)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

239 lines
54 KiB
Markdown
Raw 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.
# 接续入口 · 插件投放与分库线(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)。