Files
dsh_ai1net_server/交付物/版本管理与包更新机制-现状与方案-20260926.md
T
admin 90acb1c030 docs(平台线): 补 §9 收口记录(命令原文+输出原文+退出码)
- 本件新增 §9:落盘四处 / 事故与无损复原 / 提交推送对账 / 文档库侧 / automation / 锁
- 日志追加上节「收口(06:4x–06:5x)」:提交 2a1226c · 推送 · 对账 0 0 · 仅 3 件入库 · 未碰他线

判据留档要点
- 入口被清 0 字节事故:复核命令 git hash-object == git rev-parse HEAD:<file>(1d44073b…)
- 新增铁律:禁用 open(...,"wb").write(表达式) 复合式(open 先截断,表达式后求值)
- docs-sync-check ❌ 差异 21+4 全为他线在途,非本线引入
2026-09-26 06:47:23 +08:00

21 KiB
Raw Blame History

平台线 · 版本管理与包更新机制 —— 现状取证 + 缺陷 + 方案(规划棒产出)

会话: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 只读前置(下一棒开工先跑,⛔ 不许跳)

  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 项)

🔴 已拍板(2026-09-26 06:36 · 用户本人原话):「C 方案,然后B,这次就用版本更新机制去更新网络上各节点,子节点和设备」。

口径对齐(⛔ 执行棒务必按本行理解,勿被 §2 的层名混淆):

  • 用户说的 C 案 = 本文 层 A(止血)+ 层 B-1~B-4(版本目录化/channel 灰度/定向钉版/设备纳入)——「完整版本管理」。
  • 用户说的 B 案(随后做) = 本文 层 B-5(全局版本矩阵视图)。
  • 层 C(跨区拓扑)本次 ⛔ 不实施(属覆盖网络线架构层);但实现须与其口径兼容:每区 Manager 各持内容源、节点只向本区拉、同版本号须字节一致。
  • 收尾必须用这套机制真跑一次分发:[email protected](含以后版本)→ 47 本机 worker + 106 子节点 + 设备(桌面端),并读数验证。
  • ✅ 执行棒已登记 = automation da73ab87-fb2f-47f0-a8d8-8ea3ad35eb19(一次性 · 2026-09-26T06:52)。

原三选项(存档):

  • 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 §一 + 当日日志(收口行)

8 执行棒细化(拍板后新增 · 2026-09-26 06:36)

8.1 子阶段顺序(⛔ 不许跳、不许换序)

子阶段 内容 完成判据
S1 · 止血 层 A-1~A-4(拆超时 20 s↔300 s/按大小动态/重试退避 2s·8s/失败留痕) 106 对账快照 stale 不含 dsh-plugin-mcn-suite、failed 为空
S2 · 完整版本管理 层 B-1 版本目录化 + B-2 channel(stable/canary)+ B-3 定向钉版 + B-4 设备纳入 能"只让某节点/设备升到指定版本"、可回滚到任意历史版;设备侧真跑通一次拉取
S3 · 全局矩阵视图 层 B-5 GET /api/plugins/shared/status 一屏返回「每 flat × 每 host/设备」的版本、指纹、漂移、最近成功时间
S4 · 真跑分发(收尾必做) 用 S1–S3 落成的机制把 0.5.1 分发到 w-47 + w-106 + 设备 三处读数一致;w-106 快照 stale/failed 清空

8.2 未知项与处置(⛔ 不许"假装知道")

  1. 设备(桌面端)侧载体未取证(承 D6)⇒ 开工第一件事就是取证:桌面端有没有「包/插件目录 + 拉取入口」、凭据走哪条(序46/47 的设备 grant?)。有 ⇒ 按同一对账协议接入;没有 ⇒ 具名报告缺口 + 给最小接入方案,⛔ 不硬造。
  2. manifest 是共享层旁的旁挂状态文件 ⇒ 改其结构须向后兼容(老节点读新 manifest 不能崩)。
  3. src/worker 属机制层 ⇒ 域锁必须独占(开工前确认无其他会话在跑)。

