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

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

93 KiB
Raw Blame History

交接单 · 观测口径重构(OBS-11 计数 → 白名单集合)+ 在册小缺陷清算排序(2026-09-17)

  • 状态:⏳ 待执行(规划棒产物,2026-09-17 14:5x 出单)
  • 出单者:覆盖网络线-序11规划棒(automation 3d4dffc0-356e-4bc6-9628-b7d664da7db9)
  • 执行方式:规划与执行分离 ⇒ 执行会话只按本单开工、不读规划会话上下文
  • 上游依据(现取证据,⛔ 不必重跑):
    1. 交接单_检测时延与deadline_20260917.md §8.8-4 ③ 逐口对账(序⑩ 收口回填)—— OBS-11 78→77 的名字 = 20000(47 实例档、无活跃实例)
    2. 同单 §8.8-4 末段「🔴 ③ 的副产品」 —— 计数判据的两处结构缺陷(ⓐ 对状态敏感 ⇒ 假红|ⓑ 对替换式变化不敏感 ⇒ 假绿)
    3. 参数表_覆盖网络_20260917.md §6(OBS-11 现判据 = LISTEN_COUNT / NFT_RULES 相等)
  • 成本纪律:本单的取证命令都已写死在 §2 / §6,执行会话照抄即可(⛔ 不要另做全库探索)

§1 目标

一句话(可判定「做完了没有」):scripts/overlay-probe.cjs 的 OBS-11 由「两个计数相等」改为「白名单集合」判据(必在集 ⊆ 实际 ⊆ 必在集 ∪ 允许集 ∪ 运行期派生集,差集点名打印),nft 同族缺陷一并按同一思路改造;改造后 ① 47 无活跃实例时 OBS-11 不再假红;② 「一进一出」替换式变化必须能被判红(假绿实证见 §6-E2);并给出在册 6 条小缺陷的清算排序(只排序 + 最小验收 + 回滚,本单不修)。

本单不追求:把 OBS-09(47 无活跃实例)判绿 —— 那是环境态,归 §5.9-Q3。


§2 只读前置(S0 必须先核实;任一条不符 ⇒ 停下报告,⛔ 不硬闯)

