Files
dsh_ai1net_server/归档/交接单-20260924-归档/交接单_节点一键加入与分组准入_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

44 KiB
Raw Blame History

交接单 · 节点一键加入与分组准入(覆盖网络线 · 2026-09-18 规划棒)

归档号:131(现核 04 目录最大 = 128;129 / 130 已被单 A / 单 B 两张交接单占用 ⇒ ⛔ 不补占、不重用) 目标一句话:把覆盖网络从「两台机器手工配好的专线」变成「产品」—— 任何人开一台节点服务器就能接入;节点按网分组、只允许指定节点加入;既能共建一张大网,也能各自独立组网。 来源:用户 2026-09-18 09:07 原话 ——「b 要实现开启一台结点服务器,就能连上覆盖网络,这样做这个网络才有价值,结点可以设置分组只允许那些结点加入,既可以成为大覆盖网络也可以建立独立覆盖网络」。 性质:规划棒产物 —— 本单只做方案与判据,⛔ 零代码改动、⛔ 零服务器改动(规划与执行分离)。


§1 用户口径(功能卡,⛔ 别改写成技术题)

问 答(用户原话/直接推论)
谁用 开节点服务器的人(平台运维 / 被授权的用户)
在哪用 一台新机器上;以及节点自己的配置处
要做什么 ① 开一台节点服务器 ⇒ 就能连上覆盖网络 ② 节点可设分组,只允许指定节点加入 ③ 既能成大覆盖网络,也能建独立覆盖网络
怎样算成功 ① 新机器一条命令接入(⛔ 不是照文档改四处配置)② 名单外的节点连不上(结构性拒绝,不是"设了 ACL 但忘了")③ 两张网互相看不见 ④ 大网 = 多组共用一个骨干;独立网 = 不共享任何骨干

用户已拍板的选项:2026-09-18 08:2x 问「要不要补、补哪个」⇒ 答复 B(连"节点直连"一起补),⚠️ 且用户明示 B 的缺点他已看到(新增直连暴露面 = 命中 R5)。


§2 只读前置(开工前必读,⛔ 别重做调研)

# 文件 为什么读它
1 src/net/relay/network.ts 网抽象唯一入口:ops / u:<userId> / 其它显式名 三条分支;裸 u 是保留字
2 src/net/relay/server.ts(头部注释 + dialers 段) ⑥ 网维度:会话与端点表按 <network>/<hostId> 索引;DIAL 必须同网 + 本网白名单
3 src/net/relay/directory.ts 引导三级链(内置种子 → 签名目录 → 离线降级);relays[] 由 47 的 127.0.0.1:3080 签发
4 src/net/relay/content/peer.ts ContentPeerGroup / groupKeyOf / sameGroup(组维度现状)
5 src/web/server.ts §内容面装配段 ⚠️ 现状:只接通 local;peer 诚实回"没有";edge/region/origin 未注入
6 参数表_覆盖网络_20260917.md 唯一参数表(观测阈值 / 待测项 / 权限影响评估)

🔬 本棒已取证的三条硬事实(⛔ 别当"待确认"): ① 「分组准入」与「独立网络」的底层机制已在代码里 —— dialers 是 Map<networkId, Set<hostId>>,没列到的网络一个都拨不动(默认拒绝),且跨网 DIAL 在"能不能拨"这一步就走不到(结构性隔离,不是策略性)。 ② 「一键加入」不存在 —— 加入一台机器现在要手工做四件事(装单元 / 配密钥 / 写 DSHS_RELAY_DIALERS drop-in / DB 登记),全仓无 join 类入口。 ③ 「直连」与「P2P 取回」都不存在 —— grep -i "punch|打洞|directPath" 只命中 addr-override.ts(443 兜底钉 IP,不是打洞);内容面 peer 档在生产路径上恒返回 undefined。


§3 范围与在册文件集

3.1 分期(分期即权限分期,⛔ 不许颠倒)

期 内容 是否命中 R5 为什么这么排
P1 一键加入 + 分组准入门面(把已有的白名单机制产品化) ⛔ 不命中(零新增暴露面) 用户要的核心价值("开一台就能连上")全在这一期,且能立刻开工
P2 节点直连(打洞) + P2P 取回通道接线 🔴 命中 R5(新增直连监听/探测面) 用户选 B 已含此项,但按 R5 须先出评估(见 §7);且它技术上依赖 P1 的"节点身份与准入"

3.2 在册文件集(P1;⛔ 超出即停下报告)

  • 新增:src/net/relay/join.ts(加入流程编排:凭据校验 → 落地配置 → 注册)
  • 新增:src/net/relay/registry.ts(准入凭据的签发/校验,载荷不含密钥本体)
  • 新增:scripts/overlay-node-join.cjs(节点侧命令行入口)
  • 新增:scripts/overlay-node-admit.cjs(控制面侧:签发准入凭据 / 写白名单)
  • 修改:src/net/relay/network.ts(只加 networkId 的形状校验与"网络注册表"读取,⛔ 不改既有三条分支语义)
  • 修改:src/web/routes/ 新增 overlay-nodes.ts(管理面 API:列出/批准/移除节点)
  • 修改:scripts/overlay-probe.cjs(新增 OBS-24 / OBS-25)
  • 修改:参数表_覆盖网络_20260917.md(新增键与判据行)
  • 测试:test/overlay-join.test.mjs(新增)

⛔ P1 明确不碰:server.ts 的 DIAL/白名单语义(只读取)、wire.ts 帧格式、keys.ts 既有语义、bwrap 参数、nginx/nft。


§4 方案(技术路线)

4.1 缺口 1 · 一键加入

现状(加入一台机器的全部动作,全手工): ① 装 relay / worker 单元 → ② 配节点密钥 → ③ drop-in 写 DSHS_RELAY_DIALERS="<network>:<hostId>" → ④ DB 登记 dsh_hosts

目标形态(一条命令):

dshs-node join --network <networkId> --invite <邀请凭据>

内部四步(全自动):① 校验邀请凭据(签名 + 网络 + 有效期 + 一次性)→ ② 生成本机节点密钥(私钥不出机)→ ③ 落地本机配置 → ④ 向控制面注册并等待被列入白名单。