8.3 必须遵守的既有纪律

  • 新增能力 ⇒ 纯插入;修 bug ⇒ 可改既有逻辑但须回归用例 + 新行为可观测 + 阈值可注入(超时/重试次数一律走 env,⛔ 不写死)。
  • 测试跑 lib/ ⇒ 改完必 build(否则新旧产物混跑,症状像"注入无效")。
  • ⛔ 不改 07-scripts/;⛔ 不动 config/crypto/isolation/index 等机制层其他文件(只碰必需者且先报告)。
  • 生产变更(restart dshs-worker / 47)可直接做,动手前一句话说明;不可逆操作先报清单。

9 收口记录(2026-09-26 06:36–06:5x · 会话 p36d-platform-versioning)

口径:每条附命令原文+输出原文+退出码。⛔ 未 ssh 真机、⛔ 未改 src/**、⛔ 未碰他线在途改动。

9.1 落盘四处(命令 + 输出 + 退出码)

# 落点 命令(节选) 输出原文 rc
1 本件 §5 + §8 patch-c3.py(步 1) DOC_S5_OK / DOC_S8_OK 0
2 入口 §0 拍板行 Edit 精确片段替换(锚 = - 🔴 **2026-09-26 06:29–06:5x|用户授权) Successfully edited file 0
3 入口 §2 续作二/三注 Edit(锚 = ⛔ 平台侧问题(拉取超时/台账滞后)**只报告不改码**。详解 ⇒ 交付件 **§9**。) Successfully edited file 0
4 $DOC/05-交接单/README.md §一 第 44 行 Edit(锚 = 🔴 **2026-09-26 06:29 用户授权「你就是平台线」⇒ 已出《版本管理与包更新机制-现状与方案-20260926》**) Successfully edited file 0
5 .workbuddy/memory/2026-09-26.md Edit(追加「第 36 棒续作三 · 拍板落地棒」节) Successfully edited file 0

校验读数(wc -c / grep -c):

入口   344849 B / 986 行   grep -c "2026-09-26 06:36" = 2   grep -c "续作三" = 1
本件   14xxx B             grep -c "已拍板(2026-09-26 06:36" = 1   grep -c "^## 8 执行棒细化" = 1
README 62575 B             grep -c "06:36 用户拍板" = 1   grep -c "da73ab87" = 1
日志   20091 B             grep -c "p36d-platform-versioning" 节 = 1

9.2 🔴 事故:入口被清 0 字节(已自愈 · 无损)+ 新增铁律

  • 触发:tmp/p36/patch-c3.py 第 70 行 open(P, "wb").write(b"\n".join(parts)) —— NEW0 漏 .encode("utf-8")。
  • 输出原文:Traceback … line 70, in <module>\ TypeError: sequence item 11: expected a bytes-like object, str found,RC=1。
  • 真实伤害(比 traceback 严重得多):复合式求值顺序 = 先 open(P,"wb")(此刻文件已被截断为 0)→ 再求值 join() ⇒ join 抛错时入口已经没了。若只看 traceback 会误判成"没写进去"。
  • 发现:wc -c 接续入口_插件投放与分库线_20260922.md ⇒ 0。
  • 复原(无损):
git checkout -- "接续入口_插件投放与分库线_20260922.md"        # rc=0
wc -c  → 341330 ; wc -l → 968 ;grep -c "2026-09-26 06:29" → 1
git hash-object <file>              → 1d44073bdd40b4a3becfd45f2cd26f4cb78796ad
git rev-parse HEAD:<file>           → 1d44073bdd40b4a3becfd45f2cd26f4cb78796ad   (逐字节一致)
git status --porcelain <file>       → (空 = clean)

前提核对:HEAD 版含 06:29 规划棒行、git fetch 后 git rev-list --left-right --count origin/master...HEAD = 0 0 ⇒ 复原不丢任何已落内容。

  • 🔴 新增铁律(已写入入口 §0 事故留档):改共享文件只准 ① Edit 精确片段替换,或 ② 「先算全量 bytes 再一次性写」;⛔ 禁用 open(...,"wb").write(表达式) 复合式(表达式求值前文件已被清空)。同源风险 = 任何"先 open 再算内容"的写法。
  • 补救方式:改用 Edit 工具做四处插入,不再走整文件覆盖。

9.3 提交 / 推送 / 对账(命令 + 输出 + 退出码)

$ git add -- "交付物/版本管理与包更新机制-现状与方案-20260926.md" \
              "接续入口_插件投放与分库线_20260922.md" ".workbuddy/memory/2026-09-26.md"
$ git -c core.quotepath=false diff --cached --name-only
.workbuddy/memory/2026-09-26.md
交付物/版本管理与包更新机制-现状与方案-20260926.md
接续入口_插件投放与分库线_20260922.md                       ← 仅 3 件,⛔ 无 tmp/、⛔ 无中间产物、⛔ 无 05-交接单
$ git diff --cached --stat
 3 files changed, 94 insertions(+), 1 deletion(-)

$ git -c core.autocrlf=false commit -F -        → RC=0 ;HEAD = 2a1226c
$ git push origin master                        → RC=0
To work.alotbuy.com:maogeigei/dsh_shenxian_workspace.git
   a38d8b6..2a1226c  master -> master

$ git fetch -q origin ; git rev-list --left-right --count origin/master...HEAD
0	0                                            ← ✅ 对账归零
$ test "$(git rev-parse HEAD)" = "$(git rev-parse origin/master)" → ✅ 一致

⛔ 未 git add -A;⛔ 未代提交他线在途改动(CODEBUDDY.md / state.py / .codebuddy/rules/server-ops.md / .workbuddy/memory/MEMORY.md / 他线 接续入口_* 均未纳入)。

9.4 文档库侧

  • 本线本轮在文档库唯一改动 = $DOC/05-交接单/README.md §一 第 44 行。
  • git check-ignore -v ⇒ .gitignore:45:dsh-server-docs/05-交接单/ ⇒ 整目录被忽略、未被跟踪 ⇒ 按既有口径不入库(⛔ 未 git add -f)。
  • 推送前对账 07-scripts/docs-sync-check.sh ⇒ 结果: 存在差异 ❌(一致 268 / 内容不一致 21 / 仅本地 4 / 仅服务器 0)。🔴 差异面全为既有在途(07-scripts/*、08-skills/*、CODEBUDDY.md、01-规范/09、02-架构设计/*、04-调整方案/*、05-交接单/* 等他线在改)⇒ ⛔ 非本线引入、⛔ 本线不动不属于自己的文件;结论 = 如实记录、交文档库同步线处置。

9.5 automation 状态

id 名称 期望 实测
35e1bfbc-d247-4db2-9d90-b0580793736f 插件投放与分库线-第36棒-执行棒 PAUSED ✅ PAUSED
da73ab87-fb2f-47f0-a8d8-8ea3ad35eb19 版本管理与包更新机制-执行棒 ACTIVE · 2026-09-26T06:52 ✅ ACTIVE(排期 = 收口 06:44 + 8 min,落在 5~8 min 窗口内)

9.6 锁

  • 域锁 p36d-platform-versioning(域 = 交付物 / .workbuddy / 本线入口 / 05-交接单)= 持有中,反序释放:先 --release-publish,最后 ME="p36d-platform-versioning" --release-exec(⛔ 不带会话名 ⇒ fail-closed 拒)。
  • 复用既有会话名 p36d-platform-versioning 领发布锁(--claim-publish "p36d-platform-versioning" → ✓ 已持发布锁,rc=0)。