回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复: - 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录) - tmp/(32.4 M,按接续棒命名的过程临时区) - .workbuddy/tmp/(39.5 M) - 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物) - tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留 入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与 接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、 .workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。 排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、 打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
766 lines
77 KiB
Markdown
766 lines
77 KiB
Markdown
# 交接单 · presence(在线状态)改造
|
||
|
||
> **序 ⑱(规划棒)产物** | 2026-09-17 | 状态:**待执行**(= 下一棒 序 ⑲ 执行棒)
|
||
> **性质**:**只出规划** —— ⛔ 本棒未改任何代码、⛔ 未动 47/106 配置、⛔ 未 commit / push、⛔ 未改任何生产值。
|
||
> **依据**:`接续入口_覆盖网络线_20260916.md` §2「🎯 本轮动作」|`覆盖网络_应用场景与待完善清单_20260916.md` §五 第 7 步 + §二 / §四|`覆盖网络_瓶颈落地方案_20260916.md` **§1** + 总览|`覆盖网络_千台全场景推演_20260916.md` 瓶颈排序 / 流量预算总表
|
||
> **用户拍板** = 「**a 要做**」(2026-09-17 17:1x)⇒ presence 先做。⚠️ **B(内容分发 / 块级内容寻址)仍待拍板 ⇒ 本单只登记、⛔ 不规划**(见 §3.3-1)。
|
||
> **口径**:本单 **§8 前缀指纹** = `sed '/^## §8 回报格式/,$d' 交接单_presence在线态_20260917.md | md5sum`(⛔ 不是全文件 md5)。
|
||
|
||
---
|
||
|
||
## §0 摘要
|
||
|
||
**一句话**:presence 的第一刀必须落在**节点 / 端点在线态**(overlay node presence)上 —— 因为**全仓零应用层**(`room|chat` 命中 **0**,`presence` 字样**只出现在一处注释里**),而"房间层 presence"(1000 人大房那套订阅裁剪 / 抽样降频)**没有载体、无从下手**;节点层是它的**真前置**,且**今天就落在真实代码面上**:Manager 现在的在线态来源是**拉 relay `/status` 快照 + 15 s 陈旧回退**,这正是要替换的那条"轮询"路径。
|
||
|
||
**做法**(照抄 `瓶颈落地方案 §1` 的七条,逐条映射到本仓):
|
||
①presence 绑**连接生命周期**(WS 连上 = 在线、断开 = 离线)|②**本地批合并**(固定 1 s 窗口)|③**订阅式扇出**(只推给订阅了该 host 的 Manager / Worker)|④**批量事件**(一帧带 host 数组)|⑤**grace 10 s + 离线 debounce 30 s**|⑥**同 hostId 多连接按 device 聚合**|⑦**最终一致 5–15 s**(⛔ 不上强一致),外加 **TTL 安全网**(防 relay 崩溃漏事件)。
|
||
|
||
### §0.1 复核证据(本棒实测,3 条命令族)
|
||
|
||
| # | 命题 | 命令 | 实测输出 | 判定 |
|
||
|---|---|---|---|---|
|
||
| P1 | 全仓**零应用层**(房间 / 对话) | `grep -rilE "\b(room|chat)\b" src \| wc -l` | **0** | ✅ 成立 |
|
||
| P2 | `presence` 字样**只在注释里** | `grep -ril presence src` | **仅 `src/net/relay/placement.ts`**;该文件 **第 21 行**原文:`而我们的第一瓶颈是 **presence(在线态)**,所以速度略重(传输方案 §12 的实测依据)` | ✅ 成立(**零实现**) |
|
||
| P3 | 现役在线态来源 = **拉快照** | `grep -n "req.url === '/status'" src/net/relay/server.ts` ⇒ **413**;`grep -n "为什么必须真的把条目从 endpoints 里删掉" src/net/relay/server.ts` ⇒ **1234** | 1234 行原文写明:**「`/status` 是 Manager …」** ⇒ Manager 消费的是 relay 的 `/status` 快照 | ✅ 成立(**即"轮询"反模式**) |
|
||
|
||
> `src/net/relay/` 现存 15 个模块:`addr-override / client / dialer / directory / duplex / identity / index / keys / main / network / placement / rendezvous / server / switcher / wire` ⇒ **presence 的落点就是这一层**(不是新起一层)。
|
||
|
||
### §0.2 与入口旧结论的对账
|
||
|
||
- 入口 §2 曾记(2026-09-16):「仓库里目前**没有任何应用层代码** ⇒ "presence 改造"**无从下手**,这条旧指示是上一轮的笔误」。**本棒的处置**:该结论**前提仍然成立**(P1/P2 实测),但**结论要收窄** —— 不是"presence 无从下手",而是"**应用层 presence 无从下手**、**节点 presence 正当时**"(序 ①–⑰ 已把它的全部前置做完:网抽象+引导 ②、一机一钥+信任根 ③、443 兜底 ④、参数表+观测 ⑤、3–5 台最小形态 ⑥、失败切流+冷却语义 ⑦⑧、检测时延 ⑨)。
|
||
- 入口 §2 另有:「⛔ 未为 presence / 房间层 / 内容分发**定序**(只登记候选与优缺点)」+ 序 ⑰ 执行棒的「登记门禁」。⇒ **本棒已拿到拍板**(用户「a 要做」),门禁解除,**presence 已由"候选"转为"在册执行项"**。
|
||
|
||
### §0.3 步骤定序(理由)
|
||
|
||
**S0 基线 → S1 机制(relay 侧表)→ S2 线协议帧 → S3 批合并 → S4 消费侧 → S5 观测 → S6 真机验收 → S7 收口**。
|
||
|
||
- S1 必须最先落:**机制是唯一真活**,S2/S3 都只是它的出口形式;先红后绿在 S1 就能建起来。
|
||
- S2 与 S3 **同一批改**(帧与批合并同属"出口形式",拆开要跑两遍 build / scp)。
|
||
- S4 消费侧放 S3 之后:**只有出口定了才谈接线**,否则接线要返工(序⑭ 的教训:消费者先动 = 方向反)。
|
||
- S5 观测紧贴 S6:**没有阈值就不许跑真机**(否则"降幅多少算过"无法判定,= 序⑤ 的立单理由)。
|
||
- S6 是唯一动服务的一步,**置末**;S7 收口含部署(**属 lane 内执行细节,直接做、⛔ 不上抛**)。
|
||
|
||
### §0.4 范围外登记(⛔ 本单不规划)
|
||
|
||
| # | 项 | 为什么不在本单 |
|
||
|---|---|---|
|
||
| 1 | **B 内容分发 / 块级内容寻址** | **仍待用户拍板**(复杂度已评估为「中」:块级切分与哈希标识 + 内容寻址存储与客户端校验 + 同网段 peer 发现 + 内容源优先级链;验收判据单值可测「回源字节数 ≈ 1 份 × 组数」)⇒ **只登记一行**,⛔ 不写步骤、不定序、不留键位 |
|
||
| 2 | **房间层 presence 专属做法** | 百分比抽样降频 / idle / typing / 1000 人大房批量 bulk:**应用层专属**,且应用层不存在(P1)⇒ 只登记;⛔ 参数表**不留空键位**(口径要可执行) |
|
||
| 3 | 游戏服放 L1(三件事之二) | 属"部署规范 + 容量",与 presence 无耦合 ⇒ 另立 |
|
||
| 4 | `src/worker/tunnel.ts`(SSH 隧道,**生产死路径**)是否删 | 序 ⑯ 已判**独立决策** ⇒ 本单不处置 |
|
||
|
||
---
|
||
|
||
## §1 目标(一句话,可判定"做完了没有")
|
||
|
||
> 把覆盖网络的**节点 / 端点在线态**从「Manager 拉 relay `/status` 快照 + 15 s 陈旧回退」改为「**连接生命周期驱动 + 订阅式推送 + 本地批合并 + TTL 安全网**」;**完成判据 = 稳态 presence 帧数为 0、一次上下线变化 ≤1 帧、相对旧路径的帧数降幅 ≥ 1 个数量级(本机多实例实测 N≥4)、`/status` 兜底仍可用、且 `RELAY_FAILOVER_*` / `HB_SEC` / burst 一字未动(D1 自证)**。
|
||
|
||
---
|
||
|
||
## §2 只读前置(执行前**必须**先核实的 5 条;给命令与期望输出,⛔ 不许靠推断)
|
||
|
||
| # | 要核实的事实 | 命令(cwd = 代码仓 `D:/github/dsh_shenxian`) | 期望输出 |
|
||
|---|---|---|---|
|
||
| **P1** | 全仓**零应用层** ⇒ 本单只做节点 presence | `grep -rilE "\b(room|chat)\b" src \| wc -l` | **0**(≠0 ⇒ 先用 `head` 看是谁,再决定是否调整 §3 范围) |
|
||
| **P2** | presence **零实现** | `grep -ril presence src` ;`sed -n '21p' src/net/relay/placement.ts` | 仅 `placement.ts`;第 21 行 = **注释**(含「第一瓶颈是 presence」) |
|
||
| **P3** | 现役在线态 = **拉快照**(要替换的那条路) | `grep -n "req.url === '/status'" src/net/relay/server.ts` | `413`(行号以实测为准,⛔ 别写死) |
|
||
| **P4** | **D1 基线快照**(改前拍一次,收口时逐项比对) | 参数表 `sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md \| md5sum` ;并在参数表内 `grep -n "RELAY_FAILOVER_\|HB_SEC\|GRACEFUL_BURST"` | 参数表指纹(**序⑰ 收口值 = `8f08e74b026e6e5b5e1b3db813f031ae`**,取数时间 2026-09-17 17:2x)+ 6 个 `RELAY_FAILOVER_*` 与 `HB_SEC` / burst 现值逐行留档 |
|
||
| **P5** | 收口基线(零回归三件套的**改前值**) | 代码仓 `npm test`(**Node 22**:`npm.cmd test`)/ `node scripts/overlay-failover-drill.cjs --scene all --table "<工作区根>/参数表_覆盖网络_20260917.md"` / `node scripts/overlay-probe.cjs --table "<工作区根>/参数表_覆盖网络_20260917.md"` | `npm test` **162/161/0/1**、`--scene all` **12 PASS / 0 SKIP / 0 FAIL**、`overlay-probe` **12/12**(取值以实测为准,**必须带时间戳**) |
|
||
|
||
> ⚠️ P4 / P5 的数字**必须带"取数时间 + 复核命令"**,⛔ 不写死绝对值(`交接单/README.md §二` 的坑 2:写死的数字会被并行改动打穿)。
|
||
> ⚠️ 对 47 / 106 的任何命令:**ssh 一律显式 `-p 22`**(别名 `bt-server` 的 `32022` 是**失效残留口**);取证输出一律 `2>/dev/null`(PQ 告警会污染计数判据)。
|
||
|
||
---
|
||
|
||
## §3 范围
|
||
|
||
### 3.1 要改的(预计 8 个文件;**以实测为准**,多一个都要按 §9-3 停下报告)
|
||
|
||
| 层 | 文件 | 改什么 |
|
||
|---|---|---|
|
||
| 机制(主体) | `src/net/relay/server.ts` | presence 权威表(`hostId → {state, connId, deviceCount, lastSeenMs, endpoints[]}`)+ 订阅表 + 推送出口 + **TTL 安全网** |
|
||
| 机制 | `src/net/relay/wire.ts` | 新增帧:`SUB` / `UNSUB` / `PRESENCE` / `SNAP`(**帧号在既有分配表末尾追加**,⛔ 不改既有帧号语义) |
|
||
| 出口 | `src/net/relay/dialer.ts` 或 `client.ts` | 批合并调度(1 s 窗口)落点(S3 时定,二选一,⛔ 不两处都改) |
|
||
| 消费 | `src/net/relay/rendezvous.ts`(`RelayRendezvous`) | 订阅为主路径、`/status` **降级为兜底 / 对账**(⛔ 不删) |
|
||
| 消费 | `src/web/server.ts` | **C1 装配点**:`refreshOverlay` 一带改为订阅驱动(去掉"只能靠拉"的假设) |
|
||
| 消费 | `src/worker/relay-tunnel.ts` / `src/worker/agent.ts` | **C2 装配点**:同族接线(若实测无需改 ⇒ 在 §8 写明"已核对、无需改",⛔ 别为对称而改) |
|
||
| 测试 | `test/relay.test.mjs`(+可选新增 `test/relay-presence.test.mjs`) | 先红后绿用例;⛔ **不许改 `package.json` 的测试入口语义** |
|
||
| 观测 | `参数表_覆盖网络_20260917.md` + `scripts/overlay-probe.cjs` | 新增 `PRESENCE_*` 键(**全部有实测值,⛔ 无 `待测` 残留**)+ `OBS-13/14/15` + 探针新项 |
|
||
|
||
### 3.2 明确**不动**的(⛔ 防止顺手扩大)
|
||
|
||
1. ⛔ **不动任何生产值**:`RELAY_FAILOVER_*`(6 键)/ `HB_SEC` / burst 语义 —— 这是 **D1**,也是本线长期硬门。
|
||
2. ⛔ **不删 `/status`** —— 它是回滚链的一环(§7-②)。
|
||
3. ⛔ **不新增公网监听口**、⛔ 不改 `nft`、⛔ 不改 nginx(本单是纯应用层改动,**暴露面零变化** ⇒ §4.4 R5 未命中)。
|
||
4. ⛔ **不改 `src/net/relay/switcher.ts` 的冷却语义** —— presence 抖动抑制**独立成表**(§4.1-D7)。
|
||
5. ⛔ 不碰房间层 / 内容分发 / 打洞实现 / `src/worker/tunnel.ts`(死路径)。
|
||
6. ⛔ 不动 `04-调整方案/**` 档案正文(历史档案属性)。
|
||
7. ⛔ **不 commit / 不 push**(未获明确授权;序 ⑰ 那次授权是**当次一次性**的)。
|
||
8. 🔴 ⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径**(该值看似合法、实际自锁:归零会让"失败候选必须被排除"一起失效,实测 121–123 s 不切换)。
|
||
|
||
### 3.3 范围外登记(同 §0.4;执行棒**只登记、⛔ 不动手**)
|
||
|
||
1. **B 内容分发 / 块级内容寻址** —— 待拍板。
|
||
2. **房间层 presence 专属做法**(抽样降频 / idle / typing / bulk)—— 无载体。
|
||
3. 骨干节点服务范围(A 自用 / B 全网)—— 入口 §4 既有待拍板项,与 presence 无关。
|
||
|
||
---
|
||
|
||
## §4 决策点
|
||
|
||
### 4.1 已定项(**本棒自决,可推翻** —— 依据均为客观可判)
|
||
|
||
| # | 决策 | 值 | 依据 |
|
||
|---|---|---|---|
|
||
| **D1** | **落点层次** | **节点 / 端点 presence**(`src/net/relay/**`);⛔ 应用层 presence 不做 | §0.1 P1/P2 实测(零 `room|chat`、`presence` 零实现)+ 入口 §2 既有结论 + `清单 §五` 第 7 步把房间层整块后置 |
|
||
| **D2** | **权威归属** | **在线态权威 = relay 侧**(连接生命周期所在层);Manager 只**消费 + 缓存**,⛔ 不自己轮询推导(避免双权威) | ⚠️ **与 §3-4「权威状态单点」不冲突**:那条说的是**归属 / 租约 / 骨干资格**;presence 是**连接事实**,天然归属连接所在层。**租约仍只归 Manager 写** |
|
||
| **D3** | **一致性口径** | **最终一致**,允许 **5–15 s** 陈旧;**grace 10 s + 离线 debounce 30 s + TTL 安全网**(TTL 取 `HB_SEC` 现值的 3 倍,⛔ **不改 `HB_SEC`**) | `瓶颈落地方案 §1` 第 5/7 条;TTL 是"防网关崩溃漏事件"的安全网(第 1 条) |
|
||
| **D4** | **批合并 = 固定 1 s 窗口**(方案 A) | 每网关把本地变化攒起来,**每 1 s 一帧 pipeline 提交** | `瓶颈落地方案 §1` 第 2/4 条(业界量级对照:2 万条命令/秒 → 1 次 pipeline/秒)。**为什么不取"零延迟逐次推"**:presence 允许 5–15 s 陈旧 ⇒ 1 s 窗口完全够用,逐次推只会在抖动时制造帧爆炸(**正是要消灭的现象**) |
|
||
| **D5** | **`/status` 保留但降级** | 从"主路径"降为**兜底 / 对账**(保留代码与 nginx 放行,⛔ 不删) | 回滚链依赖它(§7-②);且它现在是 Manager 的唯一事实来源,一步切干净 = 单点失败 |
|
||
| **D6** | **订阅可见性** | 默认**只允许订阅同网络、且本机有权**的 host(沿用序③ 的逻辑名 `<net>/<hostId>` 与成员资格校验);**跨网络订阅 ⇒ 显式拒绝 + 拒绝计数 +1**,⛔ 不静默返空 | 权限只收窄(R5 方向);**静默返空 = 假绿**,本线已有判别器教训 |
|
||
| **D7** | **抖动抑制独立成表** | presence 的 grace / debounce **自成一张表**,⛔ **不复用** `switcher.ts` 的冷却表 | 两条语义不同("拨号失败"vs"在线态抖动"),混表会污染序⑦/⑧ 的冷却判据 |
|
||
|
||
### 4.2 真取舍(本单 = **空**)
|
||
|
||
三个曾考虑上抛的项(落点层次 / 批合并策略 / `/status` 去留)**均有客观判据可分出高下**(见 D1 / D4 / D5 的"依据"列)⇒ 按 §1 判据**自行拍掉、⛔ 不上抛**。
|
||
|
||
### 4.3 待拍板 = **空**
|
||
|
||
- presence 本身**已拍板**(用户「a 要做」)。
|
||
- **B(内容分发)不属本单**:它是**独立一项**、仍待拍板 ⇒ 本单只登记一行(§3.3-1),⛔ 不写成"待你拍板"塞进本节(避免把别人那题捆进来)。
|
||
|
||
### 4.4 权限影响评估(R5,**先评估后动手**)
|
||
|
||
| 维度 | 变化 | 结论 |
|
||
|---|---|---|
|
||
| 新增监听口 | **0**(帧走既有 wss 通道) | — |
|
||
| 新增凭据 | **0**(复用序③ 的节点密钥 / 成员资格) | — |
|
||
| 新增入站 | **0**(Manager / Worker **仍然只拨出**) | — |
|
||
| 可见面 | 订阅可见性**只收窄**(D6 同网络 + 有权 + 跨网络显式拒绝) | — |
|
||
|
||
> ⇒ **未命中 R5**,无需权限评估门禁,可直接开工。
|
||
|
||
### 4.5 与既有机制的对账(⛔ 不重复劳动、不重复造)
|
||
|
||
| 既有机制 | 与本单关系 |
|
||
|---|---|
|
||
| 序③ 身份 / 成员资格(`identityRequired`、`<net>/<hostId>`) | **复用**:订阅鉴权走它,⛔ 不新造一套 |
|
||
| R3 `RendezvousRegistry` / `RelayRendezvous`(relay 实时快照 + **15 s 陈旧回退**) | **被替换主路径**(保留为兜底):15 s 陈旧回退正是 D3 口径的现存实现,改造后由订阅推送取代 |
|
||
| R5 `DIAL / DIAL_ACK`(Manager 只拨出 + 本机回环池) | **不改**:presence 帧搭同一条通道,⛔ 不动拨号模型 |
|
||
| 序⑦ `directory.ts#listCandidatesFromDoc` / 序⑧ `switcher.ts`(冷却 + 一跳豁免) | **不改**:那是**地址选择**,与在线态是两件事(D7) |
|
||
| 序⑨ `waitUpOnStatus` + 终态早退(deadline 四段分解) | **不改**:那是**换址墙钟**;presence 缩短的是"Manager 多久知道谁在线",⛔ 别混为一谈 |
|
||
| 序⑤ 参数表 `OBS-01..12` + `scripts/overlay-probe.cjs` | **扩展**:新增 `OBS-13/14/15`(沿用"阈值⛔ 不许是脚本魔数"的纪律) |
|
||
| 序⑥ `RELAY_MAX_HOSTS` / `capacity.conf` | **不改**:帧数在 presence 口径下与容量无关 |
|
||
|
||
---
|
||
|
||
## §5 步骤 S0–S7(每步自带**一次可执行的验证**)
|
||
|
||
> ⚠️ **开工前**:先抢全局执行锁 `bash dsh-server-docs/scripts/handoff-guard.sh --claim-exec "<会话名>"`;抢不到 = 有会话在跑 ⇒ 停手(⛔ 不排队等)。
|
||
> ⚠️ **本单是执行棒,按单开工,⛔ 无须再出规划单**。
|
||
> ⚠️ **部署属 lane 内执行细节**(开发环境服务器,R8)⇒ 直接做,**动手前一句话说明在动什么**即可,⛔ 不上抛。
|
||
|
||
### S0 · 只读前置 + 基线采样(**零改动**)
|
||
逐条跑 §2 的 P1–P5 并**留档原文**;把 P4 的生产值快照与 P5 的三件套基线存到 `_中间产物_待清理/seq19/`。
|
||
**验证**:§2 五行输出全部与"期望输出"一致;若有偏差 ⇒ ⛔ 先停下报告,别改码。
|
||
|
||
### S1 · relay 侧 presence 权威表(**唯一真活**)
|
||
在 `src/net/relay/server.ts` 落 presence 表:**`open` ⇒ online;`close` ⇒ offline(先过 grace 10 s + debounce 30 s)**;**TTL 安全网**按 D3 兜底;同 `hostId` 多连接**按 device 聚合**(任一在线 = 在线)。
|
||
**判别器(必做,序⑤ 的教训)**:**不许只写日志** —— 必须有可断言计数 `subs` / `pushed` / `rejected`,并以 `status()` 暴露。
|
||
**验证(先红后绿)**:往 `test/relay.test.mjs` 追加用例 —— ①断开后 ≤ grace 仍 online、> grace+debounce 转 offline;②同 hostId 两连接断一条仍 online;③kill 掉通道后 TTL 内自愈。**先对旧 `lib` 跑一次拿红**,再改。
|
||
|
||
### S2 · 线协议帧(`wire.ts`)
|
||
新增 `SUB` / `UNSUB` / `PRESENCE` / `SNAP`:**帧号在既有分配表末尾追加**,⛔ 不改既有帧号语义(改了 = 与 47/106 上现役 `lib` 不兼容,热更窗口会断)。
|
||
**验证**:帧号唯一性用例(既有号集合 ∩ 新号 = ∅)+ 旧 lib 对新帧"**未知帧号 ⇒ 显式报错**"(⛔ 不静默丢弃)。
|
||
|
||
### S3 · 批合并 + 节流(1 s 窗口,D4)
|
||
每个网关把 1 s 内的变化**合并成一帧**(一帧带 host 数组);`SNAP` 供重连后**一次拿全量**(做法 ⑦:重连必须重拉)。
|
||
**验证**:单测断言 —— **稳态 0 帧**;**一次变化 ≤1 帧**;100 次连续变化(<1 s 内)合并后 **= 1 帧**。
|
||
|
||
### S4 · 消费侧接线(C1 / C2)
|
||
`RelayRendezvous` 改为**订阅为主路径**,`/status` 降为兜底;**C1** `src/web/server.ts`(`refreshOverlay` 一带)、**C2** `src/worker/relay-tunnel.ts` / `src/worker/agent.ts` 同族接线(**实测无需改就写明,⛔ 不为对称而改**)。
|
||
**验证**:单测 —— 订阅生效后**不再发起 `/status` 轮询**(判别器 = `/status` 命中计数稳态 = 0);断掉订阅 ⇒ **自动回退 `/status`** 仍能拿到在线态。
|
||
|
||
### S5 · 观测落地(参数表 + 探针)
|
||
参数表新增:`PRESENCE_BATCH_MS` / `PRESENCE_GRACE_MS` / `PRESENCE_OFFLINE_DEBOUNCE_MS` / `PRESENCE_TTL_MS` / `PRESENCE_SUB_MAX`(**每个键都要有实测值,⛔ 无 `待测` 残留**)+ `OBS-13`(presence 帧率:稳态 0)`OBS-14`(首帧即全量 `SNAP`,⛔ 无 N+1)`OBS-15`(状态陈旧 p95 ≤ 15 s);`scripts/overlay-probe.cjs` 增同名新项。
|
||
**验证**:`node scripts/overlay-probe.cjs --table "<工作区根>/参数表_覆盖网络_20260917.md"` ⇒ 新项**能判 PASS 也能判 FAIL**(⛔ 只做过 PASS 的路径 = 未验证,序⑫ 的夹具模式可复用:**假红 / 假绿两侧都要能抓**)。
|
||
|
||
### S6 · 本机多实例真机验收(**唯一动服务的一步**)
|
||
用**已拍板的本机多实例**(用户原话「本机内存大 可以模拟多台」)起 **N ≥ 4** 个节点,实测:
|
||
① 稳态 presence 帧数 = **0**;② 一次上下线 = **≤1 帧**;③ 与旧路径(按 T 秒拉 `/status`)**帧数降幅 ≥ 1 个数量级**;④ 首帧即全量;⑤ 跨网络订阅 ⇒ **显式拒绝**且计数 +1。
|
||
**验证**:五项逐条给"命令 + 实测数字 + 判定";跑完**必须归零**(`systemctl restart dshs` 把 Manager 归位回 47)并复跑探针。
|
||
|
||
### S7 · 收口(部署 + 三件套 + 回填 + 指纹)
|
||
部署 = `npm run build`(Node 22)→ `scp` 产物到 47 `/opt/dshs/lib/` 与 106 `/opt/dshs-cluster/lib/`(relay 真身另在 `/opt/dsh-relay/lib/`,**三处都要铺**)→ `systemctl restart dshs`(+ 需要时 `dshs-relay`)——**属 lane 内,直接做**;随后跑零回归三件套 + 回填 §8 + 给指纹 + 推进 `接续入口_覆盖网络线_20260916.md` §0/§2 + 写工作区日志。
|
||
**验证**:§6 的 E9/E10/E11 全绿;`overlay-probe` **12/12 + 新项 PASS**;池口 **64**;三服务 `active`。
|
||
|
||
---
|
||
|
||
## §6 验收判据 E1–E11(命令 + 期望输出,**可被第三方复现**)
|
||
|
||
| # | 判据 | 命令 / 观测点 | 期望 |
|
||
|---|---|---|---|
|
||
| **E1** | **稳态 0 帧**(变化驱动,无变化不推) | 单测 + 真机 N≥4 静置 60 s | presence 帧数 = **0**(⛔ 非 0 即未达标) |
|
||
| **E2** | **一次变化 ≤1 帧**(批合并生效) | 单测:1 s 内 100 次变化 | 帧数 = **1** |
|
||
| **E3** | **降幅 ≥ 1 个数量级**(对旧路径) | 真机:旧口径 `N` 节点 × `/T` 秒拉 → 新口径变化驱动 | 降幅 **≥ 10×**(写明 N / T / 实测算式) |
|
||
| **E4** | **首帧即全量**(⛔ 无 N+1) | 订阅后第一帧 | = `SNAP`(**1 帧**完成,⛔ 非逐 host 拉取) |
|
||
| **E5** | **生命周期正确性** | 断连 ⇒ ≤ 10 s 仍 online、> 40 s 转 offline;`kill -9` relay ⇒ TTL 内自愈 | 三条全绿(**三个阈值均来自 D3,⛔ 不许改口径去凑判据**) |
|
||
| **E6** | **多设备聚合** | 同 hostId 两连接断一条 | 仍 **online** |
|
||
| **E7** | **最终一致**(⛔ 不上强一致) | 代码面检查 | **无**跨节点同步 RPC / 无分布式锁 |
|
||
| **E8** | **权限只收窄 + 显式拒绝**(判别器) | 跨网络订阅请求 | **显式拒绝** + `rejected` 计数 **+1**(⛔ 不静默返空) |
|
||
| **E9** | **零回归三件套** | `npm test`(Node 22)/ `--scene all`(**必须带 `--table`**)/ `overlay-probe`(**必须带 `--table`**) | ≥ **162/161/0/1** + **12 PASS / 0 SKIP / 0 FAIL** + **12/12 + 新项 PASS** |
|
||
| **E10** | **参数表口径可执行** | `grep -c "待测" 参数表_覆盖网络_20260917.md` + 新增键逐项 | `PRESENCE_*` **全部有实测值**;**指纹变化必须写明新值**(口径 = `sed '/^## §10 指纹/,$d' … \| md5sum`) |
|
||
| **E11** | **D1 自证** | 与 §2-P4 的快照逐项比对 | `RELAY_FAILOVER_*` / `HB_SEC` / burst **一字未变**;`grep -c 'COOLDOWN_MS=0' scripts/` = **0** |
|
||
|
||
---
|
||
|
||
## §7 回滚(三层,**均秒级**;⛔ 不含任何 `COOLDOWN_MS=0`)
|
||
|
||
① **代码级**(主回滚):`git` 恢复本单改动的文件(或从 `.bak-seq19-<YYYYMMDD-HHMMSS>` 副本还原)→ `npm run build`(Node 22)→ `scp` 铺回 47 / 106 / relay 三处 `lib` → `systemctl restart dshs`(+ `dshs-relay`)。**判据 = 探针回 12/12 + `--scene all` 12 PASS**。
|
||
② **消费侧回退**(不重启服务即可):删掉 S4 的订阅装配点 ⇒ `RelayRendezvous` **自动回退**到既有的「relay 实时快照 + 15 s 陈旧回退」路径(**D5 保留 `/status` 就是为了这条**)。**判据 = `/status` 命中计数恢复非零 + 在线态仍正确**。
|
||
③ **观测侧**:参数表新增键**保留但标注"未启用"**(⛔ 不删行 —— 删了会让 `OBS-13/14/15` 悬空)。
|
||
|
||
> ⚠️ 备份命名一律 `.bak-seq19-<YYYYMMDD-HHMMSS>`,落**原位同目录**;relay 侧另存 `/etc/dshs/relay-keys.json.bak-seq19-*`(若本轮未动密钥则**不备份**)。
|
||
> ⛔ **回滚路径里出现 `RELAY_FAILOVER_COOLDOWN_MS=0` = 违规**(该值自锁,实测 121–123 s 不切换)。
|
||
|
||
---
|
||
|
||
## §8 回报格式(执行会话**必须**回填的证据,逐条给"命令 + 原文输出 + 判定")
|
||
|
||
```
|
||
### 8.0 回报(序 ⑲ 执行棒 · <开始时刻>–<收口时刻>)
|
||
|
||
### 8.1 只读前置复现
|
||
P1–P5 命令 + 原文输出(P4/P5 带取数时间);与 §2「期望输出」逐条对账(一致 / 偏差及处置)
|
||
|
||
### 8.2 S1–S7 逐步回报
|
||
每步:改了什么(文件 + 行级要点)/验证命令 / 原文输出 / 判定(✅ / ⚠️ 临界 / ❌)
|
||
⛔ 不许写"已完成"三字了事;**每步必须有一次可执行验证的原文**
|
||
|
||
### 8.3 验收 E1–E11 对账表
|
||
| # | 判据 | 命令 | 实测 | 判定 |
|
||
(❌ 项必须写:卡在哪 / 已做到哪一步 / 什么条件一出现必须回头)
|
||
|
||
### 8.4 真机降幅实测(E3)
|
||
N = ? / 旧口径算式的取数 / 新口径实测帧数 / 降幅 = ?
|
||
|
||
### 8.5 D1 自证
|
||
`RELAY_FAILOVER_*`(6) / `HB_SEC` / burst 改前 vs 改后逐值对比;`grep -c 'COOLDOWN_MS=0' scripts/` 实测值
|
||
|
||
### 8.6 部署与收口
|
||
build RC / scp 三处路径与产物 md5 / 重启了哪些单元 / 归零后三件套复跑值 / 池口数
|
||
|
||
### 8.7 参数表变更
|
||
新增键与实测值;**指纹:旧 → 新**(口径 = `sed '/^## §10 指纹/,$d' … | md5sum`)
|
||
|
||
### 8.8 遗留与回头条件
|
||
逐条列出;**若某条命中 §9 的回头条件 ⇒ 必须写明"命中第 N 条"并停下报告**
|
||
|
||
### 8.9 本单指纹(可复现)
|
||
`sed '/^## §8 回报格式/,$d' 交接单_presence在线态_20260917.md | md5sum` = ?(回填后**应保持不变**:§8 之后的内容不影响前缀)
|
||
```
|
||
|
||
---
|
||
|
||
## §8★ 回报(序⑲ 执行棒 · **已回填** · 2026-09-17 17:28–18:16)
|
||
|
||
> 本节 = 按上面 §8 的格式回填的**实测证据**(原文 + 命令)。所有输出取自本棒实跑,⛔ 无一条为推断。
|
||
|
||
### 8.0 基本信息
|
||
|
||
| 项 | 值 |
|
||
|---|---|
|
||
| 会话名 | `覆盖网络线-序19执行棒-presence`(全局执行锁:抢到 → 收口时释放) |
|
||
| 起止 | 17:28 → 18:16(含 S6 真机 2 分钟 + `--scene all` 6 分钟) |
|
||
| 改动文件 | 6 个 `src` + 1 个测试 + 1 个探针 + 参数表(⛔ 未 commit · 未 push) |
|
||
| 边界自证 | ⛔ 未改任何生产值 · ⛔ 未新增公网监听口 · ⛔ 未改 nft/nginx · 🔴 全程未出现 `COOLDOWN_MS=0` |
|
||
|
||
### 8.1 只读前置复现(S0)
|
||
|
||
| # | 命令 | 实测原文 | 与 §2 期望 |
|
||
|---|---|---|---|
|
||
| P1 | `grep -rilE "\b(room\|chat)\b" src \| wc -l` | `0` | 一致(零应用层) |
|
||
| P2 | `grep -ril presence src` | `src/net/relay/placement.ts`(仅注释) | 一致(只读缓存待替换已成事实) |
|
||
| P3 | `grep -n "req.url === '/status'" src/net/relay/server.ts` | `413`(`head -2` 证输出非空) | 一致 |
|
||
| P4 | `sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md \| md5sum` | `8f08e74b026e6e5b5e1b3db813f031ae` | 一致 ⇒ **D1 自证成立**(生产值一字未改) |
|
||
| P5 | `npm.cmd test` / `overlay-probe --table` / `--scene all --table` | `161 pass / 0 fail / 1 skip` / `12/12 PASS` / `12 PASS / 0 FAIL` | 一致(基线) |
|
||
|
||
**P4 生产值快照(逐值留档)**:`HB_SEC=15`(`server.ts:60`) · `HALF_OPEN_MS=37500` · `RELAY_FAILOVER_MIN_ATTEMPTS=3` · `GRACE_MS=15000` · `COOLDOWN_MS=300000` · `DEADLINE_MS=30000` · `CHECK_MS=2000` · `UP_TIMEOUT_MS=12000` · `GRACEFUL_BURST_MS=15000` · `EXEMPT=1`;`grep -c 'COOLDOWN_MS=0' scripts/` = **0**。
|
||
|
||
### 8.2 S1–S7 逐步回报
|
||
|
||
| 步 | 改了什么(文件 + 行级要点) | 验证命令 | 实测原文 | 判定 |
|
||
|---|---|---|---|---|
|
||
| **S1** | `server.ts`:`PresenceState`(`conns: Map<connId,lastActMs>` 逐连接判死)+ `presenceTouch/presenceDrop/presenceDevices/presenceEntry/publishPresence/flushPresence/presenceSnapshot` + 计数 `subs/pushed/snaps/rejected/statusHits` | `node --test --test-name-pattern="T(25\|29\|31) " test/relay.test.mjs` | 先红 `0 pass / 7 fail` → 后绿 **7/7** | ✅ |
|
||
| **S2** | `wire.ts`:**末尾追加** `SUB 0x10 / UNSUB 0x11 / PRESENCE 0x12 / SNAP 0x13`(既有 0x01–0x0f 语义一字未动) | T26(帧号唯一 + 未知帧号 0x99 必须显式拒绝) | `close 1008` + `authFailed 0→1` | ✅ |
|
||
| **S3** | `server.ts`:`presenceBatchMs` 批合并出口(1 s 窗口、一帧带数组)、`SNAP` 首帧全量、TTL 安全网逐连接摘除 | T27(6 台同窗口 ⇒ 1 帧 6 条)/T29(幽灵连接 TTL 自愈) | `Δ帧=1 条目=6` / 离线自动收口 | ✅ |
|
||
| **S4** | `client.ts`(订阅侧:`subscribePresence/presenceStatus/presenceOf/…`,失败 ≥2 次 ⇒ `unsupported` 停止再试)+ `rendezvous.ts`(`presence` 为**主路径**,`undefined` 才回落 `online`)+ `web/server.ts`(装配订阅 + `refreshRelay` 挂起 + `addressOf` ② 订阅落点) | T30(订阅新鲜 ⛔ 不读兜底;`undefined` 必须回退;两台 relay 之间**无**同步) | `calls.length = 0 / 0 / 1`;`s2.presence.length = 0` | ✅ |
|
||
| **S5** | 参数表 §3.6 新增 `PRESENCE_*` 五键 + §6 新增 `OBS-13/14/15` + 四阈值键;`overlay-probe.cjs` 新增三条**真判据**(含 `--status-fixture-2` 双快照) | 夹具五连跑(good + bad13/14/15/16) | good:三条全 `PASS`;bad13 `Δpushed=5 ⇒ FAIL`;bad14 `snaps=0 ⇒ FAIL`;bad15 `p95=60000 ⇒ FAIL`;bad16 口径漂移 ⇒ FAIL | ✅ |
|
||
| **S6** | 无改动(只跑现场) | `_tmp_seq19_s6.mjs`(**生产默认时序零注入**,N=6 本机多实例) | **8/8 ✅**(明细见 §8.4) | ✅ |
|
||
| **S7** | `npm run build` → `scp` 四处 `lib/` → 重启 4 个单元 | 见 §8.6 | build `RC=0`;四处 md5 同值;`dshs/dshs-relay/dshs-worker/dshs-pg` 全 `active` | ✅(OBS-09 除外,见 §8.8-1) |
|
||
|
||
**S1 的"先红后绿"原文**:`git stash push -m seq19-red -- <6 个 src 文件>` → 重建 `lib` → 跑 7 个新用例 = `0 pass / 7 fail`(`RED_RC=1`);`git stash pop` + 定点 LF 还原 → 重建 → **7/7 pass**。
|
||
|
||
### 8.3 验收 E1–E11 对账表
|
||
|
||
| # | 判据 | 命令 / 观测点 | 实测 | 判定 |
|
||
|---|---|---|---|---|
|
||
| **E1** | 稳态 0 帧 | 单测 T25/真机 S6 静置 60 s/探针 `OBS-13` | `Δ帧=0`(期间 24 个心跳帧在流、`lastSeenAgoMs≈101ms`)/真机 `Δpushed=0` | ✅ |
|
||
| **E2** | 一次变化 ≤1 帧 | T25/T27/S6 ② | `Δ帧=1`(上线、下线、6 台同窗口各 1 帧) | ✅ |
|
||
| **E3** | 降幅 ≥10× | S6 ③(算式见 §8.4) | 字面口径 **63×**;同消费者口径 10.5×;稳态 ∞ | ✅ |
|
||
| **E4** | 首帧即全量 | T25/S6 ④/真机 `/status` | `snapFrames=1`;`snaps 0→1`;真机 `snaps=2 = 订阅次数` | ✅ |
|
||
| **E5** | 生命周期正确性 | T25/T29/S6 ②a | 断连→改口 **40985 ms**(grace 10 s+debounce 30 s);TTL 幽灵自愈 | ✅ |
|
||
| **E6** | 多设备聚合 | T25(连接更替) | 顶旧接新期间 `online=true` 且 `devices=1`、**零新增帧** | ✅ |
|
||
| **E7** | 最终一致(无跨节点同步) | T30 尾段(两台 relay 独立) | `s1.presence=1 / s2.presence=0` | ✅ |
|
||
| **E8** | 权限只收窄 + 显式拒绝 | T28/S6 ⑤ | 客户端 `rejected=1`、服务端 `rejected 0→1`、状态回 `idle` | ✅ |
|
||
| **E9** | 零回归三件套 | `npm test` / `--scene all` / `overlay-probe` | **169/0/1**(≥162/161 ✅);`--scene all` = 见 §8.8-4;探针 **15 PASS / 1 FAIL** | ⚠️ **1 红**(`OBS-09`,见 §8.8-1) |
|
||
| **E10** | 参数表口径可执行 | `grep -c 待测` + 新键逐项 | `PRESENCE_*` **全为实测值**(⛔ 无 `待测`);指纹 `8f08…→ 13de5f9b77c486d71e5b83ec909b17b2` | ✅ |
|
||
| **E11** | D1 自证 | 与 §2-P4 快照逐项比对 + `grep -c 'COOLDOWN_MS=0' scripts/` | 六项生产值**一字未变**;禁用串计数 **0** | ✅ |
|
||
|
||
### 8.4 真机降幅实测(E3)
|
||
|
||
**现场**:本机多实例,`N = 6` 台 worker + 1 台订阅方,relay 用**生产默认时序**(零注入),窗口 `W = 104 s`(含一次下线 + 一次上线)。
|
||
**旧口径取数**:`src/web/server.ts:76 RELAY_STATUS_POLL_MS = 5000` ⇒ 同一消费者每 5 s 拉 1 次。
|
||
- 字面口径(交接单 §6 E3 原文「`N` 节点 × `/T` 秒拉」):`6 × (104 / 5) = 126` 次 → 新口径实测 **2 帧** ⇒ **63.0×**
|
||
- 同消费者口径(同一台 Manager 轮询):`104 / 5 = 21` 次 → 2 帧 ⇒ **10.5×**
|
||
- 纯稳态(60 s、零变化):旧 `12` 次 vs 新 **0 帧** ⇒ **∞**
|
||
**判定**:✅(≥10×)。⚠️ 临界说明:新口径的**下限**是"变化次数 × 1 帧"(与 N **无关**),而旧口径与 N 成正比 ⇒ N 越大降幅越大、变化越稀疏降幅越大;本窗口把"变化"人为压到 104 s 内两次(极端档)仍达 10.5×。
|
||
|
||
### 8.5 D1 自证
|
||
|
||
| 值 | 改前 | 改后 |
|
||
|---|---|---|
|
||
| `HB_SEC` | 15 | 15 |
|
||
| `HALF_OPEN_MS` | 37500 | 37500 |
|
||
| `GRACE_MS` | 15000 | 15000 |
|
||
| `COOLDOWN_MS` | 300000 | 300000 |
|
||
| `DEADLINE_MS` | 30000 | 30000 |
|
||
| `GRACEFUL_BURST_MS` | 15000 | 15000 |
|
||
| `RELAY_FAILOVER_EXEMPT` | 1 | 1 |
|
||
| `grep -c 'COOLDOWN_MS=0' scripts/` | 0 | **0** |
|
||
|
||
### 8.6 部署与收口
|
||
|
||
- `npm run build`(Node 22)= **RC 0**;`npx tsc --noEmit` = **RC 0**。
|
||
- 铺码 **四处**(产物 md5,改后值):`server.js = 182a7645f8ad3e8f240737b9dab91028`,`index/wire/client/rendezvous.js = 2a688424… / 467d38bd… / 46a025ff… / e851667d…`,`web/server.js = a360c7f4…` —— 47 `/opt/dshs/lib/net/relay/` + 47 `/opt/dsh-relay/lib/net/relay/` + 106 `/opt/dshs-cluster/lib/net/relay/` + 106 `/opt/dsh-relay/lib/net/relay/`(另 47 `/opt/dshs/lib/web/server.js`)⇒ **远端 md5 与本机逐条相同**。
|
||
- 重启单元:47 `dshs-relay`→`dshs-worker`→`dshs`;106 `dshs-relay`→`dshs-worker` ⇒ `dshs / dshs-relay / dshs-worker / dshs-pg` **全 active**。
|
||
- 归零复跑三件套:`npm test` **169/0/1**;`overlay-probe --table` = **15 PASS / 1 FAIL**(唯一红项 `OBS-09`);池口 **64**(`DIALER_POOL`,`ss` 实测 25000–25063 全绑);`--scene all` = 见 §8.8-4。
|
||
- 真机新路径证据(47 relay `/status`):`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)`。
|
||
|
||
### 8.7 参数表变更
|
||
|
||
新增键(**全部实测/可复算,⛔ 无 `待测`**):`PRESENCE_BATCH_MS=1000` · `PRESENCE_GRACE_MS=10000` · `PRESENCE_OFFLINE_DEBOUNCE_MS=30000` · `PRESENCE_TTL_MS=45000` · `PRESENCE_SUB_MAX=0` · `PRESENCE_STEADY_FRAMES_MAX=0` · `PRESENCE_SAMPLE_HITS_MIN=1` · `PRESENCE_SAMPLE_GAP_MS=3000` · `PRESENCE_STALE_P95_MAX_MS=15000`;新增观测行 `OBS-13/14/15`。
|
||
**指纹(口径 = `sed '/^## §10 指纹/,$d' … | md5sum`):`8f08e74b026e6e5b5e1b3db813f031ae` → `13de5f9b77c486d71e5b83ec909b17b2`**。
|
||
|
||
### 8.8 遗留与回头条件
|
||
|
||
1. 🔴 **`OBS-09` 红 ⇒ 命中 §9-8**(**已停下报告**)。**卡在哪**:`OBS-09` 要的是**用户实例面**(47 `127.0.0.1:20000`、对端 21000 经 relay),而 S7 规定的 `systemctl restart dshs`(+ 106 `dshs-worker`)**会把 `dsh-114801-*.scope` / `dsh-100002-*.scope` 一起收掉**;平台是**按需拉起**(拉起的唯一触发 = 用户真去打开自己的工作区),⛔ **无 reconciler**(重启后 4 分钟仍 `inactive`,Manager 日志零 spawn 记录)。⇒ **S7 的重启步骤与 E9 的探针判据在"无用户访问"时互相冲突**。**已做到哪一步**:探针 15 PASS / 1 FAIL,其余 11 项基线项全 PASS(`OBS-11` 集合判据含 nft 与监听面全清)。**什么条件一出现必须回头**:任一用户访问自己的实例页(实例被拉起)后重跑 `overlay-probe --table …` ⇒ `OBS-09` 应回 PASS;若拉起后**仍红** ⇒ 属真回归,立即回头查 `addressOf` 链路。
|
||
2. ⚠️ **本棒实测踩到并已修的假死(重要,已补测)**:`presenceTouch` 原在 `ensureEndpoint` **之前**、且 `localPort` 是 `listen(0)` 回调里**异步**才落地的 ⇒ 首帧推出去的落点是 `0`;而发布只在"在线态翻转"时发生 ⇒ 那个 `0` **永远修不回来** ⇒ 订阅方 `addressOf` 查不到落点 ⇒ **页面打不开且不报错**。修法:① `presenceTouch` 移到 `ensureEndpoint` 之后;② 落点落地时(`listen` 回调)与端口增删时**强制重推**(`publishPresence(..., force)`)。**新用例 `T32` 先红后绿覆盖**(先红实测 `localPort=0`)。真机复验:`ops/w-106` 的 `localPorts=[{port:19000, localPort:35289}]`(**非 0**)。
|
||
3. ⚠️ **已登记的既存缺陷(本单范围外,⛔ 未动)**:`web/server.ts` 的 `translateEndpoint` 走了与 `addressOf` **不同的键口径**(表键是逻辑名、传入裸 `hostId` ⇒ 恒 `undefined`)⇒ 该分支的订阅回退**故意未加**(加了也是死代码);要改须另立单(R7)。
|
||
4. ✅ **`--scene all` 本轮结果 = 12 PASS / 0 SKIP / 0 FAIL**(唯一"附带影响":幕 4 前置含一次 Manager 重启 ⇒ 可能再次收掉实例 scope;**未出现 FAIL**)。
|
||
5. ⏳ **`translateEndpoint` 键口径既存缺陷**(见上第 3 条)⇒ 建议并入序 ⑳ 或另立单。
|
||
6. ⚠️ **动态值提醒**:`w-106` 的 relay 落点 `localPort` 是**动态值**(本轮 = 35289),⛔ 不得写死进参数表。
|
||
|
||
### 8.9 本单指纹(可复现)
|
||
|
||
`sed '/^## §8 回报格式/,$d' 交接单_presence在线态_20260917.md | md5sum` = **`f612858344077420cff5f1f9ef1c942f`**(回填后**保持不变** ✅ —— §8 之后的内容不影响前缀)。
|
||
|
||
### 8.10 序 ⑳ 执行棒回报(2026-09-17 18:26–18:5x · OBS-09 口径修正 + presence 真机收益量化)
|
||
|
||
> 本棒四项:① OBS-09 口径 ② presence 真机收益量化 ③ 收口复验 ④ `translateEndpoint` 键口径缺陷处置。
|
||
> ⚠️ 本棒**只碰两个文件**:代码仓 `scripts/overlay-probe.cjs` + 工作区根 `参数表_覆盖网络_20260917.md`。**`src/**` 零改动**。
|
||
|
||
#### ① `OBS-09` 口径修正(产物 = `scripts/overlay-probe.cjs`)
|
||
|
||
**新口径 = 在册 ⇒ 必须可达**:在册判据 = `/status.endpoints[]` 中存在该实例端口**且** `online === true`;**在册** ⇒ 实例面码 ∈ `PROBE_CODE_SET`(否则 FAIL);**不在册** ⇒ **SKIP**(⛔ 不是 PASS —— 那会掩盖"该在册却掉出端点表",那一类由 `OBS-01`/`OBS-08` 守)。
|
||
新增夹具参数 `--instance-fixture <本机码>,<对端码>`(实例面码**不在**任何既有夹具里 ⇒ 必须新增;⛔ 无此参数时在册仍判 ⇒ 会 FAIL 并点名,属**契约面**、故意如此)。`add()` 增加显式 `skip` 位;`judgeObs09()` 提到分支外 ⇒ **两种模式都判**(否则夹具证不了"能判 PASS 也能判 FAIL")。
|
||
|
||
**两侧夹具实证(三段原文;夹具 = 47 上抓的**实况** `/status` + `ss` + `nft -j`)**
|
||
|
||
| 侧 | 输入 | 原文输出 | 退出码 |
|
||
|---|---|---|---|
|
||
| **A 不误红** | 端点表**补上** 20000/21000 两条在线条目 + `--instance-fixture 401,401` | `⚠️ FIXTURE PASS OBS-09 在册实例面 本机:20000=401 w-106:41695=401 (阈值 ∈ {200,401})|不在册 0 项` | **0** |
|
||
| **B 能判真红** | 同上夹具 + `--instance-fixture 502,000` | `⚠️ FIXTURE FAIL OBS-09 … |❌ 本机:20000=502 w-106:41695=0`;`❌ 1 项红:OBS-09` | **1** |
|
||
| **C 不在册 ⇒ SKIP** | 实况端点表(**只有** `w-106:19000`) | `⚠️ FIXTURE SKIP OBS-09 在册实例面 无 (阈值 ∈ {200,401})|不在册 2 项(记 SKIP 的依据):本机:20000,对端:21000` | **0** |
|
||
| **D 契约面** | 在册但**不给** `--instance-fixture` | `⚠️ FIXTURE FAIL OBS-09 … 本机:20000=NaN …` | **1** |
|
||
|
||
**真机(非夹具)**:`SKIP OBS-09 在册实例面 无 (阈值 ∈ {200,401})|不在册 2 项:本机:20000,对端:21000`、**rc=0** —— 修前该项**恒红**(序⑲ 的唯一红项),现已转为"明确的环境态"。
|
||
**自检**:`grep -cE "[0-9]{3,}" scripts/overlay-probe.cjs` = **0**(零数字纪律未破)|`node --check` OK。
|
||
**参数表**:§6 `OBS-09` 判据格已改写 ⇒ 指纹 `13de5f9b77c486d71e5b83ec909b17b2` → **`659c08949c193102f774822905fde3b7`**(§6 **只改判据文字、⛔ 无数值改动**)。
|
||
|
||
#### ② presence 真机收益量化 —— 🔴 **实测收益 ≈ 0(设计目标未达成)**
|
||
|
||
**算式**:`R_base = 60 000 / RELAY_STATUS_POLL_MS(5000) = 12 次/分钟`;`R_now = (ΔstatusHits − 探针自身读数) / 窗口分钟`;`降幅 = R_base / R_now`。
|
||
|
||
| 口径 | 实测 | 降幅 |
|
||
|---|---|---|
|
||
| 60 s 窗口(47 relay,T0=1789641041) | `statusHits 191→204`(Δ=13,自身 2)⇒ **11 次/分钟** | **1.09×** |
|
||
| 12.4 min 窗口(T0=1789641041 → 1789641786) | `statusHits 191→334`(Δ=143,自身 8)⇒ **≈10.9 次/分钟** | **1.10×** |
|
||
| 门开关占空比(journal 时间线) | 35 min 内 **11 次切换**;挂起窗口 7/20/20/10/45 s = 102 s / 2040 s ⇒ **5.0%** | 理论 **1.05×**(与实测吻合) |
|
||
|
||
**🔴 根因(代码级)**:`presenceFresh()`(`src/net/relay/client.ts:593-600`)= `state==='subscribed' && now − max(lastSnapAt,lastPushAt) ≤ PRESENCE_TTL_MS(45 s)`;而**稳态下 relay 一帧都不推**(`pushed=1 / snaps=1` 自 relay 重启起未增,实测 `Δpushed=0`)⇒ 45 s 后**必然过期** ⇒ `pollSuspended` 门重开、轮询恢复。**"零帧"(收益的来源)与"帧新鲜度"(门的判据)互相打架** ⇒ 已登记为在册缺陷 **P-1**(`交接单_观测口径与在册缺陷_20260917.md` **§13**)。
|
||
**⚠️ 口径勘误(必读)**:序⑲ 的"降幅 63× / 10.5× / ∞"是**帧**的口径(`pushed`),**不是 `/status` 轮询降幅**;本棒给出后者 = **1.10×**。
|
||
**⚠️ 附带口径提醒**:`OBS-13` 的"活性证明 `ΔstatusHits ≥ 1`"由**探针自己两次读**即满足 ⇒ 它**不能**证明"轮询停了"(真机实测 `Δ=3/3 s`,其中 2 次是探针自身)。⛔ 别把它读成收益证据。
|
||
|
||
#### ③ 收口复验三件套 + 归零
|
||
|
||
| 项 | 结果 | 基线 |
|
||
|---|---|---|
|
||
| `npm test`(Node 22) | **170 tests / 169 pass / 0 fail / 1 skip** | = 序⑲ 基线 ⇒ **零回归** |
|
||
| `overlay-probe --table`(真机) | **14 PASS / 0 FAIL / 1 SKIP**,rc=0 | 原为 15 PASS / **1 FAIL** ⇒ OBS-09 转 SKIP |
|
||
| `--scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**,rc=0(幕1-A 18898ms/幕4-A 23183ms+豁免回切 20887ms) | = 序⑲ 基线 ⇒ **零回归** |
|
||
| 收口归零 | 47 `dshs`=active、`dshs-relay`=active、**池口 64**;Manager 已归零回 47 | ✅ |
|
||
|
||
⚠️ **106 单元状态未取到**(`ssh 106.54.21.172` rc=255 = MaxStartups 限流,本线已知)⇒ ⛔ 放宽 sshd 命中 **R5** ⇒ **只报告**。
|
||
|
||
#### ④ `web/server.ts#translateEndpoint` 键口径缺陷 —— **判定 = 另立单(本棒未动手)**
|
||
|
||
**证据(代码级)**:`hostVia.set(name, row.via)`(`:733`,`name = logicalName(networkId, row.id)` = `ops/w-106`,**键 = 逻辑名**)vs 调用方 `src/supervisor/remote-spawner.ts:321 this.translateEndpoint(host.hostId, raw)`(**裸 hostId**)⇒ `:831 hostVia.get(hostId)` **恒 `undefined`** ⇒ **早退原样透传** ⇒ 该闭包(含 `:840` 的 `${hostId}:${ep.port}` 快照回退,键同样应为逻辑名;对照 `:689`/`:700` 用的就是逻辑名)**整体是死分支**。
|
||
**影响面**:仅 `via='relay'` 的**实例面**(今天 = `w-106`);现场 `ss -lntp | grep -c ':21000'` = **0**、`journalctl -u dshs --since -6h | grep -c ':21000'` = **0** ⇒ **当前零实际影响**。
|
||
**为何本棒不修**:① 修它 = 让一条**当前恒被跳过**的翻译路径**重新生效**(数据面行为变化);② 该闭包是 `buildServer` 内部闭包、**无法先红后绿**,须先抽纯函数(= 一次小重构);③ 端到端验证需起 `w-106` 实例(当前无 reconciler、实例不自回)⇒ **本棒无验证条件**。⇒ 登记为 **P-2**(缺陷单 §13,含候选修法与判据),交给下一棒。
|
||
|
||
#### ⑤ 边界自证 + 指纹
|
||
|
||
**边界**:⛔ 未改任何生产值(`RELAY_FAILOVER_*` / `HB_SEC` / burst 逐项未变;参数表改动**仅在 §6 判据文字**)|⛔ 未新增公网监听口 / 未改 nft·nginx|🔴 `COOLDOWN_MS=0` 计数 **0**|⛔ 未 commit / 未 push(`scripts/overlay-probe.cjs` 仍为 `M`)|文件面 = 代码仓 `scripts/overlay-probe.cjs`(`md5 4cbe34f1ffc51cfab78f0540224aa388`)+ 工作区根 `参数表_覆盖网络_20260917.md`;`src/**` 零改动。
|
||
**指纹**:参数表(口径 = `sed '/^## §10 指纹/,$d' … | md5sum`)**`659c08949c193102f774822905fde3b7`**(旧 `13de5f9b77c486d71e5b83ec909b17b2`)|本单 §8 前缀 **`f612858344077420cff5f1f9ef1c942f`(未变 ✅)**|探针 `4cbe34f1ffc51cfab78f0540224aa388`。
|
||
|
||
---
|
||
|
||
### 8.11 序㉑ 执行棒回报(2026-09-17 19:0x–19:4x · **P-1 与 P-2 均已修**)
|
||
|
||
> 口径依据 = 入口 §2「本轮动作」+ 缺陷单 §13。本棒四项:① 修 P-1(门判据)② 修 P-2(键口径 ⇒ 抽纯函数)③ 零回归三件套 ④ 收口。
|
||
|
||
#### ① P-1 已修 —— **门的判据从「载荷年龄」改为「订阅已建立 ∧ 链路活着」**
|
||
|
||
**我选了什么(可推翻)**:候选 **A**(任何入站帧含心跳即算"链路活着")。
|
||
|
||
- ⛔ **不选 B(relay 定期重推 `SNAP`)**:那会把**稳态零帧**这条设计属性打掉 —— 序⑲ **E1** 的机器判据就是 `Δpushed ≤ 0`,`OBS-14` 还依赖 `snaps ≤ pushed` 这个不变量;B 让 `snaps/pushed` 在稳态持续增长 ⇒ **直接推翻本单自己的验收口径**(R11:净变差)。
|
||
- ⛔ **不选 C(拉长 TTL)**:只是把竞速推远(45 s → 更长),门迟早仍会自开 ⇒ 治不了"把**没有变化**读成**没有数据**"这个语义错位。
|
||
- ✅ **A 的语义理由**:镜像的**有效性**由 ① 订阅已建立(拿到过首帧 `SNAP`)② 通道**有序可靠**(WS/TCP)共同保证 —— relay 侧每一次变化**必然**按序到达,**载荷的年龄与内容正确性无关**;只有**链路死**才让镜像失效,而链路死由半开巡检(`2.5 × hbSec` 无任何入站帧 ⇒ 主动断链)兜住,断链即 `state ≠ 'subscribed'` ⇒ 门自动打开、回退 `/status`(兜底路径**一行未删**)。
|
||
- ⚠️ **已知残余 + 回头条件**:relay 侧**静默**清掉订阅而 socket 仍活 ⇒ 本判据察觉不到。当前代码里**不可达**(唯一清空 `session.subs` 的是显式 `UNSUB`;relay 重启 / 会话回收都会断 socket)。已按 §9 第 **10** 条登记。
|
||
|
||
**改法(代码级)**:`src/net/relay/client.ts`
|
||
|
||
| 位置 | 改动 |
|
||
|---|---|
|
||
| `presenceFresh(ttlMs?)` | 判据 = `state === 'subscribed' && lastSnapAt !== 0 && (now − lastFrameAt) ≤ presenceLinkSilentMaxMs(ttlMs)`(`lastFrameAt` = **任何**入站帧,含心跳) |
|
||
| `presenceLinkSilentMaxMs()`(新增私有) | 上界 = `max(服务端 TTL, 半开阈值)` —— 🔴 **不得比链路巡检更紧**,否则门会比"链路真的死了"更早打开(= 把 P-1 换个方向重犯);测试档 TTL 被压到 3 s、心跳仍 15 s ⇒ 只用 TTL 会造**假红** |
|
||
| `halfOpenMs()`(新增私有) | 半开阈值唯一来源(`startHealthWatch` 与本判据共用,⛔ 不引入第二个时间口径) |
|
||
| `RelayPresenceStatus`(+3 字段) | `lastInboundAgoMs` / `linkSilentMaxMs` / `fresh` —— 让"门为什么开着/关着"**可判**(判别器纪律,⛔ 不静默) |
|
||
|
||
#### ② 先红后绿(原文级)
|
||
|
||
把 `presenceFresh()` **临时改回旧判据**(`max(lastSnapAt,lastPushAt)`)重建后跑新用例:
|
||
|
||
```
|
||
not ok 1 - T33 P-1:稳态零帧下门不得自己重开(判据 = 订阅已建立 ∧ 链路活着,⛔ 不是载荷年龄)
|
||
error: |-
|
||
载荷 5243ms 没来、但链路活着(入站静默 217ms ≤ 上界 3000ms)⇒ 门必须保持关闭
|
||
# pass 0 / # fail 1
|
||
```
|
||
|
||
改回新判据重建后:`ok 1 T33` / `ok 2 T34`(**2 pass / 0 fail**)。用例清单(`test/relay.test.mjs`):
|
||
`T33` 稳态超过 TTL 门**不得**自开(旧实现必红的那条);`T34` **反向失败关闭**(未订阅 / 已退订 / 被拒 / 断链 ⇒ 一律不新鲜 —— 少这条,"永远返回 true"也能让 T33 绿)。
|
||
⚠️ `T33` 用夹具前提 `hbSec: 1`:测试档 TTL 3 s < 生产心跳 15 s,不压心跳时 relay 的 `presenceDevices`(`now − conns[ts] ≤ ttl`)会在 3 s 后把连接判死 ⇒ 自己制造"离线→在线"**假帧**(生产 `TTL 45 s > 心跳 15 s`,不存在该组合)。
|
||
|
||
#### ③ 真机验收(47 relay,**同一口径 Before / After**)
|
||
|
||
算式:`窗口内 ΔstatusHits = 探针自身读数(窗口内 1 次)+ Manager 侧命中` ⇒ **`Manager 侧 = Δ − 1`**。
|
||
|
||
| 口径 | 窗口 | `statusHits` | Δ | Manager 侧 | 门开关 |
|
||
|---|---|---|---|---|---|
|
||
| **Before**(旧代码在跑,先测) | 66 s | `228 → 242` | **14** | **13**(≈ 11.8 次/分钟,= 基准 12/分钟 ✅) | 窗口内 0 行(旧口径下门基本常开,翻一次后不再翻) |
|
||
| **After**(铺新 lib + `systemctl restart dshs` 后) | 67 s | `247 → 248` | **1** | **0** ✅ | 窗口内 **0 行** |
|
||
|
||
**门开关原文(`journalctl -u dshs | grep overlay-presence`,重启后至今 5+ 分钟、远超 TTL 45 s)**:
|
||
|
||
```
|
||
Sep 17 19:09:53 … [overlay-presence] 订阅新鲜 ⇒ `/status` 轮询**挂起**(兜底路径待命)
|
||
--- 总行数: 1
|
||
```
|
||
|
||
⇒ 即"挂起"一次、**零次翻转**(旧代码在 TTL 边界必然翻转 ⇒ 轮询恢复)。旁证:两窗口 `Δpushed = 0`、`subs = 1`(稳态零帧、订阅在册)。
|
||
**部署**:本机 build ⇒ `lib/net/relay/{client,index,endpoint-target}.js` + `lib/web/server.js` scp 到 **4 处** —— 47 `/opt/dshs/lib` / 47 `/opt/dsh-relay/lib` / 106 `/opt/dshs-cluster/lib` / 106 `/opt/dsh-relay/lib`,`md5` 全部 = 本机 `6c14d4d4…`;**只在 47 重启 `dshs`**(改动是 Manager 侧;relay 服务端路径未动,106 的文件待其下次重启自然生效)。
|
||
|
||
#### ④ 观测口径:新增 `OBS-16`(门的验收判据;⛔ 不能只看 `OBS-13`)
|
||
|
||
🔴 **为什么必须新增**:`OBS-13` 的 `ΔstatusHits ≥ 1` 由**探针自己两次读**即满足 ⇒ **证明不了"轮询停了"** —— 旧口径下门 95% 时间开着、轮询照旧在跑,而 `OBS-13` 仍然全绿(实测降幅 1.10× 却长时间没人发现,正是这条)。
|
||
**新判据**(`scripts/overlay-probe.cjs`,第三次 `/status` 采样,窗口 = `PRESENCE_GATE_WINDOW_MS`):
|
||
`subs ≥ 1` **且** `ΔstatusHits ≥ 1`(自身读数 = 活性证明)**且** `≤ PRESENCE_GATE_HITS_MAX`(= 1 ⇒ 除探针外**零**命中)。
|
||
|
||
**夹具两侧实证**(夹具 = 47 实况 `/status` + `ss` + `nft -j` 原文,仅改 `counters.statusHits`;新增 `--status-fixture-3`):
|
||
|
||
| 侧 | 输入 | 原文输出 | rc |
|
||
|---|---|---|---|
|
||
| **PASS** | 三连 `181 / 182 / 183`(窗口 Δ=1) | `⚠️ FIXTURE PASS OBS-16 门窗口 ΔstatusHits=1(≥ 1 = 探针自身读数|≤ 1 = 除探针外**零**命中),窗口 60000ms,期间 subs=1` | **0** |
|
||
| **FAIL** | 第三份 `194`(窗口 Δ=12 = 旧口径下的轮询量) | `⚠️ FIXTURE FAIL OBS-16 … ΔstatusHits=12 …`;`❌ 1 项红:OBS-16` | **1** |
|
||
|
||
⚠️ 夹具模式**必须**显式给 `--status-fixture-3`:缺省 = 与第二份同一份 ⇒ Δ=0 ⇒ 活性证明不成立 ⇒ FAIL 并点名(**契约面**,故意如此,与 `OBS-09` 的 `--instance-fixture` 同规则)。
|
||
**真机(非夹具)**:`PASS OBS-16 门窗口 ΔstatusHits=1 … 窗口 60000ms,期间 subs=1` ⇒ **15 PASS / 0 FAIL / 1 SKIP**、`rc=0`(基线 14/0/1 + 新项)。
|
||
⚠️ **副作用(已知、有意)**:真机探针一轮**多花 `PRESENCE_GATE_WINDOW_MS`**(窗口必须够长,否则"轮询在跑"与"轮询停了"在计数上分不开)。
|
||
|
||
#### ⑤ P-2 已修 —— `translateEndpoint` 键口径(**抽纯函数 ⇒ 才可能先红后绿**)
|
||
|
||
**病根**:控制面所有表(`hostVia` / `relayEndpoints` / 拨号池)键都是**逻辑名**,而调用方 `RemoteSpawner.translateEndpoint(host.hostId, …)` 只给得到**裸 hostId** ⇒ `hostVia.get(hostId)` 恒 `undefined` ⇒ 早退原样透传 ⇒ **整个闭包是死分支**(`:840` 的快照回退键同错;`:689`/`:700` 用的正是逻辑名,自证口径)。
|
||
**改法**:新增纯函数模块 `src/net/relay/endpoint-target.ts` = `relayEndpointTarget()`(四支判定:未知 host / 非 relay ⇒ **原样透传**;`via=relay` ⇒ ①拨号落点 ②快照落点 ③**`unreachable` 失败关闭**)+ `hostNameIndex()`(`dsh_hosts` 行 ⇒ `hostId → 逻辑名`);`src/web/server.ts` 的闭包改为先 `hostNameById.get(hostId)` 换逻辑名再调用它。
|
||
🔑 **两处设计要点**:① `dialedPort` 传 **thunk** —— `RelayDialer#localPortFor` **会按需绑池口 / 真的拨一次**,⛔ 非 relay / 未知 host 时不许碰它(`T35` 把这钉成断言);② 失败关闭分支**点名写日志**(`[overlay-endpoint] ⛔ … 判实例不可达`),因为原故障的表现只有"空响应 + 平台零日志"。
|
||
**单测**:`T35`(四支判定 + "⛔ 不误触发拨号池")、`T36`(`hostNameIndex` 口径 + **源码级守卫**:闭包⛔ 不得再出现 `hostVia.get(hostId)` / `localPortFor(hostId` / `relayEndpoints.get(\`${hostId}`,只看代码行不看注释)。
|
||
**端到端判据(本轮未做,按入口授权"起不起来就只做到单测")**:需 `w-106` 实例在跑(当前无 reconciler、不起就自回)。可执行判据 = ① 起实例后 `POST /api/dsh/enter` **200**;② `ss -lntp | grep -c ':21000'` 在 Manager 上仍为 **0**(翻译生效 ⇒ 拨的是 relay 回环落点,⛔ 不是 worker 侧口号);③ `journalctl -u dshs | grep -c overlay-endpoint` = **0**(失败关闭未被触发);④ 反例判据:若翻译未生效,Manager 会去连 `127.0.0.1:21000` ⇒ 连接被拒(本线 2026-09-16 踩过的"空响应 + 零日志"同族)。
|
||
✅ 顺带核实(DB,只读):`dsh_hosts` = `w-106|relay|ops`、`w-47|local|ops` ⇒ **w-106 就是走被修的那条分支**;且 `endpoint === undefined` 在 `proxy.ts:240` 得到的是**显式** `{ error: 'not_running', code: 404 }`(不是崩溃、也不是静默)⇒ 失败关闭语义干净。
|
||
|
||
#### ⑥ 零回归三件套(全绿,均带 `--table`)
|
||
|
||
| 项 | 结果 | 基线 |
|
||
|---|---|---|
|
||
| `npm test`(Node 22) | **174 tests / 173 pass / 0 fail / 1 skip**(= 170/169/0/1 + 新增 T33–T36) | 170/169/0/1 ⇒ **零回归** |
|
||
| `--scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**,`rc=0`(幕1-A 19060ms/幕4-A 22543ms+豁免回切 16512ms/幕4-B/4-C 全绿) | 12/0/0 ⇒ **零回归** |
|
||
| `overlay-probe --table` | **15 PASS / 0 FAIL / 1 SKIP**,`rc=0`(`OBS-09` SKIP = 在册判据、`OBS-16` PASS) | 14/0/1 ⇒ **+1 项、零回归** |
|
||
| 收口归零 | 47 `dshs`/`dshs-relay` active、106 `dshs-relay`/`dshs-worker` active、Manager 归位 47、池口 76 行监听(`ss -lntn` 计数为 76 —— ⚠️ 与序⑳ 的"池口 64"口径**不同**:后者是 relay 自报的池容量 gauge,前者是本机监听行数,两者本就不同量,此处只作"未新增暴露面"的旁证,`OBS-11` 集合判据已绿) | ✅ |
|
||
|
||
#### ⑦ 边界自证 + 指纹
|
||
|
||
**边界**:⛔ 未改任何生产值(`RELAY_FAILOVER_*` / `HB_SEC` / burst 逐项未变;`PRESENCE_TTL_MS` **值未改**,只改判据语义)|⛔ 未新增公网监听口 / 未改 nft·nginx|🔴 `COOLDOWN_MS=0` 计数 **0**(含回滚/演练/夹具路径)|⛔ 未删 `/status` 兜底路径|⛔ 未改 `switcher.ts` 冷却语义|⛔ 未 commit / 未 push。
|
||
**文件面**:代码仓 = `src/net/relay/client.ts`(`c5d395395324e17edf9d269f34e8b3af`)|`src/net/relay/endpoint-target.ts`(**新增**,`24ac70edc52adec4928513459a3e0160`)|`src/net/relay/index.ts`|`src/web/server.ts`(`adf69e484cf00e95a132c7f6c0ae8c65`)|`test/relay.test.mjs`(`b925a672c5fb897f0598ef89d2f2bde8`)|`scripts/overlay-probe.cjs`(`61a9fc5623bb617c802daf604d3ce0f3`,旧 `4cbe34f1ffc51cfab78f0540224aa388`);工作区根 = `参数表_覆盖网络_20260917.md`。
|
||
**指纹**:参数表(口径 = `sed '/^## §10 指纹/,$d' … | md5sum`)**`42238175d84319ada99afa56d583f9db`**(旧 `659c08949c193102f774822905fde3b7`;本轮 = §3.6 +2 键、§6 +1 项 +2 阈值键、§3.6 增"P-1 口径变更"注)|本单 **§8 前缀 `f612858344077420cff5f1f9ef1c942f`(未变 ✅,§8.11 在截断点之后)**|本单全文件 md5(回填前)`b070e4228d061bb39a62d2aa0c11e416`。
|
||
**登记(⛔ 本棒未动手,只登记)**:**P-2b** —— `relayEndpointTarget` 目前只有"拨号落点 → relay 快照"两级;**P-1 修好后 `/status` 快照路径趋冷**(门长期挂起 ⇒ 快照很少刷新),当拨号池分不出槽位而**订阅推送里有落点**时仍会判失败关闭。候选 = 把 `presenceLocalPort`(订阅落点)插成第 ②'级(与 `addressOf` 的三级链对齐)。⚠️ 属**行为面新增**,按 R7「先报告后动手」登记,未在本棒实施。
|
||
|
||
---
|
||
|
||
### 8.12 序 ㉒ 执行棒回报(2026-09-17 19:33–20:0x · **P-2b 已修 + P-1 长窗复测通过**)
|
||
|
||
> 口径依据 = 入口 §2「本轮动作」+ 缺陷单 · 在册缺陷表 **P-2b** 行。本棒三件:① 修 P-2b ② P-1 修复后的**长窗(≥ 10 min)**真机复测 ③ 零回归三件套 + 收口。
|
||
|
||
#### ① P-2b 已修 —— 候选链由「两级」扩成「三级」,与 `addressOf` **逐级对齐**
|
||
|
||
**病根(一句话)**:`relayEndpointTarget` 只查 ① 拨号落点 → ② relay 快照;而 `addressOf`(`RelayRendezvous`)查 ① 拨号 → ② **订阅推送**(`presenceLocalPort`)→ ③ 快照。**P-1 修好之后**判据变成「订阅已建立 ∧ 链路活着」⇒ 订阅新鲜期**长期成立** ⇒ 快照(②)**趋冷**;此时若拨号池分不出槽位(跨网被拒 / 池满 / 尚未绑口),本判定会落到"两级都没有" ⇒ **判实例不可达(失败关闭)**,**而同一时刻 `addressOf` 明明能从订阅推送里答出落点** ⇒ **两条链漂移**:用户看到"实例打不开",日志里却什么都没有。
|
||
|
||
**改法(三个在册文件,⛔ 未越界)**:
|
||
|
||
| 文件 | 改动 |
|
||
|---|---|
|
||
| `src/net/relay/endpoint-target.ts` | 新增入参 `pushedLocalPort?: number`;`via` 联合类型加 `'pushed'`;判定顺序变 **① `dialed` → ② `pushed` → ③ `snapshot` → 失败关闭**;模块头部的判定表与"两级 → 三级"的理由写进注释 |
|
||
| `src/web/server.ts` | 闭包多传一行 `pushedLocalPort: name === undefined ? undefined : presenceLocalPort(name, ep.port)`(与 `addressOf` 的 ② 级**同一个函数、同一个顺序**);失败关闭的点名日志改成"拨号落点 / 订阅推送 / relay 快照**三级**都查不到" |
|
||
| `test/relay.test.mjs` | 新增 **T37**(三级逐支可判 + `0` 哨兵逐级下探 + 未知 host / 非 relay ⛔ 不碰拨号池)/**T38**(源码级守卫:闭包**必须**把 `presenceLocalPort(name, ep.port)` 传进判定 —— 无实例时该分支根本不执行,运行时断言抓不到) |
|
||
|
||
**先红后绿(原文级 · 同一份测试体)**:临时把 **build 产物** `lib/net/relay/endpoint-target.js` 里的 ②' 级整段去掉(源码不动)后跑:
|
||
|
||
```
|
||
not ok 1 - T37 P-2b:候选链三级(拨号 → 订阅推送 → relay 快照)逐支可判,⛔ 不误触发拨号池
|
||
error: |-
|
||
P-2b:拨号给不出时,订阅推送的落点必须先于 relay 快照被采用(与 `addressOf` 对齐)
|
||
+ actual - expected
|
||
{ kind: 'local',
|
||
+ port: 41000,
|
||
+ via: 'snapshot'
|
||
- port: 37057,
|
||
- via: 'pushed' }
|
||
# pass 0 / # fail 1
|
||
```
|
||
|
||
`tsc` 重建后:`T37` / `T38` **2 pass / 0 fail**。⇒ **红确实是"少这一级"造成的**,不是测试写歪。
|
||
|
||
#### ② P-1 长窗(706 s ≈ 11.8 min)复测 —— **门 0 次翻转、Manager 侧 0 命中**
|
||
|
||
算式(与序㉑ 同口径):`Manager 侧 = 窗口内 ΔstatusHits − 探针自身读数`;窗口开端读一次(计入 `T0` 读数本身)、末端读一次 ⇒ **自身读数 = 1**。**窗口内不跑探针**(探针的 `OBS-16` 自己会占一次窗口,会污染计数)。
|
||
|
||
| 口径 | 窗口 | `statusHits` | Δ | Manager 侧 | 门开关行数 | `Δpushed` / `Δsnaps` |
|
||
|---|---|---|---|---|---|---|
|
||
| **入册(短窗)** | 67 s | `247 → 248` | 1 | **0** | **0** | — / — |
|
||
| **本棒(长窗)** | **706 s** | `9 → 10` | **1** | **0** ✅ | **0** ✅ | **0 / 0** ✅ |
|
||
|
||
**相对基准的倍数**:基准 = `60 000 / RELAY_STATUS_POLL_MS(5 000)` = **12.000 次/分钟**;实测 raw Δ = `1 / (706/60)` = **0.085 次/分钟** ⇒ **141.2×**;**Manager 侧 = 0 次/分钟 ⇒ 无穷倍**(分母为零)。
|
||
**门开关计数原文**:`journalctl -u dshs --since @<T0> | grep -c overlay-presence` = **0**(窗口 706 s ≫ `PRESENCE_TTL_MS` 45 s ⇒ 若判据仍是"载荷年龄",这里必然 ≥ 1 ⇒ **长窗是对 P-1 修复的**独立**复核,不只是短窗的重复**)。
|
||
**旁证**:窗口两端 `subs = 1`(订阅在册)、`pushed = 1 → 1`、`snaps = 1 → 1` ⇒ 稳态零帧,正是 P-1 的病理前提。
|
||
|
||
#### ③ 零回归三件套(全绿,均带 `--table`;**部署后重跑同值**)
|
||
|
||
| 项 | 结果 | 基线 |
|
||
|---|---|---|
|
||
| `npm test`(Node 22) | **176 tests / 175 pass / 0 fail / 1 skip**(= 174/173/0/1 + T37/T38) | 174/173/0/1 ⇒ **零回归** |
|
||
| `--scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**,`rc=0`(幕1-A 15561ms/幕4-A 23612ms + 豁免回切 21145ms) | 12/0/0 ⇒ **零回归** |
|
||
| `overlay-probe --table` | **15 PASS / 0 FAIL / 1 SKIP**,`rc=0`(`OBS-09` SKIP = 在册判据;`OBS-16` PASS `ΔstatusHits=1`) | 15/0/1 ⇒ **零回归** |
|
||
| 收口归零 | 47 `dshs`/`dshs-relay`/`dshs-pg` active |106 `dshs-relay`/`dshs-worker` active |监听行 **76** / 池口 **64** |`overlay-endpoint` 失败关闭点名 **0 行** | ✅ |
|
||
|
||
⚠️ **观察项(非回归,⛔ 未处置)**:`OBS-15` 在线态陈旧 `p95 = 14220 ms`(阈值 `≤ 15000 ms`,样本 2 条)—— **贴线但未越界**;同批另一次采样为 `5347 ms` ⇒ 样本少、抖动大。**本棒未改任何阈值 / 未放宽判据**,仅登记。
|
||
|
||
#### ④ 部署 + 回滚点
|
||
|
||
`npm run build` ⇒ `lib/net/relay/{endpoint-target,index}.js` + `lib/web/server.js` scp 到 **4 处**:47 `/opt/dshs/lib`+`/opt/dsh-relay/lib`、106 `/opt/dshs-cluster/lib`+`/opt/dsh-relay/lib` ⇒ **四根 md5 全同**:
|
||
|
||
```
|
||
36b2dd9953a615de9a3e148752dffe94 net/relay/endpoint-target.js
|
||
486419ba443b99dd734ca79e987ffdea net/relay/index.js
|
||
460d360e8c6196341935b2f9a2a1d408 web/server.js
|
||
```
|
||
|
||
**只重启 47 的 `dshs`**(改动是 Manager 侧)。**回滚点**(两机同路径)= 把本棒的两处 hunk **反向改回** → `tsc` → 得到的**精确旧产物**(⛔ 不是"凭记忆重写"):
|
||
|
||
```
|
||
/opt/dsh/backups/seq22-20260917-195602/{net/relay/endpoint-target.js,net/relay/index.js,web/server.js}
|
||
57e6c9ae81d64f6586d758f5dc021a4b endpoint-target.js(旧) 486419ba443b99dd734ca79e987ffdea index.js(未变)
|
||
74c077b33c6d8afdef980171c19eee26 server.js(旧)
|
||
```
|
||
|
||
#### ⑤ 本棒两条**环境/流程**教训(下一棒别踩)
|
||
|
||
1. 🔴 **106 的 ssh 必须用别名 `test106`**(专用密钥 `id_ed25519_test106`,`IdentitiesOnly yes`;参数表 `SSH_TARGET_106` 本就写着)。裸 `ssh root@106.54.21.172` ⇒ **`Permission denied (publickey)`** —— 本棒先误判成"MaxStartups 限流 rc=255"并据此放弃,后来抓 stderr 才看到真因,改用别名后**一次通过**。⛔ 后续任何一棒**不许再把 106 连通失败笼统归因成限流**。
|
||
2. ⚠️ **原生 `scp` 不认 `E:/…` 混写路径**(Git Bash 里 `cd E:/…` 能用,但 `scp 'E:/…/f'` 会 `No such file or directory`)⇒ 一律 **先 `cd` 到目录、再用相对路径**;本棒的"回滚点落盘"第一版就是因此静默空跑(只打印了一行空 md5),已重跑纠正。
|
||
|
||
#### ⑥ 登记(⛔ 本棒未动手)
|
||
|
||
- **E2E(P-2 + P-2b)仍未做** —— 47 上活跃实例 scope = **0**(`systemctl list-units 'dsh-*.scope'` 空;`:21000` 监听 = 0)⇒ 判据仍是本单 **§8.11-⑤** 那四条,**交序 ㉓**。
|
||
- 在册缺陷表**已清空**(P-1 / P-2 / P-2b 全部关闭);剩余只有 **§9 第 10 条**(回头条件,非在册缺陷)+ **E2E 缺口** + **两项待拍板**(`B` 内容分发 / 「Manager 重启后是否自动拉起既有实例」)。
|
||
|
||
### 8.13 序 ㉓ 执行棒回报(2026-09-17 20:08–20:4x · **P-2 / P-2b 的 E2E 已验收通过 + 在册缺陷表清空 + 线内可停**)
|
||
|
||
> 口径依据 = 入口 §2「本轮动作」+ 本单 **§8.11-⑤**(E2E 四条判据)。本棒两件:① **主 = P-2/P-2b 的 E2E** ② **次 = presence 线收口复核** ③ 零回归三件套 + 收口。
|
||
|
||
#### ① 前置(T0 = 2026-09-17 20:10:57,`epoch 1789647057`)
|
||
|
||
| 项 | 值 |
|
||
|---|---|
|
||
| 47 单元 | `dshs` / `dshs-relay` / `dshs-pg` = **active** |
|
||
| `dsh_hosts`(只读) | `w-106 → http://127.0.0.1:19000` **`via=relay`** | `w-47 → http://127.0.0.1:19100` `via=local` |
|
||
| relay `/status` | `dial=0 dialDenied=0 dialFailed=0 statusHits=14 subs=1 pushed=2 snaps=2`|`endpoints=[{port:19000,hostId:w-106,localPort:39525}]` |
|
||
| 47 上 `:21000` / `:19000` 监听 | **0 / 0** |
|
||
| `overlay-endpoint` 日志(全量历史) | **0 行** |
|
||
| 47 上 `dsh-*.scope` | **0**(⚠️ guest 实例在 **106** 上 ⇒ 见 ④) |
|
||
|
||
#### ② 四条判据(E2E 主体 · 全部通过)
|
||
|
||
手段 = **R4** 临时 session:`node /opt/dshs/mksess-guest.cjs` ⇒ `POST http://127.0.0.1:3080/api/dsh/enter`(`--http1.1`,**同一条连接连发两次**)。
|
||
|
||
| # | 判据 | 期望 | 实测 | 判定 |
|
||
|---|---|---|---|---|
|
||
| ⓐ | `enter` HTTP 码 | **200** | `…\|200` + `…\|200`(同连接双发)+ 复测 6 轮**全 200** | ✅ |
|
||
| ⓑ | 47(Manager)上 `ss -lntp \| grep -c ':21000'` | **0** | **0**(`:19000` 亦为 **0**) | ✅ |
|
||
| ⓒ | `journalctl -u dshs \| grep -c overlay-endpoint` | **0** | **0**(窗口内 delta = **0**、全量历史 = **0**) | ✅ |
|
||
| ⓓ | relay `/status` `counters.dial` 增量 | **> 0** | **0 → 2**(`denied=0` / `failed=0`) | ✅ |
|
||
|
||
`enter` 响应体原文(截断):
|
||
|
||
```
|
||
{"kind":"session","instance":{"id":"b5e5b0cd-…","port":21000,"status":"running","launchToken":"dVQHA02lJYqOE8UyRJzJoTuOmpd0MBd9oMkoUzCg9Js"},"url":"https://guest.alotbuy.com/?token=…"}
|
||
```
|
||
|
||
#### ③ 判别器证据(**比 `dial` 计数更强的一条**)—— 两条链**各自**都拨到了
|
||
|
||
```
|
||
20:10:59 [relay-dialer] 落点 127.0.0.1:25000 -> ops/w-106:19000 ← agent 面
|
||
20:10:59 [relay-client] dial w-106:19000 up (stream=2)
|
||
20:13:02 [relay-dialer] 落点 127.0.0.1:25001 -> ops/w-106:21000 ← 实例面
|
||
20:13:03/04 [relay-client] dial w-106:21000 up (stream=3/4/5)
|
||
```
|
||
|
||
🔑 **为什么这条是 P-2 / P-2b 的直接验收**:`dsh_hosts` 里 w-106 的 `endpoint` 是 `http://127.0.0.1:19000`(**worker 侧口号**)。拨号日志里出现的是**逻辑名 `ops/w-106:<口号>`**(`19000` = agent 面、`21000` = 实例面),且 **47 本机 `:21000` / `:19000` 监听恒为 0** ⇒ 说明:① `hostId → 逻辑名` 的键口径修对了(**P-2**)② 拨的是 relay 回环落点、⛔ 不是 worker 侧口号 ③ 实例面那条走的就是 **P-2b** 新增的候选路径。同刻 `/status` 出现两条端点:`{port:19000,localPort:39525}` + `{port:21000,localPort:43431}`。
|
||
|
||
#### ④ 实例真身在 106(旁证)+ 用户可见面 200
|
||
|
||
- **106 侧**:`dsh-100002-ef8d1d12.scope` = **active running**,argv 末尾 = `dsh --profile web --host 127.0.0.1 --port 21000` ⇒ `127.0.0.1:21000` **在 106 上监听**(`count_21000=1`),agent 口 `19000` 亦在听;`dshs-worker` / `dshs-relay` = active。
|
||
⇒ **"47 上 `:21000` = 0" 与 "106 上有实例在听 21000" 同时成立** = 翻译生效的**强判据**(若翻译失效,Manager 会去连 **47 自己的** `127.0.0.1:21000` ⇒ 连接被拒 ⇒ 正是本线 2026-09-16 踩过的"空响应 + 零日志")。
|
||
- **用户可见面(补齐 `OBS-09` 的语义)**:`curl -H 'Host: guest.alotbuy.com' http://127.0.0.1:3080/` ⇒ **200**、**62451** 字节真 HTML(`<!doctype html> … __ModuleLoader__`)、`502/Bad Gateway` 标记 **0** ⇒ 序⑬ **Q2**(guest(w-106) 实例页 502)的判据**现已满足**。
|
||
|
||
#### ⑤ 零回归三件套(全绿;均带 `--table`)
|
||
|
||
| 项 | 结果 | 基线 | 判定 |
|
||
|---|---|---|---|
|
||
| `npm test`(Node 22) | **176 tests / 175 pass / 0 fail / 1 skip** | 176/175/0/1 | ✅ 零回归 |
|
||
| `--scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**,`rc=0`(幕1-A 17494ms/幕4-A 22234ms + 一跳豁免 17996ms) | 12/0/0 | ✅ 零回归 |
|
||
| `overlay-probe --table` | **16 PASS / 0 FAIL / 0 SKIP**,`rc=0` | 15/0/1 | ✅ **+1(better)** |
|
||
|
||
🔴 **`overlay-probe` 由 `15/0/1` → `16/0/0` 的原因 = `OBS-09` 由 SKIP 转 PASS**:
|
||
|
||
```
|
||
PASS OBS-09 在册实例面 w-106:37747=401 (阈值 ∈ {200,401})|不在册 1 项(记 SKIP 的依据):本机:20000
|
||
```
|
||
|
||
⇒ 这**正是序⑲ §9-8 写下的回头条件**("任一用户访问自己的实例页后重跑探针 ⇒ `OBS-09` 应回 PASS")**已兑现**,且**不是**真回归。其余 15 项同基线(`OBS-11` 实际 77 多出 0 缺失 0 = 端点数 1→2 各占一个回环落点,集合判据已绿;`OBS-15` p95 = **1770 ms** ≪ 15000 ms)。
|
||
**收口归零**:47 `dshs`/`dshs-relay`/`dshs-pg` active、门户 **200**、池口 **64**、`:21000` 监听 **0**|106 `dshs-worker`/`dshs-relay` active。
|
||
|
||
#### ⑥ 边界自证 + 指纹
|
||
|
||
⛔ **未改任何代码 / 未改任何生产值 / 未动 nft·nginx / 未新增公网监听口** —— 本棒**只做只读取证 + 临时 session**。逐项:
|
||
|
||
| 项 | 期望 | 实测 |
|
||
|---|---|---|
|
||
| 参数表指纹(`sed '/^## §10 指纹/,$d' … \| md5sum`) | `42238175d84319ada99afa56d583f9db`(未变) | **`42238175d84319ada99afa56d583f9db`** ✅ |
|
||
| 本单 §8 前缀 | `f612858344077420cff5f1f9ef1c942f`(未变) | **`f612858344077420cff5f1f9ef1c942f`** ✅ |
|
||
| in-册代码面 md5(`endpoint-target.ts` / `web/server.ts` / `relay.test.mjs`) | 与序㉒ 同值 | `9f421a9f…` / `8c4c072c…` / `f08088b8…` ✅ **全同** |
|
||
| 代码仓 HEAD / 改动数 | `04776af` / 10 | **`04776af` / 10** ✅ |
|
||
| 🔴 `COOLDOWN_MS=0` 命中(`scripts`+`src`+`test`) | **0** | **0** ✅ |
|
||
| R4 临时 session 残留 | **0** | **0** ✅(两轮用完即删,`user_agent='poc-curl2'`) |
|
||
| ⛔ commit / push | 无 | **无** ✅ |
|
||
|
||
#### ⑦ 收口复核结论(本棒第二件)—— **线内可停**
|
||
|
||
| 项 | 状态 |
|
||
|---|---|
|
||
| **P-1**(presence 新鲜度判据) | ✅ 已修(§8.11-①;长窗复核见 §8.12-②) |
|
||
| **P-2**(`translateEndpoint` 键口径) | ✅ 已修 + **E2E 验收通过**(本节 ②③) |
|
||
| **P-2b**(候选链两级 → 三级) | ✅ 已修 + **E2E 验收通过**(本节 ②③) |
|
||
| **E2E 缺口**(§8.11-⑤) | ✅ **已补齐**(本节 ②③④) |
|
||
| **在册缺陷表** | **空**(P-1 / P-2 / P-2b 全关闭) |
|
||
| §9 第 10 条(relay 静默丢订阅) | 回头条件(当前代码路径**不可达**)⇒ 非在册缺陷、无需动作 |
|
||
| 清单 §五 第 7 步剩余(内容分发 · 房间层 · 游戏) | **全部待拍板** |
|
||
| 入口 §4(骨干节点服务范围 A/B) | **待拍板** |
|
||
| 「Manager 重启后是否自动拉起既有实例」 | **待拍板** |
|
||
|
||
⇒ **覆盖网络线(presence 部分)线内可停**:在册缺陷清空、E2E 补齐、剩余项**全部落在「需拍板」区**。按**登记门禁**(用户 2026-09-17 明令:待拍板未定 ⇒ 不登记下一棒)**本棒不登记接续**,等拍板后再建。
|
||
|
||
#### ⑧ 本轮新登记(⛔ R7:只报告、未动手)
|
||
|
||
- **O-1(线外 · 集群化/模型落地面)**:`enter` 时 47 日志出现 `model landing failed, falling back to env injection Error: ENOENT … open '/var/lib/dshs/users/4092b965-…/home/.credentials.yaml'` —— 即**用本地路径读远端实例 home**(与序⑬ Q2 所记真因**同族**)。**影响**:`landModels` 退化为 env 注入(有兜底、**用户可见面仍 200**);**归属**:`RemoteSpawner.resolveApiKey`(平台模型落地),**不在本线 §3.1 文件集** ⇒ 仅登记。
|
||
- **O-2(观察项)**:`OBS-15` p95 本棒 = **1770 ms**(序㉒ 曾见 14220 ms,阈值 15000 ms)⇒ 抖动主导、**未越界、⛔ 未改阈值**。
|
||
|
||
**登记**:本棒**不登记下一棒**(线内可停 + 剩余项全为拍板项);下一棒的触发条件 = 用户对 **B(内容分发)/「Manager 重启后自动拉起实例」/入口 §4 骨干服务范围**中的任一项拍板。
|
||
|
||
**回填后指纹自检**:§8.13 位于 §8 之后 ⇒ **§8 前缀指纹 `f612858344077420cff5f1f9ef1c942f` 应保持不变**。
|
||
|
||
---
|
||
|
||
## §9 回头条件(**一出现必须回头**;⛔ 不许自行扩范围、⛔ 不许调生产值去凑)
|
||
|
||
1. **要动生产值才能继续** —— 需要改 `RELAY_FAILOVER_*` / `HB_SEC` / burst 任一 ⇒ **停下报告**(D1)。
|
||
2. **要扩大暴露面** —— 需新增公网监听口 / 放开遮蔽 / 跨网络或跨租户订阅 ⇒ **命中 R5** ⇒ 停下出权限评估。
|
||
3. **要超出 §3.1 的文件集** —— 除参数表 / 测试 / 探针 / 文档外,改动溢出到 `src/**` 的其他层 ⇒ **停下报告**,别扩大。
|
||
4. **批合并失效** —— 一次状态变化推不出 ≤1 帧,或稳态出现非零 presence 帧 ⇒ **停下报告**(D4 的前提被证伪)。
|
||
5. **观测跑不出单值** —— `OBS-13/14/15` 在真机拿不到可判定的数字 ⇒ **停下报告**,改口径(⛔ 不许把未验证的项标 PASS)。
|
||
6. **非静态失败** —— 跨网络订阅被**静默放行**(而非显式拒绝),或未知帧号被**静默丢弃** ⇒ **立即停**(假绿,本线头号教训)。
|
||
7. **要求"先有房间层才能验收"** ⇒ **停下报告**:本单口径是**节点层单值判据**,房间层不在范围内。
|
||
8. **零回归退化** —— `npm test` 低于 **162/161/0/1**、或 `--scene all` 出现 FAIL、或探针低于 **12/12** ⇒ **停下报告**。
|
||
9. 🔴 **任何回滚 / 演练 / 夹具路径要写 `RELAY_FAILOVER_COOLDOWN_MS=0`** ⇒ **禁止**(不是"回头",是禁令)。
|
||
10. 🆕 **序㉑(P-1 修复)新增**:**relay 侧"静默丢订阅、socket 仍活"** ⇒ 门的判据(订阅已建立 ∧ 链路活着)**察觉不到**,镜像会长期不动而没人知道。当前代码里这条路径**不可达**(唯一清空 `session.subs` 的是显式 `UNSUB`;relay 重启 / 会话回收都会断 socket)。**一旦出现(现象 = 订阅在册却再也收不到任何 `PRESENCE` 帧,且 `subs` gauge 与客户端 `state` 不一致)** ⇒ 正解 = **心跳帧携带订阅态断言**,或**周期性 `SNAP` 复核**;⛔ 不许靠"缩短 TTL"(那只是把 P-1 反向重犯)。
|
||
|
||
---
|
||
|
||
## §10 指纹与状态
|
||
|
||
| 项 | 值 |
|
||
|---|---|
|
||
| 本单 §8 前缀指纹 | **`f612858344077420cff5f1f9ef1c942f`**(落单时算出,2026-09-17 17:2x;口径 = `sed '/^## §8 回报格式/,$d' … \| md5sum`) |
|
||
| 参数表指纹(改前) | `8f08e74b026e6e5b5e1b3db813f031ae`(序⑰ 收口值,2026-09-17 17:2x) |
|
||
| 代码仓 HEAD(落单时) | `bc0dd2c`(已 push,远端 `master` 一致) |
|
||
| 本单状态 | **已执行完毕(线内可停)**:序⑲(落地 + 真机验收)→ 序⑳(`OBS-09` 口径修正 + 收益量化 ⇒ **P-1 在册**)→ 序㉑(P-1 + P-2 均已修,§8.11)→ 序㉒(P-2b 已修 + P-1 长窗复测通过,§8.12)→ **序㉓(P-2 / P-2b 的 E2E 验收通过 + 在册缺陷表清空 + 线内可停,§8.13)** ⇒ 剩余 = §9 第 10 条(回头条件)+ **三项待拍板**(`B` 内容分发 / 「Manager 重启后自动拉起实例」/ 入口 §4 骨干服务范围) |
|
||
| 参数表指纹(现行) | **`42238175d84319ada99afa56d583f9db`**(序㉑/序㉒/序㉓ 收口值;**序㉒ 与 序㉓ 均未变** —— 两棒无数值改动 ⇒ 同时是"零生产值改动"的自证;前值链 `659c08949c193102f774822905fde3b7` ← `13de5f9b77c486d71e5b83ec909b17b2` ← `8f08e74b026e6e5b5e1b3db813f031ae`) |
|
||
| 本棒边界自证 | ⛔ 未改码 / ⛔ 未动 47·106 / ⛔ 未 commit·push / ⛔ 未改任何生产值 / 🔴 `COOLDOWN_MS=0` 仅以"禁令"形式出现 |
|
||
|
||
---
|
||
|
||
## §11 规划棒回报(序 ⑱ · 2026-09-17 17:22–17:2x)
|
||
|
||
**做了什么**:只出规划 —— 产出本单(**287 行 / 纯 LF**),⛔ 零代码改动、⛔ 零服务器改动、⛔ 零 commit / push、⛔ 未改任何生产值。
|
||
|
||
**本棒实测三条证据**(§0.1 原文):`grep -rilE "\b(room|chat)\b" src | wc -l` = **0**(零应用层)|`grep -ril presence src` = **仅 `src/net/relay/placement.ts`**(第 21 行是注释)|`grep -n "req.url === '/status'" src/net/relay/server.ts` = **413** 且 **1234** 行原文写明「`/status` 是 Manager …」⇒ **现役在线态 = 拉快照(要替换的那条路)**。
|
||
|
||
**本棒的核心判断**:入口 §2 旧结论「presence 无从下手」**要收窄** —— 正确表述是「**应用层 presence 无从下手、节点 presence 正当时**」:序 ①–⑰ 已把它全部前置做完,且落地载体(`src/net/relay/**` 共 15 个模块)**今天就存在**。
|
||
|
||
**判据与边界**:
|
||
- 单内 **§4.2 真取舍 = 空**、**§4.3 待拍板 = 空**(三个曾考虑的项均有客观判据 ⇒ 自决拍掉);**§4.4 R5 评估 = 未命中**(新增监听口 / 凭据 / 入站均 **0**,可见面只收窄)。
|
||
- ⛔ 未规划 **B(内容分发 / 块级内容寻址)** —— 仍待拍板,只登记一行(§0.4-1 / §3.3-1)。
|
||
- ⛔ 未为生产值留任何改动口;**§9-9 把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写成硬禁令**(不是"回头条件")。
|
||
|
||
**登记**:下一棒 = **序 ⑲ 执行棒**,automation **`107b8e38-ccbd-4dcf-8b49-25dad7c89be9`**(名称「覆盖网络线-序19执行棒-presence节点在线态」,**一次性**,`scheduledAt` = **2026-09-17 17:28** = 收口 +4 min,`nextRunAt` = 1789637280000)。
|
||
|
||
**回填后指纹自检**:§11 位于 §8 之后 ⇒ **§8 前缀指纹 `f612858344077420cff5f1f9ef1c942f` 应保持不变**(口径 = `sed '/^## §8 回报格式/,$d' 交接单_presence在线态_20260917.md | md5sum`)。
|
||
|