Files
dsh_ai1net_server/归档/交接单-20260924-归档/交接单_覆盖网络直连与P2P_20260918.md
T
admin 7bd1151c67 chore(工作区): 归档交接单 27 件 + 新增 CODEBUDDY §9 工作区卫生
交接单按归属约定(正文落文档库、工作区只放指针)归档至
归档/交接单-20260924-归档/,含逐件判定 README:
- 16 件已被文档库正式版取代(T09–T21 + 覆盖网络-24/25/26)
- 4 件主题已被覆盖网络线入口汇总
- 7 件历史接续包/规划件

CODEBUDDY §9:收口清本棒 tmp、tmp 保留期 7 天、禁「待清理」中间态、
工作区入库只放文档与文件、不保留脚本副本、>60 KB 单文件须逐个判。
2026-09-24 07:58:57 +08:00

687 lines
87 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.
# 交接单 · 覆盖网络「节点直连 + P2P 取回」(P2 · S5–S7)
> **上游依据**:`交接单_节点一键加入与分组准入_20260918.md` **§4.3**(缺口 3 · 直连与 P2P)+ **§6 P2 段**(S5 / S6 / S7)+ **§7 权限影响评估(P2 专用)** + **§8.11**(序㊳ 登记的"待你拍板"清单)
> **归档号**:**132**(现核:`04-调整方案/` 正式档案最大 **128**;**129 / 130 / 131** 已分别用于单 A / 单 B / 序㊱ P1 ⇒ ⛔ 不重用;`mkdir .lock-132` 原子占号成功)
> **本单性质**:**规划棒产出** —— ⛔ 零代码改动 · ⛔ 零服务器改动 · ⛔ 未 commit / push
> **用户拍板时间**:**2026-09-18 12:22**(原话见 §1)
---
## §1 用户口径(功能卡,⛔ 别改写成技术题)
**用户原话**(2026-09-18 12:22,回答 §8.11 的两问):
```
1 用户可设置,默认开启提示用户 2 乙
```
**⇒ 解读(= 本期唯一功能口径,⛔ 不许扩大也不许缩小)**:
| # | 口径 | 落地含义 |
|---|---|---|
| **①** | **用户可设置** | 直连(打洞)能力由**用户自己**控制 —— 提供设置项,可开可关 |
| **②** | **默认开启** | 设置项**缺省值 = 开**(⛔ 不是"缺省关闭、用户主动开") |
| **③** | **提示用户** | 开启时**必须向用户提示**(说清"会怎样、怎么关"),⛔ 不是静默生效 |
| **④** | **来源范围 = 乙** | 47 / 106 云安全组新增的 UDP 入站,**仅放行对端节点 IP**(⛔ **不是** `0.0.0.0/0`) |
**⇒ 三条产品口径(序㊴ 自决,⚠️ 可推翻)**:
1. **设置项落在两处**:ⓐ **节点加入时**(`overlay-node-join.cjs`)默认启用并**打印提示**;ⓑ **设置/管理面**可查看与切换(默认 `true`,带提示文案)。
2. **关闭后行为 = 全部走中继**(= 既有路径,⛔ **不是**功能降级、⛔ 不影响 P1 已交付的"一键加入 + 分组准入")。
3. **提示必须"可行动"**:说清①开了之后**谁**可能直连到这台机器 ②**怎么关** ③关掉**不影响什么**。⛔ 禁止只写"已启用直连"。
**⛔ 本单不做**(防范围蔓延):虚拟网卡 / TUN-TAP(参数表 §8-① 已收窄为"不做")|mDNS / 广播发现|房间层|打洞之外的第二套传输协议。
---
## §2 只读前置(开工前必读,⛔ 别重做调研)
| # | 事实(已取证) | 证据位置 |
|---|---|---|
| 1 | 🔴 **`src/` 零 UDP / 穿透代码** —— 直连要从零建 | `grep -rniE "candidate\|punch\|directPath\|natType" src/net/relay/*.ts src/net/relay/content/*.ts` ⇒ 仅命中 `directory.ts` 的**中继候选**(⛔ 不是节点候选地址) |
| 2 | ✅ **打洞可行性已实测**(n=3 对、**2/2 可打洞**) | `scripts/overlay-holepunch.cjs`(序⑥ S4 · ⛔ **一次性脚本、不进产品路径**);参数表 §3.2 `HOLE_PUNCH_RATE_LOCAL` |
| 3 | 🔴 **47 的云安全组拦 UDP 入站** | 序⑥ S4:UDP 观察器 `0.0.0.0:21100/21101` 开放 ≈ **75 s**、**零收包**(连同机发出的都收不到);⛔ **不是 nft**(`input policy = accept`) |
| 4 | **peer 档"有账本、传输走 wss"** | `src/net/relay/content/peer.ts` 头注原话:「不做传输(取块走 `source.ts` 注入的 fetcher,**底层复用既有 wss 通道**)」 |
| 5 | **relay 拨号白名单 = `Map<network, Set<hostId>>`**;`DIAL` 必须**同网 + 在白名单**(结构性隔离) | `src/net/relay/server.ts:184` / `:201` / `:643` |
| 6 | **443 兜底 ≠ 打洞**(它是 DNS 层"钉 IP + 保持 SNI") | `src/net/relay/addr-override.ts` 头注 |
| 7 | **不引入新依赖**:`client.ts` 用 Node 22 内建全局 `WebSocket` | `addr-override.ts` 头注原话:「本项目**不引入 `undici` / `ws`**」⇒ 打洞同理,**只用内建 `dgram`** |
| 8 | 引导目录端点 = `src/web/routes/overlay.ts`(83 行,**只读无鉴权**,P0-2) | ⚠️ 它与 §3 提到的"管理面 API"**不是同一个东西**(后者 = `src/web/routes/overlay-nodes.ts`,**未建**) |
---
## §3 范围与在册文件集
**新增(预期 · ⛔ 越界即停)**
| 文件 | 职责 |
|---|---|
| `src/net/relay/direct/candidate.ts` | 候选地址的**收发与校验**(**走既有 relay wss 通道**,⛔ 零新口) |
| `src/net/relay/direct/punch.ts` | UDP 打洞探测(Node 内建 `dgram`),失败**判死** |
| `src/net/relay/direct/index.ts` | 开关 + **默认值** + 提示文案 + 降级编排 |
| `scripts/overlay-direct-probe.cjs` | 观测面(**只读**探针;判据进 `OBS-26` 起) |
| `test/overlay-direct.test.mjs` | 单测(⚠️ **刻意不进 `npm test`** —— 那是硬编码文件列表,加进去会改测试总数) |
**修改(预期)**
| 文件 | 改什么 | ⛔ 边界 |
|---|---|---|
| `src/net/relay/content/source.ts` / `runtime.ts` | peer fetcher 接上直连通道(**优先直连、回落 wss**) | 🔴 **接线后必须复算块 id** |
| `src/net/relay/join.ts` | 加入时默认启用 + 提示 | — |
| `scripts/overlay-node-join.cjs` | 同上(提示文案) | — |
| `参数表_覆盖网络_20260917.md` | 新键 + `OBS-26+` 行 | 值格**只加不改** |
**⛔ 明确不改**:`src/net/relay/server.ts` 的 `DIAL` / 白名单**语义**(只读取证)|官方 dsh 主程序与缓存(**R2**)|`nft` / `nginx`|bwrap 参数(47 = bubblewrap **0.4.0**)。
---
## §4 方案(技术路线)
### 4.1 候选地址交换 —— **复用既有通道,⛔ 零新增暴露面**
- 在**既有 relay wss 会话**上新增一种消息(暂名 `DIRECT_CANDIDATE`),载荷 = `{hostId, network, addrs[]}`。
- 🔴 **准入判定必须复用既有白名单**(`server.ts` 的 `Map<network, Set<hostId>>`)—— ⛔ **不许在直连模块里另写一份**("同一事实两处写"是本线老病根)。
- 跨网 / 不在白名单 ⇒ **显式拒绝 + 计数**(⛔ **不许静默返空** —— 本线反复踩的假绿就是这一类)。
- ⛔ 候选只含**地址与端口**,⛔ 不含任何密钥 / 身份材料。
### 4.2 打洞探测 —— 照抄成熟做法的最小集(⛔ 不发明)
- 用 Node 内建 `dgram`,**本机绑一个 UDP 口**(端口从既有参数表区间取,⛔ **不写魔数**)。
- 双方**同时向对方候选地址发包**(hole punching 的标准做法);任一方在 `PUNCH_DEADLINE_MS` 内**收到对方的包** ⇒ 该方向成立。
- **双向都成立**才判直连可用;否则**判死**。
- 判死后:**立即降级中继**,且该候选**进冷却**(复用本线既有冷却纪律,⛔ 不自造一套)。
### 4.3 开关与默认值(用户口径 ①②③ 的落点)
- 配置项(暂名 `DSHS_OVERLAY_DIRECT`)**缺省 = 开**。
- **关闭 ⇒ 零 UDP socket**(可断言:`ss -lunp` 无新增),且**不发** `DIRECT_CANDIDATE`。
- **提示**:ⓐ `join` 时 stdout 打印;ⓑ 设置/管理面显示。
### 4.4 P2P 取回接线(S6)
- `source.ts` 的 fetcher:**优先直连通道取块,失败回落 wss**。
- 🔴 **接线后必须复算块 id**(否则命中本线老病根"假绿")。
### 4.5 乙方案的落地形态(⚠️ 如实写的技术现实)
- 乙 = 云安全组只放行**对端节点 IP**。⚠️ **源 IP 的现实**:家宽 / 移动节点的**公网出口 IP 会变** ⇒ 规则会失效、且云安全组规则数**有上限**。
- **⇒ 序㊴ 自决(可推翻):乙-1 起步** —— 只对**有固定公网 IP 的节点**(云节点 / 专线)开入站;**家宽节点本期走中继**(⛔ 不影响可用性,只影响"它能不能被直连")。
- **乙-2**(半自动:平台算清单 + 人工 / 脚本写控制台)与 **乙-3**(云 API 自动化,🔴 **需云 AK/SK**)**登记为回头条件**(§9),⛔ 本单**不请求凭据**。
---
## §5 判据(先红后绿;⛔ 不许事后补写)
| # | 判据 | 形状 | 先红腿(对**旧产物**) |
|---|---|---|---|
| **D1** | 候选交换**只走同网 + 白名单** | 跨网 / 白名单外 ⇒ **显式拒绝 + 计数**(⛔ 非静默返空) | 旧产物无该消息类型 ⇒ 入口即缺 ⇒ **红** |
| **D2** | **开关生效** | `direct = false` ⇒ **零 UDP socket**(`ss -lunp` 无新增)+ 不发候选 | 旧产物无开关 ⇒ **红** |
| **D3** | **默认值 = 开**(用户口径 ②) | 新节点加入后配置读回 = `true` | 旧产物无该键 ⇒ **红** |
| **D4** | **提示存在**(用户口径 ③) | `join` stdout 匹配提示文案(含"怎么关") | 旧产物无提示 ⇒ **红** |
| **D5** | 打洞**成功路径** | 同网 + 双向白名单 ⇒ 直连建立(判别器 `punchOk > 0`) | 旧产物无实现 ⇒ **红** |
| **D6** | 打洞**失败判死** | 对端不响应 ⇒ 在 deadline 内**降级中继**+**不重试风暴** | 旧产物无实现 ⇒ **红** |
| **D7** | 🔴 **块 id 复算** | peer 接线后块 id 与接线前**逐字一致** | ⛔ **漏了这条 ⇒ 判据不算通过**(会把"零回源"做成假绿) |
| **D8** | **乙方案生效** | 云安全组 UDP 段 source **≠** `0.0.0.0/0`;且**仅**列出的对端 IP 可打洞成功 | 收口前态 = 无 UDP 入站 ⇒ **红** |
**零回归三件套(每期收口必跑)**:`npm.cmd test`(Node 22,**基线 201/200/0/1**)|`node scripts/overlay-failover-drill.cjs --scene all --table`(**基线 12P/0S/0F**)|`node scripts/overlay-probe.cjs --table`(**基线 25P/0S/0F**)。⚠️ `npm.cmd test` **没有** `--table` 参数;探针**不在** `npm test` 覆盖内。
---
## §6 步骤(每步自带验收 + 回滚)
### S5 · 直连候选交换 + 打洞探测
1. `direct/candidate.ts`(复用白名单判定)⇒ 验收 = **D1** 先红后绿。
2. `direct/punch.ts`(`dgram`,判死 + 冷却)⇒ 验收 = **D5 / D6**。
3. `direct/index.ts`(开关 + 默认值 + 提示)+ `join.ts` / `overlay-node-join.cjs` 接线 ⇒ 验收 = **D2 / D3 / D4**。
4. `scripts/overlay-direct-probe.cjs`(只读观测)+ 参数表新键与 `OBS-26+`。
5. ⚠️ **云安全组改动(乙-1)**:只在**确认对端 IP 清单**后开 UDP 小段入站 ⇒ 验收 = **D8**。
**回滚**:关 `DSHS_OVERLAY_DIRECT` ⇒ 秒级回落中继;云安全组规则**删除**即恢复零 UDP 入站。
### S6 · `peer` 档接线
1. `source.ts` fetcher 优先直连、回落 wss ⇒ 验收 = **D7**(**块 id 复算**,⛔ 漏了不算过)。
2. 回归"零回源 / 共享命中"既有读数(对照序㉔ / 序㉛ 基线)。
### S7 · 规模回归
候选数、**直连成功率**、**切换耗时**、**回源字节**四项复测 ⇒ 与既有基线(参数表 §3.2 / §7)逐项对照。
---
## §7 权限影响评估(R5)—— ✅ **用户已拍板(2026-09-18 12:22)**
| # | 项 | 结论 |
|---|---|---|
| 1 | **扩了什么** | 47 / 106 云安全组新增 **UDP 小段入站**,**source 仅对端节点 IP**(**乙**,⛔ 非 `0.0.0.0/0`);节点侧新增 **UDP socket(用户态,无内核驱动)** |
| 2 | **谁受影响** | 仅**同网 + 双向在白名单**的节点之间;⛔ 不对公网任意源开放、⛔ 不跨网、⛔ 不影响租户实例既有隔离面 |
| 3 | **不扩大也能实现的方案** | **有 —— 不做直连**(跨机流量继续走中继);P1 已交付的"一键加入 + 分组准入"**仍然完整可用** |
| 4 | **回滚方式** | 关闭开关 ⇒ 秒级回落"全走中继";删云安全组规则 ⇒ 恢复零 UDP 入站 |
| 5 | **用户已知悉** | 2026-09-18 08:2x 选 B 时已写明"需新开直连端口 = 扩大暴露面、命中 R5";**12:22 拍板 = 用户可控 + 默认开 + 提示 + 乙** |
🔴 **硬约束(⛔ 不许松)**:⛔ 直连**不得**让任何节点反向进入别人的机器|⛔ 打洞**不得**绕过既有身份强制与吊销|⛔ 白名单 / 同网判定**必须复用既有实现**(⛔ 不另写一份)|🔴 密钥本体 ⛔ 不经网络 / 不经 relay。
---
## §8 回报格式(执行棒回填;⛔ 只填空,不改小节点名)
> **本棒(序㊵ 执行棒 · P2/S5)已回填 · 2026-09-18 12:33–13:2x**
### 8.1 开工门(必填)
- 本单 §8 前前缀校验值:**`8925c2460810c6dc5cdbc075b6cdaed2`**(现算 `sed '/^## §8 回报格式/,$d' <本单> | md5sum`,与 §11.2 **逐字一致** ✅;口径复核 = 本文件 `CR=0 / LF=239` = **纯 LF**)
- 取得全局执行锁的会话名与时间:**`序㊵ 执行棒(P2·S5)`** / **2026-09-18 12:33**(`bash dsh-server-docs/scripts/handoff-guard.sh --claim-exec`;收口时 `--release-exec` 释锁)
### 8.2 逐判据回报(D1–D8 各一条,含**原始读数**)
**D1 · 候选交换「只走同网 + 白名单」**
- 🔴 **先红**(对旧产物):`node -e "require('/opt/dshs/lib/net/relay/direct/index.js')"` ⇒ `Error: Cannot find module '/opt/dshs/lib/net/relay/direct/index.js'`;`direct 目录: ❌ 不存在(旧产物无该入口 ⇒ 入口即缺)`
- 🟢 **后绿**:`PASS OBS-27 接受 3(阈值 ≥ 2) ✅|具名拒绝 9 条(阈值 ≥ 3) ✅|静默拒绝=0 ✅|按网分桶(同名跨网互不可见)=true ✅|准入实现=network.ts#isAllowedDialer`
- 机读原文字段:`candidate.accepted=3`/`rejected=9`(**每条都带具名 `reason`**:`cross-network` / `not-self-candidate` / `not-a-dialer` / `self-not-a-dialer` / `bad-shape`×2 / `secret-field` / `bad-address`×2 / `too-many-addrs` / `stale`)/`silentRejections=0`/`bucketsPerNetwork=true`/`matcher='network.ts#isAllowedDialer'`
- 🔴 **"不另写一份"的机器守卫**:`test/overlay-direct.test.mjs#T4` 读 `src/net/relay/server.ts` **源码原文**核对两处闸门(注册闸门 `const wantDialer = this.dialers.get(network)?.has(hostId) === true` / DIAL 闸门 `if (this.dialers.get(session.network)?.has(session.hostId) !== true)`),并断言 direct 模块内**不出现** `dialers.get(` 与 `.has(hostId)` ⇒ 本棒本地 `node --test test/overlay-direct.test.mjs` = **10 tests / 10 pass / 0 fail**
**D2 · 开关生效(`direct=false` ⇒ 零 UDP socket + 不发候选)**
- 🔴 **先红**:旧产物无该入口(同上);且 47 上 `DSHS_OVERLAY_DIRECT` 在 `/etc/dshs.env` + `/etc/dshs-worker.env` + `/etc/systemd/system/**` 的命中数 = **0**(**"无处可关" = 红**)
- 🟢 **后绿(三层证据并列,缺一层都可能是假绿)**:
① **实现层**(47 真机读数,同一份代码):`onCase { raw:'true', enabled:true, candidatesEmitted:1, udpSocketsOpened:1 }` ⇄ `offCase { raw:'false', enabled:false, candidateReason:'disabled', candidatesEmitted:0, udpSocketsOpened:0 }` ⇒ **开 ⇒ 1/1,关 ⇒ 双零**
② 🔴 **内核可见层**(47 真机 `ss -lunp`,端口区间 **21100–21115**):`udp = { ssBefore:0, ssDuringOnCase:1, ssDuringOffCase:0, ssAvailable:true, directRange:[21100,21115] }` —— ⚠️ `ssDuringOnCase` 是**正对照**(真绑一个直连口后 `ss` 必须看得见),原文 `UNCONN 0 0 0.0.0.0:21100 0.0.0.0:* users:(("node",…))` ⇒ **"关闭态 0 行"因此是可信的 0**,⛔ 不是"测量坏了也是 0"
③ **真机打洞腿**:`overlay-direct-probe.cjs punch --peer 106.54.21.172:21100 --deadline 3000` ⇒ `reason=deadline 收包=0 发出=21 耗时=3000ms`(⛔ 被云安全组挡 ⇒ 见 **D8**)
- ⚠️ **口径(⛔ 不许读成"生产流量已变")**:S5 **未做 S6 接线** ⇒ 直连**尚未进入数据面**;D2 量的是**实现层 + 内核可见层**,⛔ **不是端到端流量**。
**D3 · 默认值 = 开(用户口径 ②)**
- 🔴 **先红**:旧产物无该键(入口缺失);47 上 `/etc/dshs/overlay-node.json` **不存在**(`node_config_exists=no`)
- 🟢 **后绿**:`switch.defaultCase = { enabled:true, source:'default' }`(⛔ 不是"缺省关")+ `joinConf = { direct:true, readback:true, respectsArg:true, stepDetail:'已落地 /tmp/dshs-direct-selfcheck-9w6NY0/node-a.json(0600)|直连(打洞)=开(缺省)' }` ⇒ **新节点加入后配置读回 = `true`**
- 🟢 **P1 出口项(管理面 API)先红后绿**:`/api/admin/overlay-nodes` **404 → 401**、`/api/admin/overlay-nodes/direct` **404 → 401**(对照 `/dshs-overlay/bootstrap` = **200**、门户 = **200**)
- 🔴 **诚实登记**:**真机先红腿不可取** —— 旧产物**根本没有**"本机配置带回 `direct`"这一行为(47 上无 `overlay-node.json` ⇒ 无从"读到旧键")⇒ D3 主证据落在本棒自己的 `joinConf` 读数(走**部署后真 lib** + 系统临时目录,⛔ 零生产副作用)
**D4 · 提示存在(用户口径 ③,须含"怎么关")**
- 🔴 **先红**:旧产物无提示(入口缺失)
- 🟢 **后绿**:`hint = { parts:3, mentionsOff:true, mentionsScope:true, mentionsImpact:true }`(阈值 `DIRECT_HINT_MIN_PARTS = 3`);三段原文要点 = ① **谁会连进来**(只有同网 + 双向在白名单里的节点;⛔ 公网任意源、⛔ 跨网连不进来)② **怎么关**(`DSHS_OVERLAY_DIRECT=0`/`false`/`off`/`no`,或管理面把本机配置 `direct` 改 `false`;关闭后**不再绑任何 UDP 端口、也不再发候选**)③ **关掉影响什么**(⛔ 不影响接入与准入,跨机流量改走中继,代价只是"多一跳")
**D5 · 打洞成功路径**
- 🔴 **先红**:旧产物无实现(入口缺失)
- 🟢 **后绿**:`punch.success.bidirectional = true`、`attemptsOk = 2`(阈值 `DIRECT_PUNCH_MIN_OK = 1`);单向负腿 `oneWay = { bidirectional:false, a:{reason:'one-way',recvLocal:0,peerSeen:true}, b:{reason:'one-way',recvLocal:8,peerSeen:false} }` ⇒ **"有收包"一律不算成功**
- ⚠️ **诚实边界(⛔ 不许夸大)**:云上**真机双向**不可达(云安全组拦 UDP 入站;实测双向 `0/20`、`0/20`)⇒ 成功路径由**离线 NAT 模拟器**(`SimulatedNat` + `runPunchPair`,用**真 `dgram` socket** 跑**同一份**打洞代码)承载,机器可断言。⛔ **这不是"公网一定能打洞"的证明**。
**D6 · 打洞失败判死(+ 不重试风暴)**
- 🟢 **后绿**:`dead[0] = { reason:'deadline', elapsedMs:600, deadlineMs:600, bounded:true, sent:5, recvLocal:0 }` ⇒ **判死有界**;冷却 `cooldown = { ms:300000, blocked:1, secondAttemptReason:'cooldown', secondAttemptBlocked:true, zeroRejected:'throws' }`(⚠️ `DirectCooldown` 收 `ms <= 0` ⇒ **构造期直接抛**,原文 `直连冷却时长必须 > 0,收到 0(🔴 0 ⇒ 判死退化成重试风暴……)`)
- 🟢 **真机腿**:同 D2-③ —— `deadline / 收包 0 / 发出 21 / 耗时 3000ms`(**有界**)⇒ 判死 + 进冷却 **300000 ms**
- 🔴 `RELAY_FAILOVER_COOLDOWN_MS=0` 命中数 = **0**(⛔ 全仓 + 两机 env + drop-in 全扫)
**D7 · 块 id 复算 —— 🔴 不在本棒范围(属 S6)**
- ⛔ 本棒(S5)**未做** `peer` 接线 ⇒ **无块 id 变化可复算**。如实登记 = 🔴 **未验证**(⛔ 不编数)
- 变更面自证:本棒**未改** `src/net/relay/content/**` 任何一个字节(块 id 计算路径**零改动**)
- 🔴 提醒下一棒(S6):**漏了这条 ⇒ 判据不算通过**(§9-5)
**D8 · 乙方案生效 —— 🔴 未实施(红)**
- 🔴 **先红腿(收口前态)成立 · 双向实测**:`106 → 47.77.182.89:21100` 发 **20** 收 **0**(`RECV_COUNT=0 / RECV_SOURCES=(none)`)|`47 → 106.54.21.172:21100` 发 **20** 收 **0**(同)
- **现状**:**未开任何 UDP 入站规则**;宿主机侧读不到云安全组规则本体(`nft input policy = accept`、`nft list ruleset` = **72 行**与收口态逐字一致 ⇒ **挡在云侧、⛔ 非 nft**)
- 🔴 **未实施的理由(属 §1 边界外第 ④ 类「需用户提供的账号凭据或审批」)**:改云安全组需**云控制台访问方式 / 审批**,而本棒硬门**明令不许索取或硬编码任何云 AK/SK**(乙-3 = 回头条件)⇒ **上抛**,⛔ 未绕过、⛔ 未试探性开洞
- 🔴 **拟实施的 乙-1 规则(逐条,用户开控制台后按此录;⛔ 本棒未录)**:
| # | 目标实例(公网 IP) | 方向 | 协议 | 端口范围 | 源 source | 动作 |
|---|---|---|---|---|---|---|
| 1 | **47** · `47.77.182.89` | 入站 | **UDP** | `21100/21115` | **`106.54.21.172/32`** | 允许 |
| 2 | **106** · `106.54.21.172` | 入站 | **UDP** | `21100/21115` | **`47.77.182.89/32`** | 允许 |
- ⛔ **零个 `0.0.0.0/0` 条目**;⛔ **拆分等价写法一并禁止**(`0.0.0.0/1 + 128.0.0.0/1`、`::/0`、`0-255.0-255.0-255.0-255` 等**任何**"对公网任意源开放"的形态 ⇒ 命中 **§9-3** = **立刻停手回滚**)
- ⛔ 不开 TCP 段|⛔ 不开出站变更|⛔ 不动既有任何规则
- ⚠️ 端口区间来自参数表 `PUNCH_PORT_BASE = 21100` / `PUNCH_PORT_SPAN = 16` ⇒ `[21100, 21115]`(⚠️ **值随参数表变**,⛔ 不写死在别处)
- ⚠️ 回头条件:**§9-6**(家宽 / 移动节点公网出口 IP 会变 ⇒ **乙-1 覆盖不到** ⇒ 乙-2 / 乙-3,后者**需用户提供云 AK/SK**)
### 8.3 零回归三件套(含各项原始输出尾部)
| # | 命令 | 本次读数 | 基线 | 判定 |
|---|---|---|---|---|
| 1 | `npm.cmd test`(Node 22) | **201 tests / 200 pass / 0 fail / 1 skipped**(rc=0) | 201/200/0/1 | ✅ **逐字一致** |
| 2 | `overlay-failover-drill.cjs --scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**(rc=0;幕4-C **23422 ms** / deadline 30000) | 12P/0S/0F | ✅ |
| 3 | `overlay-probe.cjs --table`(真机) | **28 PASS / 0 SKIP / 0 FAIL**(rc=0) | 25P/0S/0F | ✅ **+3 = 新增 `OBS-26/27/28`** |
⚠️ 两条口径沿用序㊲:❶ `npm.cmd test` **没有** `--table`;❷ 探针**不在** `npm test` 覆盖内。
⚠️ **一次跑偏 + 已修复(如实登记)**:第一次 `--scene all` 被本棒追加的 `timeout 300` **截断** ⇒ 只出 **8 行**、且**幕4 之后的 `restore()` 未执行** ⇒ **106 `dshs-relay` 被留在停机态**(13:05:58 停、未启)。发现后**立即 `systemctl start`** 复原(`active` / `/status=200`),随后**不加外层时限重跑** ⇒ **12P/0S/0F** 且末尾 `restore()` 正常执行(两台均 `active`)。**教训**:`--scene all` 内含 **120 s + 90 s** 两段观察窗口 ⇒ ⛔ **不许给它套小于 ~8 min 的外层超时**。
### 8.4 恢复形态核实(49 / 106 两端单元与服务状态 + 门户码)
- **47**:`dshs=active` / `dshs-worker=active` / `dshs-relay=active`;relay `/status` = **200**;门户(`Host: alotbuy.com`)= **200**;`/dshs-overlay/bootstrap` = **200**
- **106**:`dshs-worker=active` / `dshs-relay=active`;relay `/status` = **200**
- **47 视角中继读数**:`used=2 / max=7515 / endpoints=[["w-106",19000],["w-106",21001]]`
- **探针四条口径行**:`PASS OBS-01 在册节点 used=2 (阈值 ≥ 2)` | `PASS OBS-04 identityOk=2 (阈值 ≥ 2)` | `PASS OBS-08 端点表 2 条 / 离线 0 条` | `PASS OBS-09 在册实例面(派生 1 条):w-106:40679=401`
- ⚠️ **演练态**曾出现 **24P / 2F / 2S**(2F = `OBS-01 used=1`、`OBS-04 identityOk=1` —— **计数**判据;2S = `OBS-08` / `OBS-09` 走"**对端中继声明了端口** ⇒ 客户端挂在另一台中继上"腿)⇒ 按既有程序**停 / 启 106 `dshs-relay`**(~24 s)⇒ 47 视角回落 `used=2` ⇒ 探针回 **28P/0S/0F**(⛔ **未重启任何 worker**、⛔ **未换实例**)
- ⚠️ `OBS-09` 派生端点端口由 **42783 → 40679** 再漂一次(**设计内**:判据跟着事实走、⛔ 未改表值 —— 这正是序㊲ 要拿到的收益)
### 8.5 边界自证(**逐条打勾**)
- ⛔ **未 commit / 未 push**:HEAD 仍 **`09ce76f`**;`git status --short` = **17** 处(与开工**逐数相同** ⇒ **零新增条目**;全部为 P1/P2 在册工作树文件)
- 🔴 **云安全组 UDP 段的 source 逐条列出**:**当前无任何 UDP 入站规则(本棒未实施)**;拟录清单见 **§8.2-D8** 两条 —— 源分别为 `106.54.21.172/32` 与 `47.77.182.89/32`,⛔ **零个 `0.0.0.0/0`**、⛔ **零个拆分等价写法**
- ⛔ **未改 `server.ts` 的 DIAL / 白名单语义**:`src/net/relay/server.ts` 本棒**零字节改动**;`src/net/relay/network.ts` **只加** `isAllowedDialer`(**纯新增**,⛔ 无调用点替换、⛔ 未动既有函数)—— 由 `T4` 读源码的机器守卫固化
- ⛔ **未改 nft / nginx**:`nft list ruleset` = **72** 行(与收口态**逐字一致**);nginx 配置合计 md5 = `ea0cfc17c60872eecd626b840370793`(未动)
- ⛔ **未改 bwrap 参数**(`--perms` 等 0.5+ 专属选项**一律未出现**;47 = bubblewrap 0.4.0 未被触碰)
- 🔴 **`RELAY_FAILOVER_COOLDOWN_MS=0` 命中数 = 0**(⛔ 未写进任何回滚 / 演练 / 夹具路径;本棒新建的冷却**构造期断言**进一步把 `0` 变成"抛异常")
- 🔴 **密钥本体不经网络 / relay**:本棒**不下发、不传输、不打印**任何密钥;`/etc/dshs/overlay-dir-key.pub`(**公钥**)为既有文件、本棒**未读其内容**
- **块 id 复算证据(D7)**:🔴 **不适用**(D7 属 **S6**)⇒ 本棒 **未验证**;变更面自证 = `src/net/relay/content/**` **零改动**
- **本棒对生产的动作清单(均 R8,动手前已一句话说明,⛔ 无不可逆项)**:① 铺产物 **4 处**(47 `/opt/dshs/lib` + `/opt/dsh-relay/lib`;106 `/opt/dshs-cluster/lib` + `/opt/dsh-relay/lib`,**32 个 md5 全同、不一致条数 0**)② 铺 3 个脚本到 47 `/opt/dshs/scripts/` ③ 重启 47 `dshs` + `dshs-worker` ④ 跑 `--scene all`(自停 / 自启 relay)⑤ 停 / 启 106 `dshs-relay` 一次(复原 47 视角)⑥ 🔴 106 `dshs-relay` 一次**意外停机**(超时截断所致,**已立即复原** —— 见 §8.3 末)⇒ 全程 ⛔ **未删任何数据 / 配置**、⛔ 未改任何 `env` **值**(`DSHS_OVERLAY_DIRECT` 两机命中 **0** ⇒ 走**缺省即开**)
### 8.6 如实留档(**必填,哪怕"没有"**)
1. 🔴 **D8 未实施(红)** —— 见 §8.2-D8:需**用户提供云控制台访问方式 / 审批**(§1 边界外第 ④ 类)。⛔ **未索取、未硬编码任何云 AK/SK**;⛔ 未做任何"自动化开洞"。
2. 🔴 **一次自造的生产影响(已修复)**:`--scene all` 被 `timeout 300` **截断** ⇒ `restore()` 未执行 ⇒ **106 `dshs-relay` 停机约 4 分钟**(13:05:58 → 13:10:0x 发现后重启)。影响面 = 该窗口内 106 worker 的跨机流量走候选链(**自愈**),⚠️ **无业务中断证据**(该窗口内门户 / 实例面**全程 200**)。⛔ 已把"不许给它套小超时"写进 §8.3。
3. 🔴 **本棒修掉的两条自造缺陷**(均在**本棒新建的观测脚本**内,⛔ **不涉任何既有生产代码**):
- `--ss` 是**布尔开关**却被按"带值选项"解析(`opt()` 只认字符串)⇒ 该腿**恒为 `null`(死代码)**;且正对照腿硬编码 `null` ⇒ "关闭态 0 行"在**测量本身坏了**时**也会是 0** = **潜在假绿**。修 = ① 按"**出现过**"判定 ② 新增**正对照**(**真绑一个直连口**,要求 `ss` 必须看得见)。
- `punch` 子命令从 `direct/index.js`(**选择性桶导出** —— 它 import 了那几个常量供**内部**用、**不向外转发**)取 `DEFAULT_PUNCH_DEADLINE_MS` / `DEFAULT_DIRECT_COOLDOWN_MS` ⇒ `undefined` ⇒ `Number(undefined)` = `NaN` ⇒ **冷却构造期断言直接抛**(原文 `直连冷却时长必须 > 0,收到 null`;⚠️ **`null` 是假象** —— `JSON.stringify(NaN) === 'null'`)。修 = 改从 `punch.js`(**常量真身**)取 + 新增**启动期硬断言**(缺任一常量 ⇒ 具名 `exit 2`,⛔ **不再静默取缺省**)。
4. ⚠️ **参数表口径与实现的一处微差(只登记,⛔ 未改判据)**:`OBS-26` 的参数表附注写"真机的 `ss -lunp` 增量腿由 `selfcheck --ss` 另取" ⇒ 该腿**产出证据、但未参与判据**(判据仍是 `offCase.udpSocketsOpened === 0`)。本棒**未改**已发布的阈值口径 ⇒ 如实登记为**证据项**而非判据项。
5. ⚠️ **夹具"先红后绿"的 diff 是 4 行不是 3 行**(原文级):第 **34–36** 行 = `OBS-26/27/28` 由 `PASS → FAIL`,**第 37 行 = 汇总行** `❌ 8 项红:…` → `❌ 11 项红:…`(由前三行**派生**)。序㊵ 之前"只差三行"的口径指的是**判据行**。
6. ⚠️ **夹具模式下有 8 条既有红项**(`OBS-08/11/13/14/16/17/19/20`)—— 夹具未给它们的输入、而那些判据 `judged=true` 仍按真值判 ⇒ ⛔ **非本棒引入**;判定本棒判据的**唯一正确口径 = 绿 / 红两腿的逐行 diff**(⛔ 不许拿"夹具全绿"当验收)。
7. ⚠️ **`overlay-direct-probe.cjs` 未被 `npm test` 覆盖**(与 `test/overlay-direct.test.mjs` 同类:`npm test` 是**硬编码文件列表**)⇒ 该脚本的回归靠**本棒手工三腿**(`selfcheck` / `punch` / 夹具对照)。⛔ **未改 `package.json`**。
8. ⚠️ **本棒只做 S5**:⛔ 未做 **S6**(`peer` 接线 + `D7` 块 id 复算)、⛔ 未做 **S7**(规模回归)—— 各是独立后续棒。
9. ⚠️ **模板笔误(只留档,⛔ 未改小节点名)**:**§8.4 小节点名把 47 写成「49」**("49 / 106 两端…")。本棒按 **47 / 106** 如实回报,⛔ **未改小节点名**(保 §8 结构不动)。
10. ⚠️ **本棒两处"未验证"如实登记**(⛔ 不编数):**D7 块 id 复算**(属 S6)|**真机双向打洞成功**(云安全组未开 ⇒ 只测到 `deadline / 0 收包`;成功路径由**离线 NAT 模拟器**承载,见 D5)。
### 8.7 指纹
- 本单 **§8 前前缀**(口径:`sed '/^## §8 回报格式/,$d' <本单> | md5sum`):**`8925c2460810c6dc5cdbc075b6cdaed2`**(回填 §8 全节后**现算,与 §11.2 逐字一致** ✅ —— §8 是**截断点**,故回填不改变它;= "§8 是截断点"这条口径的又一次自证)
- 参数表 **§10 指纹**:`d408d640246a980f702fe7b0a2895219` → **`6b37bfd506758d882d9f803678f85d23`**(本棒改动后**现算并同步**;口径 = `sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum`)
---
### 8.8 序㊶ 执行棒回报(**P2 · S6:`peer` 档接线 + D7 块 id 复算**;2026-09-18 13:35–14:0x)
> ⛔ 本小节**只新增**:**未改 §1–§7 正文**、**未改 §8.1–§8.7 小节点名**。**§8 前前缀 `8925c2460810c6dc5cdbc075b6cdaed2` 回填本小节后现算仍逐字不变** ✅(见 §8.8-⑧)。
**① 开工门**:`state.py` 已读;入口 `接续入口_覆盖网络线_20260916.md` **§0 最新两行 + §2 首行**已读(= 唯一执行依据);全局执行锁 `--claim-exec "序㊶ 执行棒(P2·S6)"` **抢到**(09-18 13:35)。**§8 前前缀开工前现算 = `8925c2460810c6dc5cdbc075b6cdaed2`** ✅ **逐字一致**(与 §11.2 / §11.5 记的值相同)。**范围 = 只做 §6 的 S6**(⛔ 未做 S7)。
**② D7 逐条回报(**先红后绿**;每一项都是原文读数,⛔ 无编数)**
- **D7 主判据 · 块 id 不变性(接线前后逐字一致)** —— 取数脚本 = **同一份** `_tmp_seq41/ids.mjs`(两版本只用**共同**构造参数,⛔ 避免"接线前"侧报错 = 假红):
**`idsDigest` = `ec70e7166ae24b1367c7c251ead8ad2979f66463ae3e6380eeda84fabb985c3a`** —— **四处读数逐字相同**:
① 本机 `_tmp_seq41/lib_before`(**接线前**本机产物)② 本机 `lib`(**接线后**)③ **47 `/opt/dshs/lib`(生产接线前)** ④ **47 `/opt/dshs/lib`(生产接线后)**。
明细:`plain putPlan[0] = 3374319c90813e8f7911654132f34a0c` / `cipher putPlan[0] = 8dd3ea58093df7130ba07722cd28bf81`(夹具 = 5,255,225 B 确定性字节;两臂 `plain` / `cipher`,密文臂 = `ContentCipher({ groupKey: 'ops|s6', epoch: 1, key: makeKey(7) })`)。
- **D7 先红腿 ① · 扰动块 id**:临时把 `blockIdOf` 改成 `sha256(bytes ‖ ff)` ⇒ `idsDigest` → **`fbb80585c86a4c492f6d71a7af9212121f35c2c43f2cc7018d08149e2cff5ac3`**、六个 plain id 全变(`3374319c…` → `7fdda0ab1af32e8f531528518d7baa49`);**还原后** `chunker.ts` md5 逐字回到 **`a69b3379c719562c46d3e63832bdf25d`**、`diff` = **空** ✅(⇒ 判据**能真红**、**还原可逐字复现**)。
- **D7 先红腿 ② · 拆掉复算闸门**(把 `const actual = blockIdOf(gotBytes)` 改成 `const actual = id`):`peer-wire` **判据 ⑦ 由 PASS 转 FAIL**,原文 `❌ ⑦ 🔴 D7 篡改块被丢弃(不算命中)|{"tier":"peer","bytesMatch":false,"hits":{"direct":0,"wss":1},"errors":{"direct":0,"wss":0},"idChecks":1,"idMismatches":0,"peerHitsTotal":1}` ⇒ **篡改块被记成"peer 命中成功"、而字节与块 id 都不符 = 假绿本形**(这正是 D7 要拦的东西);**还原后** ✅ 转绿、`idMismatches=1`、`tier=null`(= **丢弃且不算命中**)。
- **D7 观测面 · 新增子命令 `peer-wire`**(`scripts/overlay-direct-probe.cjs`,**13 条具名判据**):**本机 + 47 侧各 13/13 全绿**;块 id 不变性读数 `idsDigest = 79c77473fd4f4de815d529e04055091e0403fb4c1321a0dc768283b5d9955952`(**两机逐字相同** = D7 的**机器侧旁证**)。判据覆盖:direct 优先且**不碰 wss** / `no-address` ⇒ **具名回落 wss** / 开关关 ⇒ `disabled` / 开关非法 ⇒ `invalid-switch` / 冷却 ⇒ 具名降级 / **未装通道 ⇒ `not-wired` ×2 且诚实回"没有"** / **篡改块被丢弃** / 好块被接受 / 通道说没有 ⇒ `miss` / 通道抛错 ⇒ `errors` / 只收 S5 准入 `ok:true` 的候选 ⇒ `data-plane-pending` / 被拒 verdict 不进候选 / 不变性读数产出。
- **D7 真机形态 · `content.peerWire` 新键在线**(两机 relay `/status`;**纯新增键、⛔ 未动任何既有键**):`{"order":["direct","wss"],"wired":{"direct":true,"wss":false},"attempts":{"direct":0,"wss":0},"misses":{"direct":0,"wss":0},"errors":{"direct":0,"wss":0},"unavailable":{"direct":0,"wss":0},"hits":{"direct":0,"wss":0},"downReasons":{},"idChecks":0,"idMismatches":0,"directCandidates":0,"last":null}` ⇒ **接线已装、空闲零流量**;既有读数 `source = {"local":2,"peer":0,"edge":0,"region":0,"origin":0}` **未变**;106 侧 `crypto present: True`。
- **D7 结论**:🟢 **通过** —— `peer` 档接线后**块 id 与接线前逐字一致**(`ec70e716…`,四处同值)+ 复算闸门**先红后绿**可用 + 真机新键在线且零流量。
**③ E1 回归("零回源 / 共享命中";对照序㉔ / 序㉛ 基线)** —— 同一份 `_tmp_seq41/e1.mjs`,两臂:
| 臂 | 接线口径 | 对照腿(⛔ 不装通道) | 序㉔/㉛ 基线 | 判定 |
|---|---|---|---|---|
| A · 链级夹具(序㉔ 原口径) | `originFetches=6` / `originBytes=5,255,225` / **`amp=1.0000`** | `originFetches=24` / `originBytes=21,020,900` / `amp=4.0000` | 1.00× ⇄ 4.00× | ✅ 逐项对齐 |
| B · S6 接线口径(4 个真 `ContentRuntime`) | `servedFromPeer=18` / **`amp=1.0000`** / 逐节点 `{B:peer6, C:peer6, D:peer6}` / `peerWire.attempts={direct:0,wss:6}`、`hits=[6,6,6]`、`idChecks=[6,6,6]`、`idMismatches=[0,0,0]`、`downReasons=[{direct:not-wired:6}×3]` | `servedFromPeer=0` / `amp=4.0000` / `downReasons={direct:not-wired:6,wss:not-wired:6}` | 4.00× → 1.00× | ✅ |
**`idGate = {idChecks:18, idMismatches:0}`** ⇒ 命中链路**每一步都复算过块 id**(⛔ 不是"先信后验")。`node --test test/overlay-content.test.mjs` = **44 tests / 44 pass / 0 fail**。
**④ 零回归三件套**
| 项 | 本棒读数 | 基线 | 判定 |
|---|---|---|---|
| `npm.cmd test` | **201 / 200 / 0 / 1**(rc=0) | 201/200/0/1 | ✅ 逐字同 |
| `node scripts/overlay-failover-drill.cjs --scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**(rc=0;**6m27s**;幕4-C **18990 ms** / 30000;**未套外层超时**) | 12P/0S/0F | ✅ |
| `node scripts/overlay-probe.cjs --table` | **28 PASS / 0 SKIP / 0 FAIL**(rc=0;**部署后 + 演练后两次同值**) | 28P/0S/0F | ✅(⚠️ 基线**已是 28P**,⛔ 未拿旧的 25P 当对照) |
🔴 **上棒的坑本棒已避开**:`--scene all` **未套任何外层超时**(跑满 6m27s)⇒ 末尾 `restore()` 正常执行 ⇒ **两台 `dshs-relay` 均 `active`** ✅(⛔ 未出现 106 relay 停机态);演练后 47 视角**未出现** `endpoints=[] / used=1` ⇒ ⛔ **无需**停/启 106 relay。
**⑤ 交付与部署**(R8:动手前一句话说明;⛔ 无不可逆项)
- **改动面 = 6 个文件**(`diff -rq` 定位):`lib/net/relay/content/runtime.{js,d.ts,js.map}` + `lib/net/relay/content/source.{js,d.ts,js.map}`。源侧 = `src/net/relay/content/runtime.ts`(`peer` 接线 + **D7 复算闸门** + `peerWireSnapshot()` + 直连候选入册/退册 + 注入式 `peerChannels`)+ `src/net/relay/content/source.ts`(**仅注释**兑现度说明,**零逻辑改动**)。
- **部署 4 处**(47 `/opt/dshs/lib` + `/opt/dsh-relay/lib`;106 `/opt/dshs-cluster/lib` + `/opt/dsh-relay/lib`):`runtime.js` md5 **四处全同 = `c9a8b01a78524d31cd77fde32895839a`**、`source.js` = `43cb1b7ab5387ff9745e8dab7726b68b`(**接线前** `runtime.js` = `7fe548c42b71b9e4785f139561fe8925` = **47 生产原值** ✅ 先红腿凭此成立)。脚本侧 = `scripts/overlay-direct-probe.cjs` 铺 47 `/opt/dshs/scripts/`(md5 `8d1ea60a01fc09fb5f56e492afb76fc9`)。
- **重启**:47 `dshs-relay` + `dshs`;106 `dshs-relay`。**回滚点** = 两机 **`/opt/dsh/backups/seq41-20260918-135146/`**(各 **4** 个文件);48 h 内旧产物另有 `/opt/dsh/backups/seq40-0918-1255/`。
**⑥ 边界自证(逐条打勾)**
- ⛔ **未 commit / 未 push**:HEAD 仍 **`09ce76f`**;`git status --short` = **19** 处 = 开工 **17** + 本棒新改 `content/runtime.ts`、`content/source.ts`。
- ⛔ **未改 `src/net/relay/server.ts`**(DIAL / 白名单**语义**零字节改动;准入判定**复用** `network.ts#isAllowedDialer`,⛔ 未另写一份)。
- ⛔ **未改 `src/net/relay/content/**` 的块 id 计算路径**(`sha256(密文)` 口径**未动**;D7 量的是"接线前后一致"—— 扰动实验是**临时**的、**已逐字还原**)。
- ⛔ **未改 `nft`(72 行逐字一致)/ `nginx` / `bwrap` 参数**(47 = bubblewrap 0.4.0 未被触碰)。
- 🔴 **`RELAY_FAILOVER_COOLDOWN_MS=0` 命中数 = 0**(两机 env + systemd + scripts 全扫)。
- 🔴 **密钥本体不经网络 / relay**:本棒**不下发、不传输、不打印**任何密钥;`/etc/dshs/content-group-key.json` 两机仍 **`600`**。
- ⛔ **零个新增 / 改动生产 env**(`DSHS_OVERLAY_DIRECT` 两机命中 **0** ⇒ 走**缺省即开**);⛔ **未新增任何监听口**。
- **收口形态**:47 `dshs` / `dshs-relay` / `dshs-worker` / `dshs-pg` 全 `active`;relay `/status`=200、门户=200、`/dshs-overlay/bootstrap`=200;106 `dshs-worker` / `dshs-relay` 全 `active`、relay `/status`=200。端点表 **2 条全 `online: True`**(`w-106:19000` localPort 40337 / `w-106:21001` localPort 42099);`OBS-09` 派生 `w-106:42099=401`。
**⑦ 如实留档(必填)**
1. 🔴 **D8 仍未放行(红 · 只读核对,⛔ 未实施)**:双向并发 UDP 实测 —— **47 侧** `reason=deadline recvLocal=0 sent=29 elapsed=4001ms sources=[]`;**106 侧** `{"side":"106","target":"47.77.182.89:21100","reason":"deadline","recvLocal":0,"sent":28,"elapsedMs":4000,"sources":[]}`(**发 28/29、收 0**)。**承载线未动**:两机 `nft` **72 行**逐字一致 ⇒ 挡在**云侧**。⛔ **未改任何云侧规则**、⛔ **无任何 `0.0.0.0/0` 形态**(**未新增任何规则**)、⛔ **未索取、未硬编码任何云 AK/SK**(乙-3 = 回头条件,不在本棒范围)。拟录 **乙-1** 两条(源为对端 `/32`)**原样维持**,待你拍板。
2. ⚠️ **`peer` 档在 SG 未开时的正确形态 = 回落 wss 且块 id 不变** —— 本棒真机即此形态(`wired={direct:true, wss:false}`、`attempts` 双零 = 空闲;一旦有 peer 取块则 direct 侧以 **`data-plane-pending`** 具名降级、**不静默回"没有"**)。⛔ **未把"直连建立了"写成成功**(真机双向直连**不可达**;D5 的成功路径仍由**离线 NAT 模拟器**承载)。
3. ⚠️ **S6 的"数据面"(真实直连取块)本棒刻意未建成**:`createDirectBlockChannel#available()` 在数据面建成前**具名返回 `data-plane-pending`**、`fetch()` **抛错**(⛔ 不许静默回 `undefined`)—— **刻意保留**的具名降级,⛔ 不是缺陷;真机直连取块须待 **D8 放行**。
4. ⚠️ **106 侧观测面缺口(只读核对的绕行)**:106 `/opt/dshs-cluster/lib` **无 `registry.js`** ⇒ `overlay-direct-probe.cjs` 在 106 上**跑不起来**(原文 `Error: Cannot find module '../lib/net/relay/registry.js'`)⇒ D8 的 106 侧核对改用独立最小脚本 `d8_punch_106.cjs`(**只 `require` `punch.js`**)。本棒 ⛔ **未改探针逻辑**。⚠️ **残留**:探针文件已被 scp 到 106 `/opt/dshs-cluster/scripts/`,在**该位置不可跑**(修它 = 让探针不依赖 `registry.js`,属**独立后续项**)。
5. ⚠️ **106 `bwrap` = 0.11.0**(47 = **0.4.0**,与红线口径一致 ✅)—— 既存环境差异,**本棒未触碰 bwrap**。
6. ⚠️ **本棒三处"过程测错并当场修正"(⛔ 未编数)**:① `e1.mjs` 头部注释里的 `**…**` 被 Node 当代码解析(`rc=1`)⇒ 去掉反引号嵌套后跑通;② `originBytes` 被误当"回源次数"(`amp=3503483.3333` 天文数字)**且对照腿复用了已填充的 store(= 假对照)** ⇒ 拆成 `originFetches` + **4 台全新冷 store** 才成立;③ `peer-wire` 判据 ⑪ 首跑失败(`direct:no-address` ≠ `data-plane-pending`)⇒ 根因 = 直连候选入册键用**载荷 `hostId`**、而 `peer.ts#candidates()` 给的是**逻辑名**(`network/hostId`)⇒ 键改为 `` `${msg.network}/${msg.hostId}` `` 后 **13/13**。
7. ⚠️ **`overlay-probe.cjs` 真机首跑见 `used=None max=None`** —— 键名与该探针 `OBS-01` 的实际口径不同,**⛔ 非本棒引入**(探针仍 **28P/0S/0F**)。
8. ⚠️ **本棒只做 S6**:⛔ **未做 S7**(规模回归:候选数 / 直连成功率 / 切换耗时 / 回源字节四项复测,对照参数表 §3.2 / §7)—— 独立后续棒。
9. ⚠️ **`source.ts` 的改动是注释、⛔ 不是逻辑**(把 §8.13 登记的"peer 档接线时必须复算 `blockIdOf`"缺口**兑现点写清**:闸门落在装配层 `runtime.ts#fetchFromPeers`,**刻意不放进链** —— 放进去会让"五档通用链"认识块格式、属分层退化)⇒ 单测 44/44 佐证零逻辑改动。
**⑧ 指纹**
- 本单 **§8 前前缀**(口径:`sed '/^## §8 回报格式/,$d' <本单> | md5sum`):**`8925c2460810c6dc5cdbc075b6cdaed2`**(**写入本小节后现算,与 §11.2 / §11.5 逐字一致** ✅ —— §8 是**截断点**,故新增 §8.8 不改变它;= 该口径的又一次自证)。
- 参数表 **§10 指纹**:**`6b37bfd506758d882d9f803678f85d23`**(**现算**,与 §11.8 记的值一致 —— 本棒新增 §11.9 **在 §10 口径之外** ⇒ 指纹**不变**,**未动任何键值**)。
---
### 8.9 序㊷ 执行棒回报(**P2 · S7:规模回归 —— 候选数 / 直连成功率 / 切换耗时 / 回源字节 四项复测**;2026-09-18 14:14–14:5x)
> ⛔ 本小节**只新增**:**未改 §1–§7 正文**、**未改 §8.1–§8.8 小节点名**。**§8 前前缀 `8925c2460810c6dc5cdbc075b6cdaed2` 写入本小节后现算仍逐字不变** ✅(见 §8.9-⑦)。
**① 开工门**:`state.py` 已读;入口 `接续入口_覆盖网络线_20260916.md` **§0 最新两行 + §2 首行**已读(= 唯一执行依据);全局执行锁 `--claim-exec "序㊷ 执行棒(P2·S7)"` **抢到**(09-18 14:14)。**§8 前前缀开工前现算 = `8925c2460810c6dc5cdbc075b6cdaed2`** ✅ **逐字一致**(与 §11.2 / §11.5 / §8.8-⑧ 记的值相同)。**范围 = 只做 §6 的 S7**(四项复测;⛔ 未重做 S5 / S6、⛔ 未扩到新功能)。
**② 四项复测逐条(原始读数 + 与表内基线逐项对照;⛔ 无编数)**
**D-S7-① 候选数**
| 面 | 本次原始读数 | 表内基线 / 阈值 | 判定 |
|---|---|---|---|
| `dshs@47` 候选链 | `count=3 hosts=3 ✓ source=chain` → 收口后 `source=cache`;`urls=wss://alotbuy.com/dshs-relay|wss://relay-direct.alotbuy.com/dshs-relay|wss://106.54.21.172/dshs-relay` | `CAND_MIN=2`(参数表 §6 `OBS-21`) | ✅ **≥ 2** |
| `dshs-worker@106` 候选链 | `count=3 hosts=3 ✓ source=chain`;观测行原文 `resolves=744 count=3 hosts=3 source=chain` | 同上;序㉙ 基线 `count=3 hosts=3` | ✅ **逐字同基线** |
| 直连候选(`DIRECT_CANDIDATE`)在册 | `peerWire.directCandidates=0`(两机,空闲零流量) | ⛔ 无阈值(空闲态) | ℹ️ 信息项 |
| 直连候选准入(47 真机自检) | `accepted=3 / rejected=9(每条具名)/ silentRejections=0 / bucketsPerNetwork=true / matcher=network.ts#isAllowedDialer` | `DIRECT_CAND_ACCEPT_MIN=2` / `DIRECT_CAND_REJECT_MIN=3` | ✅ |
| **规模夹具**(`CandidateLedger#judge`,2 张网 × {4,16,64} 节点) | `accepted = 10 / 34 / 130`(= 期望值,**线性**)|`rejected = 32 / 128 / 512`(8 类原因**各 perNet 条,全部具名**)|`silentRejections = 0`|`bucketsPerNetwork = true`(同名 `hostId` 跨网互不可见,跨网窥探 ⇒ `cross-network`) | 不变量守卫 | ✅ **四项判据全绿** |
🔴 **口径(⛔ 别读成"冗余够")**:`hosts=3` **不是独立物理路径数** —— 前两条候选 `alotbuy.com` 与 `relay-direct.alotbuy.com` **摘名不同、同落 47** ⇒ **机器级**独立路径只有 **2**(47 + 106),与 §6 `OBS-21` 注记一致。**"条数够"✅ / "冗余建成"= 2 台机器**,两者**分开报**。
**D-S7-② 直连成功率**(🔴 必须区分"**本系统**的成功率"与"网络能不能打洞")
| 腿 | 本次原始读数 | 基线 / 阈值 | 判定 |
|---|---|---|---|
| **本系统(离线 NAT 模拟器,同一份 `runPunchAttempt`)** | 正腿 **24/24 双向 = 1.0000**(`reasons={a:ok:24, b:ok:24}`);**p50 建连 155 ms**(deadline 1200 ms);负腿 `one-way(a)=12` / `one-way(b)=12` 全 **0**;负腿 `both=12` 全 **deadline** | `DIRECT_PUNCH_MIN_OK=1`(`OBS-28`①) | ✅ **判据成立(1.0000,n=24)** |
| **真机(47 → 106.54.21.172:21100)** | `reason=deadline 收包=0 发出=21 耗时=3000ms` | `HOLE_PUNCH_RATE_LOCAL` = **分层**(⚠️ 那是**可行性**口径、⛔ 非本系统成功率) | 🔴 **不可达(0/1 方向)** |
| **真机(106 → 47.77.182.89:21100)** | `{"side":"106","reason":"deadline","recvLocal":0,"sent":28,"elapsedMs":4001,"sources":[]}` | 同上 | 🔴 **不可达(0/1 方向)** |
| 47 真机自检的打洞段 | `success.bidirectional=true`(模拟器腿)|`oneWay = one-way / one-way`|`dead[0] = deadline 601ms bounded=true`|`cooldown=300000 blocked=1 zeroRejected=throws` | `OBS-28`②③④ | ✅ |
🔴 **两条必须同时成立才算"诚实"**:**(甲) 判据成立** = 模拟器 1.0000 + `OBS-26/27/28` 三行 PASS(**机器可断言**);**(乙) 冗余建成** = **未达成** —— 真机双向直连**两个方向都是 `recvLocal=0`**(`D8` 云安全组未放行,见 §8.9-⑥-1)。⇒ ⛔ **本棒不把"直连建立了"写成成功**;真机形态 = **回落 wss 且块 id 不变**(D7,§8.8)。
**D-S7-③ 切换耗时**
| 项 | 本次原始读数 | 阈值 / 基线 | 判定 |
|---|---|---|---|
| `幕4-C` 切换耗时 | **20871 ms / 30000 ms**(`PASS 幕4-C`) | `RELAY_FAILOVER_DEADLINE_MS = 30000`(⚠️ 权威落点 = 参数表 **§4**,**不是 §3.2**) | ✅ **内侧** |
| `幕1-A`(杀当前入口 ⇒ 切到另一台) | **20605 ms / 30000 ms** | 同上 | ✅ |
| `幕4-A`(停 47 ⇒ 切到 106) | **18815 ms / 30000 ms** | 同上 | ✅ |
| 四段分解基线(序⑨,n=5+n=5) | 本棒三读数 **18.8–20.9 s** ∈ 基线区间 **20.9–23.9 s**(修后)**同量级**、三处**均在 deadline 内侧** | 参数表 §9 第 9 行 | ✅ |
| 派生健康读数 | 收口后 `jitter.p95AbsDeltaMs = 6 ms / overThreshold=false / alerts=3`(对照 `JITTER_LINK_MEASURED=3`);⚠️ **演练窗口内**曾读到 `p95=920 ms / overThreshold=true`(同源 = **心跳 RTT,含应用层与验签** ⇒ ⛔ 按 §3.5 口径**不得当链路 RTT 用**) | `JITTER_LIMIT_MS=20` | ✅ 收口态达标 |
**D-S7-④ 回源字节**(夹具 = **同一份** 5,255,225 B / 6 块;口径 = 逐次计 `origin` fetcher 的字节)
| 规模点 | `originFetches` | `originBytes` | 放大比 | **每组合计** | 对照腿(peer 关) | 节省比例 |
|---|---|---|---|---|---|---|
| 1 组 × 4 节点(N=4) | 6 | **5,255,225** | **1.0000** | **1.0000** | 4.0000 | 75.00% |
| 1 组 × 16 节点(N=16) | 6 | **5,255,225** | **1.0000** | **1.0000** | 16.0000 | **93.75%** |
| 4 组 × 4 节点(N=16) | 24 | **21,020,900** | **4.0000** | **1.0000** | 16.0000 | 75.00% |
| 8 组 × 4 节点(N=32) | 48 | **42,041,800** | **8.0000** | **1.0000** | 32.0000 | 75.00% |
- 🔴 **主判据(规模不变量)**:**放大比恒 ≡ 组数、每组合计恒 `1.0000`** ⇒ **回源字节 ∝ 组数,⛔ 不随组内节点数增长**(`perGroupAmplificationAlwaysOne=true` / `amplificationEqualsGroupCount=true` / `amplificationIndependentOfGroupSize=true` / `controlEqualsNodeCount=true`)。
- ✅ **与既有读数逐项对齐**:序㉔ `1.0000` ⇄ 对照 `4.0000`(本棒 N=4 点**逐字复现**);序㊛ `4 组 × 4 节点 ⇒ 21,021,572 B = 4.0001 × 包`(本棒 4 组点 = **21,020,900 B / 4.0000**,差 = 序⑳ 口径的尾块取整,**同量级同结论**)。
- **运行时接线腿**(真 `ContentRuntime`,8 组 × 4 节点):`wss-回落腿` `served=144/144`、`hits.wss=144`、`downReasons={direct:no-address:144}`、`idChecks=144 / idMismatches=0`;`direct-优先腿` `served=144/144`、**`hits.direct=144 / hits.wss=0`**、`idChecks=144 / idMismatches=0` ⇒ **优先直连且 ⛔ 不碰 wss**,且**每次命中都过 D7 复算闸门**。
- 📌 **`§10-2`(直连带来的中继带宽节省比例)**:内容面 = `1 − 组数/节点数` ⇒ **组内 4 人 75.00% / 组内 16 人 93.75%**(⚠️ 这是**内容分发面**的节省;**真机直连**的节省仍 = 0,因 D8 未放行)。
**③ 零回归三件套**
| 项 | 本棒读数 | 基线 | 判定 |
|---|---|---|---|
| `npm.cmd test`(Node 22) | **201 tests / 200 pass / 0 fail / 1 skipped**(rc=0;`duration_ms=53333`;注入脚本自检「全部合格 ✅」) | 201/200/0/1 | ✅ **逐字一致** |
| `node scripts/overlay-failover-drill.cjs --scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**(rc=0;**6m20s**;**未套任何外层超时**) | 12P/0S/0F | ✅ |
| `node scripts/overlay-probe.cjs --table`(真机) | **28 PASS / 0 SKIP / 0 FAIL**(rc=0;**开工前 + 收口后两次同值**) | 28P/0S/0F(⚠️ **非旧的 25P**) | ✅ |
🔴 **上上棒的坑本棒已避开**:`--scene all` **未套任何外层超时**(跑满 **6m20s**)⇒ 末尾 `restore()` 正常执行 ⇒ **两台 `dshs-relay` 均 `active`**(⛔ 未出现 106 relay 被留在停机态)。
**④ 恢复形态核实(47 / 106 两端单元与服务状态 + 门户码)**
- **47**:`dshs=active` / `dshs-worker=active` / `dshs-relay=active`;relay `/status` = **200**;门户(`Host: alotbuy.com`)= **200**;`https://alotbuy.com/dshs-overlay/bootstrap` = **200**
- **106**:`dshs-worker=active` / `dshs-relay=active`;relay `/status` = **200**
- **47 视角中继读数**:`used=2 / max=7515 / free=7513 / utilPct=0`;`endpoints=[["w-106",19000,online],["w-106",21001,online]]`;`sessions=[manager(rtt 8ms), w-106(rtt 373ms)]`;`counters={dial:0, dialDenied:0, dialFailed:0, refused:0, utilRefused:0, authFailed:0}`
- **探针四条口径行**:`PASS OBS-01 used=2` | `PASS OBS-04 identityOk=2` | `PASS OBS-08 端点表 2 条 / 离线 0 条` | `PASS OBS-09 在册实例面(派生 1 条):w-106:37653=401`(⚠️ 端口由 42099 → **37653** 再漂一次 = **设计内**,判据跟着事实走、⛔ 未改表值)
- ⚠️ **演练后的复原动作**:`--scene all` 收口时 47 视角 = `used=1 / endpoints=[]`(106 worker 挂到 106 自己的中继)⇒ 按既有程序处理,见 §8.9-⑥-3(**两次尝试,第二次成功**);复原后探针 **28P/0S/0F**。
**⑤ 边界自证(逐条打勾)**
- ⛔ **未 commit / 未 push**:HEAD 仍 **`09ce76f`**;`git status --short` = **19** 处(= 序㊶ 收口的 19 ⇒ **零新增条目**;全部为 P1/P2 在册工作树文件)
- ⛔ **未改 `src/net/relay/server.ts` 的 DIAL / 白名单语义**:本棒**零字节**改动(`git status` 里 `server.ts` 条目为序⑳-㊶ 既有改动,非本棒)
- ⛔ **未改 `src/net/relay/content/**` 的块 id 计算路径**(`sha256(密文)` 口径未动;⛔ 本棒**未做任何扰动实验**,D7 用的是序㊶ 已固化的闸门)
- ⛔ **未改 `nft` / `nginx` / `bwrap`**:47 `nft list ruleset` = **72** 行(逐字一致);bubblewrap **47 = 0.4.0 / 106 = 0.11.0**(未触碰)
- 🔴 **`RELAY_FAILOVER_COOLDOWN_MS=0` 命中数 = 0**(两机 `/etc/**` + systemd + `scripts/` 全扫)
- 🔴 **密钥本体不经网络 / relay**:本棒**不下发、不传输、不打印**任何密钥;`/etc/dshs/content-group-key.json` 两机仍 **`600`**
- ⛔ **零个新增 / 改动生产 env**:`DSHS_OVERLAY_DIRECT` 两机命中 **0** ⇒ 走**缺省即开**;⛔ **未新增任何监听口**(`OBS-11 多出 0 缺失 0`;`ss -lunp` 在 `21100–21115` 段收口后 **0 行**)
- ⛔ **本棒未改任何仓内文件**(⛔ `scripts/**`、⛔ `src/**`、⛔ `test/**` 全零改动)—— 三项夹具全部落在**工作区** `_tmp_seq42/`(⛔ 仓外)
- **本棒对生产的动作清单(均 R8,动手前已一句话说明,⛔ 无不可逆项)**:① `--scene all` 演练一次(自停 / 自启 relay;**幕 4 含一次 Manager 重启** = 演练设计内)② 47 上重跑 `overlay-direct-probe.cjs selfcheck --json` **两次**(⛔ 无 `--out` ⇒ ⛔ **未写** `/var/lib/dshs/overlay/direct-selfcheck.json`,该文件仍是 13:13:20 的序㊵ 版本)+ 一次 `--ss`(正对照**真绑一个 UDP 口**后立即释放)③ 47 上跑 `punch` 腿(只读 UDP,3 s)④ 106 上跑 `/tmp/d8_punch_106.cjs`(本棒 scp 的最小脚本,**只 `require` `punch.js`**)⑤ 停 / 启 106 `dshs-relay` **两次**(见 ⑥-3)⇒ ⛔ 全程**未重启任何 worker**、⛔ **未换实例**、⛔ **未删任何数据 / 配置**
**⑥ 如实留档(必填)**
1. 🔴 **D8 仍未放行(红 · 只读核对,⛔ 未实施)**:真机双向并发 UDP 实测 —— **47 侧 `reason=deadline recvLocal=0 sent=21 elapsed=3000ms sources=[]`**;**106 侧 `{"side":"106","reason":"deadline","recvLocal":0,"sent":28,"elapsedMs":4001,"sources":[]}`**(**发 21/28、收 0**)。**承载线未动**:47 `nft` **72 行**逐字一致(106 无 nft ruleset = 既存形态)⇒ 挡在**云侧**。⛔ **未改任何云侧规则**、⛔ **无任何 `0.0.0.0/0` 形态**(**本棒未新增任何规则**)、⛔ **未索取、未硬编码任何云 AK/SK**(乙-3 = 回头条件)。拟录 **乙-1** 两条(源为对端 `/32`)**原样维持**,待拍板。
2. ⚠️ **`overlay-probe.cjs` / `overlay-failover-drill.cjs` 必须从「工作区根」跑**(它们按 `/^参数表_覆盖网络_.+\.md$/` 在 **cwd** 找参数表)—— 从代码仓根跑 ⇒ `❌ 参数表装载失败 … 找到 0 个参数表`(rc=2)。本棒首次两个命令都踩了,**已改用工作区根 + 脚本绝对路径**(⛔ 该失败**不发生任何生产动作**,仅 rc=2 退出)。
3. ⚠️ **复原程序要"停够久"才生效(本棒实测修正)**:`--scene all` 后 47 视角 `used=1 / endpoints=[]`。按既有程序"停 / 启 106 `dshs-relay`"——**第一次「停 2 s 即启」未触发回落**(worker 在 `RELAY_GRACEFUL_BURST_MS=15000` 的快速重试窗内**重连回同一条** url;该窗内 `attempts` 恒 0 ⇒ 切流条件不成立)⇒ **第二次「停 26 s 后再启」成功**(停稳期间 47 侧原候选可用 ⇒ worker 切回 47,`used=2`、端点 2 条全在线)⇒ **中间 26 s 内 106 的跨机流量走 47,属设计内回落**。⛔ 全程**未重启 worker**、⛔ **未换实例**。
4. ⚠️ **一处过程测错并当场修正(⛔ 未编数)**:首次 `selfcheck --ss` **与 `punch` 腿并发**运行 ⇒ punch 腿先绑住 `21100` ⇒ 正对照 `ctrl.open(21100)` **EADDRINUSE** ⇒ `udp.ssDuringOnCase` 读成 **`null`**、`ssBefore` 读成 **1**(**看起来像"UDP 口被占"的假红**)。**单独重跑**后 = `ssBefore 0 / ssDuringOffCase 0 / ssDuringOnCase 1`(与序㊵ 读数**逐字同值**)⇒ **测量错,⛔ 非产品缺陷**。
5. ⚠️ **§6 与 prompt 的"切换耗时阈值"落点不一致(只登记,⛔ 未改正文)**:本棒 prompt 与验收写「对照参数表 **§3.2**(切换耗时阈值)」,而 **§3.2 是"打洞率"**(`HOLE_PUNCH_RATE_*`);切换耗时的**权威阈值**是 **§4** 的 `RELAY_FAILOVER_DEADLINE_MS = 30000 ms`,四段分解在 **§9 第 9 行**。本棒按**权威落点**对照(§8.9-②D-S7-③),⛔ 未为迁就指针改 §3.2 / §4 任一格。
6. ⚠️ **`--ss` 是布尔开关**(按"出现过"判定)⇒ 不给它就没有任何 `udp.*` 读数(`ssAvailable=false`)。**真机腿的取证顺序 = 单跑、⛔ 不与其它绑同一端口段的腿并发**(见 ⑥-4)。
7. ⚠️ **106 侧观测面缺口仍在**(承接 §8.8-④):106 `/opt/dshs-cluster/lib` **无 `registry.js`** ⇒ `overlay-direct-probe.cjs` 在 106 上**跑不起来** ⇒ 106 侧真机打洞腿改用最小脚本 `d8_punch_106.cjs`(**只 `require` `punch.js`**,本棒 scp 到 `/tmp/`,⛔ 未落在任何 lib / units 路径)。
8. ⚠️ **`§10` 未验证项的本棒增量(⛔ 不冒充"已判定")**:**§10-1「本系统打洞成功率」** ⇒ 本棒给出两条**并列**读数:**模拟器 1.0000(n=24,双向)/真机 0.0000(n=2 方向)** ⇒ 该项**仍不可判定"公网场景成功率"**(真机腿被 D8 挡着);**§10-3 切换耗时** ⇒ 本棒 **18.8–20.9 s**(n=3,均在 30000 ms 内侧);**§10-2 中继带宽节省** ⇒ 内容面 **75.00% / 93.75%**(模拟器),**真机 = 0**(D8)。
9. ⚠️ **本棒只做 S7**:⛔ 未重做 S5(`OBS-26/27/28` 判据面本棒只**读取**)/S6(`peer` 接线零改动,`peerWire` 只**读取**);⛔ 未开工任何新功能。
10. ⚠️ **三条夹具脚本落在工作区 `_tmp_seq42/`**(`s7_candidate_scale.mjs` / `s7_punch_scale.mjs` / `s7_origin_scale.mjs` + 读数 `*.json` + `drill.txt` / `probe_*.txt`)⇒ **⛔ 属临时产物**,按任务收尾纪律**保留至用户确认后再清理**(⛔ 本棒未删)。
**⑦ 指纹**
- 本单 **§8 前前缀**(口径:`sed '/^## §8 回报格式/,$d' <本单> | md5sum`):**`8925c2460810c6dc5cdbc075b6cdaed2`**(**写入本小节后现算,与 §11.2 / §11.5 / §8.8-⑧ 逐字一致** ✅ —— §8 是**截断点**,故新增 §8.9 不改变它;= 该口径的又一次自证)。
- 参数表 **§10 指纹**:**`6b37bfd506758d882d9f803678f85d23`**(**现算**,与 §11.7–§11.9 记的值逐字一致 —— 本棒新增 §11.10 **在 §10 口径之外** ⇒ 指纹**不变**,**未动任何键值**)。
---
### 8.10 序㊸ 执行棒回报(**观测面缺口收口**:① 探针在 106 上可跑 ② 参数表定位与 cwd 解耦 ③ 106 不可跑残留副本处置;2026-09-18 14:47–15:2x)
> ⛔ 本小节**只新增**:**未改 §1–§7 正文**、**未改 §8.1–§8.9 小节点名**。**§8 前前缀 `8925c2460810c6dc5cdbc075b6cdaed2` 写入本小节后现算仍逐字不变** ✅(见 §8.10-⑦)。
**① 开工门**:`state.py` 已读;入口 `接续入口_覆盖网络线_20260916.md` **§0 最新两行 + §2 首行**已读(= 唯一执行依据);全局执行锁 `--claim-exec "序㊸ 执行棒(观测面缺口收口)"` **抢到**(09-18 14:47)。**§8 前前缀开工前现算 = `8925c2460810c6dc5cdbc075b6cdaed2`** ✅ **逐字一致**(与 §11.2 / §11.5 / §8.8-⑧ / §8.9-⑦ 记的值相同)。**范围 = 只做观测面三件事**(⛔ 未碰数据面、⛔ 未做 D8、⛔ 未重做 S5/S6/S7)。
**② 三项逐条(原始读数;⛔ 无编数)**
**Ⅰ 探针在 106 上可跑(先红后绿)**
| 腿 | 原文读数 | 判定 |
|---|---|---|
| **先红**(106 上**改前**的副本 `/opt/dshs-cluster/scripts/overlay-direct-probe.cjs`,md5 `8d1ea60a01fc09fb5f56e492afb76fc9`) | `status` 与 `selfcheck --json` **两条命令都**:`Error: Cannot find module '../lib/net/relay/registry.js'` + `Require stack: - /opt/dshs-cluster/scripts/overlay-direct-probe.cjs` | 🔴 **跑不起来**(106 `/opt/dshs-cluster/lib` **无 `registry.js`**,实测 `ls` = `No such file or directory`) |
| **后绿**(同一**命令**、同一**规范路径**) | ⓵ `status` ⇒ rc=0 原文 `直连(打洞):开|来源 default|缺省 true` + `本机配置 /etc/dshs/overlay-node.json:不存在` ⓶ `selfcheck --json` ⇒ rc=0,机器可读读数 `{"degraded":true,"degradedLegs":["joinConf"],"joinConfAvailable":false,"joinConfReason":"module-missing","candAcc":3,"candRej":9,"silent":0,"bi":true,"ok":2,"cooldown":300000,"zero":"throws","deps":true}` ⓷ **`punch` 真机腿** `--peer 47.77.182.89:21100 --json` ⇒ rc=0 原文 `窗内零收包(deadline 3000 ms,实耗 3000 ms,发 21 包)⇒ 判死 + 进冷却 300000 ms` | ✅ **跑通并给出该子命令读数** |
- **处置方式 = 惰性 / 可选加载 + 具名降级**(⛔ **未**"再铺一份 `registry.js`"):`overlay-direct-probe.cjs` 顶层原先 `require` 了 `registry.js`(**控制面注册表**模块)与同样 import 它的 `join.js` ⇒ 106 上**任何子命令**都死在第一行。现改为:**只有 `join 回读`(判据 D3)这条腿**惰性加载这两个模块,加载不到 ⇒ 返回 `{ available:false, reason:'module-missing', missing:[{spec, code, message}] }`。
- 🔴 **具名降级(文本原文)**:`join 回读:⛔ 不可用(module-missing)—— 缺 ../lib/net/relay/registry.js , ../lib/net/relay/join.js|⚠️ 这是「没装」不是「没采到」|原文 message:Cannot find module '../lib/net/relay/registry.js' …` ⇒ **⛔ 未静默返"没有"**;读数里另给机器可读标记 `degraded:true` / `degradedLegs:["joinConf"]`,且缺块**不填** `direct`/`readback`("没装"与"跑了但读数为空"**形状完全不同** = 可分)。
- ⚠️ **只吞 `MODULE_NOT_FOUND` / `ERR_MODULE_NOT_FOUND`**(且**原文 message 原样带出**);其它异常(语法错 / 依赖内部崩)**一律照抛** —— 否则会把真故障伪装成"缺模块"。
- **本地等效夹具(零机器风险)**:把本机 `lib/net/relay/{registry,join}.js` **临时改名**后跑 `selfcheck` ⇒ **同一降级形状**、rc=0、其余腿全在(候选 3 / 具名拒绝 9 / 静默 0 / 打洞双向 ✅ / 冷却 300000 `throws` / 依赖面 ✅);**还原后 md5 逐字回位**(`951a2526789b897aa58e3abbb5dea332` / `6946e1d802494ccb7f522ef7b937614a`)。
**Ⅱ 参数表定位与 cwd 解耦(两腿对照 + 防错腿)**
| 腿 | 命令(cwd) | 原文读数 | 判定 |
|---|---|---|---|
| **先红**(**旧逻辑**副本,与 `git show HEAD:scripts/overlay-probe.cjs` 的 `resolveTablePath` **逐字一致**) | `cd D:\github\dsh_shenxian && node <旧逻辑副本>` | rc=**2** `❌ 参数表装载失败:在 D:\github\dsh_shenxian 下按 /^参数表_覆盖网络_.+\.md$/ 找到 0 个参数表(要求恰好 1 个)` | 🔴 **复现 §8.9-⑥-2 的原始失败** |
| **后绿 · 探针** | `cd D:\github\dsh_shenxian && node scripts/overlay-probe.cjs`(**不给 `--table`**) | rc=**0**,**28 PASS / 0 SKIP / 0 FAIL** | ✅ |
| **后绿 · 演练** | `cd D:\github\dsh_shenxian && node scripts/overlay-failover-drill.cjs --scene all`(**不给 `--table`**) | rc=**0**;第 1 行 `# 参数表=E:\ProgramData\AI技能\aliyun-dsh-server\参数表_覆盖网络_20260917.md`;末行 `# 结果:12 PASS / 0 SKIP / 0 FAIL` | ✅ **从代码仓根跑通并定位到工作区那份表** |
| **防错腿 A**(**同目录**人为造 2 份) | 探针 + 演练 各一次,`--dir <2 份的目录>` | 两者**均** rc=2:`在 E:\…\_tmp_seq43\two_tables 下按 /^参数表_覆盖网络_.+\.md$/ 找到 2 个参数表(要求恰好 1 个):参数表_覆盖网络_20260917.md , 参数表_覆盖网络_A.md` | ✅ **报错退出** |
| **防错腿 B**(**跨候选目录**合计 2 份) | 探针 + 演练 各一次;cwd = 含 1 份的目录 + `DSHS_OVERLAY_TABLE_REGISTRY` 指向另一含 1 份的目录 | 两者**均** rc=2:`跨候选目录共找到 2 个参数表(要求恰好 1 个,⛔ 不猜哪一份是对的):` + 逐份列出 | ✅ **报错退出** |
- **候选目录链(写死)**:`--table`(显式文件,**最高优先、直接返回**)> `--dir` > `DSHS_OVERLAY_TABLE_DIR` > **机器本地注册文件**(`DSHS_OVERLAY_TABLE_REGISTRY`,缺省 **`~/.dshs/overlay-table-dir`**,一行一个目录、`#` 注释)> `cwd` > 脚本自身目录及其上两级。
- 🔴 **"恰好 1 个才算合法"保留、且更严**:任一候选目录 ≥ 2 份 ⇒ **立即**报错;**跨候选目录合计 ≥ 2 份 ⇒ 也报错**(列出全部命中)。⛔ **一份都没找到 ⇒ 报错**并列出**所有搜过的目录** + 注册方法(**⛔ 未放宽成"找不到就用缺省"**)。
- 🔑 **为什么必须有"注册文件"这一环**(实测事实):参数表**不在仓内**(`git ls-files | grep -c 参数表` = **0**),它在**工作区根**,而本机仓在 `D:`、工作区在 `E:` —— **不同盘** ⇒ 纯路径推算**永远到不了**。故一律把机器差异放在 `$HOME`(**⛔ 仓内零机器路径**)。
**Ⅲ 106 残留副本处置(二选一 ⇒ 选"使其可跑")**
- **理由**:106 正是**真机打洞腿**的取证机;删掉它只会逼出更多 `/tmp/*.cjs` 临时脚本(那正是 §8.8-④ / §8.9-⑥-7 的脆点)。⇒ **不删,改为可跑**(⛔ **未动** `dshs-*` 单元、⛔ **未动** `lib`)。
- **证据**:修复版 scp 到**三处** 且 **md5 全同 = `4734686b5270059697ccbcf966c34ade`** —— 本机仓 / 47 `/opt/dshs/scripts/overlay-direct-probe.cjs` / 106 `/opt/dshs-cluster/scripts/overlay-direct-probe.cjs`;106 上**在规范路径**(非 `/tmp`)跑通 `status` / `selfcheck` / `punch` 三条子命令(见 Ⅰ)。
- **回滚点** = 两机 **`/opt/dsh/backups/seq43-20260918-150549/overlay-direct-probe.cjs`**(各 1 个文件,md5 `8d1ea60a01fc09fb5f56e492afb76fc9` = 改前值);`chmod 755` 保持可执行位。
**③ 零回归三件套**
| 项 | 本棒读数 | 基线 | 判定 |
|---|---|---|---|
| `npm.cmd test`(Node **22.22.2**) | **201 tests / 200 pass / 0 fail / 1 skipped**(rc=0;`duration_ms=53315.1989`;「结论:全部合格 ✅」) | 201/200/0/1 | ✅ **逐字一致** |
| `node scripts/overlay-failover-drill.cjs --scene all`(**无 `--table`,从代码仓根**) | **12 PASS / 0 SKIP / 0 FAIL**(rc=0;**6m23s**;**未套任何外层超时**;`幕4-C` **17240 ms / 30000 ms**) | 12P/0S/0F | ✅ |
| `node scripts/overlay-probe.cjs --table`(真机) | **28 PASS / 0 SKIP / 0 FAIL**(rc=0)—— **开工后(代码仓根,不给 `--table`)+ 收口后(工作区根)两次同值** | 28P/0S/0F | ✅ |
**④ 恢复形态核实(演练后 + 复原程序)**
- **47**:`dshs=active` / `dshs-worker=active` / `dshs-relay=active`;relay `/status` = **200**;门户(`Host: alotbuy.com`)= **200**
- **106**:`dshs-worker=active` / `dshs-relay=active`
- **演练后 47 视角 = `endpoints=[]`(106 worker 挂到 106 自己的中继)⇒ 按既有程序复原**:`systemctl stop dshs-relay`(106)⇒ **停够 26 s** ⇒ `systemctl start` ⇒ 等 ~20 s ⇒ 47 视角 `sessions=["ops/manager","ops/w-106"]`、`endpoints=[["w-106",19000,true],["w-106",21001,true]]` **2 条全在线** ⇒ 探针 `OBS-01 used=2` ✅(⛔ 全程**未重启 worker**、⛔ **未换实例**)
- **探针四条口径行(收口态)**:`OBS-01 used=2` | `OBS-08 端点表 2 条 / 离线 0 条` | `OBS-09 在册实例面(派生 1 条):w-106:42477=401`(端口漂移 = 设计内)| `OBS-11 集合 … 多出 0 缺失 0 | nft accept 0 多出 0 | relay 口绑定回环=1/1 条`
**⑤ 边界自证(逐条打勾)**
- ⛔ **未 commit / 未 push**:HEAD 仍 **`09ce76f`**;`git status --short` = **19** 处(= 序㊷ 收口的 19 ⇒ **零新增条目**;本棒改动的 `scripts/overlay-probe.cjs` / `scripts/overlay-failover-drill.cjs` 本就已 `M`、`scripts/overlay-direct-probe.cjs` 本就连 `??`)
- ⛔ **未改 `src/net/relay/server.ts` 的 DIAL / 白名单语义**:本棒**零字节**改动(`git status` 里无 `server.ts` 新条目)
- ⛔ **未改 `src/net/relay/content/**` 的块 id 计算路径**(`sha256(密文)` 口径未动;⛔ 本棒未做任何扰动实验)
- ⛔ **未改 `nft` / `nginx` / `bwrap`**:47 `nft list ruleset` = **72** 行(逐字一致);bubblewrap **47 = 0.4.0 / 106 = 0.11.0**(未触碰);⛔ 未改任何 nginx 配置
- 🔴 **`RELAY_FAILOVER_COOLDOWN_MS=0` 命中数 = 0**(两机 `/etc/**` + `/opt/dshs*/scripts` + unit `Environment` 全扫,四处计数**全 0**)
- 🔴 **密钥本体不经网络 / relay**:本棒**不下发、不传输、不打印**任何密钥;`/etc/dshs/content-group-key.json` 两机仍 **`600`**
- ⛔ **零个新增 / 改动生产 env**:`DSHS_OVERLAY_DIRECT` 两机命中 **0**(走缺省即开);⛔ **零新增监听口**(收口后 `ss -lunp` 在 `21100–21115` 段 **0 行**)
- ⛔ **本棒只改 3 个脚本**(`scripts/overlay-direct-probe.cjs` + `scripts/overlay-probe.cjs` + `scripts/overlay-failover-drill.cjs`);⛔ 未改 `src/**` / `test/**`;⛔ 未 `npm run build`(本棒**无 TS 改动**)
- **本棒对生产的动作清单(均 R8,动手前已一句话说明,⛔ 无不可逆项)**:① 两机各 scp 一份探针(覆盖旧副本,先备份)② 106 上跑 `status` / `selfcheck` / **一次只读 `punch`**(UDP 探测 3 s)③ `--scene all` 演练一次(自停 / 自启 relay;**幕 4 含一次 Manager 重启** = 演练设计内)④ **停 / 启 106 `dshs-relay` 各一次**(复原程序,见 ④)⇒ ⛔ 全程**未重启任何 worker**、⛔ **未换实例**、⛔ **未删任何数据 / 配置**
**⑥ 如实留档(必填)**
1. 🔴 **D8 本棒零动作(在册待拍板)**:⛔ 未改任何云侧规则、⛔ **无任何 `0.0.0.0/0` 形态**(含拆分等价写法)、⛔ 未索取 / 未硬编码任何云 AK/SK。⚠️ **本棒唯一的"触碰网络"动作 = 106 上一次只读 `punch`**(`--peer 47.77.182.89:21100`,UDP 探测 3 s,**发 21 包 / 收 0**),其结论与 §8.9-⑥-1(`D8` 未放行)**同向**,⛔ **不构成任何 D8 实施**。
2. 🔑 **Ⅱ 的解法含一个"机器本地注册"环节(必须知道)**:候选目录链里的**注册文件** = `~/.dshs/overlay-table-dir`(本机已写入**一行** = 工作区根)。⇒ **换机器 / 换工作区路径时需补这一行**(或改用 `--table` / `--dir` / `DSHS_OVERLAY_TABLE_DIR` / `DSHS_OVERLAY_TABLE_REGISTRY`)。🔴 ⛔ **仓内零机器路径**(该文件**不入库**、也不进 `git status`)。
3. ⚠️ **范围新增(1 处,已声明)**:`scripts/overlay-probe.cjs` 的 **`OBS-26` 子判据**加了 3 行 —— 当读数生产方**显式**给出 `joinConf.available === false`(即 Ⅰ 的具名降级)时,**该子判据取 SKIP + 留痕**(⛔ 不判红;detail 里点名缺哪个模块)。**理由** = Ⅰ 改变了读数形状;不改这里 ⇒ 一份**合法的 106 读数**会让 `OBS-26` **假红**(本线明令"长期挂红项会降低判据灵敏度")。**零回归佐证** = 老形状(无 `available` 键,如 47 那份 `/var/lib/dshs/overlay/direct-selfcheck.json`)走**原路径**、判定与文本**逐字不变** ⇒ 三件套 **28P/0S/0F** 成立。⚠️ 该改动**可随时回退**(3 行)。
4. ⚠️ **参数表 §6 `OBS-26` 行本棒 ⛔ 未改**(**为保 §10 指纹不变** —— §6 在 §10 口径**之内**)⇒ `OBS-26` 的"具名降级 ⇒ SKIP + 留痕"这条语义**登记在参数表 §11.11**;下次有键值变更时顺带并入 §6 行文本。
5. ⚠️ **演练脚本的 `--trace` / `--sample` 仍按 `cwd` 落盘** `_中间产物_待清理/seq9-trace/`(⛔ 本棒**未改** —— 它属那两幕的取证落盘、⛔ 不在"参数表定位"这一条的范围;`--scene all` **不会**走到它们 ⇒ 从代码仓根跑**零副作用**,实测无多余文件产生)。
6. ⚠️ **106 上 `/tmp/d8_punch_106.cjs`(序㊷ 的临时最小脚本)仍在**(**信息项**,⛔ 本棒**未动**它)—— 本棒起,106 侧真机腿用**规范路径**的探针即可,该临时脚本**已无必要**。
7. ⚠️ **一处既有行为(⛔ 未改,只登记)**:`--table` **裸开关(不给值)会被判为"未给"**(`argOf` 要求带值)⇒ 走候选目录链。本棒头一次探针就是这么跑的(**意图如此**:证明"从代码仓根、不给 `--table`"也能跑)。
8. ⚠️ **本棒 ⛔ 未碰** `src/**` / `test/**` / `package.json`(⛔ 零新依赖:改动只用了 Node 内建 `child_process` / `fs` / `os` / `path`)。
9. ⚠️ **夹具与读数全部落在工作区 `_tmp_seq43/`**(`overlay-probe.old.cjs` = 旧逻辑副本、`mk_old_probe.py`、`two_tables/` / `one_table_a/` / `one_table_b/` / `reg_cross` 防错夹具、`red_106.txt` / `green_106.txt` / `probe_from_repohost.txt` / `probe_old_from_repohost.txt` / `probe_final.txt` / `drill_all.txt` / `npm_test.txt` / `dp_degraded.*`)⇒ **⛔ 属临时产物**,按任务收尾纪律**保留至用户确认后再清理**(⛔ 本棒未删)。
**⑦ 指纹**
- 本单 **§8 前前缀**(口径:`sed '/^## §8 回报格式/,$d' <本单> | md5sum`):**`8925c2460810c6dc5cdbc075b6cdaed2`**(**写入本小节后现算,与 §11.2 / §11.5 / §8.8-⑧ / §8.9-⑦ 逐字一致** ✅ —— §8 是**截断点**,故新增 §8.10 不改变它)。
- 参数表 **§10 指纹**:**`6b37bfd506758d882d9f803678f85d23`**(**现算**)—— 本棒新增 §11.11 **在 §10 口径之外** ⇒ 指纹**不变**,**未动任何键值 / 阈值**。
---
### 8.11 序㊹ 执行棒回报(**D8 云安全组乙-1**)—— ⏸ **未收官 · 阻塞上报**(2026-09-18 15:3x–16:0x)
> ⏸ **本棒未完成实施**:用户已于本轮**批准开通**(原话见 §8.11-⑥),但**执行侧无云 API 凭据** ⇒ 依 §1 判据(边界外④「需用户提供的账号凭据或审批」)**停下上报**。⛔ 本棒**零写入生产、零改代码、零改 nft / nginx / bwrap**。
**① 开工门(必填)**
| 项 | 值 |
|---|---|
| 全局执行锁 | ✓ `--claim-exec "序㊹ 执行棒(D8 云安全组乙-1)"`(09-18 15:37 起持锁) |
| HEAD | `09ce76f`(⛔ 未 commit / 未 push) |
| 本棒动作 | **只读取证**(ssh 只读命令;⛔ 未 scp、未 build、未重启任何单元) |
**② D8 执行侧前置取证(只读实测)**
| # | 项 | 实测值 |
|---|---|---|
| a | 47 固定公网 IP / 实例 | **`47.77.182.89`** / 阿里云 `i-rj99af19cibck1ge93tq` |
| b | 106 固定公网 IP / 实例 | **`106.54.21.172`** / 腾讯云 `ins-3q6k1p8t` |
| c | 网卡形态 | 47 `eth0 172.18.16.212/18`;106 `eth0 10.0.0.8/22` ⇒ 两侧公网 IP **均为云网关 NAT 映射**(⛔ 非直挂网卡)⇒ 入站需**云安全组**放行(本地防火墙不参与 DNAT 前段) |
| d | UDP 端口区间 | `[21100, 21116)`(`punch.ts#PUNCH_PORT_BASE=21100` + `PUNCH_PORT_SPAN=16`)⇒ 规则须覆盖 **21100–21115**。⚠️ 两机 `ss -lunp` 在该段**均无监听** —— **正常**:打洞 socket 只在**实打时**才开,⛔ 不是"没装"(「没装 vs 没采到」不可混) |
| e | 直连总开关 env | `DSHS_OVERLAY_DIRECT` 在两机 `/etc/dshs*.env` **命中 0** ⇒ 走**缺省 = 开**,⛔ 无需改任何 drop-in |
| f | 47 本地防火墙 | `nft` **72 行**;`hook input … policy accept` ⇒ **不拦入站 UDP**。⚠️ 第 68 行那条 `udp … reject` 是 **沙箱 uid `100000-199999` 的出站**限制(`daddr` 含 `47.77.182.89`),⛔ **与本腿无关**(打洞跑在服务 uid) |
| g | 106 本地防火墙 | `nft` **0 行**;`iptables -P INPUT ACCEPT` + `YJ-FIREWALL-INPUT`(5 行,仅 4 条 `/32` REJECT 黑名单,**不含 47**);`ipset test YJ-GLOBAL-INBLOCK 47.77.182.89` ⇒ **NOT in set** ⇒ **不拦入站 UDP** |
| h | 47 节点注册表 | `/var/lib/dshs/overlay/nodes.json` = `{"version":1,"nodes":{}}`(**空**) |
| i | 47 目录(relays) | `directory.json` ⇒ `alotbuy.com/dshs-relay` / `relay-direct.alotbuy.com/dshs-relay` / `106.54.21.172/dshs-relay`(3 条) |
**③ 阻塞判定:为什么本棒自己做不到(原文级证据)**
| 位置 | 证据 | 结论 |
|---|---|---|
| 本机(WorkBuddy 开发机) | **无** cloud CLI、**无** `~/.aliyun` / `~/.tccli` / `~/.aws` | 无法调用云 API |
| 47(阿里云) | `aliyun` CLI **存在**(`/usr/local/bin/aliyun`)但 **`~/.aliyun/config.json` 不存在**;`aliyun configure list` = `default * \| (空) \| Invalid \| (空) \| en` | **凭据未配置** |
| 47 metadata | `http://100.100.100.200/latest/meta-data/ram/security-credentials/` ⇒ **404 - Not Found** | **无 RAM 角色**可用(拿不到 STS 临时凭据) |
| 106(腾讯云) | **无** `tccli`;metadata `http://metadata.tencentyun.com/latest/meta-data/cam/security-credentials/` ⇒ **404** | **无 CAM 角色**可用 |
⇒ **两机均无法自我调用云 API**;且本线纪律 🔴 **⛔ 不索取、⛔ 不硬编码任何云 AK/SK** ⇒ **只能由用户侧控制台落地**(或**由用户主动**给出一次性凭据 —— 本棒 ⛔ 不索要)。
**④ 待落地规则形态(乙-1,原文级 —— ⛔ 执行侧不得自行改形)**
- **47 阿里云安全组 · 入方向**:协议 `UDP` · 端口范围 `21100/21115` · 授权对象 `106.54.21.172/32`
- **106 腾讯云安全组 · 入方向**:协议 `UDP` · 端口范围 `21100/21115` · 授权对象 `47.77.182.89/32`
- 🔴 **硬门**:**⛔ 零个 `0.0.0.0/0`**(含任何拆分 / 等价写法);⛔ 不放行 TCP 段(`LISTEN_ALLOWED_RANGES` 是 TCP,⛔ 别混);⛔ 不改 `nft` / `nginx` / `bwrap`。
**④-补 控制台定位信息与人工步骤(2026-09-18 16:0x 追加,供人工落地)**
(a)**定位信息(实测,⛔ 别靠默认地域猜)**
| 角色 | 云 / 产品 | 地域 · 可用区 | 实例 ID | 公网 IP | 私网 IP |
|---|---|---|---|---|---|
| 47 | 阿里云 ECS | 🔴 **美国(硅谷)`us-west-1` · `us-west-1a`** | `i-rj99af19cibck1ge93tq` | `47.77.182.89` | `172.18.16.212`(VPC `vpc-rj900gd84ra3taamu6tbr`) |
| 106 | 腾讯云 CVM | **上海 `ap-shanghai` · `ap-shanghai-2`** | `ins-3q6k1p8t` | `106.54.21.172` | `10.0.0.8` |
⚠️ 两机**跨洋**(硅谷 ↔ 上海)⇒ 打洞腿的建连时延天然偏高(不影响规则形态,但影响 S5/D5 的耗时读数解读)。
⚠️ **两机的安全组 ID 都取不到**:阿里云 metadata `security-group-ids` ⇒ **404**;腾讯云 metadata `security-groups` ⇒ **404** ⇒ 只能**从控制台实例详情页**点进去(或先按实例 ID 找到实例,再点它的安全组)。
⚠️ 106 的实例 ID 前缀是 `ins-`(CVM),**不是** `lhins-`(轻量)⇒ 走 **CVM 安全组**口径,⛔ 不是轻量「防火墙」口径。
(b)**阿里云(47)人工步骤**:ECS 控制台 → 顶部地域切到 **美国(硅谷)** → 实例 `i-rj99af19cibck1ge93tq` → 「安全组」→ 点安全组 ID → 「访问规则」→ **入方向** → 「手动添加」→ 授权策略 **允许** / 优先级 **1** / 协议类型 **自定义 UDP** / 端口范围 **`21100/21115`** / 授权对象 **`106.54.21.172/32`** / 描述 `dshs overlay hole-punch peer 106` → 保存(**立即生效,⛔ 无需重启实例**)。
(c)**腾讯云(106)人工步骤**:CVM 控制台 → 地域 **上海** → 实例 `ins-3q6k1p8t` → 「安全组」→ 编辑规则 → **入站规则** → 添加规则 → 类型 **自定义** / 来源 **`47.77.182.89/32`** / 协议端口 **`UDP:21100-21115`** / 策略 **允许** / 备注 `dshs overlay hole-punch peer 47` → 保存(**立即生效**)。
(d)**若改为由执行侧代做(用户主动给凭据时的最小权限口径)**:阿里云 = RAM 子账号 AccessKey,仅授 `ecs:DescribeSecurityGroups` + `ecs:AuthorizeSecurityGroup`,地域限 `us-west-1`;腾讯云 = CAM 子账号,仅授 `cvm:DescribeSecurityGroupPolicies` + `cvm:ModifySecurityGroupPolicies`,地域限 `ap-shanghai`。🔴 **有效期尽量短、用完即撤**;⛔ 本棒**不索取**、⛔ 不落盘、⛔ 不写进任何配置文件。
⚠️ **已核**:连接器市场内**不存在**阿里云 / 腾讯云 CVM·ECS 安全组相关的 Connector ⇒ **没有"装个连接器就自动获得授权"这条路**。
**⑤ 本机(WorkBuddy 开发机)在测试中的角色判定 —— 回应「所有的测试是否包含本机,当做结点或终端」**
- **判定**:本机 = **测试发起端 / 观测台 + 开发机**。⛔ **不是**覆盖网节点,⛔ **不是**覆盖网终端("终端"在本线从未定义过 —— 本线节点形态只有 `worker` 一种)。
- **证据(五条,逐条可分)**:
1. 四条测试命令**均从本机发起**(`npm.cmd test` / `overlay-probe.cjs` / `overlay-failover-drill.cjs` / `overlay-direct-probe.cjs`)—— 本机是**发起端**,不是**被测对象**。
2. 本机 **零** `dshs*` / `dsh-*` 进程、**零** `20080` / `19100` / `20000` / `21000–21001` / `21100–21115` 监听 ⇒ 结构上**不在** `/status.endpoints[]` / `sessions[]` 里。
3. 47 权威注册表 `nodes.json` = `{}`(**空**)⇒ 本机**未注册**为节点。
4. 本机 `curl -4 ifconfig.me` **回落到 IPv6**(`240e:332:cd0:a710:448:…`)⇒ **无固定公网 IPv4** ⇒ ⛔ 给不出稳定 `/32` ⇒ **D8 式对端放行对本机结构上不成立**。
5. 本机 `~/.dshs/` 仅两项:`overlay-table-dir`(**序㊸ 本线自建**的参数表定位注册,⛔ 非节点注册)+ `secret.key`(64 B · 09-13 15:40 · 系 `src/config.ts#ensureSecretKey` 的**数据根密钥**,⛔ **非**覆盖网节点密钥;密钥本体 ⛔ 未读取)。
- **若要纳入**:只能作「**只拨出**」从节点(本线铁律:**worker 永远只拨出**),需装运行时 + profile + 注册为 Worker;⚠️ 因无固定公网 IPv4,该节点**天然只能用 relay / 443 兜底腿**,**直连腿结构上不可用**(会成为 L2 的"永久只走中继"节点)。
- **本棒动作**:⛔ **未改**测试拓扑,⛔ **未注册**本机 —— 仅登记判定。
**⑥ 用户口径(原话,⛔ 不改写)**
> 「**1 开通,需要我操作还是你可以自己操作,所有的测试是否包含本机,当做结点或终端**」(2026-09-18 15:4x)
**⑦ 边界自证(逐条打勾)**
- ✓ ⛔ 未 commit / 未 push(HEAD 仍 `09ce76f`);⛔ 未 scp 任何文件;⛔ 未 build
- ✓ ⛔ 未改 `nft`(47 仍 **72 行**)/ `nginx` / `bwrap` / `server.ts` / `content/**`
- ✓ ⛔ 未出现任何 `0.0.0.0/0`;⛔ 未索取 / 未硬编码任何云 AK/SK
- ✓ ⛔ 未动 `dshs*` / `dsh-*` 单元;⛔ 未改 `RELAY_FAILOVER_COOLDOWN_MS`(命中 0)
- ✓ 密钥本体 ⛔ 未读取(只取 `stat` 大小 / 时间);两机密钥权限仍 `600`
**⑧ 指纹**
- 本单 **§8 前前缀**(口径:`sed '/^## §8 回报格式/,$d' <本单> | md5sum`):**`8925c2460810c6dc5cdbc075b6cdaed2`**(**写入本小节后现算**,与 §11.2 / §11.5 / §8.8-⑧ / §8.9-⑦ / §8.10-⑦ **逐字一致** ✅ —— §8 是**截断点**,故新增 §8.11 不改变它)。
- 参数表 **§10 指纹**:**`6b37bfd506758d882d9f803678f85d23`**(**现算**)—— 本棒对参数表**零改动**(⛔ 未新增 §11 小节,因本棒未产生任何键值 / 口径变更)。
**⑨ 下一棒**
- ⏸ **阻塞中**:等用户侧云控制台落地 ④ 的两条规则(或用户主动给凭据)。
- 落地后的第一动作 = **双向实测复测**(当前基线:47→106 发 21 收 0;106→47 发 28 收 0)+ 探针 `OBS-27/28` 复跑 + 参数表 §11 补记(含 §10 指纹现算)。
- ⚠️ **本棒 ⛔ 未登记接续棒**(阻塞项需人操作,⛔ 无人值守重试无意义)。
---
## §9 回头条件(**一出现必须回头**)
1. 🔴 **任何一棒都不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进回滚 / 演练 / 夹具路径**。
2. **要改 `server.ts` 的 DIAL / 白名单语义**(而非只读取)⇒ **停下报告**。
3. **出现任何"对公网任意源开放"的入站规则** ⇒ **立刻停手回滚**(违反用户拍板 ④)。
4. **跨网 / 白名单拒绝退化成"静默返空"** ⇒ 停下修(本线老病根)。
5. 🔴 **`peer` 接线后块 id 未复算** ⇒ 判据**不算通过**。
6. 🔴 **家宽节点直连需求出现**(= 乙-1 覆盖不到)⇒ 回到 **乙-2 / 乙-3**,后者**需要用户提供云 AK/SK**(⛔ 不许自行索取或硬编码凭据)。
7. **打洞实现需要内核态 / 虚拟网卡** ⇒ 停下报告(与参数表 §8-① "不做虚拟网卡"冲突)。
8. **打洞失败率导致中继容量超 45% 设计值** ⇒ 回头重算 `RELAY_MAX_HOSTS`(参数表 §5 口径)。
---
## §10 未验证项(**一个都不许编数**)
| # | 项 | 状态 |
|---|---|---|
| 1 | **本系统**的打洞成功率(⛔ 非"网络能不能打洞") | **待测** —— 需 S5 落地后测;`HOLE_PUNCH_RATE_LOCAL` 记的是**可行性**,⛔ 不是本系统成功率 |
| 2 | 直连带来的**中继带宽节省比例** | **待测**(需 S6 / S7) |
| 3 | 直连**切换耗时** | **待测** |
| 4 | 家宽节点的**公网出口 IP 变化频率**(决定乙-2/乙-3 的紧迫度) | **待测** |
| 5 | 云安全组**规则数量上限**(能否容纳全部节点 IP) | **待测**(需查云厂商配额) |
---
## §11 指纹与状态
### 11.1 归档号
**132**(⚠️ 本单**不带 04 孪生** —— 本线 `交接单_*.md` 一律无孪生;`mkdir .lock-132` 已原子占号)
### 11.2 指纹
- **§8 前前缀**(口径:`sed '/^## §8 回报格式/,$d' <本单> | md5sum`)= **`8925c2460810c6dc5cdbc075b6cdaed2`**(**落单时现算**)
- ⚠️ **§8 回填后前缀不变、全文 md5 会变** ⇒ 全文 md5 **不写死**,权威取数 = 现算
### 11.3 状态
- **本单状态**:**待开工**(P2 · S5–S7);🔴 **权限影响评估已获用户拍板**(§7,2026-09-18 12:22)
- **P1 关系**:⛔ **不重做 P1**(一键加入 + 分组准入已收官,见 `交接单_节点一键加入与分组准入_20260918.md` §8.9)
- **P1 出口项归入本单**:`src/web/routes/overlay-nodes.ts`(管理面 API)连同 S5 一并做
### 11.4 与既有文档的关系
- 本单**取代** `交接单_节点一键加入与分组准入_20260918.md` 的 **§6 P2 段**(三行粗描述)作为开工依据;⚠️ **不改该单正文**(§8 前前缀 `085e28b56a08aa1f01999bdb3e238911` 是**不变量**)。
- `覆盖网络_补遗与参考方案_20260916.md`(WebRTC / ICE 参考)与本单 §4.2「照抄成熟做法」一致 —— ⛔ 不引入 WebRTC 依赖(§2-7)。
### 11.5 落单后实测
- **§8 前前缀** = **`8925c2460810c6dc5cdbc075b6cdaed2`**(落单后实测;⚠️ 本行在截断点**之后** ⇒ 改它不影响该不变量)
- **落单快照**:全文 md5 **`127fe5204004cb9ea895b852a15dd143`**/**239** 行(2026-09-18 12:2x;⚠️ 本行写入后即失效 ⇒ 引用前**现算**)