Files
dsh_ai1net_server/docs/覆盖网络/参数表_覆盖网络_20260917.md
T
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

932 lines
189 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-17
> **唯一来源**:`覆盖网络_应用场景与待完善清单_20260916.md` **§五 第 5 项** + **§四 P1 行** + `交接单_443兜底_20260917.md` **§8.8**
> **本表要做的事**:把散在 6 份文档里的输入参数 + 代码里已固化的常量**收成一张可复算的表**(每行带**来源等级**与**来源定位**),并填掉 443 单留下的 **45% 口径空位**。
> ⛔ **本表不引入新模型、不引第三方依赖、不做架构改动**;性质是「**固化**」,不是重新推演。
> 🔴 **`待测` 项一个都不许编数** —— 编出来的数会让整张表失去"可复算"的资格。`待测` 行的**值列留空**。
---
## §0 来源等级(四档,⛔ 不许混用)
| 等级 | 含义 | 判据 |
|---|---|---|
| **实测** | 本机 / 远端**命令原文**能复现的数 | 本表给出一条能跑的命令 |
| **推导** | 由表内其它行**按公式算出来**的数 | 本表给出复算式,独立手算能得同值 |
| **估值** | 文档里的行业口径 / 旧记录 / 推演假设 | 指到**文件:行**,且**不得**被当成实测引用 |
| **待测** | 本单取不到真值 | **值留空**,写明"由序⑥ 用 3–5 台真机换掉" |
> ⚠️ 旧记录里两个数**本次已复核判为不可用**(⛔ 别再引用):
> ① 47 规格"1.8 GB / 2 核"(09-08)⇒ 本次实测 **1870 MB / 2 核**(量级巧合,但**必须用实测值复算**);
> ② 跨云带宽 **~22 KB/s**(`覆盖网络_千台全场景推演_20260916.md:38`)⇒ 本次实测**上行下界 ≥ 192 KB/s**(≈ 9×),原值作废。
---
## §1 用法约定(**机器可读** —— 探针脚本靠它取阈值)
- **参数行格式**:``| `KEY` | 值 | 单位 | 等级 | 来源定位 | 复算式 |``
- **观测阈值行格式**:``| `OBS-NN` | 指标 | 阈值(引用键或字面值) | 判据 |``
- ✅ **`scripts/overlay-probe.cjs` 的每一个阈值都从本表读**,⛔ 脚本内**不许有魔数**(判据 = 交接单 §6 **E6**)。
- 探针运行方式(**一条命令**,cwd = 工作区根):
`cd "E:/ProgramData/AIProject/ai1net-dsh-server" && node "D:/github/dsh_shenxian/scripts/overlay-probe.cjs"`
---
## §2 运行坐标
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `SSH_TARGET_47` | `bt-server` | — | 实测 | `~/.ssh/config`(`HostName 47.77.182.89`) | `ssh -p 22 bt-server hostname` |
| `SSH_TARGET_106` | `test106` | — | 实测 | 同上(`HostName 106.54.21.172`) | `ssh -p 22 test106 hostname` |
| `SSH_PORT` | 22 | — | 实测 | 交接单 §2 注(`bt-server` 配置写的 32022 **已陈旧**) | `ssh -p 22 bt-server true` |
| `W47_HOSTNAME` | `iZrj99af19cibck1ge93tqZ` | — | 实测 | 本次 S0 P6 | `ssh -p 22 bt-server hostname` |
| `W106_HOSTNAME` | `VM-0-8-opencloudos` | — | 实测 | 本次 S0 P8 | `ssh -p 22 test106 hostname` |
| `RELAY_STATUS_URL` | `http://127.0.0.1:20080/status` | — | 实测 | relay ExecStart `--port 20080` | `ssh -p 22 bt-server 'curl -s 127.0.0.1:20080/status'` |
| `PORTAL_URL` | `http://127.0.0.1:3080/` | — | 实测 | 平台门户(⚠️ `http2 on` ⇒ **必须 `--http1.1`**,否则假 404) | `ssh -p 22 bt-server 'curl -s --http1.1 -H "Host: ai1net.com" 127.0.0.1:3080/'` |
| `PORTAL_HOST_HEADER` | `ai1net.com` | — | 实测 | 同上 | 同上 |
| `RELAY_NETWORK_ID` | `ops` | — | 实测 | `/etc/dshs.env` `DSHS_OVERLAY_NETWORK_ID` | `ssh -p 22 bt-server 'grep NETWORK_ID /etc/dshs.env'` |
| `SSH_TIMEOUT_MS` | 20000 | ms | 推导 | 探针脚本**单次 ssh** 的超时(⚠️ 放这里是为了让 `overlay-probe.cjs` **零魔数**;实测单次 ssh 往返 ≈ 1 s,取 20× 余量) | — |
> 🆕 **序⑦ 真机演练坐标**(`scripts/overlay-failover-drill.cjs` 用它取数 ⇒ 脚本内**零魔数**)。⚠️ **序㊳ 起 `RELAY_UNIT_NAME` 也被探针 `OBS-08` 读取**(⇒ 已改名、从 `DRILL_` 前缀剥离):
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `RELAY_UNIT_NAME` | `dshs-relay` | — | 实测 | 两台机的 relay 单元名(`systemctl cat dshs-relay`)。🆕 **序㊳ 由 `DRILL_RELAY_UNIT` 改名** —— 它记的是"两台机的 relay 单元名"这一**事实**、被**演练脚本与探针 `OBS-08` 共用** ⇒ 原 `DRILL_` 前缀与用途不相称(⚠️ **旧键名已不再被任何脚本读取**,⛔ 别在脚本里写回旧名) | `ssh -p 22 bt-server systemctl cat dshs-relay` |
| `DRILL_MANAGER_UNIT` | `dshs` | — | 实测 | 47 的 Manager 单元名(演练只读它的 journal 判别器) | 同上 |
| `DRILL_POLL_MS` | 500 | ms | 推导 | 演练轮询 journal 的间隔(≤ `RELAY_FAILOVER_CHECK_MS` 的 5×,保证不漏一次切换)。🆕 **序⑨:2000 → 500** —— 原 2000 让读数带 **0–2000 ms 系统性高估**,使「是否超 `RELAY_FAILOVER_DEADLINE_MS`」**两端都可能误判**(序⑨ RC-4)⇒ 这是**测量修正,不是调参**。⚠️ 改前/改后的读数**不可混比** | — |
| `DRILL_SAMPLE_N` | 5 | 次 | 推导 | 🆕 序⑨:`overlay-failover-drill.cjs --sample` 的**默认采样轮数**(口径 = 每轮都做完整归零:两台 relay `start` → `restart dshs` → 等新的 `AUTH OK host=ops/manager` → 杀入口 → 等新 `[relay-switch]`) | `node scripts/overlay-failover-drill.cjs --sample 5` |
| `DRILL_COOLDOWN_OBSERVE_MS` | 90000 | ms | 推导 | 幕 3 的「冷却期内不回跳」观察窗;⛔ **必须 < `RELAY_FAILOVER_COOLDOWN_MS`**,否则判据不成立 | — |
| `DRILL_SWITCH_MATCH_106` | `106.54.21.172` | — | 实测 | 断言 `[relay-switch]` 的目标是不是 **106**(用**目录里那个 host**,⛔ 不是 ssh 别名 `test106`) | `curl -s https://ai1net.com/dshs-overlay/bootstrap` |
| `DRILL_KILLED_MATCH` | `ai1net.com` | — | 实测 | 幕 1 被杀入口的标识(= 目录 `relays[]` 首位的 host);脚本据此判定「Manager 当前通道**是否就是**将被杀的那台」——不在 ⇒ 记 **SKIP** 而不是假装 PASS | 同上 |
| `DRILL_DETECT_BUDGET_MS` | 120000 | ms | 推导 | 幕 1/幕 2 的**观察窗**;⛔ 必须 ≥ **静默失效检测时延**(半开检测 = `2.5 × HB_SEC`,即 75 s)—— 首轮实测用 1× deadline(30 s) **必然漏判** | — |
---
## §3 输入参数(清单 §三 点名的 **5 类**;散落点收口)
> 散落点计数(P10,⛔ 只计数不重读):`调研_游戏网络特征…:19 处` / `千台全场景推演…:18` / `瓶颈落地方案…:9` / `游戏专项…:7` / `清单…:7` / `百台规模推演…:2` / `骨干层方案…:2` ⇒ **合计 64 处命中,正是"不可复算"的成因**。
### 3.1 设备占比(分层)
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `DEV_L1_PUBLIC_IP` | 10 | 台 | 估值 | `覆盖网络_百台规模推演_20260916.md:53` | — |
| `DEV_L2_HOLE_PUNCHABLE` | 30 | 台 | 估值 | `覆盖网络_百台规模推演_20260916.md:54` | — |
| `DEV_L3_RELAY_ONLY` | 45 | 台 | 估值 | `覆盖网络_百台规模推演_20260916.md:55`(原文区间 40–55,取中值) | `(40 + 55) / 2` |
| `DEV_SHARE_L3` | 0.45 | — | 估值 | `覆盖网络_百台规模推演_20260916.md:119`「L3 占 **45%**(v1 只算 15%)」+`:132` | 45 / 100(口径 = **全网节点数**的比例) |
| `FLEET_TARGET` | 1000 | 台 | 估值 | `覆盖网络_千台全场景推演_20260916.md`(标题与全文口径) | — |
| `FLEET_RELAY_DEMAND` | 450 | 台 | 推导 | 本表 §5 | `FLEET_TARGET × DEV_SHARE_L3` = 1000 × 0.45 |
### 3.2 打洞率
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `HOLE_PUNCH_RATE_INDUSTRY` | 0.90–0.94 | — | 估值 | `覆盖网络_全球架构复盘_20260916.md:38`(业内 UDP 打洞 ≈ 94%) | — |
| `HOLE_PUNCH_RATE_BY_CLASS` | 90% / 40% / 10% | — | 估值 | `方案规划方法_覆盖网络线提炼_20260916.md:45`(三档标注实例) | — |
| `HOLE_PUNCH_RATE_LOCAL` | **分层(n=3 台)**:① 云机×云机(47↔106)—— 均为 L1 直连,**不是打洞场景**;② 国内家宽/办公 NAT(本机)↔ 47 / ↔ 106 —— **对端 → 本机方向 10/10 收包 = 可打洞**;本机 → 云机方向**取不到**(云安全组拦 UDP 入站)。**两对中两对可打洞** | — | **实测** | 本单 §8.4 E6 + `overlay-holepunch.cjs --stun`(⚠️ 首选路径的 47 UDP 观察器**实测不可用**:零收包 ⇒ 降级为**第三方 STUN**,见 §8.4 `OBS-4D`) | 判定式=`对(47,本机)`、`对(106,本机)` 各「至少一个方向成功」⇒ 2/2;⚠️ **样本 n=3 对,云节点占 2/3,不代表家宽场景** |
### 3.3 每玩家带宽
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `PER_PLAYER_BW_TEXT` | 0.5 | KB/s | 估值 | `覆盖网络_调研_游戏网络特征与群聊上限_20260916.md:15` | — |
| `PER_PLAYER_BW_BATTLE` | 2–5 | KB/s | 估值 | 同上 `:15`(另一口径 `:43`「单玩家 2–20 KB/s」) | — |
| `PER_PLAYER_BW_SIEGE` | 10–20 | KB/s | 估值 | 同上 `:15` | — |
| `PER_PLAYER_BW_LOCAL` | **9.8**(n=10 玩家档)/ **3.9**(n=50 玩家档)—— 判据见来源列;聚合天花板 ≈ **200–350 KB/s** | KB/s | **实测** | 本单 §8.4 E7 + `overlay-wan.cjs --players`(合成 200 B 消息 × 5/20/50 msg/s × 10/50 玩家 × 60 s,经 relay 真机间) | 判据=`p95 ≤ 2×p50 且丢包 = 0`;⚠️ 原始丢包含**尾部在途**伪影(≈`rate × RTT`);扣掉后满足的**最高档 = 10 玩家 @50 msg/s = 9.77 KB/s/玩家**(50 玩家档最高 3.91)。**边界**:本值 = **传输层上限**;游戏协议的真实需求仍是估值(`PER_PLAYER_BW_TEXT/BATTLE/SIEGE`)——⛔ 不得读成"实测出的游戏需求" |
### 3.4 消息频率 / 扇出
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `MSG_RATE_GLOBAL` | 50 | msg/s | 估值 | `覆盖网络_千台全场景推演_20260916.md:75` | — |
| `FANOUT_AVG` | 50 | — | 估值 | 同上 `:75`("均扇出 50") | `50 × 50 = 2500 投递/秒` |
| `ROOM_FANOUT_LIMIT_100` | 100 | 人 | 估值 | `覆盖网络_调研_游戏网络特征与群聊上限_20260916.md:97`(≤100 纯扇出即可) | — |
| `PRESENCE_FANOUT_1000ROOM` | 16700 | 次/秒 | **推导** | `覆盖网络_千台全场景推演_20260916.md:79` | 千人房 presence ≈ 1000 × 16.7 次/秒(原文直接给 16,700) |
### 3.5 心跳 / 链路质量
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `HB_SEC` | 15 | s | **实测**(代码常量=运行真值) | `src/net/relay/server.ts:60` `DEFAULT_HB_SEC = 15`;运行期由 `HELLO_ACK` 下发(`server.ts:816`) | `grep -n DEFAULT_HB_SEC src/net/relay/server.ts` |
| `HB_SEC_DOC` | 20 | s | 估值(**文档口径,与代码不一致**) | `覆盖网络_千台全场景推演_20260916.md:39`「心跳间隔 20 s(沿用现有隧道自愈定时器)」 | ⚠️ 该行属**旧隧道时代**口径 —— 现已换 relay(`hbSec=15`)⇒ **文档待更正**,参数以 `HB_SEC=15` 为准 |
| `HALF_OPEN_MS` | 37500 | ms | 推导 | `src/net/relay/client.ts:602` | `max(3000, HB_SEC × 1000 × 2.5)` = 15 × 1000 × 2.5 |
| `IDLE_TIMEOUT_MS` | 45000 | ms | 实测(代码常量) | `src/net/relay/server.ts:58` | `grep -n DEFAULT_IDLE_TIMEOUT_MS src/net/relay/server.ts` |
| `JITTER_LIMIT_MS` | 20 | ms | 估值(业界口径) | `覆盖网络_调研_游戏网络特征与群聊上限_20260916.md:32`+`覆盖网络_千台全场景推演_20260916.md:90` | — ⚠️ **序㉖ 起该键语义扩展**:既当"达标限值"(序⑥ 原义),又当**"抖动劣化即切"的阈值**(`p95(|ΔRTT|) ≥ 它` ⇒ 允许换到更稳候选)。🔴 **⛔ 不新造 `JITTER_SWITCH_MS` 同值键** —— "超标"与"该换路"是**同一件事**,两个键 = 同一事实两处写(本线知识碎片化教训)。⚠️ 值 `20` 仍是**业界估值**(非实测),实测定值属待测项 |
| `JITTER_LINK_MEASURED` | **3**(判定值 = `p95(|ΔRTT|)`;另记 `mdev` = 1.37–1.38 ms、`avg` = 148.3 ms、丢包 1.3–1.7%) | ms | **实测** | 本单 §8.4 E5 + `overlay-jitter.cjs --icmp`(47→106 与 106→47 各 300 包 × 0.2 s) | **达标**:3 ms < `JITTER_LIMIT_MS`(20 ms,⚠️ 该限值是**业界估值**口径)。⚠️ 这条**推翻了**"当前跨云链路上 jitter 不可能达标"的旧定性结论 —— 旧结论是拿 `RELAY_RTT_W106` 当链路 RTT 推的(口径错,见下行) |
| `RELAY_RTT_W106` | 336 → **保留数值,但加口径修正**:该值是**心跳往返**(`server.ts:810`:测 RTT / 察觉半开 / 保 NAT 表项),**含应用层处理与验签,⛔ ≠ 网络 RTT** | ms | **实测(口径受限)** | 本单 §8.4 E5(b) 三方对比:ICMP 148 ms | TCP 握手 median 153 ms(min 138.5)| relay 心跳 344–376 ms ⇒ **差 2.3×,触发"加口径备注"判据** | 判据(S3(b)):`relay rttMs` 与 ICMP 差距 > 2× ⇒ 加备注 + 在 `OBS` 侧登记"relay `rttMs` 不得当链路 RTT 用" |
| `RELAY_RTT_MANAGER` | 7 | ms | **实测** | `/status` → `sessions[hostId=manager].rttMs` | 同上 |
| `RELAY_TTFB_S` | 0.68 | s | **实测** | 本次 S0 P7a:47 → relay 回环口 → w-106 任一端口,`%{time_starttransfer}`(6 次样本 0.676–1.083) | `ssh -p 22 bt-server 'curl -s --http1.1 -o /dev/null -w "%{time_starttransfer}\n" http://127.0.0.1:44133/'` |
| `WAN_UP_BOUND_KBPS` | 192 ⇒ **已被上行实测取代(值作废,保留作历史)** | KB/s | ~~实测(下界)~~ **作废** | 原 S0 P7 样本被 405 提前截断(131072 B / 0.685 s,含 RTT)⇒ 只是**下界**,且下界偏低 **64×** | 用 `WAN_STEADY_THROUGHPUT` 的上行值 12213 作准 |
| `WAN_STEADY_THROUGHPUT` | **352(106→47)/ 12213(47→106)** —— 取**绑定方向**(对端→中继机,即"内容从 worker 上来"那条)= **352** | KB/s | **实测** | 本单 §8.4 E4 + `overlay-wan.cjs --download|--upload`(各 5 样本) | **口径三要素**:① 谁到谁 = 106 的 `w-106p:19777` ↔ **47 本机 relay 回环端点**;② **经 relay**(非裸链路);③ 稳态段 **30 s**(弃前 3 s,爬升段字节单独计)。**中位数 352**(min 345.6 / max 352);上行侧 12213(min 11784 / max 12452)。⚠️ 非对称的成因 = 106(云轻量)**公网出带宽封顶 ≈ 2.8 Mbps**,实测三向互证:`/status` 会话计数 `in=1.905 GB` + 106 侧进程 `rchar=1.905 GB` + 下载恒定 165×64 KB/30 s |
| `WAN_UP_BOUND_KBPS` | 192 ⇒ **已被上行实测取代(值作废,保留作历史)** | KB/s | ~~实测(下界)~~ **作废** | 原 S0 P7 样本被 405 提前截断(131072 B / 0.685 s,含 RTT)⇒ 只是**下界**,且下界偏低 **64×** | 用 `WAN_STEADY_THROUGHPUT` 的上行值 12213 作准 |
**序㉖ 新增:抖动观测与容量余量键(`OBS-19` / `OBS-20` 的**唯一**取数来源 —— 代码侧 `src/net/relay/jitter.ts` `jitterThresholds()`,⛔ 模块内零魔数)**
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `JITTER_ENABLE` | 1 | — | 推导 | 序㉖:总开关(`0` ⇒ 采样与排序**全部失效**、逐字回到改造前行为)—— 探针拿它当"装了但没生效"的判别器 | `src/net/relay/jitter.ts` `jitterThresholds()` |
| `JITTER_SAMPLE_MAX` | 32 | 个 | 推导 | 序㉖:每个 url 保留的 RTT 样本上限(环状丢弃最老)。32 × 心跳 15 s ≈ **8 min 滑窗** | 同上 |
| `JITTER_MIN_SAMPLES` | 3 | 个 | 推导 | 序㉖:参与排序 / 劣化判定所需的**最小 `\|ΔRTT\|` 样本数**。不足 ⇒ 该 url 判为**未知**(⛔ 不当 0 用 —— 否则"没测过"会被误排成"最稳") | 同上 |
| `JITTER_HIST_MAX_MS` | 200 | ms | 推导 | 序㉖:直方图上界(`≥ 它` 落末桶)。取 `10 × JITTER_LIMIT_MS` ⇒ 超标样本全落末桶仍可计数 | `JITTER_LIMIT_MS × 10` = 20 × 10 |
| `JITTER_HIST_BUCKETS` | 8 | 个 | 推导 | 序㉖:直方图桶数(**固定值**;探针 `OBS-19` 拿它校验"口径一致",⛔ 不是实现细节) | 同上 |
| `JITTER_SAMPLE_GAP_MS` | 20000 | ms | 推导 | 🔴 序㉖:**同一份缓存读数的最小采样间隔** —— 必须 **> 心跳周期 `HB_SEC`(15 s)**,否则同一个 RTT 值被反复记录 ⇒ 差分恒 0 ⇒ **jitter 被系统性低估到 0**(判据**假绿**)。⚠️ relay 侧(`server.ts`)不用它:它在 `PONG` 到达那一刻采样,本来就是新测量 | `HB_SEC × 1000 + 5 s` = 15000 + 5000 |
| `RELAY_UTIL_MAX_PCT` | 70 | % | 推导 | 序㉖ · `E4`:**利用率软门** —— `used / max × 100 ≥ 它` ⇒ 拒绝**新**接入(⛔ **不驱逐任何在线节点**)。与"骨干留 30%+ 余量"(B 档口径)对齐。⚠️ `0` ⇒ 软门关闭(逐字回到改造前:只有硬容量门 `at-capacity`)。🔴 与 `refused`(硬门)**分开计**为 `utilRefused` —— 混计就分不清"真装满了(要扩容)"与"为保余量提前拦(按设计工作)" | `100 − 30`(余量口径 = B 档 30%+) |
### 3.6 presence(节点在线态 · 序⑲)—— **帧率类**参数
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `PRESENCE_BATCH_MS` | 1000 | ms | **实测**(代码常量=运行真值) | `src/net/relay/server.ts:106` `DEFAULT_PRESENCE_BATCH_MS = 1_000` | `grep -n DEFAULT_PRESENCE_BATCH_MS src/net/relay/server.ts` |
| `PRESENCE_GRACE_MS` | 10000 | ms | **实测**(代码常量=运行真值) | `src/net/relay/server.ts:89` `DEFAULT_PRESENCE_GRACE_MS = 10_000` | `grep -n DEFAULT_PRESENCE_GRACE_MS src/net/relay/server.ts` |
| `PRESENCE_OFFLINE_DEBOUNCE_MS` | 30000 | ms | **实测**(代码常量=运行真值) | `src/net/relay/server.ts:97` `DEFAULT_PRESENCE_OFFLINE_DEBOUNCE_MS = 30_000` | `grep -n DEFAULT_PRESENCE_OFFLINE_DEBOUNCE_MS src/net/relay/server.ts` |
| `PRESENCE_TTL_MS` | 45000 | ms | 推导(代码常量 × 心跳) | 服务端 `src/net/relay/server.ts:604`;客户端保守档 `src/net/relay/client.ts:329` `DEFAULT_PRESENCE_TTL_MS = 45_000`(两处同值,实测一致) | `HB_SEC × 1000 × DEFAULT_PRESENCE_TTL_FACTOR` = 15 × 1000 × 3(因子 = 3 见 `server.ts:115`) |
| `PRESENCE_SUB_MAX` | 0 | 个 | **实测**(代码常量;**0 = 不限**) | `src/net/relay/server.ts:122` `DEFAULT_PRESENCE_SUB_MAX = 0` | `grep -n DEFAULT_PRESENCE_SUB_MAX src/net/relay/server.ts` |
| `PRESENCE_GATE_WINDOW_MS` | 60000 | ms | 推导(= 12 × 轮询周期) | **序㉑**:`OBS-16` 的观测窗口 —— 必须 **≥ 4 × 轮询周期**,否则"轮询还在跑"与"轮询停了"在计数上**分不开** | `RELAY_STATUS_POLL_MS × 12` = 5000 × 12(轮询周期 = 代码常量 `src/web/server.ts:76`) |
| `PRESENCE_GATE_HITS_MAX` | 1 | 次 | 推导 | **序㉑**:门窗口内 `/status` 命中增量上限 = **探针自身那一次读数**(窗口 = (第二次采样, 第三次采样])⇒ 多出来的每一次都算"Manager 还在拉" | 探针一次采样 = 1 次读 |
| `CONTENT_BLOCK_SIZE` | 1048576 | B | 推导 | **序㉔**:块级切分的块大小 —— 单份首屏合并脚本 11,363,655 B(S0 P1)⇒ 约 11 块/份;取 2 的幂便于手算复现。**代码常量** `src/net/relay/content/chunker.ts` `DEFAULT_BLOCK_SIZE` | `1024 × 1024` |
| `CONTENT_STORE_MAX_BYTES` | 67108864 | B | 推导 | **序㉔**:块缓存上限 —— S0 P5:`MEM_BUDGET_MB = 1002`,块缓存**另立预算**(⛔ 不挤 relay 的 `MEM_PER_HOST_MB`),取 ≈ 6.4%;够整份装下 6 份 10.8 MB 包。env `CONTENT_STORE_MAX_BYTES` 可覆盖(**relay 侧另有** `DSHS_CONTENT_STORE_MAX_BYTES`,见下条);**代码缺省** `store.ts` `DEFAULT_MAX_BYTES` | `64 × 1024 × 1024` |
| `CONTENT_GROUP` | local | — | 实测 | **序㉔**:本节点在内容面上的**组名**(分组隔离 E5 的维度)—— 同 (网, 组) 才允许互相取块。env `CONTENT_GROUP` 可覆盖(**relay 侧另有** `DSHS_CONTENT_GROUP`,见下) | 装配层缺省 `'local'`(`src/web/server.ts`) |
| `CONTENT_TIER_ORDER` | local,peer,edge,region,origin | — | 推导 | **序㉔**:内容源优先级链(照抄 SCCM / DO 口径,交接单 §7)—— ⛔ 顺序是硬约束:越靠前越省带宽 | `chunker` → `source.ts` `DEFAULT_TIER_ORDER` |
| `CONTENT_TIER_HITS_MIN` | 1 | 次 | 推导 | **序㉔ · `OBS-17`**:`local` + `peer` 两档**命中计数之和**的下界 —— 同组内 ≥ 1 次"不回源即取到"才算内容寻址**真的在工作**(0 ⇒ 全是回源 ⇒ E1 未达成) | 探针两次 `/status` 的计数增量 |
| `DSHS_CONTENT_GROUP` | `relay` | — | 实测 | **序㉔ · relay 侧组名**(`src/net/relay/main.ts`)—— ⚠️ 与平台侧 `CONTENT_GROUP` **同名不同进程**:relay 是独立单元、读不了平台的 env ⇒ 双前缀。生产值在 drop-in `dshs-relay.service.d/40-content-probe.conf`(取 `relay`,与平台侧 `local` **刻意分开** ⇒ 避免"两个进程误以为在同一组") | 缺省字面量 `'local'`(`main.ts`) |
| `DSHS_CONTENT_STORE_MAX_BYTES` | 未设 | B | — | **序㉔ · relay 侧块缓存上限**(`src/net/relay/main.ts`);未设 ⇒ 走代码缺省 `DEFAULT_MAX_BYTES`(64 MiB),与平台侧同值 | 缺省 = `store.ts` `DEFAULT_MAX_BYTES` |
| `DSHS_CONTENT_PROBE_BLOCK` | `seq24-content-probe-block` | — | 推导 | **序㉔ · 活性自证**:relay 启动时把该文本当**一个真实块**写入内容寻址存储并立即走优先级链读回 ⇒ `local` 档命中 **+1**。⛔ **不是凑绿**:块确实在存储里、确实被读出(`store.hits` 同步 +1),后续同 id 请求真能命中。🔴 **它正是 `OBS-17` 判据③的输入源** —— 缺它则 relay 侧命中恒 0 ⇒ 活性判据必红(服务行为本身不变) | 无(⛔ 不设 = 不投探针块);生产 drop-in = `40-content-probe.conf` |
| `DSHS_REHYDRATE_STAGGER_MS` | 500 | ms | 推导 | **序㉕**:「逐步拉起」**逐条认领之间**的最小时间差 —— 防"重启后 N 个既有实例被同时处置"的启动风暴(E2 的可测阈值)。🔴 **代码缺省,⛔ 47/106 的 env 里未设**(D1 自证:`grep -c REHYDRATE /etc/dshs*.env` = 0)|代码位置 `src/supervisor/orchestrator.ts` `rehydrateStaggerMs()` | `500`(代码缺省) |
| `DSHS_REHYDRATE_PROBE_MS` | 2000 | ms | 推导 | **序㉕**:单条实例的 **TCP 探活**超时。🔴 **判据是"端口不通",⛔ 不是"启动了却不认识"** —— 端口在听 ⇒ 判**有效实例**、保留(与重启前稳态一致);连不上 ⇒ 才是档案 30 说的**孤儿** ⇒ 按旧行为停掉。写成后者会把好实例一起杀掉。|代码位置 `rehydrateProbeMs()` / `probeAdopted()` | `2000`(代码缺省) |
> ⚠️ 这五个键只驱动**在线态事件**(谁在线 / 何时改口),⛔ 与 `RELAY_FAILOVER_*` / `HB_SEC` / burst **无耦合** —— 序⑲ 全程一字未动(E11 的 D1 自证)。
> ⚠️ `PRESENCE_TTL_MS` 是**安全网**(兜"漏掉 close 事件"那条路),⛔ **不是常规下线路径**:常规下线走 `PRESENCE_GRACE_MS + PRESENCE_OFFLINE_DEBOUNCE_MS` = 40 s。
> 🔴 **序㉑(P-1 修复)口径变更**:`PRESENCE_TTL_MS` **不再当"订阅新鲜度"用** —— 客户端的门(`presenceFresh()`)判据改成「**订阅已建立 ∧ 链路活着**」(任何入站帧,含心跳),上界 = `max(本 TTL, 半开阈值)`。理由:presence 是**变化驱动**的,稳态零帧是**设计属性**,拿"载荷年龄"当判据会把"**没有变化**"读成"**没有数据**"(实测降幅因此只有 **1.10×**)。本键**值未改**,只是判据收窄为"入站静默上界"的一员。
> ⚠️ 口径来源 = D3(最终一致:允许 5–15 s 陈旧),⛔ 三个阈值均按代码常量取,**不许改口径去凑判据**(E5)。
> 🔴 **值格必须纯数字**(夹注 / 混写单位 ⇒ 探针 `NaN` ⇒ 假红,序⑫ 已踩)。
### 3.7 候选链观测(序㉗)—— **E3「路径多样性」的可断言面**
> 🔑 **立项依据** = 序㉖ §8.1-⑦ 第 ④ 条登记的「**E3 未取得机器断言面**」;**用户拍板** 2026-09-17 23:1x「**2、B**」= 允许改 `src/worker/relay-tunnel.ts` / `src/web/server.ts` 以暴露候选数。
> 🔴 **观测行格式(探针判据的锚点,⛔ 改它 = 破坏判据)**:`[overlay-candidates] scope=<s> resolves=<n> count=<n> hosts=<n> source=<s> detail=<s> urls=<u|u>`
> 🔴 **值格必须纯数字**(`CAND_MIN` / `RELAY_CAND_OBS_MS`);两个 unit 列表是**字符串**、逗号分隔、⛔ 不许带空格。
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `CAND_OBS_PREFIX` | `[overlay-candidates]` | — | 实测 | 代码仓 `src/worker/relay-tunnel.ts#CAND_OBS_PREFIX`(**全仓唯一写死处**,⛔ 探针侧不硬编码) | `grep -n "CAND_OBS_PREFIX = " src/worker/relay-tunnel.ts` |
| `CAND_MIN` | 2 | 条 | 实测 | 🔴 **用户口径**「**每连接候选数 ≥ 2**」(2026-09-17 23:1x「2、B」)= `OBS-21` 的判据阈值 | 本行即真值 ⇒ ⛔ **不来自节点数推导**(改了它 = 改用户口径) |
| `RELAY_CAND_OBS_MS` | 300000 | ms | 推导 | 序㉗:观测行**周期重发**间隔(⛔ **只重发上次快照、零网络 I/O**);`0` ⇒ 不重发。取 300 s(与 `DEFAULT_DIRECTORY_REFRESH_SECONDS` 同量级 —— 观测粒度不必细于目录刷新) | `src/worker/relay-tunnel.ts#candidateObsMs()` 默认值 |
| `CAND_OBS_UNITS_47` | `dshs=manager` | — | 实测 | 47 上**唯一**的 relay 客户端单元(C1 Manager)。⚠️ 格式 `unit=scope`:探针**按单元读 journalctl、再核对 `scope` 与这里一致**(⛔ 不一致即 FAIL —— 那是接线错,不是观测错)。🔴 **`dshs-worker` ⛔ 不在列**:47 的 worker 没有 `DSHS_RENDEZVOUS_URL`(`grep -c` = **0**)⇒ 它**根本不建 `RelayTunnel`**、不是 relay 客户端(`w-47` 的跨机面走 Manager 本机 `LocalRendezvous`)⇒ 列进去会读到"永远无观测行" = **假红**(本序真机首轮已踩到并修正) | `ssh -p 22 bt-server "grep -c DSHS_RENDEZVOUS_URL /etc/dshs-worker.env"`(期望 **0**) |
| `CAND_OBS_UNITS_106` | `dshs-worker=worker` | — | 实测 | 106 上**一个** relay 客户端单元及其 `scope`(worker `w-106`) | `ssh -p 22 test106 "systemctl cat dshs-worker \| grep -c DSHS_OVERLAY"` |
> ⚠️ `CAND_OBS_UNITS_*` 只列**该机实际在跑的 relay 客户端单元** —— 多列一个不存在的单元 ⇒ 探针读到"该路径无观测行"⇒ **假红**;少列一个 ⇒ **漏判一条连接路径**(⛔ 两种都是判据污染)。
---
### 3.8 退出路径守卫(序㉘ → 单 A)—— **「退出不停实例」的可断言面**
> 🔑 **立项依据** = 序㉘ 单 A(候选 `B`,用户 2026-09-17 23:3x「**选 B**」):三处 `teardown()` 一起改 ⇒ 退出进程不再停实例。
> 🔴 **本节的 6 个键只被探针读取** —— ⛔ **不写进任何 systemd drop-in / env**(本单无新生产键)。
> 🔴 **判据锚点** = **部署产物** `lib/supervisor/{orchestrator,remote-spawner,leased-spawner}.js` 里三处 `teardown()` 的 **8 行窗口**(≡ `grep -A7 "async teardown"`)—— ⛔ 改守卫标记 = 破坏判据。
> 🔴 **禁词格的值必须逗号分隔、⛔ 不许出现 `|`**(`|` 是表格列分隔符,写进去会把整张表切开)。
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `TEARDOWN_GUARD_MARKER` | `guard: teardown-must-not-stop-instances` | — | 实测 | 三处 `teardown()` 体内的守卫标记(探针侧 ⛔ 不硬编码) | `grep -c "guard: teardown-must-not-stop-instances" src/supervisor/orchestrator.ts src/supervisor/remote-spawner.ts src/supervisor/leased-spawner.ts` ⇒ **3 处各 1** |
| `TEARDOWN_FORBIDDEN_TOKENS` | `this.stop(,inner.stop(,killInstance(,/stop` | — | 实测 | `OBS-22` 的禁词表(**字面量**、⛔ 非正则 —— 避免 `|()` 与列分隔符冲突);判据 = teardown 窗口内**任一字面量命中即 FAIL** | `node --test test/orchestrator-teardown.test.mjs` ⇒ `T5`(逐文件点名)+ `T9`(判别器自证) |
| `TEARDOWN_LIB_47` | `/opt/dshs/lib/supervisor` | — | 实测 | 47 上**真正在跑**的 supervisor 产物目录(`dshs` 与 `dshs-worker` 同用) | `ssh -p 22 bt-server "systemctl cat dshs-worker \| grep -o '/opt/dshs/lib'"` |
| `TEARDOWN_LIB_106` | `/opt/dshs-cluster/lib/supervisor` | — | 实测 | 106 上**真正在跑**的 supervisor 产物目录 | `ssh -p 22 test106 "systemctl cat dshs-worker \| grep -o '/opt/dshs-cluster/lib'"` |
| `TEARDOWN_UNIT_47` | `dshs-worker` | — | 实测 | 47 上**唯一**会执行 `LocalSpawner.teardown()` 的单元。🔴 **`dshs` ⛔ 不在列**:47 是 `DSHS_DEPLOY_MODE=cluster` ⇒ Manager 构造的是 `LeasedSpawner(RemoteSpawner)`、**根本不构造 `LocalSpawner`**(因此不打 `[rehydrate]`、其 `teardown()` 在 47 上是死代码)—— 2026-09-18 序㉚ 实测:`journalctl -u dshs \| grep -c '[rehydrate]'` = **0**(全量历史) | `ssh -p 22 bt-server "systemctl show dshs -p Environment \| grep -c DEPLOY_MODE=cluster"`(期望 **1**) |
| `TEARDOWN_UNIT_106` | `dshs-worker` | — | 实测 | 106 上唯一会执行 `LocalSpawner.teardown()` 的单元 | `ssh -p 22 test106 "journalctl -u dshs-worker --no-pager \| grep -c '\[rehydrate\]'"`(期望 **>0**) |
> 🔴 **口径(2026-09-18 序㉚ 实测修正上游假设)**:「**同一语义三份实现,逐台断言**」的执行点是**两台 `dshs-worker`**,⛔ **不是** `dshs`(Manager)—— Manager 在 cluster 形态下走 `RemoteSpawner`,远端实例的寿命从来不受 Manager 退出影响。⇒ 判「退出路径不杀实例」**必须逐台 worker 判**。
### 3.9 组密钥加密(序㉘ → 单 B)—— **「中继看不到明文载荷」的可断言面**
> 🔑 **立项依据** = 用户 2026-09-17 23:2x 拍板「**C 组密钥加密**」(同组共享一把对称密钥 ⇒ 组内密文一致 ⇒ 按哈希共享块**不退化**,同时中继读不到明文)。产物 = `交接单_组密钥加密_20260918.md`(§8 前前缀 `149596460288ca1a5b7abddc490f7909`)。
> 🔴 **算法栈 = AES-256-GCM,与 `src/crypto.ts` 同源**(⛔ **不引第二栈**);**确定性**做法 = `iv = HMAC(key,"iv"‖明文) 前 12 字节`、`k = HMAC(key,"k"‖iv)`、`AAD = "<network>|<group>|<epoch>"`(⇒ 跨组 / 跨 epoch 在**认证阶段**就被拒)。
> 🔴 **块 id 口径 = β′(密文哈希)**:`blockId = sha256(密文) 前 32 hex` —— 单内 §7.3 已判 `α`(明文哈希)「只有缺点 ⇒ 自己拍掉」。
> 🔴 **密钥本体 ⛔ 不经网络、⛔ 不经 relay** —— 只走既有 `0600` 落文件 + `scp` / drop-in 通道;中继侧只广播 `(group, epoch, keyId)` **三元组**(载荷内**不含密钥**)。
> 🔴 **值格必须纯数字**;`CONTENT_KEY_FILE` 是**路径字符串**(探针按字符串读,⛔ 不参与数值比较)—— 凡"模式名 / 路径"性质的一律**只作信息键**。
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `CONTENT_KEY_FILE` | `/etc/dshs/content-group-key.json` | — | 实测 | 组密钥落点(两机同名;与节点密钥同目录同权限惯例)。⚠️ 启用由 **env 显式指定**:relay 侧 `DSHS_CONTENT_GROUP_KEY_FILE`、平台侧 `CONTENT_GROUP_KEY_FILE`(⛔ 不设 ⇒ **不启用**,行为逐字回到序㉔) | `ssh -p 22 bt-server "ls -l /etc/dshs/content-group-key.json"`(须 `-rw------- root root`) |
| `CONTENT_KEY_FILE_MODE` | 600 | — | 实测 | 🔴 密钥文件**必须**的权限(`stat -c %a` 原文比对,非"至少")。本线史上有"属主/权限被改 ⇒ 实例全崩"的先例(单 B §10 未验证项 4) | `ssh -p 22 bt-server "stat -c %a /etc/dshs/content-group-key.json"`(期望 `600`) |
| `CONTENT_EPOCH_GRACE_MS` | 86400000 | ms | 实测 | **双 epoch 过渡窗口上界** = 24 h。超窗口的历史 epoch **只解不写 → 不再解**(`epochExpired +1` 并点名)。env:`DSHS_CONTENT_EPOCH_GRACE_MS` / `CONTENT_EPOCH_GRACE_MS`(⛔ 生产未设 ⇒ 用本值 = 代码缺省 `crypto.ts#DEFAULT_EPOCH_GRACE_MS`)。🔬 **序㉛ 实测三档混存夹具(20 块 = 写 epoch 4 + 历史 1/2/3,`retiredAt` 错开在 T0 / T0+2h / T0+12h;时钟注入 `now`)** ⇒ 旧密文可解比例是**阶梯衰减**(不是一刀切),**全程归零时刻 = 最晚 `retiredAt` + 本值**:**小档 60,000 ms ⇒ 24 h 归零**|**本档 86,400,000 ms ⇒ 48 h 归零**|**大档 604,800,000 ms(7 d)⇒ 8 d 归零**;窗口内 `epochExpired=0`,超窗后逐 epoch 点名且 `decryptRejected` 同步 +1。⇒ 选值口径 = **"轮换后还允许旧密文被解多久"**:太短 ⇒ 尚未换完钥的节点掉线;太长 ⇒ 旧密钥留太久(安全)。⚠️ 轮换时点必须约在**发布窗口**(单 §9-8) | `grep -n "DEFAULT_EPOCH_GRACE_MS = " src/net/relay/content/crypto.ts`(数值=代码缺省;曲线=`_tmp_seq32/p04-epoch.txt`) |
| `CONTENT_CRYPTO_DET_MIN` | 1 | 次 | 实测 | `OBS-23` 判据② 的 **F1** 阈值:启动自证至少完成 **1** 次"同明文两次加密 ⇒ 比字节"(`detChecks ≥ 本值` 且 `detMismatches = 0`) | 本行即真值(⛔ 不来自推导) |
| `CONTENT_CRYPTO_DECRYPT_MIN` | 1 | 次 | 实测 | `OBS-23` 判据② 的 **F2** 阈值:至少 **1** 次"加密 → 入库 → 经优先级链取回 → 解密"闭环成功(`decrypts ≥ 本值` 且 `decryptRejected = 0`) | 本行即真值(⛔ 不来自推导) |
| `CONTENT_CRYPTO_SCAN_MIN` | 1 | 次 | 实测 | `OBS-23` 判据③ 的 **F4①** 阈值:至少 **1** 次**字节级**明文标记扫描(`plainScans ≥ 本值` 且 `plainLeaks = 0`)。⚠️ 只判 ①("没扫到")是假绿风险 ⇒ 反向证明在夹具 OFF 段(`--content-crypto-fixture`)—— 那条腿**必须**命中 ≥ 1 | 本行即真值(⛔ 不来自推导) |
> 🔴 **本节的 6 个键只被探针读取** —— `CONTENT_KEY_FILE` / `CONTENT_KEY_FILE_MODE` / `CONTENT_CRYPTO_*` **⛔ 不写进任何 drop-in / env**;只有 `DSHS_CONTENT_GROUP_KEY_FILE`(relay)与 `DSHS_CONTENT_EPOCH_GRACE_MS`(可选)是**生产 env**(后者未设 ⇒ 用代码缺省)。
> ⚠️ **`OBS-23` 的对抗面(⛔ 别用它们凑绿)**:`epochExpired` 稳态须 **0**;`decryptRejected` 稳态须 **0**(持续增长 ⇒ 命中单 B §9-6 回头条件,**停下报告**,⛔ 不许"重启一次看看")。
---
### 3.10 节点一键加入与分组准入(序㊱ · P1)—— **「一条命令接入 + 白名单派生」的可断言面**
> 🔑 **立项依据** = 用户 2026-09-18 09:07 原话「**b 要实现开启一台结点服务器,就能连上覆盖网络**,这样做这个网络才有价值,**结点可以设置分组只允许那些结点加入**,既可以成为大覆盖网络也可以建立独立覆盖网络」。产物 = `交接单_节点一键加入与分组准入_20260918.md`(归档号 **131**;§8 前前缀 `085e28b56a08aa1f01999bdb3e238911`)。
> 🔴 **本节新增 **0** 个生产 env** —— 下面 8 个键**只被探针 / 控制面 CLI 读取**,⛔ **不写进任何 drop-in / env**(与 §3.8 / §3.9 同纪律)。🔴 **P1 的硬门 = 零新增暴露面**:⛔ 不改 nft·nginx、⛔ 不开新监听口、⛔ 不动既有 `50-overlay-dialers.conf`。
> 🔴 **手写 drop-in 是应急通道,⛔ 不删** —— `NODES_EMERGENCY_DROPIN` 那一行就是它;派生(`derive --apply`)只**多一条**产生白名单的路,⛔ 不取代它。
> 🔴 **值格必须纯数字或纯路径**(夹注 ⇒ `NaN` ⇒ 假红);⛔ 值内不得含 `|`、`*`、反引号。
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `NODES_REGISTRY_DIR` | `/var/lib/dshs/overlay` | — | 实测 | 控制面**权威注册表**的目录(用户既有口径「归属 / 租约 / 骨干资格只能控制面写」)。⚠️ 属**状态**、⛔ 不是配置 ⇒ 放 `/var/lib/dshs/`(与 `users/` 同族),⛔ 不放 `/etc/` | `ssh -p 22 bt-server "ls -ld /var/lib/dshs/overlay"` |
| `NODES_REGISTRY_FILE` | `/var/lib/dshs/overlay/nodes.json` | — | 实测 | 注册表本体(键 = **逻辑名** `<network>/<hostId>`,与 relay 会话表同口径)。⛔ **只有控制面写**(双写 = 脑裂)。🔴 控制面 CLI 覆盖本路径的键 = **`--registry`**(⛔ **不是 `--file`** —— 那是 `apply` 的申请单入参) | `ssh -p 22 bt-server "cat /var/lib/dshs/overlay/nodes.json"` |
| `NODES_CONSUMED_DIR` | `/var/lib/dshs/overlay/consumed` | — | 实测 | **一次性台账**目录(每张邀请一个 `<nonce>.used`)。🔴 占位走 `O_CREAT\|O_EXCL`(内核原子)⇒ 两台机器同时用同一张邀请**恰好一台**成功(`JOIN` 的 `J1` 判据) | `ssh -p 22 bt-server "ls /var/lib/dshs/overlay/consumed \| wc -l"` |
| `NODES_SELFCHECK_FILE` | `/var/lib/dshs/overlay/selfcheck.json` | — | 实测 | `overlay-node-admit.cjs selfcheck --json --out <本路径>` 的读数(`OBS-25` 的数据源)。⛔ 由**人/棒显式跑**,探针只读 ⇒ **无副作用**(⛔ 探针不代跑自检) | `node scripts/overlay-node-admit.cjs selfcheck --json` |
| `NODES_EMERGENCY_DROPIN` | `/etc/systemd/system/dshs-relay.service.d/50-overlay-dialers.conf` | — | 实测 | 🔴 **手写白名单 = 应急通道**(控制面不可用时照旧生效)。派生落盘写**同路径同格式**文件 ⇒ 两者可互相替换、语义等价(⛔ 不删这一个) | `ssh -p 22 bt-server "ls -l /etc/systemd/system/dshs-relay.service.d/"` |
| `INVITE_TTL_MIN` | 30 | min | 实测(= CLI 缺省) | 邀请凭据的有效期缺省。短窗 ⇒ 凭据泄露窗口小;⚠️ 要覆盖「签发 → 节点开机 → 跑 join」的全过程 | 本行即真值(⛔ 不来自推导) |
| `JOIN_STEPS_MIN` | 4 | 步 | 实测 | `OBS-25` 判据①:`join` 编排的**四步**必须全在且全绿(`verify-invite` → `node-key` → `local-config` → `register`)。⛔ 少于 4 = 有一步被静默跳过 | `grep -c "verify-invite\|node-key\|local-config\|register" src/net/relay/join.ts` |
| `JOIN_NAMED_FAILURES_MIN` | 4 | 条 | 实测 | `OBS-25` 判据④:`selfcheck` 的**具名负腿**条数下限(坏签名 / 过期 / 错网 / 无受信签名者;实际 **5** 条 = 再含"没给通道") | `node scripts/overlay-node-admit.cjs selfcheck --json \| grep -c '"leg"'` |
> ⚠️ **一条必须写明的口径**:`NODES_REGISTRY_FILE` **不存在 ⇒ `OBS-24` 取 SKIP + 留痕**("这套准入还没开始用"是**合法状态**);而**存在却读不出来 / 解析不了 ⇒ FAIL 并点名**。两者不可混 —— 本线「"没装"与"没采到"必须可分」的又一次落地。
> ⚠️ **`OBS-25` 的私钥权限腿只在 Linux 可判**:Windows 的 `stat.mode` 不支持 Unix 权限(恒 `666`/`444`)⇒ 读数里 `nodeKeyModeOk: null` = **本平台不可判**,探针**留痕但不计绿**,⛔ 不许当 PASS;真机腿 = 在 47 上跑 selfcheck 取 `600`。
> ⚠️ **一条真机实测教训(序㊱ 首轮 · 键名混用)**:控制面 CLI 的 `--file` 已被 `apply` 占用(申请单入参),⛔ **不能兼作注册表文件的覆盖键** —— 否则 `apply --file <申请单>` 会把**注册表文件也指到申请单** ⇒ 收单在第 4 步(`loadReg`)炸成「注册表 …/app.json 形状非法」,而**前 3 步副作用已发生**(nonce 已被原子占位)⇒ 对外表现为「收单失败 + **同一张邀请再也用不了**」。注册表覆盖键 = **`--registry`**(两键分离);判据 = `test/overlay-join.test.mjs` **E10**=**CLI 级**真回环(🔴 调库式的 E9 **抓不到**这一类参数解析缺陷)。
### 3.11 直连(打洞)与 P2P(序㊵ · P2/S5)—— **「用户可设置 + 默认开 + 提示 + 乙」的可断言面**
> 🔑 **立项依据** = 用户 **2026-09-18 12:22** 原话「**1 用户可设置,默认开启提示用户 2 乙**」。产物 = `交接单_覆盖网络直连与P2P_20260918.md`(归档号 **132**;§8 前前缀 `8925c2460810c6dc5cdbc075b6cdaed2`)。
> 🔴 **本节新增 **1** 个生产 env 候选** —— 只有 `DIRECT_ENV_KEY`(`DSHS_OVERLAY_DIRECT`),且它**缺省即可用**(缺省 = 开)⇒ **无需写进任何 drop-in**;其余键**只被直连 CLI / 探针读取**。
> 🔴 **值格必须纯数字或纯路径**(夹注 ⇒ `NaN` ⇒ 假红);⛔ 值内不得含 `|`、`*`、反引号。
> 🔴 **UDP 端口区间与 `LISTEN_ALLOWED_RANGES`(TCP)不同族** ⇒ ⛔ 别把 `21100–21116` 加进那个键(探针的 `OBS-11` 走 `ss -lntp`,加进去会让"允许集"凭空多出永远不存在的 TCP 口)。
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `DIRECT_ENV_KEY` | `DSHS_OVERLAY_DIRECT` | — | 实测 | 直连总开关的 **env 键名**。⚠️ 与 `CONTENT_GROUP_KEY_FILE` 不同:本键**缺省即可用**(缺省 = 开)⇒ 不写进任何 drop-in 也生效;取值非法(既不在开集也不在关集)⇒ **判据取 `null`**(⛔ 不静默当开/当关) | `grep -rn "DSHS_OVERLAY_DIRECT" src/net/relay/direct/index.ts` |
| `DIRECT_DEFAULT_ENABLED` | 1 | 布尔 | 实测 | 用户口径②「**默认开启**」的落地值:`1` = 开(⛔ 不是"缺省关、用户主动开")。源码真值 = `direct/index.ts#DEFAULT_DIRECT_ENABLED` | `grep -n "DEFAULT_DIRECT_ENABLED = " src/net/relay/direct/index.ts` |
| `DIRECT_OFF_VALUES` | `0,false,off,no` | — | 实测 | 显式**关**的取值集(大小写不敏感);开集 = `1,true,on,yes`。**不在此两集且非空 ⇒ FAIL 并点名**(⛔ 不许静默取缺省) | 本行即真值 |
| `PUNCH_PORT_BASE` | 21100 | 端口 | 实测 | 打洞 UDP socket 的**端口基址**(沿用序⑥ S4 观察器用过的 21100 段 ⇒ ⛔ 不新造魔数)。⚠️ **UDP**,与 `LISTEN_ALLOWED_RANGES`(TCP)不同族 | `grep -n "PUNCH_PORT_BASE = " src/net/relay/direct/punch.ts` |
| `PUNCH_PORT_SPAN` | 16 | 个 | 实测 | 打洞端口区间跨度 ⇒ `[21100, 21116)`(同一台机可能有多个并发探测) | 本行即真值 |
| `PUNCH_DEADLINE_MS` | 3000 | ms | 实测 | 单次打洞探测的**收包窗口**;窗内收不到对端的包 ⇒ **判死**(⛔ 不无限重试)。⚠️ 真机腿的"对端"须是**有固定公网出口**的节点(乙-1) | 本行即真值 |
| `DIRECT_COOLDOWN_MS` | 300000 | ms | 实测 | 判死后该候选的冷却(5 min)。🔴 **必须 > 0** —— `0` 会让"判死"退化成**每次重试都真打一遍**(重试风暴);本线已把 `RELAY_FAILOVER_COOLDOWN_MS=0` 列为硬禁令 ⇒ 这里是**构造期断言**(`DirectCooldown` 收 `<= 0` 直接抛) | 本行即真值 |
| `DIRECT_PUNCH_MIN_OK` | 1 | 次 | 实测 | `OBS-28` 判据① 的**判别器**阈值:`punchOk ≥ 本值`(打洞**成功路径**必须真的发生过,⛔ 不许"看起来能打"当绿) | 本行即真值 |
| `DIRECT_CAND_ACCEPT_MIN` | 2 | 条 | 实测 | `OBS-27` 判据①:**同网 + 白名单内**的候选必须**被接受** ≥ 本值 | 本行即真值 |
| `DIRECT_CAND_REJECT_MIN` | 3 | 条 | 实测 | `OBS-27` 判据②:**必须被显式拒绝**的负腿条数下限(自检实跑 **9** 条:跨网 / 替第三人申报 / 白名单外 / 本机不在白名单 / 坏形状 / 夹带凭据字段 / 主机名当地址 / 地址超限 / 过期),且**每条都要带具名 `reason`** | `node scripts/overlay-direct-probe.cjs selfcheck --json \| grep -c '"reason"'` |
| `DIRECT_HINT_MIN_PARTS` | 3 | 段 | 实测 | `OBS-26` 判据③:提示文案必须含的三段(**①谁会连进来 ②怎么关 ③关掉不影响什么**)。⛔ 只写"已启用直连"不算 | 本行即真值 |
| `DIRECT_SELFCHECK_FILE` | `/var/lib/dshs/overlay/direct-selfcheck.json` | — | 实测 | `overlay-direct-probe.cjs selfcheck --json --out <本路径>` 的读数(`OBS-26/27/28` 的**共同**数据源)。⛔ 由**人/棒显式跑**,探针只读(⛔ 探针不代跑自检,也⛔ 不 ssh 去开 UDP 口) | `node scripts/overlay-direct-probe.cjs selfcheck --json` |
> ⚠️ **一条必须写明的口径**:`DIRECT_SELFCHECK_FILE` **不存在 ⇒ `OBS-26/27/28` 三条一起 SKIP + 留痕**("真机还没跑过直连自检");**存在却读不出来 / 解析不了 ⇒ 三条一起 FAIL 并点名**。⛔ 两者不可混(本线「"没装"与"没采到"必须可分」)。
> ⚠️ **`OBS-26/27/28` 都没有"缺省不启用"档**(与 `OBS-23` 不同):直连**缺省即开** ⇒ 只有 PASS/FAIL,⛔ 不许出现"既非 SKIP 也非 FAIL"的静默绿。
> 🔴 **准入判定必须复用既有白名单**:`network.ts#isAllowedDialer` 是**唯一**出口,它的语义与 `server.ts` 的两处闸门(注册闸门 `:1182`、`DIAL` 闸门 `:1491`)**逐字等价**;`test/overlay-direct.test.mjs#T4` 是这条的**机器守卫**(读 `server.ts` 源码核对 + 断言直连模块里⛔ 不出现 `dialers.get(`)。
> 🔴 **打洞只用 Node 内建 `dgram`**:`OBS-28` 顺带断言 `lib/net/relay/direct/*.js` 的模块说明符**只有** `node:dgram` 与同模块内相对路径(⛔ 无第三方依赖、⛔ 无内核驱动、⛔ 无虚拟网卡)。
## §4 代码已固化常量(**实测**=直接可 grep 的源码真值)
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `RELAY_PORT` | 20080 | — | 实测 | `src/net/relay/server.ts:52`+单位文件 ExecStart | `grep -n DEFAULT_RELAY_PORT src/net/relay/server.ts` |
| `RELAY_BIND` | 127.0.0.1 | — | 实测 | 主单元 ExecStart(无 `--host` ⇒ 默认回环) | `ssh -p 22 bt-server "ss -lntp \| grep 20080"` |
| `RELAY_INSTANCE_BASE` | 19000 | — | 实测 | 主单元 ExecStart `--base 19000` | 同上 |
| `RELAY_INSTANCE_SPAN` | 3000 | — | 实测 | 主单元 ExecStart `--span 3000` ⇒ 可声明窗口 `[19000, 22000)` | 同上 |
| `AUTH_DEADLINE_MS` | 5000 | ms | 实测 | `src/net/relay/server.ts:54` | `grep` |
| `AUTH_WINDOW_MS` | 60000 | ms | 实测 | `src/net/relay/server.ts:56` | `grep` |
| `CAPACITY_RETRY_AFTER_MS` | 5000 | ms | 实测 | `src/net/relay/server.ts:63` `DEFAULT_CAPACITY_RETRY_AFTER_MS` | `grep` |
| `SHUTDOWN_GRACE_MS` | 300 | ms | 实测 | `src/net/relay/server.ts:69` | `grep` |
| `MAX_STREAMS_PER_PORT` | 64 | 条 | 实测 | `src/net/relay/server.ts:71` | `grep` |
| `QUEUE_MAX_BYTES` | 1048576 | B | 实测 | `src/net/relay/server.ts:73`(`1 << 20`) | `grep` |
| `WS_PEER_HIGH_WATER` | 262144 | B | 实测 | `src/net/relay/server.ts:76`(`256 * 1024`) | `grep` |
| `CLIENT_HIGH_WATER` | 262144 | B | 实测 | `src/net/relay/client.ts:230` | `grep` |
| `CLIENT_QUEUE_MAX` | 1048576 | B | 实测 | `src/net/relay/client.ts:229` | `grep` |
| `DIRECTORY_REFRESH_SECONDS` | 300 | s | 实测 | `src/net/relay/directory.ts:60` | `grep` |
| `DIRECTORY_FETCH_TIMEOUT_MS` | 5000 | ms | 实测 | `src/net/relay/directory.ts:521` | `grep` |
| `DIRECTORY_MAX_ENTRIES` | 8 | 条 | 实测 | `src/net/relay/directory.ts:63` `MAX_ENTRIES` | `grep` |
| `DIRECTORY_MAX_ENTRY_LEN` | 512 | 字符 | 实测 | `src/net/relay/directory.ts:64` `MAX_ENTRY_LEN` | `grep` |
| `DIALER_POOL` | 64 | 口 | 实测 | `src/net/relay/dialer.ts:55` `DEFAULT_POOL`+`/etc/dshs.env` `DSHS_RELAY_DIAL_POOL` | `grep`+`ssh -p 22 bt-server 'grep DIAL_POOL /etc/dshs.env'` |
| `DIAL_PORT_BASE` | 25000 | — | 实测 | `/etc/dshs.env` `DSHS_RELAY_DIAL_PORT_BASE` | 同上 |
| `DIAL_PORT_SPAN` | 1000 | — | 实测 | `/etc/dshs.env` `DSHS_RELAY_DIAL_PORT_SPAN`(⚠️ 实绑只到 `base+POOL`) | 同上 |
| `DIAL_POOL_BOUND` | 25000–25063 | — | **实测** | 47 上 `ss -lntp`(64 口全绑)+journal `[relay-dialer] 本机落点池就绪:64 个口` | `ssh -p 22 bt-server "ss -lntp \| grep -c 250"` |
| `W47_AGENT_PORT` | 19100 | — | 实测 | `/etc/dshs.env` `DSHS_CLUSTER_AGENT_URL=http://127.0.0.1:19100`。🆕 **序㊲ 起被探针引用**(`OBS-09` 的派生用它把 `endpoints[]` 切成 agent 面 / 实例面) | `ssh -p 22 bt-server 'grep AGENT_URL /etc/dshs.env'` |
| `LOCAL_INSTANCE_PORT` | 20000 | — | 实测 | 47 上 `ss -lntp`(`127.0.0.1:20000`)。⚠️ **序㊲ 起探针⛔ 不再引用**(旧 `OBS-09` 拿它当"本机取样点";新口径只从 `/status.endpoints[]` 派生 —— 47 的 worker 不是 relay 客户端 ⇒ 本机实例面**结构性**不在端点表里)⇒ 本键只作**快照 / 对账** | `ssh -p 22 bt-server "ss -lntp \| grep 20000"` |
| `PEER_AGENT_PORT` | 19000 | — | 实测 | `/status` → `endpoints[]`/`online[]`(对端 w-106 的 `ports=19000/21001`;⚠️ **实例那一半会漂移** —— 见下一行的「实测替换」注)。🆕 **序㊲ 起被探针引用**(同 `W47_AGENT_PORT`:agent 面是**配置常量**、⛔ 不随实例替换漂移 ⇒ 适合当切分依据) | `ssh -p 22 bt-server 'curl -s 127.0.0.1:20080/status'` |
| `PEER_INSTANCE_PORT` | **21001** | — | 实测 | 🔁 **实测替换 21000 → 21001**(2026-09-18 10:2x · 收口复核棒现测;`curl -s 127.0.0.1:20080/status` ⇒ `endpoints[]` = `w-106 port=19000` + `w-106 port=21001`,两条都 `online=true`)。🔴 **漂移机理**(本棒取证):实例端口由 `spawn.ts#findFreePortInRange` 从 `DSHS_INSTANCE_PORT_BASE`(=21000) **上扫取第一个空闲口**;「访问时自然替换」在旧 scope 端口尚未释放时发起 ⇒ 落点顺延到 **21001**(旧 scope 收掉后再替换则回落 21000)⇒ **本值只是"某次现测"的快照**,⛔ 不是稳定常量。🔴 **序㊲ 起探针⛔ 不再引用本键**(`OBS-09` 的在册判据已改为从 `/status.endpoints[]` 派生,正是为了不再被这个漂移值坑)⇒ 本键降级为**快照 / 对账**用 | 同上(`ssh -p 22 test106 "ss -lntpH \| grep 2100"`) |
| `RELAY_FAILOVER_MIN_ATTEMPTS` | 3 | 次 | 实测 | `src/net/relay/switcher.ts#relayFailoverThresholds`(env 可覆写;**置 0 = 总开关关闭**) | `grep -n RELAY_FAILOVER_MIN_ATTEMPTS src/net/relay/switcher.ts` |
| `RELAY_FAILOVER_GRACE_MS` | 15000 | ms | 实测 | 同上(与 `MIN_ATTEMPTS` 取**或**:106 侧首连窗口长,只看次数会误切) | `grep` |
| `RELAY_FAILOVER_COOLDOWN_MS` | 300000 | ms | 实测 | 同上(与 `DIRECTORY_REFRESH_SECONDS` 对齐;冷却期内**不回跳**) | `grep` |
| `RELAY_FAILOVER_DEADLINE_MS` | 30000 | ms | 实测 | 同上(**验收判据**:从不健康到切换完成的允许上限) | `grep` |
| `RELAY_FAILOVER_CHECK_MS` | 2000 | ms | 实测 | 同上(健康巡检周期;独立于目录刷新周期) | `grep` |
| `RELAY_FAILOVER_UP_TIMEOUT_MS` | 12000 | ms | 实测 | 同上(**只用于换址**:新通道必须真到 `up` 才算「建起来了」)。⚠️ 序⑨:**死候选提前失败后它不再是恒等代价**(实测死候选 ≈ 0.1–1 s 就返回,慢候选仍享受完整 12 s) | `grep` |
| `RELAY_GRACEFUL_BURST_MS` | 15000 | ms | 🆕 实测 | **序⑨ 参数表化**:`client.ts` 的 `gracefulBurstMs`("计划内下线 ≠ 故障"的快速重试窗口)原先是**全仓唯一一个不可配的时延常量**(只有 `?? 15_000` 一处),现可经 env 覆写;⛔ **默认值语义逐字不变**。⚠️ 它就是**检测段 15.0 s 地板的真身**:窗口内 `attempts` 恒为 0 ⇒ `unhealthy()` 只能靠 `graceMs` 成立(序⑨ §1.2-RC-2)⇒ 改它 = 改"计划内重启不触发切流"的窗口,**先回写交接单**(D3) | `grep -n gracefulBurstMsDefault src/net/relay/client.ts` |
| `RELAY_FAILOVER_EXEMPT` | 1 | — | 实测 | 同上(🆕 序⑧:**一跳豁免总开关**,`1`=开 / `0`=关;`RELAY_FAILOVER_MIN_ATTEMPTS=0` 是**另一层**,⛔ 别混) | `grep -n RELAY_FAILOVER_EXEMPT src/net/relay/switcher.ts` |
| `DRILL_COOLDOWN_MS` | 90000 | ms | 推导 | 🆕 序⑧:**演练期**冷却覆盖值(经 `dshs.service.d/zz-drill-override.conf` 注入)——⛔ **生产默认恒为 `RELAY_FAILOVER_COOLDOWN_MS=300000`**。取值口径:必须 ① ≫ 静默失效检测时延(实测 ≈ 29 s,否则"冷却未到期"判据根本来不及观测)② ≪ `DIRECTORY_REFRESH_SECONDS`(300 s)(让冷却先过期、目录巡检后到) | — |
| `DRILL_NO_SWITCH_OBSERVE_MS` | 45000 | ms | 推导 | 🆕 序⑧:幕 4b 的**"预期不切换"观察窗**;⛔ 必须 ≥ 失效检测时延(否则 D6 现场还没形成)**且** < `DRILL_COOLDOWN_MS`(否则会跨过冷却期满、把"自然回归"误判成"切了") | — |
> 🆕 **序⑦ 新增 5 键**(中继失败切流):默认值 = 源码真值(可直接 grep);
> ⛔ 值格**必须纯数字**(夹注 ⇒ `NaN` ⇒ 假红,见 §8.8-2 教训);
> 🔑 **回滚开关 = `RELAY_FAILOVER_MIN_ATTEMPTS=0`** ⇒ 监管器**永不触发**,行为回到「原地退避重试」的现状(§7 回滚第 1 层)。
>
> 🆕 **序⑧ 新增 3 键**(切流冷却语义拆分):`RELAY_FAILOVER_EXEMPT`(代码侧,默认 `1`)+
> `DRILL_COOLDOWN_MS` / `DRILL_NO_SWITCH_OBSERVE_MS`(**只在演练脚本里用**)。
> 🔑 **本单首选回滚点 = `RELAY_FAILOVER_EXEMPT=0`** ⇒ 只关掉豁免、序⑦ 的切流能力**全部保留**;
> ⛔ 它**不是** `MIN_ATTEMPTS` 的同义词(后者关整个监管器)。
> ⚠️ 演练期改冷却**只能**经 `DRILL_COOLDOWN_MS` 注入 drop-in;⛔ `RELAY_FAILOVER_COOLDOWN_MS` 的代码默认值不动(D9)。
> ⚠️ **键名口径**:`LOCAL_*` = 中继机(47)**本机**的实例/代理口;`PEER_*` = **对端**节点(今天= w-106)的等价口。
> (键名里**不带** `106` 是为了让 `overlay-probe.cjs` 的**零数字纪律**成立 —— 见交接单 §6 E6。)
---
## §5 本单新增:**45% 口径填值**(443 单 §8.8 留下的空位)
> **443 单 §8.8 登记行原文**:「兜底启用后,relay 容量须按 **45% 的节点走中继**核算(异构纪律,⛔ 不是同构的 15%)」。
> **口径落点**(按交接单 §4.1-3):填**单台中继的 `--max-hosts`**,⛔ **不填"全网 45%"** —— 后者只作**校验**用。
### 5.1 输入(全部来自本表其它行,⛔ 无外部魔数)
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `MEM_TOTAL_MB` | 1870 | MB | **实测** | S0 P6:`free -m` 第 1 行 | `ssh -p 22 bt-server 'free -m \| head -2'` |
| `MEM_AVAILABLE_MB` | 1002 | MB | **实测** | S0 P6:`free -m` 的 `available` 列 | 同上 |
| `MEM_BUDGET_MB` | 1002 | MB | 推导 | 本表 | `= MEM_AVAILABLE_MB`(47 同时跑 PG+Manager+nginx,故**只取 available**,⛔ 不用 total) |
| `CPU_CORES` | 2 | 核 | **实测** | S0 P6:`nproc` | `ssh -p 22 bt-server nproc` |
| `FD_LIMIT` | 262144 | 个 | **实测** | S0:`/proc/<relay pid>/limits` `Max open files` | `ssh -p 22 bt-server 'cat /proc/$(systemctl show -p MainPID --value dshs-relay)/limits \| grep "open files"'` |
| `RELAY_RSS_KB` | 72888 | KB | **实测** | S0:`ps -o rss= -p <relay pid>`(used=2 时,含 Node 基座) | `ssh -p 22 bt-server 'ps -o rss= -p $(systemctl show -p MainPID --value dshs-relay)'` |
| `MEM_PER_HOST_MB` | **0.06** | MB/台 | **实测**(原为推导 2) | 本单 §8.4 E8 + `relay-mem-calibrate.mjs`:本机独立 relay 实例 + 同进程合成 client N=2/10/25/50/100/150,每点 7 次采样取中位数 ⇒ 线性回归 `RSS(N) = 48078 + 46.7·N` KB,**R² = 0.942**(≥ 0.9 ⇒ 有效) | `46.7 KB/台 × 1.2 余量 = 56.0 KB = 0.0547 MB ⇒ ceil 到 0.01 MB 位 = 0.06`。⚠️ 原推导 2 MB **高估 36×**:它把 **per-stream** 的 256 KB 高水位算进了 **per-host**;本值是**空闲会话**口径,带流量的 per-stream 开销**未测**(登记于 §9) |
| `FD_PER_HOST` | 4 | 个/台 | 推导 | 1 条 WS + 每声明端口 1 个回环 listener + 2 个临时 ⇒ 上界 4 | 保守取上界 |
| `DESIGN_MARGIN` | 0.45 | — | 估值 | **443 单 §8.8** 原文(异构纪律;⛔ 非同构 15%) | — |
### 5.2 推导式(⛔ 可独立手算,E2 判据)
```
C_MEM = floor(MEM_BUDGET_MB / MEM_PER_HOST_MB) = floor(1002 / 0.06) = 16700 # ← 序⑥ S7 重算(原 floor(1002/2)=501)
C_FD = floor(FD_LIMIT / FD_PER_HOST) = floor(262144 / 4) = 65536
C_RELAY = min(C_MEM, C_FD) = 16700 # 带宽仍不参与取 min,见 §5.4
RELAY_MAX_HOSTS = floor(C_RELAY × DESIGN_MARGIN) = floor(16700 × 0.45) = 7515 # ← 原 225
```
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `C_MEM` | **16700** | 台 | 推导 | 本表 | `floor(1002 / 0.06)` |
| `C_FD` | 65536 | 台 | 推导 | 本表 | `floor(262144 / 4)` |
| `C_RELAY` | **16700** | 台 | 推导 | 本表 | `min(16700, 65536)` |
| `RELAY_MAX_HOSTS` | **7515** | 台 | 推导 | 本表 | `floor(16700 × 0.45)`(⚠️ **本单元格必须是纯数字** —— 探针 `KEY_RE`/`cleanValue` 不认识"(原 225)"这类夹注,会把整格当值 ⇒ `NaN` ⇒ `OBS-02` 假红;旧值 225 记在此列)。🔴 🔴 **序⑥ S7 回头条件已触发并执行**(2026-09-17):`MEM_PER_HOST_MB` 2 → 0.06、`WAN_STEADY_THROUGHPUT` 待测 → 352/12213 ⇒ 已**重下发** 47 与 106 两台的 `capacity.conf`(`--max-hosts 7515`),`OBS-02` 复验 PASS |
**校验(三条,全部满足才生效)—— 序⑥ S7 重跑记录**
- ① **防自锁**:`RELAY_MAX_HOSTS > used × 4` ⇒ `7515 > 4 × 4 = 16` ✅(验收时 `used = 4`)
- ② **设计余量自洽**:`7515 / 16700 = 45.0% ≈ DESIGN_MARGIN` ✅
- ③ **千台需求校验**:`FLEET_RELAY_DEMAND` = 450 **≤** `RELAY_MAX_HOSTS` = 7515(原为 `450 > 225`)⇒ 🔴 **结论变更**:**"容量上必须 ≥2 台中继"这条理由随实测消失**(单台容量已足够)。2 台的依据改为 ① `清单 §3 关键决定`「有公网 IP 的节点自动升格中继候选」② **跨机真容灾**(106 已升格,见 §8 ④)。⚠️ "零余量"这一旧事实**不再成立**,代之以下面那条:**绑定约束从"内存"搬到了"带宽/时延"**(见 §5.4)。
### 5.3 `--max-hosts` 的**分母口径**(⛔ 必须写清,否则数字会被误读)
`--max-hosts` 是**每台中继**的**在册会话(host)数上限**,它**不是**"全网 45%",分母是**这一台中继**;`FLEET_RELAY_DEMAND`(450)**只是校验上界**。今天生产上只有 **1 台中继**(47)⇒ 本值直接生效。
### 5.4 为什么带宽**不参与** `min`(⛔ 不是漏了)—— 序⑥ S7 用实测重判
- **判据(S7-2 原文)**:若新实测吞吐使"225 台(今为 7515 台)的控制面 + 实例面流量"逼近实测吞吐 ⇒ 带宽进 `min`。
- **控制面**(用实测值重算):`1 / HB_SEC` = 0.067 次/秒/台 × 7515 台 ≈ **504 次/秒**;每台每秒控制面流量 `≈ 100 / HB_SEC = 6.7 B/s` ⇒ 7515 台 ≈ **50 KB/s**,对比绑定方向实测 **352 KB/s**(余量 7×)⇒ **控制面不绑定**。
- **实例面**:不是"稳态负载"而是"**单次传输**",正确的判据是**时延**不是容量 —— 单次 46.3 MB 冷启动 ÷ 352 KB/s ≈ **135 s**(原推算按 192 KB/s 是 247 s,按 22 KB/s 旧值则是 36 min)。⇒ ⛔ **仍不进 `min`**,但登记为**实例面单次传输的时延上界**,并给出唯一的改进方向(提升对端公网出带宽,而非加中继)。
- ⚠️ **本条的边界**:352 KB/s 是 **106(云轻量)出带宽封顶**造成的,不是 relay 栈上限(上行方向实测 12213 KB/s 证明栈本身没到这个量级)。
### 5.5 `/status.capacity` 的**语义纠正**(P2 实测 vs 交接单期望)
| 事实 | 原文 |
|---|---|
| `max = 0` 时**只有** `{max, used}`,**没有 `free`** | `src/net/relay/server.ts:502-506`:`this.maxHosts > 0 ? {max, used, free} : {max, used}`;实测 `/status` = `"capacity": {"max": 0, "used": 2}` |
| `free` **只有**在 `max > 0` 时出现 | 同上 ⇒ **E3 的 `free = max - used` 判据只在设值后成立**(设值前 `free` 字段不存在,不是 `null`/不是 `0`) |
| 满载时的 `free` 恒为 `0` | `server.ts:761`:`capacity: { max, used, free: 0 }`(拒绝载荷里) |
| `used` = **在册会话数**(= host 数,不是流数、不是端口数) | `server.ts:505`:`used: this.sessions.size` |
| `retryAfterMs` 口径 = 满载时给对端的**排队建议** | `CAPACITY_RETRY_AFTER_MS` = 5000 ms;客户端**不消耗退避**地排队(`client.ts:532 / 881-896`,附A 已核) |
### 5.6 443 单 §8.7③ 要求的回答:`relays[]` 顺序语义
> **本单必须回答**:443 单留下的「若将来 `relays[]` 引入非首位更优的显式优先级语义 ⇒ 需重新定义同源优先与它的先后关系」。
**回答**:本单**不引入**该语义。`relays[]` 的顺序语义固定为「**主入口首位**」—— 首位是主入口,其余是兜底候选;**不存在**"非首位更优"的显式优先级字段。⛔ 本表**不新增**任何优先级键(新增即等于把这条语义改了)。
---
## §6 观测阈值(探针脚本的**唯一**取数来源;⛔ 脚本内无魔数 = E6)
| ID | 指标 | 阈值 | 判据 |
|---|---|---|---|
| `OBS-01` | relay 在册会话数(`used`) | `MIN_HOSTS` | `used ≥ MIN_HOSTS` |
| `OBS-02` | `capacity.max` / `free` | `RELAY_MAX_HOSTS` | `max = RELAY_MAX_HOSTS` 且 `free = max - used` |
| `OBS-03` | 身份强制与受信签名者 | `MIN_TRUSTED_SIGNERS` | `identityRequired = true` 且 `trustedSigners ≥ MIN_TRUSTED_SIGNERS` |
| `OBS-04` | 通过节点凭据校验的注册数 | `MIN_IDENTITY_OK` | `identityOk ≥ MIN_IDENTITY_OK` |
| `OBS-05` | 吊销清单规模 | `MAX_REVOKED_HOSTS` | `revokedHosts ≤ MAX_REVOKED_HOSTS` |
| `OBS-06` | **判别器计数三件套存在** | (字段存在性) | `typeof dial/dialDenied/dialFailed === 'number'` |
| `OBS-07` | 鉴权失败累计 | `MAX_AUTH_FAILED` | `authFailed ≤ MAX_AUTH_FAILED` |
| `OBS-08` | 端点表全在线(**空表可分**) | (无 `false`) | `endpoints[].online` 全为 `true`。🔴 **序㊲:空表不再一律判红**(旧口径对"客户端挂在另一台中继"这个**合法拓扑态**判 FAIL = 假红)—— `endpoints` 为空时按**对端中继视图**(`ssh <SSH_TARGET_106> curl <RELAY_STATUS_URL>`,**只读回环**)分三种:① 对端中继**有会话在声明端口** ⇒ **SKIP + 留痕**(客户端挂在另一台中继,⛔ 非故障)② **两台都无**该客户端会话(或对端中继单元 `is-active≠active`)⇒ **FAIL 并点名**(确实没挂客户端)③ 对端中继**读不回来**(ssh / JSON 解析失败)⇒ **FAIL 并点名**(不可判,⛔ 不许静默当绿)。⛔ 零新增暴露面 / 零新增参数键;⚠️ **只在 47 视角为空时才去读**(正常态**零额外 ssh**);夹具 = `--peer-status-fixture <对端 /status 原文>` |
| `OBS-09` | 在册实例面探活(**从 `/status` 派生;⛔ 不依赖表内固定端口**) | `W47_AGENT_PORT` / `PEER_AGENT_PORT` / `PROBE_CODE_SET` | **在册 ⇒ 必须可达**:派生子 = `{ e ∈ endpoints[] \| e.online === true 且 e.port ∉ {W47_AGENT_PORT, PEER_AGENT_PORT} }`。**派生非空** ⇒ **每一个**都必须可达(走**它自己那条** `localPort` 回环落点,码 ∈ `PROBE_CODE_SET`,否则 FAIL 并点名);⚠️ 派生端口**缺码**(夹具没给 / 真机 curl 得 `000`)⇒ **FAIL 并点名**(契约面,⛔ 不静默放行)。**派生为空** ⇒ **SKIP + 留痕**(含 `OBS-08` 同款"空表可分"归类;⛔ 不是 PASS、⛔ 也不是 FAIL)。🔴 **序 ㊲:旧口径(序⑳–㊱)「按表内那个固定实例端口做在册判据」已退役** —— 实例端口由 `spawn.ts#findFreePortInRange` **上扫取第一个空闲口**,「访问时自然替换」会把它漂移(实测 `21000 → 21001`)⇒ 表值一过期就**假 SKIP**(序㊱ 收口复核棒连踩两次)。⚠️ 47 本机实例面(`:20000`)**结构性**不在 `endpoints[]` 里(47 的 worker 不是 relay 客户端)⇒ 不再是本项取样点(⚠️ 旧口径下它也**从未**真正参与判定)。夹具 = `--instance-fixture <实例端口=HTTP码>,…`(**按端口映射**);⛔ **旧的位置式 `<本机码>,<对端码>` 已退役** ⇒ 给了**直接报错退出**(静默解释 = 造一个没人看得懂的 FAIL) |
| `OBS-10` | 门户可达 | `PORTAL_CODE` | `= PORTAL_CODE` |
| `OBS-11` | 零新增暴露面(**监听集合** / **nft 入站 accept 集合** / relay 只绑回环) | `LISTEN_REQUIRED` / `LISTEN_ALLOWED` / `LISTEN_ALLOWED_RANGES` / `DIAL_POOL_BOUND` / `NFT_ALLOW_INBOUND` / `RELAY_BIND` | **三集包含式**:`必在 ⊆ 实际 ⊆ 必在 ∪ 允许 ∪ 区间(含拨号池) ∪ 派生`,差集**逐条点名**;且 `nft 入站 accept ⊆ NFT_ALLOW_INBOUND`;`RELAY_PORT` 只出现在 `RELAY_BIND` 上。🔴 **序⑫:旧口径「计数相等」已退役** —— 它对实例/端点在线态**敏感 ⇒ 假红**,对"一进一出"替换式变化**不敏感 ⇒ 假绿** |
| `OBS-12` | relay 进程内存 | `RELAY_RSS_MAX_KB` | `RSS ≤ RELAY_RSS_MAX_KB` |
| `OBS-13` | **presence 帧率(稳态)** | `PRESENCE_STEADY_FRAMES_MAX` / `PRESENCE_SAMPLE_HITS_MIN` | **两次 `/status` 采样的增量**:`Δpushed ≤ 0` **且** `ΔstatusHits ≥ 1`(活性证明:⛔ 不许"因为读不到所以看起来是 0");并附口径一致(`presenceTiming` 逐项 == `PRESENCE_*`)+ 判别器齐全(`subs/pushed/snaps/rejected/statusHits` 皆 number)+ `snaps ≤ pushed` |
| `OBS-14` | **首帧即全量 `SNAP`(⛔ 无 N+1)** | (`snaps` / `pushed` / `subs` 三者关系) | `snaps ≤ pushed` 且 **`subs > 0 ⇒ snaps ≥ 1`**(有订阅者却一帧 `SNAP` 都没发 ⇒ 首帧走"逐台拉",判 FAIL) |
| `OBS-15` | **在线态表不撒谎 + 陈旧度** | `PRESENCE_STALE_P95_MAX_MS` / `PRESENCE_TTL_MS` | 活跃会话(`lastSeenAgoMs ≤ PRESENCE_TTL_MS`)**必须**被 `presence[]` 覆盖且 `online = true`;且 `p95(lastSeenAgoMs over devices>0)` ≤ `PRESENCE_STALE_P95_MAX_MS` |
| `OBS-16` | **订阅生效期间 `/status` 轮询真的停了**(序㉑ · 在册缺陷 P-1 的验收) | `PRESENCE_GATE_WINDOW_MS` / `PRESENCE_GATE_HITS_MAX` | **第三次 `/status` 采样(门窗口)的增量**:`ΔstatusHits ≥ 1`(探针自身那一次 = **活性证明**)**且** `≤ PRESENCE_GATE_HITS_MAX`(= 1 ⇒ 除探针外**零**命中)**且** `subs ≥ 1`。🔴 **为什么不能只看 `OBS-13`**:`OBS-13` 的 `ΔstatusHits ≥ 1` 由探针自身两次读即满足 ⇒ 旧口径下门 95% 时间开着、轮询照旧在跑,而 `OBS-13` **仍然全绿** ⇒ 实测降幅只有 1.10× 却长时间没人发现。夹具实证走 `--status-fixture-3`(缺省 ⇒ Δ=0 ⇒ FAIL 并点名 = 契约面) |
| `OBS-17` | **内容寻址判别器齐全 + 命中可断言**(序㉔ · 内容分发的验收) | `CONTENT_TIER_HITS_MIN` / `CONTENT_BLOCK_SIZE` / `CONTENT_STORE_MAX_BYTES` | **两次 `/status` 的 `content` 块**:① **判别器存在性** —— `content.source` 五档(`local/peer/edge/region/origin`)皆 `number`、`content.peer` 五个键(`peerHits/peerMisses/crossGroupDenied/declarations/withdrawn`)皆 `number`、`content.store` 七键皆 `number`(⛔ 缺一即 FAIL —— 这正是本线"静默放行"回头条件的机器判据);② **口径一致** —— `content.blockSize == CONTENT_BLOCK_SIZE` 且 `content.storeMaxBytes == CONTENT_STORE_MAX_BYTES`;③ **活性** —— `local + peer` 命中计数增量 `≥ CONTENT_TIER_HITS_MIN`。🔴 **为什么必须有 ③**:只判 ①(判别器存在)会让"装了但一次都没命中"**全绿** ⇒ 那正好是 E1 失败(全是回源)的样子。夹具实证走 `--content-fixture`(缺省 ⇒ 无 `content` 块 ⇒ FAIL 并点名 = 契约面) |
| `OBS-18` | **「逐步拉起」认领逻辑在册 + 计数自洽**(序㉕ · 实例逐步拉起的验收) | `DSHS_REHYDRATE_STAGGER_MS` / `DSHS_REHYDRATE_PROBE_MS` | **`dshs-worker` journald 的 `[rehydrate]` 行**(近 24 h):① 🔴 **在册(强判据)** —— 必须存在 `[rehydrate]` 行;**换回旧行为(启动即 `cleanAllStaleScopes()`)⇒ 永远不打这行 ⇒ 必红**(本项正是"把清空换成认领"的可机器断言面)。② **计数自洽** —— 最近一条 `[rehydrate] summary {...}` 键齐全且 **`adopted + stopped === scanned`**(每条被扫到的 scope 都必须有处置结论,⛔ 不许"扫到了却不认领也不停")。⚠️ **诚实标注**:⛔ 故意**不**把"扫到几条"当判据 —— 认领发生在**进程启动时刻**、探针是**事后**读,拿"当前 scope 数"比必然假红(启动后才起的实例对不上)⇒ `scanned/adopted/stopped` **只作信息输出**,真机活性由 S6 的 E1/E3/E4 断言。夹具实证走 `--rehydrate-fixture <journalctl 原文>`(缺省 ⇒ **SKIP**:本项真机数据只能 ssh 取) |
| `OBS-19` | **抖动观测块在册 + 口径一致**(序㉖ · 骨干稳定选路的验收) | `JITTER_LIMIT_MS` / `JITTER_HIST_BUCKETS` | **`/status.jitter` 块**:① **判别器齐全** —— `p95AbsDeltaMs` / `meanAbsDeltaMs` / `maxAbsDeltaMs` / `samples` / `deltas` / `sessions` / `thresholdMs` / `alerts` 皆 `number`、`overThreshold` 为 `boolean`、`hist` 为数组(⛔ **缺一即 FAIL** —— "没样本就少一个键"会让探针分不清「没装」与「装了但还没采到」,本线两处静默失效就是这么漏过去的);② **口径一致** —— `jitter.hist.length == JITTER_HIST_BUCKETS` 且 `jitter.thresholdMs == JITTER_LIMIT_MS`(防"装了但用的是另一套默认值 / 另一个键")。③ ① 本身即覆盖"**无样本也必须结构完整**"(`sessions = 0` 时上述键依然全在)。夹具实证走 `--status-fixture` 的 `jitter` 块(`judged = true` ⇒ 夹具模式照判,⛔ 不 SKIP) |
| `OBS-20` | **容量余量仍在(⛔ 没打满)**(序㉖ · `E4` 的验收) | `RELAY_UTIL_MAX_PCT` | **`/status.capacity`**:① `utilPct` / `utilMaxPct` 皆 `number`(⛔ 不许少键);② **`utilPct < utilMaxPct`**(⚠️ 方向是**小于**)⇒ 新接入仍被接受、**余量 > 0**。🔴 `utilPct ≥ utilMaxPct` ⇒ 软门已在拦新节点 ⇒ 对"骨干应留 30%+ 余量"的口径就是**不合格**(**要扩容,⛔ 不是改判据凑绿**)。附带输出 `counters.utilRefused`(本序新增:与硬门 `refused` 分开计)作信息面。夹具实证同 `--status-fixture` |
| `OBS-21` | **每连接候选数 ≥ 2**(序㉗ · E3「路径多样性」的验收) | `CAND_MIN` | **各连接路径自己的 `CAND_OBS_PREFIX` 观测行**(按 `CAND_OBS_UNITS_47` / `CAND_OBS_UNITS_106` 逐 `<unit>@<host>` 独立判):① **该路径有观测行** —— ⛔ 缺行即 FAIL,并把 journalctl **原文字节数**一并打出(`0 字节` = 读取失败,`>0 字节` = 真没有该行 ⇒ **两者必须可区分**);② **`count ≥ CAND_MIN`**。🔑 **`hosts`(**主机名**个数)只作信息输出、⛔ 不作判据** —— 🔴 且它**不是「独立物理路径数」**:观测器**不解析 DNS**(零网络),而生产上前两条候选 `ai1net.com` 与 `relay-direct.ai1net.com` **摘名不同、同落 47** ⇒ **真机实测 `count=3` 时 `hosts` 也报 3**,而**机器级**独立路径只有 **2**(47 + 106)⇒ 这个数只能**提示**"条数够 ≠ 冗余够",「冗余建成」必须由人按机器归属判(⛔ 别拿它当独立路径数)。故本项**照实判条数**、把 `hosts` 打出来供人判,⛔ **绝不放宽判据凑绿**。⚠️ 观测行由 `relay-tunnel.ts#RelayCandidateObservation` 在**每次真实解析**时写、并按 `RELAY_CAND_OBS_MS` 周期重发上次快照;`resolves=0` + `source=unresolved` ⇒ **从未解析过**(⛔ 与"解析出 0 条"必须可区分,本线两处静默失效都栽在"分不清没装与没采到")。夹具实证走 `--candidates-fixture-47` / `--candidates-fixture-106`(journalctl 原文;某侧缺 ⇒ 该侧 SKIP、全缺 ⇒ 本项 SKIP、⛔ 夹具模式绝不去 ssh) |
| `OBS-22` | **退出路径不杀实例**(序㉘ → 单 A · 候选 `B` 的验收) | `TEARDOWN_GUARD_MARKER` / `TEARDOWN_FORBIDDEN_TOKENS` | **两件套**(逐台判,`TEARDOWN_UNIT_47` / `TEARDOWN_UNIT_106`):① 🔴 **静态守卫(判别力在此)** —— 读**部署产物** `TEARDOWN_LIB_*` 里三处 `teardown()` 窗口,要求 ⓐ 带 `TEARDOWN_GUARD_MARKER`;ⓑ 窗口内**不出现** `TEARDOWN_FORBIDDEN_TOKENS` 任何字面量。**旧产物 ⇒ 无标记 + `orchestrator` 体含 `stop(` ⇒ 必红**(= 执行棒 S4「真机先红」的可机器断言面;⚠️ 判 `lib/` 而非 `src/` —— `lib/` 才是在跑的那份)。② **认领面**:`TEARDOWN_UNIT_*` 的最近一条 `[rehydrate] summary` **存在时**要求键齐全 + `adopted + stopped === scanned` + **`scanned > 0 ⇒ adopted > 0`**(扫到存量却一条未认领 ⇒ 被杀了);**`scanned === 0` ⇒ 只作信息输出、⛔ 不判红**。🔴 **为什么 `scanned > 0` 不能无条件当判据(诚实口径,可推翻)**:`scanned === 0` 在日志上有**两种不可区分的成因** —— ⓐ 实例被退出路径杀了(旧行为现场)ⓑ 启动时本来就没有存量(平台**正常空闲态**,两机实测 `0/0`)⇒ 无条件判它 ⇒ **空闲态永久红**、判据灵敏度被磨掉。🔴 **故「重启前后 scope 数守恒」在本探针里无法只读完成**(要守恒就得先重启生产)—— 该条由执行棒 S5/S6 的**真机演练**举证(原文见 `交接单_退出路径不杀实例_20260918.md §8.7`),⛔ **探针不冒充**。夹具实证走 `--teardown-fixture-47` / `--teardown-fixture-106`(**grep 原文**;某侧缺 ⇒ 该侧 SKIP、全缺 ⇒ 本项 SKIP、⛔ 夹具模式绝不去 ssh)+ ② 复用 `--rehydrate-fixture` |
| `OBS-23` | 🔴 **组密钥加密"共享不退化 + 明文不出现"**(序㉘ → 单 B · 用户拍板「C 组密钥加密」的验收) | `CONTENT_CRYPTO_DET_MIN` / `CONTENT_CRYPTO_DECRYPT_MIN` / `CONTENT_CRYPTO_SCAN_MIN` / `CONTENT_TIER_HITS_MIN` / `CONTENT_KEY_FILE` / `CONTENT_KEY_FILE_MODE` | **`/status.content.crypto` 十键 + 密钥文件权限**:① **判别器齐全** —— `content.crypto` 的 `encrypts/decrypts/decryptRejected/epochs/epoch/epochExpired/detChecks/detMismatches/plainScans/plainLeaks` **全部**是 `number`(⛔ 缺一即 FAIL);**且(真机)** 两台机上 `stat -c %a CONTENT_KEY_FILE` **必须逐字** = `CONTENT_KEY_FILE_MODE`(**启用了加密却拿不到 `0600` 密钥文件 ⇒ 判据面不自洽 ⇒ FAIL**;⛔ 夹具模式不查文件、也不 ssh)。② **F1 + F2** —— F1 = `detChecks ≥ CONTENT_CRYPTO_DET_MIN` **且** `detMismatches = 0`(**同明文两次 ⇒ 密文逐字节相同** = 按哈希共享块的**前提**);F2 = `decrypts ≥ CONTENT_CRYPTO_DECRYPT_MIN` **且** `decryptRejected = 0` **且** `content.source.local + content.source.peer ≥ CONTENT_TIER_HITS_MIN`(🔴 阈值与 `OBS-17` ③ **同源**,⛔ 不另立一套)。③ **F4①:明文不出现** —— `plainScans ≥ CONTENT_CRYPTO_SCAN_MIN` **且** `plainLeaks = 0`(对自己刚加密出的字节做**字节级**扫描)。🔴 **为什么 ③ 单独必须有**:「**没测到**」与「**真没有**」必须可分 —— 只判 ①("判别器在")会让"加密了但明文照样落库"**全绿**;反向证明(OFF 段**必须**命中 ≥ 1)由 `--content-crypto-fixture` 承载。⚠️ **"缺省不启用" = SKIP + 留痕**(合法状态),⛔ 但**不许**既不是 SKIP 也不是 FAIL 的"静默绿"。⚠️ `epochExpired` / `decryptRejected` 稳态须 **0**;后者持续增长 ⇒ 命中单 B §9-6(**停下报告**,⛔ 不许"重启一次看看")。夹具实证走 `--content-crypto-fixture`(缺省 ⇒ 取 `--content-fixture` 的 `crypto` 子块;两处都没有 ⇒ SKIP)|🔴 **口径定稿(序㉛ · 2026-09-18)**:本行「缺省不启用 = SKIP + 留痕」**即最终口径** —— 单 B 的 §4/S6 原写的「新 `OBS-23` 对旧生产 ⇒ **FAIL**」**作废**(它会把"合法地没启用"误报成红 ⇒ 磨掉判据灵敏度);该项的"真机先红"这条腿**由权限腿承载**(把 `CONTENT_KEY_FILE` 从 `0600` 改 `0640` ⇒ FAIL 并**分别点名**两台实数权限)。⛔ 仍不许出现"既非 SKIP 也非 FAIL"的静默绿 |
| `OBS-24` | **网注册表 + 白名单派生自洽 + 跨网零共享**(序㊱ · S1/S4) | `NODES_REGISTRY_FILE` | **控制面注册表 JSON**(夹具 `--nodes-fixture`;真机 `cat`):① **形状合法**(`version` / `nodes` / `status ∈ {pending,approved}` / `nodeKey` 64 hex)② **派生集合 ≡ 该网 approved 集合**(差集**逐条点名**)③ **`pending` 一律不在派生里**("**收单 ≠ 批准**"的可断言面)④ **结构性错桶 = 0**(派生桶里的 `hostId` 必须属于该桶那张网 ⇒ **`J5`** 的机器判据)⑤ **派生桶数 ≡ 有 `approved` 的网数**(⛔ 不许把多张网合并成一张)。⚠️ 文件**不存在 ⇒ SKIP + 留痕**;**存在却读不出 / 解析不了 ⇒ FAIL 并点名**(两者可分)。🔴 **刻意不判**:**同一 `hostId` 出现在两张网**是**合法**状态(客户端自取设备名会撞名;`test/overlay-join.test.mjs#D1` 已钉死)⇒ 只作**信息项**打印,⛔ 不作判据。🔴 **序㊳ 起:夹具模式缺 `--nodes-fixture` ⇒ SKIP + 留痕(⛔ 不去 ssh 读生产)** |
| `OBS-25` | **一键加入在册 + 失败具名 + 凭据一次性**(序㊱ · S2/S3) | `JOIN_STEPS_MIN` / `JOIN_NAMED_FAILURES_MIN` | **`selfcheck` 读数 JSON**(夹具 `--join-fixture`;真机 `cat <NODES_SELFCHECK_FILE>`):① 四步齐且全绿(≥ `JOIN_STEPS_MIN`)② `application.hasPrivateKey = 0` 且申请单非空(**私钥不出机**)③ `ledger.second = invite-already-used`(**一次性**)④ `silentRejections = 0` 且具名失败 ≥ `JOIN_NAMED_FAILURES_MIN`(**⛔ 不许静默拒绝**)⑤ `registry.auditOk = true` ∧ `derivation.crossNetworkShared = 0`。⚠️ 私钥权限 `nodeKeyModeOk: null` = **本平台不可判**(Windows)⇒ 留痕不计绿;`= false` ⇒ FAIL。⚠️ 读数文件不存在 ⇒ SKIP + 留痕。🔴 **序㊳ 起:夹具模式缺 `--join-fixture` ⇒ SKIP + 留痕(⛔ 不去 ssh 读生产)** |
| `OBS-26` | 🔴 **直连开关「用户可设置 + 默认开 + 有提示」**(序㊵ · P2/S5 · 用户口径①②③ · 判据 **D2/D3/D4**) | `DIRECT_HINT_MIN_PARTS` | 数据源 = `DIRECT_SELFCHECK_FILE`(夹具 `--direct-fixture`;真机 `cat <DIRECT_SELFCHECK_FILE>`):① **默认值 = 开** —— `switch.defaultCase.enabled = true` **且** `source = default`(⛔ 不是"缺省关")② **关闭生效** —— `switch.offCase.enabled = false` **且** `udpSocketsOpened = 0` **且** `candidatesEmitted = 0`(**关闭 ⇒ 零 UDP socket + 不发候选**)③ **非法值不静默** —— `switch.badCase.enabled = null`(⛔ 不许当开、⛔ 不许当关)④ **提示可行动** —— `hint.parts ≥ DIRECT_HINT_MIN_PARTS` **且** `mentionsOff ∧ mentionsScope ∧ mentionsImpact` 全真 ⑤ **join 回读** —— `joinConf.direct = true` **且** `readback = true`(= **D3**:新节点加入后配置读回缺省 = 开)。⚠️ 本项**没有**"缺省不启用"档(直连缺省即开)⇒ 只有 PASS/FAIL + 两种留痕 SKIP(读数缺失 / 夹具缺失)。🔴 真机的 `ss -lunp` 增量腿由 `selfcheck --ss` 另取 |
| `OBS-27` | 🔴 **候选交换「只走同网 + 白名单」且拒绝必须显式**(序㊵ · P2/S5 · 判据 **D1**) | `DIRECT_CAND_ACCEPT_MIN` / `DIRECT_CAND_REJECT_MIN` | 数据源同上(`candidate` 段):① `accepted ≥ DIRECT_CAND_ACCEPT_MIN`(同网 + 白名单内 ⇒ 接受)② 负腿 `rejected.length ≥ DIRECT_CAND_REJECT_MIN` **且每条都有具名 `reason`**(跨网 / 替第三人申报 / 白名单外 / 本机不在白名单 / 坏形状 / 夹带凭据字段 / 坏地址 / 超限 / 过期)③ 🔴 `silentRejections = 0`(**返空又不计数 = 本线老病根 ⇒ 直接 FAIL**)④ `bucketsPerNetwork = true`(白名单**按网分桶**,跨网同名 hostId 互不可见)。⚠️ 准入实现必须是 `network.ts#isAllowedDialer`(= `server.ts` DIAL 白名单**同一条策略**)⇒ `impl` 字段点名实现;⛔ 直连模块里出现 `dialers.get(` = 抄了一份 ⇒ 由 `test/overlay-direct.test.mjs#T4` 守 |
| `OBS-28` | 🔴 **打洞「成功路径 + 失败判死 + 冷却非零」**(序㊵ · P2/S5 · 判据 **D5/D6**) | `DIRECT_PUNCH_MIN_OK` / `PUNCH_DEADLINE_MS` / `DIRECT_COOLDOWN_MS` | 数据源同上(`punch` / `deps` 段):① **成功路径** —— `success.bidirectional = true` **且** `attemptsOk ≥ DIRECT_PUNCH_MIN_OK` ② 🔴 **单向不算直连** —— `oneWay.bidirectional = false` **且** 两侧 `reason = one-way`("有收包"⛔ 不足以判成功)③ **失败判死** —— `dead[0].reason = deadline` **且** `bounded = true`(耗时 ≤ deadline + 3×重发间隔 ⇒ **有界**、⛔ 不无限重试)④ **冷却非零且生效** —— `cooldown.ms = DIRECT_COOLDOWN_MS`(🔴 **必须 > 0**,`0` ⇒ 重试风暴 ⇒ FAIL)**且** `secondAttemptBlocked = true`(二次尝试被挡且**不开 socket**)**且** `zeroRejected = throws`(构造期断言)⑤ **依赖面** —— `deps.ok = true`(`lib/net/relay/direct/*.js` 只 import `node:dgram` + 同模块相对路径)。⚠️ 打洞走**用户态 `dgram`**,⛔ 无内核驱动、⛔ 无虚拟网卡;**成功路径的机器断言由离线 NAT 模拟器承载**(云上真机腿被云安全组挡着 ⇒ 见 **D8** 与交接单 §2-3) |
| `OBS-29` | 🔴 **块 id 的 per-network 域分离(C)真生效**(序㊻ · `04-133 §3`) | **无新增阈值键**(五条腿全是布尔 ⇒ ⛔ 不造“凑数键”=不把判据变成配置) | 数据源 = **本机进程内自检**(直取 `lib/net/relay/content/*`;⛔ 零 ssh、⛔ 零生产依赖、夹具模式照跑):① **P1 正腿** = 同字节 + **不同 network** ⇒ 块 id **不同**,**且两侧都 ≠ 裸哈希**;② **P2 正腿** = 同 network + 同字节 ⇒ 块 id **相同**(⛔ 只测 ① 会漏掉“去重被干掉”);③ **P3 正腿** = `store` 写侧 `putRejected = 0` + 读侧 `corruptReads = 0` + `chunker#reassemble` 用同一把域密钥能重组回原内容 (⇒ **调用点无漏改**;🔴 漏一处 ⇒ 写侧复算必抛);④ 🔴 **N1 负腿(具名 `flat-key-collapses`)** = **去掉 `network` 维度**(两侧取同一 network ⇒ 同一把域密钥)⇒ ① 的谓词**必须转假**;⑤ 🔴 **N2 负腿(具名 `empty-key-falls-back-to-bare-hash`)** = `netKey` **取空** ⇒ 谓词**必须转假**,且**取空与生效必须可分**(`keyed ≠ bare`)。⛔ **正负两腿缺一不可**(本线老病根 = 装了没生效 = 静默放行)。⏳ **真机腿(回头条件 · 部署那一棒必须补)** = `/status.content` 的 `blockIdKeyed === true` 且 **47 / 106 的 `blockIdKeyId` 逐字相同**(跨机口径不一致 ⇒ 跨机取块**全部**判校验失败) |
**阈值取值(同为参数行,⛔ 不是脚本魔数)**
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `MIN_HOSTS` | 2 | 台 | 实测 | S0 P2 `/status.online[]`(manager + w-106) | `online.length` |
| `MIN_TRUSTED_SIGNERS` | 1 | 个 | 实测 | S0 P2 `trustedSigners = 1` | — |
| `MIN_IDENTITY_OK` | 2 | 次 | 实测 | S0 P2 `identityOk = 2`(⛔ 不是 3:序③ 注释里的 3 是三台节点**当时**的口径) | — |
| `MAX_REVOKED_HOSTS` | 0 | 个 | 实测 | S0 P2 `revokedHosts = 0`+`/etc/dshs/revocations.json` 空清单 | — |
| `MAX_AUTH_FAILED` | 50 | 次 | 推导 | 累计值会随时间涨(⚠️ 探针自己也污染它,见交接单 §5 S6);取"远大于稳态自然增长、又能挡住暴力破解"的档 | 经验上界 |
| `PROBE_CODE_SET` | 200,401 | — | 实测 | S0 P7a:两个面在无凭据时回 `401`;带门户 Host 时回 `200` | `curl -w '%{http_code}'` |
| `PORTAL_CODE` | 200 | — | 实测 | S0 P2/S6:`curl -s --http1.1 -o /dev/null -w '%{http_code}' -H "Host: ai1net.com" 127.0.0.1:3080/` | 同左 |
| `LISTEN_COUNT` | 79 | 行 | 实测 | ⛔ **已退役(序⑫ 集合判据替代)—— 仅对账用,不得再作为判据**。原 S0 P5:`ss -lntp \| wc -l`(与 443 单收口态**逐字一致** ✅;⚠️ 该值含"47 有活跃实例"那一档,故与无实例态恒差 `1` 层) | 同左 |
| `NFT_RULES` | 72 | 行 | 实测 | ⛔ **已退役(序⑫ 集合判据替代)—— 仅对账用,不得再作为判据**。原 S0 P5:`nft list ruleset \| wc -l`(与 443 单收口态**逐字一致** ✅;⚠️ **行数不是暴露面**:47 的 `input` 链**一条规则都没有**、policy=`accept`) | 同左 |
| `RELAY_RSS_MAX_KB` | 800000 | KB | 推导 | 由 §5 `MEM_BUDGET_MB` × 0.8 得 ≈ 800 MB(**不随 `MEM_PER_HOST_MB` 变**,故序⑥ 校准后仍保留该值)。⚠️ 原注"每 host 2 MB 假设的哨兵"**已作废**:实测斜率 46.7 KB/台 ⇒ 7515 台时预计 RSS ≈ `48078 + 46.7×7515` ≈ **390 MB**,本哨兵(781 MB)仍留 **2× 余量**;且因 per-stream 开销未测(§9),**运行期真正的操作约束就是本哨兵**,不再是 `--max-hosts` | `MEM_BUDGET_MB × 1000 × 0.8` |
**序⑲ 新增阈值键(`OBS-13/14/15` 的取数来源)**
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `PRESENCE_STEADY_FRAMES_MAX` | 0 | 帧 | 实测(口径 = 交接单 §6 **E1**) | 稳态"变化驱动"⇒ 无状态变化则一帧都不推 | 判据本身(帧数,⛔ 不是时长) |
| `PRESENCE_SAMPLE_HITS_MIN` | 1 | 次 | 推导 | 探针自己连读两次 `/status` ⇒ `ΔstatusHits` **至少 1**;用来把"读不到"与"确实为 0"分开(本线反复踩的假绿) | 探针一次采样 = 1 次读 |
| `PRESENCE_SAMPLE_GAP_MS` | 3000 | ms | 推导 | 必须 **≥ 3 × `PRESENCE_BATCH_MS`**(否则批窗口还没过去,增量无意义) | `PRESENCE_BATCH_MS × 3` = 1000 × 3 |
| `PRESENCE_STALE_P95_MAX_MS` | 15000 | ms | 推导(= 心跳周期) | D3:允许 5–15 s 陈旧;心跳每 `HB_SEC` 刷新一次 `lastSeen` ⇒ 稳态陈旧度的**上界就是心跳周期** | `HB_SEC × 1000` = 15 × 1000 |
**序㉑ 新增阈值键(`OBS-16` 的取数来源)**
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `PRESENCE_GATE_WINDOW_MS` | 60000 | ms | 推导 | 门窗口长度:必须 ≥ 4 × 轮询周期,否则判据无分辨力(轮询在跑也只看得到 0~1 次) | `RELAY_STATUS_POLL_MS × 12` = 5000 × 12 |
| `PRESENCE_GATE_HITS_MAX` | 1 | 次 | 推导 | 窗口内命中增量的上限 = 探针自身那一次(活性证明 ≥ 1 与"别人没拉" ≤ 1 由同一个数同时表达) | 探针一次采样 = 1 次读 |
> 🆕 **序⑫ 新增 4 键(`OBS-11` 集合判据的白名单,⛔ 取代 `LISTEN_COUNT` / `NFT_RULES`)**
> ⚠️ 值格**必须纯数字/纯 `地址:端口`**(夹注 ⇒ `NaN` ⇒ 假红);⛔ 值内**不得含 `|`、`*`、反引号**
> (探针 `cleanValue()` 会**剥掉 `*` 与反引号** ⇒ 写 `*:443` 会被改成 `:443` ⇒ 判据全红)。
| 键 | 值 | 单位 | 等级 | 来源定位 | 复算式 |
|---|---|---|---|---|---|
| `LISTEN_REQUIRED` | `0.0.0.0:22,[::]:22,0.0.0.0:80,0.0.0.0:443,0.0.0.0:888,127.0.0.1:3080,127.0.0.1:20080` | — | 实测 | 🆕 序⑫ S1:**必在集**(缺一条 ⇒ FAIL)。口径 = **平台工作必需 且 不受实例/端点在线态影响**的固定口。值按 47 上 `ss -lntp` **原文**逐条写;⚠️ `3080` 的口在 47 上是 `127.0.0.1:3080`(⛔ **不是** `0.0.0.0:3080` —— 原规划稿示例写错,以原文为准) | `ssh -p 22 bt-server "ss -lntp"` |
| `LISTEN_ALLOWED` | `0.0.0.0:58888,0.0.0.0:8765,127.0.0.1:15432,127.0.0.1:19100` | — | 实测 | 🆕 序⑫ S1:**允许集(非必在)** —— 出现合法、消失**不红**(只是某个可选项没开:宝塔面板口 / 管理 UI / 控制面 PG / 本机 worker agent) | 同上 |
| `LISTEN_ALLOWED_RANGES` | `127.0.0.1:20000-20999,127.0.0.1:21000-21999` | — | 实测 | 🆕 序⑫ S1:**区间允许集** = 两台 worker 的实例端口区间。⚠️ **span 取 1000**(⛔ 不是规划稿示例的 100):`dshs-worker` 单元实测 `DSHS_INSTANCE_PORT_BASE=20000`(47,经 `/proc/<pid>/environ`)/`=21000`(106,经 `/etc/dshs-worker.env`)+ 两者 `DSHS_INSTANCE_PORT_SPAN=1000` ⇒ 并集 `[20000,22000)` 正好落在 relay 声明窗口 `[19000,22000)`(`--base 19000 --span 3000`)**之内**。**拨号池⛔不在此键内**(复用 `DIAL_POOL_BOUND` 派生,避免两处漂移) | `ssh -p 22 bt-server "tr '\0' '\n' < /proc/$(ss -lntpH 'sport = :19100' \| grep -oP 'pid=\K[0-9]+' \| head -1)/environ \| grep INSTANCE_PORT"` |
| `NFT_ALLOW_INBOUND` | `tcp:22,tcp:80,tcp:443,tcp:888,tcp:3080,tcp:58888` | — | 实测 | 🆕 序⑫ S4:**入站 accept 白名单**(`nft -j` 归一后 `acceptSet ⊆` 本键)。归一形态 = `<proto>:<dport>`;「无 dport 匹配的 accept 规则」归一为 `<proto>:any` —— ⚠️ 47 的 `input` 链**目前 0 条规则** ⇒ 集合为 ∅、本键暂无标记位;**将来若出现必须显式加进来**(⛔ 不许放宽判据) | `ssh -p 22 bt-server "nft -j list ruleset"` |
> 📌 **成员归属判据(序⑫ S1 明文要求,防下一棒再吵)**:
> - **必在**(`LISTEN_REQUIRED`)= 它不在 ⇒ **平台本身坏了**(`sshd` / nginx `80`·`443` / 宝塔 `888` / 平台门户 `3080` / relay 回环口)
> - **允许**(`LISTEN_ALLOWED` + `_RANGES`)= 它不在 ⇒ 只是**某个可选项没开**(面板口 / 管理 UI / PG / worker agent / 实例档 / 拨号池)
> - 🔑 **"有/无实例态"⛔不编码进参数表**(那等于把"对状态敏感"这条缺陷写进单一来源 = 净退化,违 **R11**)⇒ 实例档直接进**允许区间**:出现不报、消失不红。
> ⚠️ **附注(序⑥ S3(b) 明文要求登记的 OBS 侧口径)**:relay `/status.sessions[].rttMs` 是**心跳往返**
> (含应用层处理与验签),**⛔ 不得当链路 RTT 用**,**⛔ 更不得据它下"跨云不可玩"的结论** ——
> 实测三方对比:ICMP 148 ms | TCP 握手 median 153 ms | relay 心跳 344–376 ms(**差 2.3×**)。
> 真判据请用 `overlay-jitter.cjs --icmp/--tcp`。
---
## §7 待测项汇总(⛔ 一个都不许编数;**与表内 `待测` 单元格一一对应**)
> 🔢 **计数口径(E1 判据)**:表内 `待测` 单元格 **4 个 → 0 个**(序⑥ 全部换成实测,见下表「结果」列)。
> 第 **5** 条是「**推导值待校准**」(`MEM_PER_HOST_MB`)—— 序⑥ 已校准为 **实测 0.06 MB/台**。
| # | 键 | 换测条件 | 归属 | 序⑥ 结果 |
|---|---|---|---|---|
| 1 | `HOLE_PUNCH_RATE_LOCAL` | 3–5 台真机(分层:家宽 / CGNAT / 移动 / 企业网) | 序⑥ | ✅ **实测(分层)**:n=3 对,2/2 对可打洞(对端→本机 10/10);⚠️ 首选观察器不可用(云安全组拦 UDP 入站)⇒ 走第三方 STUN;**不代表家宽场景** |
| 2 | `PER_PLAYER_BW_LOCAL` | 同上 | 序⑥ | ✅ **实测**:9.8 KB/s/玩家(n=10)/ 3.9(n=50);聚合天花板 200–350 KB/s |
| 3 | `WAN_STEADY_THROUGHPUT` | 两端可控载荷(本次两个面在无凭据时只回 24–68 B) | 序⑥ | ✅ **实测**:352 KB/s(106→47)/ 12213 KB/s(47→106),5 样本中位数,经 relay,稳态段 30 s |
| 4 | `JITTER_LINK_MEASURED` | 同上;⚠️ 这是**游戏可玩性的决定项**(`调研…:118`) | 序⑥ | ✅ **实测**:`p95(|ΔRTT|)` = **3 ms ⇒ 达标**(限值 20 ms 属估值口径) |
| 5 | `MEM_PER_HOST_MB`(**推导值待校准**) | 压到 ≥ 20 台再量 RSS 斜率 | 序⑥ | ✅ **实测**:N=2..150 六点,斜率 **46.7 KB/台**,**R²=0.942**;原推导 2 MB **高估 36×** |
> ✅ **序⑨ 收口(2026-09-17 14:xx)**:**无新增待测项**(表内 `待测` 仍为 **0** 个)。本轮新增的两个键
> (`DRILL_SAMPLE_N` = 5 / `RELAY_GRACEFUL_BURST_MS` = 15000)**都是实测/推导值**,⛔ 不含 `待测`;
> `DRILL_POLL_MS` 由 2000 改 500 属**测量修正**(剔掉 ≤2 s 量化误差),⛔ 不是待测项换测。
> 另:序⑨ 的四段分解登记在 **§9 第 9 行**(属"已知边界/实测记录",不进本表 §7 的"待测"口径)。
> 🆕 **序㉛(2026-09-18)复算**:仍为 `待测` **0 项** —— 序㉛ 是「补测 + 口径定稿」性质,**⛔ 未新增任何键、⛔ 未改任何值**。本棒新测的 4 项(加密性能开销 / 轮换代价 / 低熵明文定性 / 过渡窗口三档)是**验收读数**,一律落在 **§11.3 补记区**(= §10 指纹口径之外),⛔ 不冒充"待测项换真值"。
**合计:`待测` 4 项 → 0 项;待校准推导 1 项 → 已校准。(E1 判据达标)**
> 🆕 **序⑦(2026-09-17)复算**:仍为 `待测` **0 项** —— 序⑦ 新增的 `RELAY_FAILOVER_*` 5 键**全部取默认值(= 源码真值)**,不产生新的「待测」;同时 §9 第 5 行(无失败切流)改判**已闭环**。⛔ §9 第 6 行(per-stream 内存)**仍未测**,仍是序⑦ 之后的回头条件。
>
> 🆕 **序⑧(2026-09-17)复算**:仍为 `待测` **0 项** —— 序⑧ 新增 3 键(`RELAY_FAILOVER_EXEMPT` = 源码默认值 `1`;
> `DRILL_COOLDOWN_MS` / `DRILL_NO_SWITCH_OBSERVE_MS` = **演练脚本自用的推导值**)**均不产生「待测」**。
> ⚠️ 两条**口径**要记住:① `DRILL_COOLDOWN_MS` 是**演练期覆盖值**,⛔ 不是生产值(生产恒为 `RELAY_FAILOVER_COOLDOWN_MS=300000`);
> ② 幕 4b 的结论**标注了非生产冷却值**方可复现(D9)。§9 第 5 行追加"冷却语义拆分已闭环";§9 第 6 行**仍未测**。
> 🔴 **回头条件(已执行,2026-09-17 序⑥ S7)**:`MEM_PER_HOST_MB` 与 `WAN_STEADY_THROUGHPUT` **双双换成实测** ⇒ §5.2 已**重算并重下发**两台中继的 `capacity.conf`(225 → 7515),三条校验已重跑,`OBS-02` 复验 PASS。⛔ 未触发项:无(两项都换成了实测)。
---
- 🆕 **序㊱ 复算(仍 0 项)**:`OBS-24` / `OBS-25` 两条**不引入任何数值阈值**(`NODES_REGISTRY_FILE` / `NODES_SELFCHECK_FILE` 是**路径信息键**;`JOIN_STEPS_MIN` = 4 与 `JOIN_NAMED_FAILURES_MIN` = 4 由**源码常量**直接取得,⛔ 非推导)⇒ 本表 `待测` 单元格**仍为 0 个**。
## §8 权限影响评估(R5;**只有产出,无动作**)
> 交接单 §4.1-7 的**固定 6 列**:`对象 / 是否扩大权限面 / 扩大到哪一类 / 是否已可收窄 / 证据 / 结论`。
> ⛔ **结论列只允许「收窄」或「维持」**;若某项确需扩大 ⇒ 停下报告(命中 R5)。
| # | 对象 | 是否扩大 | 扩大到哪一类 | 是否已可收窄 | 证据(代码行 / 命令原文) | 结论 |
|---|---|---|---|---|---|---|
| ① | **虚拟网卡驱动**(打洞所需) | **否(本单零动作)** | 若将来做 ⇒ **权限位**(管理员/内核态驱动) | ✅ 可收窄:**不做虚拟网卡**(纯 relay 中继形态已闭环),要打洞也只走 **UDP 用户态**(无驱动) | 本表 §3.2 `HOLE_PUNCH_RATE_LOCAL` = 待测;现网 2 节点全走 relay(`/status.networks[0].sessions = [manager, w-106]`);⚠️ 我方 relay 只做 TCP/WS,**代码里没有任何 TUN/TAP 调用**(`grep -rn "tun\|tap\|ip tuntap" src/net/relay/` = 0 命中) | **维持** |
| ② | **骨干节点开端口** | **否** | — | ✅ 已收窄:骨干成员**只拨出、不开入站**(worker 永远只拨出,106 入站 = 0) | 主单元 ExecStart 无 `--host` ⇒ 默认绑 `127.0.0.1`;实测 `ss -lntp` 中 `20080` 只出现在 `127.0.0.1:20080`;`覆盖网络_骨干层方案_20260916.md`「骨干不得被默认征用」 | **维持** |
| ③ | **`nft` 打洞规则** | **否(本单零动作)** | 若将来做 ⇒ **入站面**(放行 UDP 入站) | ✅ 可收窄:现实测 `nft list ruleset` = **72 行**且与 443 单收口态逐字一致 ⇒ 打洞规则**一条都没加** | S0 P5:`ssh -p 22 bt-server 'nft list ruleset \| wc -l'` → `72`;对比交接单 §6 E9 的收口态 `72` | **维持** |
| ④ | **第二中继机(L3 跨机真容灾)** | **否(已实施;R5 要求的"实施态"逐项如下)** | 若实施 ⇒ 入站面(新机新公网口)+凭据面(新节点密钥签发) | ✅ **已实施且两项都没扩大**:**新增监听口 = 0**(relay 仍只绑 `127.0.0.1:20080`;对外**复用 106 既有 nginx 的 443**,证书 = 本机**既有** Let's Encrypt(SAN 覆盖 IP `106.54.21.172`),⛔ 未下发任何证书/私钥)|**新增凭据 = 0**(relay 侧只需**公钥类**文件:`relay-keys.json`、`overlay-signers.json`、`revocations.json`;⛔ `node-*.key` 与 `overlay-signer-key.pem` **一律留在 47**)|**106"入站 = 0"这条已收窄成果**:443 本来就开着(宝塔 nginx),本次**只在既有 server 块内加一条 `location`** ⇒ `ss -lntp` 计数**未变** | `systemctl cat dshs-relay`(106)|`ss -lntp`|106 的 `extension/106.54.21.172/relay-b.conf` | **维持** |
| ⑧ | **临时 UDP 入站面**(序⑥ S4 打洞探测的观察器) | **是(临时,已关闭)** | 入站面(UDP 高位口) | ✅ **已收窄**:对象 = 47 的 `0.0.0.0:21100/21101`(`overlay-holepunch.cjs --observer`),**开放时长 ≈ 75 s**、进程退出即释放;关闭证据 = `ss -lunp \| grep 2110[01]` ⇒ **空**。⚠️ 实测该口**零收包**(连同机发出的都收不到)⇒ 47 的**云安全组拦 UDP 入站**(⛔ 不是 nft:`input policy = accept`)⇒ S4 改走第三方 STUN | `ss -lunp`(空)|本单 §8.4 E6 | **维持** |
| ⑨ | **106 的 443 relay 路由**(序⑥ S8 新增 `location /dshs-relay`) | **是(新增可路由路径,⛔ 未新增监听口)** | 入站**路径**面(不是新口) | ✅ 已收窄:只放行 `/dshs-relay` **一个端点**(⛔ 不写 `location /`、不复制站点任何路径;未知路径 404);relay 侧仍强制 HMAC +节点凭据(`identityRequired = true`) | 本单 §8.6 + `curl -H "Upgrade: websocket" … https://106.54.21.172/dshs-relay` = **101**|`/nope` = **404** | **维持(监听口零新增)** |
| ⑤ | **443 兜底入口**(复核是否真零扩大) | **否** | — | ✅ **已收窄**(相对 sshd 时代**净减 1 个公网口**) | 实测:`32022` 已回收(`ss -lntp` 无该口);443 由 **nginx 复用**(`ss -lntp` 显示 `0.0.0.0:443` + `nginx` 4 个进程),**未新增监听**;`/etc/dshs.env` 的 `DSHS_OVERLAY_BOOTSTRAP_SEEDS` 含 `relay-direct.ai1net.com/dshs-relay`,而 `DSHS_OVERLAY_ADDR_OVERRIDES=relay-direct.ai1net.com=47.77.182.89` ⇒ 走的是**既有** 443 | **维持** |
| ⑥ | **relay 拨号白名单 `dialers`**(R5 引入) | **否** | — | ✅ **已收窄**:白名单是**准入收窄**(默认拒绝,⛔ 不是放开);且**构造时定型、运行期不可改** | `src/net/relay/server.ts:355` `private readonly dialers`;`:404` `normalizeDialers()` 在构造期;`:747` `const wantDialer = this.dialers.get(network)?.has(hostId) === true`(默认拒绝);drop-in `dialers.conf` = `Environment="DSHS_RELAY_DIALERS=ops:manager"`(只有 **1** 个拨号方) | **维持** |
| ⑦ | **每机独立密钥与信任根保管** | **否** | — | ✅ **已收窄**:私钥**只在本机**、信任根在**离线**签发;relay 侧只有**公钥**与**签名者集合** | `/etc/dshs.env` `DSHS_OVERLAY_NODE_KEY_FILE=/etc/dshs/node-manager.key`(**私钥路径仅本机**);drop-in `identity.conf` 里**只有** `DSHS_OVERLAY_ROOT_PUBKEYS=<公钥>` + `DSHS_OVERLAY_SIGNER_SET_FILE` ⇒ **私钥从不出现在 relay 的配置面**;`:399` `this.trustedSignerKeys`/`:401` `this.requireIdentity` 均 `readonly` | **维持** |
| ⑩ | **P2 直连(打洞)的 UDP 入站面**(🆕 2026-09-18 12:22 **用户已拍板**) | **是**(本期唯一"扩大"项;**已按拍板收窄到最小形态**) | 入站面(云安全组 **UDP 小段**)+ 节点侧 **UDP socket**(**用户态、无内核驱动** —— 与 ① 的"不做虚拟网卡"一致) | ✅ **已收窄**:① **来源范围 = 乙**(**仅放行对端节点 IP**,⛔ **非** `0.0.0.0/0`)② **用户可设置** + **默认开启** + **提示用户**(用户口径)③ **关闭后零 UDP socket**(可断言 `ss -lunp` 无新增)④ ⛔ 不新增任何"对公网任意源开放"的规则 | 拍板原话 = `1 用户可设置,默认开启提示用户 2 乙`(2026-09-18 12:22)|执行依据 = `交接单_覆盖网络直连与P2P_20260918.md` **§7**(判据 **D8**)|⚠️ 技术现实:对端为**家宽 / 移动**时公网出口 IP **会变** ⇒ 序㊴ 自决 **乙-1 起步**(只对**有固定公网 IP** 的节点开;家宽节点本期走中继),乙-2 / 乙-3(🔴 需云 AK/SK)登记为该单 §9 回头条件 | **扩大(经用户拍板 · 形态已收窄)** |
**两条附注**
- ⚠️ 本表 §5.1 的「**每 host 0.06 MB**」是**空闲会话**的实测斜率 ⇒ 它不覆盖 per-stream 缓冲(见 §9 新登记行)。
- ⛔ 本表口径仍成立:**零新增公网端口、零新增入站面、零凭据外发**;序⑥ 的两处"潜在扩大项"(④ 第二中继 / ⑧ 临时 UDP / ⑨ 443 路由)**逐条列在表内**,结论均为**维持**或**收窄**。
- 🆕 **一条口径更新(2026-09-18 12:22)**:**⑩ P2 直连的 UDP 入站面**已由**用户拍板**(「用户可设置 + 默认开启 + 提示用户」+ 来源范围 **乙**)⇒ 上一条的"零新增入站面"**自此只对已收口部分成立**;**⑩ = 本期唯一经拍板的扩大项**,形态已被拍板收窄(**仅对端 IP、用户可关、零内核驱动**)。⛔ **截止本行尚未实施、零动作**(开工依据 = `交接单_覆盖网络直连与P2P_20260918.md`)。
---
## §9 未纳入本表的已知边界(**登记,不改**)
| # | 边界 | 为什么不动 | 归属 |
|---|---|---|---|
| 1 | 引导链缓存两支无"答出者"信息 ⇒ 切兜底有 ≤ `DIRECTORY_REFRESH_SECONDS`(300 s)**收敛期** | 交接单 §8.7④ 明确"本单把 300 s 收进参数表并写明该收敛期,⛔ 不改缓存结构" | 已闭环(本表 §4) |
| 2 | 106 的 agent 面不吃引导链(`DSHS_RENDEZVOUS_URL` 被当 relay URL 直接用) | 交接单 §2 P8:**只记录、⛔ 不许顺手撤**(撤掉 = `tunnel===undefined` 生产回归) | 若要改,归序⑥ |
| 3 | D2 字面判据不可满足(CF 泛解析) | 未做灰云记录 ⇒ 属"解析层也不经 CF"的更大改动 | 序⑥ 后候选 |
| 4 | relay 进程**没有** `MemoryMax`(只有本表的软口径 `--max-hosts`) | 加 cgroup 内存上限 = 改单元语义 + 可能 OOM-kill relay(新失败模式)⇒ **超出本单范围**,且 R11(不许净变差) | 序⑥ 候选(与 `MEM_PER_HOST_MB` 校准一起做)→ ⚠️ 序⑥ 只做了**测量与登记**,**仍未加** `MemoryMax`(同上理由,且实测后 `--max-hosts` 已非绑定约束) |
| 5 | 🔴 **无"失败切流"**(序⑥ E9 后半判红的**根因**):客户端把 relay url **在首次解析后钉死** —— `main.js --client` 无重解析;worker 走 `DSHS_RENDEZVOUS_URL` 同样不吃引导链;只有 Manager 的**拨号通道**有周期性重解析(`web/server.ts#refreshOverlay`),而它的换址条件是"**目录里的地址变了**",与"当前 relay 挂了"**无关** | 修它 = **新功能**(需"连接失败后重解析 + 排除已失败 relay"),⛔ 属单外发现,只报告不动手(R7) | ✅ **已闭环(序⑦ · 2026-09-17)**:候选集不再退化成单点(`listOverlayRelayCandidates`);三处客户端(Manager 拨号 / worker 实例面 / `relay --client`)接同一个 `RelayFailoverSupervisor`;阈值见本表 §4 `RELAY_FAILOVER_*`。⛔ 服务端零改动(D8)**|序⑧:冷却语义拆分已闭环**(2026-09-17)—— 冷却表结构化(键仍按 url,新增 `kind`)+ `replace()` 增 `origin`(health 可一跳豁免 / directory ⛔ 不可)+ 豁免有界(每 url 每冷却周期一次)+ 双开关与判别器(`RELAY_FAILOVER_EXEMPT`、`exemptSwitches`、`|豁免` 日志标记)|真机判据 = 演练幕 4 系列(`--scene 4 / 4b / 4c`) |
| 6 | **per-stream 内存开销未测**:`MEM_PER_HOST_MB` = 0.06 MB/台 只是**空闲会话**斜率;带流量时每条流最多 `QUEUE_MAX_BYTES`(1 MB) 缓冲 | 需带流量的压测(S6 的已定口径是"每个声明 1–2 个端口的空闲会话") | 序⑦ 候选(**回头条件**:`--max-hosts` 若重新收紧,必须先有本数) |
| 7 | 106 的第二中继**不提供** `/dshs-overlay/bootstrap`(目录由 47 的 `127.0.0.1:3080` 签发;实测 3080 只绑回环、106→47:3080 `TCP_BLOCKED`) | 两条绕法都有代价:走 47:443(**同一失败域**,等于没增益)/搬证书私钥(命中 R5 扩大) | **已登记**;功能影响 = 0(客户端 `resolveOverlayRelay` ③ 对取不到的 origin `continue`,且 `relays[]` 首位是主入口) |
| 8 | 106 的 443 vhost 归**宝塔面板**管理:本次把 relay location 放进 `extension/106.54.21.172/*.conf`(面板重写 vhost 主文件**不会**丢它) | ⛔ 若将来面板重建该站点,需复查 `nginx -T \| grep dshs-relay` | 运维注意(已写进 `relay-b.conf` 头部注释) |
| 9 | **换址墙钟的四段分解**(序⑨ 实测 · 2026-09-17,n=5+n=5 真机样本,逐行对齐 journal 时间戳):**检测 15.0–16.6 s**(= `RELAY_GRACEFUL_BURST_MS` 地板 + 0–0.7 s 重试相位)+ **首试延迟 0.3–5.0 s**(`RELAY_FAILOVER_CHECK_MS` 相位 + 拨号耗时)+ **白等 12.02 s → 0.09 s**(修前 `waitUpOn` 对"必然失败的同机候选"吃满 `upTimeoutMs`;修后终态早退)+ **建连 2.7–5.2 s** ⇒ 总 **30.7–34.1 s → 20.9–23.9 s**(`RELAY_FAILOVER_DEADLINE_MS` 30000 内侧 5/5) | 白等段**已修**(`waitUpOnStatus` 终态早退,三处装配点共用一份);检测段 ⛔ **不改**(D3:15 s 的 burst 窗口是"计划内下线不是故障"的有意设计,改它 ⇒ 每次 relay 重启都切流) | ✅ **序⑨ 已闭环**(本表 §4 的 `RELAY_GRACEFUL_BURST_MS` / `RELAY_FAILOVER_UP_TIMEOUT_MS`;复算工具 = `scripts/overlay-failover-drill.cjs --trace`) |
---
## §10 指纹
- **本节口径**(**推荐核对用**,可复现):**整个 §10 不计入** ⇒ 复核命令
`sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum` ⇒ **`075a97239c2d1d8a1f442ec6cc787ede`**(**序㊿ 执行棒 · 2026-09-19 07:5x · 旧域移除棒**;本轮变更 = §3.9 **`PORTAL_HOST_HEADER` 值格** `alotbuy.com` → `ai1net.com`、**`DRILL_KILLED_MATCH` 值格**同改(🔴 演练脚本幕4-A/幕4-B 的判据就是 `includes(<本键值>)` ⇒ 不改本键 **必假红**)+ 两处 `-H "Host: alotbuy.com"` curl 示例 + §6 一条候选摘名说明(`relay-direct` 前缀由 `alotbuy.com` 改 `ai1net.com`);🔴 **零个新增 / 改动生产 env · ⛔ 未改任何阈值**(`DSHS_OVERLAY_BOOTSTRAP_SEEDS` 与 `DSHS_OVERLAY_ADDR_OVERRIDES` 走 drop-in,本表只作事实记录);**上一版 = `ac6bbbbb8c92bd57ff0dc4cd8f4983ba`** —— 其原说明照抄如下:**序㊻ 执行棒 · 2026-09-18 22:4x**;本轮变更 = §6 **新增 `OBS-29` 一行**(块 id per-network 域分离 C:五条腿 + 两条负腿具名;🔴 本行是**判据重裁**的产物 —— 原 `OBS-29` 的计划负腿(「稀释源换成确定性派生量 ⇒ 必红」)随 **D 被实测判为「作用面为空」**(序㊺)而**失去对象**);⚠️ **顺带纠正一条既有漂移**:本行原记 `d408d640246a980f702fe7b0a2895219`(序㊴ 12:2x),而序㊺ **两次独立现算**均为 `6b37bfd506758d882d9f803678f85d23` —— 原因是 **§11 补记在 §10 口径之外**(`sed` 在 `## §10 指纹` 处截断)⇒ ⛔ **不是算错、是漏更新** ⇒ 本次一并同步。🔴 **零个新增 / 改动生产 env · ⛔ 未改任何阈值 · ⛔ 值格一律未动**(只加判据行);**上一版(记录值)** = **`d408d640246a980f702fe7b0a2895219`**(**序㊴ 规划棒 · 2026-09-18 12:2x**;本轮变更 = §8 新增 **⑩ P2 直连的 UDP 入站面**一行(🆕 用户 **12:22 已拍板**:「用户可设置 + 默认开启 + 提示用户」+ 来源范围 **乙**)+ §8 附注补一条**口径更新**("零新增入站面"自此**只对已收口部分**成立);🔴 **零个新增 / 改动生产 env** —— ⑩ **仅登记**,⛔ **尚未实施、零动作**;**上一版** = `2275870a5a9eb1964a648f2950dee528`(**序㊳ 执行棒 · 2026-09-18 11:4x**;本轮变更 = ① §3.9 **`DRILL_RELAY_UNIT` 改名为 `RELAY_UNIT_NAME`**(键名与用途对齐 —— 它记的是"两台机的 relay 单元名"这一**事实**、被演练脚本与探针 `OBS-08` **共用** ⇒ 原 `DRILL_` 前缀不相称;⚠️ **值格未动**、仍 `dshs-relay`)② §3.9 小节标题补注(该键也被探针读取)③ §6 `OBS-24` / `OBS-25` 两行补「**夹具模式缺本夹具 ⇒ SKIP + 留痕**(⛔ 不去 ssh 读生产)」。🔴 **零个新增 / 改动生产 env · ⛔ 未改任何阈值 · ⛔ 值格一律未动**;**上一版** = `71848fad4ee0d6a574d833169e042fd1`(**序㊲ 执行棒 · 2026-09-18 11:1x**;本轮变更 = §6 `OBS-08` / `OBS-09` 两行**判据口径改写**(`OBS-09` 的在册判据改为**从 `/status.endpoints[]` 派生**;`OBS-08` 加「**空表可分**」)+ §3.9 四行标注(`W47_AGENT_PORT` / `PEER_AGENT_PORT` = 「序㊲ 起被探针引用」;`LOCAL_INSTANCE_PORT` / `PEER_INSTANCE_PORT` = 「序㊲ 起探针⛔ 不再引用」)。🔴 **值格一律未动 · ⛔ 零个新增 / 改动生产 env · ⛔ 未改任何阈值**(只改判据与说明);**上一版** = **`2ae954e2e07b47651a84e597c19a3444`**(序㊱ 收口复核棒 · 2026-09-18 10:3x;其变更 = §3.9 `PEER_INSTANCE_PORT` **值格 21000 → 21001**(**实测替换**,机理见该行注)+ `PEER_AGENT_PORT` 出处括注同步为 `ports=19000/21001`;⚠️ **两键都只被探针 / 本表读取 ⇒ ⛔ 零个新增 / 改动生产 env、⛔ 未改任何阈值**;上一版 = **`695dcb4d88a778840322bc221417d339`**(序 ㊱ 执行棒收口值 · 2026-09-18 10:0x,其变更 = §3.10 新增 **8 键**(`NODES_REGISTRY_DIR` / `NODES_REGISTRY_FILE` / `NODES_CONSUMED_DIR` / `NODES_SELFCHECK_FILE` / `NODES_EMERGENCY_DROPIN` / `INVITE_TTL_MIN` = 30 / `JOIN_STEPS_MIN` = 4 / `JOIN_NAMED_FAILURES_MIN` = 4)+ §6 新增 `OBS-24` / `OBS-25` 两行 + §7 补一行"序㊱ 复算(仍 0 项)" + §3.10 `NODES_REGISTRY_FILE` 行补「控制面 CLI 覆盖键 = `--registry`(⛔ 不是 `--file`)」与一条真机键名混用教训;⚠️ **8 键全部只被探针 / 控制面 CLI 读取 ⇒ ⛔ 零个新增生产 env**;上一版 = **`52e59df229102d5afa8c8bf01a960398`**(序 ㉛ 执行棒收口值 · 2026-09-18 07:4x;本轮变更 = **⛔ 零键零值变更** —— 只在 §3.9 `CONTENT_EPOCH_GRACE_MS` 行的**说明列**补"序㉛ 三档实测曲线"、§6 `OBS-23` 行末补「口径定稿」、§7 补一行"序㉛ 复算(仍 0 项)";⚠️ 值格一律未动 ⇒ 探针取阈值不受影响);再上一版 = **`08c2835a7e9f0faef20f9c1d3e619d55`**(序 ㉘ → 单 B 执行棒收口,其变更 = §3.9 新增 **6 键**(`CONTENT_KEY_FILE` / `CONTENT_KEY_FILE_MODE` / `CONTENT_EPOCH_GRACE_MS` / `CONTENT_CRYPTO_DET_MIN` / `CONTENT_CRYPTO_DECRYPT_MIN` / `CONTENT_CRYPTO_SCAN_MIN`)+ §6 新增 `OBS-23` 一行;再上一版 = **`3b295a8c43aafc4a62c6f1a59ea0b43a`**(序㉚ 收口,其变更 = §3.8 新增 6 键 `TEARDOWN_GUARD_MARKER` / `TEARDOWN_FORBIDDEN_TOKENS` / `TEARDOWN_LIB_47` / `TEARDOWN_LIB_106` / `TEARDOWN_UNIT_47` / `TEARDOWN_UNIT_106` + §6 新增 `OBS-22` 一行)。🔴 **本单新增的生产 env 只有 1 个**(relay 侧 `DSHS_CONTENT_GROUP_KEY_FILE`)—— §3.9 那 6 键**只被探针读取**,⛔ 不写进任何 drop-in / env)
- **上一版(序㉛ 收口)留档** = **`52e59df229102d5afa8c8bf01a960398`**(序㉛ 执行棒收口值 · 2026-09-18 07:4x)—— 该版口径与完整回落链**原文照抄**如下,供对照:该版变更 = **⛔ 零键零值变更**(只在 §3.9 / §6 / §7 的**说明文字**上补实测曲线与"复算仍 0 项")。
- **全文件 md5**:请用 `md5sum 参数表_覆盖网络_20260917.md` 现取 —— ⛔ 此处**故意不内嵌数值**:它包含本节自身,写进去即刻失效(自指)。
---
## §11 补记(2026-09-17 11:1x,**在 §10 指纹口径之外**)
- ✅ **"第 4/5 台真机来源"已拍板**(登记给 §9 第 5 行那条线的同批遗留):用户原话「**1 本机内存大 可以模拟多台**」⇒ 选 **本机模拟多台**,放弃"用户自备设备"与"新开云主机"。
- 本机实测:总内存 **47.6 GB** / 空闲 27.4 GB / **32 核**;relay 单实例 ≈ **48 MB** ⇒ 可模拟数十台。⚠️ **共用同一出口 IP** ⇒ 对"切流逻辑"够用,对"家宽 / 运营商 NAT 差异"无增量。
- ⛔ **不改本表任何数值**:`HOLE_PUNCH_RATE_LOCAL`(分层实测 2/2)与其样本口径**保持原样**;本补记只登记"多实例可作第 4/5 台"这一环境决定。
- 🔒 本补记位于 §10 之后 ⇒ **复核指纹 `db1317c2f7aaef7b47785c1f4fc9de03` 仍然有效**(后续引用无需换值)。
- **谁能改这张表**:任何一次实测替换(`待测` 换真值 / `MEM_PER_HOST_MB` 校准)**都会改指纹** ⇒ 改完请**同步更新本值**,并在工作区日志里记一笔"哪个键从什么换成什么"。
### §11.1 补记(2026-09-18 00:2x,序㉗ 执行棒;**同在 §10 指纹口径之外**)
- 🔴 **`CAND_OBS_UNITS_47` 首轮写错 ⇒ 已在真机首轮修正**(假红):初版误列 `dshs-worker=worker`。取证 = `ssh -p 22 bt-server "grep -c DSHS_RENDEZVOUS_URL /etc/dshs-worker.env"` ⇒ **0**、`systemctl show dshs-worker -p Environment | grep -c RENDEZVOUS` ⇒ **0** ⇒ 47 的 worker **根本不是 relay 客户端**(不建 `RelayTunnel`),列进去只会读到"永远无观测行"= 假红。现值 = `dshs=manager`(✅ 真机 `[overlay-candidates] scope=manager … count=3` 已核到)。
- 🔴 **`hosts` 的语义 = 「主机名个数」**(⛔ **不是独立物理路径数**):真机首轮 `count=3` 时 `hosts` 也报 **3**,因为 `ai1net.com` 与 `relay-direct.ai1net.com` **摘名不同、同落 47**;观测器**刻意不解析 DNS**(零网络 I/O)。⇒ 本表 §6 `OBS-21` 已写明"这个数只作信息输出、⛔ 不作判据"。
- ⚠️ **`OBS-21` 开局为红,且这是"如实判"而非缺陷**:`dshs@47 count=3 ✓` | `dshs-worker@106 count=1 ❌`(`source=chain`)。**106 侧根因已在 106 自己的日志里可机读**:`journalctl -u dshs-worker | grep overlay-dir` ⇒ 近 3h **166 行**,样本 = `⚠ 拒绝 https://alotbuy.com/dshs-overlay/bootstrap(no-trusted-keys)` + `⚠ 取目录全部失败 ⇒ 回落到内置种子地址本身:wss://alotbuy.com/dshs-relay`;取证 = `/etc/dshs-worker.env` 里 `DSHS_OVERLAY_DIR_PUBKEYS` 计数 **0**、`DSHS_OVERLAY_DIR_CACHE` 计数 **0**。⇒ **「判据成立」✓ 而「冗余建成」✗**,已按用户口径**点名**为独立后续项(⛔ 未放宽 `CAND_MIN` 凑绿)。
- ⛔ **本补记不改本表任何数值**(`CAND_MIN` = 2 是用户口径「每连接候选数 ≥ 2」的落地,⛔ 不因"当前建不成"而下调)。
- 🔁 **状态已改判(2026-09-18 06:1x,序㉙ 执行棒)⇒ 见 §11.2**:上面这条"开局为红"是**当时的事实**;现 `OBS-21` **绿**(两路径 `count=3`)。
### §11.2 补记(2026-09-18 06:1x,序㉙ 执行棒;**同在 §10 指纹口径之外**)
- 🔁 **状态改判:`OBS-21` 红 → 绿**。真机读数 = `dshs@47 count=3 hosts=3 ✓` / `dshs-worker@106 count=3 hosts=3 ✓`(**两路径都 ≥ `CAND_MIN`=2**)⇒ 探针 **22 PASS / 0 SKIP / 0 FAIL(rc=0)**。⛔ **未放宽 `CAND_MIN`、未改本表任何数值**。
- **改动(全序唯一一处)**:106 `/etc/dshs-worker.env` **追加** `DSHS_OVERLAY_DIR_PUBKEYS=fd81a6dd7ee22cc121239253d9bee33585638823f5425b537da930e5054f3349`(与 47 同一把)。**47 侧真值来源** = `/etc/systemd/system/dshs.service.d/overlay-dir.conf`(私钥 `DSHS_OVERLAY_DIR_KEY=/etc/dshs/overlay-dir-key.pem`、公钥文件 `/etc/dshs/overlay-dir-key.pub` 同值)。⛔ **未加 `DSHS_OVERLAY_DIR_CACHE`**(见下第 4 条)。
- 🔬 **先取证再动手 —— "106 取目录的两条路径"各自取证(⛔ 未假设"补钥即绿")**:**(a) 公网路径** `https://alotbuy.com/dshs-overlay/bootstrap` ⇒ 106 上 `curl` = **http 200 / remote `172.67.194.206`(Cloudflare)/ 443 字节**,且 106 日志的失败原因码是 **`no-trusted-keys`** —— 该码在 `directory.ts#fetchDirectory` 里**位于 `fetch` → `res.json()` → `parseDirectory` 之后** ⇒ **"取到了、只是验不过"**,可达性**成立**。**(b) 直连 `http://47.77.182.89:3080/...`** ⇒ `curl` **timeout(000)**(= 已知 `TCP_BLOCKED` 边界),⚠️ **但取目录流程根本不用 3080**(走域名 → 443 → nginx → 回环 3080)⇒ **该边界不构成阻塞**。
- ⚠️ **`DSHS_OVERLAY_DIR_CACHE` 106 仍为 0(如实登记,⛔ 按"只做被明确要求的事"未扩范围)**:47 的该键**同样为 0** 且 47 侧 `OBS-21` 一直绿 ⇒ 本序**与"已验证有效"的 47 配置逐项对齐**。缺它的后果 = 目录不可达时**没有 ④「过期缓存」档**、直接落 ⑤ 种子兜底(候选退回 1 条)⇒ 属**冗余韧性**而非本项判据;若要"CF 抖动期也保住 ≥2",需单独立项(⛔ 本序不顺手做)。
- ⚠️ **一条既有缺陷的实测补强(⛔ 非本序引入,⛔ 本序不改码)**:**孤儿端点条目不会自愈** —— 序㉚ 记的"替换窗口内短暂留 1 条 stale 端点条目(**自愈时延未测**)"本序给出答案:`relay-tunnel.ts#cancel()` 只撤销**当前进程** `forwarded` 集里的端口,**上一进程遗留的条目无人撤销** ⇒ 只能靠**后续一次"重新登记同一端口号"**把它覆盖回 `online=true`。**实测**:重启 106 `dshs-worker` 后旧条目 `21000→41831` **恒 `online=false`(约 10 min 未自愈)**;停掉实例让 Manager 自动重新拉起 ⇒ 新实例落回 **21000** ⇒ 条目被覆盖、转绿。⇒ **触发条件 = 上一进程的端点且该端口未被新进程重新声明**;判据影响 = `OBS-08`/`OBS-09` 在该窗口内会红(**重启后必现**),⛔ 不是"跑一会儿就好"。
- **回滚点** = 106 `/opt/dsh/backups/seq29-20260918-054852/dshs-worker.env.orig`(md5 **`6b509ce447d9519241aa73243e07a69f`**);回滚 = 删该行 → `systemctl restart dshs-worker`(秒级)。
### §11.3 补记(2026-09-18 07:3x,序㉛ 执行棒 · 单 B §10 未验证项补齐 + 口径定稿;**同在 §10 指纹口径之外**)
> 本节的数**全部是原始读数**(脚本原文落盘于工作区 `_tmp_seq32/`)⇒ ⛔ 不是估值、⛔ 未做任何粉饰;⛔ **本表任何数值未被改动**(§3.9 / §6 只增"实测依据"与"口径定稿"文字说明,值格与键表零变更)。
**① 加密性能开销(原 §10-1;`_tmp_seq32/p01-perf.txt`)** —— 纯本地、零网络。臂 A = 明文、臂 B = 组密钥加密;同一份 **11,363,655 B**(= `chunker.ts` 首屏包实测值)⇒ **11 块**;预热 2 次不计,正式 **n = 11**;分位 = **nearest-rank(⛔ 不插值)**;机 = 本机 AMD Ryzen 9 7950X / 32 逻辑核 / **Node v22.22.2**。
- 总墙钟(切块+〔加密〕+put + 链取回+〔解密〕+拼接):**明文 p50 = 22.636 ms / p95 = 23.020 ms**|**加密 p50 = 42.782 ms / p95 = 43.599 ms** ⇒ **绝对增量 +20.146 ms(p50)/ +20.579 ms(p95)**,**比值 1.890×(p50)/ 1.894×(p95)**。
- 分段(p50):put 15.981 → **27.363 ms**(+11.382)|get 6.662 → **15.444 ms**(+8.782)。
- 吞吐(按 p50 总墙钟折算):**502.02 MB/s → 265.62 MB/s**。密文固定开销 = **28 B/块**(iv 12 + tag 16)⇒ 本包 +308 B。
- 两臂**都**校验"取回字节逐字节等于原包"= true;加密臂 `decrypts = 11`、`local` 命中 11、`origin` 命中 **0**(⇒ 加解密**未**把共享打回去)。
- ⚠️ **口径诚实标注**:这是**单核单进程、页缓存全热**的本机读数 ⇒ 它是"**加密层自身的成本上界参照**",⛔ **不是**真机端到端时延承诺(真机还叠加 relay 跳、TLS、磁盘)。序㉔ 的 `E1`(回源份数)**不受影响**(本臂 `origin` 命中恒 0)。
**② 轮换代价量化(原 §10-2;`_tmp_seq32/p02-rotate.txt`)** —— 夹具 = **4 组 × 4 节点 = 16 节点**(组内 peer 共享、跨组不共享),包 **5,255,225 B**(= 序㉔ `E1` 同值)⇒ **6 块**;组名 `ops|grp0..3`;⚠️ **不预置内容**(预置会让 local 恒命中 = 假绿 —— 本棒首版夹具即栽在此处,已改正)。
- **轮换前**:第 1 轮(冷启动)**回源 = 21,021,572 B = 4.0001 × 包 = 1 × 包/组**(`local/peer/origin` 命中 = `0/72/24`)|第 2 轮(稳态)**回源 = 0**(命中 `96/0/0`)。
- **轮换(epoch 1 → 2,4 组同时)**:块 id 改变 **24/24 = 100.00%**;旧块**未清**(各节点 `storeBytes` 仍为 5,255,393)。
- **轮换后**:第 1 轮**回源 = 21,021,572 B(与冷启动逐字节同值)**|第 2 轮 **= 0**。
- ⇒ **`轮换后首轮 ÷ 轮换前冷启动 = 1.0000`**,即**轮换的代价 = 一次完整冷启动(全量回源)**;`1 × 包/组` 的 E1 口径**在轮换后依然成立**(组内共享未退化),**"之后回落"= 第二轮起 0 回源**。
- 🔴 **对照腿(⛔ 防假绿)**:同一夹具**不轮换**跑第 3 轮 ⇒ **回源 = 0** ⇒ 证明"回源是轮换**引起**的",⛔ 不是夹具恒回源。
- ⚠️ **附带代价(本轮新读数)**:节点 `storeBytes` **5,255,393 → 10,510,786**(块数 6 → 12)⇒ 旧 epoch 密文变**垃圾**且**不会自动清**(要靠 LRU 淘汰 / 手动清缓存)⇒ 轮换 = **一次全量回源 + 一份等量的缓存垃圾**。`decryptRejected` 全程 **0**(旧块按"id 不同"被绕过,⛔ 不靠"解密失败"发现)。
**③ 低熵明文的安全性(原 §10-3;`_tmp_seq32/p03-lowentropy.txt`)** —— 口径 = **只做定性**,⛔ 不写"很安全"。本棒把攻击面**拆成两个攻击者模型**分别取证(⛔ 不分清就会把结论写反):
- **模型 A(持组密钥者 = 组内成员 / 拿到 `0600` 密钥文件者)**:域 `|D| = 1000` 条 8 B 短块(`blk-0000`…),victim 明文 = `blk-0777`;字典枚举 **1000 条耗时 11.644 ms(≈ 85,878 条/秒)**、查表**命中 1**、恢复明文与原值**一致** ⇒ **低熵明文对持钥者可枚举恢复**(外推:本机单核速率下域 `2^32` ≈ **14.7 小时**)。🔴 这正是"组内互信"这一**代价**的具体含义。
- **模型 B(中继 / 无密钥者)**:拿**另一把**密钥自造字典 1000 条 ⇒ 查表**命中 0**;即使已持有 1 对(明文, 密文)⇒ 能预测的其他明文密文数 = **0 / 999**。**机理** = `iv = HMAC(组密钥, 明文)` ⇒ 无密钥者**算不出任何候选密文** ⇒ **枚举不可行**(这一点与"朴素 `iv = H(明文)` 的收敛加密"**不同**,是本方案的一个真实增益)。
- **模型 B 确实能观测到的(泄露面)**:语料 10,000 块取自 **100** 个不同明文 ⇒ **不同密文数 = 100**(= 明文不同数)⇒ 中继可读出"**这次传输里有几个不同值**"+**重复率 / 频次结构**,并可在**同 epoch 内跨节点、跨时间关联同一块**;极端域(如 `{yes, no}`)⇒ 中继看到 2 种密文,**内容空间已被压到 2 选 1**。
- **反向腿(⛔ 防"随便都能破"的误读)**:1000 条 **64 B 高熵**明文 ⇒ 不同密文数 **1000**(无碰撞)⇒ "可枚举"是**低熵**特有,⛔ 不是本加密的普遍失效。
- ⇒ **定性结论**:本方案把中继的可见面压到"**相等性 / 重复率**",**明文保密成立**;但"**内容不可推断**"**不成立**(低熵 / 高度重复的载荷会泄露结构),且**持钥者侧无保密性**。若要治低熵,只有加随机化 ⇒ 会牺牲块级共享 = **真取舍、需另立单**。
**④ 过渡窗口三档(原 §10 第 6 行;`_tmp_seq32/p04-epoch.txt`)** —— 已在 **§3.9 `CONTENT_EPOCH_GRACE_MS` 行**写入曲线(小 60 s、中 24 h、大 7 d;归零点 = 最晚 `retiredAt` + 本值 = 24 h / 48 h / 8 d;窗口内 `epochExpired=0`;超窗后逐 epoch 点名 + `decryptRejected` +1),本处只记两条**口径**:⚠️ 时钟走**注入 `now`** ⇒ 结论**完全可复现、⛔ 不依赖真实等待**;⚠️ 本夹具的写 epoch 全程 **5/5 可解**(当前密钥恒可用)。
**⑤ 口径定稿(原 §10-5 / §8.13-①)** —— 见 **§6 `OBS-23` 行末的「口径定稿」** 与 **交接单 §11.5**:**保留「缺省不启用 ⇒ SKIP + 留痕」**,单 B §4/S6 的「对旧生产必 FAIL」**作废**,"先红"腿改由**权限腿**承载。⚠️ ⛔ **单 B §4 / §6 的正文一个字都没改** —— §8 是截断点、§8 前前缀 `149596460288ca1a5b7abddc490f7909` 是**不变量**(改它 = 打穿证据链)⇒ 定稿写在 §8 之后(§8.14 / §11.5)。
**⑥ 组密钥签发固化(原 §8.13-②)** —— 已把 `scripts/overlay-keyring.cjs`(18153 B,md5 **`f1855eaef30f71fbca00ac0a92865c0e`**)铺到 47 **两处正式路径**:`/opt/dsh-relay/scripts/overlay-keyring.cjs`(**就地更新** —— 旧版 12915 B、`grep -c sign-group-key` = **0**,已入回滚点)+ `/opt/dshs/scripts/overlay-keyring.cjs`(**此前不存在** = 单 §8.13-② 记的那处缺口)。回滚点 = 47 **`/opt/dsh/backups/seq31-20260918-073857/`**。**在机上验证**:`sign-group-key` **rc=0**(凭据 **274 B**,`network=ops group=relay epoch=1 keyId=3560130eb0e5e4aa`,**载荷内密钥本体出现次数 = 0**、`keyId` = 1)|`verify-group-key` **rc=0**|**篡改 epoch ⇒ `signature-mismatch` rc=1**|交叉核对(传 `--epoch 9`)**rc=1**。🔴 **该动作不重启任何单元**(它是**纯 CLI**,⛔ 无进程加载它)⇒ 零运行时影响。
---
### §11.4 补记(2026-09-18 10:0x,序㊱ 执行棒 · P1「节点一键加入 + 分组准入」;**同在 §10 指纹口径之外**)
**① 零回归三件套(开工基线现核 = 201/200/0/1 / 12P / 23P,三者皆 rc=0)**
| 件 | 本次读数 | 与基线 |
|---|---|---|
| `npm.cmd test`(Node **22.22.2**) | **201 tests / 200 pass / 0 fail / 0 cancelled / 1 skipped** | **一致** |
| `overlay-failover-drill.cjs --scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**(幕4-C 实测 **19370 ms** / deadline 30000 ms) | **一致** |
| `overlay-probe.cjs --table` | **24 PASS / 1 SKIP / 0 FAIL**(⛔ 0 FAIL) | 详见 ②(**少 1 PASS、多 1 SKIP,已归因**) |
**② `OBS-09` 由「在册」退化为「不在册 ⇒ SKIP」(🔴 本棒**唯一未复原项**,如实记)**
- **基线**:`PASS OBS-09 … w-106:39027=401`(端点表 **2 条**)。
- **现状**:`SKIP OBS-09 在册实例面 无|不在册 2 项:本机:20000,对端:21000`(端点表 **1 条** = `w-106 port=19000 localPort=43061`)。
- **归因(均有原文)**:本次运行的 failover 演练重启了两端 relay / worker ⇒ `OBS-01`(used=1)/`OBS-04`(identityOk=1)/`OBS-08`(端点表 0 条)**同时转红**;为恢复,执行 **47 `systemctl restart dshs` + 106 `systemctl restart dshs-worker`**(开发环境服务器;动手前已声明)⇒ 三者**全部回到绿**,但 106 worker 的重注册读数为 **`accepted=[19000]`**,而演练刚结束时是 **`accepted=[19000,21000]`**(106 `journalctl -u dshs-worker` 09:48:29 vs 09:56:30 两行原文)⇒ **实例端口 `21000` 的注册未随 worker 进程重建**。⚠️ 106 实例 `:21000` 与 47 实例 `:20000` **都在监听**、门户 **200** ⇒ **无业务中断**。
- ⚠️ 这是 `OBS-09` 的**设计内合法态**(「不在册 ⇒ SKIP + 留痕」,⛔ 不判红)⇒「0 FAIL」成立;但**较基线少 1 个 PASS**,不当作没发生。
- **回头条件**:下一棒收口若 `OBS-09` 仍为 SKIP,或出现「跨机实例面访问不通」⇒ **升级为独立小项**(第一步查"实例 → 本地 worker 的端口注册"链路,⛔ 不先猜 relay)。
**③ 真机端到端链路(`_tmp_seq33/chain47.sh`,全程在 `/tmp`,⛔ 未碰生产注册表 / 未碰生产签名者密钥)** —— **rc=0,11 步全绿**:`join` 四步 4/4 → 收单 `status=pending` → **同一张邀请二次收单 ⇒ `invite-already-used`(J1)** → 批准前派生为空(收单 ≠ 批准)→ 批准后 `DSHS_RELAY_DIALERS="lab-net/lab-1"` → 三张网(`lab-net` / `ops` / `u:5`)**各得自己那条、零共享(J5)** → 申请单内**私钥出现次数 = 0**、节点私钥权限 **600**(J6)→ 一次性台账条数 **3 ≡ 已收单次数 3**。
**④ 真机自检读数(`/var/lib/dshs/overlay/selfcheck.json`,用修复后的 CLI `--out` 重跑后现取)** —— `platform=linux`|`nodeKeyMode=600` ⇒ `nodeKeyModeOk=true`(**Linux 权限腿成立**)|`joinOk=true`(四步 4/4)|`application.bytes=533`、`hasPrivateKey=0`、**`shapeOk=true` / `hostMatches=true`(CLI 回环)**|`ledger={first:ok, second:invite-already-used}`|`namedFailures=5`(阈值 ≥ 4)、`silentRejections=0`|`registry.auditOk=true`、`misfiled=0`|`derivation.crossNetworkShared=0`。
**⑤ 一条真机缺陷(已修 + 已补判据 + 已先红后绿)** —— 控制面 CLI 的 `--file` **键名混用**:它本是 `apply` 的**申请单**入参,却被注册表**文件路径**复用 ⇒ `apply --file <申请单>` 把注册表也指到申请单 ⇒ 收单在第 4 步炸,**而 nonce 已被原子占位**(对外症状 =「收单失败 + **同一张邀请再也用不了**」)。修 = 注册表覆盖键改名为 **`--registry`**;**先红后绿** = 临时改回 `opt('file')` ⇒ 新增的 **E10(CLI 级真回环)** 转红并报出**同一句**「注册表 …/app.json 形状非法」;还原 ⇒ **33/33 全绿**。⚠️ **调库式的 E9 抓不到这一类缺陷**(它绕过了 CLI 的参数解析)—— 这是"判据必须走真实路径"的又一次落地。
**⑥ 一处"刻意不判"(⛔ 防假判据)** —— `OBS-24` 原拟判「同一 `hostId` 出现在 ≥2 张网 ⇒ FAIL」,真机夹具证明这是**合法态**(客户端自取设备名会跨用户撞名;单测 `D1` 已钉死"两张网各自独立、互不影响")⇒ 该腿**没有判别力**(永远 PASS)。已降级为**信息项**,改判「结构性错桶(`auditDerivation.misfiled`)= 0」+「**派生桶数 ≡ 有 approved 的网数**」(⛔ 防把多张网合并成一张)。
**⑦ P1 出口项 / P2 接口点(本棒自决,可推翻)** —— 在册文件集里的 `src/web/routes/overlay-nodes.ts`(管理面 API:列出 / 批准 / 移除节点)**本棒不建**。理由:① 与 **P1 硬门「零新增暴露面」** 风险最高(它是唯一会动 HTTP 面的在册项);② `S1–S4` 与 `J1–J6` 的验收**均不依赖它**(`S1` 明写"只读面先行",而控制面 CLI `list` 已是只读面);③ 交接单 **§7「权限影响评估」本身就是 P2 专用**。⇒ 移入 **P2**,与 S5 的权限影响评估一并做。
**⑧ 收口复核(2026-09-18 10:0x–10:4x · 序㊱ 收口复核棒;⛔ 只复核,⛔ 未开工 P2)** —— **① `OBS-09` 由 SKIP 回到 PASS**:开工读数 `SKIP … 不在册 2 项:本机:20000,对端:21000`(端点表 1 条);链路取证 = 106 `/healthz` 原文 **`{"instances":0,"tunnel":{"ready":true,"ports":[19000]}}`** ⇒ `reconcileTunnel()` 无 `live` 端口可声明(根因 = `orchestrator.ts:558 listUserInstances()` 取 `mains`,而认领只写 `this.adopted`(:1492)⇒ 认领到的实例**平台侧不可见**);复原 = 走**设计内路径**(临时 session 一次真实用户可见面访问 ⇒ 实例被 `cleanStaleScopes` 自然替换 ⇒ 10:18:34 `dsh-100002-7d1c8cbf.scope` 起、新实例经 launch 路径声明端口)⇒ `/healthz` = `{"instances":1,"tunnel":{"ready":true,"ports":[19000,21001]}}`、用户可见面 **200 / 62451 B / `502` 标记 0**、临时 session 已删。**② 本表值格一处实测替换**:`PEER_INSTANCE_PORT` **21000 → 21001**(+ `PEER_AGENT_PORT` 括注同步 `ports=19000/21001`)—— 因替换落点漂移(`findFreePortInRange` 上扫,替换时 21000 仍被旧 scope 占着),**⛔ 未为了迁就旧值去重启生产实例**;**§10 指纹 `695dcb4d88a778840322bc221417d339` → `2ae954e2e07b47651a84e597c19a3444`**。**③ 零回归三件套** = `npm.cmd test` **201/200/0/1**|`--scene all` **12P/0S/0F**(幕4-C **17375 ms** / deadline 30000)|探针 **25 PASS / 0 SKIP / 0 FAIL(rc=0)**;`node --test test/overlay-join.test.mjs` **33/33**。**④ 两条点名(⛔ 本棒未改码)**:(a) 🔴 `OBS-09` 的"在册"判据**钉死会漂移的实例端口** ⇒ 每次「访问时自然替换」后都可能**假 SKIP**(同步表值只治输入、不治判据)⇒ 交**序㊲**;(b) ⚠️ **探针只读 47 的 `RELAY_STATUS_URL`**,而 worker 可能挂在 106 中继(`--scene all` 跑完实测 `ep=[] / used=1 / idOk=1`,= 潜在**假红**)⇒ 同交序㊲。**⑤ 边界自证**:⛔ 未 commit / push(HEAD `09ce76f`)|⛔ 未改任何生产**配置值**|🔴 `RELAY_FAILOVER_COOLDOWN_MS=0` 计数 **0**|⛔ 未开工 P2|⚠️ 对生产的 3 个动作(guest 实例一次设计内替换 / 106 `dshs-relay` 停启各一次 / 本表文档值同步)均属 **R8**、动手前已声明、**无不可逆项**。
### §11.5 补记(2026-09-18 11:1x,序㊲ 执行棒 · 观测面去常量依赖:`OBS-09` 在册判据跟随事实 + 观测点假红可分;**同在 §10 指纹口径之外**)
**① 零回归三件套**(三件都在**改动之后**跑)
| 件 | 本次读数 | 与基线 |
|---|---|---|
| `npm.cmd test`(Node **22.22.2**) | **201 tests / 200 pass / 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 实测 **18353 ms** / deadline 30000 ms) | **一致** |
| `overlay-probe.cjs --table` | **25 PASS / 0 SKIP / 0 FAIL**(rc=0) | **一致** |
⚠️ **两处口径说明**:❶ `npm.cmd test` **没有** `--table` 参数(它是测试入口,不是覆盖网络脚本)⇒ "三者都带 `--table`" 对它不适用;另两件均已带 `--table "<本表绝对路径>"`。❷ 探针**不在** `npm test` 的覆盖内(测试输出里 `grep -c overlay-probe` = **0**)⇒ 探针自身的零回归 = 上表第 3 行那次**真机**跑。
**② `OBS-09` 去常量依赖 —— 先红后绿(原文级;两侧逐行 diff **只差 `OBS-09` 一行**)**
夹具 = 本表**副本**(`PEER_INSTANCE_PORT` 值格回写成漂移前的 **21000**)+ 真实 47 `/status`(事实 = 实例在 **21001**)⇒ 正是「**表值过期、事实已漂移**」这一实际场景。
- 🔴 **红(旧码 · 旧口径)** = `SKIP OBS-09 在册实例面 无 (阈值 ∈ {200,401})|不在册 2 项(记 SKIP 的依据):本机:20000,对端:21000`
⇒ **旧判据拿表值 21000 去 `endpoints[]` 里找 ⇒ 找不到 ⇒ 假 SKIP**(与序㊱ §8.8-⑥-1 记的现象**逐字同型**)。
- ✅ **绿(新码 · 同表同夹具)** = `PASS OBS-09 在册实例面(派生 1 条):w-106:42717=401 (阈值 ∈ {200,401})`
⇒ 派生集来自 `/status.endpoints[]`(`port=21001 / localPort=42717`),**表值 21000 已完全不参与判据**。
- 🔴 **旧夹具口径已退役(⛔ 不让它静默解释)**:`--instance-fixture 401,401` ⇒ **rc=2** 且报
`❌ --instance-fixture 项 "401" 形态不合法。序㊲ 起口径 = **按实例端口映射**(如 "--instance-fixture 21001=401");⛔ 旧的位置式 "<本机码>,<对端码>" 已退役(判据不再依赖表内固定端口)`
- 🔴 **派生端口缺码 = FAIL 并点名**(⛔ 不静默放行):不给码 ⇒
`FAIL OBS-09 在册实例面(派生 1 条):w-106:42717=? (阈值 ∈ {200,401}) |❌ 缺码 21001(夹具未给该端口的码 / 真机取不到)`;
给成 `20000=200`(**旧端口**的码)**同样 FAIL** ⇒ 只有"**派生出来的那个**端口"的码才算数,⛔ 拿旧端口糊弄不过去。
**③ 「空表可分」—— 同一份 47 输入 ⇒ 三种**可分辨**输出(原文级)**
按**对端中继视图**(`ssh test106 'curl -s 127.0.0.1:20080/status'`,**只读回环**)分类:
- **(甲) 真机 · 演练态**(`--scene all` 跑完、⛔ 未做任何复原)—— 47 = `endpoints: [] / capacity.used=1 / counters.identityOk=1`(= §8.8-④ 记的 `ep=[] / used=1 / idOk=1`,**独立复现**);106 = `endpoints = [w-106:19000, w-106:21001]`:
- `FAIL OBS-01 在册节点 used=1 (阈值 ≥ 2)`
- `FAIL OBS-04 identityOk=1 (阈值 ≥ 2)`
- `SKIP OBS-08 端点表 0 条 ⇒ SKIP + 留痕|47 视角端点表为空,但**对端中继**(test106)声明了端口:w-106 ports=19000/21001 ⇒ 客户端挂在**另一台中继**上(⛔ 非故障、⛔ 不判红)`
- `SKIP OBS-09 在册实例面 无(从 /status.endpoints[] 派生为空:端点表 0 条 = agent 0 条 + 离线 0 条) ⇒ SKIP + 留痕|…(同一句归类)`
⇒ **21 PASS / 2 SKIP / 2 FAIL**(rc=1)。🔴 **旧口径对照(同态)**:旧 `OBS-08` 的 `eps.length > 0` 一条会**恒 FAIL** ⇒ 分不出"挂在别处"与"确实没挂" ⇒ 这正是被点名的**假红**,本棒已消除。⚠️ 余下 2 条红是 `OBS-01`/`OBS-04` 的**计数**判据(`used`/`identityOk` 只有 1),**⛔ 与"端点表为空"无关**。
- **(乙) 同输入 + 对端中继**没有**会话**(`--peer-status-fixture` = 真实 106 视图,其 `sessions=[]`):
- `FAIL OBS-08 端点表 0 条 ⇒ FAIL(两台中继都无该客户端会话)|对端中继(test106)在运行但**没有任何会话在声明端口**,而 47 视角端点表也为空 ⇒ **该主机确实没挂客户端**`
- `SKIP OBS-09 …(同一句归类)` ⇒ **"挂在别处"≠"确实没挂"≠"不可判"**,三者可从输出直接读出。
- **(丙) 同输入 + 未给对端中继视图**:
- `FAIL OBS-08 端点表 0 条 ⇒ FAIL(不可判)|对端中继(test106)**不可判**:夹具模式未给 `--peer-status-fixture`(⛔ 不去 ssh)`
- **(丁) 端点表非空、但派生为空**(另有 agent 端点在册)⇒ 只命中 `OBS-09` 的 SKIP 腿:
- `SKIP OBS-09 在册实例面 无(从 /status.endpoints[] 派生为空:端点表 1 条 = agent 1 条 + 离线 0 条) ⇒ SKIP + 留痕|…`
- `PASS OBS-08 端点表 1 条 / 离线 0 条` ⇒ **两种"空"确实不是一回事**("端点表空" ≠ "派生为空")。
🔴 **复原(按序㊱ §8.8-④ 同款;⛔ 未重启任何 worker、⛔ 未换实例)**:停 106 `dshs-relay` ⇒ **约 15 s** 后 47 视角回落为 `used=2 / 端点 = [(19000, 39815), (21001, 44231)]` ⇒ 启回 106 `dshs-relay`(`active`)。复跑探针 = **25 PASS / 0 SKIP / 0 FAIL(rc=0)**,`PASS OBS-09 … w-106:44231=401` —— ⚠️ `localPort` 由 **42717 → 44231**,**判据跟着事实走、⛔ 无须改表值**,这正是本项要拿到的收益。
**④ 边界自证**:⛔ 未 commit / 未 push(HEAD **`09ce76f`**;`git status --short` = **10** 处,与开工**逐数相同**;本棒只动 `scripts/overlay-probe.cjs` **一个**仓内文件)|⛔ **未改任何生产配置值**(无 drop-in / env / nft·nginx / bwrap 改动,本表**值格一律未动**)|⛔ **未新增任何暴露面**(新读数走**回环 + 既有 ssh**;复跑 `OBS-11 多出 0 缺失 0`、relay 口仍 **1/1** 绑回环)|🔴 `RELAY_FAILOVER_COOLDOWN_MS` 命中数 **0**(⛔ 未写进任何回滚 / 演练 / 夹具路径)|⛔ 未开工 P2(P2 仍待用户拍板权限影响评估)|🔴 密钥本体不经网络 / 不经 relay(本棒不碰密钥)。
**⑤ 如实留档(必填)**:
1. ⚠️ **`OBS-01` / `OBS-04` 在演练态下仍会红**(`MIN_HOSTS` = 2 / `MIN_IDENTITY_OK` = 2,而演练态 47 视角只有 `manager` 一条会话)—— 它们是**计数**判据、**不是**"端点表空不空"的判断 ⇒ 按范围⛔ 未改;**处置 = 收口时做一次 106 relay 停启让 worker 回落 47**(与序㊱ §8.8-④ 完全同款),复原后两者即绿。
2. ⚠️ **`OBS-09` 的取样面变化**:47 本机实例面(`:20000`)**结构性**不在 `endpoints[]` 里 ⇒ 不再是本项取样点;⚠️ **判据能力无净损失** —— 旧口径下它永远落在"不在册"一侧、**从未**进入 `bad` 判定(源码文件头有原话)。**本棒⛔ 未新增取样源**(若日后要给本机实例面也上判据,属独立小项)。
3. 🔬 **一条**文档口径歧义**(⛔ 非本棒引入;⚠️ 已核代码、⛔ 不凭印象下结论)**:文件头总括写"夹具模式…把远端原文喂进来 ⇒ **不 ssh**",但 `OBS-24` / `OBS-25` 的分支是 `if (<各自夹具> !== undefined) { 读夹具 } else { 去 ssh }`(无 `fixture` 守卫)⇒ **只给了 `--status-fixture`** 的一次夹具跑里,这两行其实是**生产读数**(本棒夹具跑实测:`OBS-24` = "注册表 形状合法|网 0 张",`OBS-25` = "入口在册 ✅(4/4 步)",stderr 可见多次 ssh)。⚠️ **两头都说得通**:`OBS-24`/`OBS-25` **各自的用法行**写的是"(真机 = `cat …`;**缺省/文件不存在 ⇒ SKIP + 留痕**)" ⇒ 与代码**一致**;只有**文件头那句总括**与事实不符。⇒ ⛔ 按"只做被明确要求的事"**未改**;**建议二选一**:(a)给这两项补"夹具模式一律 SKIP"的守卫(⇒ 夹具跑变成**封闭**的,与本线"夹具 = 零副作用实证手段"的定位一致)、或(b)把文件头那句限定为"**该 OBS 的**远端原文喂进来 ⇒ 不 ssh"(⇒ 承认夹具跑可混生产读数)。⇒ ✅ **序㊳ 已按 (a) 落地**:两处取数改为 `if (<夹具> !== undefined) { 读夹具 } else if (fixture) { SKIP + 留痕 } else { ssh }`(守卫排在 ssh 之前)+ 文件头新增 **「封闭性」硬约束**段(含"新增可 ssh 取数的 OBS 必须照此顺序写"的自检口径)。
4. ⚠️ **`DRILL_RELAY_UNIT` 被本棒复用**(用于把"对端中继未运行"与"对端中继读取失败"分开)—— ⛔ **未新造键**(它记的就是"两台机的 relay 单元名"这个事实,与 `JITTER_LIMIT_MS` 同款做法);⚠️ 键名 `DRILL_` 前缀与用途已不相称,建议下一棒按需改名(**文档**项)。⇒ ✅ **序㊳ 已改名 = `RELAY_UNIT_NAME`**(§3.9 键行已同步;引用点 **3 处全改**:`overlay-failover-drill.cjs` / `overlay-probe.cjs` / 本表;**值格未动**仍 `dshs-relay`)。
5. 📁 **证据落点** = 工作区 `_tmp_seq37/`(红/绿原文、演练态双视图、四组夹具、`npm test` 全文);⛔ 未清理 —— **它本身就是"先红后绿"的可复现证据**。
### §11.6 补记(2026-09-18 11:4x–12:0x,序㊳ 执行棒 · 两项收口:探针夹具封闭性 + `DRILL_RELAY_UNIT` 键名对齐;**同在 §10 指纹口径之外**)
**① 探针夹具模式封闭性**(✅ **用户拍 A 方案** = 补守卫,⛔ 不是改文件头口径)
改法(`OBS-24` / `OBS-25` 两处):取数分支由 `if (<夹具> !== undefined) { 读夹具 } else { ssh }` 改为 `… else if (fixture) { SKIP + 留痕 } else { ssh }` —— **守卫排在 ssh 之前**。文件头新增 **「封闭性」硬约束**段(夹具模式下任何取数必须来自夹具;缺该夹具 ⇒ SKIP + 留痕,⛔ 绝不回落去 ssh;并写明"新增可 ssh 取数的 OBS 必须照此顺序写"+判据=夹具跑里出现任何一次 ssh 即违约)。
**先红后绿(原文级 · 两侧逐行 diff 只差 `OBS-24` / `OBS-25` 两行)** —— 两侧用**同一份** `--table` 副本(把 `SSH_TARGET_47` / `SSH_TARGET_106` 指向 `127.0.0.1`、`SSH_PORT` 指向 `1` ⇒ **"是否 ssh"变成可观测差异**;夹具 = 真实 47 `/status`):
- 🔴 **红腿**(守卫摘掉,还原序㊱ 形态):`FAIL OBS-24 ❌ 注册表**读取失败**(⛔ 与"不存在"可分):Command failed: ssh -p 1 -o BatchMode=yes 127.0.0.1 if [ -f /var/lib/dshs/overlay/nodes.json ]; …` + `ssh: connect to host 127.0.0.1 port 1: Connection refused`;`OBS-25` 同款。**真 ssh 调用计数**(`grep -c 'Command failed: ssh'`)= **2**。
- 🟢 **绿腿**(现产物):`SKIP OBS-24 夹具模式未给 --nodes-fixture ⇒ SKIP + 留痕(⛔ 不去 ssh 读生产)`、`SKIP OBS-25 夹具模式未给 --join-fixture ⇒ SKIP + 留痕(⛔ 不去 ssh 读生产)`。**真 ssh 调用计数** = **0**。
**可分性(新守卫没把"给了夹具但读不到"也吞成 SKIP)**:给**不存在**的夹具路径 ⇒ `FAIL OBS-24 ❌ 注册表**读取失败**(⛔ 与"不存在"可分):夹具不存在:…/NO_SUCH_nodes.json`(`OBS-25` 同款)—— 与"**未给**夹具 ⇒ SKIP"**可分** ✅。
**② `DRILL_RELAY_UNIT` → `RELAY_UNIT_NAME`(键名与用途对齐)**
理由:它记的是"两台机的 relay 单元名"这一**事实**,且被**演练脚本**与**探针 `OBS-08`** 共用 ⇒ 原 `DRILL_`(演练专用坐标)前缀与用途不相称。**值格未动**(仍 `dshs-relay`)⇒ ⛔ **零个新增 / 改动生产 env**。引用点 **3 处全改**:`scripts/overlay-failover-drill.cjs`(`r.need` + 一行原因注释)|`scripts/overlay-probe.cjs`(`OBS-08` 的判别器)|本表 §3.9 键行 + 小节标题注(§11.5-⑤-4 已同步为"已改名")。⚠️ **文档库档案不改**(`04-调整方案/117-…` 与归档单 `T13-中继失败切流.md` 记的是**当时事实**,⛔ 不追改)。
**③ 零回归三件套**(三件都在**改动之后**跑)
| # | 命令 | 读数 | 开工基线 | 判定 |
|---|---|---|---|---|
| 1 | `npm.cmd test`(Node **v22.22.2**) | **201 tests / 200 pass / 0 fail / 0 cancelled / 1 skipped**(rc=0) | 同 | ✅ 逐字一致 |
| 2 | `overlay-failover-drill.cjs --scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**(rc=0;幕4-C **20770 ms** / deadline 30000) | 12P/0S/0F | ✅ |
| 3 | `overlay-probe.cjs --table`(真机) | **25 PASS / 0 SKIP / 0 FAIL**(rc=0) | 25P/0S/0F | ✅ |
⚠️ 两条口径沿用序㊲:❶ `npm.cmd test` **没有** `--table` 参数(它不经参数表,此处"三件都带 `--table`"只对后两件成立);❷ 探针**不在** `npm test` 覆盖内。
**④ 演练副作用与复原(按序㊱ §8.8-④ / 序㊲ §11.5 同款;⛔ 未重启任何 worker、⛔ 未换实例)** —— 跑完 `--scene all` 后 47 视角确为空(`endpoints=[] / used=1 / identityOk=1`)⇒ 探针呈 **21 PASS / 2 SKIP / 2 FAIL**(2F = `OBS-01 used=1 (阈值 ≥ 2)`、`OBS-04 identityOk=1 (阈值 ≥ 2)` —— 二者是**计数**判据、⛔ 与"端点表为空"无关;2S = `OBS-08` / `OBS-09` 走"对端中继声明了端口"腿)⇒ **停 106 `dshs-relay`** ⇒ **约 22 s** 后 47 视角回落 `used=2 / identityOk=2 / 端点 = [(19000, 33213), (21001, 42783)]` ⇒ **启回**(`active`)⇒ 探针回 **25 PASS / 0 SKIP / 0 FAIL(rc=0)**。⚠️ `localPort` 由 **42717 → 42783**(本次回落又漂一次;改动后首次真机跑时为 **44231**)—— **判据跟着事实走、⛔ 无须改表值**,这正是序㊲ 要拿到的收益。
🔑 **顺带取得键名改名的真机端到端证据**(比手跑单条命令更强):演练态那次 `SKIP OBS-08` 的原文是「…但**对端中继**(test106)声明了端口:w-106 ports=19000/21001…」⇒ 说明判别器**真的走完了**真机分支那条 ssh(内含 `systemctl is-active ${RELAY_UNIT_NAME}`)⇒ ✅ **改名后真机取数可用**。
**⑤ 边界自证**:⛔ 未 commit / 未 push(HEAD **`09ce76f`**;`git status --short` **11** 处 = 序㊲ 的 **10** 处 + 本棒新增的 `scripts/overlay-failover-drill.cjs`,**逐条可解释**)|⛔ **未改任何生产配置值**(本表**值格一律未动**)|⛔ **零新增暴露面**(`OBS-11 多出 0 缺失 0`、relay 口仍 **1/1** 绑回环)|🔴 `RELAY_FAILOVER_COOLDOWN_MS` 命中 **0**(⛔ 未写进任何回滚 / 演练 / 夹具路径)|⛔ 未改 bwrap / nft / nginx|⛔ 未开工 P2(P2 待拍板清单 = 交接单 **§8.11**)|🔴 密钥本体不经网络 / relay(本棒不碰密钥)。
**⑥ 证据落点** = 工作区 `_tmp_seq38/`(`red.txt` / `green.txt` / `l3_badfixture.txt` 及各自 `.err`、`real.txt`、`probe_drillstate.txt`、`probe_final.txt`、`drill.txt`、`npmtest.txt`、`mk_fixtures.py`、`repo/`);⛔ 未清理 —— 它本身就是"先红后绿"的可复现证据。
### §11.7 补记(2026-09-18 12:2x,序㊴ 规划棒 · **P2 拍板落地 + 本线首份 P2 可执行单**;**同在 §10 指纹口径之外**)
**① 用户拍板(原话,⛔ 不许改写)**:`1 用户可设置,默认开启提示用户 2 乙`(2026-09-18 12:22)
⇒ 两问闭合:**①** 打洞能力**交给用户自己控制**(可设置),平台**默认开启**且**必须提示用户**(⛔ 不是简单的"开 / 不开");**②** 云安全组 UDP 入站的**来源范围 = 乙**(固定小段、**仅放行对端节点 IP**,⛔ **非** `0.0.0.0/0`)。
**② 本表变更**:§8 新增 **⑩ P2 直连的 UDP 入站面** 一行 + 附注补一条**口径更新**;🔴 **§10 指纹** `2275870a5a9eb1964a648f2950dee528` → **`d408d640246a980f702fe7b0a2895219`**(**现算并同步**)。⚠️ **值格一律未动** ⇒ ⛔ **零个新增 / 改动生产 env**(⑩ **只登记**)。
**③ 产物 = 本线首份 P2 可执行单**:工作区根 **`交接单_覆盖网络直连与P2P_20260918.md`**(**归档号 132** · **239 行** · **§8 前前缀 `8925c2460810c6dc5cdbc075b6cdaed2`**)。结构 = 用户口径(§1)+ 只读前置 **8 条已取证事实**(§2)+ 在册文件集(§3)+ 技术路线(§4)+ 判据 **D1–D8**(§5)+ 步骤 **S5 / S6 / S7**(§6)+ 权限影响评估(§7 · 已拍板)+ 回报格式(§8)+ 回头条件(§9)+ 未验证项(§10)。🔴 两条关键判据:**D7 = peer 接线后块 id 必须复算**(⛔ 漏了不算通过)|**D8 = 云安全组 UDP 段 source ≠ `0.0.0.0/0`**。
**④ 三条如实登记**:
1. 🔴 **乙方案的技术现实**(同时写进新单 §4.5):对端为**家宽 / 移动**时**公网出口 IP 会变** ⇒ 规则会失效、且云安全组**规则数有上限** ⇒ 序㊴ **自决(可推翻)** = **乙-1 起步**(只对**有固定公网 IP** 的节点开;家宽节点本期**走中继**,⛔ 不影响可用性);**乙-2**(半自动)/ **乙-3**(云 API 自动化,🔴 **需云 AK/SK**)登记为新单 **§9 回头条件** —— ⛔ **本棒未请求任何凭据**。
2. ⚠️ **旧单正文一字未改**(`交接单_节点一键加入与分组准入_20260918.md` **§8 前前缀 `085e28b56a08aa1f01999bdb3e238911` 逐字不变** ✅)—— 拍板落地写在其 **§8.12**(截断点之后);**P2 开工依据移交新单**。
3. ⚠️ **一条遗留(仅报告,⛔ 未动手)**:`dsh-server-docs/04-调整方案/.lock-131` **仍在**(序㊱ P1 的占号窗口未释放)⇒ 后果 = 后续取号者**永久跳过 131**(本棒已现核 ⇒ 取 **132**)。⛔ 本棒不动他人锁类目录。
**⑤ 边界自证**:⛔ **零代码改动** · ⛔ **零服务器改动** · ⛔ 未 commit / 未 push(HEAD 仍 `09ce76f`)· ⛔ 未改任何生产**值**(本表**只加不改**)· ⛔ 未开工 P2(命中 R5 的项**虽已获拍板**,本棒仍**只登记不实施**)。
### §11.8 补记(2026-09-18 13:2x,序㊵ 执行棒 · **P2/S5 落地:直连候选交换 + 打洞探测 + 开关/默认值/提示 + 只读观测面**;**同在 §10 指纹口径之外**)
**① 本表变更**:**§3.11** 新增 **12 键**(`DIRECT_ENV_KEY` / `DIRECT_DEFAULT_ENABLED` / `DIRECT_OFF_VALUES` / `PUNCH_PORT_BASE` / `PUNCH_PORT_SPAN` / `PUNCH_DEADLINE_MS` / `DIRECT_COOLDOWN_MS` / `DIRECT_PUNCH_MIN_OK` / `DIRECT_CAND_ACCEPT_MIN` / `DIRECT_CAND_REJECT_MIN` / `DIRECT_HINT_MIN_PARTS` / `DIRECT_SELFCHECK_FILE`)+ **§6** 新增 **`OBS-26` / `OBS-27` / `OBS-28`** 三行。🔴 **§10 指纹** `d408d640246a980f702fe7b0a2895219` → **`6b37bfd506758d882d9f803678f85d23`**(**现算并同步**)。⚠️ **值格一律未动** ⇒ ⛔ **零个新增 / 改动生产 env**(`DSHS_OVERLAY_DIRECT` 在两机 `/etc/dshs*.env` + `systemd` 的命中数 = **0** ⇒ 走**缺省即开**)。
**② 读数** = 探针 **28 PASS / 0 SKIP / 0 FAIL**(rc=0;基线 25P ⇒ **+3 = `OBS-26/27/28`**,⛔ 零回归)|`npm.cmd test` **201/200/0/1**(与基线**逐字相同**)|`--scene all --table` **12 PASS / 0 SKIP / 0 FAIL**(rc=0;幕4-C **23422 ms** / deadline 30000)。**产物** = 工作区根 **`交接单_覆盖网络直连与P2P_20260918.md` §8**(**§8 前前缀 `8925c2460810c6dc5cdbc075b6cdaed2` 回填后逐字不变** ✅ ⇒ "§8 是截断点"这条口径的又一次自证)。
**③ 一条"证据项 ≠ 判据项"的口径登记**:`OBS-26` 附注写"真机的 `ss -lunp` 增量腿由 `selfcheck --ss` 另取" ⇒ 本棒把它做成**带正对照的可信读数**(只有 `ssDuringOnCase ≥ 1` 才认可 `ssDuringOffCase = 0`;正对照缺失 ⇒ 该 0 可能是"测量坏了" ⇒ **潜在假绿**),但**未**把它并入判据(⛔ **不改已发布的阈值口径**)⇒ 读数落点 = `DIRECT_SELFCHECK_FILE#udp`(`{ssBefore:0, ssDuringOnCase:1, ssDuringOffCase:0, ssAvailable:true}`,区间 `[21100,21115]`)。
**④ 🔴 D8 未实施(红)**:云安全组 UDP 入站**仍未放行**(双向实测均 **发 20 收 0**;宿主机侧 `nft input policy = accept` ⇒ 挡在**云侧**、⛔ 非 nft);拟录 **乙-1** 两条 = 目标 `47.77.182.89` / `106.54.21.172` × 入站 × **UDP** × `21100/21115` × 源分别为 `106.54.21.172/32` / `47.77.182.89/32` × 允许 —— ⛔ **零个 `0.0.0.0/0`**(⛔ 拆分等价写法一并禁止,命中 **交接单 §9-3** = 立刻停手回滚)。**未实施的理由** = 属 §1 边界外第 ④ 类(**需用户提供控制台访问方式 / 审批**)⇒ 本棒 ⛔ **未索取、未硬编码任何云 AK/SK**。逐条清单见交接单 **§8.2-D8**。
**⑤ 边界自证**:⛔ 未 commit / 未 push(HEAD 仍 **`09ce76f`**;`git status --short` **17** 处 = 与开工**逐数相同**)· ⛔ 未改 `src/net/relay/server.ts`(**零字节改动**;`network.ts` **只加** `isAllowedDialer`)· ⛔ 未改 `nft`(**72 行**与收口态逐字一致)/ `nginx` / `bwrap` 参数 · 🔴 `RELAY_FAILOVER_COOLDOWN_MS=0` 命中 **0** · 🔴 密钥本体 ⛔ 不经网络 / 不经 relay。
### §11.9 补记(2026-09-18 14:0x,序㊶ 执行棒 · **P2/S6:`peer` 档接线 + D7 块 id 复算**;**同在 §10 指纹口径之外**)
**① 本表变更 = 0**:本棒 ⛔ **未新增任何键、⛔ 未改任何值、⛔ 未改任何阈值**。🔴 **§10 指纹** = **`6b37bfd506758d882d9f803678f85d23`**(**现算**,与 §11.8 记的值**逐字一致**)—— 佐证"§11 小节在 §10 口径之外"。⛔ **零个新增 / 改动生产 env**(`DSHS_OVERLAY_DIRECT` 两机命中 **0** ⇒ 走**缺省即开**)。
**② 读数** = 探针 **28 PASS / 0 SKIP / 0 FAIL**(rc=0;与 §11.8 基线**同值**、⛔ **零回归**)|`npm.cmd test` **201/200/0/1**(与基线**逐字相同**)|`--scene all --table` **12 PASS / 0 SKIP / 0 FAIL**(rc=0;幕4-C **18990 ms** / deadline 30000)|`test/overlay-content.test.mjs` **44/44**。**产物** = 工作区根 **`交接单_覆盖网络直连与P2P_20260918.md` §8.8**(**§8 前前缀 `8925c2460810c6dc5cdbc075b6cdaed2` 写入后逐字不变** ✅)。
**③ 🔴 D7 主判据(本棒唯一新增判据)** = **"`peer` 接线后块 id 与接线前逐字一致"**:`idsDigest` = **`ec70e7166ae24b1367c7c251ead8ad2979f66463ae3e6380eeda84fabb985c3a`**,**四处同值**(本机接线前 / 本机接线后 / 47 生产接线前 / 47 生产接线后)⇒ ⛔ 该值**不作为表值固化**(它是**某次夹具的读数**,夹具变则值变);**口径**才是不变量 = 取回字节**必须**复算出请求的同一个块 id,不符 ⇒ **丢弃且不算命中**(`idMismatches` 计数,⛔ 不静默返"没有")。**先红两腿**已实测可用:扰动 `blockIdOf`(digest → `fbb80585c86a4c492f6d71a7af9212121f35c2c43f2cc7018d08149e2cff5ac3`)/拆闸门(`peer-wire` 判据 ⑦ 由 PASS 转 FAIL,篡改块被记成"命中成功"= **假绿本形**)。
**④ 🔴 D8 仍未放行(红 · 只读核对、⛔ 未实施)**:双向并发 UDP 实测 **47 侧 `sent=29 / recvLocal=0`**、**106 侧 `sent=28 / recvLocal=0`**(`reason=deadline`);两机 `nft` **72 行**未动 ⇒ 挡在**云侧**。**拟录 乙-1 两条**(目标 `47.77.182.89` / `106.54.21.172` × 入站 × **UDP** × `21100/21115` × 源分别为对端 `/32`)**原样维持** —— ⛔ **无任何 `0.0.0.0/0` 形态**、⛔ **未索取 / 未硬编码任何云 AK/SK**。⇒ 真机**双向直连不可达属已知**,`peer` 档在 SG 未开时的**正确形态 = 回落 wss 且块 id 不变**(即 D7 所要验的形态)。
**⑤ 边界自证**:⛔ 未 commit / 未 push(HEAD 仍 **`09ce76f`**;`git status --short` **19** 处 = 开工 17 + 本棒 2)· ⛔ 未改 `src/net/relay/server.ts`(**零字节**)· ⛔ **未改 `src/net/relay/content/**` 的块 id 计算路径**(`sha256(密文)` 口径未动;扰动实验为**临时**且**已逐字还原**)· ⛔ 未改 `nft`(**72 行**)/ `nginx` / `bwrap` · 🔴 `RELAY_FAILOVER_COOLDOWN_MS=0` 命中 **0** · 🔴 密钥本体 ⛔ 不经网络 / 不经 relay(`/etc/dshs/content-group-key.json` 两机仍 `600`)。
### §11.10 补记(2026-09-18 14:5x,序㊷ 执行棒 · **P2/S7:规模回归(候选数 / 直连成功率 / 切换耗时 / 回源字节 四项复测)**;**同在 §10 指纹口径之外**)
**① 本表变更 = 0**:本棒 ⛔ **未新增任何键、⛔ 未改任何值、⛔ 未改任何阈值**。🔴 **§10 指纹** = **`6b37bfd506758d882d9f803678f85d23`**(**现算**,与 §11.7–§11.9 记的值**逐字一致**)—— 佐证"§11 小节在 §10 口径之外"。⛔ **零个新增 / 改动生产 env**(`DSHS_OVERLAY_DIRECT` 两机命中 **0** ⇒ 走**缺省即开**);⛔ **未新增任何监听口**(收口后 `ss -lunp` 在 `21100–21115` 段 **0 行**)。
**② 四项复测读数(⛔ 不冒充"网络能不能打洞")**
| # | 项 | 本棒原始读数 | 表内落点 / 基线 | 判定 |
|---|---|---|---|---|
| ① | **候选数** | `dshs@47` `count=3 hosts=3`(`source=chain` → 收口后 `cache`)|`dshs-worker@106` `count=3 hosts=3`(`resolves=744 source=chain`)|直连候选准入 `accepted=3 / rejected=9(全具名)/ silentRejections=0 / bucketsPerNetwork=true`|**规模夹具** 2 网 × {4,16,64} 节点 ⇒ `accepted 10/34/130`(**线性**)、`rejected 32/128/512`(8 类原因各 perNet 条)、`silentRejections=0`、按网分桶 ✅ | `CAND_MIN=2`(§6 `OBS-21`)|`DIRECT_CAND_ACCEPT_MIN=2` / `DIRECT_CAND_REJECT_MIN=3`(**§3.11**) | ✅ |
| ② | **直连成功率** | **本系统(离线 NAT 模拟器,同一份 `runPunchAttempt`)正腿 `24/24 = 1.0000` 双向、p50 建连 155 ms**;负腿 `one-way(a/b)` 各 12 全 0、`both` 12 全 `deadline`|**真机** `47→106` `sent=21 recv=0`、`106→47` `sent=28 recv=0`(`reason=deadline`) | `DIRECT_PUNCH_MIN_OK=1`(§6 `OBS-28`①)|`HOLE_PUNCH_RATE_LOCAL`(**§3.2**)= **分层可行性** 2/2 —— ⚠️ **它是"能不能打洞",⛔ 不是"本系统成功率"** | ✅ **判据成立** / 🔴 **冗余未建成**(真机 0,D8) |
| ③ | **切换耗时** | `幕4-C` **20871 ms / 30000**(`PASS`);`幕1-A` **20605 ms**;`幕4-A` **18815 ms** —— 三处**均在 deadline 内侧**;收口后 `jitter.p95AbsDeltaMs=6 ms / overThreshold=false` | 🔴 阈值权威落点 = **§4 `RELAY_FAILOVER_DEADLINE_MS = 30000 ms`**(⚠️ **⛔ 不是 §3.2** —— §3.2 是**打洞率**;四段分解在 §9 第 9 行,修后基线 **20.9–23.9 s**) | ✅ |
| ④ | **回源字节** | 夹具 = 同一份 5,255,225 B / 6 块(逐次计 `origin` 取块):`1组×4节点` amp **1.0000**(5,255,225 B)`/ 1组×16节点` amp **1.0000** `| 4组×4节点` amp **4.0000**(21,020,900 B)`/ 8组×4节点` amp **8.0000**(42,041,800 B)|对照腿(peer 关)= `4.0000 / 16.0000 / 16.0000 / 32.0000`|🔴 **每组合计恒 `1.0000`** ⇒ **回源字节 ∝ 组数,⛔ 不随组内节点数增长**|运行时接线腿(8 组×4 节点)`direct-优先` `hits.direct=144 / hits.wss=0`、`idChecks=144 / idMismatches=0` | §7 既有读数(序㉔ `1.0000 ⇄ 4.0000`;序㊛ `4 组×4 节点 = 21,021,572 B = 4.0001×`) | ✅ **规模不变量成立** |
**③ 与 §5(**45% 中继口径**)的关系**:⛔ **本轮不触发 §5 的任何回头条件** —— §9-8「打洞失败率导致中继容量超 45% 设计值 ⇒ 回头重算 `RELAY_MAX_HOSTS`」**未命中**:真机打洞**成功率 0**(D8 未放行)⇒ 跨机流量**本来**就走中继,中继仍按 **45% 异构口径**(`DESIGN_MARGIN = 0.45`)设计、**容量未承压**(实测 `used=2 / max=7515 / utilPct=0`,`OBS-20` 余量 70 个百分点、软门 `utilRefused=0`)。⇒ **本棒 ⛔ 未动 `RELAY_MAX_HOSTS`(7515)**,⛔ 未重下发 `capacity.conf`。⚠️ 待 **D8 放行**、直连真机成功率可测后,§5 才有**新增量**可算(那将是一次独立回头)。
**④ 与 §7(**待测项汇总**)的关系**:本表 `待测` 单元格**仍为 0 个**(⛔ 本棒未新增键、⛔ 未把任何估值冒充实测)。⚠️ **交接单 §10 的三条**(与 §7 口径**不同层**、⛔ 别混)本棒各有增量,**但都 ⛔ 不写成"已判定"**:**§10-1 本系统打洞成功率** ⇒ **模拟器 1.0000(n=24)/真机 0.0000(n=2 方向)并列**,公网场景**仍不可判定**(真机腿被云安全组挡着);**§10-2 中继带宽节省比例** ⇒ 内容面 **75.00%**(组内 4 人)/**93.75%**(组内 16 人)= `1 − 组数/节点数`,**真机直连 = 0**;**§10-3 直连切换耗时** ⇒ **18.8–20.9 s**(n=3,均在 30000 ms 内侧)。
**⑤ 读数与产物**:探针 **28 PASS / 0 SKIP / 0 FAIL**(rc=0;**开工前 + 收口后两次同值**)|`npm.cmd test` **201/200/0/1**(逐字同基线)|`--scene all --table` **12 PASS / 0 SKIP / 0 FAIL**(rc=0;**6m20s**;⛔ **未套外层超时** ⇒ 末尾 `restore()` 正常执行、两台 relay `active`)|`git status --short` **19** 处(与序㊶ 收口**逐数相同** ⇒ 零新增条目)。**产物** = 工作区根 **`交接单_覆盖网络直连与P2P_20260918.md` §8.9**(**§8 前前缀 `8925c2460810c6dc5cdbc075b6cdaed2` 写入后逐字不变** ✅)。
### §11.11 补记(2026-09-18 15:2x,序㊸ 执行棒 · **观测面缺口收口:① 探针在 106 上可跑 ② 参数表定位与 cwd 解耦 ③ 106 残留副本处置**;**同在 §10 指纹口径之外**)
**① 本表变更 = 0(键 / 值 / 阈值全未动)**:本棒 ⛔ **未新增任何键、⛔ 未改任何值、⛔ 未改任何阈值** —— 三项收口全部落在**脚本侧**(`overlay-direct-probe.cjs` / `overlay-probe.cjs` / `overlay-failover-drill.cjs`)。🔴 **§10 指纹** = **`6b37bfd506758d882d9f803678f85d23`**(**现算**,与 §11.7–§11.10 记的值**逐字一致**)—— 又一次佐证"§11 小节在 §10 口径之外"。⛔ **零个新增 / 改动生产 env** · ⛔ **零新增监听口**。
**② 两条必须知道的"取数路径"事实(⛔ 不是阈值,故不进 §3/§6)**
- **参数表定位已与 `cwd` 解耦**(`overlay-probe.cjs` + `overlay-failover-drill.cjs`)⇒ **从代码仓根 / 任意目录都能跑**。候选目录链(写死)= `--table`(显式文件,**最高优先、直接返回**)> `--dir` > `DSHS_OVERLAY_TABLE_DIR` > **机器本地注册文件**(`DSHS_OVERLAY_TABLE_REGISTRY`,缺省 **`~/.dshs/overlay-table-dir`**,一行一个目录、`#` 注释)> `cwd` > 脚本自身目录及其上两级。
- 🔴 **"恰好 1 个才算合法"保留、且更严**:任一候选目录 ≥ 2 份 ⇒ **立即报错**;**跨候选目录合计 ≥ 2 份 ⇒ 也报错**(逐份列出)。**一份都没找到 ⇒ 报错**并列出**所有搜过的目录** + 注册方法;⛔ **未放宽成"找不到就用缺省"**(两腿防错实测:同目录 2 份 ⇒ rc=2;跨目录 2 份 ⇒ rc=2;**探针与演练各测一次**)。
- 🔑 **为什么需要"注册文件"**(实测):本表**不在仓内**(`git ls-files | grep -c 参数表` = **0**)而在**工作区根**,且本机仓在 `D:`、工作区在 `E:`(**不同盘**)⇒ 纯路径推算到不了。⇒ **机器差异一律放 `$HOME`,⛔ 仓内零机器路径**。**换机器 / 换工作区路径 ⇒ 需补这一行**(或改用 `--table` / `--dir` / 两个 env)。
**③ 观测读数形状(序㊸ 起)**:106(**worker / relay 节点**)的 `/opt/dshs-cluster/lib` **无 `registry.js`**(**控制面注册表**模块)⇒ `overlay-direct-probe.cjs selfcheck` 的 **`join 回读`(判据 D3)那条腿**在节点形态下**具名降级**:
`{"degraded":true,"degradedLegs":["joinConf"],"joinConf":{"available":false,"reason":"module-missing","missing":[{"spec":"../lib/net/relay/registry.js",…}]}}` ⇒ 文本行 `join 回读:⛔ 不可用(module-missing)—— 缺 …|⚠️ 这是「没装」不是「没采到」`。**其余子命令与其余读数照常**(106 上 `status` / `punch` / `selfcheck` **三条全通**,`candAcc=3 / candRej=9 / silent=0 / 打洞双向=true / 冷却 300000 / 零值 throws`)。🔴 **⛔ 不许静默返"没有"**:缺块**不填** `direct` / `readback`("没装"与"跑了但读数为空"**形状不同** = 可分)。
- ⚠️ **随之而来的 `OBS-26` 子判据口径(登记在此,§6 行文本本棒 ⛔ 未改 —— 为保 §10 指纹)**:读数生产方**显式**给出 `joinConf.available === false` 时,`OBS-26` 的 join 子判据取 **SKIP + 留痕**(⛔ 不判红,detail 点名缺哪个模块);**老形状(无 `available` 键)走原路径、判定与文本逐字不变**。⇒ 下次有键值变更时**顺带并入 §6 `OBS-26` 行**。
**④ 读数(零回归 + 106 侧)**:探针 **28 PASS / 0 SKIP / 0 FAIL**(rc=0;**代码仓根不给 `--table` 一次 + 收口后工作区根一次,两次同值**)|`npm.cmd test` **201/200/0/1**(逐字同基线;`duration_ms=53315.1989`)|`--scene all`(**无 `--table`,从代码仓根**)**12 PASS / 0 SKIP / 0 FAIL**(rc=0;**6m23s**;`幕4-C` **17240 ms / 30000 ms**;表路径原文 `# 参数表=E:\ProgramData\AIProject\ai1net-dsh-server\参数表_覆盖网络_20260917.md`)。**106 侧**:`status` / `selfcheck --json` / `punch --peer 47.77.182.89:21100` **三条 rc=0**(punch 原文 `窗内零收包(deadline 3000 ms,实耗 3000 ms,发 21 包)⇒ 判死 + 进冷却 300000 ms`)。**探针三处副本 md5 全同** = `4734686b5270059697ccbcf966c34ade`(仓 / 47 `/opt/dshs/scripts` / 106 `/opt/dshs-cluster/scripts`);**回滚点** = 两机 `/opt/dsh/backups/seq43-20260918-150549/`(md5 `8d1ea60a01fc09fb5f56e492afb76fc9`)。
**⑤ 产物** = 工作区根 **`交接单_覆盖网络直连与P2P_20260918.md` §8.10**(**§8 前前缀 `8925c2460810c6dc5cdbc075b6cdaed2` 写入后逐字不变** ✅)。🔴 **D8(云安全组)本棒零动作**(⛔ 未改云侧规则、⛔ 无 `0.0.0.0/0` 形态、⛔ 未索取 / 未硬编码 AK/SK)—— 仍在册待拍板。
### §11.12 补记(2026-09-18 22:2x,序㊺ 执行棒 · **低熵块治理 · `M1` 四项读数(本线首次实测)**;**同在 §10 指纹口径之外**)
**① 本表变更 = 0(键 / 值 / 阈值全未动)**:本棒 ⛔ 未新增键、⛔ 未改任何值、⛔ 未改任何阈值 —— 新增内容**全部是读数**。🔴 **§10 指纹(现算)= `6b37bfd506758d882d9f803678f85d23`**,**与写入前逐字一致** ✅(又一次佐证"§11 小节在 §10 口径之外")。⛔ 零个新增 / 改动生产 env · ⛔ 零新增监听口 · ⛔ 未动 `CONTENT_BLOCK_SIZE`。
**② 度量对象(**真实内容** · ⛔ 零合成字节)**
- **S1 = 真实首屏包**:`GET https://admin.alotbuy.com/` 壳页 → 取页面内**全部** `/plugins/??…&rev=…` combo,**按页面顺序**拼接(R4 临时会话,用完即删)。⚠️ **源码事实纠正**:combo 由宿主**运行时拼装**(`@deepseek-ai/dsh-client-modules` 的 `README.zh.md`:快照式提供、按 **3 KiB** URL 上限分区)⇒ **盘上不存在"单文件产物"**,只能 HTTP 取。
- **S2 = 真实独立分发内容** = S1 内的 **60 份 combo**(每份 combo 即一份独立分发内容),其中 **56 份**是「整体 < 1 MiB 的单块内容」。
- 🔴 **口径纠正(重要)**:本棒实测 S1 总量 = **23,629,336 B**,**远大于**本表 `:146` 注里引用的历史值 **11,363,655 B**。后者 = **最大单条 combo**(本棒实测 **11,794,471 B ≈ 12 块**)—— 历史档案里的 `11,363,655 B` **⛔ 不是"全部 combo 之和"**(当年只测了那一条 200 响应)。本棒流 md5 = **`41333ba2d6b480036c694d9fee7e5c1c`**(⚠️ combo 分区随实例启动世代变 ⇒ **跨重启不可逐字复现**,复现须记 `rev` 世代)。
**③ `M1` 四项读数**(阈值 `H ≤ 4.0 bit/byte`;子窗口 **4 KiB** / stride **4 KiB**)
| 项 | 读数 |
|---|---|
| **`M1-a`** 种类数 / 重复率 | 总块 **23** |唯一 id **23** |重复率 **0.00%** |完全重复块 **0** 个 |
| **`M1-b`** 低熵块数 / 体积 | **0 块 / 0 B** |逐块 H ∈ **[5.1807, 5.8444]** |**H 直方图 = `{5.0-5.5: 8, 5.5-6.0: 15}`** |
| **`M1-c`** 占首屏包比例 | **0.000000%** |
| **`M1-d`** 子窗口熵(反向腿) | 窗口 **5,768** 个 |**低熵段 15 段**,合计 **241,664 B = 1.0225%** |段字节 min **4,096** / max **81,920** |直方图 **`{≤4KiB: 10, 4KiB-16KiB: 3, 16KiB-64KiB: 0, 64KiB-256KiB: 2, 256KiB-1MiB: 0, >1MiB: 0}`** |最大两段 = 81,920 B ×2 |
| (S2)低熵内容 | 60 份中 **0 份**含低熵块 |逐份 Hmin ∈ **[4.8354, 6.3485]** |单块内容 **56/60** |
| S1 逐块 H | 5.2089 5.5612 5.7684 5.5095 5.6777 5.4695 5.6530 5.6974 5.2515 5.5237 5.5102 **5.1807** 5.2527 5.7228 **5.8444** 5.6450 5.5956 5.6805 5.6275 5.2722 5.3887 5.5581 5.3555 |
**④ 三条判据性结论(每条引上表数字)**
1. ✅ **"首屏包内不存在纯低熵块" —— 证实**(`04-133 §1.1` 推论 1):23 块**全部** H ≥ **5.1807** ⇒ `M1-b` / `M1-c` 皆 **0**。
2. ❌ **"< 1 MiB 的独立小内容若是低熵 ⇒ 纯低熵块" —— 未证实**(`04-133 §1.1` 推论 2):S2 里**确实有 56 份单块小内容**,但它们**全是高熵 JS bundle**(Hmin ≥ **4.8354**)、**低熵内容 0 份** ⇒ **该风险面在当前真实内容上不存在**。
3. ⚠️ **"什么算低熵"的阈值无法由本次读数定死**(对应 `04-133 §10-3` 在册项):全部块落在 **5.0–6.0** ⇒ **阈值取 ≤ 5.0 时"低熵块"恒 0**;**字节分布熵对 JS 文本不是强判别器**(参照系:均匀随机 ≈ 8.0、全零 = 0)⇒ 🔑 **`M1-d` 才是有效的腿**:它抓到 **15 段 / 1.0225%** 的**块内低熵片段**(max **81,920 B ≪ 1 MiB**)⇒ 印证"低熵以**块内小片段**形态存在、且**已被同块高熵内容稀释**"。
**⑤ 对 **D(非确定性)** 的作用面判定**:🔴 **D 的目标在当前真实内容上已由「定长 1 MiB 切分」结构性达成** —— 低熵物质只以 **≤ 81,920 B 的块内片段**存在(占 **1.0225%**)⇒ **对块级去重 / 枚举不可见**;且**不存在低熵内容单元**(④-2)。⇒ **`D-1` 无对象可稀释、`D-2` 的启用前提不成立**(`04-133 §4.1/§4.2` 的决策点 4 **两分支都不命中**)⇒ **本棒判:D ⛔ 本轮不实现**(实现 = 纯增复杂度 + 测量上零收益 ⇒ 触 **R11**「拿不出正向做法 ⇒ 立即停止」)。⚠️ **这不是降级 / 不是静默兜底**:目标已由**实测**证实达成。🔑 **D 的回头条件(具名)= 出现「低熵内容单元」** ⇒ 由 ⑥ 的固化探针**可重复复查**(再测一次即可判定,⛔ 不需要先写代码)。
**⑥ 产物(只读探针 · 零第三方依赖)**:**`scripts/overlay-entropy.cjs`** —— ⛔ 不 ssh / ⛔ 不碰网络 / ⛔ 不写生产路径,只读本地流文件;**切分调用仓库那份 `chunkify`**(`lib/net/relay/content/chunker.js`,即 `src/net/relay/content/chunker.ts` 的编译产物;可用 `OVERLAY_CHUNKER` 覆盖)⇒ ⛔ 无算法复刻(无双源)。**复现**:`node scripts/overlay-entropy.cjs --in <S1流> [--parts parts.json] [--json] [--window 4096] [--stride 4096] [--threshold 4.0]`。
🔴 **取数侧的两条实测事实(⛔ 不是阈值,故不进 §3/§6)**:**(a)** `/plugins/??…` **有鉴权**(无会话 ⇒ **401 / 24 B**)⇒ 必须临时会话;**(b)** 平台控制面的**权威库 = PG**(`DSHS_DB_URL=postgres://dshs@127.0.0.1:15432/dshs`,见 `dshs.service.d/cluster.conf`)⇒ 仓里的 `mksess.cjs` **写的是 SQLite `/var/lib/dshs/dshs.db` ⇒ 已失效**(插进去的会话 Manager 查不到 ⇒ 一插就 401)。🔴 **这是一条待收口的缺陷**(⛔ 不属本单产物,另立小项)。
---
### §11.13 补记(2026-09-18 22:3x,序㊻ 执行棒 · **C(块 id 域分离)落地 + `OBS-29` 判据重裁**;**同在 §10 指纹口径之外**)
> 落点段 = **§6 判据表**(新增 **`OBS-29`** 一行)+ **§10 指纹**(重算并回填)。🔴 **⛔ 未新增任何阈值键 / ⛔ 未改任何既有值 / ⛔ 未改任何超时 / ⛔ 未动 `DEFAULT_BLOCK_SIZE`**。
**① 新增 `OBS-29`(判据面)** —— 位置 = §6 判据表 **`OBS-28` 之后**(编号承接**现表最大值** ⇒ ⛔ 未跳号、⛔ 未复用)。五条腿**全部是布尔** ⇒ ⛔ **不需要任何新阈值键**(否则就是"凑数键"):
| 腿 | 内容 | 类别 |
|---|---|---|
| `P1` | 同字节 + **不同 network** ⇒ 块 id 必须不同,且**两侧都不等于裸 sha256** | 正腿 |
| `P2` | **同 network** + 同字节 ⇒ 块 id 必须相同(**去重不得丢**) | 正腿 |
| `P3` | 装配面(store 写/读复算 + 链取回 + 重组位)**无漏改** | 正腿 |
| `N1` | **具名负腿 `flat-key-collapses`**:netKey 取**同值** ⇒ `P1` 必红 | 负腿 |
| `N2` | **具名负腿 `empty-key-falls-back-to-bare-hash`**:netKey 取**空** ⇒ `P1` 必红 | 负腿 |
**② 判据重裁(本棒存在的主因)** —— 序㊺ 实测判 **D ⛔ 本轮不实现**(§11.12-⑤)⇒ **原 `OBS-29` 那条"稀释源换成确定性派生量 ⇒ 必红"的负腿已失去对象** ⇒ **重裁为 C 的判据**(= 上表五腿)。🔴 **⛔ 不许只给正腿** —— 本线慢性病 = "装了没生效" = **静默通过** ⇒ 两条负腿**都真跑过并具名**(见 ④ 先红后绿)。
**③ `E1` 基线重取** —— `E1` **定义不重估**(仍是「回源字节 ≈ **1 份 × 组数**」),**只重取基线读数**(C 改了 id 口径 ⇒ **旧读数作废**)。新读数:**同网 4 轮 × 4 块 ⇒ 全部 local 命中**、`source.origin = 0`、`store.puts = dedupIds.length`、`putRejected = 0`;**跨网起点 `local = 0` ⇒ 各 1 份**。
**④ 演练临时副本(⛔ 非权威,用完即删)** —— 本棒为跑通 `overlay-failover-drill.cjs` 生成过 `_tmp_seq46/参数表-演练临时.md`,**唯一差异 = `SSH_TIMEOUT_MS` `20000 → 180000`**。原因(实测):`currentChannelHint()` 的 `journalctl -u dshs --since -6h` + `grep -F '[overlay-dir] 取址'`(每 2 s 一条 ⇒ 6 h ≈ 1 万行 / ≈ 1.3 MB)**单次 ssh 需 ~47 s > 20 s** ⇒ `spawnSync ssh ETIMEDOUT`。⚠️ 该函数**源码注释自述「仅供人读,不参与判定」** ⇒ 属**已知健壮性缺陷(另立小项)**;⛔ **本棒未改演练脚本**。**权威参数表(本文件)除 ①② 外一个字节未动**。
### §11.14 补记(2026-09-19 00:1x,序㊼ 部署棒 · `C` 落地两机 + `OBS-29` 真机腿 PASS · ⚠️ `OBS-09` 退化 SKIP;**同在 §10 指纹口径之外**)
> 落点段 = **§6 `OBS-29` 行的「⏳ 真机腿」结清**(⛔ **未改该行任何字**—— 结清体现在本补记)。🔴 **⛔ 零个新增 / 改动生产 env · ⛔ 未改任何阈值 / 超时 · ⛔ 值格一律未动 · ⛔ 未动 `DEFAULT_BLOCK_SIZE`**。
**① 部署(**全量**口径 · 本棒实测认定)** —— 两机 4 处 `lib` 实为**历次增量 scp 的叠加**(47 `/opt/dshs/lib/net/relay/index.js` mtime **12:55:27**,而同目录 `content/chunker.js` **06:52:16**)⇒ 与本机 `build` 产物**同名 md5 不同 38–41 个**。本棒据此按**全量替换**(**270 文件 × 4 处**);包 = `dshs-lib-seq47.tgz`(**681,966 B**,md5 **`546b30719ca1bfca1e04189e1099424e`**);🔴 **先传包 → 两机各自验包 md5 → 两机同一窗口内解包 + 重启**。**核对 = 不一致 0**(与代码仓 `lib` 同名 md5 不同 **0** / 仅本机有 **0**;**270 × 4 = 1080** 对全额逐字节一致;各位置多出的 11–12 个 = 历史 `.bak-*` 遗留,⛔ 未删)。**回滚点** = 两机 **`/opt/dsh/backups/seq47-20260918-2347/`**(4 处;47 = 262 / 263 文件,106 = 260 / 262;共 6.2M / 6.1M)。
**② `OBS-29` 真机腿(本棒主判据 · ✅ PASS)** —— 47 / 106 `/status.content` = `blockIdKeyed **true**`、`blockIdKeyId **d6e62322e5166938**`(**两机逐字相同**)、`blockSize **1048576**`(⛔ 未动)、`storeMaxBytes **67108864**`(⛔ 未动)。**第二来源** = 两机 relay 启动**判别器**日志原文(47 **23:47:43** / 106 **23:47:37**):`[content] 块 id 口径 = HMAC-SHA256(域密钥) blockIdKeyId=d6e62322e5166938`。⇒ §6 该行「⏳ 真机腿(须部署后补)」**已结清**。
**③ 🔴 `OBS-09` 由 PASS 退化 SKIP(**既有缺陷**被本棒硬要求的「重启 106 `dshs-worker`」触发 · ⛔ 非本单引入)** —— 原文 = `SKIP OBS-09 在册实例面 无(从 /status.endpoints[] 派生为空:端点表 1 条 = agent 1 条 + 离线 0 条)`。**事实链**:106 实例 **`286172`**(`dsh --profile web --port 21001`)**启动于 10:18:34**(= 重启前 13.5 h)、**未被 teardown**(`OBS-22 scanned=1 adopted=1 stopped=0` + 日志 `[rehydrate] probe OK dsh-100002-7d1c8cbf.scope :21001`)⇒ 但 47 端点表**只有 `w-106:19000`、缺 `w-106:21001`**(本表 §3.9 `PEER_INSTANCE_PORT` 行 10:2x 实测**应有两条**)。**根因** = `relay-tunnel.ts#cancel()` 只撤销**当前进程** `forwarded` 集里的端口 ⇒ 上一进程遗留条目**无人撤销**;**恢复路径须"实例重新拉起"**(端口登记发生在 spawn 流程内)⇒ 与 memory 明令「⛔ 别为迁就旧值重启生产实例」冲突 ⇒ **本棒不修、不凑绿**(另立小项)。⏳ **本行 / §6 `OBS-09` 判据口径本棒⛔ 未改**。
**④ 零回归** —— 探针 **28 PASS / 0 FAIL / 1 SKIP**(rc=0;基线 **29P/0S/0F** ⇒ 差 1 = 上述 `OBS-09`)|`--scene all` **12 PASS / 0 SKIP / 0 FAIL**(rc=0;**6m28s**)✅ 逐字同基线|`npm test` 本轮 **⛔ 未跑**(本棒**零代码改动**,未触单测面)。
**⑤ 指纹** —— §10 现算 **`ac6bbbbb8c92bd57ff0dc4cd8f4983ba`**(与序㊻ 记录值**同值** ⇒ 本补记在 §10 口径之外);🔴 **零个新增 / 改动生产 env** · ⛔ **值格一律未动**(本棒只做部署与读数)。
### §11.15 补记(2026-09-19 01:0x,序㊽ 执行棒 · **修「重启 worker 后实例端口注册丢失 / 孤儿端点条目不自愈」⇒ `OBS-09` 由 SKIP 回到 PASS**;**同在 §10 指纹口径之外**)
**① 缺陷与落点(文件 + 行,⛔ 不是 spawn 流程)** —— `reconcileTunnel()`(对账自愈)**早就存在**(`src/worker/agent.ts:256-264`),链路是通的,只是**看不到**那条端口:对账口径 `live` **只取** `spawner.listUserInstances()`,而它的实现是 `return [...this.mains.values()]`(`src/supervisor/orchestrator.ts:558-560`)=**本进程 `launch` 过的**;认领来的既有实例**刻意不进 `mains`**(`src/supervisor/orchestrator.ts:290` 原文「已被「认领」的既有 scope —— ⛔ 刻意**不进** `mains`」,理由见文件头 序㉕:`launchToken` 不可恢复)⇒ 上一进程遗留的实例端口**没有任何人**会重新声明。relay 侧则**本来就等着**:`dropSession()` **只摘会话、⛔ 不删条目**(`src/net/relay/server.ts:2151-2203`),而 `onPortChange(add=true)` 命中既有条目时走 `ensureEndpoint()` ⇒ **复用条目、只换绑定会话**(`localPort` 沿用)⇒ **重登记 = 就地覆盖孤儿**。🔴 **⇒ §11.14-③ 那句「恢复路径须"实例重新拉起"」已被本棒实测证伪**:**存在零中断路径**。
**② 修复(2 个源文件 · ⛔ 零新依赖 / 零新监听口 / 零新 env)** —— `orchestrator.ts` 新增 `adoptedInstancePorts()`(只报 `alive === true` 且带端口的认领记录)+ `onRehydrateSettled` 回调与 `pendingProbes` / `rehydrateScheduled` 落定计数;`agent.ts` 把对账口径改为 `listUserInstances()` **∪** `adoptedInstancePorts()`,并抽出 `tunnelTick()` 供 20 s 定时器与「认领落定」回调**共用一份语义**。🔴 **⛔ 未绕过既有准入 / 白名单**:新端口走的仍是 `tunnel.forward() → RelayClient.addPort() → PORT_ADD → relay.onPortChange`(含既有 `[base, base+span)` 窗口校验 + worker 侧 `allow` 白名单)。
**③ 读数(探针 · 真机)** —— 改前 **26 PASS / 2 FAIL / 1 SKIP(rc=1)** ⇒ 改后(终态)**29 PASS / 0 FAIL / 0 SKIP(rc=0)**:`OBS-01` `FAIL used=1` → `PASS used=2`;`OBS-08` `FAIL 端点表 1 条 / 离线 1 条` → `PASS 端点表 2 条 / 离线 0 条`;`OBS-09` `SKIP 派生为空(agent 0 条 + 离线 1 条)` → `PASS 在册实例面(派生 1 条):w-106:42497=401`。⚠️ **如实纠正**:§11.14-③ 与本棒 prompt 记的「28P/0F/1S」与本棒开工实测**不同** —— 实测 **26P/2F/1S**,因 106 worker 于 **00:04:30** 按**抖动路径**切到 106 自家中继(原文 `[relay-switch] #3 …->wss://106.54.21.172/dshs-relay(原因:当前通道抖动量超标(p95|ΔRTT|=2941ms ≥ 阈值 20ms)…)`)⇒ 47 视角连 `w-106` 会话也一并失去。
**④ 端点表原文** —— 47 视角:改前 `[('w-106',19000,42313,**False**)]`(**孤儿**)⇒ 改后 `[('w-106',19000,45095,**True**), ('w-106',21001,36535,**True**)]`、`sessions=[('w-106',**[19000,21001]**),('manager')]`、`used=2`。🔴 **先红最关键的一条**:改前 **106 自己的中继表里也只有 `19000`**,而 106 上 `ss -lntpH` 明明白白 `127.0.0.1:21001` 在听 ⇒「**实例活着、却没人替它把端口声明出去**」,与"挂在哪台中继"无关。机理原文(47 relay journal):`AUTH OK … ports=[19000]` ⇒ **1 s 后** `host ops/w-106 +port 21001 -> 127.0.0.1:36535`(⚠️ 20 s 定时器首拍在 **00:31:31** ⇒ 这一枪**只能**来自新装的"认领落定即登记";且旧口径下 20 s 拍**同样看不到** 21001)。
**⑤ 孤儿自愈对照(受控 · ⛔ 未停实例)** —— [A] 停 worker 前 `used=2 eps=[(19000,45095,True),(21001,36535,True)]`|[B] `stop dshs-worker`(**孤儿态**)`used=1 eps=[(19000,45095,**False**),(21001,36535,**False**)]` ← 条目**仍留在表里**(= 改前 `OBS-08` 红形态),同时 106 `ss -lntH 'sport = :21001'` = **1 行**(**实例全程未动**)|[C] `start` 后 `used=2 eps=[(19000,45095,**True**),(21001,36535,**True**)]`。🔬 **最强一条**:`localPort` 在 [B]→[C] **逐字沿用**(`45095`/`36535` 未变)⇒ 证明是「复用既有条目、只换绑定会话」,⛔ **不是**新开监听口。
**⑥ 部署 / 回滚点** —— 包 `dshs-lib-seq48.tgz`(**685,368 B / 270 文件**,md5 **`a902c936cefdeeb3eb13c67dfcb5dd00`**;两机解包前**各自验包 md5** 均一致)。**全量核对 = 不一致 0**(**270 × 4 = 1080** 对;缺失 **0**;各位置多出 11–12 个 = 历史 `.bak-*`)。**回滚点 = 两机 `/opt/dsh/backups/seq48-20260919-0030/`**(47 = 282 / 281 文件;106 = 282 / 281)。⚠️ **如实留档**:首轮"两机同窗口"脚本把 `cd` 写进后台复合命令内 ⇒ **106 未执行、47 已落地** ⇒ **实际窗口相差 ≈ 28 s**(47 **00:30:47** / 106 **00:31:15**);本棒改动为**纯 worker / supervisor 侧增量**(⛔ 无协议帧 / 无接口形状 / 无 env 变化)⇒ 无跨版本不兼容。⛔ 本棒**未为验证重启任何实例**;唯一服务中断 = 受控对照中 `dshs-worker` 停启一次(**停用 ≈ 5 s**)。
**⑦ 未解决项(⛔ 如实 · 交下一棒)** —— **(a) 🔴 观测点单一**:`OBS-01` / `OBS-08` / `OBS-09` 的绿**取决于 106 worker 挂在哪台中继**(探针只读 47 的 `RELAY_STATUS_URL`),而 worker 通道会按**抖动**切换 ⇒ worker 一旦落到 106 自家中继,三项即**再红 / SKIP**。⛔ 本棒 **未动选路策略**(属既有设计)⇒ 登记为**未解决项**。**(b) ⚠️ 死口孤儿未回收**:替换后旧端口条目**没人 `PORT_DEL`** ⇒ 永久 `online=false` 留表(`RelayClient.removePort()` 对"本进程从未加过的端口"早退);⛔ 修它需 worker 能读到中继侧条目表,而二者**可能不同机** ⇒ 非本棒可及。
**⑧ 指纹与零变更** —— §10 现算 **`ac6bbbbb8c92bd57ff0dc4cd8f4983ba`**(与 §11.14 **同值** ⇒ 本补记在 §10 口径之外);🔴 **⛔ 值格一律未动**(含 `PEER_INSTANCE_PORT` 仍 **21001** —— 那是"某次现测"的快照,且 `OBS-09` 已不从该键取值)⇒ **零个新增 / 改动生产 env** · ⛔ 未改 nft / nginx / bwrap · ⛔ 未调 `RELAY_FAILOVER_DEADLINE_MS` / `HB_SEC` / burst · ⛔ 未禁用 `COOLDOWN_MS=0`。
### §11.16 补记(2026-09-19 01:0x–01:4x,序㊾ 执行棒 · **`OBS-01` / `OBS-08` / `OBS-09` 的数据源改为「两台中继并集」⇒ 消除「worker 归属漂移」造成的假红 / 假 SKIP**;**同在 §10 指纹口径之外**)
**① 本表变更 = 0(键 / 值 / 阈值全未动)** —— 本棒 ⛔ **未新增任何键、⛔ 未改任何值、⛔ 未改任何阈值**;唯一改动在**脚本侧**(`scripts/overlay-probe.cjs`,**单文件六处**,见 §11.15-⑦(a) 那条遗留)。⇒ 上表 §6 里 `OBS-01` / `OBS-08` / `OBS-09` 三行的**阈值与判据形态**逐字不变,**只换了数据源**(判据行末的口径说明另行补注于交接单 §16-2)。
**② 口径(并集 · 唯一入口 `readPeerView()`)** —— 两台中继**各自只看得见挂在自己身上的客户端** ⇒ 单看 47 会让三条判据的绿**取决于 106 worker 挂在哪台中继**(worker 按**抖动**换址,既有设计)⇒ 漂移态下 47 视角同时失去"会话"与"实例面" ⇒ `OBS-01` FAIL(`used=1 < 2`)/`OBS-08` FAIL(离线孤儿)/`OBS-09` SKIP(派生为空)= **假红 / 假 SKIP**。口径 = **按 `hostId`(含 `network`)合并两台中继视图**:`eps` 键 = `network:hostId:port`、`online` 取**或**、`localPort` 取**在线那一侧**;`used` = `network/hostId` **去重后的并集基数**(⛔ **不是求和** —— 同一节点两台都残留时会**重复计数** = 另一方向的假绿)。🔴 **实例探活必须回到"那一台"上做**:`localPort` 是**中继机回环**落点 ⇒ 归属 106 的端点得 ssh 到 **106** 探(47 上那个口根本不存在)。🔴 **只并这三项**:`OBS-11` 的 `derived` / `OBS-02` / `OBS-13`·`OBS-16`(47 的 `counters`)**一律保持 47 视角**(⛔ 并集只消除"看不见",⛔ 不放宽判据)。🔴 **`OBS-16` 的 Δ 不受影响**:对端那份是**另算的一次独立采样**(读 106 的 `counters`)且位置在**第三次采样之后**(门窗口 `(status2, status3]` 内只有 `sleepSync`)⇒ 对 47 `/status` 的读取次数**一次都没变**;实证 = 47 `statusHits 5→6 (Δ=1)` 而**中间插了 2 次读 106**、106 自身 `2→3`。
**③ 读数(探针 · 真机)** —— 漂移态(47 视角 `used=1 / endpoints=[]`、106 视角两条 `online=true`):`PASS OBS-01 used=2(并集: 47=1 + 对端=1 去重后计)`|`PASS OBS-08 端点表(并集) 2 条 / 离线 0 条(47 视角 0 条 + 对端 2 条)`|`PASS OBS-09 在册实例面(并集派生 1 条):w-106:40701=401` —— 🔬 探的是 **106 机上**的回环落点(47 上不存在该口)⇒ "按 `from` 分机探活"这条腿**真走到了 106**。**终态(收口后)** = **29 PASS / 0 FAIL / 0 SKIP(rc=0)**(`OBS-09` 落点 `w-106:35713=401`)。**夹具先红后绿** = 同一份夹具(真实 `/status` 原文 + 真实 `ss`/`nft` + 参数表副本)**只差**是否给对端那一半:红 `rc=1`(`OBS-08 FAIL 2 条/离线 2`|`OBS-09 SKIP`)→ 绿 `rc=0`(`OBS-08 PASS 2 条/离线 0`|`OBS-09 PASS w-106:36873=401`),两侧逐行 `diff` **只差这 2 行**(`OBS-01` 在夹具模式按既有语义记"未取证" ⇒ 其红腿由真机退化腿承载:对端不可达 ⇒ `FAIL OBS-01 used=1` =改前 47 单点判法)。
**④ 部署(⛔ 零新增监听口 / ⛔ 零服务读取)** —— 47 `/opt/dshs/scripts/overlay-probe.cjs`:旧 md5 **`eaec1ad560adbec37a15cd145a5ebb77`**(109266 B · Sep 18 12:49 = 序㊳ 期,**陈旧**)⇒ 新 **`d2f879dc3220f2800d812437518e3c63`**(135215 B · 0755);106 `/opt/dshs-cluster/scripts/overlay-probe.cjs`:**此前不存在** ⇒ 新落同 md5(root:root 0755)。🔴 **取两机同源**的理由:一份新一份旧/无,日后在那台机上跑一次就得到**旧口径读数**(本线反复踩的"同一事实两处打架")。⚠️ 该文件**不被任何单元读取**(探针是从本机 ssh 出去的工具)⇒ ⛔ 零服务影响。
**⑤ 零变更 / 指纹** —— §10 现算 **`ac6bbbbb8c92bd57ff0dc4cd8f4983ba`**(与 §11.14 / §11.15 **同值** ⇒ 本补记在 §10 口径之外);⛔ **值格一律未动** · ⛔ **零个新增 / 改动生产 env** · ⛔ 未改 nft / nginx / bwrap · ⛔ 未调 `RELAY_FAILOVER_DEADLINE_MS` / `HB_SEC` / burst · ⛔ 未禁用 `COOLDOWN_MS=0`(`grep` 计数 **0**)。⚠️ **未解决项不变**:**(a)** `D8` 云安全组(乙-1)**待你在云控制台落地**(两机+本机均无云凭据);**(b)** 死口孤儿**仍未回收**(并集只消除"看不见",`online=false` 条目仍留表)。