序㊾(本棒):
- 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 登记
620 lines
69 KiB
Markdown
620 lines
69 KiB
Markdown
# 交接单 · 低熵块治理(**测熵先行** + 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 补记
|
||
|
||
|