# 事实 命令(本机 cwd = 工作区根;47 走 ssh -p 22 bt-server) 期望输出
P1 47 监听口原文全量(判据形态的来源) ssh -p 22 bt-server "ss -lntp" > _中间产物_待清理/seq11/ss47-before.txt; wc -l < _中间产物_待清理/seq11/ss47-before.txt 77 行(含表头)⇒ 76 socket;每条都必须是 地址:端口 形态(⛔ 见 P1-注)
P1-注 🔴 形态风险(本单最容易踩):参数表的 cleanValue() 会剥掉 * 与反引号(overlay-probe.cjs:68-70)⇒ 白名单值里绝不能出现 *:443 这种写法,必须写 ss 实际打印的 0.0.0.0:443 / [::]:22。P1 的落盘原文就是唯一依据 grep -nE '\*' _中间产物_待清理/seq11/ss47-before.txt 零命中(若有命中 ⇒ 停下报告,形态与 2026-09-17 的认知不同)
P2 nft 判据的两条取数路径是否都可用 ssh -p 22 bt-server "nft --version; nft -j list ruleset | head -c 200; echo; nft list ruleset | wc -l" 版本行有输出|-j 回 JSON(首字符 {)|文本行数 = 72。⚠️ 若 -j 不可用 ⇒ 不得放弃判据,走文本退化路径并在输出里显式标记(§5-S2-d)
P3 参数表现取指纹(本单出单基线) sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum 99e9e17b0c1ce0550e4bc7626a5a0494
P4 代码仓干净度(防越界) git -C D:/github/dsh_shenxian status --short | wc -l; git -C D:/github/dsh_shenxian status --short src/ | wc -l; md5sum D:/github/dsh_shenxian/scripts/overlay-probe.cjs 总 43|src/ 改动 0|探针 md5 现取并落 §8.9
P5 relay 端点动态落点(⛔ 它不是固定值,每次重启都会变) ssh -p 22 bt-server "curl -s http://127.0.0.1:20080/status" | python3 -c "import sys,json;d=json.load(sys.stdin);print([e['localPort'] for e in d['endpoints']])" 形如 [46147](序⑩ 现值 46147;⛔ 开工时以现取为准)

⚠️ _中间产物_待清理/seq11/ 是本单的取证落盘目录(先 mkdir -p)。收口时保留(供第三方复核),⛔ 不要写进正式档案。


§3 范围

改(3 类,全部是"观测面",⛔ 无一在生产路径上)

# 文件 改动性质 是否计入本单前置指纹
1 D:/github/dsh_shenxian/scripts/overlay-probe.cjs OBS-11 判据主体 + nft 同族改造 + 取数改为回传原文 代码,不进参数表指纹
2 参数表_覆盖网络_20260917.md §6(新增集合键、退役计数键)+ §10 新增 4 键 / 退役 2 键说明 会变 ⇒ §5-S5 必须重取并记 §8.9
3 工作区根 接续入口_覆盖网络线_20260916.md §0(1 行) 口径指针 入口无指纹口径

⛔ 不动(明确写死,防顺手扩大)

  • ⛔ src/** 一字不改(本单是观测面改造;git status --short src/ | wc -l 必须仍 = 0)
  • ⛔ 不动任何生产阈值:RELAY_FAILOVER_* / HB_SEC / burst 语义 / RELAY_MAX_HOSTS / MemoryHigh·MemoryMax
  • ⛔ 不开新监听口 / 不新增凭据 / 不放宽 nft / 不碰 nginx / 不重启 dshs 或 relay(本单无生产中断)
  • ⛔ 不 commit / 不 push(未获授权)
  • ⛔ 不回去改已收官的历史单(交接单_检测时延…md 等记录的是当时实况;新口径以参数表为单一来源 ⇒ §5-S5 只在参数表内写"退役说明",不追改历史单)
  • ⛔ 不做:presence / 房间层 / 内容分发(清单第 7 步)|106 agent 面吃引导链(已判不执行)|worker 拨出方向、relay 自研形态、打洞实现|同机候选去重 / 并发竞速 open(已判不做)|第 4/5 台真机(已拍板 = 本机多实例)

§4 决策点

已定(本规划棒自决,可推翻)

  1. 判据形态 = 三集包含式集合判据(不是"计数 + 容差",也不是"只报不判"):
required ⊆ actual            // 必在集:缺一条 ⇒ FAIL(点名缺项)
actual   ⊆ required ∪ allowed ∪ derived
                             // 多一条 ⇒ FAIL(点名多出项)
derived  = { 127.0.0.1:<p> | p ∈ /status.endpoints[].localPort }   // 唯一来源 = relay 自身 /status
allowed  = 表中静态允许集(含区间,见 §5-S1)

依据:required 抓"消失"、包含式抓"新增"、derived 让合法动态落点有名字 ⇒ 替换式变化(一进一出)必然被两条断言之一命中。

  1. LISTEN_COUNT 的"有/无实例态"两值 —— ⛔ 不表达,直接消除:实例档 20000(w-47)/ 21000(w-106)进允许区间 ⇒ 有实例则出现(合法)、无实例则不出现(不红)⇒ 状态无关化。 ⛔ 不新增 LISTEN_COUNT_UP / LISTEN_COUNT_DOWN(那等于把"对状态敏感"这条缺陷编码进参数表 = 净退化,违 R11)。
  2. nft 同族缺陷一并改造(结论:改,但机制与监听口不同):nft 是内容不是端口 ⇒ 用 nft -j 入站 accept 集合 ⊆ 白名单(语义判据,比行数更贴近"零新增暴露面"的本意);nft list ruleset | wc -l 降级为"仅打印的上下文",不再参与判 PASS/FAIL。⛔ 退化为文本解析时必须显式标记,⛔ 不得静默改判据(text-fallback 字样进输出行)。
  3. LISTEN_COUNT / NFT_RULES 退役为"历史留档":两行留在参数表(供对账),但 OBS-11 判据列不再引用;退役说明写在参数表 §6 内(单一来源内自解释),⛔ 不追改历史单。
  4. 假绿/假红的自证方式 = 夹具 + 真机受控临时口(§5-S3 / §6-E2、E3)—— 判据改造必须自带"旧判据会放过、新判据能抓住"的实证,否则等于换了个说法。

硬门(三条,⛔ 任一违反即停手)

  • D1:分解类前提未复现前 ⛔ 不许改任何生产值(本单只改观测面,本就不该碰生产值 ⇒ 违反即意味着范围外扩)
  • R7:范围外缺陷先报告、不动手(§5.9 的 6 条里,本单只执行 Q6 的"结构化吸收",其余全部只排序)
  • 🔴 ⛔ 不许把 RELAY_FAILOVER_COOLDOWN_MS=0 写进任何回滚 / 演练 / 夹具路径(该值已判「看似合法、实际自锁」:num() 的 /^\d+$/ 放行 '0',但归零会让"失败候选必须被排除"失效 ⇒ 候选链卡死,实测 121–123 s 无切换;序⑩ 已把 --scene 4c / ctrl 整体移除并显式拒绝)。本单不涉及任何切流演练 ⇒ 全单不该出现这个键。

§4.3 待你拍板 = 空(本单零上抛;§5.10 的范围外清单只登记、不定序)


§5 步骤

S0 · 只读前置

照 §2 跑完 P1–P5。任一条与期望不符 ⇒ 停下报告(附原文)。⚠️ P1 与 P1-注 是全部设计的形态依据,必须先落盘再动手。

S1 · 定义白名单集合(改参数表 §6,⛔ 值一律按 P1 原文写)

新增 4 键(全部加在 §6 阈值表内,KEY_RE 能解析;⛔ 值内不得含 |、*、反引号):

键 含义 值形态(示例;实际以 P1 原文为准)
LISTEN_REQUIRED 必在集:缺失 ⇒ FAIL。口径 = 平台工作必需 且 不受实例/端点在线态影响的固定口 逗号分隔的 地址:端口,如 0.0.0.0:22,[::]:22,0.0.0.0:80,0.0.0.0:443,0.0.0.0:888,0.0.0.0:3080,127.0.0.1:20080
LISTEN_ALLOWED 允许集(非必在):出现合法、消失不红 同上形态,如 0.0.0.0:58888,127.0.0.1:8765,127.0.0.1:15432,127.0.0.1:19100
LISTEN_ALLOWED_RANGES 区间允许集(实例档 / 拨号池) 127.0.0.1:20000-20099,127.0.0.1:21000-21099(两段取自序③ P3 的实例端口区间隔离)。⚠️拨号池不在此键内 —— 复用表内既有 DIAL_POOL_BOUND(25000–25063)派生,⛔ 不重复写死(避免两处漂移)
NFT_ALLOW_INBOUND 入站 accept 白名单(S4 用) tcp:22,tcp:80,tcp:443,tcp:888,tcp:3080,tcp:58888(+ 无端口匹配的必要规则另立一个标记位,形态见 S4)

成员归属的判据(写进参数表备注,防下一棒再吵):

  • 必在 = 它不在 ⇒ 平台本身坏了(sshd / nginx 80·443 / 宝塔 888 / 平台门户 3080 / relay 20080)
  • 允许 = 它不在 ⇒ 只是某个可选项没开(运维面板 58888、管理 UI 8765、PG 15432、Worker 19100、实例档、拨号池)

⚠️ 若 P1 原文显示某口与上表推断不符(例如 888 实际不在监听)⇒ 以原文为准,并把该口从 LISTEN_REQUIRED 移到 LISTEN_ALLOWED(本单不追求"凑齐九个")。

S2 · 改探针 scripts/overlay-probe.cjs(本单唯一代码改动)

a. 取数:远端只回传原文,归一化在本地做

  • 现状 listenTotal=$(ss -lntp | wc -l) ⇒ 改为回传原文:echo "listenRaw=$(ss -lntp | base64 -w0)"(单行、base64 -w0 字母表无 = 除末尾填充 ⇒ 与现有 key=value 解析兼容)。
  • nft 同法:echo "nftRaw=$(nft -j list ruleset | base64 -w0; echo <退化标记>)" —— ⛔ -j 失败必须能分辨(见 d)。
  • ⛔ 仍只准一次 ssh(现有 remoteFacts() 一条命令取全部事实的设计不许破)。

b. 归一化(本地)

  • ss 行:跳过表头 / 空行 ⇒ 按 \s+ 切分取 Local Address:Port 列 ⇒ 从右侧第一个 : 拆 host/port([::]:22 必须拆对,⛔ 不能用左侧 indexOf)。
  • 产出集合 actual,逐条保留原文字符串(报错要点名)。

c. 判据(替换 overlay-probe.cjs:251-259 的 OBS-11)

missing = required \ actual                  // 缺项 ⇒ FAIL
extra   = actual \ (required ∪ allowed ∪ ranges ∪ derived)   // 多出 ⇒ FAIL
ok = missing.length === 0 && extra.length === 0 && relayListenTotal === relayListenLoopback && relayListenTotal > 0
  • 输出行(≤12 行的约束不许破):OBS-11 集合 必在 n/7 允许 m 区间 k 派生 d 实际 t 多出 x 缺失 y
  • 差集逐条点名写到 stderr:OBS-11 多出 127.0.0.1:9999 / OBS-11 缺失 0.0.0.0:443
  • ⛔ LISTEN_COUNT / NFT_RULES 不再被 r.num() 读取(退役;两行留在表内仅供对账)

d. nft(S4 的实现细节,与 c 同批落地)

  • nft -j 可用 ⇒ 解析 JSON:取 input 链(family ip/ip6 的 filter/inet 视 P2 原文)中 verdict 含 accept 的规则 ⇒ 归一为 {proto}:{dport}(无端口匹配的规则归为专用标记,如 established)。
  • nft -j 不可用 ⇒ nft list ruleset 文本按行抽 accept 行 ⇒ 同一判据,但输出行尾加 nft=text-fallback(⛔ 不许静默)。
  • 判据 = acceptSet ⊆ NFT_ALLOW_INBOUND;多出逐条点名(OBS-11 nft 多出 tcp:9999)。

e. 零数字纪律(判据见 §6-E5):新增代码不得出现任何 ≥3 位的数字字面量(端口/池/日期全部走参数表或从取回数据里取)⇒ grep -nE "[0-9]{3,}" scripts/overlay-probe.cjs 仍须零命中。

S3 · 假红 / 假绿的自证(本单的核心验收,⛔ 不许省)

a. 夹具模式(离线、零生产副作用):给探针加两个仅测试用 argv:--listen-fixture <file> / --nft-fixture <file>(文件内容 = 远端原文,即 ss -lntp / nft -j 的输出)。

  • 夹具模式下不 ssh;输出行首加 ⚠️ FIXTURE 标记,stderr 同时标一次(⛔ 防止被下游当成生产结论)。
  • 退出码仍按判据给 0/1(否则先红后绿无法表达)。
  • ⛔ 夹具模式必须配 --table <副本>(避免误改生产参数表)。

b. 三份夹具 + 新旧判据对照表(落盘 _中间产物_待清理/seq11/fx-*.txt,三份都从 P1 原文机械改写,⛔ 不手搓)

夹具 构造 旧判据(计数相等) 新判据(集合) 证的是
fx-normal.txt P1 原文 FAIL(77≠79 ⇒ 假红) PASS ⓐ 假红已消除
fx-swap.txt 原文去掉一行(如 0.0.0.0:888)加一行(127.0.0.1:9999)⇒ 行数不变 PASS(假绿!) FAIL(多出 9999 + 缺失 888,各点名) ⓑ 替换式变化已能抓住
fx-instance.txt 原文加上 127.0.0.1:20000(模拟 47 有活跃实例) FAIL(78≠79 ⇒ 假红) PASS 实例态不再影响判据

c. 真机正向复验(47,只读 + 一个受控临时口)

  1. 现态跑一次 ⇒ 期望 OBS-11 PASS,且 stderr 无差集(overlay-probe 总数由 10/12 → 11/12,剩余的 OBS-09 = 环境态)。
  2. 受控临时口:本机 ssh -p 22 bt-server "python3 -c '…'" 起一个只绑 127.0.0.1 的临时监听口(口值从 47 的空闲回环口里现取,⛔ 不猜、⛔ 不绑公网)⇒ 新判据必须 FAIL 并点名该口;随即关掉 ⇒ 必须回 PASS。 ⚠️ 该临时口的值必须记进 §8(自证"多出必红"用的就是它);⚠️ 用 timeout/trap 保证即使中断也会自动关闭(⛔ 不留残留监听口)。

S4 · nft 集合判据落表

按 S1 的 NFT_ALLOW_INBOUND 定值(以 P2 的 nft -j 原文为准,⛔ 不抄本单示例)。当前 nft list ruleset | wc -l = 72 ⇒ 归一后入站 accept 项数应远小于 72(含 established/related 一类无端口规则)。⚠️ 若发现白名单写不全(如 icmp 或 ct state 规则)⇒ 补白名单而不是放松判据;若确实存在"说不清来源的入站 accept"(如宝塔自行插入)⇒ 记进 §8.8 的"在册未办"并点名命令,⛔ 不许把它静默加进白名单。

S5 · 连带更新

  1. 参数表 §6:新增 4 键;LISTEN_COUNT / NFT_RULES 两行后加注「⛔ 已退役(序⑪ 集合判据替代)—— 仅对账用,不得再作为判据」(值保留,历史可比)。
  2. 参数表 §10 指纹:按表内既有口径重取现值,写进 §10 的"现取"行。
  3. 入口 接续入口_覆盖网络线_20260916.md §0:加/改 1 行口径指针(指向本单 §8.9 与参数表 §6)。
  4. ⛔ 不追改历史单(见 §3)。

S6 · 收口

按 §7 回滚准备 → 释放锁 → 用陈述句告知接续并登记下一棒(scheduledAt = 收口 + 2~5 分钟;⚠️ 下一棒 id 只能来自工具返回值,先 create、再取 id、再落盘)→ 写工作区日志 → §8 回填。


§5.9 在册小缺陷清算排序(② · 本单只排序 + 给最小验收与回滚,⛔ 不修)

序 缺陷 类型 最小验收(命令 ⇒ 期望) 回滚 依赖
Q1 mksess*.cjs 失效(/opt/dshs/mksess*.cjs 仍写 SQLite 旧库) 工具链(阻塞验收手段) 用它开一次临时 session ⇒ 实例面 curl -w '%{http_code}' 回 200/401(⛔ 非 000);用完即删 恢复原脚本 + 删 session 无(先做)
Q2 guest(w-106) 实例页 502 生产缺陷(用户可见) 同 Q1 的临时 session ⇒ w-106 实例页 200 cp -a <lib 备份>/lib/. /opt/dshs/lib/ → systemctl restart dshs Q1(没有临时 session ⇒ 无法验收)
Q3 OBS-09 实例面 000(47 无活跃实例) 环境态,非缺陷 47 起活跃实例 ⇒ OBS-09 绿 且 新 OBS-11 在"有实例态"下仍绿(20000 落在允许区间) 停实例(回到现态) Q2(同批做最省)
Q4 src/net/relay/** untracked 留档缺口 交付基线(需用户授权) git ls-files src/net/relay | wc -l > 0 且与工作区一致 不适用(只增不改) 用户授权(§4 提交边界 ⇒ 只报告)
Q5 client.ts.bak-seq7-* 残留 清理 ls src/net/relay/*.bak-* 为空 且 npm test 值不变(基线 160/159/0/1) 从同目录备份/产物重生成(⚠️ 删前先确认 lib 内已有等价产物) 无
Q6 参数表 LISTEN_COUNT=79 与现态的 1 条无名口 观测口径 被 S1–S3 结构化吸收(集合判据不再依赖计数);剩余 = 47 恢复活跃实例后复取应回 78 行 参数表备份恢复(指纹回 99e9e17b…) 本单即办;"78 行"的复核与 Q3 合批

排序理由(一句话):Q1 是"验收手段"(R4 规定实例面验收一律走临时 session)⇒ 它不修,Q2/Q3 连"做完了没有"都判不了;Q2 是唯一用户可见的坏(502);Q3 是环境态且与 Q2 同批最省;Q4 卡在授权(不在技术侧);Q5/Q6 是收尾。 本单实际动手的只有 Q6 的"吸收"部分(属 S1–S3 的副产品);⚠️ 其余 5 条 ⛔ 本单不修(R7),执行会话把它们原样转给下一棒。

§5.10 范围外(⛔ 只登记,本单不定序、不上抛)

presence / 房间层 / 内容分发(清单第 7 步)|106 agent 面吃引导链(已判不执行)|worker 只拨出 / relay 自研 / 不做打洞实现|同机候选去重 / 并发竞速 open(已判不做)|第 4/5 台真机 = 本机多实例(已拍板)。

⛔ 这些的先后顺序属"业务优先级",按 §1 判据属边界外 ⇒ 本单只登记、不排序、不决策。


§6 验收(命令 + 期望输出 + 退出码;可被第三方复现)

# 项 命令(cwd = 工作区根) 期望
E1 新判据在真实态全绿 node "D:/github/dsh_shenxian/scripts/overlay-probe.cjs"; echo "rc=$?" PASS OBS-11 …(stderr 无差集);10/12 → 11/12(剩 OBS-09 环境态);rc=1(因 OBS-09)
E2 假绿实证(本单核心) node …/overlay-probe.cjs --table <副本> --listen-fixture _中间产物_待清理/seq11/fx-swap.txt 旧判据 PASS / 新判据 FAIL,且 stderr 同时点名多出与缺失各 1 条(对照表全文进 §8)
E3 真机受控临时口 S3-c 两步(起口 ⇒ 跑探针 ⇒ 关口 ⇒ 再跑) 起口后 FAIL 且点名该口;关口后回 PASS;收口时该口必须已关闭(ss -lntp 复核 77 行)
E4 nft 判据两路可用 ssh -p 22 bt-server "nft -j list ruleset | head -c 50" + 一次探针 acceptSet ⊆ NFT_ALLOW_INBOUND;走退化路径时输出含 text-fallback
E5 零数字纪律未被破 grep -nE "[0-9]{3,}" D:/github/dsh_shenxian/scripts/overlay-probe.cjs 零命中(退出码 1)
E6 零回归(⛔ 四个都验) npm.cmd test(Node 22)|node …/overlay-failover-drill.cjs --scene all|ssh -p 22 bt-server "ss -lntp | wc -l; nft list ruleset | wc -l" 测试 ≥ 160/159/0/1|--scene all 12 PASS / 0 FAIL|77 / 72(本单不产生新监听口)
E7 越界自证 git -C D:/github/dsh_shenxian status --short src/ | wc -l; git -C D:/github/dsh_shenxian status --short | wc -l src/ = 0;总数 = 43 + 本单新增(⛔ 无 src/ 改动、无 .bak 残留)
E8 回滚可用 §7 的恢复命令 + 重跑探针 回到 10/12(OBS-11 重新红)
E9 参数表指纹已更新并记录 sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum 与 §8.9 记录值一致(⛔ 不得仍等于出单基线 99e9e17b…)

退出码约定(沿用探针原设计,⛔ 不改语义):全绿 0 / 任一红 1 / 用法或取数失败 2。


§7 回滚(全部秒级,⛔ 无生产中断)

层 改前准备 回滚命令
探针 cp -a D:/github/dsh_shenxian/scripts/overlay-probe.cjs _中间产物_待清理/seq11/overlay-probe.cjs.bak-<ts>(⛔ 不进 src/,收口时删除仓内任何 .bak) cp -a _中间产物_待清理/seq11/overlay-probe.cjs.bak-<ts> D:/github/dsh_shenxian/scripts/overlay-probe.cjs
参数表 cp -a 参数表_覆盖网络_20260917.md _中间产物_待清理/seq11/参数表.bak-<ts> 拷回 ⇒ 复核指纹回到 99e9e17b0c1ce0550e4bc7626a5a0494
入口 只加 1 行 ⇒ 手工删该行 同左
临时监听口 §5-S3-c 的 timeout/trap 兜底 ssh -p 22 bt-server "ss -lntp | wc -l" 必须回 77
  • ⛔ 本单不重启 dshs / relay ⇒ 无在线用户影响;⛔ 回滚路径不含任何 RELAY_FAILOVER_* 值(尤其 ⛔ 不含 …COOLDOWN_MS=0)。
  • ⛔ .bak 只在执行期存在:验收全绿后立即删除仓内 .bak(client.ts.bak-seq7-* 残留就是反例 ⇒ 本单顺手立规矩,并把结果写进 §8)。

§8 回报格式(执行会话按此回填;⛔ 缺一节即算未完成)

### 8.1 结论(1 行)        :OBS-11 集合判据是否落地 / E1–E9 逐条 PASS·FAIL
### 8.2 只读前置 P1–P5     :逐条贴命令与输出(P1 的 77 行原文落盘路径 + 形态核对)
### 8.3 白名单定值         :LISTEN_REQUIRED / ALLOWED / ALLOWED_RANGES / NFT_ALLOW_INBOUND 四个键的**最终值**(含"为什么是这几个")
### 8.4 假红假绿实证       :E2 的三行对照表(夹具 ⇒ 旧判据 / 新判据)+ 三份夹具的 md5
### 8.5 真机正向复验       :E1 / E3 的命令原文与输出(含临时口的**口值**与"关闭后已复原"证据)
### 8.6 nft 判据           :`-j` 是否可用、归一后的 accept 集合、与白名单的关系(⚠️ 若有"说不清来源"的项 ⇒ 点名并转 §8.8)
### 8.7 零回归 / 越界自证  :E5–E7 原文(npm test 计数、--scene all 计数、ss/nft 计数、git status 计数、src=0)
### 8.8 未过项 / 在册未办  :逐条写「现象 / 已做到哪一步 / 回头条件」;§5.9 的 Q1–Q5 原样转下一棒
### 8.9 指纹(可复现核对口径)

§8.9 指纹

  • 参数表:sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum ⇒ 出单基线 99e9e17b0c1ce0550e4bc7626a5a0494(执行后必须已变并在此回填现值)
  • 本交接单前缀(§8 及其后不计入):sed '/^## §8 回报格式/,$d' 交接单_观测口径与在册缺陷_20260917.md | md5sum ⇒ 现取值由出单棒在入口与本棒回复中登记(⛔ 本文件内不自嵌,避免自指)
  • 交叉证据(收口复取):交接单_检测时延与deadline_20260917.md = d903b4eeabf25ef381379cdbaac77e8a(§8 前缀口径)|参数表 出单基线见上
  • 原始取证落盘:_中间产物_待清理/seq11/(ss47-before.txt = P1 原文;fx-normal.txt / fx-swap.txt / fx-instance.txt = 三份夹具;overlay-probe.cjs.bak-<ts> = 探针回滚副本)

§8 执行回报(序⑫ 执行棒 · 2026-09-17 15:0x–15:2x)

⚠️ 本回报追加在 §8 之后 ⇒ §8 前缀指纹 3ece0f87… 未变(回填不进入前缀,与序⑨/⑩ 同例)。 ⚠️ 单头「状态:⏳ 待执行」一行保持原样不动 —— 改它会动前缀指纹;本棒状态以 §8.1 与本行下方为准(已执行完毕)。

8.1 结论(1 行)

✅ OBS-11 集合判据已落地;E1–E9 = 9 全绿(其中 E6-2 在第 3 次尝试才通过 —— 前两次被 106 的 sshd MaxStartups 限流中止,属环境级、非本单引入,已入册 §8.8-1)。overlay-probe 10/12 → 11/12(剩 OBS-09 = 环境态);假红已消除 + 假绿已能抓(§8.4 有对照表)。

8.2 只读前置 P1–P5

# 命令(cwd = 工作区根) 实测 期望 判定
P1 ssh -p 22 bt-server "ss -lntp" > …/seq11/ss47-before.txt 2>/dev/null; wc -l < … 77 行(=表头 1 + socket 76) 77 ✅
P1-注 awk 'NR>1 && $1=="LISTEN"{print $4}' … | grep -c '\*' 0 0 ✅(⛔ 按实质判据;见 §8.8-2-①)
P2 ssh -p 22 bt-server "nft --version; nft -j list ruleset | head -c 120; … nft list ruleset | wc -l" nftables v1.0.4|-j 回 JSON(首字符 {)|文本 72 行 同 ✅
P3 sed '/^## §10 指纹/,$d' 参数表… | md5sum 99e9e17b0c1ce0550e4bc7626a5a0494 同 ✅
P4 git -C D:/github/dsh_shenxian status --short | wc -l;… src/ | wc -l 总 43|src/ 20 43|0 总 ✅;src/ ⛔ 期望值本身有误 ⇒ 见 §8.8-2-②
P5 ssh -p 22 bt-server "curl -s 127.0.0.1:20080/status" → 取 endpoints[].localPort [40985](与 relay 20080 同 PID 781675) 现取为准 ✅(⚠️ 动态值,序⑩ 的 46147 已过期 —— 印证"⛔ 不能写死")

P1 原文的 76 条监听口分类(这是白名单定值的唯一依据): 0.0.0.0:22 / [::]:22 / 0.0.0.0:80 / 0.0.0.0:443 / 0.0.0.0:888 / 0.0.0.0:8765 / 0.0.0.0:58888 / 127.0.0.1:3080 / 127.0.0.1:15432 / 127.0.0.1:19100 / 127.0.0.1:20080 / 127.0.0.1:40985 + 127.0.0.1:25000–25063(64 口,拨号池)。 ⇒ 7 必在 + 4 允许 + 1 派生 + 64 拨号池 = 76 逐条有名、零无名口(对账闭合)。

8.3 白名单定值(四个键的最终值 + 为什么是这几个)

键 最终值 为什么是这几个
LISTEN_REQUIRED 0.0.0.0:22,[::]:22,0.0.0.0:80,0.0.0.0:443,0.0.0.0:888,127.0.0.1:3080,127.0.0.1:20080 判据 = 它不在 ⇒ 平台本身坏了(sshd/nginx 80·443/宝塔 888/平台门户 3080/relay 回环口)。⚠️ 与单内示例的唯一差异 = 3080 写在 127.0.0.1 上(47 实测绑回环,⛔ 不是 0.0.0.0:3080)—— 单内示例写错,按 P1 原文为准
LISTEN_ALLOWED 0.0.0.0:58888,0.0.0.0:8765,127.0.0.1:15432,127.0.0.1:19100 判据 = 它不在 ⇒ 只是某个可选项没开(宝塔面板口/管理 UI/控制面 PG/本机 worker agent)⇒ 出现合法、消失不红
LISTEN_ALLOWED_RANGES 127.0.0.1:20000-20999,127.0.0.1:21000-21999 两台 worker 的实例端口区间。🔴 span 取 1000,不是单内示例的 100 —— 实测 47 的 dshs-worker.service(经 /proc/<pid>/environ)与 106 的 /etc/dshs-worker.env 均为 DSHS_INSTANCE_PORT_BASE=20000/=21000 + DSHS_INSTANCE_PORT_SPAN=1000;并集 [20000,22000) 正好落在 relay 声明窗口 [19000,22000)(--base 19000 --span 3000)之内 ⇒ 自洽。⛔ 拨号池不在此键内(复用表内既有 DIAL_POOL_BOUND 派生,避免两处漂移)
NFT_ALLOW_INBOUND tcp:22,tcp:80,tcp:443,tcp:888,tcp:3080,tcp:58888 入站 accept 的语义白名单(见 §8.6)。⚠️ 归一形态 = <proto>:<dport>;「无 dport 匹配的 accept」归一为 <proto>:any —— 当前 47 的 input 链 0 条规则 ⇒ 集合为 ∅、本键暂无标记位(将来若出现必须显式加进来,⛔ 不许放宽判据)

"有/无实例态"的处置(⭐ 本单的结构性决定):⛔ 不新增 LISTEN_COUNT_UP / LISTEN_COUNT_DOWN 两个计数(那等于把"对状态敏感"编码进单一来源 = 净退化,违 R11)⇒ 把实例档 20000 / 21000 直接放进允许区间:出现不报、消失不红,状态敏感性问题被消除而不是被打补丁。

8.4 假红假绿实证(E2 核心)

判据口径先说清(⛔ 否则数字会被误读):旧判据口径 = ss -lntp | wc -l(含表头,比 LISTEN_COUNT=79);新判据口径 = socket 集合大小(不含表头)。三份夹具全部由 P1 原文机械改写(mk-fixtures.py,⛔ 不手搓):

夹具 构造 行数 旧判据 新判据 证的是
fx-normal.txt P1 原文逐字 77 FAIL(77 ≠ 79)⇒ 假红 PASS(多出 0 缺失 0) ⓐ 假红已消除
fx-swap.txt 原文 + 20000 + 21000(模拟两实例活跃 ⇒ 行数回到 79)− 0.0.0.0:888 + 127.0.0.1:9999(一进一出) 79 PASS(假绿!) FAIL —— 同时点名 OBS-11 多出 127.0.0.1:9999 + OBS-11 缺失 0.0.0.0:888 ⓑ 替换式变化已能抓住
fx-instance.txt 原文 + 127.0.0.1:20000 78 FAIL(78 ≠ 79)⇒ 假红 PASS 实例态不再影响判据
fx-nft-extra.json nft -j 原文 + 一条 INPUT 上的 accept tcp dport 9999 — — FAIL —— 点名 OBS-11 nft 多出 tcp:9999 nft 侧"多出必红"
nft-text.txt nft list ruleset 文本原文(喂给 --nft-fixture ⇒ 必走退化路径) 72 旧口径行数相等 ⇒ PASS PASS + 输出含 nft=text-fallback;accept 集合 = ∅(FORWARD 的 accept 未误抓 ⇒ 链感知有效) 退化路径同一判据、⛔ 不静默
fx-nft-text-extra.txt 上述文本 + 在 chain INPUT { 后插一行 tcp dport 9999 counter accept 73 — FAIL —— 点名 tcp:9999 退化路径同样"多出必红"

三份 ss 夹具 md5:fx-normal.txt = e53031f7f2485b4609a69128effb7210|fx-swap.txt = dadd94f85df9a2c1e115afce17bfd91e|fx-instance.txt = 45cee07da44d5c3fa4a92475f03be2ed 入场码:fx-normal rc=0|fx-swap rc=1|fx-instance rc=0|fx-nft-extra rc=1|nft-text rc=0|fx-nft-text-extra rc=1(⛔ 夹具模式退出码仍按判据给,否则"先红后绿"表达不出来)

⚠️ 与单内 §6-E2 的一处构造差异(必须记):单内写「fx-swap = 原文去掉一行加一行、行数不变」并同时期望「旧判据 PASS」。这两条指不并存 —— 若行数仍 = 77,则旧判据也 FAIL ⇒ 假绿证不出来。本棒按实质判据构造:先补两个实例口把行数抬到 79(= LISTEN_COUNT),再做一进一出 ⇒ 两条期望同时满足(旧 PASS/新 FAIL)。这是"让判据可被证伪",⛔ 不是改判据。

8.5 真机正向复验(E1 / E3)

E1(真实态):node scripts/overlay-probe.cjs ⇒ rc=1(因 OBS-09)|PASS 11 / FAIL 1(唯一红 = OBS-09 实例面 本机:20000=000 对端:21000=000)|OBS-11 原文: PASS OBS-11 集合 必在 7 允许 4 区间 3 派生 1 实际 76 多出 0 缺失 0 |nft accept 0 多出 0 |relay 口绑定回环=1/1 条 ⇒ 10/12 → 11/12 达成本单期望(原红项 OBS-11 由"77 ≠ 79"假红转为绿)。

E3(真机受控临时口) —— 口值 = 127.0.0.1:27000(选址理由:不在 required/allowed/ranges/derived/拨号池任何一处);起口方式 = ssh 'timeout 45 bash -s' < tmplisten.sh(timeout 兜底 ⇒ 即使中断也会自动关闭):

步 命令 实测
起口前 ssh bt-server "ss -lntp | wc -l" 77
起口后 ss -lntp | wc -l ⇒ 78;跑探针 FAIL OBS-11 集合 … 多出 1 缺失 0|stderr 点名 OBS-11 多出 127.0.0.1:27000|rc=1
关口后(wait 到 timeout 自然结束) ss -lntp | wc -l ⇒ 77;再跑探针 PASS OBS-11 集合 … 多出 0 缺失 0|rc=1(仅 OBS-09)

⇒ "多出必红、恢复必绿",且收口时该口已关闭、ss 回到 77(⛔ 零残留监听口)。

8.6 nft 判据

  • nft -j 可用(P2:nftables v1.0.4,首字符 {)⇒ 正路走 JSON,未走退化。
  • 🔴 归一后的 accept 集合 = ∅。原因(本条同时纠正了一个旧口径):47 的 nftables 只有 22 条规则,而 ip filter INPUT 链 0 条规则、policy = accept;nft list ruleset | wc -l = 72 里绝大多数是 ip nat / dsh_egress(output) 与注释行 ⇒ 行数根本不是入站暴露面(这正是"NFT_RULES 计数"这条旧判据的第二处结构缺陷,已在参数表内标注退役)。
  • 归一实现:取 hook=input 的链 + 沿 jump 的传递闭包,其内 verdict 含 accept 的规则 → <proto>:<dport>(dport 支持 数/区间/集合;无端口匹配 → <proto>:any)。
  • 判据 = acceptSet ⊆ NFT_ALLOW_INBOUND:今天 ∅ ⊆ 白名单 成立 ⇒ PASS。
  • ⚠️ 诚实标注:因为集合为 ∅,当前白名单并未被"现实样本"压过 ⇒ 本棒为此补了两条夹具(fx-nft-extra.json 注入 tcp:9999;fx-nft-text-extra.txt 注入文本行)证明"多出必红",⛔ 而不是让它"永远绿着过"。
  • "说不清来源的入站 accept" = 0 条 ⇒ 无需转 §8.8。

8.7 零回归 / 越界自证

项 命令 实测 期望
npm test npm.cmd test(Node v22.22.2) # tests 160 / # pass 159 / # fail 0 / # cancelled 0 / # skipped 1;rc=0 ≥ 160/159/0/1 ✅(与基线逐字一致 ⇒ 零回归)
--scene all node scripts/overlay-failover-drill.cjs --scene all 12 PASS / 0 SKIP / 0 FAIL;rc=0(⚠️ 第 3 次尝试;见 §8.8-1) 12 PASS / 0 FAIL ✅
监听口 / nft ssh bt-server "ss -lntp | wc -l; nft list ruleset | wc -l" 77 / 72 77 / 72 ✅(本单不产生新监听口)
越界:src/ git status --short src/ | wc -l 20(改动前 20 ⇒ Δ0) Δ=0 ✅
越界:总数 git status --short | wc -l 43(=改动前基线;⚠️ scripts/overlay-probe.cjs 是 untracked(??)⇒ 改它不改计数) 43 ✅
.bak 残留(仓内) git status --short | grep -i bak 空 空 ✅(本单备份全在 _中间产物_待清理/seq11/,⛔ 不进仓)
演练 env 残留 ls /etc/systemd/system/dshs.service.d/ + systemctl show dshs -p Environment 无 zz-drill-override.conf;DRILL_* 命中 0 0 ✅
E8 回滚可用 cp -a …bak-<ts> → probe ⇒ 跑探针 ⇒ cp -a …new-<ts> → probe 回滚后:FAIL OBS-11 监听口=77(阈值 79) … ⇒ 10/12;恢复后 md5 = d7e3ed77c1e0ea983b5ee879abde3130(与改后一致) 回到 10/12 ✅

8.8 未过项 / 在册未办(逐条:现象 / 已做到哪一步 / 回头条件)

§8.8-1 🔴(新发现 · 环境级 · 非本单引入)106 的 sshd 被 MaxStartups 限流 ⇒ 脚本化 ssh 偶发 rc=255,会让整场演练中止

  • 现象:--scene all 第 1 次在幕 1 全绿后崩(4 PASS / 0 FAIL,1m24s),第 2 次在启动阶段崩(8s,0 项),两次都停在 overlay-failover-drill.cjs 的 ssh() 抛 Error: Command failed: ssh … test106 …;第 3 次通过(12 PASS)。
  • 根因(证据级):106 的 journalctl -u sshd --since '-25min' 命中 586 条 error/refus/timeout/preauth(对照 47 仅 3 条),其中含原文 sshd[6366]: error: beginning MaxStartups throttling,另有大量 Connection closed by authenticating user root … [preauth]/invalid user ubuntu(外部爆破流量)+ kex_exchange_identification: read: Connection reset by peer。/etc/ssh/sshd_config 的 MaxStartups 为默认 10:30:100、106 的 fail2ban inactive ⇒ 未认证连接槽位被爆破流量打满 ⇒ 新连接被丢 ⇒ ssh rc=255。手工具复现:同一条重命令第 1 次 rc=255、第 2/3 次 rc=0(~700 ms)。
  • 已做到哪一步:① 已确认与本单改动无关 —— 本单零 src/ 改动、且演练不消费 overlay-probe.cjs,探针只连 47(47 侧 3 条错误、E1 全绿);② 已用第 3 次尝试取得 E6-2 的 12 PASS / 0 FAIL;③ 已定位到 sshd 侧的限流证据与爆破流量来源。
  • 回头条件:① 给 106 的 sshd 调 MaxStartups/起 fail2ban/把 106 的运维 ssh 改走 47 跳板(⚠️ 前两项属放宽/收紧一个安全参数 ⇒ 其中"放开限流"命中 R5,⛔ 本棒不做、不在本单范围内);② 或让脚本化 ssh 具备退避重试(属改 overlay-failover-drill.cjs ⇒ 单外,先报告)。
  • 在册:转入下一棒(平台面既有缺陷,会影响任何针对 106 的脚本化 ssh,不只是演练)。

§8.8-2 🟡 与单不符的两处 —— 已按"实质判据"处理(供下一轮修单模板时吸收)

  1. P1-注 的期望值不可满足(单方瑕疵):单内写 grep -nE '\*' ss47-before.txt ⇒ 零命中。实测不可满足 —— ss -lntp 的 peer 列恒为 0.0.0.0:*(76 行每行都有 *)。⇒ 实质判据 = Local Address:Port 列零 *:awk 'NR>1 && $1=="LISTEN"{print $4}' … | grep -c '\*' = 0 ✅。⚠️ 顺带踩到一次:首跑 ssh … > f 2>&1 把 3 行 ssh PQ 告警写进了取证文件(77 → 80)⇒ 取证落盘一律 2>/dev/null(已复取干净版)。
  2. P4 / E7 的 src/ 期望值有误(单方瑕疵):单内写「src/ 改动 = 0」。实测 20,且与本单无关 —— 它是既存未提交基线(17 个 M + 3 个 ??:src/net/relay/、src/web/routes/overlay.ts、src/worker/relay-tunnel.ts;后者正是 §5.9-Q4 的留档缺口)。⇒ 实质判据 = 本单不得新增 src/ 改动 ⇒ 20 → 20(Δ0) ✅。⚠️ 另一处易误读:总计数 43 不因本单而变,因为 scripts/overlay-probe.cjs 本身是 untracked(??)—— 不能拿"总数没变"当"没改文件"的证据,判据要落到 md5sum 与 Δ 上。

§8.8-3 📌 §5.9 的 Q1–Q5 原样转下一棒(⛔ 本棒未修,R7)

序 缺陷 本棒动作
Q1 mksess*.cjs 失效(/opt/dshs/mksess*.cjs 仍写 SQLite 旧库)⇒ 阻塞实例面验收 ⛔ 未修,原样转办(下一棒第一件)
Q2 guest(w-106) 实例页 502 ⛔ 未修,原样转办
Q3 OBS-09 实例面 000(环境态非缺陷) ⛔ 未动(本棒不起实例;起实例属 Q3,与 Q2 同批最省)
Q4 src/net/relay/** untracked 留档缺口 ⛔ 只报告(卡在用户授权,不在技术侧)
Q5 client.ts.bak-seq7-* 残留 ⛔ 未动;本棒已以身作则立起".bak 只在执行期存在"这条规矩(本棒仓内 .bak = 0)
Q6 参数表 LISTEN_COUNT=79 与现态的 1 条无名口 ✅ 已结构化吸收(集合判据不再依赖计数;那"1 条"就是 20000 实例档 ⇒ 现已在允许区间内)。⚠️ 剩余项「47 恢复活跃实例后复取应回 78 行」的复核与 Q3 合批

⚠️ 另记一条(本棒未触发但已备查):RELAY_FAILOVER_COOLDOWN_MS 全程 未被触碰;grep 演练 / 回滚 / 夹具路径中该键的 =0 形态 = 0 命中(收口复核:dshs.service.d 下无 zz-drill-override.conf、DRILL_* env 命中 0)。

8.9 指纹(收口复取 · 全部现取)

项 命令 值
参数表(§10 不计入) sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum 8f08e74b026e6e5b5e1b3db813f031ae(出单基线 99e9e17b0c1ce0550e4bc7626a5a0494 ⇒ 已变 ✅ E9)
本交接单前缀(§8 及其后不计入) sed '/^## §8 回报格式/,$d' 交接单_观测口径与在册缺陷_20260917.md | md5sum 3ece0f870cba67d0113a4c5f9de9d812(回填后未变 ✅)
交叉证据:检测时延单(§8 前缀口径) sed '/^## §8 回报格式/,$d' 交接单_检测时延与deadline_20260917.md | md5sum d903b4eeabf25ef381379cdbaac77e8a(未变 ✅)
探针(代码) md5sum D:/github/dsh_shenxian/scripts/overlay-probe.cjs 改前 0cd76d986273e04631510aa7d215d16c ⇒ 改后 d7e3ed77c1e0ea983b5ee879abde3130
入口 md5sum 接续入口_覆盖网络线_20260916.md 收口取(本单未在此登记,避免自指)
原始取证落盘 — _中间产物_待清理/seq11/(P1 原文、6 份夹具、nft47.json/nft-text.txt、status-fixture.json、探针 .bak-<ts> 与 .new-<ts>、npmtest.txt、drill-all-*.txt、real-*.txt、生成器 mk-fixtures.py/mk-nfttext-fx.py/nft-summary.py)⇒ 保留供第三方复核(⛔ 不写进正式档案)

§9 执行回报(序⑬ 执行棒 · 2026-09-17 15:2x–15:5x · 在册小缺陷清算 Q1→Q3)

9.1 结论(逐条)

序 结果 判据
Q1 ✅ 已修并验收 /opt/dshs/mksess{,-guest}.cjs 改插 PG;修前实证写进 SQLite(PG sessions=2 / SQLite=3);修后临时会话可直插 PG,实例面 curl 回 401(⛔ 非 000)
Q2 🔴 未达成 —— 阻塞在一条新发现的数据面缺陷 w-106 用户 POST /api/dsh/enter 仍 500;真因不是单内记的 landModels → writeHomeFile,而是「relay 流上的 HTTP keep-alive 复用」⇒ 见 9.3
Q3 ✅ 达成(探针 12/12 · rc=0) OBS-09 绿(本机 20000=401、w-106 42461=401)且"有实例态"下 OBS-11 仍绿(实际 77 / 多出 0 / 缺失 0、20000 落在允许区间)

9.2 Q1 · mksess*.cjs 从 SQLite 改到 PG(✅ 已修)

  • 前提复现(D1):跑旧 /opt/dshs/mksess-guest.cjs ⇒ PG sessions 仍是 2、SQLite sessions 变 3 ⇒ 会话落错库、平台读不到(R4 的实例面验收手段整体失效)。
  • 改法:实现只有一份 = /opt/dshs/mksess.cjs —— 连接串从 DSHS_DB_URL env → /etc/dshs.env → dshs.service.d/*.conf 逐个找,⛔ 不把凭据固化进 0644 脚本;mksess-guest.cjs = process.argv[2]='guest'; require('/opt/dshs/mksess.cjs') 薄封装(保留原文件名 —— 档案 76/77 与 dsh-plugin-diagnose 等多处按此名引用)。
  • 验收:node mksess.cjs ⇒ token 64 字符、PG sessions 2→3、user_agent='poc-curl2' 命中;实例面 curl -o /dev/null -w '%{http_code}' http://127.0.0.1:20000/ = 401(⛔ 非 000)。
  • 指纹:mksess.cjs = a97c0f214650fbc552c07d804fd153be|mksess-guest.cjs = af6eab38c4ff5c365ee17e29d7b98a31。原件备份:/opt/dshs/mksess{,-guest}.cjs.bak-seq13-20260917-152653(本地副本 _中间产物_待清理/seq13/)。
  • 回滚:cp -a /opt/dshs/mksess*.bak-seq13-20260917-152653 回原名 + DELETE FROM sessions WHERE user_agent='poc-curl2'。
  • ⚠️ 遗留(R7 · 只报告未动):只改了 /opt/dshs/ 这一处;文档库与技能里对 mksess.cjs 的描述("DB 直插")仍是旧口径 ⇒ 需要时另起一小棒同步。

9.3 Q2 · guest(w-106) enter 500 —— 真因是数据面(新缺陷)

现象(3/3 稳定复现):

POST /api/dsh/enter (guest) ⇒ 500
{"statusCode":500,…,"message":"agent POST /fs/isdir → 400: {\"error\":\"Bad Request\",\"message\":\"Client Error\",\"statusCode\":400}"}

最小复现(1 条命令,在 47 上跑) —— 对拨号池回环口的同一条 TCP 连接连发两条请求:

curl -s --http1.1 -w ' -> %{http_code}\n' -X POST -H 'content-type: application/json' \
  -H 'x-dsh-agent-token: <w-106 的 agent_token>' -d '{"userId":"<guest id>","relPath":""}' \
  http://127.0.0.1:25000/fs/isdir http://127.0.0.1:25000/fs/isdir
⇒ {"isDirectory":true} -> 200
⇒ {"error":"Bad Request","message":"Client Error","statusCode":400} -> 400

(/touch 或 /status 打头、/fs/isdir 收尾同样复现;两条 GET 也复现 ⇒ 与请求体无关。)

取证链(逐层排除):

  1. 客户端无辜:Manager 侧 strace -f -e trace=write,writev -p <dshs pid> 抓到写出的就是一条格式完全正确的请求(POST /fs/isdir HTTP/1.1、content-length: 62、body 完整)。
  2. 走的就是拨号池口:ss 见文件面连的是 127.0.0.1:25000;[relay-dialer] 落点 127.0.0.1:25000 -> ops/w-106:19000(localPortFor('ops/w-106', 19000))。/touch、/status 因并发各占一条新连接 ⇒ 各占一条流 ⇒ 都成功;/fs/isdir 落到已被复用的那条 ⇒ 失败。
  3. 400 的出处已定死:body 与 Fastify node_modules/fastify/fastify.js:985 的 clientError 分支逐字一致 ⇒ 这是收到非法字节流时写裸 socket 的兜底响应,⛔ 不是任何业务路由返回的;它不写 pino 日志 ⇒ 这正是"106 worker 日志里什么都没有"的原因。
  4. 106 侧抓包(tcpdump -i lo -A -s0 'tcp port 19000'):
    • 全场只有 1 条 POST /fs/isdir 到达 agent,紧接着 agent 回 400;
    • 🔴 关键:57244 > 19000 的载荷是 HTTP/1.1 200 OK … {"isDirectory":true} —— 即worker 侧把 agent 自己上一份响应回灌给了 agent ⇒ agent 把响应行当请求行解析 ⇒ clientError 400。
  5. 因此:责任面收敛到 relay 的 stream 路由(src/net/relay/server.ts 的 stream 表 / workerStream 与 dialStream 的映射)与 worker 侧本地转发(client.ts#onOpenRequest / onRemoteData → st.tcp)+ 拨号池收尾(dialer.ts 的 tcp.pipe(duplex).pipe(tcp),duplex 关闭时没有 tcp.destroy())。

影响面(⚠️ 比在册描述严重):不是"guest 一个用户的 502",而是 任何 via='relay' 的 host(今天 = w-106) —— 只要一次业务动作对同一 host 发第 2 条 agent 请求(enter 的 isDirectory → launch → waitUpOnStatus 轮询…)就必失败 ⇒ w-106 用户"登录直达工作区"整体不可用(用户可见)。

已做到哪一步 / 卡在哪:故障面已收敛到上面那一组文件,但帧级取证(relay 侧 workerStream/dialStream 路由表逐帧日志)未做 ⇒ 尚未定位到具体代码行。

回头条件(必须回头):① 拿到 relay 帧级路由证据并定位代码行;② 修复判据 = guest enter 回 200、同一连接连发两次都 200、npm test 不退化(基线 160/159/0/1)、--scene all 不退化、overlay-probe 保持 12/12。

⛔ 本棒未动手(R7 + R11):这是数据面变更;在拿到帧级证据前盲改(例如只给拨号池补 tcp.destroy())会把"静默给出错误答案"换成别的形态,还可能掩盖 relay 侧的真错 ⇒ 属净风险,留作下一棒主题(序 ⑭)。

9.4 Q3 · OBS-09 +「有实例态下的 OBS-11」(✅ 达成 · 探针 12/12)

  • 起实例:① admin(w-47,本机直连,不经 relay) 经 POST /api/dsh/enter ⇒ 200、port=20000、status=running(这同时就是 Q1 的实例面验收:curl http://127.0.0.1:20000/ = 401,⛔ 非 000);② guest(w-106) 因 9.3 的缺陷堵在 enter,改由 106 的 worker agent POST /launch(带 uid=100002 + ws 目录)拉起 ⇒ 200、port=21000、pid=237067。⚠️ 该实例未经 Manager 租约(为把 OBS-09 的对端腿测出来而"出格"了一步,已在工作区日志登记)。
  • 探针结果:12/12 PASS · rc=0,其中
    • PASS OBS-09 实例面 本机:20000=401 w-106:42461=401 (阈值 ∈ {200,401})
    • PASS OBS-11 集合 必在 7 允许 4 区间 3 派生 2 实际 77 多出 0 缺失 0 |nft accept 0 多出 0 |relay 口绑定回环=1/1 条 —— 正是 Q3 要的那一问:有实例态下 20000 落在允许区间
    • PASS OBS-08 端点表 2 条 / 离线 0 条(比无实例态多 1 条)
  • ss -lntp | wc -l = 78 = 1 表头 + 77 socket(无实例态 76 + 实例 20000 一口)⇒ 无多余监听。
  • 回滚(要回到"无实例态"):systemctl stop dsh-100002-*.scope(106)+ 停 w-47 那个实例;此后 OBS-09 回到红 —— 那是环境态,非缺陷。
  • ⚠️ 回头条件:guest 实例是出格拉起的;一旦 Manager 的 reaper 回收它 ⇒ OBS-09 对端腿回 000(环境态,按 Q3 语义非缺陷)。

9.5 其余(R7 · 只报告,⛔ 未动手)

序 状态
Q4 src/net/relay/** untracked 留档缺口 —— 未动(卡在提交授权;git status --short src/net/relay/ = 1)
Q5 src/net/relay/*.bak-seq7-* 残留 —— 未动(本棒仓内 .bak = 0,规矩继续)
Q6 已被序⑫ 结构化吸收;本棒复取 实际 77、20000 在允许区间 ⇒ 可关闭
🔴 新发现 9.3 的 relay keep-alive 缺陷(= 下一棒主题)

9.6 越界自证 & 三条硬门

  • git status --short = 43(Δ 0)|git status --short src/ = 20(Δ 0)|仓内 .bak = 0。
  • D1:全程未改任何生产值 —— 参数表指纹仍 8f08e74b026e6e5b5e1b3db813f031ae、RELAY_FAILOVER_* / HB_SEC / burst 一字未动。
  • R7:Q4 / Q5 只报告;文档库・技能侧对 mksess 的旧描述未改(见 9.2 遗留)。
  • 🔴 RELAY_FAILOVER_COOLDOWN_MS=0:0 命中(未写入任何回滚 / 演练 / 夹具路径)。
  • R4 收尾:临时会话用完即删(DELETE … user_agent='poc-curl2' ⇒ DELETE 2,sessions 回 2 = 基线)。

9.7 指纹(收口现取)

项 值
Q1 产物(47) mksess.cjs a97c0f214650fbc552c07d804fd153be|mksess-guest.cjs af6eab38c4ff5c365ee17e29d7b98a31
参数表(§10 不计入) 8f08e74b026e6e5b5e1b3db813f031ae(未变 ⇒ D1 自证)
本交接单 §8 前缀(追加 §9 后复取) 3ece0f870cba67d0113a4c5f9de9d812(未变 —— §9 在 §8 之后,不进前缀口径)
探针(代码,未改) d7e3ed77c1e0ea983b5ee879abde3130
原始取证落盘 _中间产物_待清理/seq13/(mksess.cjs / mksess-guest.cjs / probe-isdir.cjs / q2-capture.sh);106 抓包与 47 strace 原文留在各机 /tmp(cap.pcap / st2.txt)
下一棒 automation 3e0a7b01-9257-4b76-b82f-4998c9f7eab0(scheduledAt = 2026-09-17 15:53,nextRunAt = 1789631580000)

§10 执行回报(序⑭ 执行棒 · 2026-09-17 15:53–16:1x · relay 流 keep-alive 复用缺陷)

10.1 结论(逐条)

项 结果
D1 · 先复现 ✅ 复现,与 §9.3 逐字一致:同一条连接连发两条 ⇒ {"isDirectory":true} -> 200 + {"error":"Bad Request","message":"Client Error","statusCode":400} -> 400
② 帧级取证 → 定位到行 ✅ 定位到 src/net/relay/client.ts#openStream 里的一行 duplex.on('data', …)(原 975 行)
③ 先红后绿 ✅ 红:目标端收到 2 段回灌字节 HTTP/1.1 200 OK …;绿:修后 T23 通过(无杂音)
④ 端到端验收 ✅ guest enter 200、同连接连发两次 200/200(三次 200/200/200);npm test 160 pass / 0 fail / 1 skip(基线 159 pass +T23);--scene all 12 PASS / 0 SKIP / 0 FAIL;overlay-probe 12/12 PASS · rc=0

10.2 复现(D1 自证)

命令 = §9.3 原文那一条(47 上,对拨号池口同连接连发两条)。修前:200 然后 400。⛔ 全程未改任何生产值。

10.3 帧级取证与定位(这是本棒的核心)

真因:RelayClient#openStream() 把读侧接回了出向。

// src/net/relay/client.ts(修前,openStream 内)
const duplex = new MuxDuplex({ onOut: (chunk) => this.pumpDial(id, chunk), … })
this.dialStreams.set(id, st)
duplex.on('data', (chunk: Buffer) => this.pumpDial(id, chunk))   // 🔴 就是这一行

机制(逐跳):目标(agent)的响应 → worker tcp.on('data') → pumpToRelay → relay → 拨号方 onRemoteData → dial.duplex.feed(payload) → push(chunk) 触发 'data' → 🔴 该监听把它 pumpDial() 又打回 relay → worker 写进 agent socket ⇒ agent 拿 HTTP/1.1 200 OK 当请求行解析 ⇒ 非法字节流 ⇒ Fastify clientError 兜底回 400(写裸 socket、不写 pino 日志)。

⇒ 200 被回灌 ⇒ agent 回 400 ⇒ 该 400 顺着同一条 keep-alive 连接被 curl 当成"第 2 条请求的响应" —— 这就是"第 2 条必 400"的真实来源(curl 打印的 400 不是第 2 条请求的应答,而是第 1 条响应的回灌回声)。

为什么现有测试抓不到:test/relay.test.mjs#dialRoundTrip 一凑够 text.length 就 resolve,多出来的那份回声没人看。

排除项(逐层):relay 服务端无辜 —— server.ts#onData → onDialerData 只把 DATA 投给对端(st.peer),不镜像回发送方;错误方向只能是拨号方自己产生的。dialer.ts 的 tcp.pipe(duplex).pipe(tcp) 形状正确(pipe 走 _write → onOut)。

10.4 改法(唯一改动)

  • src/net/relay/client.ts:删掉那一行 duplex.on('data', …),原地换成一段⛔禁挂说明(含 106 抓包证据与 T23 指针)。
    • 为什么删是安全的:出向唯一入口是 _write → onOut → pumpDial;duplex.write() 与 tcp.pipe(duplex) 都走 _write ⇒ 两者本就是同一条路,那个监听纯属多余且方向错。
    • 附带修好的一条:读侧不再被提前"吃掉" ⇒ openStream() 返回到 tcp.pipe(duplex) 之间到达的响应会留在读缓冲而不是被丢/被回灌。
  • test/relay.test.mjs:新增 T23「拨号流严格单向」—— 目标端逐帧记账(POST … 记 requests、其余记 garbage),一旦入向回灌 ⇒ garbage 非空 ⇒ 断言点名(不是"计数差不多"那种软判据)。
    • 文件已在 package.json 的 npm test 列表内 ⇒ ⛔ 未改 package.json(守住"改动不超出 src/net/relay/** + 其测试")。

10.5 先红后绿(原文级)

修前(旧 lib):
not ok 1 - T23 拨号流严格单向:入向的响应 ⛔ 不得被回灌进 agent socket
    拨号流不是单向的 —— 入向字节被回灌进 agent socket(共 2 段):
    ["HTTP/1.1 200 OK\r\ncontent-length: 19\r\n\r\n{\"isDirectory\":true}", …(同上)]

build 后:ok 1 - T23 …   # pass 1 / # fail 0

⚠️ 过程中踩到并修掉的测试卫生坑:onDown → teardownDialStreams() 会 duplex.destroy(new Error('link down: …')),而 MuxDuplex 上没有 'error' 监听 ⇒ Node 抛 uncaughtException,表现 =「T23 自己 pass、整个文件 fail」。生产侧 dialer.ts 有同形监听,测试里补上即可(已在 T23 的 t.after 注释里写死原因)。

10.6 端到端验收(原文级)

① guest POST /api/dsh/enter(同连接 ×2)
{"kind":"session","instance":{"id":"ae024d2c-…","port":21000,"status":"running"},"url":"https://guest.alotbuy.com/?token=…"} -> 200
(同上)-> 200

② 帧级复验:已分配的池口 = 25000(同连接 ×3)
{"isDirectory":true} -> 200 / 200 / 200        (修前 = 200 后必 400)
  • npm test = 160 pass / 0 fail / 1 skip(= 基线 159 pass + T23,零退化)
  • --scene all = 12 PASS / 0 SKIP / 0 FAIL(幕1-A 17874ms、幕4-A 18754ms 均 < deadline 30000ms)
  • overlay-probe(带参数表)= 12/12 PASS · rc=0
  • 部署:client.js(b8b29afba06ed6347cbefd29091f8c73)铺到 5 处 —— 47 /opt/dshs/lib、47 /opt/dshs-cluster/lib、47 /opt/dsh-relay/lib、106 /opt/dshs-cluster/lib、106 /opt/dsh-relay/lib;各留 .bak-20260917-1558xx。
    • 只重启了 47 的 dshs(Manager = 拨号方);dshs-relay / 106 dshs-worker 未重启(改动是拨号方专属一行,worker/relay 侧行为逐字未变)。
    • R7 传播面自证:铺之前对本机 lib/net/relay/*.js 与 47 /opt/dshs/lib/net/relay/*.js 逐文件对账 ⇒ Δ 只有 client.js 一个文件(无夹带改动)。

10.7 越界自证 & 三条硬门

  • D1:参数表指纹 8f08e74b026e6e5b5e1b3db813f031ae(未变) ⇒ 未改任何生产值;RELAY_FAILOVER_* / HB_SEC / burst 一字未动。
  • R7:git status --short = 43、src/ = 20(均 Δ0);仓内 .bak/.tgz = 0;⛔ 未 commit / 未 push。
  • 🔴 RELAY_FAILOVER_COOLDOWN_MS 置 0:本机仓 0 命中 / 47 lib 0 命中(未写进任何回滚·演练·夹具路径)。
  • R4:临时会话用完即删(DELETE 1 ⇒ sessions 回 2 = 基线)。
  • 未超范围:改动只有 src/net/relay/client.ts + test/relay.test.mjs 两个文件,未动 package.json、未动 worker 侧协议、未重启 relay 之外的服务(Manager 重启属本次修复必需,已按 R8 直接做)。

10.8 🔴 新发现在册缺陷 B(R7 · 只报告,未动手)

项 内容
现象 拨号池未分配的槽位,被任意一条连接碰到后 ⇒ 该槽位永久死亡;而 localPortFor() 之后照样把这个口号发出去 ⇒ ECONNREFUSED(本棒实测:[relay-dialer] 落点 127.0.0.1:25000 -> ops/w-106:19000 紧接 connect ECONNREFUSED 127.0.0.1:25000,enter 回 500 fetch failed)
出处 src/net/relay/dialer.ts#onConn:const key = slot.key; if (key === undefined) { slot.server.close(); tcp.destroy(); return } —— 关闭服务器本身,却不把该槽位从 slots 里摘掉,于是 key 仍是 undefined ⇒ localPortFor() 的 slots.find(s => s.key === undefined) 下次还会选中它
怎么被触发 本棒自己触发:口池刚绑好、落点还没分配时,对 127.0.0.1:25000 打了一次探针(§9.3 那条命令的"提前版")⇒ 25000 当场死掉;池报"64 个口"而 ss 只见 63,正是这个缺口
为什么算缺陷 属本线反复强调的"静默给出错误答案"同族:失败被推迟到下一次分配,且现场只有 ECONNREFUSED,看不出"这个槽位早废了"
候选修法(⛔ 未实施,留给下一棒先取证) ① onConn 撞到未分配槽位时只 destroy 连接、不 close 服务器(最小改动,但"外部随机连入"仍会被静默丢弃);② close 之后把槽位从 slots 摘掉/标记废弃,让 localPortFor 不再选中(更彻底,需处理 bound 计数与日志);③ 池口号改绑在独立的不可路由网段/更冷门口位并加"首次使用才分配"的显式状态机。判据:未分配槽位被连接后,status().pool 与实际在听口号数必须恒等,且 localPortFor 永不返回已死口号
回头条件 单独一棒处理;⛔ 在拿到"槽位被谁连、何时连"的帧级证据前不要只改 onConn

10.9 其余在册未办(原样转下一棒,⛔ 未动手)

序 状态
Q4 src/net/relay/** untracked 留档缺口 —— ⚠️ 本棒的修复正好落在该缺口内(client.ts 是新文件、test/relay.test.mjs 也是 ??)⇒ 二者目前只存在于工作区,不在版本库里。commit / push 仍需你的明确授权(本棒未获授权,未动)。
Q5 src/net/relay/*.bak-seq7-* 残留 —— 未动(本棒仓内 .bak = 0)
seq13 遗留 文档库・技能侧对 mksess.cjs 的描述("DB 直插")仍是旧口径 ⇒ 需同步时另起一小棒
长期在册 dsh-auto-handoff-chain 在文档库无副本 ⇒ 该技能的"三处同步"从未建立
§8.8-2 幕4-A 耗时仍贴近 deadline(本次 18754ms / 30000ms)⇒ 临界项,未恶化也未解决

10.10 指纹(收口现取)

项 值
修复源文件 src/net/relay/client.ts = 6ffb117f3f6df1c2456641e176529ce8
测试文件 test/relay.test.mjs = 14aa2808bc6446d34af19306d43c4e8d(1149 行,含 T23)
部署产物 lib/net/relay/client.js = b8b29afba06ed6347cbefd29091f8c73(五处远端同值)
参数表(§10 不计入) 8f08e74b026e6e5b5e1b3db813f031ae(未变 ⇒ D1 自证)
本交接单 §8 前缀(追加 §10 后复取) 3ece0f870cba67d0113a4c5f9de9d812(未变 —— §10 在 §8 之后,不进前缀口径)
原始取证落盘 _中间产物_待清理/seq14/(deploy-seq14.sh / evidence-47.sh / final-verify.sh);--scene all 原文 = 本地 /tmp/seq14-scene-all.txt
下一棒 automation 1933b18a-0c2a-46a2-829c-440a900fed08(「覆盖网络线-序15执行棒-拨号池槽位自毁缺陷B修复」,一次性,scheduledAt = 2026-09-17 16:14,nextRunAt = 1789632840000)

§11 执行回报(序⑮ 执行棒 · 2026-09-17 16:14–16:3x · 拨号池未分配槽位自毁「缺陷 B」)

11.1 结论(逐条)

✅ 缺陷 B 已修并端到端验收:src/net/relay/dialer.ts(1 个源文件)+ test/relay.test.mjs(新增 T24);判据 a / b / c 三条全绿;⛔ 未改任何生产值、⛔ 未 commit / push、零上抛;收口 6 件全办。

11.2 复现(D1:先复现、后动手 —— ⛔ 没在"只看到 ECONNREFUSED"的情况下动 onConn)

① 进程内确定性复现(_中间产物_待清理/seq15/repro-defect-b.mjs;私有口段 47000、私有实例段 47100,零生产影响)—— 修前原文:

① 池就绪(注册=true):status().pool = 4 · 实际在听 = 4 · 区间 47000..47003
② 对未分配口 47000 发一条连接 ⇒ connected
   status().pool = 4 · 实际在听 = 3  ⇒ 🔴 判据 a 不等(池在撒谎)
   该路径日志行数 = 0
③ localPortFor(ops/w-xs, 47110) = 47000(死口 = 47000)
④ 🔴 判据 b:落点不通 = ECONNREFUSED

② 47 现网事实(只读取证,落盘 A47.txt;⛔ 全程未连任何池口):

  • dshs 于 16:09:11 重启 ⇒ 池口 64 个全在听(ss -lntH 'sport >= :25000 and sport <= :25063' | wc -l = 64)、ss -lntp 总行数 79、逐口核对 无缺失。
  • journal 里 ECONNREFUSED 127.0.0.1:25000 全量 3 条,全部落在 16:00:31 / 16:00:35 / 16:00:40,全部是 POST /api/dsh/enter → 500 fetch failed(栈 = supervisor/remote-spawner.js:142 ← :211 ← web/routes/dsh.js:200)。
  • 而这三条紧跟在 16:00:27 的 [relay-dialer] 落点 127.0.0.1:25000 -> ops/w-106:19000 之后 ⇒ "落点已分配、却连不上" —— 这正是"该槽位早就死了"的演绎证据(唯一能关掉池槽位监听又保留槽位的路径就是 onConn 的 key === undefined 分支)。
  • 谁 / 何时连了未分配槽位:是我们自己的取证探针触发的,不是自然发生 —— 该路径零日志零计数(全量 journal grep -c '未分配' = 0),只能靠上面那组矛盾反推。⇒ 这也正是"必须补日志/计数"的直接理由。

11.3 定位(到代码行)

src/net/relay/dialer.ts#onConn:

const key = slot.key
if (key === undefined) { slot.server.close(); tcp.destroy(); return }

关掉的只是这个口的服务器,而槽位仍留在 slots 里、key 仍是 undefined ⇒ localPortFor() 的 slots.find((s) => s.key === undefined) 下次还会选中它 ⇒ 把一个没人听的口号当落点发出去。失败被推迟到下一次分配,现场只有 ECONNREFUSED。

11.4 改法(三个候选里取 ①,理由 = R11)

  1. onConn:⛔ 不再 close() 服务器,只 tcp.destroy() + 计数(新增 stray)+ 点名日志。 ⛔ 不取候选 ②(把槽位从 slots 摘掉):那等于让任意一条本地连接都能永久蚕食池容量(扫 64 次即可把池扫空)= 净退化(R11);候选 ③(换口段 + 显式状态机)超出 src/net/relay/** 必要范围。
  2. 纵深防御:localPortFor() 的空槽查找与 lruIdle() 都只认 server.listening 的槽;一个可成交槽都没有时**点名"哪几个口已不在听"**再拒(失败关闭,⛔ 绝不发死口号)。
  3. status():pool 改报实际还在听的槽位数(不再报 start() 那一刻的常量)+ 新增 stray 计数 —— 让"池在撒谎"这类事不可能不被看见。

11.5 先红后绿(原文级)

  • 红(新用例 + 旧 lib/):判据 a:池账(3) 与实际在听(2) 必须恒等 —— 不等即"池在撒谎" ⇒ 3 !== 2(ERR_ASSERTION)。
  • 绿(npm run build 后):ok 1 - T24 拨号池:未分配槽位被一条连接命中后 ⛔ 不得自毁… / # pass 1 / # fail 0。
  • 新用例 = T24(test/relay.test.mjs 尾部,文件 1318 行);⛔ 未改 package.json。

11.6 端到端验收(47 现网原文)

① 收口终验(verify-seq15.sh,含一次 systemctl restart dshs)—— ⚠️ 这个顺序就是缺陷 B 的复现顺序:

步 原文
干净池基线 池口在听数 = 64 |[relay-dialer] 本机落点池就绪:64 个口(25000..26000)
🔴 故意对未分配落点 25000 发一条连接 TCP 已连上 ⇒ 打后池口在听数仍 64、stray 日志 1 行:[relay-dialer] ⛔ 未分配落点 127.0.0.1:25000 收到一条连接 ⇒ 只丢弃该连接、槽位保留(累计 1 次)
guest POST /api/dsh/enter(同连接 ×2) 200 / 200(instance.port=21000 status=running)
帧级(该落点口) 落点口 = 25000(就是刚被打过的那一个)|该口在听 = 1|同连接连发 3 次 ⇒ 200 / 200 / 200({"isDirectory":true})
R4 sessions 清理后回 2|结束态池口 = 64

② 最终态复验(verify-seq15-final.sh,⛔ 不重启,就在收口现场把同一顺序再走一遍)= verify-47-final.txt,逐项与 ① 同(64 → 触发 → 仍 64 / stray=1 → 200 / 200 → 25000 在听 200×3 → 结束态 64)。

③ 零回归:npm test = 162 tests / 161 pass / 0 fail / 1 skip(基线 160 pass + 新 T24)|--scene all = 12 PASS / 0 SKIP / 0 FAIL(幕 4-A 19537 ms、幕 4-C 20809 ms / 30000 ms)|overlay-probe --table "E:/…/参数表_覆盖网络_20260917.md" = 12/12 PASS · rc=0(OBS-11 集合 … 实际 78 多出 0 缺失 0、OBS-09 本机:20000=401 w-106:33909=401)。

部署:lib/net/relay/dialer.js = 6446fe9b23bca2649137adcbf4bf9d1f,五处同值(47:/opt/dshs /opt/dshs-cluster /opt/dsh-relay;106:/opt/dshs-cluster /opt/dsh-relay,106 无 /opt/dshs);每处各留 .bak-20260917-1621xx;只重启了 47 的 dshs(拨号方专属改动)。

11.7 越界自证 & 三条硬门

门 自证
D1 参数表指纹(§10 截断口径)仍 8f08e74b026e6e5b5e1b3db813f031ae(未变)|⛔ 未动 RELAY_FAILOVER_* / HB_SEC / burst
R7 git status 总行数 43、src/ 20 均为既存基线(Δ0);本棒只改 src/net/relay/dialer.ts + test/relay.test.mjs(两者同处 untracked 的 src/net/relay/**、test/ 属既有缺口)|⛔ 未 commit / push|Q4 / Q5 只报告未动手;范围未越出 src/net/relay/** + 其测试(⛔ 未动 worker 侧协议、未重启 relay / 106)
COOLDOWN 远端严格判据(赋 0)0 命中|⚠️ 本棒 verify-seq15.sh 里的宽松正则 "\?0"\? 会误报 2 —— 命中 switcher.js:53 的默认字面量 num('RELAY_FAILOVER_COOLDOWN_MS', 300_000) 里的 0;口径以「值被赋 0」为准(grep -E '=\s*0|, 0\)|"0"')

11.8 其余在册未办(原样转下一棒,⛔ 未动手)

序 状态
Q4 src/net/relay/** untracked 留档缺口 —— ⛔ 卡在"提交 / 推送授权"(本棒未获授权,未动;本棒的修复与 T24 同样只存在于工作区)
Q5 代码仓 src/net/relay/*.bak-* 残留 3 个(本棒未清,R7)
seq13 遗留 文档库 / 技能侧对 /opt/dshs/mksess.cjs 的「DB 直插」描述仍是旧口径(实际已改 PG)
长期在册 技能 dsh-auto-handoff-chain 在文档库无副本 ⇒ 该技能「三处同步」从未建立
§8.8-2 幕 4-A 耗时 19537 / 20809 ms(贴近 deadline 30000 ms)⇒ 临界项,未恶化也未解决

11.9 指纹(收口现取)

项 值
修复源文件 src/net/relay/dialer.ts = f3a608a75f7edd0aa0de8cceb4de5615
测试文件 test/relay.test.mjs = 587f9d80e9330e33badb281e6bd6c719(1318 行,含 T24)
部署产物 lib/net/relay/dialer.js = 6446fe9b23bca2649137adcbf4bf9d1f(五处远端同值)
参数表(不计入 §11 口径) 8f08e74b026e6e5b5e1b3db813f031ae(未变 ⇒ D1 自证;口径 = sed '/^## §10 指纹/,$d' … | md5sum)
本交接单 §8 前缀(追加 §11 后复取) 3ece0f870cba67d0113a4c5f9de9d812(未变 —— §11 在 §8 之后,不进前缀口径)
原始取证落盘 _中间产物_待清理/seq15/:evidence-A-47.sh+A47.txt|repro-defect-b.mjs|deploy-seq15.sh+deploy-47.txt/deploy-106.txt|verify-seq15.sh+verify-47.txt|verify-seq15-final.sh+verify-47-final.txt|npmtest-seq15.txt|scene-all.txt|probe.txt
下一棒 automation 91c53ef8-c3f0-4bb3-9f0a-943790178176(「覆盖网络线-序16规划棒-会合中继拆分复核与在册收尾定序」,一次性,scheduledAt = 2026-09-17 16:36,nextRunAt = 1789634160000)

11.10 范围外 · 只报告(R7,⛔ 未动手)

ssh 别名 bt-server 指向的 32022 是失效残留口。 本机 ~/.ssh/config 里 bt-server 写的是 Port 32022,而 47 上 sshd 只监听 22(/etc/ssh/sshd_config:151 Port 22;master pid 733418 启动于 2026-09-16 22:12:48、/etc/ssh/ 下无 sshd_config.d drop-in;32022 无监听)。项目脚本一律显式写 ssh -p 22 bt-server(近 20 份脚本 42 处均如此)⇒ 只有"直接敲别名"会 Connection refused。本棒为绕开它多花了 3 次调用;⛔ 未改 47 的 sshd_config、⛔ 未改本机 ssh config(等指示)。


§12 执行回报(序⑯ 规划棒 · 2026-09-17 16:36–16:5x · 会合/中继拆分复核 + 在册收尾定序)

12.1 结论(1 行)

① 会合 / 中继从 Manager 拆分 ⇒ 判「不做」(四个耦合点 C1–C4 + 两步改造 S3/S4 逐条已被覆盖,未覆盖部分 = 无)|② 剩余在册四条已定序并出单 ⇒ 产物 = 工作区根 交接单_在册收尾_20260917.md(243 行;§8 前缀指纹 bab83b7219b2669d5a6e9f1acf782e1f,全文 md5 ca13b9ded3d71268f8b0e56755d7aea4)= 序 ⑰ 执行棒(S1 mksess 文档口径 → S2 技能三处同步 → S3 Q5 清理 → S4 幕 4-A 临界项)。⛔ 只出规划:未改码、未动 47/106、未 commit/push、零上抛。

12.2 复核判定(① 二选一 → 判「不做」)

靶子 现状证据(只读取证) 判定
C1 会合地址硬编码在 Worker env src/config.ts:485-490:DSHS_RENDEZVOUS_URL 优先 / DSHS_TUNNEL_TARGET 降兜底(注释原文「覆盖网络 S1:会合地址出 env」) ✅ 序② P0-2 覆盖
C2 中继落点 = Manager loopback + 两端同号 src/config.ts:498-500 DSHS_RELAY_DIAL_PORT_BASE=25000 / SPAN=1000 / POOL=64 ⇒ 落点在 Manager 本机回环池;src/net/relay/dialer.ts:11「Manager 也像 worker 一样只拨出一条 wss」 ✅ R5 覆盖
C3 可达性登记磨掉「经谁中转」 src/net/rendezvous.ts:103-106「按 via 选实现的注册表」+ Reachability.via(:27/:65/:98)+ agentBaseUrlOf() 单一取址入口(src/net/reachability.ts:99) ✅ S2/P2 覆盖
C4 控制面 PG 走同一隧道 方案 §8.1 实测:106/47 worker env 均无 DSHS_TUNNEL_STATIC_PORTS ✅ 前提不成立
S3 回环别名 方案 §9.1 自我证伪(gatewayports no)⇒ 改「实例端口区间隔离」,且 R5 后「同号」前提消失 ✅ 已被更强做法替代
S4 中继独立成单元 / 会合可换机 / 多实例 dshs-relay 独立单元(47+106,只绑回环 20080)+ 序⑥ S8 第二中继 + 序⑦「杀 47 ⇒ 切 106」实测 ✅ R2 / 序⑥S8 / 序⑦ 覆盖

处置已落:会合中继拆分_取证与改造方案_20260916.md 头部加状态块(「已完成使命 · 仅存档 · ⛔ 勿再按 S0–S4 开工」+ 逐条覆盖指针),正文未改(历史档案属性)。 ⚠️ 顺带登记(⛔ 未处置):src/worker/tunnel.ts(SSH 隧道)现已是生产死路径(47 同机不建隧道、106 走 wss://)⇒ 是否删 = 独立决策(可能保留作 ssh 兜底变体)。

12.3 在册项定序(② → 序 ⑰ 执行棒,写进同一张单)

序 项 为什么在这个位置
S1 mksess 文档口径(「DB 直插」→「PG 直插」) 必须最先 —— 它改 skills/** 正文,S2 的镜像同步要把它的产物一起带上 ⇒ 二者共用一次 scp(硬顺序)
S2 技能 dsh-auto-handoff-chain 三处同步 紧随 S1(同面、共用一次 scp);它是自动接续链路载体 ⇒ 影响后续每一棒的可复现性
S3 Q5 .bak-seq7-* 清理 与 S1/S2 无依赖,同属「零服务副作用批」⇒ 合批省一次抢锁
S4 §8.8-2 临界项(幕 4-A) 唯一动服务项 ⇒ 置末;收尾 restart dshs 归零不影响已完成工作

排序一句话:S1 → S2(硬依赖)→ S3(零风险清理)→ S4(唯一动服务,置末)。

12.4 本棒取证(只读 3 条命令族;⛔ 未连任何池口、未动 47/106)

# 命令(cwd = D:/github/dsh_shenxian) 关键输出
1 grep -rn "DSHS_TUNNEL_TARGET|32022" --include=*.ts src + grep -rn "DSHS_RENDEZVOUS_URL" --include=*.ts src C1 现状(新变量优先、旧变量兜底)
2 grep -rn "via|25000|dialer" src/net/rendezvous.ts src/net/relay/dialer.ts + grep -rn "DSHS_RELAY_DIAL" src/config.ts C2/C3 现状
3 find src test scripts -name "*.bak-*" + git ls-files src/net/relay | wc -l + git ls-files --error-unmatch <bak> 🔴 Q5 真实范围 = 5 个(非 3 个);src/net/relay tracked = 0(Q4 自证)

🔴 本棒两条新事实(写进单里,均已自决处置):

  1. Q5 范围修正:3 → 5 —— 除原单记的 src/net/relay/{client,directory,main}.ts.bak-seq7-20260917-112114 外,还有 2 个同批残留:src/web/server.ts.bak-seq7-20260917-112114、src/worker/tunnel.ts.bak-seq7-20260917-112114。5 个全部 untracked(git ls-files --error-unmatch 报 pathspec 不匹配)⇒ 删除只减 untracked、不动版本库基线。已按 D2 扩到 5 个(同批、同类、均为过期快照 ⇒ 无回滚价值)。
  2. 04-调整方案/** 里的 mksess 旧口径不改 —— 档案属性是当时事实(当时确实直插 SQLite),改写 = 销毁溯源。已按 D1 只改操作性载体(02-运维手册.md + skills/** + 本机技能 10 行),在运维手册 R4 段写一行勘误指针。
  3. 幕 4-A 判据取三档递进(D3):p95 ≤ 24 s = 健康|24–27 s = 临界(记录、⛔ 不判 FAIL)|> 27 s = 回头条件触发 —— 观测样本 19.5/20.8/22.2 s,把「假红 vs 预警提前量」的取舍消解掉。

12.5 越界自证

门 自证
D1 参数表(截断口径)仍 8f08e74b026e6e5b5e1b3db813f031ae(未变)|⛔ 未动 RELAY_FAILOVER_* / HB_SEC / burst
R7 本棒零代码改动、零服务器改动(只新增 1 份单 + 给 1 份存档方案加状态块)|⛔ 未 commit / push(HEAD 仍 640813e)|Q4 / Q5 只报告未动手
COOLDOWN ⛔ 本棒未触任何演练 / 夹具路径;单内已把 🔴 禁令原文写进 §3.2
ssh 本棒未对 47 / 106 执行任何命令(纯本地只读)⇒ 无 -p 22 相关风险面

12.6 在册未办(原样转 §12.8 的下一棒;⛔ 只报告)

序 状态
Q4 src/net/relay/** untracked 留档缺口 —— 🔴 卡在「提交 / 推送授权」(实测 git ls-files src/net/relay = 0)⇒ ⛔ 未写进执行范围,单内 §0.4 只登记一行
Q5 已定序进 S3(范围修正为 5 个)
§8.8-1 106 sshd MaxStartups 限流(外部爆破)⇒ 脚本化 ssh 偶发 rc=255;⛔ 不放宽(命中 R5)⇒ 仍在册
§5.10 三项 presence / 房间层 / 内容分发 —— ⛔ 属业务优先级,单内 §3.3 只登记候选与优缺点、不定序

12.7 指纹(收口现取)

项 值
本棒产物 交接单_在册收尾_20260917.md = 243 行;全文 md5 ca13b9ded3d71268f8b0e56755d7aea4;§8 前缀指纹 bab83b7219b2669d5a6e9f1acf782e1f(口径 = sed '/^## §8 回报格式/,$d' … | md5sum);行尾 = 纯 LF(CR=0 自证)
参数表(不计入本单口径) 8f08e74b026e6e5b5e1b3db813f031ae(未变 ⇒ D1 自证)
被加状态块的存档方案 会合中继拆分_取证与改造方案_20260916.md(头部新增状态块,正文未改;原 278 行)
本交接单 §8 前缀 3ece0f870cba67d0113a4c5f9de9d812(未变 —— §12 在 §8 之后,不进前缀口径)
本机技能 mksess 命中 文档库 skills = 10 行 / 本机技能 = 10 行(同数 ⇒ 两副本同步态;待 S1 同改)
下一棒 automation 067b0892-964e-4deb-bf84-4f1c37bebea8(「覆盖网络线-序17执行棒-在册收尾(mksess口径/技能三处同步/Q5清理/幕4A临界项)」,一次性,scheduledAt = 2026-09-17 16:44,nextRunAt = 1789634640000)

12.8 下一棒(序 ⑰ · 执行棒)

开工依据 = 工作区根 交接单_在册收尾_20260917.md(§8 前缀指纹 bab83b7219b2669d5a6e9f1acf782e1f),按 §5 的 S1 → S2 → S3 → S4 顺序执行。 已登记:automation 067b0892-964e-4deb-bf84-4f1c37bebea8(一次性,scheduledAt = 2026-09-17 16:44,nextRunAt = 1789634640000)⇒ 已用陈述句告知用户。 两条硬门:D1(不改任何生产值)|R7(Q4 ⛔ 只报告、⛔ 不 commit/push;范围外先报告后动手)。 🔴 ⛔ 不许把 RELAY_FAILOVER_COOLDOWN_MS=0 写进任何回滚 / 演练 / 夹具路径。 已定项(⛔ 不必再上抛):D1 只改操作性载体(档案不动)|D2 Q5 扩到 5 个|D3 幕 4-A 三档判据|D4 不处理 tunnel.ts 死路径|D5 触发回头条件时停下报告、不调生产值。 收口必须:释放锁 + 陈述句告知 + 登记下一棒(id 取自工具返回值)+ 推进 接续入口_覆盖网络线_20260916.md §0/§2 + 写工作区日志 + 在本单回填 §8。⛔ 不 commit / 不 push。


§13 序 ⑳ 新增在册缺陷(2026-09-17 18:2x–19:2x · ⛔ 本棒只登记、未动手)

本棒在量化 presence 真机收益时新查证两条。两者均按 R7 处理:先报告、后动手。 完整取证、命令原文与判定 = 交接单 交接单_presence在线态_20260917.md §8.10。

# 缺陷 证据(可复现) 影响面 判定
P-1 presenceFresh() 的新鲜度判据与"稳态零帧"互相打架 ⇒ 订阅生效期间 /status 轮询照旧在跑,presence 的核心收益未兑现 ① 判据 = state==='subscribed' && now − max(lastSnapAt,lastPushAt) ≤ PRESENCE_TTL_MS(src/net/relay/client.ts:593-600)② 稳态 pushed/snaps 自 relay 重启起恒为 1(一帧都不推)⇒ 45 s 后必然过期 ⇒ 门重开 ③ 实测 R_now ≈ 10.9 次/分钟 vs 基准 R_base = 60 000 / RELAY_STATUS_POLL_MS(5000) = 12 次/分钟 ⇒ 降幅 1.10× ④ 门开关时间线:35 min 内 11 次切换、挂起窗口 7/20/20/10/45 s ⇒ 占空比 5.0%(理论降幅 1.05×,与实测吻合) 全平台(Manager→relay 的读取量≈无变化);用户不可见 ⇒ 属成本 / 设计目标项 另立单(技术项,下一棒可自决)
P-2 src/web/server.ts#translateEndpoint 键口径不一致 ⇒ 整个闭包是死分支(via='relay' 的实例面翻译从不生效) ① :733 hostVia.set(name, row.via),name = logicalName(networkId, row.id) = ops/w-106(键 = 逻辑名)② 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 ⇒ 当前零实际影响 另立单(本棒无验证条件:无活跃实例 + 该闭包不可先红后绿,需先抽纯函数)

P-1 候选修法(供下一棒选): A) 把任何入站帧(含协议心跳)计入新鲜度 —— 最小改动、语义正(连接活着 + 订阅已建立 ⇒ 镜像有效); B) relay 定期重推 SNAP —— 自愈(漏推可被下一次纠正),代价 = 周期流量; C) 拉长 PRESENCE_TTL_MS —— 只把竞速推远,⛔ 不推荐单用。 P-2 候选修法:把翻译判定抽成纯函数 relayEndpointTarget({via, dialedPort, snapshotLocalPort}) + 建 hostId → 逻辑名 映射(hostNameById);判据 = via≠relay ⇒ 原样透传 / via=relay ⇒ ①拨号落点 ②快照落点 ③**undefined(失败关闭,⛔ 不原样透传);单测至少 4 例,含"传裸 hostId** 必须命中"的键口径回归。 两条共同硬门:D1(不改任何生产值)|R5(不扩大暴露面)|R7(⛔ 不 commit / 不 push)|🔴 ⛔ 不许把 RELAY_FAILOVER_COOLDOWN_MS=0 写进任何回滚 / 演练 / 夹具路径。


§14 序㉑ 回报(2026-09-17 19:0x–19:4x · P-1 与 P-2 均已修;详细取证 = 交接单_presence在线态_20260917.md §8.11)

# 项 判定 结果(一句话)
P-1 presenceFresh() 新鲜度判据与"稳态零帧"打架 ✅ 已修 判据改为「订阅已建立 ∧ 链路活着」(任何入站帧,含心跳;上界 = max(TTL, 半开阈值))。候选取 A:⛔ B(周期 SNAP)会**推翻序⑲ E1「稳态 Δpushed ≤ 0」**与 OBS-14 的 snaps ≤ pushed ⇒ R11 净变差;⛔ C 只把竞速推远。真机同口径 Before/After:67 s 窗口 ΔstatusHits 14 → **1**(探针自身读数 = 1 ⇒ Manager 侧 13 → 0),门开关 0 次翻转(重启后 5+ 分钟仅 1 行"挂起")。
P-2 translateEndpoint 键口径不一致 ⇒ 闭包是死分支 ✅ 已修(单测面) 抽出纯函数 src/net/relay/endpoint-target.ts(relayEndpointTarget + hostNameIndex)+ 闭包先换逻辑名(hostNameById);T35 四支判定(含"⛔ 不误触发拨号池")、T36 键口径 + 源码级守卫(⛔ 不得再出现裸 hostId 作键)。E2E 未做(无活跃实例 ⇒ 按入口授权"只做到单测",可执行判据已写进 presence 单 §8.11-⑤)。
新 OBS-16(门的验收判据) ➕ 落地 ⛔ 不能只看 OBS-13(它的 ΔstatusHits ≥ 1 由探针自身两次读即满足 ⇒ 旧口径下轮询照旧在跑也全绿);新增第三次采样 + PRESENCE_GATE_WINDOW_MS / PRESENCE_GATE_HITS_MAX;夹具两侧实证 PASS(Δ=1, rc=0) / FAIL(Δ=12, rc=1);真机 PASS。
新 P-2b(本棒只登记、⛔ 未动手) ⏳ 在册 relayEndpointTarget 只有"拨号落点 → relay 快照"两级;P-1 修好后快照路径趋冷,若拨号池分不出槽位而订阅推送里有落点仍会判失败关闭 ⇒ 候选 = 把 presenceLocalPort 插成 ②'级(与 addressOf 三级链对齐)。属行为面新增 ⇒ 按 R7 先报告后动手。

零回归三件套:npm test 174/173/0/1(基线 170/169/0/1 + 新 T33–T36)|--scene all 12 PASS / 0 SKIP / 0 FAIL|overlay-probe 15 PASS / 0 FAIL / 1 SKIP(基线 14/0/1 + OBS-16)。 边界自证:⛔ 未改任何生产值(RELAY_FAILOVER_* / HB_SEC / burst;PRESENCE_TTL_MS 值未改)|⛔ 未新增公网口 / 未改 nft·nginx|🔴 COOLDOWN_MS=0 计数 0|⛔ 未 commit / 未 push。 指纹:参数表 42238175d84319ada99afa56d583f9db(旧 659c08949c193102f774822905fde3b7)|探针 61a9fc5623bb617c802daf604d3ce0f3(旧 4cbe34f1ffc51cfab78f0540224aa388)|🔴 本单指纹改记「前缀口径」(⛔ 不记全文 —— §13/§14 自身就是回填产物,全文 md5 必然自指过时):sed '/^## §13 序 ⑳ 新增在册缺陷/,$d' 交接单_观测口径与在册缺陷_20260917.md | md5sum = fc0389d759c57a14d038b6450a36d8e1(§13 之前的正文;回填 §13/§14 不影响它,可复现)|收口时全文 md5 实测 = 28bbe2a7467569a0ad76a486ae37546d(814 行、CR=0),⚠️ 仅作当次快照,⛔ 别当复现判据(旧记的 5495f2d2360aea64e763ec0e5aeb2987 是"写完 §14 但尚未补 §13 的 P-2b 行"那一瞬的值,⚠️ 已作废)。


§15 序 ㉒ 回报(2026-09-17 19:33–20:0x · P-2b 已修 + P-1 长窗复测通过;详细取证 = 交接单_presence在线态_20260917.md §8.12)

# 项 判定 结果(一句话)
P-2b relayEndpointTarget 候选链只有两级 ⇒ 订阅推送里有落点也会判失败关闭 ✅ 已修 补 ②' 级 pushedLocalPort,链变三级且与 addressOf 逐级对齐;改动只在 3 个在册文件(endpoint-target.ts / web/server.ts / test/relay.test.mjs);先红后绿(产物里去掉 ②' 级 ⇒ T37 actual {41000,'snapshot'} vs expected {37057,'pushed'});新增 T37(三级逐支 + 0 哨兵逐级下探 + ⛔ 不碰拨号池)/T38(源码级守卫:闭包必须把 presenceLocalPort(name, ep.port) 传进判定)。
P-1(长窗) 序㉑ 只验过 67 s 短窗 ✅ 长窗通过 窗口 706 s(11.77 min):ΔstatusHits 9→10 ⇒ Manager 侧 = 0;门开关窗口内 0 行;Δpushed=0 / Δsnaps=0。相对基准 60 000/5 000 = 12 次/分钟:raw 0.085 次/分钟 ⇒ 141.2×(Manager 侧零命中 ⇒ 无穷倍)。
新 环境勘误:106 的 ssh 口径 🔴 已定 必须用别名 test106(专用密钥 id_ed25519_test106)。裸 ssh [email protected] ⇒ Permission denied (publickey) —— ⛔ 别再误判成 MaxStartups 限流(本棒先误判、后取证纠正)。
遗留 E2E(P-2 + P-2b) ⏳ 未做 无活跃实例(47 上 dsh-systemctl list-units 'dsh-*.scope' = 0)⇒ 判据仍留 presence 单 §8.11-⑤,交 序 ㉓。

在册缺陷表(现行):P-1 ✅ / P-2 ✅(单测面)/ P-2b ✅ —— 无未关闭在册缺陷;剩余仅 §9-10「relay 侧静默丢订阅而 socket 仍活 ⇒ 门察觉不到」(当前代码路径不可达,属回头条件而非在册缺陷)+ E2E 缺口。 零回归三件套:npm test 176/175/0/1(基线 174/173/0/1 + T37/T38)|--scene all 12 PASS / 0 SKIP / 0 FAIL|overlay-probe 15 PASS / 0 FAIL / 1 SKIP(部署后重跑同值)。 部署 + 回滚点:lib/net/relay/{endpoint-target,index}.js + lib/web/server.js 铺 4 处(47 /opt/dshs/lib+/opt/dsh-relay/lib、106 /opt/dshs-cluster/lib+/opt/dsh-relay/lib),md5 四根全同(36b2dd99… / 486419ba… / 460d360e…);只重启 47 的 dshs;回滚点 = 两机 /opt/dsh/backups/seq22-20260917-195602/(旧 md5 57e6c9ae… / 486419ba… / 74c077b3…,由"反向改回两处 hunk → tsc"精确重放取得)。 边界自证:⛔ 未改任何生产值(RELAY_FAILOVER_* / HB_SEC / burst 逐项未变)|⛔ 未新增公网口 / 未改 nft·nginx|🔴 COOLDOWN_MS=0 计数 0|⛔ 未 commit / 未 push。 指纹:参数表 42238175d84319ada99afa56d583f9db(未变 ✅ —— 本棒无数值改动)|探针 61a9fc5623bb617c802daf604d3ce0f3(未变)|本单 §13 前缀口径 fc0389d759c57a14d038b6450a36d8e1(未变 ✅ —— §15 在截断点之后)|代码面:endpoint-target.ts 9f421a9f7bdff18cd02fd60cf4add2fb、web/server.ts 8c4c072cb7dc068b4f378f51b5d5a29a、test/relay.test.mjs f08088b8c07bf7f18a0c0ce0a98ee018。


§16 序 ㉓ 回报(2026-09-17 20:08–20:4x · E2E 已验收 + 在册缺陷表清空 + 线内可停;详细取证 = 交接单_presence在线态_20260917.md §8.13)

# 项 判定 结果(一句话)
E2E(P-2 + P-2b) 序㉑/序㉒ 都只做到单测 ✅ 已验收 R4 临时 session ⇒ POST /api/dsh/enter:ⓐ 200(同连接双发 + 6 轮复测全 200)ⓑ 47 上 :21000 监听 0(:19000 亦 0)ⓒ overlay-endpoint 窗口内 0 行(失败关闭未触发)ⓓ relay counters.dial 0 → 2。判别器更强证据:拨号原文 落点 127.0.0.1:25000 -> ops/w-106:19000(agent 面)+ 落点 127.0.0.1:25001 -> ops/w-106:21000(实例面)⇒ 键口径(逻辑名)与三级候选链都在生产上走通了;同时 106 上 dsh-100002-…scope argv 末尾 --port 21000 在听 ⇒ "47 无 21000 / 106 有 21000" 同时成立 = 翻译生效强判据。
用户可见面 序⑬ Q2(guest(w-106) 实例页 502) ✅ 判据已满足 curl -H 'Host: guest.alotbuy.com' 127.0.0.1:3080/ ⇒ 200、62451 字节真 HTML、502 标记 0。
新 OBS-09 由 SKIP → PASS ✅ 回头条件兑现 PASS OBS-09 在册实例面 w-106:37747=401 ⇒ 序⑲ §9-8 的回头条件已兑现("有用户访问实例后 OBS-09 应回 PASS"),⛔ 不是真回归。探针整表 16 PASS / 0 FAIL / 0 SKIP(基线 15/0/1 ⇒ +1 better)。
新登记(R7 · ⛔ 未动手) O-1:model landing failed, falling back to env injection Error: ENOENT … /var/lib/dshs/users/<uid>/home/.credentials.yaml ⏳ 线外 用本地路径读远端实例 home(与序⑬ Q2 所记真因同族);有 env 注入兜底、用户可见面仍 200;归属 = RemoteSpawner.resolveApiKey(平台模型落地面)⇒ 不在覆盖网络线 §3.1 文件集,仅登记、待定立项。
观察项 OBS-15 在线态陈旧 p95 ✅ 未越界 本棒 1770 ms(序㉒ 曾见 14220 ms,阈值 15000 ms)⇒ 抖动主导,⛔ 未改阈值。

在册缺陷表(现行):P-1 ✅ / P-2 ✅ / P-2b ✅ + E2E ✅ ⇒ 空。剩余仅 §9-10(relay 静默丢订阅 ⇒ 回头条件,当前代码路径不可达)+ 三项待拍板(B 内容分发 / 「Manager 重启后自动拉起实例」/ 入口 §4 骨干服务范围)。 收口复核结论:覆盖网络线线内可停 ⇒ 按登记门禁(用户 2026-09-17 明令)本棒不登记下一棒,等拍板后再建。 零回归三件套:npm test 176/175/0/1 | --scene all 12 PASS / 0 SKIP / 0 FAIL(rc=0)| overlay-probe 16 PASS / 0 FAIL / 0 SKIP(rc=0)。 边界自证:⛔ 未改任何代码(in-册三个文件 md5 与序㉒ 全同)|⛔ 未改任何生产值(参数表指纹 未变)|⛔ 未动 nft·nginx / 未新增公网口|🔴 COOLDOWN_MS=0 计数 0|⛔ 未 commit / 未 push|R4 临时 session 残留 0。 指纹:参数表 42238175d84319ada99afa56d583f9db(未变 ✅)|探针 61a9fc5623bb617c802daf604d3ce0f3(未变)|本单 §13 前缀口径 fc0389d759c57a14d038b6450a36d8e1(未变 ✅ —— §16 在截断点之后)|代码仓 HEAD 04776af(改动数 10,与 T0 一致)。