11 KiB
平台线 · 版本管理与包更新机制 —— 现状取证 + 缺陷 + 方案(规划棒产出)
会话:
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 <Manager>/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 |
| 权威清单 | <sharedRoot>/.manifest.json(version/tgzSha256/treeSha256/fileCount/backupTgz/rollbackTgz) |
真机读 |
| 对账快照 | <sharedRoot>/.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 版本目录化:共享层从「一份包」改为「一份包 + 版本目录」——
<sharedRoot>/<flat>/ # 现状:只有当前版
<sharedRoot>/.versions/<flat>/<ver>/ # 建议:历史版本留档(tgz + treeSha256)
<sharedRoot>/.manifest.json # 每 flat 增:current / channel / history[]
⇒ 支持任意版本回滚(不再只有 rollbackTgz 一档)。
B-2 目标版本声明(channel):manifest 每 flat 增 channel: { stable: <ver>, canary: <ver> } + 节点侧 pullChannel(env)。
⇒ 节点按 channel 拉 ⇒ 天然支持灰度和定向发布(测试节点走 canary)。
B-3 节点/设备下发列表(可选):channel 之外再支持 pinnedHosts: { <hostId>: <ver> } ⇒ 单节点钉版/降级。
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 只读前置(下一棒开工先跑,⛔ 不许跳)
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" "$WS/state.py" --onlinebash $DOC/07-scripts/preflight-lock.sh "<会话名>" src/worker/agent.ts src/web/routes/business-plugins.ts- 🔴
src/worker属机制层 ⇒ 按域锁规则「机制层必须独占」;开工前必须确认无其他会话在跑(state.py看持有者活性)。 - 真机只读:
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 八段交接要素(下一棒 = 执行棒)
- 目标:修 D1(106 拉包超时)并按用户拍板落地层 A(/+B-5/+B)。
- 只读前置:同 §4。
- 范围:
src/worker/agent.ts(syncSharedLayerOnce超时/重试)+(按拍板)src/web/routes/business-plugins.ts(对账面)+ 新增只读状态路由;⛔ 不改manifest结构之外的既有语义;⛔ 不动07-scripts/。 - 决策点:§5 那一项(须先拿到回话再开工)。
- 步骤:① 只读复核 §1 全部行号仍在 ② 落 A-1/A-2/A-3/A-4 ③ 单测(若有)+
npm test④build⑤ 47restart dshs-worker⑥ 读 106 对账快照确认stale/failed清空 ⑦(拍板含 C/B 时)落状态路由 ⑧ 收口。 - 验收:主判据 =
.node-sync-reports.json里w-106的stale不含dsh-plugin-mcn-suite且failed为空;副判据 = 34/362/5 影子读不变、红线零新增、npm test不劣化。 - 回滚:
git revert本棒提交 +restart dshs-worker;⚠️ 对账/下载失败本就不动本地文件 ⇒ 无数据风险。 - 回报格式:逐条附命令原文 + 输出原文 + 退出码;未达成具名。
7 本轮已改文件(⛔ 仅本工作区产物,未碰 src/**)
- 本文件(新建)|入口 §0/§2 +
$DOC/05-交接单/README.md §一+ 当日日志(收口行)