Files
dsh_ai1net_server/归档/交接单-20260924-归档/交接单_检测时延与deadline_20260917.md
T
admin 7bd1151c67 chore(工作区): 归档交接单 27 件 + 新增 CODEBUDDY §9 工作区卫生
交接单按归属约定(正文落文档库、工作区只放指针)归档至
归档/交接单-20260924-归档/,含逐件判定 README:
- 16 件已被文档库正式版取代(T09–T21 + 覆盖网络-24/25/26)
- 4 件主题已被覆盖网络线入口汇总
- 7 件历史接续包/规划件

CODEBUDDY §9:收口清本棒 tmp、tmp 保留期 7 天、禁「待清理」中间态、
工作区入库只放文档与文件、不保留脚本副本、>60 KB 单文件须逐个判。
2026-09-24 07:58:57 +08:00

558 lines
60 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.
# 交接单 · 检测时延与 deadline(覆盖网络 · 序 ⑨)
> **一单只做一件事**:把「杀中继 ⇒ 切流完成」的 **30 s 墙钟拆开量化**,并用**分解结果**决定改哪里 —— 目标是让真机样本回到 `RELAY_FAILOVER_DEADLINE_MS`(30000) 的**内侧**,⛔ **不是**把 deadline 调大。
>
> **上游依据(均已核实,⛔ 不必重读全文)**:
> - `交接单_切流冷却语义_20260917.md` **§8.8-2**(现象 + 口径提示"该值含静默失效检测时延 ≈ 2.5×`HB_SEC`")—— ⚠️ **本单 §1.2 已用实测勘误该口径**,见 `RC-5`
> - 同上 **§8.8-3**(OBS-11 回头条件)⇒ 本单 **§2-P7 已复取**,判定见下
> - 同上 **§8.8-4**(`RELAY_FAILOVER_COOLDOWN_MS=0` 自锁)⇒ 本单 **§7 回滚 ⛔ 禁用该值**
> - 同上 **§9**(`switcher.ts` 阈值六键默认值 `300000 / 30000 / 15000 / 3 / 2000 / 12000` + 三个装配点 + env 覆盖生效)
> - `交接单_中继失败切流_20260917.md` **§8.8-2**(五样本全景 `30563 / 27878 / 29176 / 32550 / 29586`;触发条件 = 样本 > 30000 ⇒ 立即回头)—— **本单的立项依据**
> - 代码单一来源:`D:/github/dsh_shenxian/src/net/relay/switcher.ts`(+ `client.ts` / `web/server.ts`)
>
> **本单性质**:**规划棒产出**。⛔ 只出单、不改服务器、不改代码(规划与执行分离)。
---
## §1 目标
### 1.1 一句话
**先证明这 30 s 花在哪两段上,再决定改「检测」还是改「拨号」** —— 现在证据指向:**检测 ≈ 15 s(优雅重启的 burst 窗口地板)、拨号 ≈ 12 s(对一个必然失败的同机候选白等 `upTimeoutMs`)、真正建连只要 ≈ 2.8 s**。
### 1.2 本单的核心判定:**30 s 去哪了(用现有日志分解,不需要新埋点)**
**方法**:`[relay-client] down / [relay-skip] / [relay-switch]` 三种行**本来就带毫秒级时间戳**(`journalctl -o short-unix`)⇒ **分解是"读现有日志",不是"加新探针"**。
**样本 ① · 序⑧ 幕 4 收口(drill 读数 29586 ms,与五样本里的 29586 同一轮)** —— 逐行原文:
| 事件 | epoch | 相对 BYE | 段长 |
|---|---|---|---|
| `[relay-client] down (peer bye: server restarting (was up)); attempt #0 [graceful, burst window 15000ms]` | `1789617465.999982` | 0 | — |
| `attempt #1, retry in 975ms`(burst 窗口耗尽,退避才从 0 起算) | `1789617481.084742` | **+15.085 s** | **① 检测 = 15.08 s** |
| `[relay-skip] ⛔ 新通道起不来(wss://relay-direct.alotbuy.com/dshs-relay)… 该候选进冷却 300000ms` | `1789617493.375446` | +27.376 s | **② 白等 = 12.00 s**(= `waitUpOn` 吃满 `upTimeoutMs`) |
| `[relay-switch] #1 wss://alotbuy.com/… -> wss://106.54.21.172/…(原因:当前通道不健康(… attempts=4 unhealthyForMs=29376 …))` | `1789617496.196091` | **+30.196 s** | **③ 建连 = 2.82 s** |
**样本 ② · 序⑦ 30563 ms 那一轮** —— 同结构、逐行原文:
| 事件 | epoch | 相对 BYE |
|---|---|---|
| `peer bye: server restarting (was up) … burst window 15000ms` | `1789616881.632718` | 0 |
| `[relay-skip] ⛔ 新通道起不来(relay-direct)… 进冷却 300000ms` | `1789616910.195416` | **+28.56 s** |
| `[relay-switch] #1 alotbuy -> 106(… attempts=5 unhealthyForMs=30563 …)` | `1789616912.919233` | **+31.29 s**(drill 读数 30563 ⇒ 偏移 0.73 s = `systemctl stop` 返回晚于 BYE 的时间) |
**⇒ 两样本结构完全一致:`15.0(检测)+ ~1.5(tick 相位)+ 12.0(白等)+ 2.1(建连)≈ 30.6 s`。**
**分解结论(一句话)**:
> **检测段 = 15 s 不是"半开检测",而是 `gracefulBurstMs` 的地板;拨号段 = 12 s 里 12 s 全是白等(`waitUpOn` 不对终态失败早退),真正的跨机建连只要 2.8 s。**
> ⇒ 两段各占一半;**其中 12 s 是纯浪费**(生产目录前两条候选同在 47)。
**五样本按同一结构反算(⛔ 未逐行复核,须由 §5-S2 验证)**:
| 样本 | 总 | 检测 | tick 相位 | 白等 | 建连 | 是否自洽 |
|---|---|---|---|---|---|---|
| 30563 | 30.56 | 15.00 | ~1.5 | 12.00 | ~2.1 | ✅ 已逐行复核 |
| 29586 | 30.20 | 15.08 | ~0.3 | 12.00 | 2.82 | ✅ 已逐行复核 |
| 27878 | 27.88 | 15.00 | ~0.2 | 12.00 | ~0.7 | ⚠️ 反算自洽,未复核 |
| 29176 | 29.18 | 15.00 | ~0.2 | 12.00 | ~2.0 | ⚠️ 反算自洽,未复核 |
| 32550 | 32.55 | 15.10 | ~0.2 | 12.00 | ~5.2 | ⚠️ 反算自洽,未复核 |
> 🔴 **可被证伪**:以上结构的**硬地板 = 15.0 + 12.0 = 27.0 s**。若 §5-S2 采到**任一 < 27 s 的样本** ⇒ **本单的分解被证伪**,立刻停下报告(⛔ 不许带着错分解去改代码)。
**根因清单(按可省时长排序)**:
| # | 根因 | 证据 | 可省 |
|---|---|---|---|
| **RC-1** | **`waitUpOn` 对"终态失败"不早退** —— `server.ts:499-506` 只轮询 `state === 'up'`,直到 `deadline` 才返回 `false` ⇒ **每个死候选固定吃满 `upTimeoutMs`**。生产目录 `[alotbuy.com(47), relay-direct.alotbuy.com(47), 106]` 前两条**同机**(`server.ts:517-519` 注释原文已承认"这个坑一定会踩到"),杀 47 ⇒ 第一次 tick **必然**先试 `relay-direct` ⇒ **白等 12 s** | 两样本的 `[relay-skip] ⛔ 新通道起不来` 行,间隔 ≡ `upTimeoutMs` | **~12 s** |
| **RC-2** | **`gracefulBurstMs`(15_000) 期间 `attempts` 恒为 0**(`client.ts:565-580` 三处 `this.attempts = 0`)⇒ `unhealthy()`(`switcher.ts:244` = `attempts ≥ 3 ∨ unhealthyForMs ≥ graceMs`)**只能靠 `graceMs=15000` 成立** ⇒ 优雅停机(`systemctl stop/restart`、部署)路径的**检测地板 = 15.0 s** | 两样本 `attempt #0 [graceful …]` → `attempt #1` 间隔 = 15.085 / 15.06 s | 受 DP-4 约束,**本单⛔不改** |
| **RC-3** | **tick 周期 2000 ms** ⇒ 判不健康 → 发起换址之间存在 0–2 s 相位误差 | 实测 0.29 / 1.48 s | 0–2 s |
| **RC-4** | **`DRILL_POLL_MS` = 2000 ms** ⇒ 演练读数本身有 **0–2 s 系统性高估** | drill `waitSwitch` 每 2 s 轮询一次 | **测量误差,非产品时延** |
| **RC-5** | 🔴 **勘误**:上单 §8.8-2 的口径提示"该值**含静默失效检测时延 ≈ 2.5×`HB_SEC`**(= 37.5 s)"在**优雅停机路径上不成立** —— 两样本都**没走到半开检测**(`half-open suspected` 行为零命中)。⇒ 该口径只在**丢包型静默失效**时生效;**`HB_SEC` 不在这条关键路径上** ⇒ ⛔ 别为了它去动心跳/控制面流量模型 | 样本 ①②的原文链条里**无** `half-open` 行 | 0(防误改) |
### 1.3 三问的判定
#### ① 30 s 里多少是检测、多少是拨号?
**判定:检测 15.0–15.1 s(50%)/拨号 12.0 s 白等 + 0.7–5.2 s 建连(50%)。** 依据见 §1.2 两个逐行复核样本。
#### ② 该先改哪里?
**判定:先改「拨号段的白等」(RC-1),⛔ 不动 deadline、⛔ 不动 `HB_SEC`。**
- **为什么不是调 deadline**:deadline 是**验收判据**(上单 §8.8-2 原文:"放宽 = 作废本轮判据")⇒ 调大 = 把问题藏起来,**明确违反"要解决问题、不将就妥协"**。
- **为什么不先动检测段(RC-2)**:15 s 的 burst 窗口是**有意设计** —— `client.ts:30` 注释原文"**计划内下线不是故障**:`gracefulRetryMs` 后立刻重连"。把它改成"graceful 也触发切流"⇒ **每次 relay 重启/部署都切一次流** = 正是 D5 要防的抖动。⇒ **判为已定项,⛔ 不动**(§4.1-D3)。
- **为什么拨号段是"真缺陷"**:12 s 不是"等一个慢候选",是**等一个必然失败的候选**(`waitUpOn` 不看终态),且生产目录结构决定**每次从 47 切走都必然命中** ⇒ 纯浪费、且有确定性的修法。
#### ③ 判据怎么改才算"不将就"?
**判定:把"总时长 ≤ deadline"细化为"三段各自的预算",并把测量误差从判据里剔掉。**
- 总判据保留(`kill → switch ≤ 30000 ms`),但**必须同时看三段**:`检测 ≤ 16 s`(graceful)/`白等 ≤ 1 s`(修后)/`建连 ≤ 6 s`。
- **`DRILL_POLL_MS` 2000 → 500**:否则 ≤2 s 的量化误差会让"是否超 deadline"的判定**既可能假红也可能假绿**(RC-4)。这是**测量修正,不是调参**。
---
## §2 只读前置(⛔ 只读,不改;P1–P10 逐条核实后才允许进 S 段)
| # | 命令(原文) | 期望 |
|---|---|---|
| **P1** | `bash "D:/github/dsh_shenxian/dsh-server-docs/scripts/handoff-guard.sh" --claim-exec "覆盖网络线-序9执行棒"` | `✓ 已持全局执行锁`。**抢不到 ⇒ 有会话在跑 ⇒ 停下报告** |
| **P2** | `"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" "E:/ProgramData/AI技能/aliyun-dsh-server/state.py"` | 锁 owner = 本会话名;HEAD = `640813e`;入口 = `接续入口_覆盖网络线_20260916.md`,其 §2 指向本单 |
| **P3** | `cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md \| md5sum` | **`e6b669c257d8e8964273b3b400238351`**(【实测·本棒复取】;不一致 ⇒ 参数表被人动过,停下报告) |
| **P4** | `cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §8 回报格式/,$d' 交接单_检测时延与deadline_20260917.md \| md5sum` | 与 **§8.9** 里记载的"本单出单值"**逐字一致**(⚠️ 该值**故意只写在 §8 之内** —— §8 本身不计入哈希,写在这里会让哈希自指失效) |
| **P5** | 上两单前缀:`sed '/^## §8 回报格式/,$d' 交接单_切流冷却语义_20260917.md \| md5sum` ⇒ **`aa3a6ec0d66dd81f465cea2a3a08ad27`**;`…交接单_中继失败切流_20260917.md \| md5sum` ⇒ **`419abf308c00b7668e8898aaa91ba9e8`** | 两个值**逐字一致**(【实测·本棒复取】)⇒ 底稿未被改动 |
| **P6** | `cd "D:/github/dsh_shenxian" && grep -n "gracefulBurstMs\|halfOpenMs" src/net/relay/client.ts && grep -n "const waitUpOn" -A 8 src/web/server.ts && grep -n "private unhealthy\|async open\|deps.open" src/net/relay/switcher.ts` | 锚点**存在性**(⛔ 不验行号):`client.ts` ≈ `137`/`146`/`567`/`631`;`server.ts` ≈ `499`(`waitUpOn` 只轮询 `up`、**不看终态**)/`524`;`switcher.ts` ≈ `244`(`unhealthy`)/`300`(`await deps.open`) |
| **P7** | `cd "E:/ProgramData/AI技能/aliyun-dsh-server" && node "D:/github/dsh_shenxian/scripts/overlay-probe.cjs" > /tmp/seq9-probe-s0.txt 2>&1; echo $?` | **12/12 PASS**、`exit 0`。**【实测·本棒 13:3x 复取】当前为 `10/12`(`OBS-09` / `OBS-11` 红)** ⇒ 见下方口径说明 |
| **P8** | `ssh -p 22 bt-server "systemctl is-active dshs-relay dshs \|\| true; curl -s http://127.0.0.1:20080/status"` + `ssh -p 22 test106 "systemctl is-active dshs-relay dshs-worker \|\| true"` | 两台 relay `active`;47 `/status` 的 `capacity.used = 2`、`online[]` 含 `manager` + `w-106`。⚠️ **106 nginx 归宝塔托管 ⇒ `is-active nginx`=`inactive` 是正常态,⛔ 别据以判故** |
| **P9** | `npm.cmd test`(**必须 Node v22**) | **155 tests / 154 pass / 0 fail / 1 skipped**(序⑧ 收口基线;⚠️ 若因新增用例上浮,写清"新增 n / 基线 155") |
| **P10** | 🔴 **分解复现(本单的核心前置)**:`ssh -p 22 bt-server "journalctl -u dshs --since -6h -o short-unix --no-pager \| grep -E 'relay-switch\|relay-skip\|relay-client. down'"` ⇒ 抽 §1.2 的两个样本,**逐行算出四段数值** | **样本①(`unhealthyForMs=29376`)= 检测 15.085 s + 白等 12.00 s + 建连 2.82 s**;**样本②(`unhealthyForMs=30563`)= 28.56 / 12.00 / 2.72 s**。**复现不出 ⇒ 停下报告**(说明 §1.2 的分解是错的,⛔ 不许带着它进 S3) |
### 2.1 P7 的口径(承上单 §8.8-3 的回头条件)—— **本棒已复取,判定:未命中**
- **复取值**:`ss -lntp | wc -l` = **77**(= **76 socket + 1 行表头**);**两次连采同值**(`sample1=77` / `sample2=77`);`nft=72`、relay 口绑回环 `1/1`、门户 `200`、两台 relay `active`。
- **环境态钉因**:`LOCAL_INSTANCE_PORT`(20000) **未监听** ⇒ **47 无活跃实例**(`/opt/dsh/users/main` 存在但无实例进程,`ps` 里只有控制面 / `w-47` worker / relay 三个 node)。⇒ **口径目标 = 78**("无活跃实例"档),不是 79。
- **判定**:`78 − 77 = 1`,**差值 1 < 2 ⇒ 未命中**上单 §8.8-3 的回头条件("差值 ≥ 2");且另一分支要求"**有活跃实例**时仍 ≠ 79",本棒**无活跃实例** ⇒ 分支不适用。另:相对序⑧ 收口值(同为 77)**零变化**。
- **⛔ 只报告不动手(R7)**:`78 → 77` 这**1 个口**从未被定位(上单已核销 9 个固定口 / 64 个拨号池口 / `nft` / relay 回环绑定)。⇒ 记为**在册未办**,本单**不修**;只把"完整端口清单留档"作为 §5-S0 的一个动作(**取证,不是修复**),让下次漂移可比对。
### 2.2 已核实的代码事实(⛔ 执行棒不必重复探索)
1. **`waitUpOn` 不会对失败早退**(RC-1 的代码真身):`server.ts:499-506` 每 100 ms 查一次 `client.status().state === 'up'`,直到 `deadline` 才 `return false`。⇒ **死候选**(连不上 / 握手失败)与**慢候选**在这一层**不可区分**,代价恒为 `upTimeoutMs`。
2. **`upTimeoutMs` 有三处消费点**(改一处不够):`web/server.ts:524`(Manager 拨号通道 · C1)/`worker/relay-tunnel.ts:104` 与 `:158`(worker 实例面 · C2)/`net/relay/main.ts:315`(独立 `relay --client` · C3)。⚠️ 序⑦ 的 D1 就是为"同一件事别在多处各写一份"立的 —— **要么抽公共,要么每处都改并逐处验收**。
3. **`gracefulBurstMs` 是"写死的默认值、不可配"**:`client.ts:137` 是可选 option,全仓 `grep` **只有** `client.ts:567` 的 `?? 15_000`,**无任何 env 键 / 无任何装配点赋值** ⇒ **运行时无法调**(对比 `RELAY_FAILOVER_*` 全部可经 env 覆盖)。
4. **burst 期间 `attempts` 被强制归零**:`client.ts:565-580` 三个分支各自 `this.attempts = 0`(`queued` / `graceful` / `burst`)⇒ 与 `switcher.ts:244` 的 `unhealthy()` 组合出"15 s 检测地板"(RC-2)。
5. **半开检测参数与触发**:`client.ts:631` `halfOpenMs ?? max(3000, hbSec×1000×2.5)` = **37.5 s**;`:632` 巡检 `tick = halfOpen/4`;`:636` 判据 `age > halfOpen`。⚠️ 仅在 `state === 'up'` 时计时 ⇒ **只在"连着但不来帧"(丢包)时生效**(RC-5)。
6. **演练读数的两个系统性偏差**:① `waitSwitch` 每 `DRILL_POLL_MS`(2000) 轮询一次 ⇒ **读数高估 0–2 s**;② `t0 = Date.now()` 在 `stopRelay` **返回之后** ⇒ 比 relay 的 `BYE` 行**晚 ≈ 0.6–0.73 s**(两样本实测;这正好解释为什么 drill 读数与 `unhealthyForMs` 数值接近)。
7. **`[relay-switch]` 行自带 `state/attempts/unhealthyForMs/阈值`** ⇒ 三段的边界**不需要新埋点**,只需时间戳对齐(D2 的"⛔ 不新造探测帧"继续成立)。
8. **本机 = 生产的前身** ⇒ 改动会经下一次 `scp` 传导到 47/106。⛔ 推送前先 `git status --short` 确认待传清单只含本次真实改动(当前 = **43** 项)。
9. **`src/net/relay/**` 在 git 里 untracked** ⇒ 改动**用 `git diff` 看不到**,只能用 `grep -c` / `wc -l` / `git status --short` 核(⚠️ 目录里还留着一个 `client.ts.bak-seq7-*`)。
---
## §3 范围
### 3.1 要改的(且只有这些)
| # | 文件 | 改动 | 理由(为什么非改不可) |
|---|---|---|---|
| 1 | `src/web/server.ts`(+ 对齐 `worker/relay-tunnel.ts` / `net/relay/main.ts`) | **`waitUpOn` 增加"终态失败早退"**:新通道一旦进入**终态失败态**(建议判据 = 已发生一次明确的失败退避且**不在 burst 窗口内**,或 `lastFatalReason` 命中)⇒ 立即 `return false`,⛔ **不再等满 `upTimeoutMs`**。三处消费点**逐处验收**;能抽公共就抽公共(D1) | **RC-1 的唯一落点** ⇒ 直接省掉 12 s。判据必须"不误伤慢候选":仍能连上只是慢的候选,依旧享受完整 12 s |
| 2 | `scripts/overlay-failover-drill.cjs` | 新增 **`--trace`**:从 journal 抽 `down/skip/switch` 三类行,**逐样本算出四段**(检测 / tick 相位 / 白等 / 建连)并落盘原始行 | E1–E5 的判据本体(分解必须**可被第三方复现**)。⛔ 原有 `--scene 1\|2\|3\|4\|4b\|all` 语义**一字不改** |
| 3 | `test/relay-failover.test.mjs` | 新增用例:**死候选 ⇒ `open()` 提前失败**(注入"永不 `up` 且已进 backoff"的假客户端)/**慢候选 ⇒ 仍等满超时**(护栏,⛔ 防"早退"误伤) | 本线纪律:**先红后绿已实证两次**;"早退"必须**有护栏用例**否则会把慢候选误杀(= R11 净退化) |
| 4 | `src/net/relay/client.ts` | **把 `gracefulBurstMs` 参数表化**(新增 env 键 `RELAY_GRACEFUL_BURST_MS`,**默认值 15_000 不变**) | 消除"唯一一个不可配的时延常量"(符合本线既有的"阈值零魔数"纪律,`overlay-probe` E6)。⚠️ 只加可配性,**⛔ 不改值** |
| 5 | `参数表_覆盖网络_20260917.md` | ① `DRILL_POLL_MS` **2000 → 500**;② 新增 `DRILL_SAMPLE_N` = `5`;③ 新增 `RELAY_GRACEFUL_BURST_MS` = `15000`;④ **§9 表格新增一行「时延分解(序⑨ · 实测)」**记录四段数值 + 两个样本的 epoch 原文;⑤ §7 计数回填;⑥ §10 指纹更新 | 参数表 = 单一来源。🔴 **值格必须纯数字**(上单 §8.6 的假红坑:夹注 ⇒ `NaN` ⇒ `OBS-02` 假红) |
| 6 | `overlay-failover-drill.cjs` 的 `waitSwitch` / 参数表 `DRILL_POLL_MS` | 轮询周期 2000 → 500 ms(随第 5 项) | **RC-4**:把 ≤2 s 的量化误差从判据里剔掉 —— 否则"是否超 deadline"**两端都可能误判** |
### 3.2 ⛔ 不动什么(防顺手扩大)
- ⛔ **不动 `RELAY_FAILOVER_DEADLINE_MS`**(30000)。**这是本单的纪律核心**:放宽判据 = 作废判据。若 §5-S7 复测**仍**超界 ⇒ **写进 §8.8 报告,⛔ 不许自行改值**。
- ⛔ **不动 `gracefulBurstMs` 的默认值**(只加 env 可配性 = §3.1-4);⛔ **不动 `client.ts` 的 burst 语义**("计划内下线不触发切流"是**有意设计**,见 §1.3-②)。
- ⛔ **不动 `HB_SEC`**(15) —— 分解已证 `half-open`(37.5 s) **不在关键路径**上(RC-5);动它等于为一个没发生的路径改控制面流量模型。
- ⛔ **不动 `RELAY_FAILOVER_COOLDOWN_MS`**(300000);🔴 **⛔ 绝对不许把 `COOLDOWN_MS=0` 写进任何回滚 / 演练路径**(上单 §8.8-4:归零连带废掉"失败候选必须被排除" ⇒ 候选链自锁,实测 121–123 s 无切换)。演练期要缩短**只走 `DRILL_COOLDOWN_MS`**。
- ⛔ **不动 `RELAY_FAILOVER_MIN_ATTEMPTS` / `GRACE_MS` / `CHECK_MS`**(本单不改判据阈值;若要改,先回 §8 报告)。
- ⛔ **不做"同机候选去重 / 并发竞速 open"**(§4.2-DP-2):运行时**拿不到"同机"维度**(候选只是 url 字符串),要做得在取址层新增 host 维度 = 扩大改造面;并发 open 会**同时建两条通道**(多占口池口 + 多一次 AUTH)⇒ 暴露面/资源面变化需另行评估。**记为在册未办(R7 只报告)**。
- ⛔ **不新增任何监听口 / 凭据 / 对外可读端点**;⛔ 不动 nft / 安全组 / nginx;⛔ 不改 relay 服务端协议。
- ⛔ **不动 worker 会合面**(`DSHS_RENDEZVOUS_URL` / `DSHS_TUNNEL_TARGET` —— 已判"撤掉 ⇒ 判不执行",别再复议);⛔ 不动 directory 引导链。
- ⛔ **不重做序 ②–⑧**;⛔ 不做 presence / 房间层 / 内容分发(清单第 7 步);⛔ 不做打洞实现。
- ⛔ **不 commit / 不 push**(未获授权)。
- ⛔ **范围外的缺陷先报告、不顺手改**(R7)。在册:OBS-11 的 1 口差(§2.1)|OBS-09 实例面 000|guest(w-106) 实例页 502|`mksess*.cjs` 失效|`unhealthyForMs` 记账起点偏早(上单 §8.8-5)|`src/net/relay/**` untracked 留档缺口|`client.ts.bak-seq7-*` 残留。
---
## §4 决策点
### 4.1 已定项(规划棒已拍 —— 执行棒**不得自行更改**;要改必须回写 §8 并说明理由)
| # | 已定 | 理由 / 反例 |
|---|---|---|
| **D1** | **顺序 = 先量化再调参**:§2-P10 的分解**复现成功之前**,⛔ **不许改任何 `RELAY_FAILOVER_*` 的生产值**(含 deadline / minAttempts / grace / upTimeout) | 用户明令 + 本单立项初衷。分解**已经能用现有日志做出来**(§1.2)⇒ 没有理由跳步 |
| **D2** | **判据来源 = 现有日志行的时间戳**,⛔ 不新增探测帧 / 不新开端点 / 不引外部监控 | 承序⑦ D2;`[relay-switch]` 行自带 `attempts / unhealthyForMs / 阈值`,信息已足够 |
| **D3** | **15 s 的 burst 检测地板⛔ 不改**(保持"计划内下线不触发切流") | `client.ts:30` 原文语义;改它 ⇒ 每次 relay 重启都切流 = D5 要防的抖动(R11 净退化) |
| **D4** | **首选修法 = RC-1 的"死候选早退"**;`upTimeoutMs` **降值**只作**降级替代** | 降 `upTimeoutMs` 会误伤"慢但能通"的候选(实测 106 建连 2.7–2.8 s ⇒ 降到 4 s 已贴边)⇒ 只有在 RC-1 拿不到 12 s 时才考虑 |
| **D5** | **`DRILL_POLL_MS` 2000 → 500**(测量修正,非调参) | RC-4:≤2 s 量化误差会让判定两端误判 |
| **D6** | **deadline 保持 30000**,且**必须继续按 30000 判**(⛔ 不设"宽限窗口"、⛔ 不写"接近即视为通过") | 判据一旦软化,本单与上单的结论**都不可比** |
| **D7** | **三处装配点必须逐处验收**(`server.ts` / `relay-tunnel.ts` / `main.ts`) | 序⑦ D1 的教训("同一件事在多处各写一遍,其中一处悄悄漏了") |
| **D8** | **服务端零改动**(relay 侧最多只读) | 缩小失败域(承序⑦ D8 / 序⑧ D12) |
| **D9** | 若最终确需动 `upTimeoutMs` ⇒ **必须同时给出"慢候选不被误杀"的判据**(例如"某台成功建连耗时的 P95 + 余量")并回填参数表 | ⛔ 不许凭感觉给一个更小的数 |
### 4.2 交给执行棒自决(⛔ **不上升为提问**)
"终态失败"的**具体判据**(用 `state === 'backoff'`?用"已发生 ≥1 次非 burst 退避"?用 `lastFatalReason` 是否存在?还是给 `RelayClient` 加一个只读的 `failedTerminally()` 投影)|早退判据的**代码落点**(`waitUpOn` 内联 vs 抽 `src/net/relay/` 公共工具)|是否把三处 `waitUpOn`/`waitUp` 合并|`--trace` 的**输出格式**(四段数值 + 原文行)/落盘路径|`DRILL_SAMPLE_N` 的采法(是否复用 `--scene ctrl`)|`RELAY_GRACEFUL_BURST_MS` 的键名与是否需要 `DRILL_` 前缀的演练覆盖|§9「时延分解」行的**表格字段设计**|日志文案细节。
### 4.3 真需要用户拍板的(**命中才问,且一轮只问这一句**)
**本单 = 空。**
三问的判定全部落在"技术实现 / 测量与判据"边界内(§1.3 已逐条给出判定与依据);最容易被误当成"业务意图"的 **D3(relay 计划内重启该不该触发切流)** 已有**明确的设计原文依据**(`client.ts:30` 注释 + burst 窗口的存在理由)⇒ 属"客观可判",**自行拍掉、不上抛**。
### 4.4 技术实现裁决顺序(⚠️ 与 `dsh-decision-method §4.4` 一致)
① **先复用现成能力**(现有日志行的时间戳 + `RelayClient.status()` 的 `state/attempts` + 既有 `[relay-skip]` 判别器)→ ② **再改现有模块**(`waitUpOn` 一处/三处)→ ③ **最后才新增**(`--trace`、两个单测、参数表两个键)→ ⛔ **不引第三方依赖**。
### 4.5 权限影响评估(按红线 R5 要求先出;**结论:未命中 R5,暴露面零变化**)
| 维度 | 本单影响 | 判定 |
|---|---|---|
| **新增监听口** | **0**(只改客户端换址时序;候选集不变) | ✅ 不扩大 |
| **新增凭据** | **0** | ✅ 不扩大 |
| **放宽访问控制** | **0**(早退只是"更早放弃一个已失败的候选",⛔ 绝不放宽为"连目录外地址") | ✅ 不扩大 |
| **入站** | 106 入站**仍为 0**;47 无新增入站口 | ✅ 不扩大 |
| **新增可读面** | **0**(D2:⛔ 不新开端点;`--trace` 只读本机 journal) | ✅ 不扩大 |
| **暴露面净变化** | **0** | ✅ |
> ⚠️ **若执行中为"能切过去"而允许连目录外的地址 ⇒ 立刻停下**:那才是真的 R5(扩大信任面 = 任意重定向),必须回来出评估。
---
## §5 步骤(S0–S9;**每步自带一次可执行的验证**)
### S0 · 只读取证(= §2 P1–P10 + 端口清单留档)
- **动作**:按 §2 逐条跑;**P3/P5 指纹逐字一致**;**P10 的分解必须复现**;额外**把 47 的完整端口清单落盘**(`ss -lntp | tail -n +2 | awk '{print $4}' | sort -n | uniq > /tmp/seq9-ports-<ts>.txt`)—— 这是**取证**,用于下次 `78 → 77` 类漂移的可比对(⛔ 不是修复)。
- **验证**:P1–P10 全部 ✅;P10 的四段数值与 §1.2 一致(容差:检测段 ±0.5 s、白等段 ±0.3 s、建连段 ±1.5 s)。
- **⛔ 硬门**:任一 ❌ ⇒ 停下报告,**不进 S1**。
### S1 · 时间轴抽取(只读,先出工具)
- **动作**:给 `scripts/overlay-failover-drill.cjs` 加 `--trace`:一次 `journalctl` 取回窗口内 `relay-client. down` / `relay-skip` / `relay-switch` 三类行(⚠️ **`--since` 一律用 `@<epoch>`**;⚠️ 远端**读状态**命令一律 `|| true`;⚠️ 保留 `JOURNALCTL-ERR` 哨兵),按**样本切分**(以 `down (… (was up))` 或进程号变化为界)算出四段,**并原样打印所依据的行**。
- **验证**:对 §1.2 的两个历史样本跑 `--trace` ⇒ 四段数值**与 §1.2 表格逐项对得上**。
- **⚠️ 口径**:`--trace` **只读**,⛔ 不停 relay、⛔ 不改 env、⛔ 不写远端文件。
### S2 · N=5 采样(判据分布的地基)
- **动作**:跑 5 次"杀当前入口"(每轮之间必须**归零**:① 两台 relay `systemctl start` ② 重启 Manager(`restart dshs`)⇒ 通道回到目录首位 ③ 等 47 的 relay 日志出现新的 `AUTH OK host=ops/manager`);每次用 `--trace` 落盘。
- **验证**:5 份 trace 齐全;**每份的"检测 + 白等"≥ 27.0 s** ⇒ 若出现 **< 27 s 的样本** ⇒ 🔴 **分解被证伪,立刻停下报告**(§1.2 的可证伪条款)。
- **⚠️ 注意事项**:① **必须避开 `--scene ctrl`(`COOLDOWN=0`)那类轮次** —— 会污染候选顺序;② 每轮**记录 `DRILL_POLL_MS` 值**(S6 改前后各一组,读数不可混比)。
### S3 · 按分布选方案(**门 D1 在此解锁**)
- **动作**:算三段的中位数与极值:
- **若白等段中位数 ≥ 8 s** ⇒ 做 **D4 首选**(S4 的死候选早退);
- **若白等段中位数 < 8 s** ⇒ 说明 RC-1 不是主因 ⇒ **停下报告**,把精力转向 RC-3/RC-2 的重新评估(⛔ 不许硬套)。
- **验证**:一段结论 + 三行数据(中位数 / 最小值 / 最大值)写进 §8。
- **⛔ 硬门**:S2 未跑完 ⇒ ⛔ 不许进 S4。
### S4 · 死候选早退(RC-1 落点)
- **动作**:`waitUpOn`(及另两处等价逻辑)在轮询里增加**终态失败**判定 ⇒ 立即 `return false`;⛔ **不得**把"还在 burst 窗口内的短间隔重试"误判成终态(那会让 relay 的**计划内重启**被当成死候选 ⇒ 每次部署都切流)。
- **验证**:单测 **先红后绿**;`npm.cmd test` ⇒ **155 + 新增 / 全绿**(基线 155)。
- **⚠️ 易错点**:三处消费点(`server.ts:524` / `relay-tunnel.ts:104`、`:158` / `main.ts:315`)**逐处确认**;⛔ 别只改一处就宣布完成。
### S5 · 护栏用例(防"早退"误杀慢候选)
- **动作**:新增单测:**慢候选**(连得上、只是 `up` 来得晚,< `upTimeoutMs`)必须**仍然成功**;**死候选**(进 backoff 且出 burst 窗口)必须**提前失败**。
- **验证**:两条同时绿;且**既有用例一条不红**。
- **⚠️ 不变量**:这条是"早退"的护栏 —— 没有它,早退可能变成"更频繁地切到第三候选"甚至"全部候选都判失败"(R11)。
### S6 · 参数表闭环
- **动作**:`DRILL_POLL_MS` 2000 → 500;新增 `DRILL_SAMPLE_N`、`RELAY_GRACEFUL_BURST_MS`;**§9 新增「时延分解(序⑨ · 实测)」行**(四段数值 + 两个样本 epoch 原文);§7 计数回填;§10 指纹复取回填。
- **验证**:`grep -nE 'DRILL_POLL_MS|DRILL_SAMPLE_N|RELAY_GRACEFUL_BURST_MS' 参数表_覆盖网络_20260917.md` ⇒ **值格纯数字、无夹注**。
- **⚠️ 假红坑**:值格带夹注 ⇒ `NaN` ⇒ `OBS-02` 假红(上单 §8.6 原文教训)。
### S7 · 真机复测(修后 N=5,**判据不许软化**)
- **动作**:S4 部署后重跑 S2 的 5 轮(`scp` + `restart dshs`;⚠️ **relay 真身在 `/opt/dsh-relay/lib/`,两处都要铺**)。
- **验证**:**五样本 kill→switch ≤ 30000 ms(用新的 500 ms 轮询口径读数)**;且白等段 ≤ 1 s。
- **⛔ 硬门**:若**仍**有样本 > 30000 ms ⇒ **写进 §8.8 报告并给出下一步候选(含 `upTimeoutMs` 的降值方案 + 慢候选 P95 证据)**,⛔ **不许自行改 deadline**。
### S8 · 不退化
- **动作**:`--scene all`(1/2/3/4/4b)+ `overlay-probe` + `npm test` + 门户 `200` + 双实例面 ∈ `PROBE_CODE_SET` + `ss -lntp | wc -l` 与 §2-P7 对照。
- **验证**:见 E9/E10。**幕 1/2/3/4 结果不得变红**。
- **⚠️ 观察点**:若出现新的 `[relay-skip]` 或 `[relay-switch]` 形态变化 ⇒ 事件行**必须原样抄进 §8**(含 `attempts / unhealthyForMs`)。
### S9 · 收口
- **动作**:清理临时产物(trace 落盘件先移 `_中间产物_待清理/`);**释放锁**;登记下一棒 automation(`scheduledAt` = 收口时刻 + 2~5 分钟);**用陈述句告知**接续;推进入口 §2;写工作区日志。
- **验证**:见 E11/E12。
---
## §6 验收(判据清单;命令 + 期望输出,可被第三方复现)
| # | 判据 | 命令 / 观察点 | 期望 |
|---|---|---|---|
| **E1** | 🔴 **分解可复现**(D1 的解锁条件) | §2-P10 + `--trace` 对两个历史样本 | 检测 `15.02–15.60 s`;白等 `11.7–12.3 s`;建连 `1.3–4.3 s`;**三段之和 ≡ 墙钟(误差 ≤ 0.5 s)** |
| **E2** | **分解未被证伪** | S2 的 5 份 trace | **每份"检测 + 白等" ≥ 27.0 s**;出现 < 27 s ⇒ **停下**(§1.2 可证伪条款) |
| **E3** | **白等段有原文证据** | `--trace` 输出里出现 `[relay-skip] ⛔ 新通道起不来(wss://relay-direct.alotbuy.com/dshs-relay)` | 该行与 open 发起时刻的间隔 **≥ 11.7 s** |
| **E4** | **测量误差已剔除**(D5) | `grep '^| `DRILL_POLL_MS`' 参数表_覆盖网络_20260917.md` + drill 实跑 | 值 = **500**,且复测读数与 trace 逐样本一致(差 ≤ 0.6 s) |
| **E5** | **样本量达标** | S2 / S7 各 5 轮 | 5/5 份 trace 齐全,且**每轮都记录了归零动作** |
| **E6** | 🔴 **修后 5/5 在 deadline 内侧** | S7 | `kill → switch ≤ 30000 ms` **5/5**;**白等段 ≤ 1 s** |
| **E7** | **不误伤慢候选**(护栏) | 单测(S5) | "慢候选仍成功" + "死候选提前失败" 两条同时绿 |
| **E8** | **三处装配点逐处验收**(D7) | `grep -n "waitUpOn" -A 12 src/web/server.ts src/worker/relay-tunnel.ts src/net/relay/main.ts` | 三处的早退语义**一致**;报告逐处给出证据 |
| **E9** | **不退化** | `--scene all` + `npm test`(基线 **155**)+ `overlay-probe` + 门户 `200` + 双实例面 ∈ `PROBE_CODE_SET` | 与 §2-P8/P9 对照**逐项一致**;幕 1/2/3/4 结果**不变红** |
| **E10** | **deadline 未被放宽**(D6) | `grep -n 'RELAY_FAILOVER_DEADLINE_MS' src/net/relay/switcher.ts 参数表_覆盖网络_20260917.md` | **仍是 `30000`**;⛔ 参数表 / drop-in / 演练脚本里**零处**把它改成别的值 |
| **E11** | **上单遗留不恶化** | §2.1(`ss -lntp \| wc -l`)|OBS-09/11|`nft` 72 | 与 §2-P7 逐项一致;若变红 ⇒ 停下报告(非本单范围) |
| **E12** | **无越界** | `git status --short` + `grep -rc "COOLDOWN_MS=0\|COOLDOWN_MS= 0"`(drill / 参数表 / drop-in) | 只含 §3.1 列的文件;⛔ 无 commit / push;🔴 **`COOLDOWN_MS=0` 在回滚 / 演练路径里零命中** |
| **E13** | **收口四件套** | 锁 `--release-exec` 已释放;下一棒 automation 已登记**并已用陈述句告知**;入口 §2 已推进;工作区日志已写 | ✅ |
---
## §7 回滚
**三层,均秒级:**
1. **配置层(不改代码即可回到现状)** —— 本单**新增的键全部有安全默认值**:
- `RELAY_GRACEFUL_BURST_MS` 删掉 ⇒ 回落到代码默认 `15_000`(**逐字回到现状**)。
- `DRILL_POLL_MS` 改回 `2000` ⇒ 只影响**演练读数**,不影响产品。
- ⇒ 改完 `daemon-reload` → `systemctl restart dshs`(47)/`restart dshs-worker`(106,如涉及)。
- 🔴 **⛔ 禁用项**:**不许**用 `RELAY_FAILOVER_COOLDOWN_MS=0` 当"回滚/对照"开关(上单 §8.8-4:归零 ⇒ 候选链自锁,实测 121–123 s 无切换)。真要缩短冷却**只走 `DRILL_COOLDOWN_MS`**。
2. **代码层**:用出单后立即做的 `.bak-seq9-<ts>` 备份还原被改文件(⛔ `git diff` 对 `src/net/relay/**` **无效** —— 它 untracked)→ `npm run build` → `scp lib/` 到 47 `/opt/dshs/lib/` + `/opt/dsh-relay/lib/`;106 `/opt/dshs-cluster/lib/` + `/opt/dsh-relay/lib/` → `restart dshs` + `restart dshs-worker`。
- **落点自证**:改的是 `server.ts` 的 `waitUpOn` 与 `client.ts` 的 burst 键 ⇒ 建议用一个稳定的可 grep 标记(例如早退分支里的专属日志片段)在远端 `lib/` 里 `grep -c` 自证,**三处 lib 全部**。
3. **服务层**:S2/S7 采样若停了 relay ⇒ `systemctl start dshs-relay`(两台)。**两台 relay 的 drop-in(`capacity.conf`)本单不动**。
**⚠️ 回滚后必须复验**:`overlay-probe`(按 §2.1 口径判:`OBS-09` / `OBS-11` 属环境态)|`ss -lntp | wc -l` = **77**(无活跃实例档,与 §2.1 同值)|双实例面 ∈ `PROBE_CODE_SET`|门户 `200`|`--scene 1` 仍能在 deadline 内侧切换。
---
## §8 回报格式(执行棒按此格式收口;沿用序⑤–⑧ 单的分节)
> ## ✅ 执行棒回报(覆盖网络线 · 序⑨ 执行棒 | 2026-09-17 13:41–14:3x)
>
> **一句话结论**:**§2-P10 的四段分解复现成功(D1 解锁)⇒ 「死候选白等」12.03 s → **0.09 s**(省掉 12 s)⇒ 真机样本 **5/5 从「全部超 deadline」变成「全部在 deadline 内侧」**(30.2–34.1 s → **19.8–23.9 s**)。**
> **⛔ 未动**:`RELAY_FAILOVER_DEADLINE_MS`(30000) / `HB_SEC`(15) / burst 语义 / 服务端零改动 / 未 commit·未 push。
> **🔴 三条硬门**:**D1** ✅(分解先复现,再改值 —— 且本单**一个生产阈值都没改**)|**R7** ✅(范围外只报告:§8.8)|**⛔ `COOLDOWN_MS=0`** ✅(本单新增行零命中,见 E12)。
### 8.1 S0 快照(P1–P10)
| # | 命令(原文摘要) | 记录 | 判定 |
|---|---|---|---|
| **P1** | `handoff-guard.sh --claim-exec "覆盖网络线-序9执行棒"` | `✓ 已持全局执行锁` | ✅ |
| **P2** | `state.py` | 锁空闲→抢到;HEAD `640813e`;入口 = `接续入口_覆盖网络线_20260916.md`(§2 指向本单);工作区 **43** 处改动 | ✅ |
| **P3** | `sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md \| md5sum` | **`e6b669c257d8e8964273b3b400238351`** | ✅ 逐字一致 |
| **P4** | `sed '/^## §8 回报格式/,$d' 交接单_检测时延与deadline_20260917.md \| md5sum` | **`d903b4eeabf25ef381379cdbaac77e8a`** | ✅ **逐字一致 ⇒ 单未被改动**(收口后仍为此值,见 8.9) |
| **P5** | 上两单前缀 | 序⑧ `aa3a6ec0d66dd81f465cea2a3a08ad27`|序⑦ `419abf308c00b7668e8898aaa91ba9e8` | ✅ 两值逐字一致 |
| **P6** | `grep -n gracefulBurstMs\|halfOpenMs client.ts`;`grep -n "const waitUpOn" -A8 server.ts`;`switcher.ts` 锚点 | `client.ts:137/146/567/631`;`server.ts:499-506`(**只轮询 `up`、不看终态**);`switcher.ts:244/300`;**`waitUpOn` 三处** = `server.ts:499+524`|`relay-tunnel.ts:104+189`|`main.ts:155+315`(⚠️ 单 §2-P6 记的 `main.ts` 函数在 **155**、调用在 **315**,单内写 315 指调用点 ⇒ 一致) | ✅ 锚点全部存在 |
| **P7** | `node scripts/overlay-probe.cjs`(cwd = 工作区根) | **10/12**(`OBS-09` 实例面 `000`|`OBS-11` 监听口 **77** vs 阈值 79),`exit=1` | ✅ 与 §2.1 / §9.1 **逐项同值**(环境态:47 无活跃实例) |
| **P8** | 两台 `systemctl is-active` + 47 `/status` | 47 `dshs`=active / `dshs-relay`=active;`capacity.used=**2**`、`online[]` = `manager(session=6959e3e1fb5a2956)` + `w-106(session=416e7764a86752db ports=19000)`;106 `dshs-worker`=active / `dshs-relay`=active | ✅ |
| **P9** | `npm.cmd test`(Node 22) | **155 tests / 154 pass / 0 fail / 1 skipped** | ✅ 与序⑧ 收口基线逐字一致 |
| **P10** | `journalctl -u dshs --since -6h -o short-unix \| grep -E 'relay-switch\|relay-skip\|relay-client. down'` 抽两样本逐行对齐 | 见 **§8.2** —— **逐项复现**(误差 ≤ 0.03 s) | ✅ **D1 解锁** |
### 8.2 **时延分解表(本单的核心产出)**
**口径**:四段全部由 **journal 现有行的时间戳**算出(⛔ 无新埋点,D2);
`--trace` 的**白等**锚点 = 「新候选客户端首败」→「放弃」(⑨ 修前修后**同一口径**,见 8.2.3)。
#### 8.2.1 单内 §1.2 的两个历史样本 —— **逐项复现**(§6-E1 / D1 的解锁证据)
| 样本(epoch 原文) | 检测 | 首试延迟 | **白等** | 建连 | 总 | `unhealthyForMs` | 与 §1.2 比对 |
|---|---|---|---|---|---|---|---|
| `1789616881.632718` | **15.119** | 1.416 | **12.029** | 2.724 | **31.287** | 30563 | 单内记 15.00 / ~1.5 / 12.00 / ~2.1 ⇒ ✅ 逐项一致 |
| `1789617465.999982` | **15.085** | 0.258 | **12.033** | 2.821 | **30.196** | 29376 | 单内记 15.085 / 0.29 / 12.00 / 2.82 ⇒ ✅ **逐字一致** |
**所依据的行原文(样本② `1789616881`,节选三行)**:
```
1789616881.632718 … [relay-client] down (peer bye: server restarting (was up)); attempt #0 [graceful, burst window 15000ms], retry in 300ms
1789616896.751232 … [relay-client] down (transport error); attempt #1, retry in 1054ms ← 检测段终点(burst 耗尽)
1789616898.166825 … [relay-client] down (transport error); attempt #1, retry in 799ms ← **新候选客户端首败**(白等起点)
1789616910.195416 … [relay-skip] ⛔ 新通道起不来(wss://relay-direct.alotbuy.com/dshs-relay)… 进冷却 300000ms ← 放弃点
1789616912.919233 … [relay-switch] #1 … -> wss://106.54.21.172/…(unhealthyForMs=30563) ← 样本终点
```
> 🔴 **`--trace` 对"白等起点"的识别依据**(可被第三方复核):`open()` 里的候选客户端是**全新**实例
> (`attempts=0`、无 burst 窗口)⇒ 它第一次拨号失败就打**不带方括号标签**的 `attempt #1`;
> 而老通道客户端在 burst 耗尽时也打一条 `attempt #1`,**那条正好等于 burst 耗尽行本身**(被排除)
> ⇒ 窗口内"最后一条不带标签的 `attempt #1`"唯一指向新候选客户端。
#### 8.2.2 全量样本(`--trace --since -6h`,19 个样本中 15 个"单跳")
| # | 样本 epoch | 检测 | 首试延迟 | 白等 | 建连 | 总 | 签名 |
|---|---|---|---|---|---|---|---|
| 1 | `1789616881.632718` | 15.119 | 1.416 | **12.029** | 2.724 | 31.287 | 修前 |
| 2 | `1789617465.999982` | 15.085 | 0.258 | **12.033** | 2.821 | 30.196 | 修前 |
| 3 | `1789620716.466165` | 15.209 | 1.269 | **12.035** | 3.718 | 32.232 | 修前 |
| 4 | `1789621434.115527` | 15.490 | 1.830 | **12.034** | 3.768 | 33.122 | 修前 |
| 5 | `1789623996.90519` | 15.291 | 0.985 | **12.041** | 2.716 | 31.034 | 修前(S2-1) |
| 6 | `1789624064.033677` | 15.136 | 0.808 | **12.026** | 2.721 | 30.692 | 修前(S2-2) |
| 7 | `1789624132.71806` | 15.298 | 1.660 | **12.044** | 4.221 | 33.223 | 修前(S2-3) |
| 8 | `1789624194.346892` | 15.738 | 0.468 | **12.021** | 3.717 | 31.943 | 修前(S2-4) |
| 9 | `1789624258.224431` | 15.671 | 2.818 | **12.033** | 3.621 | 34.143 | 修前(S2-5) |
| 10 | `1789624473.578564` | 16.645 | 3.031 | **0.092** | 2.717 | 22.485 | 修后(S7-1) |
| 11 | `1789624534.000883` | 15.229 | 0.332 | **0.090** | 5.223 | 20.873 | 修后(S7-2) |
| 12 | `1789624587.689803` | 15.017 | 1.480 | **0.092** | 4.719 | 21.307 | 修后(S7-3) |
| 13 | `1789624640.289734` | 15.096 | 5.016 | **0.092** | 3.730 | 23.933 | 修后(S7-4) |
| 14 | `1789624693.055419` | 15.262 | 1.585 | **0.090** | 4.719 | 21.656 | 修后(S7-5) |
| 15 | `1789625104.721054` | 15.386 | 1.610 | **0.093** | 2.721 | 19.810 | 修后(幕 4-A) |
- **修前(n=9)**:检测 **15.085–15.738**(中位 15.29)|白等 **12.021–12.044**(中位 12.033)|建连 2.716–4.221|**总 30.196–34.143 ⇒ 9/9 全部 > `deadline` 30000**
- **修后(n=6)**:检测 **15.017–16.645**(中位 15.26)|白等 **0.090–0.093**(中位 0.092)|建连 2.717–5.223|**总 19.810–23.933 ⇒ 6/6 全部 ≤ `deadline`**
- **自洽**:15/15 样本 `检测 + 首试延迟 + 白等 + 建连 ≡ 总`(误差 ≤ 0.05 s)✅
#### 8.2.3 白等段的口径说明(⛔ 防误读)
- 单内 §1.2 把白等写成 `≡ upTimeoutMs`(12.000 s)—— 那是**修前的定义式**。本棒用**可直接测的锚点**替代它:
**白等 = `t(放弃点) − t(新候选客户端首败)`** ⇒ 修前实测 **12.021–12.044**(与定义式 12.000 相差 21–44 ms = 拨号耗时),
修后实测 **0.090–0.093 s**。⇒ 同一个度量在修前修后都成立,**修前的 12 s 是"真等待",修后只剩 0.09 s**。
- ⚠️ 单内 §1.2 的"tick 相位"在本口径下改称**首试延迟**(= `RELAY_FAILOVER_CHECK_MS` 相位 + 拨号耗时),
⛔ 不是"测量被篡改":四段划分与总长自洽校验逐样本通过(见 8.2.2)。
### 8.3 代码改动清单
| # | 文件 | 改动点 | `wc -l` 前后 | 先红后绿 |
|---|---|---|---|---|
| 1 | `src/net/relay/client.ts` | ① 接口+`status()` 新增**只读投影** `inGracefulBurstWindow`;② 新增 `gracefulBurstMsDefault()`(env 参数表化,**默认 15_000 逐字不变**);③ 新增 `openedChannelFailedTerminally()`(**RC-1 判据**);④ 新增 `waitUpOnStatus()`(**三处装配点收口成一份**,D7) | **1306 → 1403** | ✅ |
| 2 | `src/net/relay/index.ts` | 导出上述 3 个符号 | 2 行改 | — |
| 3 | `src/web/server.ts`(**C1**) | `waitUpOn` 改为委托 `waitUpOnStatus` + 判死日志(`[relay-failover] ⛔ 新通道终态失败…`) | **908 → 917** | ✅ |
| 4 | `src/worker/relay-tunnel.ts`(**C2**) | 同上 | **220 → 224** | ✅ |
| 5 | `src/net/relay/main.ts`(**C3**) | 同上(+ `log` 透传) | **359 → 363** | ✅ |
| 6 | `scripts/overlay-failover-drill.cjs` | 新增 **`--trace`**(四段分解·**只读**)+ **`--sample N`**(N 轮采样,含完整归零序列);⛔ 原有 `--scene 1/2/3/4/4b/4c/ctrl` **语义一字未改**(仅在 `--help` 追加 2 行) | **720 → 1032** | — |
| 7 | `test/relay-failover.test.mjs` | 新增 **F18(护栏·慢候选)+ F19(死候选早退)+ F20(burst 窗口不许判死)+ F21(判据四反例)+ F22(env 参数表化)** | **731 → 844** | ✅ |
**E8 · 三处装配点逐处验收**(D7):`grep -n waitUpOnStatus` ⇒ `server.ts`(C1)/`relay-tunnel.ts`(C2)/`main.ts`(C3)**三处全部委托同一实现**;
**落点自证(远端 lib 四份)**:`grep -c "openedChannelFailedTerminally"` ⇒ 47 `/opt/dshs/lib/net/relay/client.js` = **3**、47 `/opt/dsh-relay/lib/…` = **3**、106 `/opt/dshs-cluster/lib/…` = **3**、106 `/opt/dsh-relay/lib/…` = **3**;
`grep -c "新通道终态失败"` ⇒ `/opt/dshs/lib/web/server.js` = **1**、`/opt/dshs-cluster/lib/worker/relay-tunnel.js` = **1**。⚠️ 属主按原值 `197108:197121` 复原(⛔ 不引入 R10 的 root 属主问题)。
### 8.4 单测与本地验证
- **`npm.cmd test`(Node 22)= 160 tests / 159 pass / 0 fail / 1 skipped**(基线 **155** + 新增 **5**)。
- **先红后绿**(实证):改完用例先对**旧 `lib/` 产物**跑 ⇒
`SyntaxError: The requested module '../lib/net/relay/index.js' does not provide an export named 'openedChannelFailedTerminally'`(**红**);
`npm run build` 后 `node --test test/relay-failover.test.mjs` ⇒ **22/22 全绿**;全量 `npm test` ⇒ **159/0**。
- **护栏用例(E7 的断言切面)**:
- **F19(红→绿的分水岭)**:假客户端在 100 ms 处进 `backoff`+`attempts=1`+非 burst ⇒ 断言 `ok===false` **且 `ms < 2000`**(旧实现必然 12 000 ms)。
- **F18(护栏)**:慢候选 `connecting → up@800ms` ⇒ 断言 `ok===true` 且等到 ~800 ms ⇒ **⛔ 早退不许误杀慢候选**(R11 不变量)。
- **F20(D3 保护)**:`backoff attempts=1` 但 `inGracefulBurstWindow=true` ⇒ 必须继续等,窗口过后 `up` ⇒ `ok===true`(⛔ 不许在窗口内判死)。
- **F21**:判据四反例(`connecting` / `handshaking` / `queued`(attempts=0) / burst 窗口内)**一律 false**。
- **F22**:`RELAY_GRACEFUL_BURST_MS` 默认 **15 000 逐字不变**;空串/非法/负数 ⇒ 回落默认;显式覆写才生效。
### 8.5 真机复测(修复前 N=5 / 修复后 N=5)
**读数口径**:切换耗时 = `--sample` 的墙钟(**含 `DRILL_POLL_MS` 的量化误差**);四段另由 `--trace` 从 journal 时间戳复算(**与轮询口径无关**)。
| 轮 | 修复前 `DRILL_POLL_MS=2000` | ≤30000? | 修复后 `DRILL_POLL_MS=500` | ≤30000? |
|---|---|---|---|---|
| 1 | 33914 ms | ❌ | **25465 ms** | ✅ |
| 2 | 31646 ms | ❌ | **25342 ms** | ✅ |
| 3 | 34078 ms | ❌ | **21985 ms** | ✅ |
| 4 | 35321 ms | ❌ | **27389 ms** | ✅ |
| 5 | 41252 ms | ❌ | **23318 ms** | ✅ |
| 合计 | **5/5 超界**(中位 34078) | ❌ | **5/5 在内侧**(中位 25342) | ✅ |
**每轮的归零动作(E5)**:① 两台 relay `systemctl start` → ② `systemctl restart dshs`(通道回到目录首位 = 47)→ ③ 等 47 relay 日志出现**新的** `AUTH OK host=ops/manager`(修复前 AUTH 就绪 2359–4112 ms/修复后 2834–4761 ms)→ ④ 记 `t0` → `systemctl stop dshs-relay`(47) → 等新 `[relay-switch]` → ⑤ 复原 relay。**10/10 轮全部执行归零**;⛔ 全程**未施加任何演练 env 覆盖**(尤其未出现冷却归零)。
**同轮的四段读数(journal 口径)**:修复前白等 **12.021–12.044**/修复后 **0.090–0.093**(见 8.2.2 第 5–14 行)。
### 8.6 参数表改动
| 键 | 旧 → 新 | 值格自证(纯数字) | 说明 |
|---|---|---|---|
| `DRILL_POLL_MS` | **2000 → 500** | `500` | **测量修正**(剔掉 ≤2 s 量化误差 ⇒ 超/不超 deadline 两端都可能误判,RC-4)。⚠️ 改前/改后读数不可混比(8.5 已分档) |
| `DRILL_SAMPLE_N` | 🆕 `5` | `5` | `--sample` 的默认轮数(= S2/S7 的 N) |
| `RELAY_GRACEFUL_BURST_MS` | 🆕 `15000` | `15000` | 把"全仓唯一一个不可配的时延常量"参数表化(`gracefulBurstMs`);⛔ **默认值语义逐字不变**、⛔ 本单未改其值 |
| §9 表格 | 🆕 **第 9 行「换址墙钟的四段分解」** | — | 四段数值 + 修前修后两档总长 |
| §7 计数 | 回填一行 | — | 待测项 **仍为 0**(新增两个键都是实测/推导值) |
**值格纯数字自证**:`grep -nE '^\| `(DRILL_POLL_MS|DRILL_SAMPLE_N|RELAY_GRACEFUL_BURST_MS)`'` ⇒ 值格依次为 `500` / `5` / `15000`(**无夹注** ⇒ ⛔ 不会引发 `OBS-02` 假红)。
### 8.7 不退化(S0 对照)
| 项 | S0(§8.1) | 收口复取 | 判定 |
|---|---|---|---|
| `npm test` | 155/154/0/1 | **160/159/0/1**(+5 新用例) | ✅ 无红 |
| `--scene all`(幕 1/2/3/4) | 序⑧ 全绿 | **11 PASS / 1 FAIL**(幕2-B,见 8.8-1);**幕 4-A/B/C 全绿**(豁免切回 47 **22213 ms ≤ 30000**) | ⚠️ 见 8.8-1(归因 = drill 状态依赖,**正确前置下 3/3 PASS**) |
| `overlay-probe` | 10/12(`OBS-09`/`OBS-11` 红) | **10/12,同样只有 `OBS-09`/`OBS-11` 红** | ✅ 逐项一致 |
| `ss -lntp \| wc -l`(47) | **77** | **77** | ✅ |
| `nft` 规则行数 | 72 | **72** | ✅ |
| 门户 `http_code` | 200 | **200** | ✅ |
| 双实例面 | `000/000`(无活跃实例) | `000/000` | ✅ 同值(OBS-09 环境态) |
| 两台 relay 单元 | active/active | **active/active** | ✅ |
| 演练 env 残留(`RELAY_FAILOVER_*` 计数) | — | **0** | ✅ 生产值未被演练污染 |
| `git status --short` 计数 | **43** | **43** | ✅ 无越界(新增的 4 个文件均在本单 §3.1 范围内,且 `src/net/relay/**`·`relay-tunnel.ts`·drill·test 本就是 untracked) |
### 8.8 未过项 / 遗留
**1. ✅ 序⑩ 已修(2026-09-17 14:4x)· 原报:🔴 `--scene all` 的幕 2-B 判 FAIL(归因:drill 自身的状态依赖,⛔ 非本单改动引入)**
- **① 现象**:`幕2-B 留下「无候选 ⇒ 原地退避」的判别器证据(D6)` ⇒ 窗口内 **0 行 `[relay-skip]`**;同窗口内 `[relay-client] down` / `[relay-switch]` 也**全为 0 行**(journal 取证:`1789624835±` 的 120 s 窗口里 Manager 一行 relay 日志都没有 ⇒ **它的通道根本没被打断**)。
- **② 已做到哪一步**:定位到 **drill 代码本身** —— 幕 2 的注释写"两台全杀",但实现**只停 106**(`await stopRelay(h106)`,`overlay-failover-drill.cjs` 幕 2 块首行),而**幕 1 已把 Manager 的通道切到了 47** ⇒ 幕 2 停的是一台 Manager 已不在用的 relay ⇒ 无失效、无判别器。**在正确前置下重跑 ⇒ 3 PASS / 0 FAIL**(`幕2 前置:47 relay=inactive / 106 relay=inactive`,判别器原文:`[relay-skip] ⚠ 豁免尝试也起不来(wss://alotbuy.com/dshs-relay)⇒ **保持原通道**;重置该候选冷却…`)。⇒ **与本单改动无关**(本单只改"新通道等待",不触碰"无候选"分支;且该窗口内 Manager 完全无事件)。
- **③ 回头条件**:**只要有人需要 `--scene all` 一键全绿**(如 CI 化 / 交给第三方复跑)⇒ 必须先把幕 2 改成"停掉**当前通道所在**的那台 relay"(与幕 1 同一套 `killTarget` 判定),否则它**永远依赖上一轮留下的通道归属**。⛔ 本单**不动**(§3.1 明令"原有 `--scene` 语义一字不改"+R7 范围外先报告)。
**2. 🔴 在册未办(本单**只报告、不动手**)**
- ✅ **序⑩ 已清(现 `grep -rho 'COOLDOWN_MS=0' scripts/ | wc -l` = `0`)· 原报:`COOLDOWN_MS=0` 在演练脚本里仍有 5 处命中**(`overlay-failover-drill.cjs`:`--scene 4c` / `--scene ctrl` 的文案与实现)—— **全部是序⑧ 遗留**,本单新增行**零命中**(`git diff -U0 | grep -c '^+.*COOLDOWN_MS=0'` = **0**)。按上单 §8.8-4(归零 ⇒ 候选链自锁、实测 121–123 s 无切换)与 §3.2 禁令 ⇒ 建议下一棒**删掉 `--scene ctrl` 及其 4 处文案**(`4c` 可保留:它走 `DRILL_COOLDOWN_MS` 覆盖,不碰生产键)。回头条件:**任何一次把 `--scene ctrl` 当"回滚/对照"用**之前必须删掉。
- ✅ **序⑩ 已定位(见下方 §8.8-4)· 原报:OBS-11 的 `78 → 77` 那 1 个口**:本棒复取仍 = **77**(与 S0/序⑧ 同值),**未定位**(R7 只报告)。
- 承上单:`OBS-09`(47 无活跃实例 ⇒ 实例面 `000`)|guest(w-106) 实例页 502|106 agent 面不吃引导链(已判不执行)|`mksess*.cjs` 失效|`src/net/relay/**` untracked 留档缺口|`client.ts.bak-seq7-*` 残留。
- **⚠️ 本棒对"当前通道归属"有副作用**(幕 4 / S2/S7 采样会把 Manager 留在 106):收口时已 **`systemctl restart dshs` 归零回 47** 并复验(`used=2`、`online[]` 含 `manager` + `w-106`、probe 回到 10/12)。
**3. ⛔ 明确未做(保持单内 §3.2)**:未动 `deadline`/`HB_SEC`/burst 语义/`MIN_ATTEMPTS`/`GRACE_MS`/`CHECK_MS`/生产 `COOLDOWN_MS`;未做"同机候选去重 / 并发竞速 open";未新增监听口·凭据·端点;未改服务端;未 commit / 未 push。
**4. ✅ 序⑩ 收口回填(2026-09-17 14:23–14:5x · 执行棒 · 三件技术债全清)**
| # | 遗留 | 结果 | 证据(现取) |
|---|---|---|---|
| ① | 幕 2 状态依赖(幕 2 只停 106 ⇒ 停的是**不用**的那台) | ✅ **已修** | 修法 = 幕 2 起手**现场重读权威通道归属**(`lastManagerAuthOn` 比对)后**两台都停**(幂等);`--scene all` = **12 PASS / 0 SKIP / 0 FAIL**(原 11/1);`--scene 2` 单独复跑 = **3 PASS / 0 FAIL**(前置行原文:`本幕开始时活跃通道 = bt-server(epoch 47=…/106=…)`) |
| ② | `COOLDOWN_MS=0` × 5 处(`--scene 4c` / `--scene ctrl`) | ✅ **已清,0 命中** | `grep -rho 'COOLDOWN_MS=0' scripts/ \| wc -l` = **0**;两个场景名**整体移除**并**显式拒绝**(`--scene 4c` / `ctrl` ⇒ 退出码 **2** + 提示走 `DRILL_COOLDOWN_MS`),⛔ 不静默空跑;`node --check` 通过 |
| ③ | OBS-11 的 `78 → 77` 那 1 口 | ✅ **已定位(有名字)** | 逐口对账见下 ⇒ **差额 = `20000`**(47 实例档,当前无活跃实例) |
**③ 逐口对账(应然 78 行 vs 实际 77 行 · `ss -lntp`,含表头)**
| 组 | 应然条数 | 实际 | 说明 |
|---|---|---|---|
| 九固定口 `22/80/443/888/3080/8765/15432/19100/20080` | 9 | ✅ 全在 | — |
| `[::]:22`(sshd 的 IPv6 **第二条绑定行**) | 1 | ✅ 在 | 计数是"**行**"不是"端口",故 22 占 2 行 |
| 拨号池 `25000–25063` | 64 | ✅ **64 口全在** | 脚本逐口点名验证 |
| w-106 落点(relay **动态分配**) | 1 | ✅ 在(值 = `46147`) | `/status.endpoints[0].localPort=46147`,与 relay `20080` **同 PID 779508** |
| BT-Panel `58888` | 1 | ✅ 在 | — |
| **47 实例档 `20000`** | 1 | ❌ **缺** | **就是这 1 口** —— 47 当前**无活跃实例**(`ss` 无 `127.0.0.1:20000`) |
| 表头 | 1 | ✅ 在 | — |
⇒ **76 条 socket 逐条点名、零无名**;`78 − 77 = 1` 的名字 = **`20000`(实例档随实例上线/下线)**,**不是**暴露面消失。
⇒ 🔴 **附带订正(口径书写错误)**:原清单把 `39463` 写成固定项 —— 它其实是 relay 为 w-106 端点**动态分配**的落点(现值 `46147`,同 PID)。⛔ **口径里不能写固定值**,否则每次重启都会误报"口变了"。
⇒ ⚠️ **仍无名的 1 条(在册未办)**:参数表 `LISTEN_COUNT` = **79**(S0 基线,78 socket)比"应然·无实例态 77"高 **2**、比"应然·有实例态 78 行"高 **1**。这 1~2 条**无法定名**,卡点 = **S0 原始 `ss` 清单未留档**(已查工作区与 `04-调整方案/`,均无)。**回头条件**:① 47 恢复活跃实例后复取(应回到 **78 行**)② 届时若仍 **77** ⇒ 另有 socket 确实消失 ⇒ 按"零新增暴露面"重定基线,⛔ 不许把差值当噪声放过。
**🔴 ③ 的副产品(⛔ 本棒不动手 · 属方案改动 ⇒ 转规划棒)**:OBS-11 用「**计数相等**」当暴露面判据有**两个结构性弱点** —— ⓐ 对**状态**敏感(实例档/端点落点在线态一变就红 ⇒ 假红);ⓑ 对**替换式**变化**不敏感**(一进一出计数不变 ⇒ **假绿**,正是本项目最忌的静默失效)。⇒ 建议下一棒规划 **白名单集合判据**(`实际监听行 ⊆ 允许集`,并打印差集)替代计数相等;连带处理 `LISTEN_COUNT` 的"有/无实例态"两值。⛔ 本棒只报告(§3.2 范围 + D1 纪律)。
### 8.9 指纹(收口后的可复现核对口径)
- **参数表**:`cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum`
⇒ **现取 = `99e9e17b0c1ce0550e4bc7626a5a0494`**(本单出单值 = `e6b669c257d8e8964273b3b400238351` ⇒ **已随 §3.1-5 的四处改动更新**)
- **本交接单前缀(§8 及其后不计入)**:`cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §8 回报格式/,$d' 交接单_检测时延与deadline_20260917.md | md5sum`
⇒ **现取 = `d903b4eeabf25ef381379cdbaac77e8a`** = 出单值 ✅ **回填 §8 后未变**(底稿零改动)
- **交叉证据(上两单,收口复取)**:`切流冷却语义_20260917.md` = **`aa3a6ec0d66dd81f465cea2a3a08ad27`**|`中继失败切流_20260917.md` = **`419abf308c00b7668e8898aaa91ba9e8`**(两者均未变)
- 本单**全文件** md5 请现取(⛔ 不内嵌,自指)
- **序⑩ 原始取证落盘**:`_中间产物_待清理/seq10/`(`ss47-actual.txt` = 47 监听口全量 77 行;`drill-all-after.txt` = `--scene all` **12 PASS** 原文;`drill-scene2-after.txt` = 幕 2 单跑 **3 PASS** 原文)
- **原始取证落盘**(供第三方复核):`_中间产物_待清理/seq9-trace/`(`s2-before.json` 修前 5 轮、`s7-after.json` 修后 5 轮、`full-6h.json` 全窗口 19 样本、`seq9-sample-*.json` 采样明细)
---
## §9 附录 · 本单的取证基线(供执行棒复核,⛔ 不必重跑)
| 事实 | 出处 |
|---|---|
| `relayFailoverThresholds(env = process.env)` ⇒ **env 覆盖生效**;六键默认值 `300000 / 30000 / 15000 / 3 / 2000 / 12000` + `exempt` | `switcher.ts:55-95` |
| **唯一换址入口** `replace(targetUrl, reason, origin)`;`open()` 在 `:300` 被 await | `switcher.ts:204-310` |
| **`unhealthy()`** = `state==='backoff' && (attempts ≥ minAttempts ∨ unhealthyForMs ≥ graceMs)` | `switcher.ts:244` |
| `tick()` 周期 = `checkMs`(2000);D6 现场 ⇒ `noCandidateChecks` + `[relay-skip]`(节流) | `switcher.ts`(`tick` 内) |
| **`waitUpOn` 只轮询 `state === 'up'`,直到 `deadline` 才 false**(RC-1 真身) | `web/server.ts:499-506` |
| `open` 的三处消费点 | `web/server.ts:524`(C1)/`worker/relay-tunnel.ts:104`、`:158`(C2)/`net/relay/main.ts:315`(C3) |
| 生产目录 3 条候选、**前 2 条同机 47** —— 注释原文已承认 | `web/server.ts:517-519` |
| **`gracefulBurstMs` 默认 15_000、不可配**(全仓只有 `?? 15_000` 一处) | `client.ts:137`/`:567` |
| **burst 三个分支各自 `attempts = 0`**(RC-2) | `client.ts:559-580` |
| `gracefulRetryMs` 默认 300;`reconnectMinMs` 1000/`reconnectMaxMs` 30000;退避含 ±25% 抖动 | `client.ts:554-588` |
| 半开检测 `halfOpen = max(3000, hbSec×1000×2.5)` = **37.5 s**;仅在 `state==='up'` 计时 | `client.ts:631-638` |
| `DEFAULT_HB_SEC = 15`(运行真值;`HB_SEC_DOC=20` 是旧隧道时代文档口径,⚠️ 待更正) | `server.ts:60`/参数表 §4 |
| 演练读数偏差:`waitSwitch` 每 `DRILL_POLL_MS` 轮询;`t0` 在 `stopRelay` 返回之后(比 BYE 晚 0.6–0.73 s) | `overlay-failover-drill.cjs`(`waitSwitch` / `runScene1`) |
| 本单两个逐行复核样本的 epoch | ① `1789617466 / …7493.375 / …7496.196`(`unhealthyForMs=29376`)② `1789616882 / …6910.195 / …6912.919`(`unhealthyForMs=30563`) |
### 9.1 本棒(规划棒)已做的只读取证(⛔ 执行棒不必重做)
| 项 | 读数 | 出处 |
|---|---|---|
| 工作区 HEAD | `640813e`;`git status --short` = **43** | `state.py` / `git status` |
| 参数表指纹 | `e6b669c257d8e8964273b3b400238351` | 现取 |
| 上两单前缀指纹 | `aa3a6ec0d66dd81f465cea2a3a08ad27` / `419abf308c00b7668e8898aaa91ba9e8` | 现取 |
| `overlay-probe` | **10/12**(`OBS-09` / `OBS-11` 红);`exit=1`;原始输出落 `/tmp/seq9-probe-s0.txt` | 现取 |
| `ss -lntp \| wc -l`(47) | **77**(76 socket + 表头);连采两次同值;`nft=72` | 现取 |
| 47 端口清单 | 9 固定口(`22/80/443/888/3080/8765/15432/19100/20080`)+ 拨号池 `25000–25063`(64) + `39463`(w-106 落点)+ `58888`(BT-Panel) | 现取 |
| 47 活跃实例 | **无**(`20000` 未监听;`ps` 只有控制面 / `w-47` / relay) ⇒ OBS-09 红属环境态 | 现取 |
| 47 relay `/status` | `capacity.used=2`;`online[]` = `manager` + `w-106`;`dialers=["manager"]` | 现取 |
| 时延分解 | **检测 15.08 s / 白等 12.00 s / 建连 2.82 s**(+ 相位 0.29 s) | §1.2 样本① |
---
## §10 指纹
- **本节口径**(推荐核对用,可复现):**整个 §10 不计入** ⇒
`cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §10 指纹/,$d' 交接单_检测时延与deadline_20260917.md | md5sum`
- ⚠️ **本单另有 §8 前缀口径**(§8 及其后不计入)⇒ 见 **§8 回报格式** 内 §8.9 的取值处(收口时回填)。
- **全文件 md5**:请现取(⛔ 本行故意不内嵌数值 —— 包含本节自身,写进去即刻失效)。