Files
dsh_shenxian/dsh-server-docs/04-调整方案/133-覆盖网络-低熵块治理方案-C域分离与D非确定性.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

199 lines
18 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 非确定性**)
- 版本:**v1 规划稿**(2026-09-18 · 规划棒 · ⛔ 零代码 / 零服务器改动)
- 状态:📐 **规划完成 · 待执行**(第一个动作 = **补测「低熵块种类数 / 体积 / 占首屏包比例」**,⛔ 从未测过)
- 上游决策:**用户 2026-09-18 21:28 拍板 —— 采纳 C + D**;**B(OPRF / SA-MLE)降级为可选加强、⛔ 本轮不立项**
- 权威来源(⛔ 不另起炉灶、⛔ 不重查文献、⛔ 不重新评估方案优劣):
- 工作区根 `调研_MLE加密去重最优方案_20260918.md`(**§3** 六方案族 / **§5** 结构性发现 / **§5.5** 复杂度与收益)
- `.workbuddy/memory/2026-09-18.md` **21:1x / 21:5x / 21:2x** 三节(C / D / B 定义、量化读数、"**收敛加密下持组密钥 ≠ 能解密**"这一前提)
- 阈值与判据面单一来源:工作区根 `参数表_覆盖网络_20260917.md`(⚠️ 现版 §10 指纹 = `d408d640246a980f702fe7b0a2895219`,**以现算为准**)
- 执行载体:`交接单/覆盖网络-序45-低熵块治理-测熵与实现.md`
---
## 0. 一句话
**分两件事治**:**C** 把块 id 的收敛哈希 `HA` 从"裸 `sha256(字节)`"改成"**每个 network 一把密钥**的 keyed hash"(切断跨 network 的相关性推断,代价≈0);**D** 让**低熵块**不再确定性(首选 **D-1「稀释域」** —— 低熵小字段与高熵内容拼在一起再加密;备选 **D-2** 每块随机密钥 + 密钥封装)。**方案能否定量,取决于一个从未测过的读数** ⇒ 因此**第一个动作不是改代码,是测熵**。
---
## 1. 现状取证(源码事实 · 本轮新读)
| # | 事实 | 出处(文件:行) | 含义 |
|---|---|---|---|
| 1 | 块 id = **`sha256(字节)` 前 32 hex**(**只由字节决定**,⛔ 无密钥) | `src/net/relay/content/chunker.ts:108-110`(`blockIdOf`),`BLOCK_ID_HEX_LEN = 32`(同文件 `:64`) | 这就是 **C** 要改的那一行;也是"相同明文 ⇒ 相同块 id ⇒ 跨节点共享"的**唯一**来源 |
| 2 | 整份内容的 id 同样 = `sha256(全部落库字节)` | 同文件 `:113-115`(`contentIdOf`) | **C 必须同时改它**,⛔ 只改 `blockIdOf` 会留下"内容指纹仍裸哈希"的缺口 |
| 3 | 块密钥**含明文成分**:`iv = HMAC(key,"iv"‖plain)` 前 12 B ⇒ `k = HMAC(key,"k"‖iv)` | `src/net/relay/content/crypto.ts:405-413`(`ivOf` / `keyOf`) | ⇒ 🔑 **持组密钥 ≠ 能解密**(本题的前提)。⇒ 低熵块之所以可枚举,是"**猜明文 + 复算 iv/k + 比对**",⛔ 不是"直接解密" |
| 4 | 组密钥文件缺省 `/etc/dshs/content-group-key.json` | 同文件 `:47`(`DEFAULT_GROUP_KEY_FILE`) | **C 的 per-network 密钥**应从这条既有链路派生,⛔ 不新引入密钥来源 |
| 5 | 🔴 **切分是「定长 1 MiB」**(`offset += blockSize` 死循环) | `chunker.ts:137-158`;`DEFAULT_BLOCK_SIZE = 1024*1024`(`:60`,⚠️ **常量而非配置**) | ⇒ 一份首屏包 10.8 MB ≈ **11 块**,每块是 **1 MiB 混合内容**。⇒ 🔑 **首屏包里的"纯低熵块"在结构上几乎不可能存在**(除非整整 1 MiB 都是低熵) |
| 6 | 块存储 = **纯内存**,调用方**没给** `dir` | `src/web/server.ts:909-911`(`new ContentStore({ maxBytes })`,**无 dir**);`runtime.ts:347-350` 同 | ⇒ 块**不落盘** ⇒ **"低熵块"无法从生产盘上"捞"出来** ⇒ 测熵**只能对"真实内容"离线做** |
| 7 | 快照上限 = `CONTENT_STORE_MAX_BYTES` 64 MiB | `参数表_覆盖网络_20260917.md:147` | 内存预算独立(⛔ 不挤 relay 的 `MEM_PER_HOST_MB`)⇒ D 若引入"合并加密",**是否增加常驻字节**要在这笔账里算 |
### 1.1 🔴 本轮最重要的结构发现(⛔ 尚无任何文档记录 · ⚠️ 待实测确认)
由事实 5 直接推出两条,**方向相反**,共同决定 D 的边界:
1. **对"首屏包"而言,D-1 几乎是免费的** —— 定长 1 MiB 意味着低熵小字段**已经被同块的高熵内容稀释**了。⇒ D-1 在首屏包上**代价≈0、收益≈0**(本来就不暴露)。
2. 🔴 **D 的真实战场不在首屏包,而在「小于 1 MiB 的独立内容」** —— 任何**整体小于 1 MiB 的一份内容**只会切成 **1 个块**;若该内容本身低熵(配置 / 状态 / 清单 / 密钥交换载荷类),它就是一块**纯低熵块** ⇒ 中继可数相等性、持钥者可枚举。
⇒ **推论(决定待测项的形状)**:测熵**必须同时覆盖两个集合**,只测首屏包会得出"没有低熵块"的**假绿**结论。
⚠️ 本条为**源码推导**,⛔ 不是实测 ⇒ 待测项 `M1` 的第一条就是要**证实或推翻它**。
---
## 2. 第一个动作:补测「低熵块的种类数 / 体积 / 占首屏包比例」(**M1**)
### 2.1 为什么必须放第一个(⛔ 不是"顺手做的取证")
- **D 的边界由它决定** —— "什么算低熵、稀释单元取多大、要不要合并"全是它的函数(`调研…§6` 阻碍 4 已点明「**什么算低熵本身无可靠判据**」)。
- **它从未被测过,而且历史上所有"低熵"读数都是合成字节** —— 三条硬证据:
| # | 证据 | 出处 | 说明 |
|---|---|---|---|
| a | perf 用的是 `makeBytes(PACK_BYTES, SEED)` | `_tmp_seq32/p01-perf.mjs:42` | **合成** 11,363,655 B 缓冲,⛔ 不是真首屏包 —— 对 perf(尺寸驱动)无害,对**熵统计致命** |
| b | 低熵定性读数用 `blk-0000…` 8 B 合成块、域 1000 | `_tmp_seq32/p03-lowentropy.txt`;引用见 `参数表_覆盖网络_20260917.md:617-622` | 结论是**定性**的("可枚举"),⛔ 从未给出"真实内容里低熵占多少" |
| c | 块存储纯内存、不落盘 | §1 事实 6 | ⇒ 生产侧**没有任何**可回捞的低熵块样本 |
### 2.2 待测项定义(可判定 · 落 `参数表` §11.3 补记区)
**度量对象(两个集合,⛔ 都用真实内容)**
- **S1 = 真实首屏包**:门户 `GET <portal>/plugins/`(= 既有口径的 `11,363,655 B`)。⚠️ **只读 GET**,⛔ 不重启、⛔ 不写盘。
- **S2 = 真实"独立小内容"集**:覆盖网络**实际分发**的、**非首屏**的内容(⛔ 由执行棒先枚举来源并留痕;若确实不存在 ⇒ **报 SKIP + 说清"不存在"**,⛔ 不许用合成字节填充)。
**方法(本机离线 · 纯函数 · 零生产触碰)**
用**代码常量** `DEFAULT_BLOCK_SIZE`(= 1 MiB)复刻 `chunkify` 的**定长切分**,再对每块统计。⚠️ **必须调用仓库里那份 `chunkify`**(⛔ 不复刻一份算法 —— 复刻=双源,本线明令禁止)。
**指标(每条都可机器断言)**
| 编号 | 指标 | 定义 | 期望落点 |
|---|---|---|---|
| **M1-a** | **种类数 / 重复率** | 唯一块 id 数 ÷ 总块数;并给出**完全重复块**(同 id 出现 ≥ 2 次)的清单 | 数字 |
| **M1-b** | **低熵块数 / 体积** | 逐块经验 Shannon 熵(字节分布,bit/byte),阈值初值 **H ≤ 4.0**(⚠️ **阈值本身是待定项**,须在报告里给出 H 的**直方图**,⛔ 不给单一阈值当结论) | 块数 + 字节数 + 直方图 |
| **M1-c** | **占首屏包比例** | 低熵块字节 ÷ 首屏包字节 | 百分数(**这就是"能不能定量"的那个数**) |
| **M1-d** | 🔑 **反向腿 · 子窗口熵** | 整块高熵 ≠ 块内无低熵字段:按**滑窗**(如 4 KiB)扫整份内容,给出"低熵窗口"的**尺寸分布** | 尺寸分布(**决定 D-1 的稀释单元粒度**) |
🔴 **M1-d 是防假绿的关键腿**:只做整块统计,会因为 §1.1 事实 5 得出"全是高熵块"的**假绿** —— 而那**恰恰漏掉**了低熵字段真实存在的形态(它们是块内的一小段)。本条同时**同时交叉验证** §1.1 的两条推论。
**落点与边界**
- 先可落 `_tmp_seq*/` 一次性脚本取证,**证据齐后固化为只读探针** `scripts/overlay-entropy.cjs`(⛔ **零第三方依赖**,只 `node:crypto` + `node:fs`)。
- 读数进 `参数表_覆盖网络_20260917.md` **§11.3 补记区**(⚠️ 属**验收读数**,⛔ 不进 §10 指纹口径 —— 沿用序㉛ 的既定做法,见该表 `:506`)。
- ⛔ **不动 §10 指纹**(除非同时新增判据键,那时按 §3.3 一并处理)。
---
## 3. 方案 C —— 域分离(per-network keyed hash)
### 3.1 改什么
把**内容寻址的两个哈希**从"裸哈希"改成"**带 per-network 密钥的 keyed hash**":
| 现状 | 目标 | 落点 |
|---|---|---|
| `blockIdOf(bytes) = sha256(bytes)[0:32]` | `blockIdOf(bytes, netKey) = HMAC-SHA256(netKey, bytes)[0:32]` | `chunker.ts:108-110` |
| `contentIdOf(bytes) = sha256(bytes)[0:32]` | `contentIdOf(bytes, netKey) = HMAC-SHA256(netKey, bytes)[0:32]` | `chunker.ts:113-115` |
🔑 **密钥来源**:复用既有组密钥链路从 **network 维度**派生(`crypto.ts:47` 的组密钥文件 + 既有的 network 标识),⛔ **不新增密钥文件、不新增 env**。⚠️ per-network 密钥的**派生写法**(`HMAC(groupKey, "overlay-block-id"‖network)` 之类)属**技术实现** ⇒ 由执行棒自决,⛔ 不上抛。
### 3.2 治什么 / 不治什么(口径必须写死)
- ✅ **治**:跨 network 的 **COF / LRI / 离线枚举**;中继从"同一块 id 同时出现在 A / B 两个 network"推出的**跨租户相关性**。
- 🔴 **不治**:**同一 network 内部**持钥者枚举(`85,878 条/秒` 原样存在)⇒ **C 是低成本加分项,⛔ 不是 §10-3 的解**。
- ✅ **代价≈0 的依据**:**跨 network 本来就不该去重**(不同租户 / 不同覆盖网络)⇒ 去重域的收窄**踩在不值得保留的地方**。
### 3.3 🔴 C 的真实代价:**块 id 口径换代(这是本方案最重的一笔)**
- 块 id 一变 ⇒ **全部既有块 id 失效**(缓存全放弃、去重率归零重算)⇒ **直接触发组密钥单 §9-5 的回头条件**。
- ⇒ ⚠️ **C 不能"悄悄上"**,必须与 **`E1` 判据的基线重置**同时做(见 §5)。
- ⚠️ **换接口形状也要改**:`blockIdOf` 从 1 参变 2 参 ⇒ **所有调用点**(`store.ts` 校验路径、`chunker.ts` 内部、装配层)都要一起改。⇒ **执行前必须先出"调用点清单"**(⛔ 漏一处 = 校验必红且极难定位)。
---
## 4. 方案 D —— 低熵块非确定性
### 4.1 首选 **D-1「稀释域」合并加密**
**做法**:把低熵小字段与**高熵内容**拼在一起再加密 ⇒ 明文域从 `100` 扩到 `100 × 2³²` ⇒ 不可枚举。
**代价**:这批块的**去重失效**(低熵块去重本无价值 ⇒ 代价踩在不值得保留的地方);**块 id 口径不变**(⚠️ 与 C 的方向**相反** —— C 换 id,D-1 只换"这批块加不加密、怎么加密")。
🔴 **D-1 的定义式设计约束(本方案必须写死,否则实现会做错)**:**稀释源必须是"攻击者猜不到"的那个量**。
- 因为本题的攻击面是"**持组密钥者 · 猜明文 + 复算**"(§1 事实 3)⇒ 若稀释源是**攻击者能自己算出来的量**(如"由组密钥确定性派生的每块盐"、或块序号),**稀释对持钥者完全无效**(他能复算出同一份密文)。
- ⇒ **稀释源必须包含真随机且不出现在明文可见面的字节**;⚠️ 该随机量的**存放位置**(密文内 / 密钥封装体里 / 块外层)是**技术实现**,执行棒自决 —— 但**判据必须能区分它是不是真随机**(见 §5 判据面)。
⚠️ **与 §1.1 的联动**:若 `M1` 证实"首屏包内不存在纯低熵块",**D-1 在首屏包上的作用面 ≈ 0** ⇒ D 的实际工作范围将收窄到 **S2(独立小内容)**,⛔ 那时**不许**为了"让 D 有用"而把块切小(切小 = 块数暴涨 = 控制面开销与回源字节都恶化 ⇒ **净变差,触 R11**)。
### 4.2 备选 **D-2 每块随机密钥 + 密钥封装**
**做法**:彻底非确定性 —— 每块一把随机密钥,密钥随内容封装(用组密钥包裹)。
**代价**:该块**不去重**;**新增密钥封装配送**这一层(=新的出错面)。
**定位**:**仅在 D-1 的稀释源无法满足 §4.1 约束时启用** —— 即"低熵内容确实是**独立成块**且**没有任何高熵内容可与它同批**"时。
---
## 5. 判据面 / 影响面 / 验收
### 5.1 判据面(⚠️ 这是"改完凭什么算改对")
| 项 | 要求 |
|---|---|
| **新增 OBS 行** | 现有 OBS 编号已到 **`OBS-28`**(`参数表` 第 264-270 行一带)⇒ 本单新增自 **`OBS-29`** 起,⛔ **不得跳号、不得复用** |
| **新增阈值键** | 若 D 需断言"稀释源是真随机"⇒ 需新增键(形如 `CONTENT_ENTROPY_*` / `CONTENT_DILUTE_*`)。⚠️ **键名与取值由执行棒定**,⛔ 不上抛 |
| 🔴 **必须有的负腿** | 「**稀释源被换成确定性派生量 ⇒ 判据必红**」—— ⛔ 否则"上了个无效的稀释"会**全绿**(本线老病根:装了但没生效 = 静默放行) |
> 🆕 **序㊻ 修订(2026-09-18 22:4x · 执行棒回填 · ⛔ 不改本方案的技术口径,只改判据面)**
>
> **① D 本轮不实现**(依据 = 序㊺ 实测:真实首屏包 23 块**0 个低熵块**、60 份真实独立小内容**0 份低熵**
> ⇒ `D-1` **无对象可稀释**、`D-2` **前提不成立**)。⇒ 上表那条**负腿失去对象**(无稀释源可换)。
> **② `OBS-29` 已重裁成 C 的判据**(编号仍在 **`OBS-29`**,⛔ 不跳号不复用):
> **正腿** = 同字节 + **不同 network** ⇒ 块 id **不同**(**且两侧都 ≠ 裸哈希**);**正腿** = 同 network + 同字节 ⇒ 块 id **相同**(⛔ 只测前者会漏"去了重");
> **装配面腿** = `store` 两处复算 + 重组位全过(⇒ 调用点无漏改);
> 🔴 **负腿(真跑、具名)** = **去掉 per-network 维度**(域密钥取同值 / 取空)⇒ 谓词**必红**(`flat-key-collapses` / `empty-key-falls-back-to-bare-hash`)。
> **③ `E1` 基线**按 §5 的要求**重取**(C 换了 id 口径 ⇒ 旧读数作废);⚠️ `E1` 的**定义不重估**。
> **④ §3.3 的口径不变**:块 id 换代 ⇒ 缓存全清、去重率归零重算 —— ⛔ 回滚也**不是无损**。
| **`E1` 基线重置** | C 换 id 口径 ⇒ `CONTENT_TIER_HITS_MIN` 等**命中类阈值的前提变了**;⚠️ `E1` 本身(回源字节 ≈ 1 份 × 组数)**不重估**,但**基线读数必须重取**(⛔ 不许拿旧读数当对照组) |
| **`OBS-17` 口径一致腿** | 该行断言 `content.blockSize == CONTENT_BLOCK_SIZE` 且 `content.storeMaxBytes == CONTENT_STORE_MAX_BYTES`(`参数表:418`)⇒ 若 D 引入额外常驻字节,**需在此处补列,⛔ 不许绕过** |
### 5.2 影响面(**执行前必须先出清单**)
| 面 | 改动量级 | 是否触"超过 10 文件先出清单" |
|---|---|---|
| `src/net/relay/content/chunker.ts` | 小(两个函数 + 调用点) | 否 |
| `chunker.ts` / `store.ts` / 装配层的 **`blockIdOf` 调用点** | **待清点**(⚠️ 数量未知 ⇒ 清点后若 > 10 ⇒ **先出清单再动**) | ⚠️ **可能触** |
| `src/net/relay/content/crypto.ts` | 中(D-1/D-2 的加密路径) | 否 |
| `scripts/overlay-probe.cjs` | 小(新增 OBS) | 否 |
| `参数表_覆盖网络_20260917.md` | 小(§11.3 + §6 判据行)|⚠️ **§10 指纹会变**(新增键才变) | 否 |
| 生产 env | 🔴 **预期为 0 个新增** | — |
### 5.3 验收(三段式,沿用本线既有做法)
1. **离线**:`M1` 读数落盘 + 熵直方图 + 探针自检(夹具模式封闭 ⇒ **⛔ 不 ssh**)。
2. **本机**:`npm test`(Node 22)全绿 + `OBS-29` 正腿绿、**负腿红**。
3. **真机**:`/status` 的 `content` 块 —— ⚠️ **只读**,⛔ 不重启在线服务;⚠️ `http2 on` 环境 curl 必须 `--http1.1`。
### 5.4 回滚
- **代码**:C / D 必须是**可独立回退**的两处开关(C 的密钥可"传空 ⇒ 回落裸哈希";D 可"不启用稀释 ⇒ 回落确定性")⇒ ⛔ 不许做成"上了就下不来"。
- **口径**:C 回退后**块 id 会再变一次**(⇒ 缓存再清一次)—— ⚠️ 这条要写进回滚说明,⛔ 不许默认"回滚 = 无损"。
- **判据**:`参数表` 回退到本文件所记的指纹 `d408d640246a980f702fe7b0a2895219`(⚠️ 以现算为准)。
---
## 6. 边界(本方案**不做**的事)
- ⛔ **不立项 B**(OPRF / SA-MLE)—— 用户已拍板降级为可选加强;其接口位(Manager 做 KS)保留为**将来可插**,⛔ 本轮不写任何代码。
- ⛔ **不做"按熵分层加盐"那条老路**(它必须同时动块 id 与去重率 ⇒ 比 C+D 更重,`调研…§5.5` 已量化)。
- ⛔ **不改 `DEFAULT_BLOCK_SIZE`**(切小 = 净变差 ⇒ 触 R11)。
- ⛔ **不引第三方依赖**、⛔ **不动 `package.json`**、⛔ **不改生产实例 / ⛔ 不重启在线服务**(本轮规划棒另加:⛔ 零代码、⛔ 零服务器触碰)。
- ⛔ 合规 / 数据主权**不进本方案**(口径:方案只做技术实现)。
---
## 7. 参考
- `调研_MLE加密去重最优方案_20260918.md` —— §3 方案族全景表 / §5.3 域分离 / §5.4 三选项 / §5.5 复杂度与收益 / §6 六条阻碍
- `.workbuddy/memory/2026-09-18.md` —— 21:1x(三不兼得)/21:5x(文献印证 + 域分离发现)/21:2x(C/D/B 对比与"持钥者 ≠ 能解密")
- `参数表_覆盖网络_20260917.md` —— `:146`(`CONTENT_BLOCK_SIZE`)/`:147`(`CONTENT_STORE_MAX_BYTES`)/`:418`(`OBS-17`)/`:506`(§11.3 补记区用法)/`:617-622`(§10-3 低熵现状定性)
- 源码 —— `chunker.ts:60/64/108-115/137-158`|`crypto.ts:47/405-413`|`store.ts:1-40`(模块定位与"不做加解密"边界)|`server.ts:909-911`|`runtime.ts:347-350`