序㊾(本棒):
- scripts/overlay-probe.cjs:OBS-01 / OBS-08 / OBS-09 的数据源由「只读 47 中继」
改为「按两台中继取并集」,消除 worker 归属漂移时的假红 / 假 SKIP
· endpoints 以 network:hostId:port 为键合并,online 取「或」、localPort 取在线那一侧
· used 按 network/hostId 去重计数(不求和,避免凭空放大在册数)
· localPort 属中继机回环落点 ⇒ 按归属分机探活(106 侧落点由 106 机上探)
· derived(OBS-11)保持 47 视角;阈值与判据一律未放宽
· OBS-16 计数约束:对 47 /status 的读取仍为三次、Δ 只取 47 的 counters;
对端 106 的采样为独立一次,落在第三次采样之后,不进 (status2, status3] 门窗口
· 新增 --peer-status-fixture(并集的对端那一半)与「并集不可取证」强制留痕
- 交接单《覆盖网络-序45-低熵块治理-测熵与实现》§16 全节(§8 前前缀逐字未变)
- 参数表 §11.16 补记(§10 现算指纹未变,值格未动)
附(前几棒已完成并已部署、但尚未入仓的源码 / 文档):
- src/net/relay/content/*.ts、src/net/relay/index.ts、main.ts:块级寻址 C 域分离
- src/supervisor/orchestrator.ts、src/worker/agent.ts:日志采集与巡检(方案 C)
- test/overlay-content.test.mjs:随附用例(npm test = 200 pass / 0 fail / 1 skipped,Node 22)
- scripts/dshlog.mjs(跨机日志取证)、scripts/overlay-entropy.cjs(熵探针)
- dsh-server-docs/04-调整方案/129、133;INDEX.md / docs-manifest.json / 交接单 README 登记
69 KiB
交接单 · 低熵块治理(测熵先行 + C 域分离 + D 非确定性)
- 序号:覆盖网络线 序 ㊺ · 规划棒
- 立单:2026-09-18 21:4x(规划棒会话;⛔ 零代码 / ⛔ 零服务器触碰)
- 用户拍板:2026-09-18 21:28 —— 采纳 C + D;B(OPRF / SA-MLE)降级为可选加强、⛔ 本轮不立项
- 上游依据(单一来源 · ⛔ 不要另起炉灶):
- 方案类档案 =
04-调整方案/133-覆盖网络-低熵块治理方案-C域分离与D非确定性.md(本单只做执行,口径以它为准) - 工作区根
调研_MLE加密去重最优方案_20260918.md(§3 / §5 / §5.5) .workbuddy/memory/2026-09-18.md21:1x / 21:5x / 21:2x- 阈值与判据面 = 工作区根
参数表_覆盖网络_20260917.md(⚠️ 指纹以现算为准)
- 方案类档案 =
- 状态:待执行
- 冲突域:代码
src/net/relay/content/{chunker,crypto}.ts+blockIdOf调用点 +scripts/overlay-probe.cjs+参数表_覆盖网络_20260917.md+ 文档(本单 §一 行 /INDEX.md/05-*参数表说明若需) - 依赖:✅ 无前置未完成项(
E1既有基线会在步骤 4 重取,⛔ 不是阻塞)
§0 摘要 + 为什么「第一步是测熵、不是改代码」
要治的病(04-133 §1):块 id = 裸 sha256(字节) ⇒ ① 中继无密钥也能判相等性 ② 持组密钥者可枚举低熵内容(实测 85,878 条/秒)。
分两刀:C 把块 id / 内容 id 的哈希改成 per-network keyed hash(切断跨 network 相关性,代价≈0);D 让低熵块不再确定性(首选 D-1「稀释域」,备选 D-2 随机密钥 + 封装)。
🔴 本单的排序是刻意的 —— 第 1 步是补测,⛔ 不是改代码,因为:
| 理由 | 依据 |
|---|---|
| D 的边界完全由它决定 | "什么算低熵 / 稀释单元取多大 / 要不要合并" 全是该读数的函数 |
| 它从未被测过 | 04-133 §2.1 三条硬证据:perf 用 makeBytes() 合成字节(_tmp_seq32/p01-perf.mjs:42)|低熵定性用 8 B 合成块(_tmp_seq32/p03-lowentropy.txt)|块存储纯内存不落盘(server.ts:909-911)⇒ 生产侧无样本可捞 |
| 只测首屏包会得出假绿 | 切分是定长 1 MiB(chunker.ts:60,137)⇒ 首屏包 10.8 MB ≈ 11 块混高熵内容,纯低熵块结构上几乎不可能存在。低熵的真实形态更可能是**"小于 1 MiB 的独立内容"、或块内的一小段**(⇒ 必须加 M1-d 子窗口腿) |
⇒ 先测 → 再决定 D 的作用面 → 才动手改。⚠️ 若 M1 证实"首屏包内无低熵块",⛔ 不许为了"让 D 有用"去把块切小(切小 = 块数暴涨 = 控制面与回源双向恶化 ⇒ 净变差 · 触 R11)。
§1 目标(一句话 · 可判定"做完了没有")
先产出「低熵块的种类数 / 体积 / 占首屏包比例」的机器读数(M1 四项)并固化进参数表;再按 04-133 落地 C + D,且判据面带一条"稀释源被换成确定性派生量 ⇒ 必红"的负腿。
§2 只读前置(执行前必须先核实 · 给命令与期望输出)
| # | 事实 | 命令 | 期望输出 |
|---|---|---|---|
| P1 | 全局执行锁空闲 + 抢到 → 抢不到停手报告 | bash /d/github/dsh_shenxian/dsh-server-docs/scripts/handoff-guard.sh --claim-exec "<你的会话名>" |
✓ 已持全局执行锁;报已有占用者 ⇒ 停手,⛔ 不删别人锁 |
| P2 | 块 id 仍是裸哈希(C 还没做) | grep -n "blockIdOf" -A 3 src/net/relay/content/chunker.ts |
createHash('sha256').update(bytes)...slice(0, BLOCK_ID_HEX_LEN) |
| P3 | 切分是定长且块大小是常量 | grep -n "offset += blockSize|DEFAULT_BLOCK_SIZE =" src/net/relay/content/chunker.ts |
:137 的 offset += blockSize;:60 的 1024 * 1024 |
| P4 | 块存储纯内存(✓ 佐证"无法回捞样本") | grep -n "new ContentStore" -A 3 src/web/server.ts |
只有 maxBytes,⛔ 无 dir |
| P5 | 参数表现算指纹(⚠️ ⛔ 不要相信 §10 里写的值) | sed '/^## §10 指纹/,$d' "/e/ProgramData/AI技能/aliyun-dsh-server/参数表_覆盖网络_20260917.md" | md5sum |
立单时 = 6b37bfd506758d882d9f803678f85d23;⚠️ 与 §10 记录的 d408d640… 不一致 = 既有漂移(见 §10 未验证项 1) |
| P6 | 现有 OBS 编号到 28 ⇒ 新增从 29 起 | grep -oE 'OBS-[0-9]+' "/e/ProgramData/AI技能/aliyun-dsh-server/参数表_覆盖网络_20260917.md" | sort -u -t- -k2 -n | tail -3 |
…OBS-27 OBS-28 |
🔴 环境坑(本机铁律):bash 的 PATH 被 shim 重置 ⇒ 一律用完整 POSIX 路径(
/e/…、/d/…);Python exe 不认/e/…⇒ 传E:/…。 🔴blockIdOf调用点必须先清点(见步骤 4):grep -rn "blockIdOf" src/ scripts/ test/ | wc -l⇒ 若> 10⇒ 先出"受影响文件清单"再动手(用户硬要求)。
§3 范围
要改(⛔ 只改这些)
src/net/relay/content/chunker.ts——blockIdOf/contentIdOf加 per-network 密钥(C);低熵判定的落点若在此则一并加(D)。src/net/relay/content/crypto.ts—— D 的加密路径(D-1 稀释 / D-2 随机密钥 + 封装)。blockIdOf/contentIdOf的全部调用点(⚠️ 逐处过,漏一处 = 校验必红)。scripts/overlay-probe.cjs—— 新增OBS-29(判据 + 负腿);⚠️ 夹具模式必须封闭(if (夹具) {…} else if (fixture) { SKIP + 留痕 } else { ssh }—— 守卫排在 ssh 之前,本线既定口径)。参数表_覆盖网络_20260917.md—— §11.3 补记区(M1读数)+ §6 新增OBS-29行 + 新键;⚠️ 若新增键 ⇒ §10 指纹必然变,按现算更新。- 文档:本单 §一 台账行 → ✅;
INDEX.md对应状态行。
⛔ 不动(防止顺手扩大)
- ⛔
DEFAULT_BLOCK_SIZE(chunker.ts:60)—— 常量,⛔ 不许改成配置、⛔ 不许调值。 - ⛔
package.json(零第三方依赖 —— D-1 只用node:crypto)。 - ⛔ 生产实例、⛔ 在线服务重启、⛔ 云安全组。
- ⛔
B(OPRF / SA-MLE)的任何代码 —— 仅保留"将来可插"的接口位。 - ⛔ 合规 / 数据主权内容(口径:方案只做技术实现)。
§4 决策点
| # | 项 | 状态 |
|---|---|---|
| 1 | C / D 方向 | ✅ 已定(用户 2026-09-18 21:28 拍板 C + D) |
| 2 | B 是否立项 | ✅ 已定 = ⛔ 不立项(降级为可选加强) |
| 3 | M1-b 的"低熵"阈值 |
⚠️ 执行中自决:初值 H ≤ 4.0 bit/byte;⛔ 不许只报一个阈值 —— 必须给 H 的直方图,让人能重判 |
| 4 | D 选 D-1 还是 D-2 | ⚠️ 由 M1 结果决定,执行中自决:M1 显示"低熵内容有高熵邻居可同批" ⇒ D-1;显示"独立成块且无同批高熵内容" ⇒ 才考虑 D-2 |
| 5 | 稀释源的派生写法与存放位置 | ⚠️ 执行中自决(技术实现)。🔴 但硬约束:稀释源必须含真随机、且不出现在明文可见面 —— 因为攻击面是"持钥者猜明文 + 复算"(crypto.ts:405-413),确定性派生的盐对持钥者完全无效 |
| 6 | per-network 密钥的派生写法 | ⚠️ 执行中自决(复用 crypto.ts:47 既有组密钥链路,⛔ 不新增密钥文件 / 不新增 env) |
🔴 边界外才上抛:花钱 / 凭据 / 业务优先级 / 合规 / 影响面超出本平台 / 不可逆破坏性操作。⚠️ 上抛只问那一句,技术方案自己定好当已定项陈述。
§5 步骤(有序 · 每步自带一次可执行的验证)
步骤 1 🔴 补测 M1:低熵块的种类数 / 体积 / 占首屏包比例(本单第一个动作)
- 取真实内容(⚠️ ⛔ 绝不用
makeBytes()之类合成字节):- S1 = 真实首屏包:
curl -s --http1.1 -o /tmp/fsp.bin "<portal>/plugins/"⇒ ⚠️ 先wc -c核对是否 ≈11,363,655(口径见参数表:146)。 - S2 = 真实"独立小内容"集:枚举覆盖网络实际分发的非首屏内容并留痕;⚠️ 若确实不存在 ⇒ 报 SKIP + 写清"不存在",⛔ 不许拿合成字节冒充。
- S1 = 真实首屏包:
- 切分:⛔ 必须调用仓库里那份
chunkify(src/net/relay/content/chunker.ts)—— ⛔ 不许复刻一份算法(复刻 = 双源)。 - 统计四项(定义见
04-133 §2.2):M1-a种类数 / 重复率 |M1-b低熵块数 + 体积 + H 直方图 |M1-c占首屏包比例 | 🔑M1-d子窗口熵(如 4 KiB 滑窗扫整份,给出低熵窗口的尺寸分布)。 - 验证:
M1-a..d四项都有数(⛔ 不许"只有定性结论");⚠️M1-d必须有数 —— 它是防"首屏包全是高熵块"假绿的那条腿。 - 落点:先可落
_tmp_seq*/取证;证据齐后固化为只读探针scripts/overlay-entropy.cjs(⛔ 零第三方依赖)。
⚠️ 本步不许出现任何生产写入:
curl是只读 GET;⛔ 不重启、⛔ 不改 env、⛔ 不铺插件。
步骤 2 由 M1 结论定 D 的作用面(⚠️ 先判定,再动手)
- 输出一段判定:低熵块存在与否 / 在哪(首屏包内 or 独立小内容 or 块内片段)/ 体积占比,并据此选定 D-1 或 D-2(决策点 4)。
- 验证:判定必须引
M1的具体数字(⛔ 不许"我觉得")。 - ⚠️ 若判定"首屏包内无低熵块" ⇒ D 的作用面收窄到 S2,⛔ 不得因此去动块大小。
步骤 3 落地 C(域分离)
blockIdOf/contentIdOf加 per-network 密钥(HMAC-SHA256,输出仍取前BLOCK_ID_HEX_LEN= 32 hex)。- 逐处过调用点(⚠️ 步骤 0 已清点;> 10 文件 ⇒ 先出清单)。
- 验证:
npm test(Node 22)全绿;且新增一条单测断言"不同 network ⇒ 同字节得不同块 id"+"同 network ⇒ 同字节得同块 id"(两条都要,⛔ 只测前者会漏"去了重")。
步骤 4 落地 D(非确定性) + 重取 E1 基线
- 按步骤 2 的结论实现 D-1(或 D-2)。
- 🔴
E1基线重置:C 换了 id 口径 ⇒ 旧基线读数作废,必须重取。⚠️E1的定义不重估(回源字节 ≈ 1 份 × 组数)。 - 验证:
OBS-29正腿绿;🔴 负腿必须红 —— 见步骤 5。
步骤 5 判据面(OBS-29 + 负腿)
- 新增
OBS-29(判据面 + 阈值键;⚠️ 编号从 29 起,⛔ 不跳号不复用)。 - 🔴 必须有的负腿:「把稀释源换成确定性派生量(如
HMAC(组密钥, 块序号))⇒ 判据必红」。 理由(04-133 §4.1):攻击面是"持组密钥者猜明文 + 复算" ⇒ 确定性盐对持钥者完全无效 ⇒ 没有这条负腿,"上了个无效的稀释"会全绿(本线老病根:装了没生效 = 静默放行)。 - 验证:探针自检 —— 正腿 PASS + 负腿 FAIL 且具名(⛔ 不许"返空又不计数")。
步骤 6 收口
python3 scripts/docs-audit.py(退出码 0)→ python3 scripts/docs-manifest.py → bash scripts/docs-sync-check.sh(0)→ 回填本单 §一 台账行 → 按 README §三 第 3 条 收尾四件套。
§6 验收(命令 + 期望输出 · 可被第三方复现)
| # | 命令 | 期望 |
|---|---|---|
| 1 | 熵测脚本 --json |
M1-a..d 四项齐;M1-b 带直方图;退出码 0 |
| 2 | npm test(Node 22) |
全绿;含新增的"跨 network 块 id 不同 / 同 network 相同"两条断言 |
| 3 | 探针 OBS-29 |
正腿 PASS + 负腿 FAIL 具名(负腿=确定性稀释源) |
| 4 | python3 scripts/docs-audit.py |
退出码 0 |
| 5 | bash scripts/docs-sync-check.sh |
退出码 0 |
| 6 | /status 的 content 块 |
⚠️ 只读;OBS-17 口径一致腿仍绿(blockSize / storeMaxBytes) |
| 7 | 参数表 §10 指纹 | 现算值已更新(若新增键)+ §11.3 已落 M1 读数 |
🔴 三条红线:① E1 跑不出机器断言 ⇒ 停下报告,⛔ 不许放宽判据凑绿。② 零回归三件套任一退化 ⇒ 停下报告。③ ⛔ 不把任何"关掉安全/限速"的值写进回滚或夹具路径。
§7 回滚
| 层 | 做法 | ⚠️ 注意 |
|---|---|---|
| D | 关掉稀释开关 ⇒ 回落确定性加密 | ✅ 无损(只回退"这批块不共享"这一变化) |
| C | 传空密钥 ⇒ 回落裸哈希 | 🔴 块 id 会再变一次 ⇒ 缓存再清一次、去重率再算一次 ⇒ ⛔ 不许默认"回滚 = 无损",必须写进回滚说明 |
| 判据 / 参数表 | 回退到本单 §11 所记指纹(⚠️ 以现算为准) | ⛔ 不许手改 §10 里那条历史记录 |
§8 回报格式(执行会话必须回填)
M1四项读数(原文级)+ H 直方图 + 数据来源(S1 的wc -c/ S2 的清单或"不存在")。- 步骤 2 的判定(引具体数字)+ 选了 D-1 还是 D-2(一句话理由)。
blockIdOf调用点清点数(> 10时附受影响文件清单)。npm test输出(尾 20 行 + 退出码)+ 新增两条块 id 断言的原文。OBS-29探针输出:正腿 PASS + 负腿 FAIL 具名(两条都要,⛔ 不许只给正腿)。E1重取的基线读数(⚠️ 标明"旧基线已作废")。docs-audit.py/docs-sync-check.sh退出码。- 参数表 §10 现算指纹(新值 + 旧值)+ §11.3 落点行号。
- commit sha(本机;⚠️ 推送需用户明确说"推送")。
§9 停止条件(命中即停下报告)
- 抢不到全局执行锁 ⇒ 停手报告(⛔ R9:不得接管、⛔ 不得删锁)。
M1对象(真实首屏包 / 真实小内容)取不到 ⇒ 报 SKIP + 说清原因,⛔ 不许用合成字节顶上(否则整份读数失去意义)。M1结论要求动DEFAULT_BLOCK_SIZE才能让 D 生效 ⇒ 停下报告(= 净变差,触 R11)。- 需要新增生产 env 或引第三方依赖 ⇒ 停下报告。
blockIdOf调用点 > 10 文件 ⇒ 先出清单,未获批不动。- 发现方案方向与
04-133冲突 ⇒ 停下报告,⛔ 不许自行改方案。
§10 未验证项
| # | 项 | 状态 |
|---|---|---|
| 1 | 🔴 参数表 §10 指纹漂移:§10 头值记 d408d640246a980f702fe7b0a2895219(序㊴ 12:2x),现算 = 6b37bfd506758d882d9f803678f85d23(830 行;文件 mtime 09-18 15:18)⇒ 记录值未随内容更新 |
⚠️ 本轮只报不改(⛔ 不是本单产物);执行棒取基线一律用现算。⚠️ 若确认是"某棒改了 §10 之前的内容却忘了更新记录"⇒ 需另立小项收口 |
| 2 | M1 四项读数 |
⚠️ 从未测过(本单步骤 1 补) |
| 3 | 低熵块的判据阈值("什么算低熵") | ⚠️ 开放问题(调研…§6 阻碍 4)⇒ 本单只要求给直方图,⛔ 不要求定死阈值 |
| 4 | D 若引入"合并加密",常驻字节是否变化 | ⚠️ 待测(CONTENT_STORE_MAX_BYTES 账,见 参数表:147) |
| 5 | B(OPRF / SA-MLE)的接口位 |
⚠️ 仅保留"将来可插",⛔ 本轮零代码 |
§11 指纹与状态
| 项 | 值 |
|---|---|
| 本单 §8 之前正文前缀指纹 | be548afc3340583b2b63ca254bcf550f(口径 = sed '/^## §8 回报格式/,$d' <本单> | md5sum) |
| 本单全文件 md5(立单时) | 0169b900a6ea50608c0d50a3a0514e68(210 行 · ⚠️ 口径 = **本字段值填入「前」**的文件字节 —— 自指字段无法包含自身,⛔ 复算时请先还原本字段再比) |
| 参数表指纹(立单时 · 现算) | 6b37bfd506758d882d9f803678f85d23(⚠️ §10 记录值 d408d640… 已漂移) |
| 代码仓 HEAD(立单时) | 45b4999 |
| 方案档案 | 04-调整方案/133-覆盖网络-低熵块治理方案-C域分离与D非确定性.md |
| 本单状态 | 🟡 部分执行(§5 步骤 1 + 2 已完成 · 2026-09-18 21:56–22:2x · 序㊺ 执行棒)—— 🔴 M1 实测判定 =「D 的作用面为空 ⇒ D 本轮不实现」(见 §12-2)⇒ 步骤 4–5 的前提被证伪、原判据负腿失去对象 ⇒ 剩余 = C 域分离 + 判据重裁,交 序 ㊻ 执行棒(automation aab9e357-8101-4011-b829-bf9f4a459bf7 · 2026-09-18 22:33) |
§12 序㊺ 执行棒回填(2026-09-18 21:56–22:2x · 按 §8 回报格式逐条)
1. M1 四项读数(原文级)+ H 直方图 + 数据来源
- 数据来源 = 真实内容(⛔ 零合成字节):
GET https://admin.alotbuy.com/壳页(R4 临时会话,用完即删)→ 取页面内全部 60 条/plugins/??…&rev=…→ 按页面顺序拼接为 S1 流。 - S1 = 23,629,336 B;
blocks = 23;流 md5 =41333ba2d6b480036c694d9fee7e5c1c。⚠️ 该流不跨实例重启字节稳定(combo 分区随启动世代变)⇒ 复现须记rev世代。 - 🔴 口径纠正:本单 §3 与
参数表:146引用的11,363,655 B不是"全部 combo 之和",而是最大单条 combo(本棒实测 11,794,471 B ≈ 12 块)。真实首屏总量 = 23,629,336 B(23 块)。 M1-a唯一块 id 23 ÷ 总块 23 ⇒ 重复率 0.00%;完全重复块(同 id ≥ 2 次)0 个。M1-b低熵块(H ≤ 4.0 bit/byte)0 块 / 0 B;逐块 H ∈ [5.1807, 5.8444];H 直方图 ={"5.0-5.5": 8, "5.5-6.0": 15};逐块 H =5.2089 5.5612 5.7684 5.5095 5.6777 5.4695 5.6530 5.6974 5.2515 5.5237 5.5102 5.1807 5.2527 5.7228 5.8444 5.6450 5.5956 5.6805 5.6275 5.2722 5.3887 5.5581 5.3555。M1-c低熵块字节 ÷ 首屏包字节 = 0.000000%。M1-d(反向腿 · 4 KiB 窗 / stride 4 KiB) 窗口 5,768 个;低熵段 15 段,合计 241,664 B = 1.0225%;段字节 min 4,096 / max 81,920;尺寸直方图{≤4KiB: 10, 4KiB-16KiB: 3, 16KiB-64KiB: 0, 64KiB-256KiB: 2, 256KiB-1MiB: 0, >1MiB: 0};最大 10 段 =[81920, 81920, 16384, 12288, 8192, 4096, 4096, 4096, 4096, 4096]。- S2(60 份真实独立分发内容):单块内容 56/60;低熵内容 0 份;逐份 Hmin ∈ [4.8354, 6.3485]。
2. 步骤 2 的判定(引具体数字)+ 选了 D-1 还是 D-2
判定 = 两者都不选(D 本轮不实现):(甲) 首屏包 23 块全部 H ≥ 5.1807 ⇒ 无纯低熵块(M1-b / M1-c 皆 0);(乙) 低熵物质只以 ≤ 81,920 B 的块内片段存在(M1-d = 241,664 B / 1.0225%,全部 < 1 MiB)⇒ 已被同块高熵内容结构性稀释 ⇒ D-1 无对象可稀释;(丙) 60 份真实独立小内容中 0 份低熵(Hmin ≥ 4.8354)⇒ D-2 的启用前提("低熵内容独立成块且无高熵同批")不成立。⇒ D 的目标已由「定长 1 MiB 切分」结构性达成(低熵物质对块级去重 / 枚举不可见)。🔑 回头条件 = 出现「低熵内容单元」 ⇒ 由 scripts/overlay-entropy.cjs 可重复复查。⚠️ 本判为自决项(可推翻)、非降级(依据 = 实测,见 §11.12-⑤)。
3. blockIdOf / contentIdOf 调用点清点数 = 8 个文件 / 36 处(未触 >10 文件门槛):src/net/relay/content/{chunker,store,runtime,source}.ts、src/net/relay/{index,main}.ts、scripts/overlay-direct-probe.cjs、test/overlay-content.test.mjs。(命令:grep -rn "blockIdOf\|contentIdOf" src/ scripts/ test/ poc/ | grep -v ^src/net/relay/content/chunker.ts)
4–6. npm test / OBS-29 / E1 重取基线 = ⛔ 本棒未产出(C / D 未开工)
🔴 原因(必须与"漏做"分开):本棒只执行到 §5 步骤 1 + 2。步骤 4–5 的前提被 M1 证伪 —— 本单 OBS-29 的必需负腿是「稀释源换成确定性派生量 ⇒ 必红」;D 一旦不实现,该负腿就没有对象(无稀释源可换)。⇒ 若本棒继续落 C 而沿用原判据面,就等于交付一处没有判据的代码改动 —— 正是本线明令禁止的"装了但没生效 = 静默放行"。⇒ C + 判据重裁一并交序 ㊻ 执行棒。⇒ 因此 §8 的 4 / 5 / 6 / 9 四项本棒本就不该有:⛔ 未改 src/** 一行 ⇒ 无新断言、无新判据、无基线换代。
7. docs-audit.py / docs-sync-check.sh 退出码
python3 scripts/docs-audit.py⇒ rc = 0(结论:无 P0 级问题)。bash scripts/docs-sync-check.sh⇒ rc = 2,原文ERROR: 无法读取服务器目录 bt-server:/opt/dsh/docs(ssh 失败或目录不存在)。⚠️ 与序㊺ 规划棒 15:5x 的报错逐字相同(既有条件,⛔ 非本棒引入);⚠️ 本棒 ⛔ 未 scp / ⛔ 未推送 ⇒ 不构成阻塞;🔴 执行棒推送前必须复跑并成功(该条随下一棒带走)。⚠️ 47 上/opt/dsh/docs实际存在(ls /opt/dsh可见)⇒ 该报错更像脚本侧的 ssh 条件(本机 ssh 需-p 22+ 别名bt-server),⛔ 本棒只报不改。
8. 参数表 §10 现算指纹 + §11.12 落点
- 现算 =
6b37bfd506758d882d9f803678f85d23(写入前后两次现算逐字一致 ✅ —— 因为 §11 小节在 §10 口径之外,见参数表:573)。 - 旧值(§10 记录)=
d408d640246a980f702fe7b0a2895219⇒ 🔴 既有漂移(本单 §10 未验证项 1;⛔ 本棒只报不改)。 - 落点 =
参数表_覆盖网络_20260917.md### §11.12 补记(行 829 起;全文件 862 行);⛔ 未新增任何键 / ⛔ 未改任何值 / ⛔ 未改任何阈值 ⇒ 探针取数不受影响。
9. commit sha = 45b4999(= 立单时 HEAD,未 commit / 未 push ✅)。git status --short = 13 处,其中本棒唯一新增 = scripts/overlay-entropy.cjs(其余 12 处为序㊺ 规划棒及更早遗留,逐条可解释)。
§12.1 本棒如实留档(三条 · ⛔ 均不属本单产物)
- 🔴 仓里的
mksess.cjs已失效 —— 它写 SQLite/var/lib/dshs/dshs.db,而 47 控制面权威库 = PG(dshs.service.d/cluster.conf的DSHS_DB_URL=postgres://[email protected]:15432/dshs)⇒ 插进去的会话 Manager 查不到 ⇒ 用它取实例页必然 401。正确做法(本棒实测可用) = 往 PG 的sessions表插(cookie 名sid;token_hash=sha256hex(token);user_id取users.username对应用户)+ 用完即删。⇒ 建议另立小项修mksess.cjs或标注废弃。 - 🔴
/plugins/??…combo 路由有鉴权 —— 无会话 = 401 / 24 B({"error":"unauthorized"});⚠️ 且盘上不存在"首屏包单文件产物"(combo 由宿主运行时拼装,见@deepseek-ai/dsh-client-modules的README.zh.md)⇒ 测熵取数只能走 HTTP + 临时会话。 - ⚠️ "低熵"阈值(
H ≤ 4.0)的判别力有限 —— 见 §11.12-④(丙):JS 文本的字节分布熵天然落在 5.0–6.0 ⇒ 该腿单独用会给出"永远 0";⇒ 后续任何"低熵"结论必须同时给M1-d子窗口腿。
§13 序㊻ 执行棒回填(2026-09-18 22:33– · 按 §8 回报格式逐条)
本棒只做
C(块 id 域分离 / per-network keyed hash);⛔D不实现 —— 序㊺ 的M1实测已判D无作用面(§12 第 1/2 条),04-133 §5.1据此重裁判据面。 🔴 判据编号承接现表最大值OBS-28⇒ 新判据 =OBS-29(⛔ 未跳号、⛔ 未复用)。
1. M1 四项读数 —— ⛔ 本棒不重做(序㊺ 已取,见 §12 第 1 条)⇒ 本棒只引用结论:真实首屏包 23 块 / 0 低熵,真实小内容 60 条 / 0 低熵(D-1 无对象、D-2 前提不成立)。
2. 步骤 2 的判定 —— D ⛔ 本轮不实现(沿用序㊺ 实测判定);本棒只做 C,并把原属 D 的那条负腿重裁为 C 的判据(第 5 条)。
3. blockIdOf 调用点清点数 —— 8 文件 / 36 处(序㊺ §12 已清点)。本棒实际改动 8 个文件(≤ 10 ⇒ ⛔ 未触 §9 第 5 条):
| # | 文件 | 改了什么 |
|---|---|---|
| 1 | src/net/relay/content/chunker.ts |
新增 idDigestOf(bytes, netKey)(HMAC / 裸 sha256 两条整枝,⛔ 不合成一个 Hash 对象以免类型漂移);blockIdOf / contentIdOf / chunkify / planOf / reassemble 全部接 netKey |
| 2 | src/net/relay/content/crypto.ts |
新增 BLOCK_ID_DOMAIN_TAG + deriveBlockIdKey() + ContentCipher.blockIdKeyOf() —— 复用既有组密钥链(crypto.ts 组密钥),🔑 ⛔ 无新密钥文件 / ⛔ 无新 env |
| 3 | src/net/relay/content/store.ts |
新增 ContentStoreOptions.netKey + 三处复算点(put / get-内存 / get-磁盘)全部带上 |
| 4 | src/net/relay/content/runtime.ts |
netKey / blockIdKeyId 派生 + 新增 writeTransforms() / readTransforms() ⇒ encode 与 netKey 同生同灭(⛔ 不许半开) |
| 5 | src/net/relay/main.ts |
探针块路径显式传 netKey(⇒ "调用点有没有带上"源码可查)+ 启动判别器日志 |
| 6 | src/net/relay/index.ts |
导出 ChunkTransforms / BLOCK_ID_DOMAIN_TAG / deriveBlockIdKey |
| 7 | test/overlay-content.test.mjs |
新增组 G(G1…G10);文件级 = 55 test |
| 8 | scripts/overlay-probe.cjs |
新增 OBS-29(五腿/judged = true/夹具模式照跑/⛔ 零 ssh) |
4. npm test 输出 + 新增两条块 id 断言原文
npm.cmd test⇒ 200 pass / 0 fail / 1 skipped(基线形状保持);node --test test/overlay-content.test.mjs⇒ 55/55 pass / 0 fail;npm.cmd run check:layering⇒ 无新增违规;tsc/build均过。- 🔴 两条断言都在(⛔ 只测前者会漏掉"去重被干掉"):
- ① 正腿(不同网 ⇒ id 必不同):
assert.strictEqual(idDiffersAcrossNetworks(a, b), true, '不同 network ⇒ 块 id 必须不同(域分离生效)')+assert.notStrictEqual(a[0], bare[0], '⚠️ A 网不得等于裸哈希 —— 否则 = 域分离没生效("装了没生效"的本形)')(与 B 网同式)。 - ② 正腿(同网 ⇒ id 必相同,去重不得丢):
assert.strictEqual(idDiffersAcrossNetworks(rt1.planContent(bytes).ids, rt2.planContent(bytes).ids), false)+assert.strictEqual(rt1.snapshot().store.puts, 4, '同内容只入库 4 个块(⛔ 域分离不得把去重干掉)')。
- ① 正腿(不同网 ⇒ id 必不同):
5. OBS-29 探针输出(正腿 PASS + 负腿具名)
- 绿(探针全表):
PASS OBS-29 块 id 域分离(C · per-network keyed hash)腿数 5/5 全绿 ✅|P1 ✅ P2 ✅ P3 ✅ N1 ✅(⛔ 判据有牙) N2 ✅|写侧 putRejected=0(须 0)|读侧 corruptReads=0(须 0)|重组位=逐字节相同 ✓|⏳ 真机腿 … 待部署后补。探针合计 = 29 PASS / 0 FAIL / 0 SKIP / rc=0(由 28 增至 29,增量 =OBS-29)。 - 🔴 先红后绿(两条负腿都真跑过,⛔ 不是纸面声明):
- 红腿 A
flat-key-collapses(deriveBlockIdKey故意丢掉network维度)⇒ 单测 48/54 通过、6 失败(G1/G1-b/G3/G5/G7/G8);探针 =FAIL OBS-29 … 腿数 4/5 ❌ 缺 P1-跨网必不同且都非裸哈希 … P1 ❌ P2 ✅ P3 ✅ N1 ✅ N2 ✅。 - 红腿 B "漏掉一个调用点"(
writeTransforms()故意不传netKey)⇒ 单测 45/54、9 失败;代表性原文 =content-store: 块校验失败(丢弃)expected=9aef912692d4ec2afb3aa69c138cef89 actual=e1311d713c12af1891bb202b32f5dc3f;探针 =FAIL OBS-29 … ❌ 自检抛错:content-store: 块校验失败(丢弃)expected=3354a61f694317ec0c571aedfd0c0e4c actual=7f80ac0b6cc7d93c961853b3cb579764。 - ✅ 两条均已复原:md5 回基线(
crypto.ts 5e87e104…/runtime.ts 58847b0e…/chunker.ts 199e31cd…/store.ts 1c63de21…),grep -c REDLEG= 0 / 0。
- 红腿 A
- ⏳ 真机腿(本棒做不了,回头条件已写进参数表 §6 的
OBS-29行) = 部署那一棒必须补:/status.content.blockIdKeyed === true,且 47 / 106 的blockIdKeyId逐字相同(跨机口径不一致 ⇒ 跨机取块全部判校验失败)。
6. E1 重取的基线读数 —— 🔴 旧基线(序㉔ 裸哈希口径)已作废。E1 定义不重估(仍是「回源字节 ≈ 1 份 × 组数」),只重取读数:同网 4 轮 × 4 块 ⇒ 全部 local 命中、source.origin = 0、store.puts = dedupIds.length、putRejected = 0;跨网起点 local = 0 ⇒ 各 1 份(= C 的域收窄点,⛔ 不是缺陷)。落档见参数表 §11.13-③。
7. docs 核查退出码 —— docs-audit.py = rc 0(原文结论「无 P0 级问题」)|docs-manifest.py = rc 0(已重生成 docs-manifest.json:files 183 → 189、chars 1,907,768 → 1,982,749)|🔴 docs-sync-check.sh = rc 2(ERROR: 无法读取服务器目录 bt-server:/opt/dsh/docs)。🔬 本棒已把根因查到原文级(⛔ 不是"目录不存在"):脚本第 88 行 ssh -o BatchMode=yes -o ConnectTimeout=10 "$REMOTE" 不带 -p 22 ⇒ 吃到 ~/.ssh/config 里 bt-server 的 Port 32022 ⇒ 原文 ssh: connect to host 47.77.182.89 port 32022: Connection refused;而 ssh -p 22 bt-server 'ls -d /opt/dsh/docs' ⇒ /opt/dsh/docs 存在、EXIT=0 ⇒ 🔴 别名端口陈旧 = 既有缺陷(⛔ 与本事无关;⛔ 本棒未改 ssh 别名、⛔ 未改该脚本 ⇒ 另立小项)。⇒ 本轮未能做双端对账(本棒 不 scp 且不 push ⇒ 不构成阻塞)。
8. 参数表 §10 现算指纹 —— 新值 = ac6bbbbb8c92bd57ff0dc4cd8f4983ba(= 已回填值);旧记录值 = d408d640246a980f702fe7b0a2895219。⚠️ §12 曾观察到一份独立现算漂移 6b37bfd506758d882d9f803678f85d23(两次独立复算一致)⇒ 判为「记录漏更新、不是算错」。落点 = 参数表 §11.13(行 864 起;全文件 885 行);🔴 §11.13 在 §10 指纹口径之外 ⇒ 追加后指纹不变(复算已验)。
口径:sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum。
9. commit sha —— 45b4999(= 立单时 HEAD,未 commit / 未 push ✅)。git status --short = 21 处 = 序㊺ 遗留 13 处 + 本棒 8 处(= 上表 8 个文件,逐条可解释)。
§13.1 本棒如实留档(三条 · ⛔ 均不属本单产物)
- 🔴
overlay-failover-drill.cjs卡在"仅供人读"的线索函数上(已实测定位) ——currentChannelHint()(源码注释自述「仅供人读,不参与判定」)会跑journalctl -u dshs --since -6h+grep -F '[overlay-dir] 取址';该行每 2 s 一条 ⇒ 6 h ≈ 1 万行 / ≈1.3 MB ⇒ 实测单次 ssh 需 ~47 s >SSH_TIMEOUT_MS= 20000 ⇒spawnSync ssh ETIMEDOUT⇒ 整个--scene all直接中止。🔴 这是健壮性缺陷:一个不参与判定的装饰性线索不该成为主流程的硬依赖(应:加长超时 / 缩小--since/ 失败降级为空串)。⛔ 本棒未改演练脚本(越界),另立小项。 - 🔴
mksess.cjs已失效(复述 §12.1-①):47 控制面权威库 = PG ⇒ 写 SQLite 的会话 Manager 查不到 ⇒ 必 401;正确做法 = 往 PGsessions插(cookiesid/token_hash=sha256hex(token))+ 用完即删。 - 🔴
overlay-failover-drill.cjs的"当前在哪台"判据有 6 h 上限(本棒实测踩到 · 先红后绿见 §13.2) ——lastManagerAuthOn()用硬编码--since -6h捞[relay] AUTH OK host=ops/manager;Manager 隧道稳定建立超过 6 h 就不再重注册 ⇒ 两台 relay 都读成 -1 ⇒killTarget = null⇒幕1-A降级为 SKIP、且幕1-B/幕1-C两腿结构上位于else分支(脚本 863–866 行)⇒ 一行都不打印 ⇒ 12 腿里 3 腿未判。🔴 这是判据口径缺陷("最近注册时间"不能用来判断"当前在哪台"),⛔ 不是产品回归;⛔ 本棒未改演练脚本(scripts/overlay-failover-drill.cjs零改动)⇒ 另立小项。
§13.2 零回归三件套(本棒实测读数 · 三件全绿)
| 件 | 命令 | 读数 |
|---|---|---|
| ① 单测 | npm.cmd test / node --test test/overlay-content.test.mjs |
200 pass / 0 fail / 1 skipped / 55/55(0 fail) |
| ② 探针 | node scripts/overlay-probe.cjs --table … |
29 PASS / 0 FAIL / 0 SKIP(rc=0;基线 28 ⇒ +1 = OBS-29) |
| ③ 演练 | node scripts/overlay-failover-drill.cjs --scene all --table … |
12 PASS / 0 SKIP / 0 FAIL(rc=0;耗时 8m25s) |
🔴 ③ 是先红后绿,⛔ 不是第一次就绿的(如实留档):首跑 = 9 PASS / 1 SKIP / 0 FAIL(rc=0)—— 幕1-A 报 SKIP(原文「⚠️ 无法唯一确定 Manager 当前在哪台(47 最近注册=-1 / 106=-1)」),且 幕1-B / 幕1-C 两腿一行都没打印。根因(原文级取证) = Manager 那条隧道已稳定建立 6 h 38 min:开工前两侧最后一条 AUTH = 1789720529(47)/ 1789720501(106),开工时刻 = 1789744400(= 23:13:20)⇒ 间隔 23,871 s ≈ 6.63 h > 6 h ⇒ 落在脚本硬编码的 --since -6h 窗口之外 ⇒ 两台都读 -1 ⇒ killTarget = null ⇒ 只记 幕1-A(SKIP) + 幕1-D(PASS)。数目自洽:9 PASS + 1 SKIP + 2 未打印 = 12 ✅。⇒ 🔴 归因 = 演练脚本判据口径缺陷(6 h 上限),⛔ 不是产品回归、⛔ 与本棒 C 改动无关。
绿腿 = 按脚本自己给出的归零办法(原文「归零办法:重启 dshs ⇒ 通道回到目录首位,其 relay 会留下新的 AUTH 行」)重启一次 47 dshs(R8 · 动手前已声明)⇒ lastManagerAuthOn 读到新鲜 AUTH(1789745060 = 23:24:20)⇒ 12 PASS / 0 SKIP / 0 FAIL:幕1-A 15,698 ms(deadline 30000)|幕1-B Manager active|幕1-C 门户 200|幕2-A/B/C ✅|幕3-A ✅|幕4-A 20,302 ms|幕4-B ✅|幕4-C 22,926 ms / 30,000。⏳ 残余未判项 = 0(重跑那轮 12 腿全部给出判定);⚠️ 但 幕1-A/B/C 的口径缺陷仍在(⛔ 本棒未修 ⇒ 另立小项)。
生产态核对(演练后 · ⛔ 无残留停机) = 47 dshs / dshs-relay / dshs-worker 全 active;106 dshs-relay / dshs-worker active(dshs inactive = 正常 —— 106 无 Manager);门户(http://127.0.0.1:3080/ + Host: alotbuy.com)200。⚠️ 本棒对生产的唯一动作 = 重启 47 dshs 一次(R8;无不可逆项);⛔ 未动任何配置值 / ⛔ 未改 nft·nginx·bwrap / ⛔ 未 scp / ⛔ 未 push。
§14 序㊼ 部署棒回填(2026-09-18 23:43 – 2026-09-19 00:1x · 按 §8 回报格式逐条)
本棒 =
C的落地棒:只做「把lib部署到两机 + 验OBS-29真机腿 + 复跑零回归」。🔴 零代码改动 · 零判据改动 · 零参数表值格改动。
1. 部署口径(本棒实测认定 · ⚠️ 与"只传改动文件"的旧印象不同)
- 实测两机 4 处
lib都是历次增量 scp 的叠加(各文件 mtime 不一:47/opt/dshs/lib/net/relay/index.js= 12:55:27(序㊵),而同目录content/chunker.js= 06:52:16)⇒ 与代码仓当前build产物同名 md5 不同 38–41 个(含client/directory/switcher/wire/orchestrator等与本单无关的文件)。 - ⇒ 本棒按全量替换口径部署(270 文件 × 4 处)。🔴 两机同一窗口内换(先各自传包,再并行解包 + 重启)。
- 包 =
dshs-lib-seq47.tgz(681,966 B / 270 文件,md5546b30719ca1bfca1e04189e1099424e);🔴 两机解包前各自先验包 md5(不符即停手)。
2. 备份(4 处 · 可回滚) —— 两机 /opt/dsh/backups/seq47-20260918-2347/:
| 机 | 目录 | 备份内容 |
|---|---|---|
| 47 | _opt_dshs_lib / _opt_dsh-relay_lib |
262 / 263 文件(共 6.2M) |
| 106 | _opt_dshs-cluster_lib / _opt_dsh-relay_lib |
260 / 262 文件(共 6.1M) |
3. 部署结果(逐文件 md5 核对 · 不一致 0)
- 47
/opt/dshs/lib= 282、/opt/dsh-relay/lib= 281;106/opt/dshs-cluster/lib= 282、/opt/dsh-relay/lib= 281。 - 与代码仓
lib:同名 md5 不同 = 0(270 × 4 = 1080 对全额逐字节一致)、仅本机有 = 0。 - ⚠️ 各位置多出的 11–12 个 = 历史
.bak-*遗留(09-17 时期备份,⛔ 本棒未删)。 - ⚠️ prompt 里"核 32 个 md5"的口径未能复现 —— 本棒现核为 270 × 4;已按全量核对,并把该差异如实记在此处。
4. 重启(R8 · 动手前已声明) —— 47 dshs-relay / dshs / dshs-worker 全 active;106 dshs-relay / dshs-worker active(dshs inactive = 正常 —— 106 无 Manager)。
5. OBS-29 真机腿 = ✅ PASS(本棒主判据)
- 47
/status.content:blockIdKeyed=true/blockIdKeyId=d6e62322e5166938/blockSize=1048576(⛔ 未动)/storeMaxBytes=67108864(⛔ 未动)。 - 106 同上,
blockIdKeyId逐字相同(=d6e62322e5166938)。 - 🔬 交叉锁(第二来源) = 两机 relay 启动判别器日志原文:
[content] 块 id 口径 = HMAC-SHA256(域密钥) blockIdKeyId=d6e62322e5166938(47 23:47:43 / 106 23:47:37)。
6. 零回归三件套(本棒读数)
| 件 | 读数 | 与基线 |
|---|---|---|
| ① 探针 | 28 PASS / 0 FAIL / 1 SKIP(rc=0) | ⚠️ 基线 29P/0S/0F ⇒ OBS-09 退化为 SKIP(见第 7 条) |
② --scene all |
12 PASS / 0 SKIP / 0 FAIL(rc=0;6m28s) | ✅ 与基线逐字同(幕1-A 本次未触发 6 h 坑 —— 因本棒刚重启 47 dshs、AUTH 行新鲜) |
③ OBS-29 行 |
五腿全绿 P1/P2/P3/N1/N2 |
✅ 与序㊻ 本机读数一致 |
7. 🔴 OBS-09 SKIP 的成因与定性(本棒唯一差异 · 如实留档)
- 原文 =
SKIP OBS-09 在册实例面 无(从 /status.endpoints[] 派生为空:端点表 1 条 = agent 1 条 + 离线 0 条)⇒ SKIP + 留痕|对端中继(test106)在运行但没有任何会话在声明端口…。 - 事实链(四条,均已实测):① 106 实例
286172健在(node /usr/bin/dsh --profile web --port 21001,启动于 10:18:34 = 本棒重启前 13.5 h)② 未被 teardown:OBS-22=scanned=1 adopted=1 stopped=0+ 日志[rehydrate] probe OK dsh-100002-7d1c8cbf.scope :21001③ 但 47 relay 端点表只有w-106:19000,缺w-106:21001(参数表 §3.9PEER_INSTANCE_PORT行 10:2x 实测应有两条)④ 复跑探针两次(间隔 ~6 min)均未自愈。 - 定性 = 🔴 既有缺陷被本棒硬要求的「重启 106
dshs-worker」触发 —— ⛔ 非本单引入、⛔ 非业务中断。历史留档(本文件 §0「序 ㉙」行)已实测同一机理:relay-tunnel.ts#cancel()只撤销当前进程forwarded集里的端口 ⇒ 上一进程遗留条目无人撤销 ⇒ 原文结论「重启后OBS-08/OBS-09必红,⛔ 不是"跑一会儿就好"」。 - 🔴 恢复路径须"实例重新拉起"(端口登记发生在 spawn 流程)⇒ 必然中断该实例 ⇒ 与 memory 明令「⛔ 别为迁就旧值重启生产实例」冲突 ⇒ 本棒不修、不凑绿,另立小项(回头条件 = 出「跨机实例面访问不通」或下一次收口仍 SKIP)。
8. 边界自证
⛔ 未 commit / 未 push(HEAD 45b4999 未动;git status 仍 21 处)|⛔ 未改 DEFAULT_BLOCK_SIZE / package.json|⛔ 零新依赖|⛔ 未改 nft / nginx / bwrap|⛔ 未改参数表值格(§10 现算指纹 ac6bbbbb8c92bd57ff0dc4cd8f4983ba 与 §13 记录同值;本棒补记落 §11.14 = 在 §10 口径之外)|🔴 密钥本体不经网络 / 不经 relay(本棒只读 /status,⛔ 未触碰密钥文件)|⛔ 未调 RELAY_FAILOVER_DEADLINE_MS、⛔ 未动 HB_SEC·burst、⛔ 未禁用 COOLDOWN_MS。
9. 回滚 —— 两机同窗口把 4 处 lib 回退到 seq47-20260918-2347/ 并重启该机单元。⚠️ C 的 id 换代不是无损(回退后 id 会再变一次);但块存储纯内存 ⇒ 重启即清空、无脏块残留。
10. 生产态终态核对 —— 47 三单元 active;106 dshs-relay / dshs-worker active(dshs inactive = 正常);两机 relay /status 可读、blockIdKeyed=true、blockIdKeyId 逐字相同;演练 幕1-C / 幕2-C 门户 200 ✅。
§15 序㊽ 执行棒回填(2026-09-19 00:15 – 01:0x · 按 §8 回报格式逐条)
本棒 =
OBS-09「重启 worker 后实例端口注册丢失 / 孤儿端点条目不自愈」修复棒。🔴 零中断:⛔ 未为验证重启任何实例 —— 走的是「认领落定后重登记」路径,⛔ 不是 spawn 路径(见 §15-1 的第 ④ 条:§14-7 那句「恢复须实例重新拉起」已被本棒实测证伪)。
1. 判定依据(文件 + 行 + 原文)——「重启后重注册已认领实例端口」该落在哪一步
reconcileTunnel()(对账自愈)早就存在,链路本身是通的 —— 它只是看不到那条端口:
| # | 位置(改前) | 原文 / 事实 |
|---|---|---|
| ① | src/worker/agent.ts:256-264 |
const live = new Set((await spawner.listUserInstances()).map((i) => i.port).filter(…)) —— 对账口径只取一条腿 |
| ② | src/supervisor/orchestrator.ts:558-560 |
listUserInstances(): Instance[] { return [...this.mains.values()] } —— 口径 = 本进程 launch 过的(mains) |
| ③ | src/supervisor/orchestrator.ts:290 |
/** 已被「认领」的既有 scope —— ⛔ 刻意**不进** mains:见文件头 序 ㉕ 的边界说明。 */ ⇒ adoptOne() 只写 this.adopted |
| ④ | src/supervisor/orchestrator.ts:187-192(文件头 序㉕) |
「🔴 一条必须先说的客观边界(本序实测得出,⛔ 别再试图绕过):『认领』不可能做到"用户无感直接复用"…」⇒ 认领实例恒不进 mains(launchToken 不可恢复) |
| ⑤ | src/net/relay/server.ts:2151-2203(dropSession) |
for (const ep of this.endpoints.values()) { if (ep.session === session) ep.session = undefined } —— 断开只摘会话、⛔ 不删条目 ⟹ 上一进程的条目留在表里(online=false) |
| ⑥ | src/net/relay/server.ts:1447-1466(onPortChange(add=true)) |
const existing = this.endpoints.get(endpointKey(…)); … const ep = this.ensureEndpoint(…); ep.session = session ⇒ 复用既有条目、只换绑定会话(localPort 沿用)⟹ 重登记 = 就地覆盖孤儿 |
结论(落点判定) = 修复必须落在 ① 的对账口径,⛔ 不是 spawn 流程:②③④ 是刻意设计(⛔ 不改认领语义、⛔ 不把认领实例写进 mains),而 ⑤⑥ 说明 relay 侧本来就等着有人重新声明那个口 ⇒ 零中断路径真实存在。
2. 实现(2 个源文件 · ⛔ 零新依赖 · ⛔ 零新暴露面)
| 文件 | 改动 |
|---|---|
src/supervisor/orchestrator.ts |
新增 adoptedInstancePorts()(只报 alive === true 且带端口的认领记录);新增 onRehydrateSettled 回调 + pendingProbes / rehydrateScheduled 落定计数(settleRehydrate() / maybeRehydrateSettled());rehydrateAdoptedScopes() 三个出口均落 settleRehydrate();probeAdopted() 在途计数 ∓1 后补射 |
src/worker/agent.ts |
对账口径改为 liveInstancePorts() = listUserInstances() ∪ adoptedInstancePorts();抽出 tunnelTick()(自愈 + 对账)供定时器与回调共用一份语义;装配 spawner.onRehydrateSettled = () => void tunnelTick() |
🔴 ⛔ 未绕过既有准入 / 白名单:新端口走的是同一条 tunnel.forward() → RelayClient.addPort() → PORT_ADD → relay.onPortChange(含既有 [base, base+span) 窗口校验 + worker 侧 allow 白名单)⇒ ⛔ 零新代码路径、零新监听口、零新 env。
3. 先红后绿(原文级)
(a) 探针读数
改前(probe_before.txt · 00:23:09) |
改后(probe_final.txt · 00:46:33 · 终态) |
|
|---|---|---|
| 计数 | 26 PASS / 2 FAIL / 1 SKIP(rc=1) | 29 PASS / 0 FAIL / 0 SKIP(rc=0) |
OBS-01 |
FAIL 在册节点 used=1 (阈值 ≥ 2) |
PASS 在册节点 used=2 (阈值 ≥ 2) |
OBS-08 |
FAIL 端点表 1 条 / 离线 1 条 |
PASS 端点表 2 条 / 离线 0 条 |
OBS-09 |
SKIP 在册实例面 无(…端点表 1 条 = agent 0 条 + 离线 1 条) |
PASS 在册实例面(派生 1 条):w-106:42497=401 (阈值 ∈ {200,401}) |
⚠️ 如实纠正:prompt 与本单 §14-6 记的「退化 = 28P/0F/1S」与本棒开工实测不一致 —— 实测 26P/2F/1S(OBS-01 也已转红)。归因有原文:106 worker 在 00:04:30 按抖动路径切到了 106 自己的中继 —— [relay-switch] #3 wss://alotbuy.com/dshs-relay -> wss://106.54.21.172/dshs-relay(原因:当前通道抖动量超标(p95|ΔRTT|=2941ms ≥ 阈值 20ms)且 wss://106.54.21.172/dshs-relay 更稳(0ms);冷却 wss://alotbuy.com/dshs-relay 至 +300000ms) ⇒ 47 视角同时失去 w-106 会话与其实例面。⛔ 本棒未编数、按实测留档。
(b) 端点表原文(/status 直读)
| 时刻 | 47 relay endpoints[] |
106 relay endpoints[] |
|---|---|---|
| 改前 | [('w-106',19000,42313,**False**)];sessions=[('manager',[])];used=1 |
[('w-106',19000,38587,**True**)];sessions=[('w-106',**[19000]**)] |
| 改后 · 00:33:17 | [('w-106',19000,45095,**True**), ('w-106',21001,36535,**True**)];sessions=[('w-106',**[19000,21001]**),('manager',[])];used=2 |
[](worker 已回落 47) |
| 终态 · 00:44:06(演练后) | [('w-106',19000,39471,True), ('w-106',21001,42497,True)];used=2 |
[] |
🔴 先红最关键的一条:改前 106 relay 自己的表里也只有 19000 —— 而 106 上 ss -lntpH 明明白白 127.0.0.1:21001 在听、scope 数 = 1 ⇒ 「实例活着、却没人替它把端口声明出去」,与"挂在哪台中继"无关(⛔ 不是单纯"挂在另一台中继")。
(c) 机理原文(47 relay journal · --since 00:31:00)
00:31:13 [relay] AUTH OK host=ops/w-106 session=4cc572b38351c711 ports=[19000] remote=127.0.0.1:50714
00:31:13 [relay] endpoint ops/w-106:19000 -> 127.0.0.1:45095 (loopback)
00:31:14 [relay] endpoint ops/w-106:21001 -> 127.0.0.1:36535 (loopback)
00:31:14 [relay] host ops/w-106 +port 21001 -> 127.0.0.1:36535
⇒ HELLO 只带 ports=[19000](静态口),1 s 后由新装的「认领落定即登记」补出 +port 21001。⚠️ 这一枪必须来自新回调:20 s 定时器首拍落在 00:31:31,而 +port 在 00:31:14;且旧口径下 20 s 拍同样看不到 21001(live 只取 mains)。
(d) 孤儿条目自愈 —— 受控对照(⛔ 未停实例)
| 步骤 | 47 relay 读数 |
|---|---|
| [A] 停 worker 前 | used=2 eps=[(19000,45095,True),(21001,36535,True)] |
[B] systemctl stop dshs-worker(孤儿态) |
used=1 eps=[(19000,45095,**False**),(21001,36535,**False**)] ← 条目仍留在表里(正是改前 OBS-08 的红形态);同时 106 ss -lntH 'sport = :21001' = 1 行 ⇒ 实例进程全程未动 |
[C] systemctl start dshs-worker |
used=2 eps=[(19000,45095,**True**),(21001,36535,**True**)] |
🔬 最强的一条:localPort 在 [B]→[C] 逐字沿用(45095 / 36535 未变)⇒ 证明走的是 §1-⑥ 的「复用既有条目、只换绑定会话」,⛔ 不是新开监听口。
4. 零回归三件套(终态)
| 件 | 读数 | 与基线 |
|---|---|---|
| ① 探针(真机 · 终态) | 29 PASS / 0 FAIL / 0 SKIP(rc=0;probe_final.txt 00:46:33) |
✅ 回到基线 29P/0S/0F |
② npm.cmd test(Node v22.22.2) |
200 pass / 0 fail / 1 skipped(# tests 201;rc=0;53.2 s) |
✅ 逐字同基线 |
③ --scene all(overlay-failover-drill.cjs --table) |
12 PASS / 0 SKIP / 0 FAIL(rc=0;6m01s) | ✅ 逐字同基线;⚠️ 未套外层超时;幕4-C 27087 ms / 30000 |
附加守卫(本棒自加):node --test test/orchestrator-rehydrate.test.mjs = 18/18 pass —— 该文件刻意不进 npm test,但它是唯一盯 adopted / mains 边界的用例 ⇒ 动这一处必须跑。
5. 部署(口径 / 备份 / 核对 / 窗口)
- 包 =
dshs-lib-seq48.tgz(685,368 B / 270 文件,md5a902c936cefdeeb3eb13c67dfcb5dd00);两机解包前各自先验包 md5 —— 两侧均回a902c936…✅。 - 备份 = 两机
/opt/dsh/backups/seq48-20260919-0030/:47_opt_dshs_lib282 /_opt_dsh-relay_lib281;106_opt_dshs-cluster_lib282 /_opt_dsh-relay_lib281(与 §14-2 逐数相同)。 - 核对(全量口径)= 270 × 4 = 1080 对:同名 md5 不一致 0、缺失 0;各位置多出 11–12 个 = 历史
.bak-*遗留(⛔ 未删)。 - 重启(R8 · 动手前一句话已声明)= 47
dshs-relay/dshs/dshs-worker全active;106dshs-relay/dshs-workeractive(dshsinactive= 正常,106 无 Manager)。 - 🔴 如实留档(本棒自造偏差):首轮「两机同窗口」脚本里我把
cd写进了后台复合命令内 ⇒ 第二个后台作业不在目标目录 ⇒ 106 未执行(No such file or directory),而 47 已落地并重启(rc=0,全active)⇒ 两机实际窗口相差 ≈ 28 s(47 00:30:47 / 106 00:31:15),⛔ 未做到"严格同窗口"。影响评估 = 本棒改动是纯 worker / supervisor 侧增量(新方法 + 对账口径 + 一个可选回调字段;⛔ 无协议帧 / 无接口形状 / 无 env 变化)⇒ 47 新 / 106 旧的那 28 s 内无跨版本不兼容;随后三件套全绿可作旁证。⛔ 未回滚重做(重做只会再造一次同类窗口 + 一次多余重启)。
6. 边界自证
⛔ 未 commit / 未 push(HEAD 45b4999 未动)|⛔ 未改 DEFAULT_BLOCK_SIZE / package.json|⛔ 零新依赖|⛔ 未改 nft / nginx / bwrap|🔴 ⛔ 未改参数表值格(只追加 §11.15 补记)|🔴 密钥本体 ⛔ 不经网络 / 不经 relay(本棒只读 /status 与 journal,⛔ 未触碰密钥文件)|⛔ 未调 RELAY_FAILOVER_DEADLINE_MS、⛔ 未动 HB_SEC / burst、⛔ 未禁用 COOLDOWN_MS=0|§8 前前缀 be548afc3340583b2b63ca254bcf550f 回填本 §15 后逐字不变 ✅。
7. 停止条件核对(⛔ 一条都未命中)
⛔ 未改网络拓扑|⛔ 未新增监听口(OBS-11 多出 0 缺失 0)|⛔ 未改 nft / nginx|⛔ 未放开遮蔽 ⇒ 无需停手。
8. 中断记录(如实)
🔴 本棒 ⛔ 未重启 / 未停任何实例 —— 走的是认领后重登记路径(§14-7 那条「必须中断实例」的旧判断已被证伪)。唯一服务中断 = 受控对照演示中的 dshs-worker 停启各一次(停用窗口 ≈ 5 s;期间 106 实例与用户面全程未动:ss -lntH 'sport = :21001' 全程 1 行)。
9. 回滚
两机同窗口把 4 处 lib 回退到 /opt/dsh/backups/seq48-20260919-0030/ 并重启该机单元。⚠️ 本棒不涉及块 id / 存储口径 ⇒ 回退无损(⛔ 与 §14 的 C 不同,⛔ 不会"再变一次 id")。
10. 未验证项 / 遗留(⛔ 如实,⛔ 不编数)
- 🔴
OBS-01/OBS-08/OBS-09的绿不稳固 —— 取决于 106 worker 挂在哪台中继。worker 通道会按抖动切换(改前实测原文已引于 §15-3a),而探针只读 47 的RELAY_STATUS_URL⇒ 一旦 worker 落到 106 自家中继,47 视角即无实例面 ⇒ 三项会再次转红 / SKIP。🔴 本棒 ⛔ 未动选路策略(属既有设计:switcher.ts抖动换址 + 一跳豁免)⇒ 这是"探针观测点单一"与"多中继归属可漂移"之间的口径缺口,如实登记为未解决项(回头条件 = 下一次收口复跑探针若再出现OBS-01/OBS-09红或 SKIP)。 - ⚠️ "已消失端口的孤儿"仍未回收:本棒修的是「活着的实例端口重登记」(⇒ 活口的孤儿会被就地覆盖)。若实例被自然替换(
21001 → 21002),旧端口条目没人PORT_DEL⇒ 会以online=false永久留在表里(正是OBS-08的红形态)。⛔ 本棒的reconcileTunnel撤不掉它 ——RelayClient.removePort()对"本进程从未加过的端口"直接早退。回头条件 = 出现端口替换后OBS-08再红。(⚠️ 修它需要 worker 能读到"中继侧自己的条目表",worker 与中继可能不同机 ⇒ 非本棒可及。) - ⚠️
PEER_INSTANCE_PORT现值仍 21001(本轮快照);OBS-09已不从该键取值 ⇒ ⛔ 无需改值格。
11. 收口时的现场观测(⛔ 如实 · 留给序㊾ 作"先红"基线)
收口前最后一轮只读核对(00:52:37)显示 §15-10-1 那条遗留当场复现 —— 106 worker 又按抖动路径挂回了 106 自家中继:
| 机 | units |
relay /status |
|---|---|---|
| 47 | dshs / dshs-relay / dshs-worker 全 active |
used=1 · eps=[('w-106',19000,**False**),('w-106',21001,**False**)](两条离线孤儿) |
| 106 | dshs-relay / dshs-worker active(dshs inactive = 正常) |
used=1 · eps=[('w-106',19000,**True**),('w-106',21001,**True**)] |
🔴 两条要点(⛔ 不许混为一谈):
- ✅ 修复本身是有效的 —— 106 自己的中继表里两条都
online=true(含实例面21001)。这正是本棒要的形态:改前 106 自家中继表里只有19000(见 §15-3b)。⇒ 本棒修复与挂在哪台中继无关,都成立。 - ⚠️ 但探针的绿不稳固 —— 探针只读 47,此刻 47 视角是"1 条会话 + 2 条离线条目" ⇒ 若此刻复跑探针,读数会回到
OBS-01 FAIL/OBS-08 FAIL/OBS-09 SKIP(≈26P/2F/1S)。🔴 本棒录取的 29P/0F/0S 是 00:35:59 与 00:46:33 两个时刻的实测真值(worker 当时挂在 47),⛔ 不是打了折扣;也 ⛔ 未为了让读数好看去重启 worker(那属"凑绿",且抖动换址会让它再漂)。 - 📌 ⇒ 本棒交付的验收结论必须按"两条腿"读:① 修复有效(凡 worker 所在的中继,实例面必在册且
online)② 探针观测面单一(单一观测点 + 可漂移的归属 = 假红 / 假 SKIP)—— 第 ② 条已登记为序㊾ 执行棒(automation2febff6b-5786-46a3-b345-b648f2e19df3),本棒 ⛔ 不越界修。 - ⚠️ 另:本棒为受控对照停启过一次
dshs-worker(§15-8),其后 worker 重启即按DSHS_RENDEZVOUS_URL=wss://alotbuy.com/dshs-relay(= 47)落位;00:52 这次漂移是它自己按 jitter 换址的,⛔ 与本棒的停启无关(停启发生在 00:36 前后)。
§16 序㊾ 执行棒回填(2026-09-19 01:0x – 01:4x · 按 §8 回报格式逐条)
本棒 = 「探针观测面单一」修复棒(结清 §15-10-1 那条遗留)。🔴 只改观测面 —— 改动面 =
scripts/overlay-probe.cjs一个文件;⛔src/**零改动、⛔ 零新增监听口、⛔ 未改 nft / nginx / bwrap、⛔ 未动任何服务配置。
1. 缺陷与落点(⛔ 不是网络故障、⛔ 不是 worker 故障)
三条判据的绿取决于 106 worker 挂在哪台中继,而 worker 通道会按抖动换址(switcher.ts,属既有设计)⇒ worker 一旦落到 106 自家中继,47 视角就同时失去"会话"与"实例面":
| # | 判据 | 旧数据源(唯一) | 漂移态下 47 视角读数 → 判决 |
|---|---|---|---|
| ① | OBS-01 在册节点数 |
47 /status.capacity.used |
used=1(只有 manager)⇒ FAIL(阈值 ≥ 2) |
| ② | OBS-08 端点表全在线 |
47 /status.endpoints[] |
2 条 / 离线 2 条(w-106 的两条离线孤儿)⇒ FAIL |
| ③ | OBS-09 在册实例面探活 |
47 /status.endpoints[] 派生 |
派生子为空(两条都 online=false)⇒ SKIP |
🔴 性质 = 假红 / 假 SKIP —— 客户端一直好好的,只是"不在我这一台"(worker 会话与实例端口都如实在册,在106 自己的中继表里:used=1 + 两条 online=true)。⇒ 落点 = 探针的取数范围,⛔ 不是"把判据放宽"。
2. 实现(一个文件 · 六处改动 · ⛔ 阈值一字未动)
| # | 位置 | 改动 |
|---|---|---|
| ① | main() 新增 readPeerView() |
对端(106)/status 的唯一取数入口(真机 = 一次 ssh 取回 /status 原文 + __RELAY_ACTIVE__ 活性哨兵;夹具 = --peer-status-fixture;缓存一次供并集与「空表可分」共用 ⇒ ⛔ 零重复 ssh) |
| ② | 新增 mergeEndpoints() |
端点表并集:键 = network:hostId:port;online 取或;localPort 取在线那一侧(离线条目的落点已失效);from 记"该条最终采信谁" |
| ③ | 新增 unionUsed() |
在册节点数并集 = network/hostId 去重后的并集基数(⛔ 不是 a.used + b.used —— 同一节点在两台都残留时会重复计数 = 另一方向的假绿);任一侧 sessions[] 不可用 ⇒ 记 exact=false 并回落 max(各侧 used)(⛔ 不假装是并集) |
| ④ | 新增 remoteInstanceCodes() |
只取实例面 HTTP 码的一次 ssh —— 并集里归属 106 的端点,其回环落点只存在于 106(在 47 上探必然 000 = 假红)⇒ 按 from 分机探活;⚠️ 只在真有 106 侧在册实例时才发(正常态零额外取数) |
| ⑤ | judgeObs08() / judgeObs09() / OBS-01 |
数据源换成并集(判据形态、阈值、派生规则一字未动) |
| ⑥ | classifyEmptyView() 收敛进 ① |
原"只在 47 视角为空时才去读对端"的老逻辑改由 readPeerView() 承载(同一个坑不再写第二份) |
🔴 只并这三项:OBS-11 的 derived(= 47 自己的监听面)/OBS-02(47 自身容量自洽)/OBS-13·OBS-16(47 的 counters)一律保持 47 视角 —— 把 106 的落点并进 derived 等于凭空放宽"多出"判据。⚠️ 真机模式无条件读一次对端(⇒ 并集是完整的);⛔ 不做"缺什么补什么"的按需读 —— 那会留下一个假绿口子(106 上多一台 47 不知道的节点时,47 视角照样绿)。⚠️ 对端读不回来 ⇒ 不硬失败,回落 47 视角并强制留痕「并集不可取证(…)⇒ 本项仅按 47 视角判」。
3. 先红后绿(原文级)
(a) 夹具腿(同一份夹具,两侧只差是否给对端那一半) —— 夹具 = 真实 /status 原文(s1/s2/s3 = 47 视角 + counters.statusHits 递加,peer.json = 106 视角)+ --listen-fixture / --nft-fixture(真实 47 ss / nft)+ 参数表副本(SSH_TARGET_47/106 → 127.0.0.1、SSH_PORT → 1 ⇒ "夹具跑里到底有没有 ssh"可观测):
| 腿 | 命令差异 | rc | OBS-08 |
OBS-09 |
|---|---|---|---|---|
| 🔴 红 | 不给 --peer-status-fixture(= 并集退化成 47 视角) |
1 | FAIL 端点表(并集) 2 条 / 离线 2 条 |并集: 47 视角 2 条 + 对端 ? 条 去重后 2 条 |
SKIP 在册实例面 无(从并集端点表派生为空:并集 2 条 = agent 0 条 + 离线 2 条) |
| 🟢 绿 | 加 --peer-status-fixture |
0 | PASS 端点表(并集) 2 条 / 离线 0 条 |并集: 47 视角 2 条 + 对端 2 条 去重后 2 条 |
PASS 在册实例面(并集派生 1 条):w-106:36873=401 (阈值 ∈ {200,401}) |
🔬 两侧逐行 diff 只差这 2 行(OBS-01 在夹具模式下按既有语义记"夹具模式未取证" ⇒ 它的红腿由下面 (b)(c) 承载)。
(b) 真机 · 漂移态(--scene all 收口后现场恰好就是"47 视角空") —— 47 /status = used=1 · endpoints=[]、106 /status = used=1 · eps=[('w-106',19000,46105,True),('w-106',21001,40701,True)],探针原文:
PASS OBS-01 在册节点 used=2 (阈值 ≥ 2) |并集: 47 视角 used=1 + 对端 used=1 ⇒ 按 network/hostId 去重后计
PASS OBS-08 端点表(并集) 2 条 / 离线 0 条 |并集: 47 视角 0 条 + 对端 2 条 去重后 2 条
PASS OBS-09 在册实例面(并集派生 1 条):w-106:40701=401 (阈值 ∈ {200,401})
🔬 最强的一条:OBS-09 探的是 40701 —— 那是 106 机上的回环落点(47 上根本不存在该口)⇒ 证明"按 from 分机探活"这条腿真的走到了 106,⛔ 不是拿 47 的旧落点凑绿。⚠️ 该轮唯一红 = OBS-04 identityOk=1(演练余波:--scene all 重启了 47 relay ⇒ 计数从零开始;⛔ 与并集无关,既有处置 = 停/启 106 relay 逼 worker 回落,复原后即绿——本棒已执行,见 §16-5)。
(c) 真机 · 退化腿(对端不可达) —— 表副本只把 SSH_TARGET_106 指向 127.0.0.1:rc=1,三条原文 = FAIL OBS-01 used=1(并集不可取证 ⇒ 仅 47 视角)/FAIL OBS-08 端点表(并集) 2 条 / 离线 2 条/SKIP OBS-09 ⇒ 与改前的 47 单点判法逐条一致。⚠️ 如实留档:该腿还带出 4 条与并集无关的红(OBS-18 / OBS-21 / OBS-22 / OBS-23 —— 它们本就要 ssh 到 106),⇒ 该腿只用于取"三条原文",⛔ 不能当"改前全量读数"用;改前全量真值 = 26 PASS / 2 FAIL / 1 SKIP(rc=1)(§15-11 现场 + 本棒 prompt 同数)。
4. 硬约束③ 实证 —— OBS-16 的 Δ 不被对端那次采样污染(本棒先核后做)
| 项 | 事实 |
|---|---|
对 47 /status 的读取次数 |
一次都没变(仍是三次:status / status2 / status3,判据全部复用已采到的那份) |
| 对端那份的位置 | 在第三次采样之后 ⇒ ⛔ 不进 OBS-16 的门窗口 (status2, status3](该窗口里只有 sleepSync) |
| 隔离性实验(原文) | 47 statusHits: 5 → 6 (Δ=1),中间插入了 2 次读 106;106 自身 2 → 3 ⇒ ✅ 读 106 不会动 47 的计数器(OBS-16 只取 47 的 counters) |
| 运行期留痕 | OBS-16 行末已写明:|Δ 口径 = **仅 47** 的 counters(对端 106 的采样另算一次、⛔ 不入 Δ;对 47 的读数次数仍为三次) —— ⛔ 防日后被误并 |
5. 零回归三件套(终态)
| 件 | 读数 | 与基线 |
|---|---|---|
| ① 探针(真机 · 终态) | 29 PASS / 0 FAIL / 0 SKIP(rc=0;probe_final.out) |
✅ 回到基线 29P/0S/0F(OBS-01 used=2|OBS-08 2 条/离线 0|OBS-09 w-106:35713=401) |
② npm.cmd test(Node v22.22.2) |
200 pass / 0 fail / 1 skipped(# tests 201;rc=0) |
✅ 逐字同基线 |
③ --scene all |
12 PASS / 0 SKIP / 0 FAIL(rc=0;6m10s;⚠️ 未套外层超时) | ✅ 逐字同基线;幕1-A 15386ms / 幕4-C 19650ms(deadline 30000ms);幕1-A 本次未触发 6 h 坑 |
⚠️ 演练后处置(既有程序 · R8) = 收口时 47 视角为 endpoints=[] / used=1 ⇒ 停 / 启 106 dshs-relay 逼 worker 按候选链回落 47(⛔ 未重启 worker、⛔ 未换实例)。⚠️ 首轮"停 3 s 即起"不足够(106 是该 worker 候选链的第一条,它会回连 106)⇒ 实际停机 45 s 后回落成功(47 used=2 idOk=2、两条端点 online=true),随后恢复 106 relay(active)⇒ 终态读数如上。
6. 部署(落点 / 既有 md5 / 新 md5 · ⛔ 零新增监听口)
| 机 | 落点 | 既有 | 本棒 |
|---|---|---|---|
| 47 | /opt/dshs/scripts/overlay-probe.cjs |
md5 eaec1ad560adbec37a15cd145a5ebb77(109266 B · Sep 18 12:49 = 序㊳ 期,陈旧) |
就地更新 ⇒ d2f879dc3220f2800d812437518e3c63(135215 B · 0755) |
| 106 | /opt/dshs-cluster/scripts/overlay-probe.cjs |
不存在(该机 scripts/ 是 47 的子集 50 件;overlay 系只有 direct-probe / keyring / relaykey-add) |
新落 ⇒ d2f879dc…(root:root 0755) |
✅ 两机落点 md5 与代码仓同值(d2f879dc3220f2800d812437518e3c63)。🔴 为什么 106 也落:两台机若一份新一份旧/无,日后在那台机上跑一次就得到旧口径读数(= 本线反复踩的"同一事实两处打架")⇒ 取两机同源。⚠️ 该文件不被任何单元读取(探针是"从本机 ssh 出去"的工具)⇒ ⛔ 零服务影响、⛔ 零监听口。⚠️ 本棒 ⛔ 未按"备份 + 替换"三段式处理 47 的旧副本(直接覆盖)—— 该副本是被 git 追踪文件的陈旧拷贝、且代码仓内有更新版本 ⇒ 判定无损(如实留档)。
7. 边界自证
⛔ 未 commit / 未 push(HEAD 45b4999 未动)|⛔ 未改 DEFAULT_BLOCK_SIZE / package.json|⛔ 零新依赖(改动全在既有 Node 内建模块)|⛔ 未改 nft / nginx / bwrap|🔴 ⛔ 未改参数表值格(只追加 §11.16 补记)|🔴 密钥本体 ⛔ 不经网络 / ⛔ 不经 relay(本棒只读 /status)|⛔ 未调 RELAY_FAILOVER_DEADLINE_MS、⛔ 未动 HB_SEC / burst、⛔ 未禁用 COOLDOWN_MS=0(grep 计数 0)|§8 前前缀 be548afc3340583b2b63ca254bcf550f 回填本 §16 后逐字不变 ✅。
8. 生产动作(R8 · 动手前已声明 · 全可回溯)
① 两机 scripts/ 落点更新(纯文本、无服务读取)② --scene all 演练(内含多次 relay / Manager 停启)③ 收口处置 = 106 dshs-relay 停 45 s / 起(逼 worker 回落 47)。⚠️ 多次读 /status(探针三次 + 对端一次 + 取证若干)—— ⛔ 均为只读、⛔ 未改任何计数语义。⛔ 零不可逆动作、⛔ 未停/未换任何实例。
9. 回滚
改动面 = 单文件,回滚 = 反向应用 §16-2 的六处改动(或从代码仓取上一版覆盖)。🔴 如实留档:本机没有改前那份的字节级副本(工作树是未提交状态 git status M,开工时未做 .bak;git show HEAD:scripts/overlay-probe.cjs = 114850 B ≠ 改前 122541 B)⇒ 回滚依据 = §16-2 的改动清单。⚠️ 47 的旧副本(109266 B)也不是合法回滚目标(陈旧,缺序㊲–㊻ 多轮改动)。
10. 未验证项 / 遗留(⛔ 如实)
- 🔴
D8云安全组(乙-1)仍未落地 —— 两机 + 本机均无云凭据(序㊹ 已取证)⇒ 只能你在云控制台落地(47 入站UDP 21100/21115←106.54.21.172/32;106 入站 ←47.77.182.89/32;⛔ 零个0.0.0.0/0)。你已批准但定"先记下后续再处理" ⇒ 本棒按挂起处理、⛔ 未开工、⛔ 不索要 AK/SK。 - ⚠️ "已消失端口的孤儿"仍未回收(§15-10-2)—— 与并集无关:并集能消除"看不见"(
OBS-08不再因离线孤儿转红),但那些online=false的条目仍永久留在表里。回头条件不变:出现端口替换后OBS-08再红。 - ⚠️
OBS-04(identityOk ≥ 2)对中继重启敏感(计数归零)⇒ 演练 / 重启后需一次回落动作才回绿。⛔ 本棒未改该判据(属 §1 边界内既有口径,如实登记)。 - ⚠️ 夹具模式下
OBS-01仍记"夹具模式未取证"(既有语义、⛔ 本棒未改)⇒ 它的红腿只能在真机上取(本节 §16-3b/3c)。
11. 产物 / 证据落点
- 代码:
scripts/overlay-probe.cjs(唯一改动文件;md5d2f879dc3220f2800d812437518e3c63) - 证据目录:
E:\ProgramData\AI技能\aliyun-dsh-server\_tmp_seq49\——probe_final.out(终态 29P)/probe_drift_green.out(漂移态三条绿 +OBS-04演练余波)/fxA_red.out↔fxB_green.out(夹具两腿逐行 diff)/real_red.out(对端不可达退化腿)/exp_obs16.txt(隔离性实验)/npmtest.txt/drill_all.txt/两机/status原文relay47.json/relay106.json - 参数表:§11.16 补记