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

87 KiB
Raw Blame 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;⚠️ 本行写入后即失效 ⇒ 引用前现算)