Files
dsh_shenxian/dsh-server-docs/ops/接续入口_覆盖网络线_20260916.md
T

278 lines
93 KiB
Markdown
Raw Normal View History

# 接续入口 · 覆盖网络线(2026-09-16)
> **这是本工作线的唯一入口。** 新会话先读这一份(约 3 KB),再按需打开下面的文档。
> 详细过程记录在 `.workbuddy/memory/2026-09-16.md` 末尾的「会话接续包」——**不必先读那个 60 KB 日志**。
---
## 0. 当前状态(一句话)
> 🆕 **2026-09-17 18:1x 刷新(序 ⑲ 执行棒收官 · presence 节点在线态已上线)**:**presence 改造已落地生产并真机验收** —— 产物 = `交接单_presence在线态_20260917.md` **§8★ 回报(8.0–8.9 已回填)**。**改法**:节点/端点在线态从「Manager 每 5 s 拉 `/status` 快照」改为「**连接生命周期驱动 + 订阅式推送 + 1 s 批合并 + TTL 安全网**」;`wire.ts` **末尾追加** `SUB 0x10 / UNSUB 0x11 / PRESENCE 0x12 / SNAP 0x13`;`client.ts` 订阅侧 + `rendezvous.ts` 主路径(订阅 `undefined` 才回落 `/status`)+ `web/server.ts` 装配(订阅新鲜时 `/status` 轮询**挂起**)。**真机数据(47 relay)**:`presenceTiming={graceMs 10000, offlineDebounceMs 30000, batchMs 1000, ttlMs 45000, subMax 0}`、`counters={subs 1, pushed 3, snaps 2, rejected 0}`、Manager 日志 `[relay-client] presence SNAP 2 条(scope=all)`。**验收**:`npm test` **169/0/1**(基线 161 + 新用例 **T25–T32 共 8 个**)|单测**先红后绿**(0/7 → 8/8)|**S6 本机多实例真机 8/8**(稳态 60 s **Δ帧=0**(期间 24 个心跳帧在流)|一次上下线 **各 1 帧**、断连→改口 **40985 ms**|首帧 `snapFrames=1`|跨网 **显式拒绝 rejected 0→1**|降幅 **63×**(字面口径)/10.5×(同消费者)/∞(稳态))|`--scene all` **12 PASS / 0 SKIP / 0 FAIL**|探针 **15 PASS / 1 FAIL**(唯一红项 `OBS-09`)|池口 **64**|`dshs/dshs-relay/dshs-worker/dshs-pg` 全 active。🔴 **两个必须记住的实测**:① **假死已修** —— `presenceTouch` 原早于 `ensureEndpoint`、且落点口号是 `listen(0)` **异步**才落地 ⇒ 首帧推 **`localPort=0`** 且**永不重推** ⇒ 订阅方 `addressOf` 查不到 ⇒ **页面打不开但不报错**;修法 = touch 后移 + 落点落地/端口变更时**强制重推**(`publishPresence(force)`),**新用例 `T32` 先红后绿覆盖**,真机复验 `localPorts=[{port:19000, localPort:35289}]`(非 0)。② **`OBS-09` 红 = S7 步骤与 E9 判据冲突**(`systemctl restart dshs` 会收掉 `dsh-*.scope`,平台**按需拉起**、⛔ 无 reconciler ⇒ 无人访问时实例不自回)⇒ 已按 **§9-8 停下报告**(详见该单 §8.8-1/§8.8-4)。**边界自证**:⛔ 未改任何生产值(六值逐项未变)· ⛔ 未新增公网口 · ⛔ 未改 nft/nginx · 🔴 `COOLDOWN_MS=0` 计数 **0** · ⛔ 未 commit / 未 push。**参数表指纹 `8f08e74b026e6e5b5e1b3db813f031ae` → `13de5f9b77c486d71e5b83ec909b17b2`**。
>
> 🆕 **2026-09-17 17:2x 刷新(序 ⑱ 规划棒收官 · presence 已出可执行单)**:**presence(在线状态)改造的规划已完成** ⇒ 产物 = 工作区根 **`交接单_presence在线态_20260917.md`**(287 行 + §11 回报;**§8 前缀指纹 `f612858344077420cff5f1f9ef1c942f`**;全文件 md5 `619a37818765152d74cef499e31bc41d`,落单时;⚠️ 加 §11 后**总字节变了、前缀指纹不变**)。**核心判断**:presence 的第一刀落在**节点 / 端点在线态** —— 因为**全仓零应用层**(`grep -rilE "\b(room|chat)\b" src | wc -l` = **0**)、`presence` 字样**只在 `src/net/relay/placement.ts:21` 的注释里**,而 Manager 现在的在线态来源是**拉 relay `/status` 快照 + 15 s 陈旧回退**(`src/net/relay/server.ts` 第 **413** / **1234** 行原文自证)= 正是要替换的"轮询"路径。**做法** = 照抄 `覆盖网络_瓶颈落地方案_20260916.md §1` 七条并逐条映射到本仓(**连接生命周期** + **1 s 批合并** + **订阅式扇出** + grace 10 s / debounce 30 s + **同 hostId 设备聚合** + **最终一致 5–15 s** + **TTL 安全网**)。**用户拍板** = 「**a 要做**」;⚠️ **B(内容分发 / 块级内容寻址)仍待拍板** ⇒ **只登记、⛔ 不规划**(该单 §0.4-1)。**边界**:§4.2 真取舍 = **空**、§4.3 待拍板 = **空**、**§4.4 R5 = 未命中**(新增监听口 / 凭据 / 入站均 **0**,可见面只收窄);**§9 有 9 条回头条件**(第 9 条 = 🔴 `COOLDOWN_MS=0` 硬禁令)。➡️ **下一棒 = 序 ⑲(执行棒)**,automation **`107b8e38-ccbd-4dcf-8b49-25dad7c89be9`**(一次性 · **2026-09-17 17:28** = 收口 +4 min)。
>
> 🆕 **(已被上行取代)2026-09-17 17:1x 刷新(用户拍板 ⇒ 下一棒已登记)**:用户答「**a 要做**」⇒ **presence(在线状态)改造正式启动**。**下一棒 = 序 ⑱(规划棒)**,automation **`50882a38-07cd-494c-ae73-cbbf27f3604d`**(一次性 · **2026-09-17 17:22** = 收口 +~5 min)⇒ 产出 = presence 的**可执行交接单**(依据 = `覆盖网络_应用场景与待完善清单_20260916.md` §五 第 7 步 + `覆盖网络_千台全场景推演_20260916.md` + `覆盖网络_瓶颈落地方案_20260916.md`);⚠️ **只出规划**(⛔ 不改码 / ⛔ 不动 47/106 / ⛔ 不 commit·push)。⚠️ 另一项 **B = 内容分发(块级内容寻址)仍待拍板** —— 复杂度已评估为「**中**」(方案 §3:需新建 ① 块级切分与哈希标识 ② 内容寻址存储 + 客户端校验 ③ 同网段 peer 发现 + 内容源优先级链;可照抄 BranchCache / Delivery Optimization / SCCM;验收判据单值可测「**回源字节数 ≈ 1 份 × 组数**」;⛔ 不做"包级"=已被证过的全量重拉风暴成因)⇒ **⛔ 本棒未登记、未规划**。
>
> 🆕 **2026-09-17 17:3x 刷新(序 ⑰ 执行棒收官 · 本线在册项清干)**:**四条在册项 —— S1 ✅ / S2 ✅ / S3 ✅ / S4 ⚠️(临界档 · 记录入册、⛔ 不判 FAIL)**。**S1** mksess 口径「DB 直插」→「**PG 直插**」(5 处操作性载体:`02-运维手册.md` + `skills/dsh-change-workflow/SKILL.md`×3 行 + `skills/dsh-env-bootstrap/references/常驻规则-快照.md`×1 行 + 本机技能同内容;⛔ `04-调整方案/**` 档案正文**未动**;判据 E1 = **0 行**)|**S2** 技能 `dsh-auto-handoff-chain`(v1.3.2)**三处同步首次建立** —— 文档库新建副本 + `README.md`/`INDEX.md` 登记 + 47 镜像 `/opt/dsh/docs/skills/` ⇒ `diff -r` = **0 行**、md5 **三处同值**(`0c5c4103…` / `636c4f33…`)|**S3** 代码仓 `.bak-seq7-*` **5 个 → 0**(全 untracked、seq7 11:21 过期快照 ⇒ **非有效回滚点**;`npm run build` RC=0 + lib 产物 md5 **零差异**)|**S4** 幕 4-A 五样本 **18772 / 20121 / 20246 / 21223 / 24477 ms** ⇒ p95(保守 nearest-rank 口径)= **24477 ms** ⇒ **临界档(24000 < p95 ≤ 27000)⇒ 记录在册、⛔ 不判 FAIL、⛔ 未调任何生产值**,⚠️ 回头条件(> 27000 ms)**未触发**。**零回归全绿**:`npm test` **162/161/0/1**、`--scene all` **12 PASS / 0 SKIP / 0 FAIL**(幕 4-A 20419 / 18333 ms)、`overlay-probe` **12/12**;收口三服务 active、池口 **64**。
> 🗃 **Q4 已由用户授权解除并办结**(原话「执行完毕后 同步到仓库」)⇒ `146c3d2`(代码/测试面 **53** 文件)+ `bc0dd2c`(文档库面 **10** 文件)已 push;远端 `master` = **`bc0dd2c`**,`git ls-files src/net/relay | wc -l` **0 → 15**(序⑭/⑮ 的修复**首次进版本库**)。
> 🛑 **本线已无自动接续**:四条在册项清干后,**剩余项全部落在「需拍板」区**(presence / 房间层 / 内容分发**定序** = 业务优先级;骨干节点服务范围 = §4 的 A/B 既有待拍板项)⇒ 按登记门禁**不登记下一棒**,等你拍板后再建。证据 = `交接单_在册收尾_20260917.md` **§9**;参数表指纹 **`8f08e74b026e6e5b5e1b3db813f031ae` 未变**。
>
> 🆕 **2026-09-17 16:4x 刷新(序 ⑯ 规划棒收官)**:**① 复核判定 = 会合 / 中继从 Manager 拆分「不做」** —— 原方案 `会合中继拆分_取证与改造方案_20260916.md` 的四个耦合点 C1–C4 与两步改造 S3/S4 **逐条已被覆盖、未覆盖部分 = 无**:C1 → 序② P0-2(`DSHS_RENDEZVOUS_URL` 优先 / `DSHS_TUNNEL_TARGET` 降兜底,`src/config.ts:485-490`)|C2 → **R5**(Manager 只拨出、落点搬到本机回环池 `127.0.0.1:25000..26000`,`src/config.ts:498-500`;`dialer.ts:11`)|C3 → S2/P2(`dsh_hosts.via` + `RendezvousRegistry`)|C4 → 方案 §8.1 实测**前提不成立**|S3 → 方案 §9.1 自我证伪后改「实例端口区间隔离」|S4 → R2(`dshs-relay` 独立单元)+ 序⑥ S8(106 升格第二中继)+ 序⑦(多实例切流实测)。该方案文档**头部已加状态块、正文未改**(存档)。**② 剩余在册四条已定序并出执行单** ⇒ 产物 = 工作区根 **`交接单_在册收尾_20260917.md`**(243 行;**§8 前缀指纹 `bab83b7219b2669d5a6e9f1acf782e1f`**、全文 md5 `ca13b9ded3d71268f8b0e56755d7aea4`)= **序 ⑰ 执行棒**,顺序 = **S1 mksess 文档口径(「DB 直插」→「PG 直插」)→ S2 技能 `dsh-auto-handoff-chain` 三处同步建立 → S3 清理代码仓 `.bak-seq7-*`(🔴 **范围修正:5 个,不是 3 个**)→ S4 幕 4-A 余量判据化 + 5 样本采样**。🔴 本棒两条新事实:**① Q5 真实范围 = 5 个**(多出 `src/web/server.ts.bak-seq7-*`、`src/worker/tunnel.ts.bak-seq7-*`,**5 个全 untracked**、均为过期快照 ⇒ 无回滚价值)② **`04-调整方案/**` 档案的 mksess 旧口径不改**(档案 = 当时事实,只改操作性载体 + 写勘误指针)。⚠️ 登记:`src/worker/tunnel.ts`(SSH 隧道)现已是**生产死路径**,是否删 = 独立决策,⛔ 本单未处置。➡️ **下一棒 = 序 ⑰(执行棒)**,automation **`067b0892-964e-4deb-bf84-4f1c37bebea8`**(16:44 起)。证据 = `交接单_观测口径与在册缺陷_20260917.md` **§12**。
>
> 🆕 **2026-09-17 16:3x 刷新(序 ⑮ 执行棒收官 → 缺陷 B 已修)**:**拨号池"未分配槽位被一条连接永久杀死"已修并端到端验收** —— 病根 = `src/net/relay/dialer.ts#onConn` 见到 `slot.key === undefined` 时 `slot.server.close()`:关掉的只是**服务器**,槽位**仍留在 `slots` 里**、`key` 仍是 `undefined` ⇒ `localPortFor()` 的 `slots.find(s => s.key === undefined)` **下次还会选中它** ⇒ 把死口号当落点发出去(47 上实测:`16:00:27 落点 127.0.0.1:25000 -> ops/w-106:19000` 紧接 `16:00:31/35/40` 三条 `POST /api/dsh/enter` → **500 `fetch failed: connect ECONNREFUSED 127.0.0.1:25000`**)。**改法 = 只丢弃那条无法路由的连接(槽位原样保留)+ `stray` 计数 + 点名日志**,另加纵深防御(`localPortFor`/`lruIdle` 只认 `server.listening`;`status().pool` 改报"实际在听")。⛔ **不取"把槽位从 `slots` 摘掉"那条** —— 那等于让**任意本地连接**都能**永久**蚕食池容量(R11)。**验收**:**先故意对未分配池口 25000 发一条连接**,随后 guest `enter` 仍 **200/200**、该口同连接连发 3 次 **200/200/200**、打后池口仍 **64**;`npm test` **162/161/0/1**(+T24)、`--scene all` **12 PASS / 0 SKIP / 0 FAIL**、`overlay-probe` **12/12**。部署 = `dialer.js`(`6446fe9b…`)**五处同值**、只重启 47 的 `dshs`。⚠️ 顺带确认一条**环境口径**:对 47/106 的 ssh **必须显式 `-p 22`** —— 别名 `bt-server` 里的 `32022` 是**失效残留口**(47 上 sshd 只监听 22,自 09-16 22:12:48 起)。➡️ **下一棒 = 序 ⑯(规划棒:会合/中继拆分复核 + 在册收尾定序)**,automation `91c53ef8-c3f0-4bb3-9f0a-943790178176`(16:36 起)。证据 = `交接单_观测口径与在册缺陷_20260917.md` **§11**。
>
> 🆕 **2026-09-17 16:1x 刷新(序 ⑭ 执行棒收官)**:**relay 流 keep-alive 复用缺陷已修并端到端验收** —— 真因 = `src/net/relay/client.ts#openStream` 里多挂了一行 `duplex.on('data', …)`,把**读侧接回了出向**:agent 的响应经 relay 回到拨号方后又被 `pumpDial()` 打回去,worker 把它写进 agent socket ⇒ agent 拿 `HTTP/1.1 200 OK` 当**请求行**解析 ⇒ Fastify `clientError` 回 `400`(**用户可见:任何 `via='relay'` 的 host(今天 = w-106)用户 `POST /api/dsh/enter` 必 500,「登录直达工作区」整体不可用**;curl 看到的"第 2 条 400"其实是第 1 条响应的回声)。**改法 = 删掉那一行**(出向唯一入口是 `_write`→`onOut`;`duplex.write()` 与 `tcp.pipe(duplex)` 本就走同一条路,该监听纯属多余且方向错)。**验收**:guest `enter` **200**、同连接连发两次 **200/200**(三次 200/200/200)、`npm test` **160 pass / 0 fail / 1 skip**(基线 159 + 新回归用例 **T23**)、`--scene all` **12 PASS / 0 SKIP / 0 FAIL**、`overlay-probe` **12/12**。部署 = `client.js`(`b8b29afb…`)铺 5 处、**只重启 47 的 `dshs`**(改动是拨号方专属)。🔴 **新发现在册缺陷 B(R7 只报告、未动手)**:拨号池**未分配**槽位被任意一条连接碰到 ⇒ 该槽位**永久死亡**(`dialer.ts#onConn` 只 `slot.server.close()` 却不摘除槽位),而 `localPortFor()` 之后照样把它发出去 ⇒ `ECONNREFUSED` ⇒ `enter` 500 `fetch failed`。证据 + 三个候选修法 + 判据 = `交接单_观测口径与在册缺陷_20260917.md` **§10.8**;参数表指纹仍 **`8f08e74b026e6e5b5e1b3db813f031ae`**(**未变** ⇒ D1 自证)。➡️ **下一棒 = 序 ⑮(执行棒:缺陷 B 修复)**。
> 🆕 **2026-09-17 15:5x 刷新(序 ⑬ 执行棒收官)**:**在册缺陷清算 Q1→Q3 —— `Q1 ✅` / `Q2 🔴 阻塞(新真因)` / `Q3 ✅`**。**Q1**:`/opt/dshs/mksess{,-guest}.cjs` 由**写 SQLite 旧库**改为**写 PG**(连接串从 env / `dshs.env` / `dshs.service.d` 读,⛔ 不固化凭据)⇒ R4 的实例面验收手段恢复(实例面 `curl` 回 **401**,非 `000`);指纹 `mksess.cjs` **`a97c0f214650fbc552c07d804fd153be`**、`mksess-guest.cjs` **`af6eab38c4ff5c365ee17e29d7b98a31`**。**Q3** ✅:**探针 12/12 PASS · rc=0** —— `OBS-09 本机:20000=401 w-106:42461=401` 绿,且"有实例态"下 `OBS-11` 仍绿(实际 77 / 多出 0 / 缺失 0、`20000` 在允许区间)。🔴 **Q2 未达成,且真因不是单内记的 `landModels` 本地路径** —— 是**新发现的数据面缺陷**:**relay 流上的 HTTP keep-alive 复用**(同一条连接第 2 条 agent 请求必回 `400 clientError`,机制 = **worker 侧把上一份响应回灌给 agent**)⇒ **任何 `via='relay'` 的 host(今天 = w-106)用户 `POST /api/dsh/enter` 必 500 = 登录直达整体不可用(用户可见)**。证据链与 1 条命令的最小复现 = `交接单_观测口径与在册缺陷_20260917.md` **§9.3**;`src/` 与参数表**零改动**(参数表指纹仍 `8f08e74b…`)。➡️ **下一棒 = 序 ⑭(执行棒:relay 流 keep-alive 缺陷修复)**。
> 🆕 **2026-09-17 15:3x 刷新(序 ⑫ 执行棒收官)**:**`OBS-11` 已由「计数相等」改为「三集包含式白名单集合」(`nft` 同族改「入站 accept 集合 ⊆ 白名单」)** —— 参数表 §6 **新增 4 键**(`LISTEN_REQUIRED` / `LISTEN_ALLOWED` / `LISTEN_ALLOWED_RANGES` / `NFT_ALLOW_INBOUND`)、`LISTEN_COUNT` / `NFT_RULES` **退役为仅对账**;唯一代码改动 = 代码仓 `scripts/overlay-probe.cjs`(⛔ `src/**` 零改动 ⇒ `git status src/` **20 → 20**);**新增夹具模式**(`--listen/nft/status-fixture`,离线、零生产副作用、输出带 `FIXTURE` 标记)。**验收**:`overlay-probe` **10/12 → 11/12**(剩 `OBS-09` = 环境态);ⓐ **假红已消除**(真态 77 行 ≠ 旧阈值 79 ⇒ 旧判据必红,新判据 PASS)ⓑ **假绿已能抓**(`fx-swap` 行数恒 79 ⇒ 旧判据 PASS,新判据同时点名 `多出 127.0.0.1:9999` + `缺失 0.0.0.0:888`)ⓒ 真机受控临时口「多出必红」实证(`127.0.0.1:27000` 起口 ⇒ FAIL 点名,关口 ⇒ 回 PASS、`ss` 回 77)ⓓ `nft` 走 `-j` 正路且退化文本路径**链感知**(不误抓 `FORWARD` 的 accept);零回归 = `npm test` **160/159/0/1**、`--scene all` 全绿、`ss` 77 / `nft` 72。**参数表指纹 `99e9e17b…` → `8f08e74b026e6e5b5e1b3db813f031ae`**。⚠️ 两条与单不符处已按**实质判据**处理(P1-注 的「零 `*`」⇒「**Local 列**零 `*`」;P4/E7 的「`src/` = 0」⇒「**不新增** `src/` 改动」,20 是既存未提交基线)。➡️ **下一棒 = 序 ⑬(执行棒:在册小缺陷清算 Q1→Q2→Q3)**。证据 = 本单 §8 + `_中间产物_待清理/seq11/`。
>
> 🆕 **2026-09-17 15:0x 刷新(序 ⑪ 规划棒收官)**:**观测口径重构已定序并出单** —— 工作区根 `交接单_观测口径与在册缺陷_20260917.md`(**§8 前缀指纹 `3ece0f870cba67d0113a4c5f9de9d812`**,244 行)⇒ **① OBS-11 判据由「计数相等」改为「白名单集合」**(三集包含式 + `nft` 同族改「入站 accept 集合」)|**② 6 条在册小缺陷清算排序**(`mksess*.cjs` 失效 ⇒ **阻塞实例面验收 ⇒ 先修**|guest(w-106) 实例页 502|…;排序见该单 §5.9)。范围 = **只改观测面**(`scripts/overlay-probe.cjs` + 参数表 §6),⛔ `src/**` 零改动。➕ 顺手办:`.workbuddy/memory/MEMORY.md` **真瘦身 8 121 → 7 712 字符**(回到注入上限内)。➡️ **下一棒 = 序 ⑫(执行棒)**,口径见 §2「🎯 本轮动作」。证据 = 该单 §5–§7 与 §8.9。
>
> 🆕 **2026-09-17 14:5x 刷新(序 ⑩ 收官 · 三件技术债全清)**:**① 幕 2 状态依赖 ✅ 已修**(`--scene all` **12 PASS / 0 SKIP / 0 FAIL**,原 11/1;`--scene 2` 单跑 **3 PASS / 0 FAIL**)|**② `COOLDOWN_MS=0` ✅ 0 命中**(`--scene 4c` / `ctrl` 整体移除 + 显式拒绝 = 退出码 2)|**③ OBS-11 `78 → 77` ✅ 有名字 = `20000`**(47 实例档;当前 47 无活跃实例 ⇒ 未监听。76 条 socket **逐条点名、零无名**,池 64 口全在;另订正 `39463` 实为 relay **动态**落点,现 `46147`)。➡️ **下一棒 = 序 ⑪(规划棒:OBS-11 计数口径 → 白名单集合 + 在册小缺陷清算排序)**,口径见 §2「🎯 本轮动作」。证据 = `交接单_检测时延与deadline_20260917.md` **§8.8-4**。
>
> 🆕 **2026-09-17 13:5x 刷新(序 ⑤–⑨ 全部收官)**:**① 参数表 · 观测 · 权限评估(⑤)② 3–5 台最小形态(⑥)③ 中继失败切流(⑦)④ 切流冷却语义(⑧)⑤ 检测时延与 deadline(⑨)—— 五序全绿**。现行形态:**两台真中继(47 + 106,均只绑回环)**|`RELAY_MAX_HOSTS` = **7515**|**失败自动切流 + 冷却语义(含一跳豁免)+ 换址终态失败早退**已落三处装配点|`npm test` **160/159/0/1**。🔴 **本线唯一的"换址慢"已闭环**:白等 **12.03 s → 0.09 s**、端到端 **34.1 s(中位)→ 25.3 s(中位)**。⇒ **下一棒 = 序 ⑩(覆盖网络线技术债清算 · 执行棒)**,口径见 §2「🎯 本轮动作」。
> 🆕 **2026-09-17 11:0x 刷新(序 ⑥ 执行部分收官)**:**覆盖网络已是"两台真中继"** —— 47 + **106**(106 复用既有 443、零新增公网口、只绑回环 `20080`);参数表 **五项待测全部换成实测**、**待测 4 → 0**;`RELAY_MAX_HOSTS` 按实测重算 **225 → 7515** 并已下发两台;**收口 `overlay-probe` 12/12 PASS**、47 监听口 **79 = S0 基线**、`npm test` 137 pass ⇒ **无退化**。⚠️ **唯一未过项 = 「杀掉任一台中继不自动切另一台」(无失败切流)** ⇒ 下一棒 = **序 ⑦ 规划棒**。证据 = `交接单_最小形态真机批次_20260917.md` §8(§8.9 可复现指纹)。
> 🟢 **2026-09-16 23:0x 刷新(机制层 R0–R5 全部完工)**:**R2 / R3 / R4 / R5 全部落地并端到端验收** —— relay 在 47 常驻(`dshs-relay`,只绑 `127.0.0.1:20080`)|经 `https://alotbuy.com/dshs-relay` 可达(origin 与 CF 双路 `101`)|**w-106 已 `via='relay'`**(agent 面与**实例面**都经 relay)|**sshd 反向隧道已下线且不可能重建**(47 收回隧道公钥、`32022` 回收;106 摘掉 `DSHS_TUNNEL_*`、私钥移走)⇒ 47 公网暴露面**净减 1 口**。
> 🆕 **R5 = 会合可换机(本轮新增)**:R1–R4 隐含的「**Manager 必须与 relay 同机**」这条前提**已不存在** —— Manager 改为**只拨出**一条 wss,落点搬到**自己本机**的回环池(`127.0.0.1:25000+`,64 口)。**relay 放哪台机器都不再影响 Manager。**
> **完整证据 = 交接单 `交接单_relay落地R2-R4_20260916.md` §9(R2)/ §10.4(R3 Step 0)/ §10.5(R3 本体 + R4 全链)/ §11(R5)**(均含命令原文级输出);单测 `relay + remote-spawner + reachability + instance-port` = **38/38**(R5 新增 T18/T19);本机 `lib/` 与 47 `/opt/dshs/lib/` 对账 **0 差异**(R5 产物 29/29 hash 全同)。
> 🔴 **本线留下的一条硬教训(别重犯)**:`RemoteSpawner` 构造函数漏赋值 `this.translateEndpoint` ⇒ 端点翻译**静默失效** ⇒ Manager 拿 Worker 侧口号往**自己本机**拨 ⇒ **浏览器只见「空响应」、平台零日志**。定位靠**判别器**(临时监听 `21000` 把请求接走),不靠读代码猜 —— 判据已钉进 `test/remote-spawner.test.mjs`(先红后绿已实证)。
> ✅ **R5 又抓到同族第二条(已于 2026-09-16 23:4x 修完并端到端验收 ⇒ R2-R4 单子 §12)**:`src/fs/remote-user-fs.ts:70-73` 在 `agentFor(hostId)` 解析不出时**静默回退到 w-47 本地 agent** ⇒ w-106 用户的 launch/文件面在**每次重启后的一小段窗口**里回 **假 `404 {"error":"not_found"}`**(与"文件夹不存在"完全同形)。**判别器 = 看 relay 有没有 `DIAL`**:没有 = 请求根本没出去。
> 🔴 **下一个动作(⛔ 已勘误,勿再按旧指示走)**:⛔ **不是** "presence 改造"。机制层 R0–R5 已齐,但 `覆盖网络_应用场景与待完善清单 §五` 把**房间层/presence 排在第 7 步**,其前置是 **② 网抽象 + 地址规划 + 引导(P0-1/2/3) ③ 一机一钥 + 信任根 ④ 443/TCP 兜底 ⑤ 参数表 ⑥ 3–5 台最小形态**(且仓库里目前**没有应用层代码**,"presence 改造"无从下手)。⛔ 不要再做 R2/R3/R4/R5、不要再开 32023、不要再做 SSH 版中继。
> 🟢 **2026-09-17 07:4x 刷新(② 收官)**:**序 ② 已完成**(P0-1 网抽象 / P0-2 引导三级链 / P0-3 名字解析与授权,三步全部落地 + 端到端验收;执行进度:`交接单_网抽象与地址规划R6_20260916.md` 的「执行进度」段,指纹 `0d3f1f305e9e6219afa97cdfecff05ba`)。
> ⇒ **下一棒已定序(2026-09-17 07:5x 定序 · 09:1x 刷新)**:~~序 ③(一机一钥 + 信任根)~~ ✅ **已完成**(证据 = `交接单_一机一钥与信任根_20260917.md` §8)→ ~~序 ④(443/TCP 兜底)~~ ✅ **已完成**(证据 = `交接单_443兜底_20260917.md` §8)→ ~~**下一棒 = 序 ⑤(参数表·观测·权限评估)**~~(⛔ **已过期**:⑤–⑨ 均已收官 ⇒ 现行下一棒 = **序 ⑩**,见上方最新刷新行与 §2「🎯 本轮动作」)。**定序理由**:③ 是身份基线、④ 是失败域分离,⑤ 建立在前两者之上(参数表要把 ④ 带回的 45% 口径填成数值,权限评估要覆盖 L3 跨机真容灾)。⛔ 不要重做 ② / ③ / ④ 的任何一步。
> 另:本轮把 §11.6 **记下没动手**的两条缺陷修完并端到端验收 —— 见 R2-R4 单子 **§12**(A1 假 404 真因定死为"文件面 `hostDirectory` 无刷新机制"+ 治本/治安全双修 + 冷启动直接证据;A2 `state.py` 恒报锁空闲)。
覆盖网络/客户端化这条线已产出 **10 份规划文档 + 1 份执行交接单**(全在工作区根目录),**代码已有 S0+S1 落地(本机 commit `640813e`),且 R2–R5 的产物已铺到 47(Manager)与 106(Worker)两端**(R5 产物对账 29/29 hash 全同);⚠️ **方案 S3 的机制已于 2026-09-16 15:2x 实测证伪并改写** ⇒ 详见下方「15:2x 复核」与 §2。
| 项 | 值 |
|---|---|
| 文档库执行锁 | ✅ **空闲**(2026-09-17 15:0x 由「覆盖网络线-序11规划棒」释放;该棒只出规划 + 改 `.workbuddy/memory/MEMORY.md`,⛔ 未碰 `src/`、未动参数表) |
| **S0 / S1 落地真相** | ✅ **两端都已铺**(2026-09-16 23:0x 复核):本机 `lib/` vs 47 `/opt/dshs/lib/` ⇒ **R5 产物逐文件对账 29/29 hash 全同**;relay 运行时 `/opt/dsh-relay/lib/net/relay/*` 同步 |
| **15:2x 复核(实测)** | 🔴 **S3 原机制证伪**:47 `sshd gatewayports no` ⇒ `-R 127.0.0.2:…` 被**静默改写为 `127.0.0.1`**(ssh 侧零报错)⇒ 回环别名方案作废,改「**实例端口区间隔离**」|🔴 **发现真 bug**:`findFreePort()` 各 worker 各自随机 ⇒ 同号在 47 的 `127.0.0.1` 撞号、而 `tunnel.forward()` 返回值被忽略 ⇒ **静默打到别人实例**|⚠️ `verify-cluster-cross.mjs` 默认端口 13080 ≠ 现役 3080,且**有生产副作用**(改容量/建用户/拉实例)⇒ 跑完要清 |
| 代码 / 服务器 | 🆕 **序⑩ 改动(2026-09-17)**:仅 `scripts/overlay-failover-drill.cjs`(幕 2 状态无关化 + 删掉 `4c`/`ctrl` 两个 `COOLDOWN_MS=0` 场景),⛔ **未碰 `src/`**、未改任何生产阈值、relay/Manager 均已复原并复验(`systemctl restart dshs` 归零回 47)|⚠️ **本轮有改动**:代码仓 `src/net/relay/*`(R5:`wire/duplex(新)/server/client/dialer(新)/main/index`)+ `src/config.ts` + `src/web/server.ts` + `test/relay.test.mjs`;47 上 relay/manager 两个 drop-in + `relay-keys.json`(均有 `.bak-r5*` 备份) |
| 文档库 | ✅ **10 份已转正式档案 103–112**(`4e3a1a4`)+ 已推镜像,**双端对账 192/192 全绿**(2026-09-16 11:5x) |
| 当前基线 | 代码仓 HEAD = `640813e` —— ✅ **已 push**(2026-09-16)|⚠️ 本机工作区**有未提交改动**(S2 会合中继拆分 + R3/R4/R5 产物 + 新增 `test/remote-spawner.test.mjs`)⇒ ⛔ **未获授权不 commit / 不 push** |
| 根副本 vs 档案 | 本线 10 份根文档的正文**已入档案 103–112**(行覆盖 **98.5–99.0%**,差异仅一级标题行)⇒ **根副本保留为工作副本,⛔ 不搬**(被 11 处引用,搬走会断链) |
| **自动接续** | ❌ **已于 11:00 删除,且⛔ 不要再建** —— 两版都试过(7 积分/轮 → 优化版 **9 积分/轮**),**证明"定时任务省积分"是伪命题**。原因见下方「成本真相」。 |
> 💰 **成本真相(终版 · 2026-09-16 11:2x,**实测那个会话本身**)**
> ⛔ 前两版都错:v1 把固定注入当"一次性入场费"(错);v2 我**按最新 mtime 猜会话**,分析到了另一个"会话复盘"会话(`e2e090be`,错)。
>
> **定位方法(可复跑,别再用 mtime 猜)**:拿 prompt 里的独有字串去搜转录 ——
> `grep "能脚本化的全部脚本化" <projects>/*.jsonl` ⇒ 唯一命中 **`e265f0cd-bd24-48c7-953e-4bbfa615bc7d.jsonl`**(761 KB,10:50:09 结束)。
>
> | 指标 | 实测 |
> |---|---|
> | **工具调用次数** | **32**(Bash 16 / Edit 6 / Write 5 / Read 4 / present_files 1) |
> | 模型请求(记录数) | 54(转录对每次请求记**两条**:一条无 `cache_read`、一条有 ⇒ **实际约 27**) |
> | **Σ input** | 5,205,114(同口径折算 ≈ 2.6 M) |
> | Σ cache_read | 2.55 M(≈49%) |
> | 单次 input:首 → 峰/末 | 52,582 → **141,401** |
> | **固定注入 5 万 × 请求数** | **占 51.9%**(对折不改变比例) |
> | **本次新灌入的内容** | 工具输出合计 **84,870 字符 ≈ 2.4 万 token ⇒ 仅占 0.5%** |
>
> **它实际干了什么**:32 次调用集中在 `D:/github/dsh_shenxian` 的 **git / 文件读写**,并读过一次 `接续入口`(4,443 字符)。**并没有去读源码做取证。**
>
> **三条结论**
> 1. **成本 ≈ 请求次数 × (固定注入 + 累积历史)**。本次 27–54 次请求里,**固定注入一项就占 51.9%** —— 即**花掉的一半积分,只是"把指令与常驻规则重复发了二三十遍"**。
> 2. **本次真正"读进来"的东西极少(≈2.4 万 token,占 0.5%)** ⇒ 这笔钱**不是"上下文太大"造成的,是"请求太多次"造成的**。
> 3. ⇒ **唯一的大杠杆 = 压请求次数(= 压工具调用次数)**:32 次调用若能压到 1–3 次,Σinput 掉一个数量级(9 积分 → ~1 积分量级)。**这才是真能省下来的钱。**
>
> **⛔ 由此两条硬规矩**
> ① **批量活不要"要求 agent 写脚本",而要"只准跑这个脚本"** —— 脚本**由上一会话预先写好**,任务里命令式指定;agent 没有探索余地,请求次数自然压到个位数。
> ② **绝不把"读代码 / 大范围取证"派给无人值守会话**(要么脚本先 grep 计数,要么有人盯着)。
> ⚠️ **成本教训(2026-09-16 实测,勿重蹈)**
> 1. **"新会话"不等于"不花钱"**:本工作区**每轮固定开销实测 ≈ 51,830 token**(系统提示 + 工具定义 + 常驻规则 + 注入的记忆)⇒ 任何新会话第一轮都要付这笔钱。准确的收益口径是「**把每轮水位从 39 万降到 ~5 万(约 1/8)**」,**不是"零成本"**(我此前把它说成"降低 token 消耗",属**不准确承诺**)。
> 2. **最大的可压缩项 = 每轮注入的记忆文件超限**:✅ **2026-09-16 修复轮已办** —— 实测真被截断的是**用户级** `MEMORY.md`(注入上限 **≈4,000 字符**),已按**分层重排、零删除**修好;🔑 **重排是杠杆,删减不是**(方法已沉淀为 `dsh-knowledge-upkeep §8.6「注入预算」`)。
> 3. ⛔ **不要把"10 份文档档案化"交给 agent 逐份做** —— 那是 20+ 轮 × 7 积分 ≈ **140+ 积分**,违反项目省积分第一招「**批量活写脚本**」。**正确做法 = 写一个转换脚本一次跑完**(加 8 段头部 + 原子占号 + 登记 + audit 全在脚本里),agent 只读摘要 ⇒ **1–2 轮完成**。
---
## 1. 十份文档(按阅读顺序;前 3 份是决策层,其余是专项)
| # | 文件 | 一句话 |
|---|---|---|
| 1 | `可行性评估_客户端安装与覆盖网络互联_20260916.md` | 判定可做;列出**现有架构已给出的 80% 形状**(soft 隔离 = 裸子进程 / Worker 四条协议纪律 / 拨出式反向隧道 / 全仓仅一处平台分支);§六 = 与另两份客户端文档的对账 |
| 2 | `覆盖网络_全球架构复盘_20260916.md` | 五层架构 + 12 条内建特性 + 选型对照 + **10 类风暴类型学** + 流量组织五原则 + 7 步落地顺序 |
| 3 | `覆盖网络_瓶颈落地方案_20260916.md` | **九大瓶颈逐个给可执行做法**(含 Slack / SCCM 官方做法照抄点 + 验收判据);**"只做三件事"= presence + 游戏服放 L1 + 块级内容寻址** |
| 4 | `覆盖网络_骨干层方案_20260916.md` | 多中心骨干(≤10–20 成员)+ 选择性加入;三条硬约束(接入/成员/可见三分离、资格只能控制面签发、骨干不得被默认征用) |
| 5 | `覆盖网络_百台规模推演_20260916.md`(v2) | 100 台异构画像;L1 10 / L2 30 / **L3 需中继 40–55**;全互联 4,950 vs 星型 100 |
| 6 | `覆盖网络_千台全场景推演_20260916.md` | 1000 台 × 11 场景;**瓶颈排序**(presence 第一)+ 流量预算总表 |
| 7 | `覆盖网络_游戏专项_MMORPG与MUD_20260916.md` | MMORPG/MUD/传奇 = **最匹配**;核心简化:**玩家之间不需要互联,中继按"服数"算** |
| 8 | `覆盖网络_调研_游戏网络特征与群聊上限_20260916.md` | 游戏网络参数(每玩家 2–20 KB/s、jitter<20 ms)+ 群聊上限**按扇出预算定义**的分档 |
| 9 | `覆盖网络_答疑_群聊备份迁移保密_20260916.md` | 四问:群聊+agent / 备份(主备份用对象存储)/ 迁移提速 / 三层加密与元数据保护 |
| 10 | `覆盖网络_补遗与参考方案_20260916.md` | 补遗 24 条 + 参考方案对照 + **反模式 12 条** |
| 11 | `参数表_覆盖网络_20260917.md` | **序⑤ 产物 · 唯一一张参数表**(155 行):输入参数 + 代码固化常量 + 45% 口径填值(`--max-hosts = 225`)+ 观测阈值 `OBS-01..12` + 权限影响评估 + 待测汇总。**观测探针** = 代码仓 `scripts/overlay-probe.cjs`(一条命令 / 12 行 / 退出码 0-1-2;cwd = 本工作区根即可跑) |
**另有两份他人产出(已按最佳方案收口,勿重复改)**:`dsh客户端化部署方案_20260916.md`(v3,平台侧单一来源)|`dsh桌面客户端_开发方案_20260916.md`(v1,交付载体层)。
> 🧭 **方法论(2026-09-16 16:4x 新增)**:`方案规划方法_覆盖网络线提炼_20260916.md` —— 把本线「**信息收集 → 方案调研 → 场景梳理 → 逻辑验证 → 应用推演**」五段作业法反推成可复用方法(含每段骨架/出口判据、**出口闸门三件事**、8 次真实纠错自证、可抄模板)。**下次做新方案规划时先读它。**
---
## 2. 下一步
> ✅ **上一轮已完成(2026-09-17 08:32,执行棒)**:**序 ③(一机一钥 + 信任根)已收官** —— 四层密钥模型(离线根 → 47 在线签名者 → 每机节点密钥 → 会话)落地;relay `keys` 表改**逻辑名 `<net>/<hostId>`** 索引 + **成员资格校验**;新增密钥仪式 CLI `scripts/overlay-keyring.cjs` 与 `test/overlay-identity.test.mjs`(25 例);**吊销演练 + 节点密钥轮换 + 根密钥恢复演练(判据①②③全绿,794 ms)全部通过**;**relay 已开强制身份**(`identityRequired=true` / `identityOk=3`);`npm test` 130/129/0;对账 148/148 + 26/26 全同。**全部证据 = `交接单_一机一钥与信任根_20260917.md` §8**(回填后指纹 `md5 204c0f69630e5117c227828f5fc01f2d`,427 行)。⚠️ 一条**与序 ③ 无关**的未过项:guest(w-106) 实例页 **502**(真因 = 平台 `landModels → writeHomeFile` 用**本地路径**写远端实例 home;另发现 `/opt/dshs/mksess*.cjs` 仍写 SQLite 旧库、**已失效**)⇒ 两条并入序 ④ 的相邻缺陷清单。⛔ 不要重做序 ③。
>
> ✅ **上一轮已完成(2026-09-17 08:4x,规划棒)**:**序 ④ 的 443/TCP 兜底交接单已出** —— `交接单_443兜底_20260917.md`(工作区根,225 行,**md5 `b9aa6bbc0a458481627f7aeb1f17ab54`**)。要点:端口仍 443(零新增公网口)+ 47 上**独立 server 块 / 独立 server_name**(去 CF、去门户 conf 两个失败域)+ 入口列表走 **`DSHS_OVERLAY_BOOTSTRAP_SEEDS`**(⛔ 不用会**压制引导链**的 `DSHS_RELAY_URL`)+ 地址覆盖走**独立配置项**(⛔ 不塞 URL / 不塞签名目录)+ 签名目录 `relays[]`/`bootstrap[]` 自动广播(改一次 seeds 全网刷新)+ 降级必须可 grep。**L3(跨机真容灾)已明确后置**(命中 R5,属序 ⑤ 权限评估)。⛔ 不要重做规划。
>
> ✅ **上一轮已完成(2026-09-17 09:1x,执行棒)**:**序 ④(443/TCP 兜底)已收官** —— 47 上新增**独立 443 server 块** `relay-direct.conf`(`server_name relay-direct.alotbuy.com`,**只**放行 `/dshs-relay` 与 `/dshs-overlay/bootstrap`、其余一律 404)+ **seeds 广播**(`relays[]`/`bootstrap[]` 均含兜底项、**主入口首位**)+ 客户端**地址覆盖**(定向 DNS 改写 ⇒ 直连指定 IP、**SNI 仍为域名**、证书照验)+ **「同源优先」**(补掉"兜底项在取址路径上**永远选不中**"这个真缺陷 —— 它使 §1 目标与 D6 同时落空)。**D1–D8 逐条验收**(D2 **字面**判据因 `*.alotbuy.com` 是 **CF 泛解析**而不可满足 ⇒ 按**实质**判据「不经 CF」的两条独立证据判绿;**§4.3 的 A/B 两个候选都不需要 ⇒ 本单零依赖关闭**)。**全部证据 = `交接单_443兜底_20260917.md` §8**(该单 **§8.9** 有可复现指纹口径)。
> ⚠️ **序 ④ 带回的四条遗留**(下一位读者必须看):① D2 解析层 —— 运行期"去 CF"由 `DSHS_OVERLAY_ADDR_OVERRIDES` 保证,"解析层也不经 CF"需灰云记录(未做);② 🔴 **106 的 agent 面不吃引导链** —— `DSHS_RENDEZVOUS_URL` 被 `worker/agent.ts` **直接当 relay URL 用**,⚠️ **S2-附 的字面执行会造成生产回归(`tunnel===undefined`)⇒ 已判定不执行**;③ 引导链缓存两支没有"答出者"信息 ⇒ CF 打挂后切换有 **≤300 s 收敛期**;④ **45% 容量口径未填数值**(留给序 ⑤ 参数表)。
> ✅ **上一轮已完成(2026-09-17 09:1x,规划棒)**:**序 ⑤ 的交接单已出** —— `交接单_参数表与观测_20260917.md`(工作区根,**250 行**,md5 `40f08167990eac54ec8a026b9c57f807`)。要点:**三件一体**(参数表 / 观测最小集 / 权限评估,= 清单 §五 第 5 项原文);**唯一代码改动 = `src/net/relay/server.ts` 补 `dial/dialDenied/dialFailed` 判别器计数**(今天 `DIAL` **只有日志行**、脚本无法断言 —— 判别器教训的固化);45% 口径**按"单台中继 `--max-hosts`"填值**(⛔ 不填"全网 45%",实例参数填全网数字不可执行),设值走 47 **新建 drop-in** `capacity.conf` + 硬约束 **`max > used×4`**(防自锁,不满足就不设值);观测落成**一条命令出 PASS/FAIL** 的 `scripts/overlay-probe.cjs`(阈值⛔不许是脚本魔数);**L3 跨机真容灾只评估、不实施,归序 ⑥**(序⑥本就要起 3–5 台 ⇒ 合批明显更省,现在做要多花一台机器/一个公网 IP);**§4.3 待拍板 = 空**。⛔ 不要重做规划。
>
> ⛔ 不要重做序 ② / ③ / ④。
>
> ✅ **上一轮已完成(2026-09-17 09:5x,规划棒)**:**序 ⑥ 的交接单已出** —— `交接单_最小形态真机批次_20260917.md`(工作区根,**321 行**;核对口径 `sed '/^## §10 指纹/,$d' 交接单_最小形态真机批次_20260917.md | md5sum` = `8bea0ac79697b80790a273b54f1db37c`)。要点:**S0–S9 + E1–E12 + 回滚 + §8 回报格式**;五项待测/待校准(打洞率 / 每玩家带宽 / 跨云稳态吞吐 / 中继 jitter / `MEM_PER_HOST_MB`)**逐项写了"怎么测 / 样本多大 / 判据 / 写回参数表哪一行"**;**合批落地 L3 第二中继机(106 升格)**;**回头条件写死**(§5 S7 七件必做)。🔴 **本单新增一条关键事实**:`src/` 全仓**零 UDP / NAT 穿透代码**(`grep -iE "dgram|createSocket|stun|punch|udp" src/` 命中**全是 `signature`/`native` 假阳性**)⇒ ⇒ **打洞项的口径定为"测该网络能不能打洞",⛔ 本单不做打洞实现**(`§4.1-1`);另一条:`RELAY_RTT_W106 = 336 ms` 是**心跳往返**、**不一定是网络 RTT** ⇒ 本单要求做 **ICMP / TCP / relay 三方对比**(`S3(b)`;若它其实是口径问题,所有"跨云不可玩"的结论都要重判)。**§4.3 唯一待你拍板项 = 第 4/5 台真机的来源**(A 先用现有 3 台 / B 你自备设备跑一次性探测 / C 新开云主机;**倾向 A+B,⛔ 执行棒不必等**)。⛔ 不要重做规划。
>
> ✅ **上一轮已完成(2026-09-17 11:0x,执行棒)**:**序 ⑥(3–5 台最小形态)执行部分已收官** —— 五项待测/待校准**全部换成实测**(`HOLE_PUNCH_RATE_LOCAL` 分层实测 2/2 可打洞、`PER_PLAYER_BW_LOCAL` 9.8/3.9 KB/s、`JITTER_LINK_MEASURED` `p95(|ΔRTT|)`=**3 ms 达标**、`WAN_STEADY_THROUGHPUT` 352/12213 KB/s、`MEM_PER_HOST_MB` **2 → 0.06**,R²=0.9424)+ **参数表 §7 待测 4 → 0**;🔴 **S7 回头条件已触发并执行**:`RELAY_MAX_HOSTS` **225 → 7515** 并**重下发** 47/106 两台的 `capacity.conf`(drop-in 重写 ExecStart,绕开"CLI 优先于 env"静默忽略坑);**S8 L3 第二中继机已落地**(106 `dshs-relay` 只绑 `127.0.0.1:20080`,复用既有 443 ⇒ **零新增公网口**,WS 握手 **101**,seeds 末位广播);**收口 12/12 PASS**、监听口 **79 = 基线**、`npm test` 137 pass。**全部证据 = `交接单_最小形态真机批次_20260917.md` §8**(1–8.9 全节;§8.9 可复现指纹:参数表 `db1317c2f7aaef7b47785c1f4fc9de03`、本单前缀 `1edde731eba5034c5f6f3a43864e5a8e`)。⚠️ **唯一未过项 = E9 后半「无失败切流」**(客户端把 relay url 首次解析后**钉死**;worker 不吃引导链;Manager 只在"目录地址变了"时换址)⇒ **见 §8.8-1,已判为"新功能",本轮只报告不动手**。
>
> ✅ **上一轮已完成(2026-09-17 11:1x,规划棒)**:**序 ⑦ 的交接单已出** —— `交接单_中继失败切流_20260917.md`(工作区根,**280 行**;**§8 前缀指纹 `419abf308c00b7668e8898aaa91ba9e8`**,口径 = `sed '/^## §8 回报格式/,$d' … | md5sum`)。要点:**一份切换实现(新增 `src/net/relay/switcher.ts`)+ 三个装配点复用**(C1 Manager 拨号通道 `web/server.ts#refreshOverlay` / C2 worker 实例面 / C3 `relay --client`);🔴 **规划棒新查出的证据级细化**:根因不止"换址条件是目录地址变了",而是 `directory.ts:348 pickFromDoc` **取到第一个候选就 return** ⇒ **候选集退化成单点**(重解析恒返回同一台;S1 就改这里);**阈值已定** = `attempts ≥ 3` **∨** `backoff ≥ 15 s`(D3)+ **排除 + 冷却 300 s**(D5)+ **无候选时不切、绝不静默回退默认机**(D6);**S7 三幕真机演练**(杀 106 / 杀 47 / 两台全杀 —— 幕 3 就是 **R11** 的现场判据);**§4.5 已出 R5 评估:未命中,暴露面零变化(新增监听口 0 / 凭据 0 / 入站 0)**;**§4.3 待拍板 = 空**(第 4/5 台真机来源属序⑥ 遗留同题,⛔ 执行棒不必等)。⛔ 不要重做规划。
>
> ✅ **拍板已闭环(2026-09-17 11:00,用户原话)**:「**1 本机内存大 可以模拟多台**」⇒ **第 4/5 台 = 本机多实例**(本机 47.6 GB / 空闲 27.4 GB / 32 核;relay 单实例 ≈ 48 MB),**放弃**"用户自备设备"与"新开云主机"。⚠️ 局限:多实例共用同一出口 IP ⇒ 对**切流逻辑**够用、对**家宽 / 运营商 NAT 差异**无增量。**已落三处**:`参数表_覆盖网络_20260917.md` §11 / `交接单_最小形态真机批次_20260917.md` §11 / 本单 `交接单_中继失败切流_20260917.md` §11(三处补记**均在各自指纹口径之外** ⇒ 三个指纹均不变)。⛔ **后续任何一棒都不许再上抛这道题**。
>
> ✅ **上一轮已完成(2026-09-17 11:2x–12:2x,执行棒)**:**序 ⑦(中继失败切流)已收官** —— S1–S5 全落(`directory.ts` 拆出 `listCandidatesFromDoc`;**新增 `src/net/relay/switcher.ts` = 唯一实现**,三装配点 C1 `web/server.ts` / C2 `worker/relay-tunnel.ts`+`worker/agent.ts` / C3 `net/relay/main.ts` 复用;`test/relay-failover.test.mjs` F1–F11;`scripts/overlay-failover-drill.cjs` 三幕真机演练);参数表新增 `RELAY_FAILOVER_*` 6 键 + 7 个演练坐标,**§9 第 5 行已改「已闭环」**(指纹 → `24cf2efdbcdcbe61267126ed65dba006`)。**全部证据 = `交接单_中继失败切流_20260917.md` §8**(§8 前缀指纹 `419abf308c00b7668e8898aaa91ba9e8`,收口后**未变**)。真机:`--scene all` = **8 PASS / 0 SKIP / 0 FAIL**;杀 47 ⇒ 切到 106,**三个时延样本 30563 / 27878 / 29176 ms(全部贴 30 s deadline)**;两台全挂 ⇒ **0 行切换 + D6 原生判别器**;`npm test` **149/148/0/1**(+F1–F11)。⚠️ 遗留四条见该单 **§8.8**(**§8.8-4 = 下一棒主题**)。⛔ 不要重做序 ⑦。
>
> ✅ **上一轮已完成(2026-09-17 12:3x,规划棒)**:**序 ⑧ 的交接单已出** —— `交接单_切流冷却语义_20260917.md`(工作区根,**345 行**;**§8 前缀指纹 `aa3a6ec0d66dd81f465cea2a3a08ad27`**,口径 = `sed '/^## §8 回报格式/,$d' … | md5sum`)。**三问已给判定**:① **应当拆开,但拆的是「准入方向」而不是「冷却时长」** —— health 路径允许**一跳豁免**、directory 路径 **⛔ 完全不豁免**(依据 = 11:43:26 真机实测:directory 靠"地址变了"把刚冷却的 47 换回来 ⇒ 给它豁免权 = 复活抖动风暴)② **键仍按 `url`**,新增 `kind ∈ {switched-away, open-failed}` **只决定豁免优先级**(把键换成"原因"会让同一 url 存在多条冷却 ⇒ 抖动抑制失效 = **净退化 R11**;且 `reason` 是自由文本 ⇒ 字符串耦合)③ 🔴 **E5 改判为「幕 4」** —— 本单**已证明**(命题 P-③)在 D5 生产值下"杀 106 时 47 未被冷却"这个窗口**不会被自然产生**(当前在 106 ⟹ 47 必曾进冷却 ⟹ 若未满 300 s 则仍在冷却)⇒ 上单 §8.8-3 的回头条件**指向一条走不通的路**;正解 = `停 47 → 切 106 → 恢复 47 → 停 106(预期 D6,记 SKIP)→ 等冷却过期 → 断言 ≤ deadline 切回 47`(副产品 = §8.8-4 的正面复现)。**另加一条判据级事实**:**构 C(`--url` / `DSHS_RELAY_URL` 钉 106)已判不可用** —— `directory.ts:642-651` 明证 env 显式会让**候选链退化成单点**,杀 106 后仍无候选。**S0–S9 + E1–E13 + 回滚 + §8 回报格式均已就位**;**§4.3 待拍板 = 空**;**§4.5 R5 评估 = 未命中,暴露面零变化**。⛔ 不要重做规划。
>
> ✅ **上一轮已完成(2026-09-17 12:40–13:2x,执行棒)**:**序 ⑧(切流冷却语义)已收官** —— S1–S5 全落:`src/net/relay/switcher.ts` 冷却表结构化(键仍按 `url` + 新增 `kind ∈ {switched-away, open-failed}`)+ `replace(targetUrl, reason, origin)` 闸门**只对 directory 收口**(health 可**一跳豁免**)+ `tick()` 的 **D6 现场**加豁免(新增单点判据 `pickExemptTarget`)+ 豁免有界(每 url 每冷却周期一次,`exemptedAtMs`;豁免也失败 ⇒ 重置冷却 + 本周期不再豁免);`src/web/server.ts` **仅一处**(传 `'directory'`);`scripts/overlay-failover-drill.cjs` 新增 `--scene 4|4b|4c|ctrl`;`test/relay-failover.test.mjs` +**F12–F17**。**全部证据 = `交接单_切流冷却语义_20260917.md` §8**(1–8.9 全节;**§8 前缀指纹 `aa3a6ec0d66dd81f465cea2a3a08ad27`,回填后未变**)。**真机核心成果**:**幕 4(构 A)= 4 PASS / 0 FAIL** —— 豁免在 **19218 ms** 内切回(原文 `|豁免 kind=switched-away 剩 269991ms` = "47 当时确在冷却"的**直接证据**,原因 = 当前通道不健康/health 路径)⇒ **对序⑦ §8.8-4 缺陷正面闭环**(序⑦ 下同场景最长 ~300 s 不切流);**幕 4b = 4 PASS** —— D6 判别器原文(`候选 3 条,排除 3 条`)+ 冷却未过期 **0 行**切换 + 冷却过期后**自然**切回(25125 ms,**无豁免标记**)⇒ 归因收敛到冷却语义;幕 1/2/3 未退化(幕1-A 29586 ms PASS);`npm test` **155/154/0/1**(+F12–F17);部署 47/106 四处 lib,`grep -c RELAY_FAILOVER_EXEMPT <lib>/net/relay/switcher.js` = **2** 自证。⚠️ **遗留六条见该单 §8.8**(🔴 **§8.8-2 = 下一棒主题**:S0 基线样本 **32550 ms > deadline 30000**,回头条件已命中)。⛔ 不要重做序 ⑧。
>
> 🔴 **本单一条判据级新发现(§8.8-4,留档)**:**`RELAY_FAILOVER_COOLDOWN_MS=0` 是"看起来合法、实际会自锁"的配置** —— `num()` 的 `/^\d+$/` 放行 `'0'`,但归零会让**序⑦ F10 的"失败候选必须被排除"**一起失效 ⇒ 候选链卡在第一个失败候选上(实测 **121–123 s 无切换**)。构 B(原定归因构造)因此判**不可用**,已由 **幕 4b** 替代并拿到更强证据。⛔ **任何一棒都不许把 `COOLDOWN_MS=0` 写进回滚路径**。
>
> ✅ **上一轮已完成(2026-09-17 13:40–13:5x,执行棒)**:**序 ⑨(检测时延与 deadline)已收官** —— **P10 分解逐项复现成功**(两样本结构完全一致:检测 **15.085 / 15.119 s** + 首试 0.258 / 1.416 s + 白等 **12.033 / 12.029 s** + 建连 2.821 / 2.724 s)⇒ D1 解锁;**S2 修前 N=5 = 5/5 全超 deadline**(33914 / 31646 / 34078 / 35321 / 41252 ms,中位 34078);**S4 落点 = 抽公共 `waitUpOnStatus`(`src/net/relay/client.ts`)+ 三处装配点全部委托**(C1 `web/server.ts` / C2 `worker/relay-tunnel.ts` / C3 `net/relay/main.ts`,D7 收口),落点自证 47/106 四处 `lib` 各 3 命中;**S5 先红后绿**(改完先对旧 lib 跑 ⇒ `SyntaxError: … does not provide an export named 'openedChannelFailedTerminally'`;build 后 22/22 全绿);**S7 修后 N=5 = 5/5 进 deadline 内侧**(25465 / 25342 / 21985 / 27389 / 23318 ms,中位 25342);**白等 12.03 s → 0.09 s**、总 19.81–23.93 s,早退日志原文 `[relay-failover] ⛔ 新通道终态失败(state=backoff attempts=1 burst=false lastError="transport error")⇒ 提前放弃,不等满 12000ms`;**S8 不退化**:`npm test` **160/159/0/1**(基线 155 +新增 F18–F22)、`--scene all` 11 PASS / 1 FAIL(幕 2-B 已归因)、幕 4-A/B/C 全绿、`overlay-probe` 10/12 与 S0 逐项一致、`ss -lntp|wc -l` = 77、`nft` = 72、门户 200、演练 env 残留 0;**收口归零**(`restart dshs` 把 Manager 归位回 47,probe 回到 10/12)。**全部证据 = `交接单_检测时延与deadline_20260917.md` §8**(1–8.9 全节;**§8 前缀指纹 `d903b4eeabf25ef381379cdbaac77e8a`,回填后未变**;全文件 md5 `ccc28dafd10004d11740834004b02154`,529 行)。参数表指纹 `e6b669c257d8e8964273b3b400238351` → **`99e9e17b0c1ce0550e4bc7626a5a0494`**(新增 `DRILL_SAMPLE_N`=5 / `RELAY_GRACEFUL_BURST_MS`=15000,`DRILL_POLL_MS` 2000→500,§9 新增第 9 行「换址墙钟的四段分解」)。⚠️ **遗留三条见该单 §8.8**(= 下一棒主题)。⛔ 不要重做序 ⑨。
>
> ✅ **序 ⑩ 已收官(2026-09-17 14:23–14:5x)** |🆕 **下一棒已登记自动接续** = automation `3d4dffc0-356e-4bc6-9628-b7d664da7db9`「覆盖网络线-序11规划棒(观测口径重构+在册小缺陷清算)」(**一次性** · `scheduledAt` = **2026-09-17 14:50** = 收口 +5 min · `nextRunAt` = 1789627800000)⇒ 三件技术债全清 —— 幕 2 状态依赖**已修**(幕 2 起手现场重读权威通道归属后**两台都停** ⇒ 与上一幕解耦;`--scene all` **12 PASS / 0 FAIL**)|`COOLDOWN_MS=0` **0 命中**(`4c`/`ctrl` 整体移除并**显式拒绝**,⛔ 不静默空跑)|OBS-11 差额**有名字 = `20000`**(47 实例档、无活跃实例)。证据 = `交接单_检测时延与deadline_20260917.md` **§8.8-4**。
>
> ✅ **序 ⑪ 已收官(2026-09-17 15:0x,规划棒)** —— 出单 = `交接单_观测口径与在册缺陷_20260917.md`(**§8 前缀指纹 `3ece0f870cba67d0113a4c5f9de9d812`**)⇒ **OBS-11 判据 计数 → 白名单集合**(三集包含式 + `nft` 入站 accept 集合)+ 6 条在册小缺陷排序(§5.9);只改观测面,⛔ `src/**` 零改动。⛔ 不要重做规划。
>
> ⏹️ **(存档)序 ⑭ 本轮动作口径(已收官 · 结果见 `交接单_观测口径与在册缺陷_20260917.md` **§10**)**:**序 ⑭ · 执行棒 —— 修掉「relay 流上的 HTTP keep-alive 复用」这条数据面缺陷**(口径 = `交接单_观测口径与在册缺陷_20260917.md` **§9.3**:现象 + **1 条命令的最小复现** + 完整取证链 + 回头条件;⚠️ 不必再出规划单)。**背景(上一棒结论)**:Q1 ✅(`mksess` 改 PG)、Q3 ✅(探针 **12/12**)、**Q2 🔴 未达成** —— 单内记的 `landModels → writeHomeFile` 本地路径问题**不是** `enter` 500 的真因;真因是**同一条连接上的第 2 条 agent 请求必回 `400`(Fastify `clientError`)**,抓包实证 = **worker 侧把 agent 上一份响应回灌给了 agent**。**开工顺序**:① 先按 §9.3 复现;② **补帧级取证**(relay `workerStream`/`dialStream` 路由表、`net/relay/server.ts` 的 stream 表、`dialer.ts` 的 `tcp.pipe(duplex).pipe(tcp)` 收尾、`client.ts#onOpenRequest/onRemoteData`)**定位到代码行再改**;③ 先红后绿;④ 端到端:guest `enter` **200**、同连接连发两次都 **200**、`npm test` 不退化(160/159/0/1)、`--scene all` 不退化、探针保持 **12/12**。**三条硬门**:**D1**(复现类前提未复现前 ⛔ 不许改任何生产值;⛔ 不动 `RELAY_FAILOVER_*`/`HB_SEC`/burst)|**R7**(Q4 `src/net/relay/**` untracked 缺口卡在**提交授权** ⇒ ⛔ 只报告、⛔ 不 commit/push;Q5 `.bak-seq7-*` 残留未动)|🔴 ⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径**。⛔ **若改动必须超出 `src/net/relay/**` + 其测试 ⇒ 停下报告、别扩大**。⛔ 不 commit / 不 push。**收口必须**:释放锁 + 陈述句告知 + 登记下一棒 + 推进本入口 + 写工作区日志。
>
> ✅ **序 ⑮ 已收官(2026-09-17 16:14–16:3x,执行棒)**:**缺陷 B(拨号池未分配槽位自毁)已修并端到端验收** —— 判据 a/b/c 全绿;**先故意对未分配池口 25000 发一条连接**,随后 guest `enter` 仍 **200/200**、该口同连接连发 3 次 **200/200/200**、打后池口仍 **64**;`npm test` **162/161/0/1**、`--scene all` **12 PASS / 0 SKIP / 0 FAIL**、`overlay-probe` **12/12**。改法 = `onConn` ⛔ 不再 `close()` 服务器(只丢连接 + `stray` 计数 + 点名日志)+ `localPortFor`/`lruIdle` 只认 `server.listening` + `status().pool` 改报"实际在听"并新增 `stray`;⛔ 不取"把槽位从 `slots` 摘掉"那条(= 让任意本地连接**永久**蚕食池容量,R11)。**全部证据 = `交接单_观测口径与在册缺陷_20260917.md` §11**(含 47 上 16:00:31/35/40 三条 500 的时间线归因)。⚠️ 顺带确认一条**环境口径**:对 47/106 的 ssh **必须显式 `-p 22`**(别名 `bt-server` 里的 `32022` 是失效残留口,见该单 §11.10)。
>
> ⏹️ **(存档)序 ⑰ 本轮动作口径(已收官 · 结果见 `交接单_在册收尾_20260917.md` §9)**:**序 ⑰ · 执行棒 —— 在册收尾(S1 → S2 → S3 → S4)**(⚠️ **执行棒:按单开工,无须再出规划单**)。**开工依据 = 工作区根 `交接单_在册收尾_20260917.md`**(**§8 前缀指纹 `bab83b7219b2669d5a6e9f1acf782e1f`**,全文 md5 `ca13b9ded3d71268f8b0e56755d7aea4`)⇒ 按该单 §5 顺序:**S1** mksess 文档口径校正(「DB 直插」→「PG 直插」;只改操作性载体 = `02-运维手册.md` + `skills/**` + 本机技能,⛔ **不改 `04-调整方案/**` 档案正文**)|**S2** 技能 `dsh-auto-handoff-chain` **三处同步建立**(本机 ← 文档库新建副本 ← 47 镜像 `/opt/dsh/docs/skills/`;**与 S1 共用一次 scp** ⇒ 故 S1 必先)|**S3** 清理代码仓 `.bak-seq7-*`(🔴 **范围修正 = 5 个**,全 untracked、均为过期快照 ⇒ 非有效回滚点)|**S4** 幕 4-A 余量判据化(三档:`p95 ≤ 24 s` 健康 / `24–27 s` 临界记录不判 FAIL / `> 27 s` 停下报告)+ `--scene 4` × `DRILL_SAMPLE_N`=5 样本。**验收** = 单内 §6 的 E1–E9(其中 E6 三件套 = `npm test` 162/161/0/1、`--scene all` 12 PASS、探针 12/12)。**两条硬门**:**D1**(不改任何生产值)|**R7**(Q4 ⛔ 只报告、⛔ 不 commit/push)。🔴 ⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径**。⛔ 不改 `04-调整方案/**` 档案正文、⛔ 不重做已收官序 ①–⑮、⛔ 不做 presence / 房间层 / 内容分发。**收口必须**:释放锁 + 陈述句告知 + 登记下一棒 + 推进本入口 + 写工作区日志 + 回填该单 §8。
>
> ⏹️ **(存档)序 ⑯ 本轮动作口径(已收官 · 结果见 `交接单_观测口径与在册缺陷_20260917.md` **§12**)**:**序 ⑯ · 规划棒 —— 复核「会合 / 中继从 Manager 拆分」是否仍需要 + 给剩余在册项定序、出执行单**(⚠️ **只出规划、⛔ 不改码、⛔ 不动服务器**)。依据 = 工作区根 `会合中继拆分_取证与改造方案_20260916.md`(S0–S4)+ 事实「**R5 已解掉『Manager 必须与 relay 同机』**(Manager 改为只拨出一条 wss、落点搬到本机回环池 `127.0.0.1:25000+`)」。**只读取证 ≤3 条命令**,结论落成二选一:① 已被 R5 覆盖 ⇒ 判「不做」+ 写清证据;② 仍有未覆盖部分 ⇒ 写成含**逐步验收 / 回滚**的 S 步骤。**同一张单**里给剩余在册项定序:`Q5`(`src/net/relay/*.bak-*` 3 个残留)|`seq13 文档口径`(文档库 / 技能对 `mksess.cjs` 的「DB 直插」描述已过期)|`长期在册`(技能 `dsh-auto-handoff-chain` 在文档库无副本 ⇒ 三处同步从未建立)|`§8.8-2`(幕 4-A 19.5–20.8 s 的临界项)。⛔ **Q4(`src/net/relay/**` untracked 的提交 / 推送授权)不写进执行范围**(待你拍板,只登记一行);⛔ 不得自行为 presence / 房间层 / 内容分发定序(需拍板)。**收口必须**:释锁 + 陈述句告知 + 登记下一棒 + 推进入口 + 写工作区日志 + 追加回报。
>
> ⏹️ **(存档)序 ⑮ 本轮动作口径(已收官 · 结果见 `交接单_观测口径与在册缺陷_20260917.md` §11)**:**序 ⑮ · 执行棒 —— 修掉「拨号池未分配槽位被任意一条连接永久杀死」(缺陷 B)**(口径 = `交接单_观测口径与在册缺陷_20260917.md` **§10.8**:现象 + 出处代码行 `src/net/relay/dialer.ts#onConn` + 三个候选修法 + 判据;⚠️ 不必再出规划单)。**背景(上一棒结论)**:序 ⑭ 已修好 relay keep-alive 复用缺陷(guest `enter` 200、探针 12/12),但**从旁路发现了缺陷 B** —— `onConn` 撞到 `key === undefined`(未分配槽位)时只 `slot.server.close()` + `tcp.destroy()`,**不把槽位从 `slots` 里摘掉** ⇒ 该槽位永久死亡,而 `localPortFor()` 的 `slots.find(s => s.key === undefined)` **下次还会选中它** ⇒ `ECONNREFUSED`(实测:池报"64 个口"而 `ss` 只见 63,缺口正是死掉的 `25000`)。**开工顺序**:① 先**不改代码**复现 + 帧级取证(谁/何时连了未分配槽位;`status().pool` 与实际在听口号数何时起不相等);② **定位到代码行**再改;③ 先红后绿(往 `test/relay.test.mjs` 追加用例,⛔ 不许改 `package.json`);④ 端到端:guest `enter` **200**、同连接连发两次 **200**、`npm test` 不退化(现基线 **160/0/1 skip**)、`--scene all` 不退化(现 **12 PASS**)、`overlay-probe` **12/12**(须带 `--table`)。🔴 **本棒专属陷阱**:⛔ **绝不要对「未分配」的池口发连接**(那正是缺陷 B 的触发器)—— 要打池口,**先用一次真实 `enter` 把落点分配出来**,再从日志 `[relay-dialer] 落点 127.0.0.1:<口> -> <逻辑名>:<端口>` 取**已分配**的那个口号。**三条硬门**:**D1**|**R7**(Q4 untracked 缺口卡在**提交授权** ⇒ ⛔ 只报告、⛔ 不 commit/push)|🔴 ⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径**。⛔ **若改动必须超出 `src/net/relay/**` + 其测试 ⇒ 停下报告、别扩大**。⛔ 不 commit / 不 push。**收口必须**:释放锁 + 陈述句告知 + 登记下一棒 + 推进本入口 + 写工作区日志 + 追加本单 §11。
> ✅ **序 ⑮ 已完成**(automation `1933b18a-0c2a-46a2-829c-440a900fed08` 已执行完毕、入口 §0/§2 已推进)。
> 🏁 **序 ⑰ 已收官(2026-09-17 16:44–17:3x,执行棒)—— 本线「在册收尾四条」全部清干**:**S1 ✅** mksess 文档口径校正(「DB 直插」→「PG 直插」,5 处操作性载体;E1 = 0 行)|**S2 ✅** 技能 `dsh-auto-handoff-chain` **三处同步首次建立**(本机 ↔ 文档库 ↔ 47 镜像,`diff -r` = 0 行、md5 三处同值;S1 的两个技能文件同批上镜像)|**S3 ✅** 代码仓 `.bak-seq7-*` 清理(**5 个 → 0**;untracked、非有效回滚点;`npm run build` RC=0 + lib 产物 md5 零差异)|**S4 ⚠️ 临界档**(`--scene 4` × 5 样本 18772/20121/20246/21223/**24477** ms;p95 保守口径 = **24477 ms** ⇒ 记录入册、⛔ 不判 FAIL、⛔ 未调任何生产值;⛔ 回头条件未触发)。**零回归三件套全绿**:`npm test` **162/161/0/1**、`--scene all` **12 PASS / 0 SKIP / 0 FAIL**、`overlay-probe` **12/12**;收口归零 = 三服务 active + 池口 **64**。**全部证据 = `交接单_在册收尾_20260917.md` §9**(§8 前缀指纹 `bab83b72…` 回填后**未变**;参数表 `8f08e74b…` **未变** ⇒ D1 自证)。
> 🗃 **Q4 已解除**(用户本轮明确授权「执行完毕后 同步到仓库」)⇒ 两个 commit 已推送:**`146c3d2`**(代码/测试面 53 文件)+ **`bc0dd2c`**(文档库面 10 文件);远端 `master` = **`bc0dd2c`**,`git ls-files src/net/relay | wc -l` **0 → 15**(untracked 缺口闭合)。
> 🛑 **本线已无自动接续**:四条在册项全部清干后,**剩余项全部落在「需拍板」区**(presence / 房间层 / 内容分发**定序** = 业务优先级;骨干节点服务范围 = §4 的 A/B 既有待拍板项)⇒ 按登记门禁(`dsh-auto-handoff-chain §3.3`)**不登记下一棒**,等你拍板后再建。
> ⏹️ **(存档)序 ⑱ 本轮动作口径(已收官 · 产物 = 工作区根 `交接单_presence在线态_20260917.md`,§8 前缀指纹 `f612858344077420cff5f1f9ef1c942f`)**:**序 ⑱ · 规划棒 —— presence(在线状态)改造的方案规划**(⚠️ **只出规划**:⛔ 不改任何代码、⛔ 不动 47/106 配置、⛔ 不 commit / 不 push、⛔ 不改任何生产值)。**依据** = `覆盖网络_应用场景与待完善清单_20260916.md` **§五 第 7 步** + `覆盖网络_千台全场景推演_20260916.md` + `覆盖网络_瓶颈落地方案_20260916.md`。**用户拍板** = 「**a 要做**」(2026-09-17 17:1x)⇒ presence 先做。**产出** = 按项目 8 段模板的**执行交接单**。⚠️ **B(内容分发 / 块级内容寻址)仍待拍板 ⇒ ⛔ 本棒不规划、只登记**。✅ **已收官(17:2x)** —— 落点判为**节点 / 端点在线态**(零应用层自证),S0–S7 + E1–E11 + §9 九条回头条件 + 三层回滚已就位;**§4.2 真取舍 = 空、§4.3 待拍板 = 空、§4.4 R5 = 未命中**。⛔ **不要重做规划**。
>
> 🎯 **本轮动作(2026-09-17 17:28 起,唯一一件)**:**序 ⑲ · 执行棒 —— presence(节点在线态)改造落地**(⚠️ **按单开工,⛔ 无须再出规划单**)。**开工依据 = 工作区根 `交接单_presence在线态_20260917.md`**(**§8 前缀指纹 `f612858344077420cff5f1f9ef1c942f`**)⇒ 按该单 §5 顺序:**S0** 只读前置 + 基线采样(§2 的 P1–P5,**零改动**)|**S1** relay 侧 presence 权威表(连接生命周期驱动 + **TTL 安全网** + 同 hostId 设备聚合;**判别器必须落计数**,⛔ 不许只写日志)|**S2+S3** 线协议帧(`SUB/UNSUB/PRESENCE/SNAP`,帧号**末尾追加**)+ **1 s 窗口批合并**|**S4** 消费侧接线(C1 `web/server.ts` / C2 worker,**`/status` 降级为兜底、⛔ 不删**)|**S5** 观测(参数表 `PRESENCE_*` + `OBS-13/14/15` + 探针新项,⛔ 无 `待测` 残留)|**S6** 本机多实例真机验收(N ≥ 4,实测 **稳态 0 帧 / 一次变化 ≤1 帧 / 降幅 ≥ 10×**)|**S7** 收口(部署属 lane 内,**直接做**)。**验收** = 单内 **E1–E11**(零回归三件套 = `npm test` **162/161/0/1**、`--scene all` **12 PASS**、`overlay-probe` **12/12 + 新项**,均**必须带 `--table`**)。**三条硬门**:**D1**(⛔ 不改任何生产值 —— `RELAY_FAILOVER_*` / `HB_SEC` / burst)|**R5**(⛔ 不新增公网监听口 / 不改 nft·nginx;新增监听口·凭据·入站必须仍为 **0**)|**R7**(⛔ 不改 `04-调整方案/**` 档案正文、⛔ 不 commit / 不 push)。🔴 ⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径**。⚠️ **命中该单 §9 的任一条回头条件**(要动生产值 / 要扩大暴露面 / 要超出该单 §3.1 文件集 / 批合并失效 / 观测跑不出单值 / 出现静默放行 / 要求"先有房间层" / 零回归退化)⇒ **立刻停下报告,别扩大**。⛔ 不做内容分发、⛔ 不做房间层、⛔ 不做打洞实现、⛔ 不重做已收官序 ①–⑰。**收口必须**:释放锁 + 陈述句告知 + 登记下一棒 + 推进本入口 + 写工作区日志 + 回填该单 §8。
>
> 📌 **自动接续登记(2026-09-17 17:2x)**:**下一棒 = automation id `107b8e38-ccbd-4dcf-8b49-25dad7c89be9`**(名称「覆盖网络线-序19执行棒-presence节点在线态」,**一次性**,`scheduledAt` = **2026-09-17 17:28** = 收口 +4 min,`nextRunAt` = 1789637280000)⇒ **已登记自动接续,约 4 分钟后自动开新会话;接续点 = 本文件 §2「🎯 本轮动作」**。(已完成的一次性棒:序 ⑱ 规划棒 = `50882a38-07cd-494c-ae73-cbbf27f3604d`;序 ⑰ 执行棒 = `067b0892-964e-4deb-bf84-4f1c37bebea8`。)
>
> 📌 **(历史)自动接续登记(2026-09-17 16:4x)**:**下一棒 = automation id `067b0892-964e-4deb-bf84-4f1c37bebea8`**(名称「覆盖网络线-序17执行棒-在册收尾(mksess口径/技能三处同步/Q5清理/幕4A临界项)」,**一次性**,`scheduledAt` = **2026-09-17 16:44**,`nextRunAt` = 1789634640000)⇒ **已登记自动接续,约 5 分钟后自动开新会话;接续点 = 本文件 §2「🎯 本轮动作」**。(已完成的一次性棒:序 ⑯ 规划棒 = `91c53ef8-c3f0-4bb3-9f0a-943790178176`。)
>
> 📌 **(历史)自动接续登记(2026-09-17 16:3x)**:**下一棒 = automation id `91c53ef8-c3f0-4bb3-9f0a-943790178176`**(名称「覆盖网络线-序16规划棒-会合中继拆分复核与在册收尾定序」,**一次性**,`scheduledAt` = **2026-09-17 16:36**,`nextRunAt` = 1789634160000)⇒ **已登记,约 5 分钟后自动开新会话接续,接续点 = 本文件 §2「🎯 本轮动作」**。
>
> 📌 **(历史)自动接续登记(2026-09-17 16:1x)**:**下一棒 = automation id `1933b18a-0c2a-46a2-829c-440a900fed08`**(名称「覆盖网络线-序15执行棒-拨号池槽位自毁缺陷B修复」,**一次性**,`scheduledAt` = **2026-09-17 16:14** = 收口 +4 min,`nextRunAt` = 1789632840000)⇒ 已登记,会自动开新会话接续。(已完成的一次性棒:序 ⑭ 执行棒 = `3e0a7b01-9257-4b76-b82f-4998c9f7eab0`;序 ⑬ 执行棒 = `a513b25c-9399-4fd5-9a00-a19a04c36d74`。)
>
> ⏹️ **(存档)序 ⑬ 本轮动作口径(已收官 · 结果见 `交接单_观测口径与在册缺陷_20260917.md` §9)**:**序 ⑬ · 执行棒 —— 在册小缺陷清算 `Q1 → Q2 → Q3`**(口径 = `交接单_观测口径与在册缺陷_20260917.md` **§5.9 表** + 本棒 §8.8 转办;**该单已给每条的最小验收与回滚 ⇒ 直接执行、无须再出规划单**)。按序三条:**Q1** `/opt/dshs/mksess*.cjs` 仍写 **SQLite 旧库** ⇒ **已失效**(判据 = 用它开一次临时 session ⇒ 实例面 `curl -w '%{http_code}'` 回 **200/401**,⛔ 非 `000`;用完即删。**它是 R4 规定的唯一实例面验收手段 ⇒ 不修则 Q2/Q3 连"做完了没有"都判不了**)|**Q2** guest(**w-106**) 实例页 **502**(真因 = 平台 `landModels → writeHomeFile` 用**本地路径**写**远端**实例 home;判据 = 同 Q1 的临时 session ⇒ w-106 实例页 **200**)|**Q3** `OBS-09` 实例面 `000`(**环境态非缺陷**:47 无活跃实例 ⇒ 起一个 ⇒ `OBS-09` 绿 **且** 新 `OBS-11` 在"有实例态"下仍绿 = `20000` 落在允许区间)。⚠️ **Q3 与 Q2 同批做最省**(同一临时 session 一次验两件)。**三条硬门**:**D1**(分解类前提未复现前 ⛔ 不许改任何生产值)|**R7**(范围外先报告、不动手 ⇒ **Q4 `src/net/relay/**` untracked 缺口卡在"用户授权"、⛔ 本棒只报告;Q5 `client.ts.bak-*` 残留 = 清理类,待 Q1–Q3 后办**)|🔴 ⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径**(该值看似合法、实际自锁)。⛔ **不 commit / 不 push**(Q4 要授权)。**收口必须**:释放锁 + 陈述句告知 + 登记下一棒 + 推进本入口 + 写工作区日志。
>
> 📌 **自动接续登记(2026-09-17 15:2x)**:**下一棒 = automation id `a513b25c-9399-4fd5-9a00-a19a04c36d74`**(名称「覆盖网络线-序13执行棒-在册小缺陷清算Q1-Q3」,**一次性**,`scheduledAt` = **2026-09-17 15:24** = 收口 +3 min,`nextRunAt` = 1789629840000)⇒ 已登记,会自动开新会话接续。(已完成的一次性棒:序 ⑫ 执行棒 = `52645861-6895-459c-a07e-a5d686787d9c`。)
>
> ~~(存档)序 ⑫ 执行棒口径:**序 ⑫ · 执行棒 —— 观测口径重构(`OBS-11` 计数 → 白名单集合)**。✅ **已收官(2026-09-17 15:3x)**:见上 §0 最新刷新行;产物 = 代码仓 `scripts/overlay-probe.cjs`(新增夹具模式)+ 参数表 §6 四键;`overlay-probe` **11/12**;参数表指纹 `8f08e74b026e6e5b5e1b3db813f031ae`。⛔ 不要重做。~~
>
> ~~(存档)序 ⑪ 规划棒口径:**序 ⑪ · 规划棒 —— 观测口径重构 + 在册小缺陷清算排序**。⚠️ **只出规划、不改码**(规划与执行分离):产出交接单(8 段模板)+ 为下一棒定序。范围**仅限边界内技术项**:① **OBS-11 判据从「计数相等」改为「白名单集合」**(现口径两处结构缺陷:对实例/端点在线态**敏感 ⇒ 假红**;对"一进一出"**替换式变化不敏感 ⇒ 假绿**),连带处理 `LISTEN_COUNT` 的"有/无实例态"两值与 `scripts/overlay-probe.cjs` 改造;② **在册小缺陷清算排序**(`OBS-09` 实例面 000|guest(w-106) 实例页 502|`mksess*.cjs` 失效|`src/net/relay/**` untracked 留档缺口|`client.ts.bak-seq7-*` 残留|参数表 `LISTEN_COUNT`=79 与现态差的 1 条无名口)。⛔ **不得自行为"大方向 / 业务优先级"定序**(presence / 房间层 / 内容分发 = **需用户拍板**,只列候选与优缺点、不决)。➕ **顺手办(同一棒内)**:`.workbuddy/memory/MEMORY.md` 的**真瘦身** —— 现 **8 121 字符 > 注入上限(实测 ≈7 78x)** ⇒ 每轮尾部被截断;把 §二 里"已有 PLAYBOOK 指针"的规则**下沉为一行指针**(体积只减不增)。**✅ 已办(15:0x)= `MEMORY.md` 8 121 → 7 712 字符**。⛔ 不 commit / 不 push。~~
>
> ~~**序 ⑩ · 执行棒(已收官 · 内容留档)**~~:**序 ⑩ · 执行棒 —— 覆盖网络线技术债清算(3 件)**。**直接出执行棒、无须规划棒**:三件均已定位、均属 §1 边界内技术项(实现路径 / 排查方法 / 脚本自洽),**无可上抛项**。三步:**① 演练脚本自洽 —— 幕 2 的状态依赖**(`scripts/overlay-failover-drill.cjs`:注释写"两台全杀"、实现只停 106,而幕 1 已把 Manager 通道切到 47 ⇒ 停的是当前**不用**的那台 ⇒ 120 s 窗口 0 行 `[relay-skip]`;修法 = 幕 2 先判定当前活跃通道再停它,并复验正确前置下 3 PASS);**② 清掉 `COOLDOWN_MS=0` 的 5 处命中**(全部序⑧ 遗留 ⇒ 该值已判「看似合法、实际自锁」⇒ 从演练 / 回滚路径彻底移除,改用合法构造);**③ OBS-11 的 `78 → 77` 那 1 口定位**(两次连采同值、差 1 < 2 未命中回头条件 ⇒ 需把 78 与 77 逐口对账,定位是哪一条消失)。**验收**:`--scene all` 一键全绿(含幕 2)+ `grep -c 'COOLDOWN_MS=0' scripts/` = **0** + OBS-11 差值**有名字**(否则写明"已排查 N 条、卡在哪、回头条件")。**三条硬门**:**D1**(分解类前提未复现前 ⛔ 不许改任何生产值)|**R7**(范围外缺陷先报告、不动手)|🔴 ⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练路径**。⛔ 不 commit / 不 push;⛔ 不做 presence / 房间层 / 内容分发。**收口必须**:释放锁 + 登记下一棒 + 推进本入口 + 写工作区日志。
>
> 📌 **自动接续登记(2026-09-17 14:1x)**:**下一棒 = automation id `be7242dd-ac3e-43d0-b52c-a465683cf972`**(名称「覆盖网络线-序10执行棒-技术债清算」,一次性,`scheduledAt` = **2026-09-17 14:23**)⇒ 已登记,会自动开新会话接续。(已完成的一次性棒:序 ⑨ 执行棒 = `2ca4048b-ab74-4845-8964-47a88910c679`。)
>
> ~~(存档)序 ⑨ 规划棒口径:~~ **序 ⑨ · 执行棒 —— 按 `交接单_检测时延与deadline_20260917.md` 开工**(工作区根;8 段模板;**出单前缀指纹 = `d903b4eeabf25ef381379cdbaac77e8a`**)。**核心结论(规划棒已用现有日志分解完成,执行棒须先复现)**:30 s ≈ **检测 15.0–15.1 s**(= `gracefulBurstMs` 的 15 s 地板,⛔ **不是** 2.5×`HB_SEC`=37.5 s,上单 §8.8-2 的口径提示已在单内勘误)+ **白等 12.0 s**(`waitUpOn` 只轮询 `state==='up'`、**不对终态失败早退** ⇒ 对一个必然失败的同机候选吃满 `upTimeoutMs`)+ **建连 2.7–2.8 s**;两个逐行复核样本(`unhealthyForMs=30563` / `29376`)结构完全一致。**首选修法 = `waitUpOn` 死候选早退**(三处装配点 `server.ts` / `relay-tunnel.ts` / `relay/main.ts` 逐处验收),⛔ **不调 `RELAY_FAILOVER_DEADLINE_MS`、⛔ 不动 `HB_SEC`、⛔ 不动 burst 语义**。**开工第一件事 = 单内 §2-P10 分解复现**(复现不出,或 S2 采到任一 < 27 s 的样本 ⇒ 停下报告)。**OBS-11 已复取 = `77`**(47 无活跃实例档,口径 78;差 1 < 2 ⇒ 未命中 §8.8-3 回头条件;两次连采同值)。**三条硬门**:**D1**(⛔ 分解复现成功前不许改任何 `RELAY_FAILOVER_*` 生产值)|**R7**(范围外缺陷先报告、不动手)|**§8.8-4**(🔴 ⛔ 不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练路径)。⛔ 不 commit / 不 push;⛔ 不做 presence / 房间层 / 内容分发(清单第 7 步)。**收口必须**:释放锁 + 登记下一棒 + 推进本入口 + 写工作区日志。
>
> ~~(存档)序 ⑥ 规划棒口径:**序 ⑤ · 执行棒已收官** —— 证据 = `交接单_参数表与观测_20260917.md` **§8 执行回报**(可复现指纹见该单 **§8.9**;E1–E9 = **8 绿 + 1 部分绿**;唯一部分绿 = E5 的"在 47 上受控制造一次真实拨号",卡点 / 已做到哪一步 / 回头条件见该单 **8.7-①**)⇒ **下一棒 = 序 ⑥ · 3–5 台最小形态**:按 `覆盖网络_应用场景与待完善清单_20260916.md` **§五 第 6 行**开工 —— **先把 `参数表_覆盖网络_20260917.md` §7 的 4 个 `待测` 项 + 1 个待校准推导项换成实机实测**(打洞率 / 每玩家带宽 / 跨云稳态吞吐 / 中继 jitter / `MEM_PER_HOST_MB`),并**合批**落地 **L3 第二中继机**(参数表 §5.2 校验③ 已算出:千台 L3 需求 450 > 单台 `--max-hosts` 225 ⇒ **需 ≥2 台中继**)。⚠️ **回头条件已写死**:`MEM_PER_HOST_MB` 或 `WAN_STEADY_THROUGHPUT` 一换成实测 ⇒ **必须重算 `RELAY_MAX_HOSTS` 并重下发 47 的 `capacity.conf`**。⚠️ 两条待办随本轮带过来:① guest(w-106) 实例页 **502**(`landModels → writeHomeFile` 用**本地路径**写远端实例 home;且 `/opt/dshs/mksess*.cjs` 仍写 SQLite 旧库、**已失效**);② 🔴 **106 agent 面不吃引导链**(`worker/agent.ts` 把 `DSHS_RENDEZVOUS_URL` 直接当 relay URL 用;按字面撤掉 = `tunnel===undefined` 生产回归,**已判不执行**)。⛔ 不重做序 ②/③/④/⑤;⛔ 不做 presence(清单第 7 步);⛔ 不 commit / 不 push。
>
> ~~(存档)原规划棒口径:**序 ⑤ · 规划棒 —— 给「参数表 · 观测 · 权限评估」出一份可执行交接单**(落盘工作区根,命名 `交接单_参数表与观测_20260917.md`,按 8 段模板:目标 / 只读前置 / 范围 / 决策点 / 步骤 S0–Sn / 逐条验收判据 / 回滚 / §8 回报格式)。**必带输入** = `交接单_443兜底_20260917.md` **§8.7 的四条遗留** + 该单 **§8.8 的 45% 口径待填参数位**(relay `--max-hosts` / `/status` 的 `capacity{max,used}` / `retryAfterMs`)+ **L3 跨机真容灾**(命中 **R5**,须先出权限影响评估)。⛔ 本轮**只出单、不改服务器**(规划与执行分离);**不 commit / 不 push / 不做 presence**;收口必须释放锁 + **登记下一棒**。~~
>
> ~~(存档)原规划棒口径:**序 ④ · 规划棒 —— 给「443/TCP 兜底」出一份可执行交接单**(落盘到工作区根,命名 `交接单_443兜底_20260917.md`,按 8 段模板:目标 / 只读前置 / 范围 / 决策点 / 步骤 S0–Sn / 逐条验收判据 / 回滚 / §8 回报格式)。**背景**:现役只有 `wss://alotbuy.com/dshs-relay` 一条路且经 nginx + CF(单点);序 ④ 要补的是**CF / nginx 不可用时的 443/TCP 直连兜底**(异构设计纪律:relay 不按同构 15%,而是 **45% 设计、55% 留余量**)。**取证入口** = `覆盖网络_应用场景与待完善清单 §五`(主线第 ④ 项)+ `覆盖网络_补遗与参考方案_20260916.md` + `覆盖网络_全球架构复盘_20260916.md`。⛔ 本轮**只出单、不改服务器**(规划与执行分离);**不 commit / 不 push / 不做序 ⑤ / 不碰 presence**;收口必须释放锁 + **登记下一棒**。
> 🟢 **最新口径覆盖本节历史内容(23:0x)**:**R2 / R3 / R4 / R5 全部已完成并端到端验收**(→ 交接单 **§9 / §10.4 / §10.5 / §11**)⇒ **机制层 R0–R5 已齐**(**R5 已解掉"Manager 必须与 relay 同机"这条前提**)。
> 🔴 **下一动作已勘误**:⛔ **不是** "presence 改造"。按 `覆盖网络_应用场景与待完善清单 §五` 的主线,presence/房间层是**第 7 步**,前置为 **② 网抽象 + 地址规划 + 引导(P0-1/2/3) → ③ 一机一钥 + 信任根 → ④ 443/TCP 兜底 → ⑤ 参数表 → ⑥ 3–5 台最小形态**。(另:仓库里目前**没有任何应用层代码** —— 全仓唯一含 "presence" 字样的文件是 `src/net/relay/placement.ts` ⇒ "presence 改造"根本无从下手,这条旧指示是上一轮的笔误。)
> ✅ **2026-09-17 07:4x:序 ② 已收官(①②③ 全绿,其执行进度见 `交接单_网抽象与地址规划R6_20260916.md`)** ⇒ 🟢 **2026-09-17 07:5x 已定序:下一棒 = 序 ③(一机一钥 + 信任根)**,之后 ④ / ⑤(理由见 §0)。
> ⛔ **不要再做 R2 / R3 / R4 / R5、不要再开 `32023`、不要再做 SSH 版中继**(隧道凭据与 `32022` 都已收回,那条路已不可能重建)。
> (按顺序 · **2026-09-16 11:5x 刷新**)
1. **先抢锁**:`bash dsh-server-docs/scripts/handoff-guard.sh --claim-exec "<会话名>"` —— 锁已释放,抢到即可开工。
2. **【第一优先 · 真活】会合 / 中继从 Manager 拆分的落地**(方案:工作区根 `会合中继拆分_取证与改造方案_20260916.md`,S0–S4 分步 + 每步验收 / 回滚)
- ✅ **P1 / P2 / P3 已完成并验收(2026-09-16 15:5x–16:2x)** —— 全部实测证据在 **交接单 §8**(含命令原文级输出)。摘要:
· **P1** 47 对齐到 `640813e`(Manager 侧真补上 S0+S1)|多用户验收:`admin`(w-47) 与 `guest`(w-106) 实例页均 200、跨机文件面落盘已核实
· **P2** `dsh_hosts.via` 列(迁移 v8)|回填 `w-47=local` / `w-106=manager-ssh`|`hostsProvider` 走 `RendezvousRegistry`|`GET /api/admin/hosts` 已可见 `via`
· **P3** **实例端口区间隔离**(`w-47` 20000+ / `w-106` 21000+,避开 OS 临时段)⇒ 修掉「跨 worker 同号 ⇒ 47 的 `127.0.0.1` 撞号 ⇒ `tunnel.forward()` 返回值被忽略 ⇒ **静默拨到别人实例**」这条真 bug;两实例实测落 20000 / 21000 且互不重叠
- ✅ **P4(SSH 版中继 / 新开 32023)已判「不做」**(17:2x)—— SSH 反向隧道**不是"不安全"**(专用非特权账号 + `PermitOpen` + 限速是业界标准做法),但**不该长期做数据面**;它想要的两样(与宝塔 sshd 解耦、甩掉 root 凭据)在 relay 方案里**同样拿到** ⇒ 两步合一。⛔ **不要新开 32023、不要再做 SSH 版中继**。
- ✅ **R0/R1/R1.5/R2 已完成**(R0 判据表+链路画像、R1 relay 最小闭环、R1.5 韧性+准入选点、**R2 常驻+nginx 443 暴露**)⇒ 全部证据在方案 **§9–§14**。
- ✅ **R3 + R4 已完成并端到端验收(2026-09-16 21:37–22:2x)** —— R3=`RendezvousRegistry` 已接线 `RelayRendezvous`(含 relay 实时快照 + 15 s 陈旧回退)+ 106 常驻 + `w-106 via='relay'`;R4=实例面经 relay(`PORT_ADD/DEL/ACK` + `RelayTunnel` + 端点翻译)+ **sshd 反向隧道下线且不可能重建**(47 收回隧道公钥、`32022` 回收;106 摘 `DSHS_TUNNEL_*`、私钥移走)⇒ 47 公网暴露面**净减 1 口**。证据 = 交接单 **§10.5**(含一份"静默失效"缺陷的定位与修复 + 先红后绿回归测试)。
- ✅ **R5 = 会合可换机 已完成并端到端验收(2026-09-16 22:3x–23:0x)** —— Manager 改为 `dialer: true` **只拨出**一条 wss,落点在**自己本机**预绑池(`127.0.0.1:25000..26099`,64 口);新增 `DIAL(0x0e)/DIAL_ACK(0x0f)` 帧、`StreamPeer` 抽象(数据面同形)、relay 侧 `DSHS_RELAY_DIALERS=manager` 白名单 + 每机独立密钥(权限只收窄)。证据 = 交接单 **§11**:① 单测 **38/38**;② E2E `endpoints: [(19000, 41233, 0)]`(**relay 回环口 streams=0**)+ `DIAL manager -> w-106:21000 ok`(实例面)+ 门户 200/62451 B;③ 🔴 **换机等价实验**(清掉 `DSHS_RELAY_STATUS_URL` ⇒ Manager 对 relay 回环口一无所知)**仍全通**,两个回环端点 `streams` **全为 0**;④ 新增 64 口**全部仅回环**,公网零新增。
- ⏳ **下一步 = 主线第 ② 项「网抽象 + 地址规划 + 引导」**(`覆盖网络_应用场景与待完善清单 §五`)—— ⛔ **不是 presence**(那是第 7 步)。⛔ 不要再做 R0/R2/R3/R4/R5。
🟢 **阶段 ①(S0–S4 + R5)已完工;`清单 §11.6` 记下的两条缺陷(A1 假 404 / A2 锁信号)也已修完并端到端验收**(→ R2-R4 单子 **§12**)。
🟢 **2026-09-17 07:4x:② 的 Step 3(P0-3 名字解析与授权)也已完成并端到端验收 ⇒ ② 全单关闭(①②③ 全绿)**(证据 = ② 单的「执行进度」段)。⇒ **下一棒不再看 ② 的单子**,按 `清单 §五` 主线往下走(序 ③ 一机一钥 + 信任根 / 序 ④ 443/TCP 兜底 / 序 ⑤ 参数表·观测·权限评估 —— **开工顺序已于 07:5x 定为 ③ → ④ → ⑤**,理由见 §0)。⛔ 不要再重做 Step 1 / Step 2 / Step 3。
- ✅ **A1 已修并端到端验收(2026-09-16 23:2x–23:4x)** —— `RemoteUserFs.target()` 原在 `agentFor(hostId)` 查不到时**静默回退** `DSHS_CLUSTER_AGENT_URL`(= w-47 本地 agent)⇒ 每次 `restart dshs` 的窗口期内 w-106 用户回**假 `404 not_found`**(与“文件夹不存在”同形,误导排查)。**真因** = 文件面 `server.ts:262` 的 `hostDirectory` 是惰性 Map、此前**只被 spawner** 刷新 ⇒ 启动时表里只有本机。**修法** = `ensureHost` **按需补齐**(治本)+新增码 `host_unresolved` → **503 且零请求发默认机**(治安全)。**证据** = R2-R4 单子 **§12**(先红后绿 + 冷启动 `launch 200` + relay 有 `DIAL` + `not_found=0`)。判别器仍是:**relay 有没有 `DIAL`**。
- ~~原 R0 描述~~(已过期,仅存档):只看 `接续包_覆盖网络线_20260916.md`(它 17:29 已同步):**R0-a** 把六条判据(认证模型 / 默认姿态 / 零入站 / 爆炸半径 / 可观测 / 生态)补成**可打分表**;**R0-b** 测链路画像(本机↔47↔106 的 RTT / jitter / 带宽)。**R1 起才选 relay 实现**(候选 OpenZiti / Nebula 身份型优先 > rathole > frp)。⛔ **本轮不引入任何第三方 relay**。
- 🔴 **本 §2 的口径截至 2026-09-16 17:29**(17:2x 起 P4 改判、17:3x 撤销"首选 frp")。⚠️ **接续包每次更新后,必须回来同步本入口** —— 16:43–17:29 期间本入口曾滞后 41 分钟,导致 17:04 那位自动接续会话按旧口径得出「P4 **并入自研 relay**」的结论,与最新定案(**实现未定、先做 R0**)**表述不一致**(方向一致、粒度不同)。⛔ 别再让"下一棒的第一信息源"比接续包旧。
- ⏳ **未做**:git commit(未授权)、档案占号建档(建议与 P4 合并做,届时才有 commit hash 可写)。
3. **平台那批待办** ✅ **已清**:`BRIEF.md` 刷新至 09-16|T08 单子已归档 `交接单/archive/交接单-已完成/`|`.doing-T08` 已随 T08 归档(内容保留为 `T08-执行标记-已释放.md`)|`04-调整方案/.lock-*` 16 个空占号残留已清|未提交改动已按 5 个定向 commit 提交(HEAD `59f1a89`,**已 push**,远端 `master` 一致)。
⏳ **仍未动**:`INDEX.md §二` 的既有失真(`04-90~04-102` 共 13 行物理落在表格外 ⇒ `docs-index-stats.py` 统计不到)—— 搬别人的行有风险,留待专项。
4. **10 份文档转正式档案** ✅ **已完成**(档案 103–112,`4e3a1a4`;正文行覆盖率 98.5–99.0%)。
5. **`MEMORY.md` 精简** ✅ **已完成**(工作区 -26%;**真被截断的是用户级那份**,已按「分层重排、零删除」修好)。
6. **技术侧开工**:按 `覆盖网络_瓶颈落地方案_20260916.md` 的「只做三件事」(presence 改造 + 游戏服放 L1 + 块级内容寻址)开工;**前置仍是第 2 条**。
7. **代码分层 / 依赖方向**(与覆盖网络并行的一条线)—— ✅ **P0 已完成**(2026-09-16 13:2x,本机):新增 `docs/architecture.md`(四层 `①入口 → ②领域 → ③能力 → ④基础` + **R1–R4 判据** + 三条工程纪律)+ `README.md` 文档表登记。
- 📄 评估文档 = 工作区根 `项目代码_分层范式与迭代风险评估_20260916.md`(**§8 已勘误**)。
- 🔴 **勘误(勿再复用旧结论)**:所谓「**3 处基础层反向依赖**」**不成立** —— 那几处是**入口层 `cli.ts`** 的合法依赖(`①→③`);实测 `src/*.ts` 里从子目录 import 的**只有 `cli.ts`**,基础层四文件**零 import** ⇒ **真实违规 0 处**。
- ✅ **可执行判据已补(同日)**:`scripts/check-layering.mjs`(`npm run check:layering`,已挂进 `npm run verify`)—— 静态扫描四层 + **ratchet 基线**(`scripts/layering-baseline.json`,新增违规 ⇒ 退出码 1;只许减)。**首跑实测 60 个 .ts**:`①24 / ②0 / ③32 / ④4`,**现存违规 5 条**(全是 `③能力→①入口`:`supervisor/proxy.ts`→`web/auth.ts`+`web/middleware/authn.ts`;`fs/{local,remote}-user-fs.ts`+`fs/workspace.ts`→`web/middleware/fs-guard.ts`)。
- ⏳ **剩三件(均命中 R7,动工前先出受影响清单)**:① **整改上面那 5 条**(修法已写在 `docs/architecture.md §5.1`)② 补 `src/domain/*`(把 `web/routes` 的业务规则抽出来 —— **行为敏感重构**,非纯类型)③ 给模块入口补「职责 / 依赖谁 / 被谁依赖」。
- **顺序建议**:**P0 ✅ → 覆盖网络 S1–S4 → P1**(P1 不阻塞覆盖网络)。
8. ✅ **两条「已定位未修」缺陷 —— 均已于 2026-09-16 23:4x 修完并端到端验收**(证据 = R2-R4 单子 **§12**):
- **① `state.py` 锁误报(安全信号)**:`state.py:39` 原把 `交接单/.exec-lock` 当**文件**读,而 `--claim-exec` 建的是**目录**(内含 `OWNER`)⇒ 永远报「[锁] 空闲 ✅ 可以动手」⇒ **每个新会话读到的第一个信号是错的**。**已修**:是目录就读 `LOCK/OWNER`(兼容遗留的普通文件形态);复验 = 持锁时正确报「🔴 被占用」。
- **② 假 404 / 静默回退到错 agent**:见上方第 2 条末段(`src/fs/remote-user-fs.ts` 的 `target()`) —— **已修**。
> ✅ **上一轮已完成(2026-09-17 17:28–18:16,执行棒)**:**序 ⑲(presence 节点在线态)已收官并上线** —— 详见 §0 最新那条刷新的全部数据;**产物 = `交接单_presence在线态_20260917.md` §8★ 回报(8.0–8.9 已回填)**。一句话:**在线态从"每 5 s 拉 `/status`"改为"连接生命周期 + 订阅推送 + 1 s 批合并 + TTL 安全网"**,真机 `counters={subs 1, pushed 3, snaps 2}`、单测 **169/0/1**、`--scene all` **12 PASS / 0 FAIL**、S6 本机多实例 **8/8**。⛔ 不要重做序 ⑲。
>
> 🎯 **序 ⑳ 的动作已定(本棒登记)**:① **修 `OBS-09` 口径** —— 「实例不在册 ⇒ **SKIP**(⛔ 不是 PASS、也不是假红);在册 ⇒ 必须可达 `PROBE_CODE_SET`」+ 两侧夹具实证;② **量化 presence 真机收益**(`counters.statusHits` 增速 ⇒ `/status` 轮询降幅,给算式与实测);③ presence 线**收口复验**(`npm test` / `--scene all --table` / `overlay-probe --table`);④ 处置 `web/server.ts` `translateEndpoint` 的**键口径既存缺陷**(`hostVia` 表键 = 逻辑名、传入裸 `hostId` ⇒ 恒 `undefined`)。
> ➡️ **下一棒 = 序 ⑳(执行棒)**,automation **`51cfd12f-918d-4069-81d8-c148fc174282`**(一次性 · **2026-09-17 18:20** = 收口 +4 min)。⛔ 未登记更后一棒:**`OBS-09` 的判据口径修正属技术实现 ⇒ 本棒自决**,但「**平台是否要在 Manager 重启后自动拉起既有实例**」是**业务取向**(可用性 vs 资源/复杂度)⇒ **待拍板,⛔ 不作为任何一棒的前置**(详见本棒回报的最后一节)。
---
## 3. 关键决定(**已定,勿再上抛**)
1. **方案只做技术实现;跨境数据合规由使用者自行考虑**(用户原话)⇒ 选型不为合规让路、跨境不设限、⛔ 不再把合规当前置条件。
2. **单机自用 ≠ 不需要互联**(用户原话:「单机用也要互联,这正是建立覆盖网络的目的」)⇒ **租户维度收窄 ≠ 网络维度收窄**。
3. **覆盖网络按异构设计**:中继按 **45% 设计 / 55% 留余量**(不是同构假设的 15%);**必须补 443/TCP 兜底**;有公网 IP 的节点升格为中继候选。
4. **权威状态单点**:归属/租约/骨干资格只能控制面写;会合/中继可多实例。
5. **形态已拍板**:单机自用为默认;"当服务器给他人用"的多人维度已放弃。
6. **游戏重点是 MMORPG(2D/2.5D)/ MUD / 传奇类** ⇒ 判定上修为"最匹配",非 3D 强实时竞技。
## 4. 待拍板(唯一一项,真取舍)
**骨干节点的服务范围**:A 只服务自己名下设备(优点:无资源/计费纠纷、权限面不变;缺点:骨干少、冗余低)| B 服务全网(优点:骨干多、可用性高、中继容量易满足;缺点:他人流量跑在你的机器上、元数据暴露、被攻破影响面大)。
**倾向 A→B 渐进**。详见 `覆盖网络_骨干层方案_20260916.md` §7。
---
## 5. 回滚点
- **R5(本轮)三层回滚,均秒级**(详见交接单 §11.7):
① **Manager 拨号通道** = 删 manager drop-in 里 `DSHS_RELAY_DIAL_*` 5 行 → `daemon-reload` → `restart dshs`(`addressOf` 自动回退到 R3 的 `/status` 快照路径,"relay 同机"假设回来);
② **relay 侧白名单** = 删 `dshs-relay.service.d/dialers.conf`(不删也行:无拨号方接入 ⇒ 行为同 R3);
③ **代码级** = revert `src/net/relay/*` + `src/config.ts` + `src/web/server.ts` → `npm run build` → scp `lib/` → `restart dshs`。
⚠️ 备份:`/etc/dshs/relay-keys.json.bak-r5-20260916-224838`、`/etc/systemd/system/dshs.service.d/cluster.conf.bak-r5-20260916-224838`(及 `.bak-r5swap-20260916-225546`)。
- **缺陷 A1/A2(本轮)回滚**(详见交接单 §12.5):A1 = `cp -a /opt/dshs/lib-bak-a1-20260916-232914/lib/. /opt/dshs/lib/` → `systemctl restart dshs`(回到"假 404"复现);A2 = 还原 `state.py` 第 1 节为"按文件读"(**不建议**)。
⚠️ **A1 的备份目录只有这一份**(推送过程中滚动过两次,同名文件以后者为准,两次都在同一个目录里)。
- **有意保留、⛔ 别当"没做完"**:R5 新增的 `127.0.0.1:25000..25063` **仅回环**(公网零新增);relay 仍为端口绑动态回环口(`exposeLoopback` **有意未关**,理由见交接单 §11.5)。
- 文档侧:10 份为**新增**;被修改的 3 份(客户端化部署方案 v3 / 桌面客户端方案 v1 / 可行性评估)改动均为**追加或标注**,可反向还原。
- 服务器侧待复核事实:控制面机规格(1.8 GB / 2 核 是 09-08 旧记录);47↔106 跨云实测 **~22 KB/s**。
> 📌 交接单指纹(2026-09-16 23:4x):`tail -n +4 交接单_relay落地R2-R4_20260916.md | md5sum` = `b110b5c4e3eb8afcac08af537d333c99`(**本单已执行完毕**:R2→§9、R3 Step 0→§10.4、R3 本体+R4→§10.5、**R5→§11**、**缺陷 A1/A2→§12**)
> 📌 **② 的交接单**(**已执行完毕**):`交接单_网抽象与地址规划R6_20260916.md`,指纹 `0d3f1f305e9e6219afa97cdfecff05ba`。
> 🟢 **2026-09-17 07:4x 更新(② 收官)**:**Step 1(P0-1 网抽象)/ Step 2(P0-2 引导三级链)/ Step 3(P0-3 名字解析与授权)全部落地并端到端验收** ——
> 证据见该单的「**执行进度**」段(含判据命令与实测输出)。**② 单关闭 ⇒ 它不再是下一棒的开工依据**(指纹留着只用于"文档有没有被改过")。
> ⛔ **不要重做 Step 1 / Step 2 / Step 3**。下一棒按 `清单 §五` 往下:序 ③ 一机一钥 + 信任根(P0-4)/ 序 ④ 443/TCP 兜底 / 序 ⑤ 参数表·观测·权限评估
> —— **开工顺序已于 2026-09-17 07:5x 定为 序③ → 序④ → 序⑤**(理由见 §0)。