Files
dsh_ai1net_server/交付物/版本管理与包更新机制-现状与方案-20260926.md
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

249 lines
21 KiB
Markdown
Raw Permalink 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.
# 平台线 · 版本管理与包更新机制 —— 现状取证 + 缺陷 + 方案(规划棒产出)
> **会话**:`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)。