diff --git a/.workbuddy/memory/2026-09-26.md b/.workbuddy/memory/2026-09-26.md new file mode 100644 index 0000000..2a82d8b --- /dev/null +++ b/.workbuddy/memory/2026-09-26.md @@ -0,0 +1,118 @@ +# 2026-09-26 + +## 覆盖网络线 / IM 线 · 端口拍板 + 设备当中继可行性(承接 09-25 深夜,用户两条口径 · 只读 · 零代码) + +> 用户口径:「**1、先不开端口,后续有需要再开**」「**2、还有可以让性能和网速高的设备当中继,比如另一个会话开发的 dsh桌面客户端版本**」 + +### 拍板 ①:D8 云安全组「先不开」(已定 · 已落两处载体) +- 落点:`接续入口_覆盖网络线_20260916.md §0`(新增 2026-09-26 首行)+ 状态层 `MEMORY.md`。 +- 🔴 **性质变化**:D8 由「**唯一在册红 / 待用户操作**」→「**用户已知并主动搁置**」,⛔ **不再当待办**;**重开条件** = 要做**跨节点内容分发**(插件内容 / 大文件 / 镜像同步)时一并定。 +- **影响面(⛔ 别误传)**:真机打洞率仍 **0** ⇒ **内容面**跨节点分发的带宽节省(潜力 **75%**(4 节点)– **93.75%**(16 节点))**暂拿不到**;⛔ **不影响群聊 / 房间 / 消息 / 历史 / 在线态 / 插件** —— 全走 **443 中继**,功能完整。 +- 📌 **口径澄清(重要 · 上一轮我表述错过一次)**:`relay-direct` = **443 兜底中继入口**(⛔ 不是 P2P);**UDP `21100-21115` 只服务 P2P** ⇒ 中继走 443 ⇒ **功能不用开端口**。(原文取证:`addr-override.ts` 注释「443/TCP 兜底 · 去 CF」) + +### 拍板 ②:设备当中继 —— 🔴 **路径已存在,但要分清两个角色** +- **现成基础(实证)**:中继白名单 `dialers: Map>` **默认拒绝** ⇒ **加节点=配置动作**;一键加入(`overlay-node-join.cjs` / `overlay-node-admit.cjs`)+ 分组准入**已落地**;**桌面端节点接入已实测**(`dsh-ai1net-desktop/docs/S2_覆盖网络节点接入编排与守护实测报告_20260920.md` —— 守护 / 退避自愈 / 开机自启全就位);`lib/net/relay/main.js` **同一入口既起客户端(`--client`)也起服务端(`--server` = 中继)**。 +- 🔴 **两个角色必须分清**:**拨出方**(`--client`)✅ **已实测**、**不需可达**;**中继 / 被拨入方**(`--server`)⚠️ **未实测**、**需要"别人能连到它"**。 +- ⚠️ **设备当中继也有自己的"端口问题"**:用户侧 NAT / 路由器端口映射 / 打洞 —— ⛔ **不是**云安全组那件事,但**同源**;✅ **同局域网 / 组织内网场景几乎零门槛**(可直接用)。 +- **与既有路线关系**:已拍板「引入 Centrifugo 类外部连接层」**支持多实例** ⇒ 本设想 = **把连接层节点来源从"只由平台提供"扩展为"平台 + 用户自备高性能设备"** ⇒ **与既有方向一致,⛔ 不改架构**。 + +### 🔴 推荐替代路径(⛔ 不需要任何端口):设备主动拉(fan-out on read) +服务器收消息 → 只落库 + 广播**小信令** → 各设备**拨出**去拉(拨出不需可达)。 +- **优势**:卸载**同一块扇出成本**,却**不需要用户侧任何网络前提**;**延迟略增**(一次往返)。 +- **前提**:需把固定间隔轮询改成「**信令触发 + 增量拉**」—— IM 线第 14 棒的插件 `subscribe` 轮询**已是雏形**(⚠️ 那一版是**固定轮询**,随设备数线性,属我引入的负载)。 + +### 建议推进顺序(技术项 · 可自决) +① 先做「**设备主动拉**」改造 → ② 同时在**局域网**小范围试「设备当中继」(零公网前提、验证中继服务端角色)→ ③ 跨公网中继留到**确实需要**时(与"是否重开端口"一起定)。 + +### 产出与边界 +- 产出 ⇒ `交付物/端口决策与设备中继可行性-20260926.md`(含拍板 / 角色区分 / 替代路径 / 顺序)。 +- 承接件 ⇒ `交付物/端口与容量瓶颈评估-20260925.md`(含 **🔴 扇出背压隐患**:`ws.ts` 的 `send()` 丢弃 `socket.write()` 返回值 ⇒ 慢客户端内存无界;**单点即可打垮服务器**,建议**先修**)。 +- ⛔ 零代码改动、未部署、未 commit/push、未动 47/106、未动云安全组、**未改桌面线一个字节(只读)**。⛔ **未登记下一棒**。 + +--- + +## IM 线 · 第 15 棒(执行棒)✅ 扇出背压修复(用户拍板 A 案) + +> 用户口径:「**A 方案**」(= 现在修背压)。会话 `IM线-第15棒-扇出背压修复`|域锁 2 域(`src/im` · `test/im-sdk.test.mjs`),收口后已释放。 + +### 修的是什么(前一轮查出的隐患) +`src/im/ws.ts` 的 `send()` **丢弃** `socket.write()` 返回值、也不看水位 ⇒ **慢客户端(弱网 / 故意不读)让待写缓冲无界增长** ⇒ **单点即可把进程内存顶爆**(丢的不只是那条消息,是整个进程)。 + +### 交付(1 新 3 改 · 改动形态极干净) +- 改 `src/im/ws.ts`(**+84 / −1**):新增常量 `IM_SEND_BUFFER_LIMIT_BYTES`(4 MiB,= 单帧上限的 4 倍)+ 运行时选项 `sendBufferLimitBytes`(**可注入** ⇒ 可测、可按真实水位标定);`send()` 加三道:① `write()` 返回 false ⇒ `hub.noteBackpressure(writableLength)` 且**仍算已投递** ② 水位 **> 上限** ⇒ `noteBackpressure` + `noteBackpressureClose` + **具名断开**(`1013 send-buffer-overflow`)+ 落 `im_send_buffer_overflow` warn 日志 ③ 恰等于上限 ⇒ 不断开。 + 🔴 **关键:`ws.ts` 被删改的既有行只有 1 行 —— 正是 `socket.write(textFrame(text))`**(可机械断言)。 +- 改 `src/im/hub.ts`(**+36 / −0 纯插入**):`HubStats` 加 `backpressureEvents` / `peakPendingBytes` / `closedByBackpressure`;`counters` 同步加;新增 `noteBackpressure()` / `noteBackpressureClose()`(**只记账、⛔ 不抛、⛔ 不做 I/O**)。`stats()` 用展开语法 ⇒ 自动带上。 +- 新 `test/im-backpressure.test.mjs`(**8 例**)。 +- 改根 `package.json`(`test`/`verify` 各登记 1 处;`numstat` 仍 **2/2** ⇒ 上一棒的 Editor 替换法生效 ✅)。 + +### 判据读数 +`npm run build` rc=0|`node --test test/im-backpressure.test.mjs` = **8 / 8 过 / 0 败**|`npm test` = **626 / 616 过 / 5 败 / 5 跳过**(基线 618/608 ⇒ **+8 全为本棒**;5 败**仍是**投放线 `tar EBUSY`,⛔ 未增一败)|`check:layering` rc=0 **✅ 无新增违规**|`store.ts` **零改动**。 + +### 🔴 两条设计取舍(可推翻) +1. **`write()=false` 不虚减 `delivered`** —— false 的含义是"水位满",**数据已排队、会送达** ⇒ 若记成 skipped,"送达数"会随客户端网速变小、污染观测与判据 3。⇒ 记账归记账,投递语义不变。 +2. **阈值可注入而非写死** —— 本值是**保守起点**(未标定)⇒ 注入化让"标定后改参数"不必改代码。 + +### 🔴 判据演进(已写进 `09 §8`) +「修 bug」与「新增能力」判据**不同**:新增能力 ⇒ **纯插入**;**修 bug ⇒ 允许改既有逻辑**,但必须同时:① 既有语义有回归用例(本棒 = `delivered === onlineIn`)② 新行为可观测(计数/峰值/断开数)③ 阈值可注入。142 §五-6 意图不变(⛔ 不为场景改内核,只许"接扩展点"+"修内核自身缺陷")。 + +### ⚠️ 一条实测教训(值得复用) +**测试跑的是 `lib/` 编译产物** ⇒ 我改完 `ws.ts` 后**忘记重新 build**,导致 `hub.ts`(新的)与 `ws.ts`(旧的)不一致 ⇒ 测试连挂两次,症状是"断言该断开却投递了"而**诊断输出显示注入值正确** ⇒ 一度误以为是 Node 属性遮蔽问题。✅ **定位法**:打印两个**独立观测点**(注入值 ✅ 但 `backpressureEvents=0`)⇒ 一眼看出"读到的值对、但走的是旧代码" ⇒ **改完必 build**。 +另附一条真知识:**Node 的 `writableLength` 是原型 getter**(非实例自有属性),实例级 `Object.defineProperty` 可覆盖;子类原型 getter 也有效(本夹具用实例 getter 最稳)。 + +### 文档同步 +`01-规范/09-…契约.md`:**§8 判据表升级为四档**(store 零改动/hub 纯插入/ws 允许改但删除行**只限投递点**/routes 纯插入)+ **新增 §11「投递可靠性与容量边界」**(11.1 背压三条行为与判据 / 11.2 阈值依据与观测面 / 11.3 容量实测读数与单房上限)。 + +### 边界 +⛔ 未部署、⛔ 未动 47/106、⛔ 未 commit/push、⛔ 未动云安全组、⛔ 未改 A–E 五单结论。⛔ **未登记下一棒**。 +⚠️ **生产标定未做**:阈值仍为保守起点(4 MiB)⇒ 建议在真实流量下取一轮 `peakPendingBytes` 后校准(属观测/调参,⛔ 不是代码改动)。 +🔴 **一条待用户确认的连锁影响**:原先"背压后 `delivered` 会小于 `onlineIn`"的**预期没有发生**(因为设计上不虚减)⇒ 若后续要"按慢客户端计 skipped",那是**语义变更**,需重新拍板。 + +--- + +## 🔴 收口(32.8 万上下文强制)· IM 线 → 已开自动接续 + +**用户本轮三件事**(原话):「创建接续会话把IM的改造优化处理完成」「然后更新IM接入文档」「并回复插件的对接文档」。 + +### 已落 +- **接续包** ⇒ `接续包_IM接入面收尾与插件对接答复_20260926.md`(md5 **`da0721b2bb28150e57ee423c6c3dfff5`**,2026-09-26 00:32 冻结)。 +- **接续棒** ⇒ automation **`a31da051-9d0b-445b-b232-9f5617ff1fe9`**(一次性 · `scheduledAt = 2026-09-26T00:38` · `cwds = ["E:/ProgramData/AIProject/aliyun-dsh-server"]` 正斜杠)。 + - ✅ **cwds 形态已实测确认**(view 了 09-24 那条 IM 接续棒)= **正斜杠 + 大写盘符** ⇒ 与状态层口径一致;⚠️ IM 线入口 §0 头部那条「小写盘符 + 反斜杠」的规定与实测**不符**,以实测为准(⛔ 未改入口那句,待专门清理)。 + - prompt 含**口径门禁 md5**、开机四步、工具上限 40。 +- **入口** ⇒ `接续入口_IM线_20260922.md §0` 追加本回合收口行(背压修复 + 三份评估 + 接续登记)。 +- **今日日志** ⇒ 本文件上述各节。 + +### 接续包的三项「未完成」(新棒依据) +1. **IM 改造收尾三件**:① 桥 5 端点**端到端真跑**(只过单测、从未真跑)② **部署 47**(R8,动手前声明)③ **生产标定背压阈值**(改参数、⛔ 不改代码)。 +2. 🔴 **反向通道(发言规则 / 事件回调)= 待用户拍板** —— ⛔ 新棒不许替原会话拍成「已定」、⛔ 不许自决补做;用户未表态则收口时再上抛。原会话判断(可推翻):**不必急着补**(降级三条已够:房间 `config` / 出向纠偏 / `host.subscribe()` 轮询)。 +3. **文档 + 答复**:09 已到 §11,**缺「插件作者快速上手」**(用法散在 §10.3,且 `poc/business-plugins-im/` 三个示例是**旧契约**,未含第 8 项出向与跨进程桥);插件对接答复件**只能在本工作区产出、由用户转交**(⛔ 不得改 `dsh-plugin-partment` 一个字节)。 + +### 边界 +⛔ 未部署 / 未 commit / 未 push / 未动 47/106 / 未动云安全组。本会话**不再开新任务**(成本按当前水位线性放大)。 + +## IM 接入面收尾棒(00:38–01:0x) +- 承 `接续包_IM接入面收尾与插件对接答复_20260926.md`(md5 已校);域锁 4 域,本棒自释放。 +- **端到端真跑**(本棒核心):`tmp/im-bridge-e2e-20260926.mjs` 真起平台 + 真 HTTP + 插件侧 SDK,**23 例 fail 0**。 + 顺带**抓出真缺陷并修**:`src/web/routes/im.ts` 的 `bridgeAuth` 把请求头里的**包名**当内部 id 查注册表, + 而注册表按 `pluginIdOf(包名)` 存 ⇒ 出向恒 `plugin-unavailable`(第 8 项在生产不可达)。 + 单测抓不到的原因:`test/im-plugin-bridge.test.mjs` 刻意 mock fetch、直接递归一化 id,**不经过这层翻译**。 + ⇒ 判据沉淀:**"注册面"的纯单测不能替代"经过鉴权层翻译"的真 HTTP 验收**。 +- **部署 47**:`/opt/dshs` 推 `lib/im/**` + `lib/web/routes/im.js` + `sdk/**`;备份 `/opt/dsh/backups/im-bridge-predeploy-20260926.tgz`; + 重启 `dshs` = active,`/api/im/plugins/bridge`、`/api/im/stats` 均 401(路由已加载)。106 不需要(IM 宿主在 Manager)。 + ⚠️ **坑**:本机 tar 会保留本地 uid ⇒ 47 上落地文件属主变 `197108`,须 `chown -R root:root` 修回;下次加 `--owner=root --group=root`。 +- **文档**:09 新增 §12「插件作者快速上手」(只增不改);**答复件** `交付物/插件对接答复件-20260926.md`。 +- **未做**:① 背压阈值生产标定(需 admin 会话/真实流量)② 反向通道(待用户拍板)③ 09 的 #2/#4/#5/#8 四条纯文档整改 ④ 可跑的最小示例工程。 + +## 第 36 棒续作二 · 规划棒(2026-09-26 06:29–06:5x)· 会话 `p36c-platform-versioning` + +**用户原话**:「你就是平台线,看看如何修复(还有 worker 如何更新包,是不是应该有个机制,现在的网络结构各种节点和设备,如何进行版本管理和包更新机制)」⇒ **授权扩到平台线**,但本轮按「规划与执行分离」只读取证 + 出方案,⛔ 未改 `src/**` 一行。 + +### 取证要点 +- **机制骨架齐全**(⛔ 不是「没有机制」):worker `启动即拉 + 定时轮询`(`src/worker/agent.ts:816-819`)、`POST /shared-layer/pull`(`:812`,带 agent token)、主流程 `syncSharedLayerOnce`(`:373`)、对账 `POST /api/plugins/shared/node/sync`(`:422-427`)、Manager 对账面(`src/web/routes/business-plugins.ts:373-428`)、权威清单 `.manifest.json`、对账快照 `.node-sync-reports.json`。 +- 🔴 **超时根因(D1)**:`src/worker/agent.ts:376` `const timeoutMs = opts.timeoutMs ?? 20000` —— **对账面(`:426`)与产物下载面(`:508`)共用同一 20 s 常量** ⇒ 2.0 MB 包跨云拉不完 ⇒ `AbortError: The operation was aborted due to timeout`(与 106 上报串**逐字吻合**)⇒ 106 永远停在旧版。 +- 其余缺陷:D2 无重试退避 · D3 失败历史不可见 · D4 只有「最新一份」版本 · D5 无 channel/定向 ⇒ 不能灰度 · D6 **设备(桌面端)不在分发链上** · D7 无全局版本矩阵视图 · D8 台账与实时视图不一致。 + +### 产出 +`$WS/交付物/版本管理与包更新机制-现状与方案-20260926.md` —— 现状取证(带行号)+ 8 条缺陷 + **三层方案**(A 止血/B 版本管理机制/C 跨区拓扑)+ 只读前置 + 决策点 + **8 段交接要素**。⇒ **同时是下一棒的「唯一执行单」**。 + +### 🔴 未登记下一棒 +§5 有 **1 项待拍板**(修到什么程度:只修超时 A / A+全局视图 C / A+完整版本管理 B)⇒ 依「要用户拍板的,等拍了再新建接续会话」**⛔ 未登记 automation**。我的倾向:**A 先落、C 紧随**。 diff --git a/交付物/版本管理与包更新机制-现状与方案-20260926.md b/交付物/版本管理与包更新机制-现状与方案-20260926.md new file mode 100644 index 0000000..a8e1cc2 --- /dev/null +++ b/交付物/版本管理与包更新机制-现状与方案-20260926.md @@ -0,0 +1,125 @@ +# 平台线 · 版本管理与包更新机制 —— 现状取证 + 缺陷 + 方案(规划棒产出) + +> **会话**:`p36c-platform-versioning`(2026-09-26 06:29–)|**工作区**:`E:/ProgramData/AIProject/aliyun-dsh-server` +> **上游**:用户 2026-09-26 06:2x 原话「**你就是平台线,看看如何修复(还有 worker 如何更新包,是不是应该有个机制,现在的网络结构各种节点和设备,如何进行版本管理和包更新机制)**」⇒ 本棒**授权扩到平台线**(`src/**` 可改),但本轮**只读取证 + 出方案**(规划与执行分离)。 +> **上下文**:承第 36 棒续作(交付件 `MCN数据面接入-阶段二-切档与收口-20260925.md §9`)—— 106 上报 `stale:[dsh-plugin-mcn-suite]` + `failed:[timeout]`。 +> **本文件同时是下一棒的「唯一执行单」**(§6 为 8 段交接要素)。 + +--- + +## 1 现状取证(代码级 · 全部带 `文件:行号`,⛔ 未改一行) + +### 1.1 已有机制:**节点主动拉(pull)+ 主节点对账告知** + +| 环节 | 实现 | 位置 | +|---|---|---| +| worker 定时拉 | 启动即拉一次 + `setInterval(…, pullIntervalMs)` | `src/worker/agent.ts:816-819` | +| worker 手动拉 | `POST /shared-layer/pull`(带 `AGENT_TOKEN_HEADER`,⛔ 不进免凭据白名单) | `src/worker/agent.ts:812` | +| 拉取主流程 | `syncSharedLayerOnce(opts)` | `src/worker/agent.ts:373` | +| 第 1 步:对账 | `POST /api/plugins/shared/node/sync`,body = 本机实况 `inventory[]` | `src/worker/agent.ts:422-427` | +| 第 2 步:按差异拉产物 | 下载 + 校验 + 解包 + 覆盖 | `src/worker/agent.ts:~455-520` | +| Manager 侧对账面 | 差异告知(`missing`/`stale`/`extra`)+ 落对账快照 | `src/web/routes/business-plugins.ts:373-428` | +| 权威清单 | `/.manifest.json`(`version`/`tgzSha256`/`treeSha256`/`fileCount`/`backupTgz`/`rollbackTgz`) | 真机读 | +| 对账快照 | `/.node-sync-reports.json`(**每 host 只留最新一条**,共 20 条) | `business-plugins.ts:386,416-428` | +| 共享层落点 | `config.bundledPluginDir` = `/var/lib/dshs/bundled-plugins`(root 所有、实例只读挂 `--ro-bind-try`) | `src/config.ts` + `orchestrator.ts` | +| 装配 | `app.supervisor.applyPlugins(userId, items)`;集群形态 → worker agent `POST /plugins/apply` | `business-plugins.ts:640-641, 2261` | + +**结论**:机制**骨架齐全**(拉取 / 对账 / 指纹 / 回滚素材 / 装配 / 定时),⛔ **不是"没有机制"**。 + +### 1.2 缺陷清单(按影响排序) + +| # | 缺陷 | 证据 | 影响 | +|---|---|---|---| +| **D1** | **下载超时硬编码 20 s,且与"对账"共用同一常量** | `src/worker/agent.ts:376` `const timeoutMs = opts.timeoutMs ?? 20000`;对账面 `:426`、下载面 `:508` 都用它 | 2.0 MB 包跨云(47↔106 经 relay)拉不完 ⇒ `AbortError: The operation was aborted due to timeout`(与 106 上报串**逐字吻合**)⇒ **106 永远停在旧版** | +| **D2** | **无显式重试/退避** | 失败只写进 `failed[]`,靠下一个定时轮再试 | 偶发网络抖动 = 白等一整轮 | +| **D3** | **失败历史不可见** | `.node-sync-reports.json` 每 host 只留最新一条(`business-plugins.ts:417`) | 无法回答"这个节点连续失败几次了" | +| **D4** | **版本管理只有"最新一份"** | manifest 每 flat 只有一组 `version`/指纹;`rollbackTgz` 只保**上一版** | 无法:多版本并存 · 灰度 · 按节点/设备定向 · 降级到任意历史版 | +| **D5** | **无"目标版本"声明面** | 节点拉取目标是"Manager 当前共享层",无 channel/版本钉选 | 不能"先让测试节点升,再全量" | +| **D6** | **设备(桌面端)不在分发链上** | 分发链只覆盖 worker;桌面端节点/设备无包更新通路(本轮未取证到任何设备侧拉取实现) | 设备侧版本无从管理 | +| **D7** | **平台侧无全局版本矩阵视图** | 只能逐 host 看 `reports`(且只留最新) | 运维要"一屏看全节点版本与漂移",现在做不到 | +| **D8** | **台账状态与实时视图不一致**(关联发现) | `dsh_instances.status=stopped` vs 实时 `running@20001` | **影响一切"实例是否在跑"的判断**(第 36 棒已被误导过一次) | + +--- + +## 2 方案(分层:先堵血、再补机制、后做拓扑) + +### 2.1 层 A —— **立即修复**(小改、可独立验证、⛔ 不动数据模型) + +| 项 | 做法 | 判据 | +|---|---|---| +| **A-1 超时分离** | 新增 `syncTimeoutMs ?? 20000`(对账)与 `downloadTimeoutMs ?? 300000`(产物下载)两个参数,`:426` 用前者、`:508` 用后者 | 106 拉 2 MB 包成功;`failed` 清空 | +| **A-2 按大小动态超时** | 若有 `content-length`,`effective = max(60s, size / 已知最低速率 + 余量)`,并设上限 | 大包不再靠猜 | +| **A-3 重试 + 退避** | 单轮内失败重试 2 次(指数退避 2s/8s),仍失败才写 `failed` | 抖动可自愈 | +| **A-4 失败留痕** | `failed` 记 `{flat, attempts, lastError, firstFailedAt}`;对账快照保留改为「每 host 每条 flat 一条历史」或另落 append-only 日志 | 可回答"连续失败几次" | +| **A-5 台账纠偏** | 单独立项核查 `dsh_instances.status` 写回路径(属 D8,⛔ 不在本棒范围) | 台账 == 实时视图 | + +> ⚠️ **A-1/A-3 是"纯插入"型改动**(不破回归)⇒ 符合本平台既有「新增能力 ⇒ 纯插入;修 bug ⇒ 可改既有逻辑但须回归用例 + 新行为可观测 + 阈值可注入」纪律。 + +### 2.2 层 B —— **版本管理与更新机制**(机制补全) + +**B-1 版本目录化**:共享层从「一份包」改为「**一份包 + 版本目录**」—— +``` +// # 现状:只有当前版 +/.versions/// # 建议:历史版本留档(tgz + treeSha256) +/.manifest.json # 每 flat 增:current / channel / history[] +``` +⇒ 支持**任意版本回滚**(不再只有 `rollbackTgz` 一档)。 + +**B-2 目标版本声明(channel)**:manifest 每 flat 增 `channel: { stable: , canary: }` + 节点侧 `pullChannel`(env)。 +⇒ 节点按 channel 拉 ⇒ 天然支持**灰度和定向发布**(测试节点走 canary)。 + +**B-3 节点/设备下发列表(可选)**:`channel` 之外再支持 `pinnedHosts: { : }` ⇒ 单节点钉版/降级。 + +**B-4 设备(桌面端)纳入**:设备侧实现同一个 `syncSharedLayerOnce` 语义(或复用打包好的 SDK/客户端),凭据走**设备 grant**(序46/47 已有设备凭据体系)。 +⇒ 设备与 worker 用**同一套对账协议**,只是 `hostId` 换成设备 id。 + +**B-5 全局版本矩阵**:新增 Manager 侧 `GET /api/plugins/shared/status` —— 返回 `{ flat → { current, 各 host 的 version/指纹/drift/最近成功时间 } }`。 +⇒ **一屏看全节点与设备**(用户问的"如何进行版本管理"的运维面)。 + +### 2.3 层 C —— **拓扑与跨区联邦**(架构级,承 `架构设计/覆盖网络-顶层架构全貌.md`) + +- **内容源归属**:每个 **Manager 各持有自己的共享层**(= 各区 Manager 自带内容源),节点/设备只向**自己所属区的 Manager** 拉 ⇒ ⛔ 不做"全网单点源"(否则跨区带宽与单点故障)。 +- **跨区一致性**:包由**版本号 + treeSha256** 标识 ⇒ 各区可独立发包,**同一版本号必须字节一致**(发布时校验 `tgzSha256`);不一致 = 发布失败。 +- **离线/弱网**:拉取失败**不影响实例运行**(已有语义:本轮不动本地文件)⇒ 版本更新是**最终一致**,不是强一致。 +- **安全**:包校验用 `tgzSha256` + 解包后 `treeSha256`(已有);🔴 建议补**发布者签名校验**(承覆盖网络线"根密钥只管授权撤回签名者"的口径)—— ⚠️ 属跨线议题,**本棒只登记不设计**。 + +--- + +## 3 本轮**立即能定**与**需用户拍板**的分界 + +- **立即能做(无需拍板)**:层 A 全部(病因明确、纯技术);层 B-5(只读视图,不加新概念)。 +- **需拍板**:**层 B 的深度** —— 做到"单版本 + 任意回滚"(B-1)就够,还是直接做到"channel 灰度 + 定向钉版"(B-1~B-3)?(见 §5 待拍板) +- **跨线登记**:B-4(设备纳入)依赖序46/47 的设备凭据体系;层 C 属覆盖网络线架构层 ⇒ **只登记,不本线实施**。 + +--- + +## 4 只读前置(下一棒开工先跑,⛔ 不许跳) + +1. `"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" "$WS/state.py" --online` +2. `bash $DOC/07-scripts/preflight-lock.sh "<会话名>" src/worker/agent.ts src/web/routes/business-plugins.ts` +3. 🔴 **`src/worker` 属机制层** ⇒ 按域锁规则「机制层必须独占」;**开工前必须确认无其他会话在跑**(`state.py` 看持有者活性)。 +4. 真机只读:`cat /var/lib/dshs/bundled-plugins/.node-sync-reports.json`(47)+ `systemctl cat dshs-worker.service`(看 pull 相关 env)。 + +## 5 决策点(**待拍板 = 1 项**) + +**要用户定的是**:106 拉包超时要修到什么程度 —— 只把超时调大让它能拉完,还是顺手把"版本管理"补成可灰度可回滚的机制? + +- **A 案(只修超时,层 A)**:优点 = 改动小(两处常量 + 一个重试)、当天可验、106 立刻能拿到修复;缺点 = 版本管理仍是"最新一份",下次要灰度/回滚还得再做一轮。 +- **B 案(层 A + 层 B)**:优点 = 一次把"节点与设备的版本管理与更新机制"补齐(含全局矩阵视图);缺点 = 要动 manifest 结构与装配口径,回归面更大,且要新增概念(channel),周期长。 +- **C 案(层 A + 只加全局矩阵视图 B-5)**:优点 = 先解决"看不见"的问题(D7),成本低,为后续灰度留观测底座;缺点 = 仍无灰度/多版本能力。 +- **我的倾向**:**A 先落、C 紧随**(可推翻)—— 106 上有真实用户在等修复,A 是止血;B-5 是纯读视图、不加概念,风险低且立刻让版本漂移可见;**层 B 的通道化等有过一次真实灰度需求再做**。 + +## 6 八段交接要素(下一棒 = 执行棒) + +1. **目标**:修 D1(106 拉包超时)并按用户拍板落地层 A(/+B-5/+B)。 +2. **只读前置**:同 §4。 +3. **范围**:`src/worker/agent.ts`(`syncSharedLayerOnce` 超时/重试)+(按拍板)`src/web/routes/business-plugins.ts`(对账面)+ 新增只读状态路由;⛔ 不改 `manifest` 结构之外的既有语义;⛔ 不动 `07-scripts/`。 +4. **决策点**:§5 那一项(须先拿到回话再开工)。 +5. **步骤**:① 只读复核 §1 全部行号仍在 ② 落 A-1/A-2/A-3/A-4 ③ 单测(若有)+ `npm test` ④ `build` ⑤ 47 `restart dshs-worker`⑥ 读 106 对账快照确认 `stale`/`failed` 清空 ⑦(拍板含 C/B 时)落状态路由 ⑧ 收口。 +6. **验收**:**主判据 = `.node-sync-reports.json` 里 `w-106` 的 `stale` **不含** `dsh-plugin-mcn-suite` 且 `failed` 为空**;副判据 = 34/362/5 影子读不变、红线零新增、`npm test` 不劣化。 +7. **回滚**:`git revert` 本棒提交 + `restart dshs-worker`;⚠️ 对账/下载失败**本就不动本地文件** ⇒ 无数据风险。 +8. **回报格式**:逐条附命令原文 + 输出原文 + 退出码;未达成具名。 + +## 7 本轮已改文件(⛔ 仅本工作区产物,未碰 `src/**`) + +- 本文件(新建)|入口 §0/§2 + `$DOC/05-交接单/README.md §一` + 当日日志(收口行) diff --git a/接续入口_插件投放与分库线_20260922.md b/接续入口_插件投放与分库线_20260922.md index 3342e18..1d44073 100644 --- a/接续入口_插件投放与分库线_20260922.md +++ b/接续入口_插件投放与分库线_20260922.md @@ -9,6 +9,14 @@ ## §0 状态(**最新一行 = 执行依据**) +- 🔴 **2026-09-26 06:29–06:5x|用户授权「你就是平台线」⇒ 第 36 棒续作二 · 规划棒(`p36c-platform-versioning`)—— 只读取证 + 出方案,⛔ 未改 `src/**` 一行;⛔ 未登记下一棒(撞 1 项待拍板)** —— + **用户原话**:「**你就是平台线,看看如何修复(还有 worker 如何更新包,是不是应该有个机制,现在的网络结构各种节点和设备,如何进行版本管理和包更新机制)**」⇒ **本轮按「规划与执行分离」只产出方案**。 + **成果** ⇒ `交付物/版本管理与包更新机制-现状与方案-20260926.md`(**同时是下一棒的「唯一执行单」**,含 8 段交接要素)。要点: + **① 现状**:机制骨架**齐全**(worker 启动即拉+定时轮询 `src/worker/agent.ts:816-819`、`POST /shared-layer/pull` `:812`、主流程 `syncSharedLayerOnce` `:373`、对账 `:422-427`、Manager 对账面 `business-plugins.ts:373-428`、权威清单 `.manifest.json`、对账快照 `.node-sync-reports.json`)⇒ ⛔ **不是「没有机制」**。 + **② 超时根因(D1)**:`src/worker/agent.ts:376` `const timeoutMs = opts.timeoutMs ?? 20000` —— **对账面 `:426` 与产物下载面 `:508` 共用同一 20 s 常量** ⇒ 2.0 MB 包跨云(47↔106 经 relay)拉不完 ⇒ `AbortError: The operation was aborted due to timeout`(与 106 上报串**逐字吻合**)⇒ **106 永远停在旧版**。 + **③ 缺陷另 7 条**:D2 无重试退避 · D3 失败历史不可见(每 host 只留最新一条)· D4 版本只有「最新一份」(`rollbackTgz` 仅一档)· D5 无 channel/目标版本声明面 ⇒ 不能灰度 · D6 **设备(桌面端)不在分发链上** · D7 平台侧无全局版本矩阵视图 · D8 台账 `dsh_instances.status` 与实时视图不一致(承 §9)。 + **④ 方案分层**:**层 A 立即修复**(超时分离 20 s↔300 s/按大小动态/重试退避/失败留痕 ⇒ 纯插入型)· **层 B 版本管理机制**(版本目录化 + channel 灰度 + 定向钉版 + 设备纳入 + 全局矩阵视图)· **层 C 拓扑与跨区联邦**(**每区 Manager 各持内容源**,节点只向本区拉;版本号+`treeSha256` 为跨区一致性标识;⛔ 不做全网单点源)。 + **⑤ 待拍板 1 项**:只修超时(A)/A+全局视图(C)/A+完整版本管理(B)—— 我的倾向 **A 先落、C 紧随**(106 有真实用户在等,A 是止血;C 是纯读视图、不加概念)。🔴 **拍板未到 ⇒ ⛔ 未登记下一棒**。 - 🔴 **2026-09-26 00:17–00:4x|第 36 棒 续作(用户两条拍板落地,`p36b-exec-plugin-dataline`)—— A2「真实例进程内」面 ✅ 达成 · A8 改判「机制在跑 · 该件拉取超时」· ⛔ 本条不新增下一棒** —— **拍板① = A(启动实例取进程内读数)**:🔴 **纠正上一棒** —— 实例**一直在跑**(端口 `20001`;实时 `GET /api/admin/users//dsh/status` ⇒ `running:true`),上一棒所据 **PG `dsh_instances.status=stopped` 是滞后失真**。⛔ 本棒**未启动、未停止**任何实例。**进程内读数**:`curl --compressed -H "Cookie: dsh_token=<实例 token>" http://127.0.0.1:20001/mcn/api/accounts` ⇒ **`200`** + 13,155 B 正文(`{"ok":true,"total":5,"page":1,…,"account_name":"王微斯",…}`))⇒ `total:5` 与影子读 `hot_accounts 5=5` 一致 ⇒ **A2 全达成**。 **拍板② = 「worker 应该使用插件更新机制」(⛔ 不是给 106 开 ssh 通道)**:机制**已落地且在跑** —— `src/worker/agent.ts:812` `POST /shared-layer/pull`(带 agent token)+ `:816-819` **启动即拉一次 + 定时轮询**;源码注释原话「**`restart dshs-worker` 就等于完成一次共享层同步,⛔ 不必再手工 scp**」;形态 = **节点主动拉(pull)+ 主节点对账告知**(`src/web/routes/business-plugins.ts:373-377`),快照落 `/.node-sync-reports.json`。**真机对账快照**(`at` ⇒ **2026-09-25 23:52:11 CST**,即我 23:22 `share 0.5.1` 之后):`w-106` · `authoritative:4` · `missing:[]` · **`stale:["dsh-plugin-mcn-suite"]`** · **`failed:[{"flat":"dsh-plugin-mcn-suite","error":"The operation was aborted due to timeout"}]`** ⇒ **106 确实在自主拉取,不是「没通道」**;但**该件拉取超时失败** ⇒ 仍为旧版 ⇒ **A8 由「未取证」改判为「机制在跑 · 该件超时失败」**。🔴 **属平台侧参数/实现问题**(2.0 MB 包跨云拉取超时)⇒ 依硬约束**只报告**(修复方向:调大超时 / 分块续拉 / 失败退避重试)。