关键设计(已定项,可推翻):

  • 控制面仍是权威单点(用户既有口径「归属/租约/骨干资格只能控制面写」)⇒ 加入的批准权在控制面,节点侧只是"申请 + 自动落地"。
  • 邀请凭据 = 控制面签的 (network, 有效期, 一次性 nonce),载荷不含任何密钥本体(照抄组密钥凭据的既有形态)。
  • 白名单由控制面派生(不再手写 drop-in):控制面把"已批准节点集合"投影成 relay 的白名单配置并 reload ⇒ 手写 drop-in 退化为应急通道(保留,⛔ 不删)。

4.2 缺口 2 · 分组准入门面

现状:机制完整(按网分桶 + 默认拒绝),但配置靠手写字符串(如 u:5:manager),写错了长得像"这张网不存在"。

目标:把它变成可管理的东西 —— ① 列出现有网与节点 ② 批准/移除节点 ③ 给节点的分组可改 ④ 独立网 = 新增一个 networkId,零代码(isNetworkId 已支持显式命名)。

判据(⛔ 不许做成"策略性 ACL"):跨网拒绝必须仍然是结构性的 —— 拒绝发生在"能不能拨"这一步,而不是"拨通了再查表"。

4.3 缺口 3 · 直连与 P2P(P2,命中 R5)

现状:⛔ 无打洞;内容面 peer 档有账本无通道。

目标:① 节点间交换候选地址 → 尝试直连 → 失败判死降级中继(⚠️ 照抄成熟做法,不发明)② 把 peer 档接上真实取回通道(这是"文件走 P2P 分发"成立的唯一前提)。

红线:⛔ 直连不得让任何节点反向进入别人的机器;直连只在同网 + 双方都在白名单时建立;⛔ 打洞不得绕过既有的身份强制与吊销。


§5 判据(先红后绿;⛔ 不许事后补写)

# 判据 形状 先红腿(对旧产物)
J1 邀请凭据一次性 同一凭据第二次使用 ⇒ 拒绝,原因码具名 旧产物无 join ⇒ 入口即缺 ⇒ 红
J2 白名单外的节点拨不动 未批准 hostId 拨号 ⇒ 拒绝(非超时、非静默) 机制已存在 ⇒ 本腿复验不新建
J3 跨网结构性拒绝 跨网 DIAL 在"能不能拨"处被拒;两端观测面零交集 同上
J4 一条命令接入 新机器 join 后两端观测面同时出现该节点,且 online=true 旧产物 ⇒ 红(无入口)
J5 独立网零共享 新 networkId 与 ops 网:relay 侧无共同 dialer 条目、无共同会话 旧产物 ⇒ 用 OBS-24 断言
J6 私钥不出机 节点侧私钥文件权限 0600;凭据/请求载荷逐字不含密钥本体 照抄组密钥既有腿

零回归三件套(每期收口必跑,三者都带 --table):npm.cmd test(Node 22)|node scripts/overlay-failover-drill.cjs --scene all --table|node scripts/overlay-probe.cjs --table(开工基线 = 23 PASS / 0 SKIP / 0 FAIL)。


§6 步骤(每步自带验收 + 回滚)

P1(⛔ 不命中 R5,可立即开工)

  • S1 · 网注册表与节点清单(只读面先行):控制面能列出「有哪些网、每张网有哪些节点、谁已批准」;探针可断言。⇒ 验收 = 探针新判据绿 + 零回归。
  • S2 · 准入凭据(签发/校验):控制面签发 + 节点侧校验;一次性 + 有效期 + 网络绑定。⇒ 验收 = J1 先红后绿。
  • S3 · join 编排:一条命令完成四步;失败必须具名(⛔ 不许静默拒绝)。⇒ 验收 = J4。
  • S4 · 白名单派生:控制面把已批准集合投影成 relay 白名单并 reload;手写 drop-in 退化为应急通道。⇒ 验收 = J2/J5。

P2(🔴 命中 R5,S5 前必须先出权限影响评估并经用户点头)

  • S5 · 直连候选交换 + 打洞探测:同网 + 双向白名单前置;失败判死降级中继。
  • S6 · peer 档接线:把真实取回通道接上(⚠️ 接线后必须复算块 id,否则命中本线老病根"假绿")。
  • S7 · 规模回归:候选数、切换时间、回源字节三项复测(本线已有基线读数可比)。

§7 权限影响评估(R5 · P2 专用;⛔ P1 不适用)

  1. 扩了什么:新增节点间直连探测(新增监听/出站探测行为)。⚠️ 具体端口/协议在 S5 设计时定,届时逐条列出,⛔ 不在此处含糊带过。
  2. 谁受影响:仅同网且双向在白名单的节点之间;⛔ 不对公网开放、⛔ 不跨网、⛔ 不影响租户实例的既有隔离面。
  3. 有没有不扩大也能实现的方案:有 —— 就是本期不做直连(跨机流量继续走中继)。⇒ 直连的唯一收益 = 省中继带宽 + 让 P2P 成立;⛔ 若用户不接受新增暴露面,P1 已交付的"一键加入 + 分组准入"仍然完整可用。
  4. 回滚方式:删直连配置 → reload → 行为回落"全部走中继"(秒级);验收 = 探针直连判据归位 + 零回归三件套。

⚠️ 用户已知悉:2026-09-18 08:2x 选 B 时,B 的缺点里明确写了"需要新开直连端口 = 扩大暴露面、命中 R5、必须单独点头"。⇒ 本单执行到 S5 之前会再停一次(给当时的具体端口/协议清单),这是 R5 的硬要求,⛔ 不是重复提问。


§8 回报格式(执行棒回填;⛔ 只填空,不改小节点名)

8.1 开工门(必填)

  • 本单 §8 前前缀 = 085e28b56a08aa1f01999bdb3e238911(2026-09-18 09:2x 现算;与 §11.2 逐字一致 ✅)
  • 抢锁结果 = 抢到(--claim-exec "执行棒P1-节点一键加入与分组准入-0918",锁空闲)|开工时刻 = 2026-09-18 09:2x|执行棒名 = 执行棒P1-节点一键加入与分组准入-0918
  • 开工基线现核:git rev-parse --short HEAD = 09ce76f(未提交改动 = 上棒遗留的 _tmp_seq24/、_中间产物_待清理/)|npm.cmd test = 201/200/0/1|探针 = 23 PASS / 0 SKIP / 0 FAIL

