Files
dsh_shenxian/dsh-server-docs/交接单/覆盖网络-序45-低熵块治理-测熵与实现.md
T
admin d2ef362a98 feat(overlay): 覆盖网络线 序㊾ —— 探针观测面改「两台中继并集」(附 序㊽ 源码/文档补提交)
序㊾(本棒):
- 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 登记
2026-09-19 05:35:35 +08:00

620 lines
69 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 交接单 · 低熵块治理(**测熵先行** + 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.md` 21: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 范围
**要改**(⛔ 只改这些)
1. `src/net/relay/content/chunker.ts` —— `blockIdOf` / `contentIdOf` 加 per-network 密钥(C);低熵判定的落点若在此则一并加(D)。
2. `src/net/relay/content/crypto.ts` —— D 的加密路径(D-1 稀释 / D-2 随机密钥 + 封装)。
3. `blockIdOf` / `contentIdOf` 的**全部调用点**(⚠️ 逐处过,漏一处 = 校验必红)。
4. `scripts/overlay-probe.cjs` —— 新增 `OBS-29`(判据 + 负腿);⚠️ **夹具模式必须封闭**(`if (夹具) {…} else if (fixture) { SKIP + 留痕 } else { ssh }` —— 守卫**排在 ssh 之前**,本线既定口径)。
5. `参数表_覆盖网络_20260917.md` —— §11.3 补记区(`M1` 读数)+ §6 新增 `OBS-29` 行 + 新键;⚠️ 若新增键 ⇒ §10 指纹**必然变**,按现算更新。
6. **文档**:本单 §一 台账行 → ✅;`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`:低熵块的种类数 / 体积 / 占首屏包比例**(**本单第一个动作**)
1. **取真实内容**(⚠️ ⛔ **绝不用 `makeBytes()` 之类合成字节**):
- **S1 = 真实首屏包**:`curl -s --http1.1 -o /tmp/fsp.bin "<portal>/plugins/"` ⇒ ⚠️ 先 `wc -c` 核对是否 ≈ `11,363,655`(口径见 `参数表:146`)。
- **S2 = 真实"独立小内容"集**:枚举覆盖网络**实际分发**的非首屏内容并留痕;⚠️ **若确实不存在 ⇒ 报 SKIP + 写清"不存在"**,⛔ 不许拿合成字节冒充。
2. **切分**:⛔ **必须调用仓库里那份 `chunkify`**(`src/net/relay/content/chunker.ts`)—— ⛔ 不许复刻一份算法(复刻 = 双源)。
3. **统计四项**(定义见 `04-133 §2.2`):`M1-a` 种类数 / 重复率 | `M1-b` 低熵块数 + 体积 + **H 直方图** | `M1-c` 占首屏包比例 | 🔑 `M1-d` **子窗口熵**(如 4 KiB 滑窗扫整份,给出低熵窗口的**尺寸分布**)。
4. **验证**:`M1-a..d` 四项都有数(⛔ 不许"只有定性结论");⚠️ **`M1-d` 必须有数** —— 它是防"首屏包全是高熵块"假绿的那条腿。
5. **落点**:先可落 `_tmp_seq*/` 取证;证据齐后**固化为只读探针** `scripts/overlay-entropy.cjs`(⛔ 零第三方依赖)。
> ⚠️ **本步不许出现任何生产写入**:`curl` 是只读 GET;⛔ 不重启、⛔ 不改 env、⛔ 不铺插件。
### 步骤 2 由 `M1` 结论**定 D 的作用面**(⚠️ 先判定,再动手)
- 输出一段**判定**:低熵块**存在与否 / 在哪(首屏包内 or 独立小内容 or 块内片段)/ 体积占比**,并据此**选定 D-1 或 D-2**(决策点 4)。
- **验证**:判定必须**引 `M1` 的具体数字**(⛔ 不许"我觉得")。
- ⚠️ 若判定"首屏包内无低熵块" ⇒ **D 的作用面收窄到 S2**,**⛔ 不得**因此去动块大小。
### 步骤 3 落地 **C(域分离)**
1. `blockIdOf` / `contentIdOf` 加 per-network 密钥(HMAC-SHA256,输出仍取前 `BLOCK_ID_HEX_LEN` = 32 hex)。
2. **逐处过调用点**(⚠️ 步骤 0 已清点;**> 10 文件 ⇒ 先出清单**)。
3. **验证**:`npm test`(Node 22)全绿;**且**新增一条单测断言"**不同 network ⇒ 同字节得不同块 id**"+"**同 network ⇒ 同字节得同块 id**"(两条都要,⛔ 只测前者会漏"去了重")。
### 步骤 4 落地 **D(非确定性)** + **重取 `E1` 基线**
1. 按步骤 2 的结论实现 D-1(或 D-2)。
2. 🔴 **`E1` 基线重置**:C 换了 id 口径 ⇒ **旧基线读数作废**,必须**重取**。⚠️ `E1` 的**定义不重估**(回源字节 ≈ 1 份 × 组数)。
3. **验证**:`OBS-29` 正腿绿;🔴 **负腿必须红** —— 见步骤 5。
### 步骤 5 判据面(`OBS-29` + 负腿)
1. 新增 `OBS-29`(判据面 + 阈值键;⚠️ 编号从 **29** 起,⛔ 不跳号不复用)。
2. 🔴 **必须有的负腿**:「**把稀释源换成确定性派生量(如 `HMAC(组密钥, 块序号)`)⇒ 判据必红**」。
理由(`04-133 §4.1`):攻击面是"**持组密钥者猜明文 + 复算**" ⇒ 确定性盐**对持钥者完全无效** ⇒ 没有这条负腿,"上了个无效的稀释"会**全绿**(本线老病根:装了没生效 = 静默放行)。
3. **验证**:探针自检 —— 正腿 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 回报格式(执行会话必须回填)
1. **`M1` 四项读数**(原文级)+ **H 直方图** + 数据来源(S1 的 `wc -c` / S2 的清单或"不存在")。
2. **步骤 2 的判定**(引具体数字)+ **选了 D-1 还是 D-2**(一句话理由)。
3. **`blockIdOf` 调用点清点数**(`> 10` 时附受影响文件清单)。
4. `npm test` 输出(尾 20 行 + 退出码)+ **新增两条块 id 断言的原文**。
5. `OBS-29` 探针输出:**正腿 PASS** + **负腿 FAIL 具名**(两条都要,⛔ 不许只给正腿)。
6. `E1` **重取的基线读数**(⚠️ 标明"旧基线已作废")。
7. `docs-audit.py` / `docs-sync-check.sh` 退出码。
8. 参数表 §10 **现算**指纹(新值 + 旧值)+ §11.3 落点行号。
9. commit sha(本机;⚠️ **推送需用户明确说"推送"**)。
---
## §9 停止条件(命中即**停下报告**)
1. 抢不到全局执行锁 ⇒ **停手报告**(⛔ R9:不得接管、⛔ 不得删锁)。
2. `M1` 对象(真实首屏包 / 真实小内容)**取不到** ⇒ **报 SKIP + 说清原因**,⛔ **不许用合成字节顶上**(否则整份读数失去意义)。
3. `M1` 结论要求**动 `DEFAULT_BLOCK_SIZE`** 才能让 D 生效 ⇒ **停下报告**(= 净变差,触 R11)。
4. 需要**新增生产 env** 或**引第三方依赖** ⇒ **停下报告**。
5. `blockIdOf` 调用点 **> 10 文件** ⇒ **先出清单**,未获批不动。
6. 发现方案方向与 `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 本棒如实留档(三条 · ⛔ 均不属本单产物)
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` 或标注废弃。
2. 🔴 **`/plugins/??…` combo 路由有鉴权** —— 无会话 = **401 / 24 B**(`{"error":"unauthorized"}`);⚠️ 且**盘上不存在"首屏包单文件产物"**(combo 由宿主**运行时**拼装,见 `@deepseek-ai/dsh-client-modules` 的 `README.zh.md`)⇒ **测熵取数只能走 HTTP + 临时会话**。
3. ⚠️ **"低熵"阈值(`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 个块(⛔ 域分离不得把去重干掉)')`。
**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**。
- ⏳ **真机腿(本棒做不了,回头条件已写进参数表 §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 本棒如实留档(三条 · ⛔ 均不属本单产物)
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` / 失败降级为空串)。⛔ **本棒未改演练脚本**(越界),**另立小项**。
2. 🔴 **`mksess.cjs` 已失效**(复述 §12.1-①):47 控制面**权威库 = PG** ⇒ 写 SQLite 的会话 **Manager 查不到 ⇒ 必 401**;正确做法 = 往 PG `sessions` 插(cookie `sid`/`token_hash` = `sha256hex(token)`)+ 用完即删。
3. 🔴 **`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 文件**,md5 **`546b30719ca1bfca1e04189e1099424e`**);🔴 两机**解包前各自先验包 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.9 `PEER_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 文件**,md5 **`a902c936cefdeeb3eb13c67dfcb5dd00`**);两机**解包前各自先验包 md5** —— 两侧均回 `a902c936…` ✅。
- 备份 = 两机 `/opt/dsh/backups/seq48-20260919-0030/`:47 `_opt_dshs_lib` **282** / `_opt_dsh-relay_lib` **281**;106 `_opt_dshs-cluster_lib` **282** / `_opt_dsh-relay_lib` **281**(与 §14-2 **逐数相同**)。
- 核对(**全量**口径)= **270 × 4 = 1080 对**:同名 md5 不一致 **0**、缺失 **0**;各位置多出 **11–12** 个 = 历史 `.bak-*` 遗留(⛔ 未删)。
- 重启(R8 · 动手前一句话已声明)= 47 `dshs-relay` / `dshs` / `dshs-worker` 全 `active`;106 `dshs-relay` / `dshs-worker` `active`(`dshs` `inactive` = **正常**,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. 未验证项 / 遗留(⛔ 如实,⛔ 不编数)**
1. 🔴 **`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)。
2. ⚠️ **"已消失端口的孤儿"仍未回收**:本棒修的是「**活着的**实例端口重登记」(⇒ 活口的孤儿会被就地覆盖)。若实例被自然替换(`21001 → 21002`),旧端口条目**没人 `PORT_DEL`** ⇒ 会以 `online=false` **永久留在表里**(正是 `OBS-08` 的红形态)。⛔ 本棒的 `reconcileTunnel` 撤不掉它 —— `RelayClient.removePort()` 对"本进程从未加过的端口"直接早退。**回头条件** = 出现端口替换后 `OBS-08` 再红。(⚠️ 修它需要 worker 能读到"中继侧自己的条目表",worker 与中继**可能不同机** ⇒ 非本棒可及。)
3. ⚠️ `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)—— 第 ② 条**已登记为序㊾ 执行棒**(automation `2febff6b-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. 未验证项 / 遗留(⛔ 如实)**
1. 🔴 **`D8` 云安全组(乙-1)仍未落地** —— 两机 + 本机**均无云凭据**(序㊹ 已取证)⇒ **只能你在云控制台落地**(47 入站 `UDP 21100/21115` ← `106.54.21.172/32`;106 入站 ← `47.77.182.89/32`;⛔ 零个 `0.0.0.0/0`)。**你已批准但定"先记下后续再处理" ⇒ 本棒按挂起处理、⛔ 未开工、⛔ 不索要 AK/SK**。
2. ⚠️ **"已消失端口的孤儿"仍未回收**(§15-10-2)—— 与并集无关:**并集能消除"看不见"**(`OBS-08` 不再因离线孤儿转红),但那些 `online=false` 的条目**仍永久留在表里**。**回头条件不变**:出现端口替换后 `OBS-08` 再红。
3. ⚠️ `OBS-04`(`identityOk ≥ 2`)对**中继重启**敏感(计数归零)⇒ 演练 / 重启后需一次回落动作才回绿。**⛔ 本棒未改该判据**(属 §1 边界内既有口径,如实登记)。
4. ⚠️ 夹具模式下 `OBS-01` 仍记"夹具模式未取证"(既有语义、⛔ 本棒未改)⇒ 它的红腿只能在**真机**上取(本节 §16-3b/3c)。
**11. 产物 / 证据落点**
- 代码:`scripts/overlay-probe.cjs`(**唯一改动文件**;md5 **`d2f879dc3220f2800d812437518e3c63`**)
- 证据目录:`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 补记