Files
dsh_ai1net_server/.workbuddy/memory/2026-09-26.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

175 lines
21 KiB
Markdown
Raw 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.
# 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**。