8.2 逐判据回报(J1–J6 各一条,含原始读数)

  • J1 邀请凭据一次性 —— 先红腿:lib 里把 consumeNonce 的 flag:'wx' 改成 'w' ⇒ 单测 B1/B2 双红,原文 ⛔ 竞态未守住:ok=12(须恰好 1)(12 进程真并发)⇒ 后绿腿:还原后 B1/B2 绿,且 33/33 全绿、lib md5 逐字一致 951a2526789b897aa58e3abbb5dea332。真机腿:chain47.sh 步(6) 原文 ✗ apply 失败:invite-already-used — nonce d4019b3c… 已被用掉(一次性凭据,⛔ 不重复受理)(rc=1)⇒ 判定:绿。
  • J2 白名单外的节点拨不动 —— 本腿复验不新建(机制已存在):OBS-06 判别器 dial=0 dialDenied=0 dialFailed=0(都是 number)+ OBS-11 relay 口绑定回环=1/1 条。⚠️ "新节点真机拨不动 / 批准后拨得动"这一条未在生产 relay 上执行(见 §8.4、§8.6)。
  • J3 跨网结构性拒绝 —— 复验:derive 产物按网分桶(network.ts#normalizeDialers 同一入口),三网原文 [lab-net] DSHS_RELAY_DIALERS=lab-net/lab-1|[ops] …=ops/lab-1|[u:5] …=u:5/lab-1 ⇒ 零交集;OBS-25 读得 自审 auditOk=true 跨网共享=0。⇒ 判定:绿(结构性,非策略性 ACL)。
  • J4 一条命令接入 —— 先红腿 = 旧产物无 join 入口 ⇒ 红(具名 missing required --file);后绿腿 = chain47.sh 步(4) 原文 ✓ join 成功:lab-net/lab-1|公钥 e18630527a6f7b5f…|四步 4/4,四步 verify-invite / node-key / local-config / register 全 ✓。⚠️ "两端观测面同时出现 + online=true"未在生产 relay 上执行(同上)。
  • J5 独立网零共享 —— 真机步(9)原文:三张网各得自己那条、⛔ 无共同条目;步(10) 注册表全貌 = ops(approved 1/pending 0) · lab-net(…1/0) · u:5(…1/0),三条记录的键分别是 lab-net/lab-1 / ops/lab-1 / u:5/lab-1。OBS-24 五项:形状合法 ✅|派生自审一致 ✅|pending 未进派生 ✅|结构性错桶 0 ✅|派生桶数 0 ≡ 有 approved 的网数 0 ✅(生产注册表为空 ⇒ 本项在真机读的是"空表也自洽")。⇒ 判定:绿。
  • J6 私钥不出机 —— 真机原文:FLAG: 申请单里私钥出现次数(须 0)= 0|FLAG: 节点私钥权限(须 600)= 600|邀请 doc 键 = ['expiresAt','issuedAt','network','nonce','version'](⛔ 无任何密钥)|selfcheck application.bytes=533 / hasPrivateKey=0 / shapeOk=true / hostMatches=true|nodeKeyModeOk=true(Linux 权限腿成立)。⇒ 判定:绿。

8.3 零回归三件套(三者都带 --table)

  • npm.cmd test 201/200/0/1(rc=0;Node 22.22.2,与开工基线逐字一致)|--scene all 12P/0S/0F(rc=0;幕4-C 实测 19370ms / deadline 30000ms)|探针 24P/1S/0F(rc=0)
  • ⚠️ 探针与开工基线(23P/0S/0F)的差 = 本棒新增 OBS-24/OBS-25 两条 +2P,但 OBS-09 由「在册」退化为「不在册 ⇒ SKIP」−1P/+1S。FAIL 数同为 0。归因与未复原说明 ⇒ 参数表 §11.4-②。

8.4 恢复形态核实

  • 派生侧(真机 /tmp 表外链路):三张网各 1 条、零共享;批准前派生为空(Environment="DSHS_RELAY_DIALERS=")⇒ 「收单 ≠ 批准」成立。
  • 🔴 relay 侧:未执行——⛔ 未把派生产物落到生产 50-overlay-dialers.conf、⛔ 未 reload relay(P1 硬门 D1「不改任何生产值」)⇒ "新节点加入后两端观测面同时出现 / 端点表新增条目 / used 变化" = 未验证(⛔ 不编数)。
  • 参考读数(本次运行演练后的现网状态,⚠️ 与本棒改动无关):capacity {max:7515, used:2, free:7513, utilPct:0}|online = [manager(ports= 空), w-106(ports=19000)]|endpoints 1 条(w-106 port=19000 localPort=43061 online=true)。

8.5 边界自证(逐条打勾)

  • ⛔ 未 commit / push(R7):git rev-parse --short HEAD = 09ce76f(与开工一致)|git status --short = 3 个 M(overlay-probe.cjs / index.ts / network.ts)+ 5 个 ??(overlay-node-admit.cjs / overlay-node-join.cjs / join.ts / registry.ts / test/overlay-join.test.mjs)—— 全部在册,⛔ 无越界文件 ✅
  • ⛔ 未改任何生产值(D1):⛔ 无 drop-in 改动、⛔ 无 env 改动、⛔ 未 reload 任何单元配置。⚠️ 如实补记:本棒新增了状态目录 /var/lib/dshs/overlay/(nodes.json 0644 + consumed/ + selfcheck.json)—— 它是 §3.10 登记的状态(非配置);⛔ 未覆盖任何既有文件 ✅
  • ⛔ 未新增公网监听口、未动 nft·nginx(R5):OBS-11 PASS 原文 nft accept 0 多出 0|relay 口绑定回环=1/1 条 ✅
  • 🔴 RELAY_FAILOVER_COOLDOWN_MS=0 计数 = 0(grep -c 在 overlay-node-admit.cjs / overlay-node-join.cjs / overlay-probe.cjs / test/overlay-join.test.mjs / registry.ts / join.ts / network.ts 七个文件上全部 = 0)✅
  • ⛔ 未改 bwrap 参数(grep -c "bwrap\|perms" 在 registry.ts / join.ts = 0 / 0)✅
  • 🔴 密钥本体不经网络 / 不经 relay:申请单内私钥出现次数 0|邀请 doc 无密钥键|OBS-25 私钥泄露=0 ✅

8.6 如实留档(必填,哪怕"没有")

  • 未做的项:src/web/routes/overlay-nodes.ts(管理面 API)未建 ⇒ 本棒自决移入 P2(理由:与 P1 硬门「零新增暴露面」冲突风险最高;S1–S4/J1–J6 验收均不依赖它;交接单 §7 本身即 P2 专用)。⚠️ 可推翻。
  • 未测的项:① 「派生落盘 ⇒ relay reload ⇒ 新节点真能拨通」这条生产闭环(因 D1 硬门,见 §8.4);② 另见本单 §10 未验证项(4 条,本棒未新增未验证项)。
  • 过程中发现的额外问题(⛔ 只报告、未顺手改):
    1. 🔴 控制面 CLI --file 键名混用(真机缺陷,已修 + 已补 E10 先红后绿):apply --file <申请单> 曾把注册表文件也指到申请单 ⇒ 收单炸在第 4 步而 nonce 已被占位(对外 =「收单失败 + 同一张邀请再也用不了」)。修 = 覆盖键改名 --registry。教训:调库式判据(E9)抓不到参数解析类缺陷 ⇒ 必须有CLI 级真回环(E10)。
    2. ⚠️ OBS-24 原拟判据没有判别力("同一 hostId 出现在 ≥2 张网 ⇒ FAIL"实为合法态)⇒ 已降级为信息项,改判「结构性错桶=0」+「派生桶数 ≡ 有 approved 的网数」。
    3. 🔴 本次零回归演练的副作用(本棒唯一未复原项):OBS-09 由 PASS 变 SKIP。归因有原文:演练后 OBS-01/04/08 转红 ⇒ 执行恢复(47 restart dshs + 106 restart dshs-worker)⇒ 三者回绿,但 106 worker 重注册读数由 accepted=[19000,21000] 变为 accepted=[19000](实例端口 21000 的注册未随进程重建)。✅ 两端实例 :20000/:21000 都在监听、门户 200,无业务中断。回头条件 = 下一棒收口若仍 SKIP 或出现「跨机实例面访问不通」⇒ 升级独立小项(先查"实例 → 本地 worker 端口注册"链路)。详 ⇒ 参数表 §11.4-②。

8.7 指纹

  • 参数表 §10 指纹(现算并同步)= 695dcb4d88a778840322bc221417d339(口径:sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum)|本单全文 md5(写入本行前现算)= 2344d947499745a5c1e5a5cb56d0b274/236 行(⚠️ 写入本行后即失效 ⇒ 引用前现算);§8 前前缀 回填后仍 = 085e28b56a08aa1f01999bdb3e238911(不变量,已复算 ✅)

8.8 收口复核(序㊱ 收口复核棒 · 2026-09-18 10:0x–10:4x;⛔ 只复核,⛔ 未开工 P2)

  • 开工门复核:本单 §8 前前缀 = 085e28b56a08aa1f01999bdb3e238911(现算,与 §8.1/§11.2 逐字一致 ✅)|抢锁 = 抢到(--claim-exec "收口复核棒-序36-P1-OBS09",锁空闲)|现核基线 = HEAD 09ce76f、git status --short = 10 处(3 M + 7 ??,与 §8.5 逐数相同)
  • ① OBS-09 复核 + 复原(逐段原文)
    • 开工读数(10:1x):SKIP OBS-09 在册实例面 无 (阈值 ∈ {200,401})|不在册 2 项:本机:20000,对端:21000|/status 端点表 1 条 = w-106 port=19000 localPort=43061 online=true ⇒ 与 §8.6-3 / 参数表 §11.4-② 记录一致。
    • 链路取证(「实例 → 本地 worker 的端口注册」):端口集 = staticPorts + forwarded(worker/agent.ts:323)|106 /healthz 原文 = {"hostId":"w-106","instances":0,"tunnel":{"ready":true,"ports":[19000]}} ⇒ worker 自己认不到实例(instances=0)⇒ reconcileTunnel()(agent.ts:257-263,20 s 一跳)拿不到 live 端口 ⇒ 永不 forward(21000)。根因链(原文级):orchestrator.ts:558 listUserInstances() = [...this.mains.values()],而 rehydrateAdoptedScopes() 认领时只写 this.adopted(:1492)⇒ ⛔ 不进 mains ⇒ 09:56:28 重启后实例 scope 被认领([rehydrate] adopted … port=21000 + probe OK :21000)但平台侧不可见 ⇒ 端口不再声明。⇒ 这是候选 B 已知边界的直接后果("不被杀 + 访问时自然替换",⛔ 非"重启后直接可用"),⛔ 不是 relay 侧故障(relay 全程正常,OBS-12 RSS 70 MB)。
    • 复原动作(设计内路径 · 零代码 / 零配置改动):临时 session(/opt/dshs/mksess.cjs guest,R4)⇒ 一次真实用户可见面访问(curl -H 'Host: guest.alotbuy.com' -b sid=…)⇒ 10:18:34 [dsh-child main] Running as unit: dsh-100002-7d1c8cbf.scope(旧 scope dsh-100002-2e9202dd 被 cleanStaleScopes 收掉)⇒ 新实例走正常 launch 路径声明端口:/healthz = {"instances":1,"tunnel":{"ready":true,"ports":[19000,21001]}}|/status 端点表 = {19000 ✓, 21001 ✓}。用户可见面 = http 200 / 62451 B / 502 标记 0(与 §8.12-4 同口径)|临时 session 已删(DELETE 1 ⇒ 余 0)。
    • 🔴 复原后第一次复跑仍 SKIP 的真因 = 判据输入过期(假 SKIP):替换落点漂到 21001(spawn.ts#findFreePortInRange 从 DSHS_INSTANCE_PORT_BASE 上扫第一个空闲口;替换发起时 21000 仍被旧 scope 占着 ⇒ 顺延),而 OBS-09 的"在册"按表内固定端口取 ⇒ 对不上。
    • 处置(事实优先,⛔ 不为了迁就旧值去重启生产实例):参数表 PEER_INSTANCE_PORT 实测替换 21000 → 21001(+ PEER_AGENT_PORT 出处括注同步 ports=19000/21001);§10 指纹 695dcb4d88a778840322bc221417d339 → 2ae954e2e07b47651a84e597c19a3444(现算并已同步写进 §10)。
    • 复原读数:PASS OBS-09 在册实例面 w-106:42717=401 (阈值 ∈ {200,401})|不在册 1 项:本机:20000 ⇒ 由 SKIP 回到 PASS ✅。⚠️ 本机:20000 恒"不在册"是结构性的(47 的 worker 不是 relay 客户端,§11.1 已钉死)⇒ ⛔ 不是缺陷。
  • ② 产物可复现:node --test test/overlay-join.test.mjs = 33 tests / 33 pass / 0 fail / 0 skipped(rc=0)|探针 0 FAIL ⇒ 两件都成立。
  • ③ 零回归三件套:npm.cmd test(Node 22.22.2)= 201 / 200 pass / 0 fail / 0 cancelled / 1 skipped(与基线逐字一致)|overlay-failover-drill.cjs --scene all --table = 12 PASS / 0 SKIP / 0 FAIL(幕4-C 实测 17375 ms / deadline 30000 ms)|overlay-probe.cjs --table = 25 PASS / 0 SKIP / 0 FAIL(rc=0)。
  • ④ 🔴 演练副作用的同型复现与处置(如实记):--scene all 跑完后 47 中继读数 = ep=[] / used=1 / idOk=1 —— 演练把 106 worker 的通道逼到了 106 自己的中继(106 中继 sessions=["w-106"] 原文可证)⇒ 若就此复跑,OBS-01/04/08/09 会同型转红(与 §11.4-② 同因)。处置(⛔ 未重启任何 worker、⛔ 未换实例):停 106 dshs-relay ⇒ 逼它按候选链回落 47(实测 ~15 s 后 used=2 / idOk=2 / ep={19000 ✓,21001 ✓})⇒ 再启 106 dshs-relay(active,⚠️ 该单元在演练前是 inactive ⇒ 本棒把它留在了 active)。⚠️ 由此点名一条观测缺口:探针只读 47 的 RELAY_STATUS_URL,而 worker 可能挂在 106 的中继 ⇒ 47 视角"端点表为空"⛔ 不等于"覆盖网络断了"(潜在假红)。
  • ⑤ 边界自证:⛔ 未 commit / push(HEAD 09ce76f;git status --short = 10 处,与开工逐数相同)|⛔ 未改任何生产配置值(无 drop-in / env / nft·nginx / bwrap 改动)|🔴 RELAY_FAILOVER_COOLDOWN_MS=0 计数 = 0|🔴 密钥本体不经网络 / 不经 relay|⛔ 未开工 P2(P2 仍待用户拍板权限影响评估)。
  • ⑥ 如实留档(必填):
    1. 🔴 OBS-09 的"在册"判据钉死了两个会漂移的端口 ⇒ 每次「访问时自然替换」后都可能变成假 SKIP(本棒连踩两次:端点表 []→21001;同步表值只是把输入改对,⛔ 判据本身的脆弱性未治)。⇒ 已点名 = 下一棒(序㊲):把"在册实例面"改为从 /status.endpoints[] 派生该主机名下的在线实例端点(⛔ 本棒按"只做被明确要求的事"未改判据)。
    2. ⚠️ 本棒对生产做了 3 个动作(均属 R8 开发环境、动手前已声明):① guest 实例一次「访问时自然替换」(设计内路径,非人工杀实例)② 106 dshs-relay 停 / 启各一次(逼 worker 回落 47,约 1 min 单中继运行)③ 参数表文档值同步。⛔ 无不可逆操作、⛔ 无用户数据改动。
    3. ⚠️ --scene all 会改变通道拓扑且演练自身不复原(把 worker 逼到 106 中继)⇒ 每次跑完都要"回落 47"才算回到可观测基线;这一点建议写进演练脚本头部(⛔ 本棒未改脚本)。

8.9 收口复核(序㊲ 执行棒 · 2026-09-18 11:0x–11:2x;⛔ 只做 §8.8-⑥ 的两条点名,⛔ 未开工 P2)

  • 开工门:本单 §8 前前缀 = 085e28b56a08aa1f01999bdb3e238911(现算,与 §8.1/§8.7/§8.8 逐字一致 ✅;⚠️ 口径 = sed '/^## §8 /,$d' <本单> | md5sum)|抢锁 = 抢到(--claim-exec "序㊲ 执行棒(观测面去常量依赖)",锁空闲)|现核基线 = HEAD 09ce76f、git status --short = 10 处(与 §8.8 逐数相同)
  • ① §8.8-⑥-1 点名(OBS-09 的"在册"判据钉死会漂移的端口 ⇒ 假 SKIP)—— 已消除:判据改为从 /status.endpoints[] 派生「该主机名下、非 agent 端口的在线实例端点」,每一个都必须可达(码 ∈ PROBE_CODE_SET),派生为空 ⇒ SKIP + 留痕。先红后绿(原文级 · 两侧逐行 diff 只差 OBS-09 一行):红 = SKIP OBS-09 在册实例面 无 (阈值 ∈ {200,401})|不在册 2 项(记 SKIP 的依据):本机:20000,对端:21000(夹具 = 表值回写成漂移前的 21000 + 真实 47 /status(实例在 21001))|绿 = PASS OBS-09 在册实例面(派生 1 条):w-106:42717=401 (阈值 ∈ {200,401})。🔴 旧夹具口径(位置式)已退役并硬报错(rc=2);🔴 派生子缺码 ⇒ FAIL 并点名(拿旧端口的码糊弄不过去)。收益实测:复原后 localPort 由 42717 → 44231,探针无须改表值即回绿(⚠️ 这正是 §8.8-① "同步表值只治输入、不治判据"的对照)。
  • ② §8.8-⑥-2 点名(探针只读 47 中继 ⇒ ep=[] 时潜在假红**)—— 已消除(两种情形可分):新增判别源 = 对端中继的 /status(ssh <SSH_TARGET_106> curl <RELAY_STATUS_URL>,只读回环;⛔ 零新增暴露面、零新增参数键;⚠️ 只在 47 视角为空时才去读 ⇒ 正常态零额外 ssh**)。同一份 47 空表输入 ⇒ 三种可分输出:(甲) 对端中继声明了端口 ⇒ SKIP OBS-08 端点表 0 条 ⇒ SKIP + 留痕|… 对端中继(test106)声明了端口:w-106 ports=19000/21001 ⇒ 客户端挂在**另一台中继**上(⛔ 非故障、⛔ 不判红)(真机 · --scene all 演练态实测:47 = ep=[] / used=1 / idOk=1,106 = [w-106:19000, w-106:21001])|(乙) 两台中继都无该客户端会话 ⇒ FAIL OBS-08 …(两台中继都无该客户端会话)|(丙) 对端中继读不回来 ⇒ FAIL OBS-08 …(不可判)(⛔ 不许静默当绿)。⚠️ 旧口径在同态下 OBS-08 恒 FAIL(eps.length > 0)⇒ 分不出两者,这就是被点名的假红。
  • ③ 零回归三件套(都在改动之后跑;⚠️ npm.cmd test 无 --table 参数,另两件已带):npm.cmd test 201/200/0/1(rc=0,逐字一致)|overlay-failover-drill.cjs --scene all --table = 12 PASS / 0 SKIP / 0 FAIL(rc=0;幕4-C 18353 ms / deadline 30000)|探针 = 25 PASS / 0 SKIP / 0 FAIL(rc=0)⇒ 与基线逐数一致(⚠️ 探针不在 npm test 覆盖内,其零回归即此行)。
  • ④ 复原(按 §8.8-④ 同款;⛔ 未重启任何 worker、⛔ 未换实例):停 106 dshs-relay ⇒ 约 15 s 后 47 视角回落 used=2 / 端点 = [(19000, 39815), (21001, 44231)] ⇒ 启回 106 dshs-relay(active)⇒ 探针回绿。⚠️ 演练态下 OBS-01 / OBS-04 仍红(used=1 / identityOk=1 < 各自阈值 2)—— 二者是计数判据、⛔ 与"端点表为空"无关,复原后即绿(详见「参数表_覆盖网络_20260917.md」§11.5-⑤-1)。
  • ⑤ 边界自证:⛔ 未 commit / 未 push(HEAD 09ce76f;git status --short 10 处与开工逐数相同;本棒只动 scripts/overlay-probe.cjs 一个仓内文件)|⛔ 未改任何生产配置值(参数表值格一律未动)|⛔ 未新增暴露面(OBS-11 多出 0 缺失 0、relay 口仍 1/1 回环)|🔴 RELAY_FAILOVER_COOLDOWN_MS 命中 0|⛔ 未开工 P2|🔴 密钥本体不经网络 / relay。
  • ⑥ 如实留档:参数表_覆盖网络_20260917.md §11.5 已新增小节(零回归三件套 + ①② 原红绿原文 + 可分三态 + 边界自证 + 5 条留档;🔴 §10 指纹 2ae954e2e07b47651a84e597c19a3444 → 71848fad4ee0d6a574d833169e042fd1,现算并已同步)。⚠️ 本棒另发现一条文档口径歧义(探针文件头总括称"夹具模式…把远端原文喂进来 ⇒ 不 ssh",但 OBS-24/OBS-25 的分支无 fixture 守卫 ⇒ 未给各自夹具时会真去 ssh 取生产读数;⚠️ 这两项各自的用法行与代码一致,只有文件头那句总括与事实不符)⇒ ⛔ 按"只做被明确要求的事"未改、已在 §11.5-⑤-3 登记二选一建议。📁 证据 = 工作区 _tmp_seq37/。

8.10 收口复核(序㊳ 执行棒 · 2026-09-18 11:3x–12:0x;⛔ 只做用户拍板的两项收口,⛔ 未开工 P2)

  • ① 探针夹具封闭性(✅ 用户拍 A 方案):OBS-24 / OBS-25 两处取数由 if (<夹具> !== undefined) { 读夹具 } else { ssh } 改为 … else if (fixture) { SKIP + 留痕 } else { ssh }(守卫排在 ssh 之前);探针文件头新增「封闭性」硬约束段。先红后绿(原文级 · 两侧逐行 diff 只差这两行) —— 两侧同一份 --table 副本(SSH_TARGET_47 / SSH_TARGET_106 → 127.0.0.1、SSH_PORT → 1 ⇒ "是否 ssh"变成可观测差异):🔴 红腿(守卫摘掉 = 还原序㊱ 形态)= FAIL OBS-24 ❌ 注册表**读取失败**(⛔ 与"不存在"可分):Command failed: ssh -p 1 -o BatchMode=yes 127.0.0.1 … + ssh: connect to host 127.0.0.1 port 1: Connection refused(真 ssh 调用 2 次)|🟢 绿腿(现产物)= SKIP OBS-24 夹具模式未给 --nodes-fixture ⇒ SKIP + 留痕(⛔ 不去 ssh 读生产)(真 ssh 调用 0 次)。可分性 = 给不存在的夹具路径 ⇒ FAIL … ❌ 注册表**读取失败**…:夹具不存在:…/NO_SUCH_nodes.json(⛔ 不是 SKIP)。
  • ② 键名对齐:参数表 DRILL_RELAY_UNIT → RELAY_UNIT_NAME(值格未动、仍 dshs-relay)⇒ ⛔ 零个新增 / 改动生产 env;引用点 3 处全改(overlay-failover-drill.cjs + 原因注释 / overlay-probe.cjs / 参数表 §3.9 + 小节标题注 + §11.5-⑤-4 状态)。⚠️ 文档库档案不改(04-调整方案/117-… / 归档单 T13 记的是当时事实)。真机验证 = 原样取数命令 ⇒ __RELAY_ACTIVE__=active ✅。
  • ③ P2 待拍板清单已登记 ⇒ 见本节 §8.11(只登记、⛔ 零动作)。
  • ④ 零回归三件套(改动之后跑):npm.cmd test(Node v22.22.2)= 201 / 200 / 0 fail / 0 cancelled / 1 skipped(rc=0,逐字一致)|overlay-failover-drill.cjs --scene all --table = 12 PASS / 0 SKIP / 0 FAIL(rc=0;幕4-C 20770 ms / deadline 30000)|overlay-probe.cjs --table(真机)= 25 PASS / 0 SKIP / 0 FAIL(rc=0)。⚠️ 复原(按 §8.8-④ 同款,⛔ 未重启任何 worker、⛔ 未换实例):演练后 47 视角确为空 ⇒ 探针呈 21P / 2S / 2F(2F = OBS-01 used=1、OBS-04 identityOk=1,计数判据、⛔ 与"端点表为空"无关)⇒ 停 / 启 106 dshs-relay(约 22 s 后 47 视角回落 used=2 / identityOk=2 / 端点 = [(19000,33213),(21001,42783)])⇒ 探针回绿。🔑 顺带取得键名改名的真机端到端证据:演练态 SKIP OBS-08 原文含「对端中继(test106)声明了端口…」⇒ 判别器真的走完了真机分支那条 ssh(内含 systemctl is-active ${RELAY_UNIT_NAME})✅。
  • ⑤ 边界自证:⛔ 未 commit / 未 push(HEAD 09ce76f;git status --short 11 处 = 序㊲ 的 10 处 + 本棒新增 scripts/overlay-failover-drill.cjs 一处 M,逐条可解释)|⛔ 未改任何生产配置值(参数表值格一律未动)|⛔ 零新增暴露面|🔴 RELAY_FAILOVER_COOLDOWN_MS 命中 0|⛔ 未改 bwrap / nft / nginx|⛔ 未开工 P2|🔴 密钥本体不经网络 / relay。
  • ⑥ 如实留档:参数表 §11.6 已新增小节(🔴 §10 指纹 71848fad4ee0d6a574d833169e042fd1 → 2275870a5a9eb1964a648f2950dee528,现算并同步)|§8 前前缀 085e28b56a08aa1f01999bdb3e238911 逐字不变 ✅(本节在截断点之后)。🔬 两条口径:① 本棒"红腿"用的是同一份"ssh 目标不可达"的表副本 ⇒ ⛔ 未碰任何生产机(真 ssh 2 次全部打在 127.0.0.1:1);② 既有夹具语义(--instance-fixture / --peer-status-fixture / 序㊲ 的 OBS-08 三态)未受影响(回归自证见参数表 §11.6)。📁 证据 = 工作区 _tmp_seq38/。

8.11 P2 待你拍板的具体内容(R5 权限影响评估 · 序㊳ 登记,⛔ 未开工)

起因:序㊳ 执行棒被点名"说下要定的具体内容"。本节把 §7-1 那句「具体端口/协议在 S5 设计时定,届时逐条列出」提前落实成清单 —— 让拍板不必先读代码。⛔ 本节只登记,不含任何动作。

已实测的三条事实(拍板依据,全部来自本线既有读数,⛔ 非推演):

  1. 🔴 47 / 106 的云安全组拦 UDP 入站(⛔ 不是 nft:input policy = accept)。证据 = 序⑥ S4:47 上 0.0.0.0:21100/21101 UDP 观察器开放 ≈ 75 s、零收包(连同机发出的都收不到)⇒ 已关闭(ss -lunp 空)。
  2. 打洞成功率(现网实测 n=3 对、2/2 可打洞):对端→本机方向 10/10 收包;本机→云机方向取不到(= 事实 1 的直接后果)。⚠️ 样本里云节点占 2/3,不代表家宽场景。
  3. 云机↔云机(47↔106)本来就是 L1 直连、不是打洞场景 ⇒ 直连的收益面只在"用户设备 ↔ 云节点"这一段。

要你拍的只有两件事:

① 要不要为打洞开「UDP 入站」的云安全组规则?(这是本期唯一真正的门禁)

A. 开 优点:打通"云节点 → 用户设备"这一方向,配合已是 10/10 的反方向 ⇒ 直连率接近业内值(0.90–0.94);S6「P2P 取回通道」才有成立前提。 缺点:新增入站暴露面(UDP 端口可被公网扫描 / 发包);且必须在云控制台改安全组 —— 这一步 ⛔ 本平台代码侧自决不了。

B. 不开 优点:零新增暴露面;P1 交付的"一键加入 + 分组准入"仍然完整可用。 缺点:链路两端只要有一端是云节点就只能走中继 ⇒ 直连率显著下降、省不了中继带宽;S6 的 P2P 取回失去成立前提(内容分发继续走中继)。

② 若选 A,入站 UDP 段的"来源范围"怎么定?

甲. 固定小段 + 对 0.0.0.0/0 放行 优点:规则最少、不会因对端 IP 变化而失效;实现最简单。 缺点:暴露面最大(全网可扫该 UDP 段)。

乙. 固定小段 + 仅放行对端节点 IP 优点:暴露面最小(只有白名单节点能发包),与本线"权限只准收窄"一致。 缺点:云安全组规则数有上限;家宽 / 移动 IP 会变 ⇒ 需动态维护、规则会膨胀。

丙. 不开 UDP,改走已建成的 443/TCP 兜底 优点:复用既有收口(零新增暴露面)。 缺点:TCP 443 打洞成功率远低于 UDP,且 addr-override.ts 目前只做"钉 IP"、不是打洞 ⇒ 事实上等于不做直连。

影响面与窗口(R8 口径):改动只落在 47 / 106 的云安全组 + 两台 relay 配置上;⛔ 不碰租户实例隔离面、⛔ 不跨网、⛔ 不影响既有 443 收口。回滚 = 删直连配置 + reload ⇒ 秒级回落"全部走中继"(§7-4)。⚠️ 时间窗可避开用户活跃时段。

本棒倾向(陈述句,可推翻):先拍 ①。若你要的是"看到线上效果",最小可验证形态 = A + 乙(固定小段 + 仅对端 IP)—— 它同时满足"能打洞"与"暴露面最小";甲 留到"直连率被证明是瓶颈"时再放开。⚠️ 若你更看重零新增暴露面,选 B 也完全不亏 —— 代价只是"直连 + P2P"这两项功能不做,P1 已交付的能力不受影响。


8.12 拍板落地(2026-09-18 12:22 · 序㊴ 规划棒;⛔ 未开工)

  • 用户原话:1 用户可设置,默认开启提示用户 2 乙
  • ⇒ §8.11 的两问已闭合:
    • ① 开不开 UDP 入站 ⇒ 「用户可设置 + 默认开启 + 提示用户」—— ⛔ 不是简单的"开"或"不开",而是把开关交给用户自己、平台默认开并必须提示(⇒ 产品口径见新单 §1);
    • ② 入站 UDP 段的来源范围 ⇒ 乙(固定小段、仅放行对端节点 IP,⛔ 非 0.0.0.0/0)。
  • ⇒ 产物 = 本线第一份 P2 可执行单:工作区根 交接单_覆盖网络直连与P2P_20260918.md(归档号 132;§8 前前缀 8925c2460810c6dc5cdbc075b6cdaed2;239 行)。⛔ 本单正文一字未改(§8 前前缀 085e28b56a08aa1f01999bdb3e238911 仍是不变量 ✅)—— P2 的开工依据自此移交新单,本单 §6 的 P2 三行粗描述退化为指针。
  • 🔴 一条如实登记的技术现实(同时写在新单 §4.5):乙要求云安全组按"对端节点 IP"放行,而家宽 / 移动节点的公网出口 IP 会变 ⇒ 规则会失效、且规则数有上限。序㊴ 自决(可推翻) = 乙-1 起步:只对有固定公网 IP 的节点开(云节点 / 专线);家宽节点本期走中继(⛔ 不影响可用性,只影响"它能不能被直连")。乙-2(半自动)/ 乙-3(云 API 自动化,🔴 需云 AK/SK)登记为新单 §9 回头条件 —— ⛔ 本棒未请求任何凭据。
  • ⚠️ 一条遗留(仅报告,⛔ 未动手):dsh-server-docs/04-调整方案/.lock-131 仍在(序㊱ P1 的占号窗口未释放)⇒ 后果 = 后续取号者永久跳过 131(本棒已现核 ⇒ 取 132)。⛔ 本棒不动他人锁类目录,登记在此待处置。

§9 回头条件(一出现必须回头)

  1. 🔴 任何一棒都不许把 RELAY_FAILOVER_COOLDOWN_MS=0 写进回滚 / 演练 / 夹具路径(自锁:实测 121–123 s 无切换)。
  2. 要改 server.ts 的 DIAL/白名单语义(而非只读取)⇒ 停下报告(本单只做"配置派生 + 管理面")。
  3. P1 阶段出现任何新增暴露面 ⇒ 停下报告(P1 的前提就是零新增暴露面)。
  4. 跨网拒绝从"结构性"退化成"策略性"(拨通了再查表)⇒ 立刻停手回滚。
  5. join 流程出现静默拒绝(配置错伪装成"网络不通")⇒ 停下修,⛔ 不许带着上线。
  6. peer 档接线后块 id 未复算 ⇒ 判据不算通过(会把"零回源"做成假绿)。
  7. 要动 wire.ts 帧格式 / keys.ts 既有语义 ⇒ 停下报告(R7)。

§10 未验证项(一个都不许编数)

# 未验证项 怎么验(口径)
1 节点直连的成功率与收益 需 P2 落地后测:直连成功比例、切换耗时、省下的中继带宽(⛔ 现无数据)
2 一次性凭据的并发竞争 同一凭据被两台机器同时使用 ⇒ 必须恰好一台成功(需并发夹具)
3 多网络下的容量口径 --max-hosts 是全局还是按网?⇒ 需明确并写进参数表
4 节点规模上限 本单不测;引用既有推演值(百台:L3 需中继 40–55)时⛔ 须标明是推演非实测

§11 指纹与状态

11.1 归档号

  • 现核最大号:ls 04-调整方案/ | grep -oE '^[0-9]+' | sort -n | tail -1 ⇒ 128(2026-09-18 09:0x 实测)
  • 占号:mkdir 04-调整方案/.lock-131 ⇒ 成功 ⇒ 本单归档号 = 131(129 / 130 = 单 A / 单 B,⛔ 不重用)
  • ⚠️ 本棒不落 04 孪生(与本线全部 交接单_*.md 一致)⇒ 占号窗口在落单后即释放,执行棒收官建档案时须重新现核取号

11.2 指纹

  • §8 前前缀(口径:sed '/^## §8 回报格式/,$d' <本单> | md5sum)= 085e28b56a08aa1f01999bdb3e238911
  • ⚠️ §8 回填后前缀不变、全文 md5 会变 ⇒ 全文 md5 不写死,权威取数 = 现算

11.3 状态

  • 本单状态:P1 已执行收官(2026-09-18 10:0x · 执行棒 执行棒P1-节点一键加入与分组准入-0918);P2(S5–S7)未开工(命中 R5 ⇒ 必须先出权限影响评估并经用户点头)
  • 本棒(执行棒)边界自证:⛔ 未 commit / 未 push · ⛔ 未改任何既有 drop-in / env / nft·nginx / bwrap · ⛔ 零新增公网监听口 · ⛔ 密钥本体不经网络(详见 §8.5)
  • P1 出口项:src/web/routes/overlay-nodes.ts(管理面 API)本棒自决移入 P2(理由见 §8.6;⚠️ 可推翻)
  • 🔴 本棒唯一未复原项:OBS-09 由 PASS 退化为 SKIP(演练副作用,已归因、有原文;回头条件见 §8.6-3 与 参数表 §11.4-②)

11.4 与既有方案的关系

  • 本单不取代任何既有文档;覆盖网络_骨干层方案_20260916.md(骨干 ≤10–20 成员 + 选择性加入)与本单的"分组准入"是同一件事的两个层次:那份讲"骨干怎么选",本单讲"节点怎么进来"。
  • ⚠️ 与 会合中继拆分_取证与改造方案_20260916.md(S0–S4,入口 §2 标注为"第一优先 · 真活")有交集:本单 P1 的"白名单派生"会动到 relay 侧配置投影 ⇒ 执行前须与该方案的进度对账,⛔ 不许两条线同时改 relay 配置面。

11.5 落单后实测

  • §8 前前缀 = 085e28b56a08aa1f01999bdb3e238911(落单后实测;⚠️ 本行在截断点之后 ⇒ 改它不影响该不变量)
  • 落单快照:全文 md5 ca4ca8aad292efaf02357e1e2dacb6da/214 行(2026-09-18 09:1x;⚠️ 本行写入后即失效 ⇒ 引用前现算)