feat(overlay): 内容块级寻址 + 实例逐步拉起 + 骨干选路 + 组密钥加密(序24–㉛ 累积同步)

代码
- 内容分发块级寻址:新增 src/net/relay/content/{chunker,store,runtime,source,peer,crypto}.ts
- 组密钥(C 档)确定性加密:AES-256-GCM,块 id β′ = sha256(密文) 前 32 hex;双 epoch 过渡窗口
- 实例生命周期:三处 teardown() 不再杀实例(local/remote/leased-spawner);启动认领 + TCP 探活判孤儿
- 骨干选路:jitter 选路 + endpoint-target;relay client/server/wire/identity/directory/rendezvous/switcher 调整
- 工作台 src/web/server.ts、src/worker/relay-tunnel.ts 装配与候选链观测

脚本与测试
- scripts/overlay-{probe,keyring,jitter}.cjs 更新
- 探针新增 OBS-21(每连接候选数)/ OBS-22(teardown 静态守卫 + 认领面)/ OBS-23(组密钥加密)
- 新增 test/{orchestrator-teardown,orchestrator-rehydrate,overlay-content,overlay-jitter}.test.mjs;relay 两例更新

文档
- 新增交接单:覆盖网络-序24-内容分发块级寻址 / 序25-实例逐步拉起 / 序26-骨干稳定选路与加密
- INDEX.md、交接单/README.md、skills/dsh-auto-handoff-chain/SKILL.md 同步

验收(零回归,2026-09-18 08:0x 复核)
- npm test           201 tests / 200 pass / 0 fail / 1 skipped
- overlay-failover-drill --scene all --table   12 PASS / 0 SKIP / 0 FAIL
- overlay-probe --table                        23 PASS / 0 SKIP / 0 FAIL (rc=0)
This commit is contained in:
admin committed 2026-09-18 08:08:51 +08:00
1 parent 04776af4b1
commit 09ce76f3af
38 files changed
+9133 -211

No files matched your search

