Files
dsh_ai1net_server/.workbuddy/memory/2026-09-26.md
T

174 lines
21 KiB
Markdown
Raw Normal View History

# 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<network, Set<hostId>>` **默认拒绝** ⇒ **加节点=配置动作**;一键加入(`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 紧随**。
## IM 反向通道定案棒(06:3x)
- **用户拍板**:「都需要实现完整,才能让插件接入,否则两边都要返工」⇒ 反向通道(平台 → 插件)**必须补、做完整**。
- **本棒只定案不实现**(会话预算 18 万+,硬约束)⇒ 产出施工规格 `接续包_IM反向通道实现_20260926.md`(含设计已定项/文件清单/验收/回滚/开工命令)+ 登记执行棒 automation `cce748b8`。
- **设计要点(已定项)**:实例侧 SDK **长轮询拉取**(保持"拨出式",⛔ 平台不打进实例、⛔ 不需实例可寻址、⛔ 不新增凭据);
`speak.check` 同步(带 deadline)· `event.deliver` 异步(不阻塞消息流);
超时缺省**放行**(与同进程 `binding.ts` 口径逐字一致,⛔ 不新增"不可达即冻结房间")+ 具名计数;契约可声明 `onTimeout:'deny'`(回合制必须声明);每实例有界队列,满则丢最旧并计数。
- **口径同步 3 处**:09 §10.4/§10.5/§12.5 · `交付物/IM插件跨进程桥-20260925.md §5/§6` · `交付物/插件对接答复件-20260926.md §0`。
- 🔴 **实测发现**:`p36c-platform-versioning` 会话持 `src/web`、`aliyun-dsh-server/交付物`、`.workbuddy`、`05-交接单` 域
⇒ 执行棒必须先 `preflight-lock.sh`;`src/web` 未释放时**先做不冲突部分**(新文件/文档),`routes/im.ts` 放最后。
- **教训(沿用)**:跨进程能力的**单测替代不了真 HTTP 验收**(本线 09-26 已实证一次)。
## 插件投放与分库线 · 第 36 棒续作三 · 拍板落地棒(2026-09-26 06:36)· 会话 `p36d-platform-versioning`
### 拍板原话(用户本人 · 06:36)
「**C 方案,然后B,这次就用版本更新机制去更新网络上各节点,子节点和设备**」
### 口径(⛔ 别被方案里的层名混淆)
- 用户说的 **C 案** = 层 A(止血 20 s↔300 s/按大小动态/重试退避/失败留痕)+ 层 B-1~B-4(版本目录化/channel 灰度/定向钉版/**设备纳入**)。
- **随后做** 的 **B 案** = 层 B-5(全局版本矩阵视图)。
- **层 C 跨区拓扑本次不做**(属覆盖网络线架构层),但实现须**兼容**其口径:每区 Manager 各持内容源、节点只向本区拉、同版本号须字节一致。
- **收尾必须真跑一次分发**:`[email protected]`(含以后版本)→ **47 本机 worker + 106 子节点 + 设备(桌面端)**,读数验证(`w-106` 对账快照 `stale`/`failed` 清空)。
- 🔴 **执行棒第一件事 = 取证设备(桌面端)侧载体**(承缺陷 D6「设备不在分发链上」):桌面端有无「包/插件目录 + 拉取入口」、凭据走哪条。**有 ⇒ 按同一对账协议接入;没有 ⇒ 具名报告缺口 + 给最小接入方案,⛔ 不硬造。**
### 产出(本棒只做记录落地,⛔ 未改 `src/**`)
- 唯一执行单 `交付物/版本管理与包更新机制-现状与方案-20260926.md`:§5 改为「🔴 已拍板」+ **§8 执行棒细化**(§8.1 S1→S4 顺序表/§8.2 未知项三条/§8.3 既有纪律)⇒ 14,893 B。
- 入口 `接续入口_插件投放与分库线_20260922.md`:§0 落 06:36 拍板行(最新一行 = 执行依据)+ §2 落「续作二/续作三」注 ⇒ 343,592 → **已含两节**,977 → 986 行。
- `$DOC/05-交接单/README.md §一` 第 44 行(04 单状态行)追加 06:36 拍板 + §8 细化 + 执行棒登记。
- ✅ **执行棒 automation = `da73ab87-fb2f-47f0-a8d8-8ea3ad35eb19`**(一次性 · `2026-09-26T06:52`);本线入口 automation `35e1bfbc-…` 保持 `PAUSED`。
### 🔴 本棒事故(已自愈 · 值得进技能)
`tmp/p36/patch-c3.py` 步 1 成功(交付件 §5/§8 落盘),步 2 报 `TypeError: sequence item 11: expected a bytes-like object, str found`(`NEW0` 漏 `.encode("utf-8")`)。**真正的伤害不在那行 TypeError** —— 复合式 `open(P,"wb").write(b"\n".join(parts))` 的求值顺序是 **先 `open()`(立即截断!)→ 再求值 `join()`**,join 抛错 ⇒ **入口被清成 0 字节**。
- **发现方式**:事后 `wc -c` 得 `0`(若只看脚本 traceback 只会以为"没写进去")。
- **复原**:`git checkout -- <入口>`;HEAD 版 = **341,330 B / 968 行**、含 06:29 规划棒行、且远端对账 `0 0` ⇒ **无损**;复原后 `git hash-object` == `git rev-parse HEAD:<file>` = `1d44073b…`。
- **判据(新增铁律)**:改共享文件**只准** Edit 精确片段替换,或「**先算全量 bytes 再一次性写**」;⛔ **禁用 `open(...,"wb").write(表达式)` 复合式**(表达式求值前文件已被清空)。同源风险:**任何"写前先 open 再算内容"的写法**都有此坑。
- ✅ **补救**:改用 Edit 工具做三处插入(入口 §0/§2、README §一),不再走整文件覆盖。
### 边界
⛔ 未改 `D:/github/dsh_shenxian/src/**` 一行(只读取证);⛔ 未 ssh 真机做任何变更(本棒纯记录落地 + 收口);⛔ 未碰他线 `src/web`·`routes/im.ts`。
### 收口(06:4x–06:5x)
- **提交** `2a1226c`(`docs(平台线): 版本管理与包更新机制 拍板落地(C案→B案 · 执行棒登记)`)→ **推送** `a38d8b6..2a1226c master -> master`(rc=0)→ **对账** `git rev-list --left-right --count origin/master...HEAD` = **`0 0`**、`HEAD == origin/master` ✅。
- **仅 3 件入库**:本件 + 本线入口 + 本日志;`git diff --cached --name-only` 自查**无** `tmp/`/中间产物/`05-交接单`;⛔ **未 `git add -A`**、⛔ **未代提交他线**(`CODEBUDDY.md`/`state.py`/`.codebuddy/rules/server-ops.md`/`.workbuddy/memory/MEMORY.md`/他线 `接续入口_*` 全部未纳入)。
- **文档库侧**:本线唯一改动 = `$DOC/05-交接单/README.md §一` 第 44 行;`git check-ignore -v` ⇒ `.gitignore:45` 整目录忽略、未跟踪 ⇒ **按既有口径不入库**(⛔ 未 `-f`)。推送前 `docs-sync-check.sh` = **❌(一致 268/不一致 21/仅本地 4/仅服务器 0)**,差异面**全为他线在途**(`07-scripts/*`、`08-skills/*`、`CODEBUDDY.md`、`01-规范/09`、`02-架构设计/*`、`04-调整方案/*`、`05-交接单/*`)⇒ **非本线引入、交文档库同步线处置**。
- **automation**:`35e1bfbc`(第 36 棒入口)= **`PAUSED`** ✅;`da73ab87`(执行棒)= **`ACTIVE` · `2026-09-26T06:52`** ✅(收口 06:44 + 8 min,落在 5~8 min 窗口内)。
- **锁**:域锁 `p36d-platform-versioning` 反序释放(先 `--release-publish` → 最后 `ME="p36d-platform-versioning" --release-exec`)。
- 📄 收口证据全文(命令原文 + 输出原文 + 退出码)⇒ 本件 **§9**。