Files
dsh_ai1net_server/归档/交接单-20260924-归档/交接单_覆盖网络直连与P2P_20260918.md
T

686 lines
87 KiB
Markdown
Raw Normal View History

# 交接单 · 覆盖网络「节点直连 + 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;⚠️ 本行写入后即失效 ⇒ 引用前**现算**)