+3
View File
@@ -22,6 +22,9 @@
| `T06-模型设置页-多厂家条目可开关.md` | ✅ **已完成并归档**(2026-09-13 23:5x,`craft-session-T06`) | `—`(已释放) | 平台自建「设置 → **模型设置**」:条目**各自开关、可同时启用** + 官方「模型」分区对全角色隐藏 + spawn 时把「已启用条目」落地到实例 `.credentials.yaml` / `settings.yaml` | **代码** `src/db/*`、`src/web/{model-landing.ts,server.ts,routes/auth.ts}`、`ensure-role-profile-patch.cjs`、`poc/business-plugins`(**0.3.11**) + `systemctl restart dshs` + `ensure-biz-plugins --all --restart`;**文档** `README`/`INDEX`/`03-路线图` | ✅ 无阻塞(口径用户已定);证据与判据见 **`04-87`**;⚠️ 本轮**顺手修掉两颗雷**:角色补丁脚本的整文件覆盖会抹掉 admin 另两个平台块、`verify-mem-model` 的陈旧断言 |
| `T07-内置dsh安装路径探测.md` | ✅ **已完成并归档**(2026-09-14 05:0x,`craft-session-installpath` + `craft-session-tailclear`) | `—`(已释放) | 修 **P1 静默失效**:三处把内置 dsh 安装目录写死 ⇒ 在 `npm root -g` = `/usr/lib/node_modules` 的发行版上,厂家目录读空 / 兼容性预检安全网失效 / 目录选择器 import 抛错 | **代码** 新增 `src/web/dsh-install.ts` + 三处调用点(含 `poc/workspace-scoped-picker`)+ 插件 `business-plugins` **0.3.13** / `workspace-scoped-picker` **0.1.5** + build/restart/铺发;**文档** `04-88` + `README` 补 3 个 env | ✅ 无阻塞;**由开源导出会话提出**(它抢不到锁按 R9 停手),源仓独立复核后经用户「确认修改」落地。⚠️ 复核时**发现提出方漏了第 3 处**、且其 env 名是导出侧的 ⇒ 均已纠正 |
| `T08-集群化落地-兼容单例模式.md` | ✅ **已完成并归档**(2026-09-15 18:0x,`exec-cluster-1a`) | `—`(已释放) | 把平台改造成「1 组 Manager(≥2 台,也支持单活)+ N 台 Worker + 共享归属状态」的集群形态;**硬约束 = 全程兼容单例模式** + 每步可单独回滚。**证据**:S0–S7 全绿(冒烟 6/8 与开工基线相同 · 5 个端到端 · lease 单测两后端 10/10)|**真跨机演练**(47 Manager / 106 Worker,跨云)✅|**域名形态访问** ✅|**2026-09-15 17:4x 生产整体切换**(47=Manager+本地 Worker w-47、106=Worker w-106、控制面库=47 的 PG13;存量用户留 w-47、新用户落 w-106;**回滚=删 drop-in**)。⚠️ **残留小项**(已登记 `03-路线图 §二`):`join-worker.sh` 一键装机 · 隧道服务化(心跳重建)· 集中日志/metrics · `smoke-domain` 定性。详见 §9–§16 | **代码**(`src/supervisor/`、`src/db/`、`src/cli.ts`、`src/fs/`)+ **服务器**(**106 另起一套**测试环境;**47 不动**)+ **文档**(本单 + 完工时的 `04-调整方案/101`) | 设计单一来源 = 项目根 `集群化改造方案_Manager-Worker_20260914.md`(19 节);**决策 D1–D5 已定**(双活+支持单活 / 存储可插拔 / 自建 PG / 跨云只当测试床 / 测试环境用 106);⏳ **仅 D6「生产拓扑最终落点」需用户拍板**,且**不阻塞 S0–S5** |
| `覆盖网络-序24-内容分发块级寻址.md` | ⏳ **待执行**(2026-09-17 20:4x 立单) | 🔄 已登记 automation `91251b14-9ee8-42a9-9d89-8559b7af75ed`(**2026-09-17 20:42** 起) | 首屏包冷启动改造:**块级内容寻址 + 同网段 peer 优先**(内容源优先级链 本地→同网段 peer→边缘缓存→分发点→公网源)。**主判据 `E1` = 版本发布时回源字节数 ≈ 1 份 × 组数**。🔴 **必须块级,⛔ 绝不做包级**(包级 = 版本一发即全量重拉风暴) | **代码** `src/net/relay/content/{chunker,store,peer,source}.ts` + `index.ts`/`web/server.ts`/`scripts/overlay-probe.cjs`/参数表;**文档** 本单 §8 | 用户拍板 2026-09-17:「**B 内容分发(块级内容寻址)是否立项:做**」;依据 `覆盖网络_瓶颈落地方案_20260916.md §3`;**§7 前缀 `49d0f405e08c909e18ba6825d1442b9d`** |
| `覆盖网络-序25-实例逐步拉起.md` | ⏳ **待执行**(2026-09-17 20:4x 立单;**待 序㉔ 收口时登记下一棒**) | ⛔ 未登记(等 序㉔ 收口) | Manager(portal)重启后**逐步接管既有实例**,而不是启动即清空。⚠️ **关键事实**:`orchestrator.ts:208` 构造函数里 `cleanAllStaleScopes()` ⇒ 现在**portal 一启动就把全部实例 scope 清掉** ⇒ 本单 = 把「清空」换成「接管」。**主判据 `E1` = 重启后实例数不变** | **代码** `src/supervisor/orchestrator.ts`(`cleanAllStaleScopes` / `cleanStaleScopes` 一带);**文档** 本单 §8 | 用户拍板 2026-09-17:「**Manager 重启后是否自动拉起既有实例:逐步拉起**」;三条硬约束(⛔ 不许删 `cleanStaleScopes(uid)`/⛔ 不许改成"什么都不做"/⛔ 不许"重启后重新 spawn 一遍");**§7 前缀 `d9d48121e68faa00e1da17ad8fea36ad`** |
| `覆盖网络-序26-骨干稳定选路与加密.md` | ⏳ **待执行**(2026-09-17 20:4x 立单;**待 序㉕ 收口时登记下一棒**) | ⛔ 未登记(等 序㉕ 收口) | 骨干节点服务范围落地:**连接稳定高效**(jitter 选路 + 2–3 候选路径 + 中继余量 30%+ + ≤10 成员全互联)+ **数据安全可加密传输**(TLS 已在 + 签名/哈希完整性 + 元数据最小化)。**主判据 `E1` = jitter 更低但 RTT 更高的候选被选中** | **代码** 新增 `src/net/relay/jitter.ts` + `directory.ts`/`switcher.ts`(只加"jitter 劣化即切",⛔ 不改冷却语义)/`server.ts`/`scripts/overlay-jitter.cjs`/参数表;**文档** 本单 §8 | 用户拍板 2026-09-17:「**入口 §4 骨干节点的服务范围:按照连接稳定高效的方式 数据安全可加密传输**」;判定 = **A 起步、口径按 B 的质量标准建设**;**§8 前缀 `dbcbe633aa1aef92f4c35c77fad3ec11`** |
> **🔒 占用怎么声明(2026-09-12 新增,防两会话撞车)**:开工前先**原子占位** ——
> `mkdir 交接单/.doing-<单号>`(`mkdir` 原子:**成功=你拿到;报 File exists=已有人在做 → 停手,别开工**),
@@ -0,0 +1,167 @@
# 交接单 · 内容分发(块级内容寻址 · 同网段 peer 优先)
- **序号**:覆盖网络线 **序 ㉔ · 规划棒**
- **立单**:2026-09-17 20:3x
- **用户拍板**:**「B 内容分发(块级内容寻址)是否立项:做」**(2026-09-17 20:2x)
- **上游依据**:`覆盖网络_瓶颈落地方案_20260916.md` **§3**(照抄"内容分发三件套"+验收判据)/`覆盖网络_应用场景与待完善清单_20260916.md` **§五 第 7 步**/`覆盖网络_千台全场景推演_20260916.md`
- **状态**:**待执行**(执行棒按本单 §4 顺序开工,⛔ 无须再出规划单)
- **⚠️ 用户新增口径**(见 §7):**传输必须"连接稳定高效 + 数据可加密"** ⇒ 本单的传输面按此定档
---
## §0 摘要
把 **10.8 GB/次的首屏包冷启动**(瓶颈榜第 3 项,也是版本发布风暴的第 5/9 项成因)从「每台设备各自回源」改成「**块级内容寻址 + 同网段 peer 优先**」。**一次投资同时治 #3 / #5 / #9 三项瓶颈**(上游口径:三件套覆盖最大三个瓶颈,且**都不需要改传输协议**)。
**核心判断(一句话)**:**必须做"块级 + 内容寻址",⛔ 不做"包级"。** 这是 BranchCache(块级)vs Peer Cache(包级)的分水岭 —— 包级必须完整下载完才能当 peer 源,**版本一变全部 peer 源同时失效 ⇒ 客户端集体回源 = "版本一发就全量重拉"的风暴成因**(本线已实证过一次)。
**验收判据(单值可测)**:**版本发布时回源字节数 ≈ 1 份 × 组数**(而不是 1000 份)—— 局域网内多台设备**只回源一次**。
---
## §1 目标(可判定"做完了没有")
| # | 判据 | 期望 | 怎么测 |
|---|---|---|---|
| **E1** | 回源放大比 | **≈ 1 份 × 组数**(同组 N 台设备只回源 1 份) | 同组内 4 台本地多实例同时拉同一版本 ⇒ 统计源侧出向字节数 |
| **E2** | 只拿到一部分也能开始共享 | 下载 30% 即产生可服务的块 | 中断后另一台从该 peer 能取到已持有块 |
| **E3** | 版本更新只传变化块 | 变化 5% 的版本 ⇒ 传输量 ≤ 变化块量 × 组内台数 | 改一个文件重打包 ⇒ 对比传输字节数 |
| **E4** | 客户端校验哈希 | 篡改块 ⇒ 丢弃并回源,⛔ 不落盘 | 故意改一个块的字节 ⇒ 断言 `hash mismatch` 且回源 |
| **E5** | 分组隔离 | 跨组**不**互相穿透(⛔ 不出现 A 组从 B 组取块) | 两组并存 ⇒ 断言跨组 0 命中 |
| **E6** | 内容源优先级可观测 | 每次取块**点名**来源档位(`local` / `peer` / `edge` / `origin`) | 判别器计数(⛔ 不许只写日志) |
| **E7** | 零回归 | `npm test` **176/175/0/1**、`--scene all --table` **12P/0S/0F**、`overlay-probe --table` **16P/0F/0S** | 三件套跑同值 |
---
## §2 只读前置(执行前**必须**先核实的 5 条;⛔ 不许靠推断)
| # | 要核实什么 | 命令 | 期望 |
|---|---|---|---|
| **P1** | 首屏包实际形态与大小 | `ls -l` 目标产物 + `du -sh` | 确认 10.8 GB/次 的口径(**是"一份包"还是"N 个文件"**)⇒ 直接决定块大小 |
| **P2** | 当前回源路径(几台设备各自回源) | 平台侧出向流量 + 实例启动日志 | 现状 = 每台各自回源(**这是本单的 Before 基线**) |
| **P3** | 现有"同网段"判定手段 | 47 / 106 的内网地址段 | 当前只有 2 台真机(47 / 106,**跨云不同网段**)⇒ **同网段 peer 只能在"本机多实例"上验证**(用户已拍板"本机内存大可以模拟多台") |
| **P4** | 已加载走 304 的能力 | 平台代理层 | 上游已确认"平台已有此能力"(`瓶颈落地方案 §3.6`)⇒ ⛔ 不重做 |
| **P5** | 存储与内存预算 | `df -h` / 空闲内存 | 块存储放哪、peer 缓存上限多少(⚠️ 参考 `MEM_PER_HOST_MB` 现测 **0.06**,块缓存要另算) |
---
## §3 范围
### 3.1 在册文件集(⚠️ **超出此集必须先停下报告** — R7)
| 面 | 文件 | 说明 |
|---|---|---|
| 新增 | `src/net/relay/content/chunker.ts` | 块级切分(固定块 + 内容哈希) |
| 新增 | `src/net/relay/content/store.ts` | 内容寻址存储(哈希 → 块;含校验) |
| 新增 | `src/net/relay/content/peer.ts` | 同网段 peer 发现与取块 |
| 新增 | `src/net/relay/content/source.ts` | 内容源优先级链 |
| 新增 | `test/overlay-content.test.mjs` | 单测(⛔ 不改 `package.json` 的 test 列表 ⇒ 需与既有 13 个测试文件一致的追加方式) |
| 改动 | `src/net/relay/index.ts` | 仅导出 |
| 改动 | `src/web/server.ts` | 仅装配(⛔ 不动 presence / 端点翻译既有逻辑) |
| 改动 | `scripts/overlay-probe.cjs` | 新增观测项 + 阈值键 |
| 改动 | `参数表_覆盖网络_20260917.md` | 新增 `CONTENT_*` 键 |
⛔ **不在本单范围**:房间层、打洞实现、游戏服、改传输协议、改 `switcher.ts` 冷却语义、删 `/status` 兜底路径。
### 3.2 顺带治好的既有瓶颈(⛔ 但不扩范围)
| 瓶颈榜 | 项 | 本单如何覆盖 |
|---|---|---|
| #3 | 首屏包冷启动 10.8 GB | 主目标 |
| #5 | (同族:版本发布风暴) | 块级 ⇒ 只传变化块 |
| #9 | 版本碎片 | 与 #3 共用一套内容寻址机制(版本包走同一条链) |
---
## §4 步骤 S0–S7(每步自带**一次可执行的验证**)
> ⚠️ 每步做完**立即验证**,⛔ 不许"全部写完再验"。
| 步 | 做什么 | 验证(一次可执行) | 回滚 |
|---|---|---|---|
| **S0** | 只读前置 `P1–P5` + 基线采样(**零改动**) | 输出 Before 回源字节数(= E1 的分母) | 无(只读) |
| **S1** | 块级切分 + 内容寻址存储(`chunker.ts` / `store.ts`) | 对同一份内容切两次 ⇒ **块哈希序列完全一致**;改 1 字节 ⇒ **只有 1 块变化** | 删新增文件 |
| **S2** | 客户端校验(`E4`) | 篡改块 ⇒ 断言 `hash mismatch` + 丢弃 + 回源 | 同上 |
| **S3** | 内容源优先级链(`source.ts`)+ **判别器计数**(`E6`) | 夹具:四档各命中一次 ⇒ 计数逐档递增 | 同上 |
| **S4** | 同网段 peer 发现与取块(`peer.ts`)+ 分组(`E5`) | 本机多实例(N ≥ 4):**先让 A 拉完,再让 B 拉 ⇒ B 从 peer 取** | 关 peer 开关 ⇒ 回 S3 行为 |
| **S5** | 装配 + 观测(参数表 `CONTENT_*` + 探针新项) | 探针出一项 PASS/FAIL(**⛔ 阈值不许是脚本魔数**) | 还原装配 |
| **S6** | 真机验收:`E1`(回源 ≈ 1 份 × 组数)+ `E2`(部分即可共享)+ `E3`(只传变化块) | 本机多实例 4 台同组同时拉同一版本 | 同 S4 |
| **S7** | 收口(部署属 lane 内,**直接做**)+ 零回归三件套(`E7`) | 三件套跑同值 | 产物回滚点 |
---
## §5 验收判据 E1–E7
见 **§1**。⚠️ **`E1` 是唯一的主判据**:断它一句话 —— **"回源字节数 ≈ 1 份 × 组数"**。
---
## §6 回滚(两层,均秒级)
1. **装配级**(推荐):`src/web/server.ts` 的 content 装配块整段移除 → `npm run build` → scp `lib/` → `restart dshs` ⇒ 回到"每台各自回源"的旧行为(**新模块文件留着不加载 ⇒ 零副作用**)。
2. **产物级**:scp 回滚点 `/opt/dsh/backups/seq24-<ts>/` 的 `lib/{web/server.js,net/relay/*.js}` → `systemctl restart dshs`。
🔴 ⛔ **回滚路径里不许出现 `RELAY_FAILOVER_COOLDOWN_MS=0`**(该值看似合法、实际自锁)。
---
## §7 传输面定档(**用户本棒新增口径**:连接稳定高效 + 数据可加密)
用户原话(2026-09-17 20:2x):**「按照连接稳定高效的方式 数据安全可加密传输」** ⇒ 本单的传输面按此定档,**属已定项、⛔ 不再上抛**:
| 档 | 我选什么(可推翻) | 理由 |
|---|---|---|
| **传输载体** | **复用既有自研 relay 通道**(`via='relay'` 回环落点 + wss 为主、443/TCP 兜底),⛔ **不新开公网端口** | 已端到端验收(序㉓ E2E 通过);"稳定"靠已有的**失败自动切流 + 冷却语义 + 一跳豁免** |
| **传输加密** | **peer 取块走既有 wss(TLS)**;**跨机块传输再加一层内容级校验(哈希)** ⇒ 机密性 + 完整性**双保** | 「数据安全可加密」= ① 传输加密(TLS 已在)② **内容完整性**由哈希兜住(`E4`)⇒ 即使中间节点被控也**改不了块** |
| **端到端加密** | **本阶段不做**(⛔ 不引入第二套密钥体系) | 会与"内容寻址 + 跨 peer 共享"**直接冲突**(端到端加密 ⇒ 每个接收者密文不同 ⇒ 无法按哈希共享块)⇒ **要加密就失去共享**,属真取舍;本阶段取"共享优先 + TLS + 哈希校验",把 E2E 登记为**待评估**(见 §9) |
| **稳定性指标** | 选路按 **jitter 排序⛔ 不按 RTT**(上游 §4 口径)+ 每连接保 2–3 条候选路径 | 「连接稳定高效」的机器判据 = **每连接 jitter 直方图**(超阈值切路径) |
---
## §8 回报格式(执行会话**必须**回填)
```markdown
### 8.x 序 ㉔ 执行棒回报(YYYY-MM-DD HH:MM–HH:MM)
#### ① 各步结果(S0–S7,逐条给"命令 + 原文输出 + 判定")
#### ② 主判据 E1:回源字节数 ≈ 1 份 × 组数
- 组内台数 = N;实测回源字节 = X;放大比 = X / 一份大小 = ?
#### ③ 先红后绿(原文级)
- (去掉某一档 ⇒ 哪条用例变红;改回 ⇒ 转绿)
#### ④ 零回归三件套(均带 `--table`)
| 项 | 结果 | 基线 |
#### ⑤ 边界自证
⛔ 未改任何生产值 / ⛔ 未新增公网监听口 / ⛔ 未改 nft·nginx / 🔴 `COOLDOWN_MS=0` 计数 = ? / ⛔ 未 commit·未 push
#### ⑥ 指纹
参数表 = ?(前值 `42238175d84319ada99afa56d583f9db`)|探针 = ?|代码面各文件 md5 = ?
```
---
## §9 回头条件(**一出现必须回头**;⛔ 不许自行扩范围、⛔ 不许调生产值去凑)
1. 要做"**包级**"(而非块级)⇒ **立即停下** —— 那正是版本风暴的成因(本线已实证)。
2. 需要**超出 §3.1 文件集** ⇒ 停下报告(R7)。
3. 要**新增公网监听口 / 改 nft·nginx** ⇒ 停下报告(R5)。
4. 要**动任何生产值**(`RELAY_FAILOVER_*` / `HB_SEC` / burst / `PRESENCE_*`)⇒ 停下报告。
5. **`E1` 放大比跑不出"≈ 1 份 × 组数"**(例如 ≥ 2 份)⇒ 停下报告,⛔ 不许放宽判据凑绿。
6. **要求"端到端加密"** ⇒ 属**真取舍**(加密即失去按哈希共享块)⇒ 停下上抛,⛔ 不许自行取舍。
7. 零回归三件套任一退化 ⇒ 停下报告。
8. 出现**静默放行**(判别器不计数、只写日志)⇒ 停下报告。
9. 🔴 ⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径**。
---
## §10 指纹与状态
| 项 | 值 |
|---|---|
| 本单 §7 之前正文前缀指纹 | **`49d0f405e08c909e18ba6825d1442b9d`**(口径 = `sed '/^## §7 /,$d' 交接单_内容分发块级寻址_20260917.md \| md5sum`) |
| 参数表指纹(立单时) | **`42238175d84319ada99afa56d583f9db`** |
| 代码仓 HEAD(立单时) | `04776af`(工作区 10 处未提交改动) |
| 本单全文件 md5(立单时) | `3198288c8b0b102d43c87ca9a3a31cd2`(166 行) |
| 本单状态 | **待执行** ⇒ 交**序 ㉔ 执行棒** |
@@ -0,0 +1,162 @@
# 交接单 · Manager 重启后「逐步拉起」既有实例
- **序号**:覆盖网络线 **序 ㉕ · 规划棒**
- **立单**:2026-09-17 20:3x
- **用户拍板**:**「Manager 重启后是否自动拉起既有实例:逐步拉起」**(2026-09-17 20:2x)
- **上游依据**:档案 **30**(`dsh-server-docs/04-调整方案/30-编排器孤儿实例清理.md`)/序⑲ §8.8-1("按需拉起、无 reconciler"的实测)/序⑬ P3(`OBS-09` 因无实例而 SKIP)
- **状态**:**待执行**
---
## §0 摘要 + ⚠️ 一条必须先说的反直觉事实
用户拍板 = **"逐步拉起"**(staggered rehydrate)。但**读源码发现:现在的架构与目标不是"缺一个 reconciler",而是"启动时主动清空"** ——
| 位置 | 现状 | 与目标的差距 |
|---|---|---|
| `src/supervisor/orchestrator.ts:208`(构造函数) | `this.cleanAllStaleScopes()` —— **portal 启动即清掉全部实例 scope** | 🔴 **这是"逐步拉起"的直接阻碍**:重启后 **N=0**,没有任何可接管的实例 |
| `:1225` | `stopScopesByPrefix('dsh-', /^dsh-\d+-[0-9a-f]+\.scope$/)` | 清得**很干净**(连"哪些曾经在线"的信息一起丢了) |
| `:1218` | `cleanStaleScopes(uid)` —— spawn 前清**同 uid** 名下的残留 scope | ⚠️ 这一条是**必要的**(档案 30 的根因:双实例共 profile ⇒ 写冲突)⇒ **⛔ 不许为了"逐步拉起"把它去掉** |
**根因链(档案 30 原文)**:实例真实生命周期在 **OS 层(systemd scope)**,而编排器**只靠内存 map 追踪** ⇒ 重启后 map 空 ⇒ 旧 scope 变**孤儿**(编排器不认识)⇒ 双实例共享 profile ⇒ session/settings 写冲突 ⇒ "模型连接异常"。当时的修法是"**统一清掉防孤儿**"。
⇒ **本单的核心判断**:**"逐步拉起"= 把"清空"换成"接管"** —— 启动时**不要清**,而是**扫描 OS 层既有 scope ⇒ 逐个回填进内存 map ⇒ 按节流逐个 probe/rehydrate**。⚠️ **前提是先有"可恢复的实例账本"**(否则"清"是唯一安全的做法)。
---
## §1 目标(可判定"做完了没有")
| # | 判据 | 期望 | 怎么测 |
|---|---|---|---|
| **E1** | 重启后实例**不被清掉** | `restart dshs` 后 5 s 内,重启前在跑的 scope **仍在** | 先起 2 个实例 ⇒ `restart dshs` ⇒ `systemctl list-units 'dsh-*.scope'` **仍为 2** |
| **E2** | **逐个**(节流)接管,⛔ 不风暴 | 接管速率受限(`REHYDRATE_CONCURRENCY` / `REHYDRATE_STAGGER_MS` 阈值内) | 4 个实例 ⇒ 日志逐条时间差 ≥ 阈值;⛔ 无"同一秒 4 条" |
| **E3** | 任一实例都是**原实例**(不是新起) | 接管后 `port` 不变、无新 scope 产生 | 对比重启前后的 scope 名与 `--port` 参数 |
| **E4** | 单实例保证**仍成立**(⛔ 不回归档案 30) | 接管后同 uid **只有 1 个** scope;新 spawn 前仍清同 uid 残留 | 断言 `dsh-<uid>-*` 计数 = 1 |
| **E5** | 无实例时**不自起**(⛔ 不违反"按需"语义) | 重启前**没有**实例 ⇒ 重启后仍为 0 | 空跑一次 `restart dshs` ⇒ scope = 0 |
| **E6** | 内存/启动压力可控 | 接管过程 47 可用内存不跌穿阈值;无 OOM kill | `free` 采样 + `journalctl -u dshs \| grep -c 'oom'` = 0 |
| **E7** | 零回归 | `npm test` **176/175/0/1**、`--scene all --table` **12P/0S/0F**、`overlay-probe --table` **16P/0F/0S** | 三件套跑同值 |
---
## §2 只读前置(执行前**必须**先核实的 4 条)
| # | 要核实什么 | 命令 | 期望 |
|---|---|---|---|
| **P1** | 实例 scope 与 uid 的对应 | `systemctl list-units 'dsh-*.scope' --plain` + 对照 `users` 表 | 确认 `dsh-<uid>-<id>.scope` 里 **`<uid>` = 用户 uid**(档案 30 口径),⛔ 别与"用户 id"混 |
| **P2** | scope 的 `--port` 参数**重启后是否可读回** | `cat /proc/<pid>/cmdline` 或 `systemctl show -p MainPID` 后读 `/proc` | ⚠️ **这是可恢复性的关键**:拿不到端口 ⇒ 没法接管(要另想办法) |
| **P3** | 47 上当前实例数(T0 现场) | `systemctl list-units 'dsh-*.scope'` | 序㉓ 实测 47 = **0**、106 = **1**(⚠️ 实例在 **106** 上)⇒ 本单验收要**主动先起实例** |
| **P4** | 是否有"实例账本"可持久化 | 查 `RemoteSpawner` / `LeasedSpawner` 落库情况 | 若既无 DB 记录也无 /proc 可读 ⇒ **必须先补一步"实例账本"**(见 §4 S1 前置) |
---
## §3 范围
### 3.1 在册文件集(⚠️ **超出必须先停下报告** — R7)
| 面 | 文件 | 说明 |
|---|---|---|
| 改动 | `src/supervisor/orchestrator.ts` | ①② 启动流程:清空 → 接管;新增 `rehydrate()` |
| 改动 | `参数表_覆盖网络_20260917.md` | 新增 `REHYDRATE_*` 键 |
| 改动 | `scripts/overlay-probe.cjs` | 新增观测项(重启后 scope 数 = 重启前) |
| 可能新增 | `test/orchestrator-rehydrate.test.mjs` | 单测(⛔ 不改 `package.json` test 列表的结构) |
⛔ **不在本单范围**:改 bwrap 参数(🔴 **47 是 bubblewrap 0.4.0**,`--perms` 属 0.5+ ⇒ 会起不来)、改 `cleanStaleScopes(uid)` 的"spawn 前清同 uid"语义、动 `RELAY_FAILOVER_*` / `HB_SEC` / burst。
### 3.2 三条硬约束(**违反即事故**)
1. 🔴 **`cleanStaleScopes(uid)`(spawn 前清同 uid)⛔ 不许删** —— 它是档案 30 的修法本体,删了就回到"双实例共 profile"。
2. 🔴 **`cleanAllStaleScopes()` 只能改成"接管",⛔ 不能改成"什么都不做"** —— 因为内存 map 为空时,**孤儿会失控**(这正是档案 30 的原始故障)。
3. 🔴 **接管必须"认原实例"** —— ⛔ 不许"重启后重新 spawn 一遍"(那会**换端口、换 scope 名**,且瞬间 N 个一起起 = 启动风暴)。
---
## §4 步骤 S0–S6
| 步 | 做什么 | 验证(一次可执行) | 回滚 |
|---|---|---|---|
| **S0** | 只读前置 `P1–P4`(**零改动**) | 拿到"scope 名 ↔ uid ↔ 端口"三者的可读性结论 | 无(只读) |
| **S1** | **(可能需要)实例账本**:把"在线实例"持久化(⚠️ 归属判定:跟着用户走 ⇒ **放实例 home**;本机运维用 ⇒ **Worker 本地库**;⛔ 只有 Manager 能写归属/租约) | 重启后能列出"重启前在跑的实例"清单(含 uid / 端口) | 删账本文件 |
| **S2** | **接管取代清空**:启动扫描 → 回填 map(⛔ 不 stop、⛔ 不 spawn) | `E1`:`restart dshs` 后 scope 数**不变** | 还原 `cleanAllStaleScopes()` 调用 |
| **S3** | **节流**:`REHYDRATE_CONCURRENCY` / `REHYDRATE_STAGGER_MS` | `E2`:4 实例 ⇒ 日志时间差 ≥ 阈值、⛔ 无同秒多条 | 阈值调回 0 ⇒ 回 S2 行为 |
| **S4** | **单实例保证复验**(⛔ 不回归档案 30) | `E4`:同 uid 恒 1 个 scope;`E5`:无实例时不自起 | 同 S2 |
| **S5** | 观测 + 探针新项 | 探针出一项 PASS/FAIL(⛔ 阈值不许是脚本魔数) | 还原探针 |
| **S6** | 真机验收(`E1`–`E6`)+ 零回归(`E7`)+ 收口 | `E6`:内存不跌穿、无 OOM;三件套同值 | 产物回滚点 |
---
## §5 真机验收怎么造条件的(⚠️ 关键)
⚠️ **不能只靠"等用户访问"**(本线已因"无 reconciler ⇒ 不自回"卡过一次)。验收必须:
1. **先主动起 2–4 个实例**(用 R4 临时 session 走真实 `enter`,或用本机多实例)⇒ 记录 scope 名 / uid / 端口;
2. `systemctl restart dshs`;
3. **立刻**(≤ 5 s)与 **+60 s** 两个时刻各取一次 `list-units 'dsh-*.scope'`;
4. 断言 `E1`(数不变)/`E2`(逐条时间差)/`E3`(端口不变、⛔ 无新 scope)/`E4`(同 uid = 1)。
⚠️ **已知副作用**:`restart dshs` **会收掉用户实例 scope**(本线既有实测)⇒ 本单要**故意利用**这个副作用做 E1/E5 的对照(重启前有实例 → 应被接管;重启前无实例 → 应保持 0)。
---
## §6 回滚(两层)
1. **代码级**:还原 `orchestrator.ts` 的启动流程为原 `cleanAllStaleScopes()` 调用 → `tsc` → scp `lib/` → `restart dshs` ⇒ 回到"统一清掉防孤儿"(档案 30 行为)。
2. **产物级**:scp 回滚点 `/opt/dsh/backups/seq25-<ts>/lib/supervisor/orchestrator.js`。
🔴 ⛔ **回滚路径里不许出现 `RELAY_FAILOVER_COOLDOWN_MS=0`**。
---
## §7 回报格式(执行会话**必须**回填)
```markdown
### 8.x 序 ㉕ 执行棒回报(YYYY-MM-DD HH:MM–HH:MM)
#### ① S0–S6 逐条(命令 + 原文输出 + 判定)
#### ② 主判据 E1:重启后实例数不变
- 重启前 scope = ? | +5 s = ? | +60 s = ? | 端口是否全同 = ?
#### ③ 先红后绿(原文级)
- (先跑"仍清空"的旧行为 ⇒ 哪条断言红;改成接管 ⇒ 绿)
#### ④ 零回归三件套(均带 `--table`)
#### ⑤ 边界自证
⛔ 未改生产值 / ⛔ 未改 bwrap 参数 / ⛔ 未删 `cleanStaleScopes(uid)` / 🔴 `COOLDOWN_MS=0` = ? / ⛔ 未 commit·未 push
#### ⑥ 指纹
参数表 = ?(前值 `42238175d84319ada99afa56d583f9db`)|orchestrator.ts md5 = ?
```
---
## §8 回头条件(**一出现必须回头**)
1. `P2` 拿不到 scope 的端口 ⇒ **停下报告**("接管"不可行,需先出"实例账本"方案)。
2. 接管导致**双实例共 profile**(档案 30 故障复现)⇒ 立刻回滚 + 报告。
3. 需要**超出 §3.1 文件集** ⇒ 停下报告(R7)。
4. 要**改 bwrap 参数 / 改配额 / 动生产值** ⇒ 停下报告。
5. `E2` 跑不出节流(同秒多条)⇒ 停下报告,⛔ 不许放宽判据。
6. 零回归三件套任一退化 ⇒ 停下报告。
7. 🔴 ⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径**。
---
## §9 未验证项
| # | 项 | 状态 |
|---|---|---|
| 1 | 重启后能否从 OS 层读回实例端口 | ⚠️ 待 `P2` 取证 |
| 2 | 是否需要独立"实例账本" | ⚠️ 待 `P4` 取证 |
| 3 | 接管 4 个实例的内存与启动耗时 | ⚠️ 待 S6 实测 |
---
## §10 指纹与状态
| 项 | 值 |
|---|---|
| 本单 §7 之前正文前缀指纹 | **`d9d48121e68faa00e1da17ad8fea36ad`**(口径 = `sed '/^## §7 /,$d' 交接单_实例逐步拉起_20260917.md \| md5sum`) |
| 参数表指纹(立单时) | **`42238175d84319ada99afa56d583f9db`** |
| 代码仓 HEAD(立单时) | `04776af` |
| 本单全文件 md5(立单时) | `2c53a018c2d9bfc2c422d0758413a485`(161 行) |
| 本单状态 | **待执行** ⇒ 交**序 ㉕ 执行棒** |
@@ -0,0 +1,177 @@
# 交接单 · 骨干节点落地(稳定高效选路 + 传输可加密)
- **序号**:覆盖网络线 **序 ㉖ · 规划棒**
- **立单**:2026-09-17 20:3x
- **用户拍板**:**「入口 §4 骨干节点的服务范围:按照连接稳定高效的方式 数据安全可加密传输」**(2026-09-17 20:2x)
- **上游依据**:`覆盖网络_骨干层方案_20260916.md` **§7(原 A/B 待拍板)**/**§8 落地顺序**/`覆盖网络_瓶颈落地方案_20260916.md` **§4(jitter 是一等指标)**/`覆盖网络_补遗与参考方案_20260916.md`(45% 设计 / 55% 余量口径)
- **状态**:**待执行**
---
## §0 摘要 + 我把用户口径翻译成的两条硬判据
用户**没有按 A/B 选**,而是给了一句**目标导向**的话:**「按照连接稳定高效的方式 数据安全可加密传输」**。
⇒ **我的解读(已定项,可推翻)**:用户要的不是"服务范围"这个二元选择,而是**两条可验证的工程质量判据**:
| 口径 | 翻译成机器判据 | 与 A/B 的关系 |
|---|---|---|
| **连接稳定高效** | **选路按 jitter 排序**(⛔ 不按 RTT)+ 每连接保 2–3 条候选路径 + 中继利用率留 **30%+** 余量 + 骨干间心跳 ≤10 成员全互联 | **与 A/B 正交** —— 无论自用还是全网,这条都要满足 |
| **数据安全可加密传输** | ① 传输层加密(**wss/TLS 已在**,⛔ 不新造)② **内容/信令完整性由签名与哈希兜住**(本线已有 relay 签名目录 + 密钥体系)③ **对外只暴露"转发能力",⛔ 不暴露"看到谁连谁"的元数据** | **B 档的最大缺点正是"骨干会看到流量元数据"** ⇒ 用户这句**恰好把 B 档的最大代价按住了** |
✅ **因此我的判定(可推翻):按 "A 起步、口径按 B 的质量标准建设" 落地** —— 即 **§7 原倾向 A→B 渐进 与用户口径并不冲突**:
- **可见范围**仍取 **A**(只服务自己名下设备):无计费/合规纠纷、权限面不变(⛔ 不命中 R5)、元数据暴露面最小 —— **这直接满足"数据安全"**;
- **质量与冗余**按 **B 的标准**建设(多中心骨干、jitter 选路、30%+ 余量):**这直接满足"稳定高效"**;
- **"可加密"** ⇒ 做成**能力就位**(TLS + 签名 + 内容哈希),**⛔ 不在本单引入端到端加密**(见 §7)。
---
## §1 目标(可判定"做完了没有")
| # | 判据 | 期望 | 怎么测 |
|---|---|---|---|
| **E1** | **选路按 jitter**(⛔ 不按 RTT) | 两候选路径中 jitter 更低者被选中,**即使其 RTT 更高** | 夹具:候选 A(RTT 20ms/jitter 15ms) vs B(RTT 30ms/jitter 2ms) ⇒ 断言选 **B** |
| **E2** | **jitter 可观测** | 每连接有 jitter 直方图 + 超阈值自动切路径并**告警** | 判别器计数 + 直方图落盘 |
| **E3** | **路径多样性** | 每连接维护 **2–3 条**候选(直连 / 就近中继 / 备用中继) | `/status` 可查候选数 ≥ 2 |
| **E4** | **利用率留余量** | 中继利用率 ≤ **70%**(留 30%+) | `used / capacity` 可查、超限**拒绝新接入**(⛔ 不打满) |
| **E5** | **骨干互认只校验不自行批准** | 控制面签发资格;骨干间只验签 | 未签名骨干接入 ⇒ 被拒并**点名** |
| **E6** | **元数据最小化**(A 档) | 骨干**只知"转发给谁",不知"谁在连谁"**;跨用户**不可见** | 断言跨用户 0 命中(与内容分发的 `E5` 同族) |
| **E7** | 零回归 | `npm test` **176/175/0/1**、`--scene all --table` **12P/0S/0F**、`overlay-probe --table` **16P/0F/0S** | 三件套跑同值 |
---
## §2 只读前置(执行前**必须**先核实的 5 条)
| # | 要核实什么 | 命令 | 期望 |
|---|---|---|---|
| **P1** | 现行 relay 是否**已**做到"只绑回环 + 443 兜底" | `ss -lntp` + relay ExecStart | 已实测:两台 relay 均**只绑 `127.0.0.1:20080`**、106 复用既有 443 ⇒ **零新增公网口**(⛔ 本单不许破坏这条) |
| **P2** | 现行选路是**按什么排序** | 读 `src/net/relay/switcher.ts` / `directory.ts` | ⚠️ 预判:**按健康度/冷却**,**⛔ 不是按 jitter** ⇒ 这是本单的主要缺口 |
| **P3** | jitter 现测值 | `scripts/overlay-jitter.cjs` | 已有脚本;序⑥ 实测 `p95(\|ΔRTT\|)` = **3 ms**(达标)⇒ **本单把"点测"变成"持续采样 + 参与选路"** |
| **P4** | 骨干"资格"体系现状 | `scripts/overlay-keyring.cjs` + relay `keys` 表 | 已有四层密钥模型(离线根 → 在线签名者 → 每机节点密钥 → 会话)+ 逻辑名索引 + 成员资格校验 ⇒ **E5 大部分已就位** |
| **P5** | 45% 容量口径现值 | 参数表 `RELAY_MAX_HOSTS` | 实测 **7515**(序⑥ 重算并已下发两台)⇒ 本单**只读不改**,⛔ 不动容量值 |
---
## §3 范围
### 3.1 在册文件集(⚠️ **超出必须先停下报告** — R7)
| 面 | 文件 | 说明 |
|---|---|---|
| 新增 | `src/net/relay/jitter.ts` | jitter 采样与直方图(**选路的输入**) |
| 改动 | `src/net/relay/directory.ts` | 候选排序:健康度 → **jitter 为主序**(⛔ 不删既有冷却语义) |
| 改动 | `src/net/relay/switcher.ts` | ⚠️ **只加"jitter 劣化即切",⛔ 不改冷却语义** |
| 改动 | `src/net/relay/server.ts` | 利用率守卫(`E4`)+ 候选路径可查(`E3`) |
| 改动 | `scripts/overlay-jitter.cjs` | 点测 → 持续采样 |
| 改动 | `scripts/overlay-probe.cjs` | 新增 `OBS-17`(jitter)/`OBS-18`(余量) |
| 改动 | `参数表_覆盖网络_20260917.md` | 新增 `JITTER_*` / `RELAY_UTIL_MAX_PCT` 键 |
⛔ **不在本单范围**:`RELAY_FAILOVER_*` 任何值、`HB_SEC`、burst、`switcher.ts` 冷却语义、`RELAY_MAX_HOSTS`、房间层、打洞实现、内容分发的块级实现(**另单**,见 `交接单_内容分发块级寻址_20260917.md`)。
### 3.2 三条硬约束
1. 🔴 **⛔ 不改 `switcher.ts` 冷却语义**(本线持久禁令)—— 只**新增** jitter 触发条件。
2. 🔴 **⛔ 不新增公网监听口 / 不改 nft·nginx**(R5)—— 骨干仍只绑回环 + 443/TCP 兜底复用。
3. 🔴 **骨干资格只能控制面签发**(权威状态单点 Manager)—— 骨干**只校验,不自行批准**。
---
## §4 步骤 S0–S6
| 步 | 做什么 | 验证(一次可执行) | 回滚 |
|---|---|---|---|
| **S0** | 只读前置 `P1–P5` + 基线 jitter 采样(**零改动**) | 输出 Before:现行选路按什么排序(**原文行号级证据**) | 无(只读) |
| **S1** | jitter 采样与直方图(`jitter.ts`) | 夹具:喂 3 条已知 jitter 序列 ⇒ 直方图与 p95 **逐项可断言** | 删新增文件 |
| **S2** | **候选排序改为 jitter 为主序**(`directory.ts`) | `E1`:**先红后绿**(jitter 更低但 RTT 更高的那条**必须被选中**) | 还原排序函数 |
| **S3** | 切换条件新增"jitter 劣化即切"(`switcher.ts`,⛔ 不碰冷却) | `E2`:劣化 ⇒ 切 + 告警;⛔ 冷却语义断言**不变** | 移除新增条件 |
| **S4** | 利用率守卫 + 候选可查(`server.ts`) | `E4`:利用率 > 阈值 ⇒ 拒绝新接入并**点名**;`E3`:候选数 ≥ 2 | 还原守卫 |
| **S5** | 骨干互认与元数据最小化复验(`E5` / `E6`) | 未签名骨干被拒并点名;跨用户 0 命中 | 同 S2 |
| **S6** | 真机验收 + 观测 + 零回归(`E7`)+ 收口 | 三件套同值;探针新增 2 项 PASS | 产物回滚点 |
---
## §5 验收判据 E1–E7
见 **§1**。⚠️ **`E1` 是主判据**("选路按 jitter 排序而非 RTT")—— 这一条**能不能机器断言**,决定了"连接稳定高效"是不是**嘴上说说**。
---
## §6 回滚(两层)
1. **排序级**:还原 `directory.ts` 的排序函数 + 移除 `switcher.ts` 新增条件 → `tsc` → scp `lib/` → `restart dshs` ⇒ 回到"按健康度/冷却"的旧选路。
2. **产物级**:scp 回滚点 `/opt/dsh/backups/seq26-<ts>/lib/net/relay/{directory,switcher,server,jitter}.js`。
🔴 ⛔ **回滚路径里不许出现 `RELAY_FAILOVER_COOLDOWN_MS=0`**。
---
## §7 安全与加密档位(**用户口径的关键落点**;属已定项、⛔ 不再上抛)
用户原话:**「数据安全可加密传输」**。逐条落法:
| 层 | 现状 | 本单怎么做 |
|---|---|---|
| **传输层加密** | ✅ **wss / TLS 已在**(relay 经 `https://alotbuy.com/dshs-relay` 可达、双路 101) | **复用,⛔ 不重造**。骨干之间同样只走 wss |
| **身份与授权** | ✅ 四层密钥模型已落地(离线根 → 在线签名者 → 每机节点密钥 → 会话)+ 逻辑名索引 + 成员资格校验 + 吊销演练通过 | `E5`:**骨干间只校验,不自行批准**(补齐"骨干资格"这一档) |
| **完整性** | ✅ 有签名目录 | 信令与目录**全程验签**;⛔ 不引入第二套密钥体系 |
| **元数据最小化** | ⚠️ B 档的最大缺点是"看到谁连谁" | `E6`:**A 档可见范围** ⇒ 骨干**只知转发目标、不知连接双方**;跨用户不可见 |
| **端到端加密(E2E)** | ⛔ **不做** | **属真取舍**:E2E 会让"每个接收者密文不同" ⇒ **与内容分发的按哈希共享块直接冲突**(要加密就失去共享)⇒ 本阶段取 **"TLS + 验签 + 内容哈希"**;E2E 登记为**待评估**(见 §9-3) |
🔑 **一句话**:**"可加密"= 能力就位(TLS + 验签 + 哈希)+ 元数据最小化**,⛔ **不是**"现在就上端到端加密"。
---
## §8 回报格式(执行会话**必须**回填)
```markdown
### 8.x 序 ㉖ 执行棒回报(YYYY-MM-DD HH:MM–HH:MM)
#### ① S0–S6 逐条(命令 + 原文输出 + 判定)
#### ② 主判据 E1:jitter 更低(但 RTT 更高)的候选被选中
- 候选 A = ? | 候选 B = ? | 实测选中 = ? | 原文 = ?
#### ③ 先红后绿(原文级)
#### ④ 零回归三件套(均带 `--table`)
#### ⑤ 边界自证
⛔ 未改 `switcher.ts` 冷却语义 / ⛔ 未改生产值 / ⛔ 未新增公网口 / ⛔ 未改 nft·nginx / 🔴 `COOLDOWN_MS=0` = ? / ⛔ 未 commit·未 push
#### ⑥ 指纹
参数表 = ?(前值 `42238175d84319ada99afa56d583f9db`)|directory.ts / switcher.ts / server.ts md5 = ?
```
---
## §9 回头条件(**一出现必须回头**)
1. 要**改 `switcher.ts` 冷却语义** ⇒ 停下报告(持久禁令)。
2. 要**新增公网监听口 / 改 nft·nginx** ⇒ 停下报告(R5)。
3. 要引入**端到端加密**(E2E)⇒ **属真取舍**(加密即失去按哈希共享块)⇒ 停下上抛。
4. 要**改 `RELAY_MAX_HOSTS` / 45% 容量口径值** ⇒ 停下报告。
5. 需要**超出 §3.1 文件集** ⇒ 停下报告(R7)。
6. `E1` 跑不出机器断言 ⇒ 停下报告,⛔ 不许放宽判据凑绿。
7. 零回归三件套任一退化 ⇒ 停下报告。
8. 🔴 ⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径**。
---
## §10 未验证项
| # | 项 | 状态 |
|---|---|---|
| 1 | 各网络类型实际占比(决定骨干数量需求) | ⚠️ 上游仍为推演设定 |
| 2 | 骨干上行带宽是否够(家宽上行常远小于下行) | ⚠️ 上游未实测 |
| 3 | E2E 加密与块级共享的取舍 | ⚠️ 本单**未做**,登记待评估 |
---
## §11 指纹与状态
| 项 | 值 |
|---|---|
| 本单 §8 之前正文前缀指纹 | **`dbcbe633aa1aef92f4c35c77fad3ec11`**(口径 = `sed '/^## §8 /,$d' 交接单_骨干稳定选路与加密_20260917.md \| md5sum`) |
| 参数表指纹(立单时) | **`42238175d84319ada99afa56d583f9db`** |
| 代码仓 HEAD(立单时) | `04776af` |
| 本单全文件 md5(立单时) | `f85b86234c2ecfaef5b2e43aa40d86a3`(176 行) |
| 本单状态 | **待执行** ⇒ 交**序 ㉖ 执行棒** |