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

330 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 交接单 · 节点一键加入与分组准入(覆盖网络线 · 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;⚠️ 本行写入后即失效 ⇒ 引用前**现算**)