Files
dsh_ai1net_server/交接单/交接单_组密钥加密_20260918.md
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

725 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.
# 交接单 · 组密钥加密(块级共享与机密性兼得)
- **序号**:覆盖网络线 **序 ㉘ · 规划棒 → 单 B**(执行棒待跑)
- **立单**:2026-09-18 00:5x
- **用户拍板**:**「选 C 组密钥加密」**(2026-09-17 23:2x,原话;⚠️ 该档位在 23:3x 的更正轮里**明确未变**)
- **上游依据**:入口 §4 用户口径「**按照连接稳定高效的方式 数据安全可加密传输**」|`交接单_骨干稳定选路与加密_20260917.md`(§4 加密属真取舍、已登记待评估)|`交接单_内容分发块级寻址_20260917.md`(序 ㉔ 块级内容寻址已落地)
- **归档号**:**130**(占号动作已执行,见 §11;⛔ 63 是空号、不补占)
- **状态**:**待执行**
> **本单与 `交接单/README.md §二` 8 段模板的对应**:§1 目标|§2 只读前置|§3 范围|§4 步骤|§5 验收(模板 6)|§6 回滚(模板 7)|**§7 决策点 + 影响面**|**§8 回报格式(留空给执行棒)**|§9 回头条件|§10 未验证项|§11 指纹与状态。
---
## §0 摘要
### 0.1 摘要(一句话)
**给内容面加一层"组密钥"对称加密**:同一个「内容组」共享一把对称密钥 ⇒ **组内密文逐字节一致** ⇒ 序 ㉔ 的「按哈希共享块」**仍然成立**(去重与同组 peer 命中不退化),同时**中继进程读不到明文载荷**。⇒ 相对「按接收者 E2E」(牺牲性能)与「保持现状」(牺牲对中继的机密性),本方案**两者兼得**。
### 0.2 🔴 本棒一条**必须纠正的前人前提**(取证得出,⛔ 请以本棒为准)
> 「块 id 用**明文哈希** ⇒ **跨组也能共享同一块**」 —— 这句在上游讨论里被当作明文哈希的**主要优点**。**本棒取证判定:在本方案(组密钥加密)下它不成立。**
理由(两条,均为可读源码事实):
1. **序 ㉔ 的跨组共享今天本就是被显式拒绝的** —— `src/net/relay/content/peer.ts:64-72` 的 `sameGroup()` 是"跨组"的**唯一**判据处,配套计数器 `crossGroupDenied`;验收 `E5` 的口径就是"**跨组 0 穿透且 `crossGroupDenied ≥ 1`**"(参数表 §6 `OBS-17`)。⇒ 明文哈希**拿不到**一个今天不存在的收益。
2. 🔑 **加密之后,"共享密文"这件事本身失去意义** —— 组密钥是**组作用域**的 ⇒ 组 B 即便取到组 A 的密文块,**没有组 A 的密钥就解不开**(强行用组 B 密钥解 ⇒ 校验失败)。⇒ 所谓"跨组共享同一块"只共享了**一堆解不开的字节**。
⇒ **结论**:明文哈希的"跨组共享"优点在本方案下**净收益 ≈ 0**,而它的缺点(中继可对已知明文做**存在性确认**)仍在 ⇒ **本单推荐"密钥相关/密文相关哈希"**(见 §7.3,⛔ 该处是真取舍,已给逐项优缺点与推荐理由)。
---
## §1 目标
内容面(`src/net/relay/content/**` 及其装配点)新增**组密钥加密层**,满足三条:
1. **组内密文一致**:同一组、同一明文块,任意节点加密后**密文逐字节相同**(`md5` 相等);
2. **共享不退化**:同组内第二次取块命中 `local` 或 `peer` 档(⛔ 不许退化成"次次回源");
3. **中继看不到明文载荷**:relay 进程持有的任何持久/日志/`/status` 里**不含明文块字节**,且**不持有可解密的密钥**。
**可判定的"做完了没有"**:§5 的 **F1–F6** 全绿 —— 特别是 **F1(同组密文一致)+ F2(命中 `local`/`peer`)+ F3(跨组不可解)+ F4(明文不出现,且**反向夹具证明 F4 有效**)+ F5(`E1` 主判据不退化)**。
---
## §2 只读前置(执行前必须先核实,**逐条给命令与期望输出**)
> 全部只读;⛔ 不改任何文件、不改任何生产值。**任一条与期望不符 ⇒ 停下报告。**
| # | 核实内容 | 命令 | 期望输出 |
|---|---|---|---|
| 1 | **组定义现状**(能否复用,⛔ 不新造) | `cd D:/github/dsh_shenxian && grep -n -A3 "export function groupKeyOf" src/net/relay/content/peer.ts; grep -n "CONTENT_GROUP\|DSHS_CONTENT_GROUP" src/web/server.ts src/net/relay/main.ts` | 组键 = **`<network>\|<group>`**(分隔符 `\|`,`:61`)|平台侧缺省 `'local'`(`web/server.ts`)、relay 侧 `'relay'`(`main.ts`)—— **刻意分开**(防"两个进程误以为同组")。⇒ **"组" = (网, 组) 二元组,与序 ㉔ 同构 ⇒ 直接复用。** |
| 2 | **块 id 算法现状**(本单取舍的起点) | `grep -n -A3 "export function blockIdOf" src/net/relay/content/chunker.ts` | `sha256(字节).slice(0, 32)` = **对明文**哈希(⚠️ 若已变成密钥相关 ⇒ **本单已被别人做过** ⇒ 停下报告)。 |
| 3 | **现有加密面(本线尚无内容层加密)** | `grep -rniE "encrypt\|cipher\|gcm\|aes\|chacha" src/net/relay/ --include=*.ts \| wc -l` | **`0`**(本棒实测)。⇒ 加密层是**纯新增**,⛔ 不改既有行为。 |
| 4 | **密钥模型(四层,⛔ 不许引第二套)** | `sed -n '14,30p' src/net/relay/identity.ts; grep -n "asymmetricKeyType !== 'ed25519'" src/net/relay/identity.ts; ls -l /etc/dshs/overlay-signer-key.pem`(47) | 四层 = **根(离线,只授权/撤销签名者)→ 签名者(在线,47)→ 节点密钥(每机一把 `0600`)→ 会话(内存)**|🔴 **节点密钥 = `ed25519`**(`identity.ts:197/318`,纯签名、**不能做公钥加密**)⇒ **这是 §7.2 分发方案的决定性约束**|签名者文件在 47 上存在。 |
| 5 | **既有对称封装(实现底座,⛔ 不新造算法栈)** | `sed -n '1,10p' src/crypto.ts` | **AES-256-GCM** + `deriveKey(secret) = sha256(secret)`(用于用户凭据 at-rest)。⇒ 本单的算法栈**照此复用**(同为 AES-256-GCM)。 |
| 6 | **中继是否能看到块 id 集合**(决定 §7.3 取舍有无现实意义) | `grep -n "PEER_COUNTER_KEYS\|declarations\|withdrawn" src/net/relay/content/peer.ts; sed -n '200,215p' src/net/relay/main.ts` | 计数键含 **`declarations` / `withdrawn`** ⇒ 声明走 relay 消息 ⇒ 🔴 **relay 进程持有 `ContentPeerGroup` 注册表,能看到"哪些块 id 存在"。** ⇒ §7.3 的取舍**有现实意义**(不是理论问题)。 |
| 7 | **基线三件套 + 参数表指纹** | `cd D:/github/dsh_shenxian && npm.cmd test 2>&1 \| tail -5`;`node scripts/overlay-probe.cjs --table`;`sed '/^## §10 指纹/,$d' "E:/ProgramData/AI技能/aliyun-dsh-server/参数表_覆盖网络_20260917.md" \| md5sum` | `npm test` = **201 pass / 200 / 0 fail / 1 skip**|`overlay-probe --table` = **19 PASS / 1 SKIP / 1 FAIL**(1 FAIL = `OBS-21`,**线内在册缺口、⛔ 非本单引入**)|参数表指纹 = **`7fc5889341b99fb26bd05cad0313960b`**(本棒 00:4x 现核)。⛔ 判据一律用**现核值**,本单写的是快照。 |
| 8 | **`OBS` 编号现核**(防撞号) | `grep -oE 'OBS-[0-9]+' scripts/overlay-probe.cjs \| sort -u \| tail -1` | 本棒实测 = **`OBS-21`** ⇒ 本单预留 **`OBS-23`**(`OBS-22` 已分配给**单 A**);⚠️ 若执行时已被占用 ⇒ **顺延**(序 ㉖ 踩过编号被前序占用)。 |
---
## §3 范围
### 3.1 在册文件集
| 面 | 文件 | 说明 |
|---|---|---|
| **新增** | `src/net/relay/content/crypto.ts` | 组密钥装载(`0600` 文件)+ 加解密 + 组内确定性(收敛)加密 + epoch 管理 |
| 改动 | `src/net/relay/content/chunker.ts` | 块 id 口径(⛔ **仅在 §7.3 选定 β 时需要**;选 α 则**一行不改**) |
| 改动 | `src/net/relay/content/store.ts` | `put`/`get` 接入密文口径 + 新增 `decryptRejected` 计数 |
| 改动 | `src/net/relay/content/source.ts` | 五档链路的取回结果统一解密 + 失败留痕 |
| 改动 | `src/net/relay/content/peer.ts` | 同组判定与 epoch 一致性(⛔ **仍只认 `sameGroup`,不放松跨组**) |
| 改动 | `src/net/relay/content/runtime.ts` | 装配组密钥(缺省不启用 ⇒ 行为不变) |
| 改动 | `src/net/relay/main.ts` | relay 侧装配(第 5 条 `contentRuntime`,`:208`) |
| 改动 | `src/web/server.ts` | 平台侧装配(`contentSource`/`contentPeers` 一带) |
| 改动 | `scripts/overlay-probe.cjs` | 新增 `OBS-23` |
| 改动 | `test/overlay-content.test.mjs` | 新增 F 组用例(⛔ 不改 `package.json` test 列表结构) |
| 改动 | `参数表_覆盖网络_20260917.md` | §3.6 新增加密键 + §6 新增 `OBS-23` |
| 改动 | `scripts/overlay-keyring.cjs` | 扩展"组密钥"签发/验签子命令(复用既有仪式 CLI) |
### 3.2 ⛔ 不动什么(防顺手扩大)
- ⛔ **不引入第二套密钥体系** —— 不新根、不新签名链;组密钥的**授权方仍是既有签名者**(见 §7.2)。
- ⛔ 不动 `wire.ts` 的帧格式与既有消息号(新增消息须另立单);⛔ 不动 `keys.ts` / `identity.ts` 的**既有**语义(只**扩展**)。
- ⛔ 不动 `RELAY_FAILOVER_*` / `HB_SEC` / burst / `switcher.ts` 的冷却语义。
- ⛔ **不放松跨组** —— `crossGroupDenied` 必须保持"跨组 0 穿透"(`E5` 不得退化)。
- ⛔ 不改加密**传输层**(TLS 由 nginx 终结,已是现状)—— 本单只做**内容载荷层**。
- ⛔ 不新增公网监听口、不改 `nft` / `nginx`(**R5**)。
- ⛔ 不改任何**生产值**(**D1**);⛔ 不 commit / 不 push(**R7**)。
- ⛔ **不许顺手做**:房间层 / 打洞实现(本线明令不做)。
---
## §4 步骤(每步自带一次可执行的验证)
> 三条硬门贯穿全程:**D1**|**R5**|**R7**。
### S0 · 开工对账
- 跑 §2 全部 8 条并**原样留档**。不符 ⇒ 停下报告。
- 抢锁:`bash "D:/github/dsh_shenxian/dsh-server-docs/scripts/handoff-guard.sh" --claim-exec "覆盖网络线-序28单B执行棒"`;**抢不到 ⇒ 只报告并停**(**R9**)。
- **验证**:guard 返回「✓ 已持全局执行锁」。
### S1 · 组密钥的**定义与装载**(复用四层模型,⛔ 零新信任根)
- **密钥形状**:`groupKey = 32 字节随机对称密钥`(与 `src/crypto.ts` 的 AES-256-GCM 口径一致);随密钥记录 **`epoch`**(单调递增整数)。
- **装载**:`src/net/relay/content/crypto.ts` 从一个 `0600` 文件读 `{ group, epoch, key(base64) }`;**文件缺失 / 权限不对 / 组名不匹配 ⇒ 一律"不启用加密"并记一行判别器日志**(⛔ 不静默降级成"以为加密了")。
- **授权**:由**既有签名者**(47,`/etc/dshs/overlay-signer-key.pem`)用**既有签名载荷**签发 `(group, epoch, keyId)` **三元组**(**⛔ 载荷内不含密钥本体**);节点用**既有验签链**校验 ⇒ 认下当前 epoch。
- **验证**:`node scripts/overlay-keyring.cjs <子命令>` 生成的凭据能被**既有** `directory.ts`/`identity.ts` 的验签路径接受(rc=0);篡改 `epoch` ⇒ **验签失败**(失败关闭)。
### S2 · 组内确定性(收敛)加密 —— **本单的技术核心**
- 目标:**同组 + 同明文 ⇒ 同密文**(否则"按哈希共享块"直接失效)。
- 做法(三行即可说清):`k_block = HKDF/HMAC(组密钥, 明文块)` 派生**块级密钥**;`IV = HMAC(组密钥, 明文块) 的前 12 字节`(**确定性、不留随机数**);`密文 = AES-256-GCM(k_block, IV, 明文)`;**认证标签**一并存(篡改即读侧丢弃)。
- ⚠️ **必须同时落 AAD**:把 `(group, epoch)` 绑进 AAD ⇒ 跨 epoch / 跨组的密文**在认证阶段就被拒**(⛔ 不靠"解出来是乱码"来判)。
- **验证(先红后绿)**:对同一明文加密 **2 次** ⇒ 密文 `md5` **相等**;换 `epoch` ⇒ 密文**不等**(且旧 epoch 密文用新密钥解 ⇒ **认证失败**)。
### S3 · 块 id 口径(⛔ §7.3 选定的那一项)
- **推荐项(β':密文哈希)**:`blockId = sha256(密文).slice(0,32)` —— 因为 S2 已保证组内密文一致 ⇒ 组内 id **稳定**、组外 id **不可预测**。
- **验证(先红后绿)**:同组两次独立"切块 + 加密" ⇒ 块 id 序列**逐字一致**;换组密钥 ⇒ 全部 id **改变**(这是"轮换 ⇒ 全量回源"的**直接证据**,必须留档,不得粉饰)。
### S4 · `store` / `source` / `peer` 接入
- `store.put/id/get` 全程只处理**密文**(读回后 S2 解密 + 认证);`get` 认证失败 ⇒ **丢弃 + `decryptRejected += 1`**(⛔ 不静默返空)。
- `source.ts` 五档链路的**解密位置统一**(⛔ 不许"local 档不解密、peer 档解密"这种双口径)。
- `peer.ts`:**仍只认 `sameGroup`**;新增 **epoch 一致性**:声明 `(group, epoch)`,epoch 不一致**不视为同组可用**(⇒ 轮换过渡期由 S5 处理)。
- **验证**:`test/overlay-content.test.mjs` 原 27 例**不退化**;新增 F 组用例(§5)。
### S5 · 轮换与过渡态
- **新成员获取**:见 §7.2(**密钥本体不经 relay**)。
- **成员退出 / 例行轮换**:签名者签新 `epoch` ⇒ 节点加载**新密钥**(**旧 epoch 保留为"仅解不写"**)⇒ 过渡窗口内新旧密文都能解;**写入一律用新 epoch**。
- **过渡窗口必须上界**:参数化(建议 `CONTENT_EPOCH_GRACE_MS`,⚠️ 值格 ⛔ 必须纯数字,不得夹注 —— 夹注会被读成 `NaN` ⇒ 假红)。
- **验证**:新旧 epoch 混存时,两种密文都能解;超窗口后旧 epoch 密文 ⇒ `decryptRejected += 1` 且**点名**。
### S6 · 判据(`OBS-23` + 夹具 + **先红后绿**)
- 夹具四段(照序 ㉔ `--content-fixture` 的做法):**OFF**(无加密键)/**ON**(同组)/**CROSS**(跨组)/**ROT**(双 epoch)。
- `OBS-23` 三判据(见 §5):① 加解密计数键齐全(⛔ 缺一即 FAIL)② 同组密文一致 + 命中 `local`/`peer` ③ 明文不出现(对已知明文标记做字节级断言)。
- **真机先红**:新 `OBS-23` 对**旧生产** ⇒ **FAIL**(并打印可区分证据)。
- **验证**:四段夹具的判定与预期**逐段一致**,且 OFF/CROSS/ROT 三段的 FAIL/PASS **互不混淆**。
### S7 · 部署 + 零回归 + 收口
- `npm run build` ⇒ 铺到**四处**(47 `/opt/dshs/lib` + `/opt/dsh-relay/lib`、106 `/opt/dshs-cluster/lib` + `/opt/dsh-relay/lib`)—— 🔴 **relay 类产物必须两边都铺**(序 ㉖ 踩过"`dshs` 侧落后一个版本")。
- 部署前建回滚点 `/opt/dsh/backups/seq28-<ts>/`(旧 md5 记录在 §8)。
- ⚠️ **密钥文件与 env 的铺发**属运维动作:落地前先确认**密钥文件权限 = `0600`、属主正确**(⚠️ 本线史上有"属主被改 ⇒ 实例全崩"的先例)。
- 重启 `dshs-relay`(两台)+ `dshs`(47)+ `dshs-worker`(106)(**R8**:动手前一句话说明)。
- 零回归:`npm.cmd test`|`--scene all --table` = **12 PASS / 0 SKIP / 0 FAIL**|`--table` = 基线 +1 行(`OBS-21` 仍 FAIL = 线内在册缺口)。
- 回填 §8;推进 `接续入口_覆盖网络线_20260916.md` §0 + §2;写工作区日志(append-only)。
---
## §5 验收判据(**可机器断言、可被第三方复现**)
| # | 判据 | 命令 / 口径 | 期望 | 为什么必须有它 |
|---|---|---|---|---|
| **F1** | **同组密文一致**(本单技术核心) | 夹具 ON:同一明文块独立加密 **2 次** ⇒ `md5` 比对 | **相等** | 不等 ⇒ "按哈希共享块"直接失效(序 ㉔ 的 `E1` 崩) |
| **F2** | **共享不退化** | 夹具 ON:第一次取块后第二次取块 ⇒ 两次 `/status` 的 `content.store.hits` / `content.peer.peerHits` **增量 ≥ 1** | 命中 `local` 或 `peer` | 只判 F1 不够 —— 加密对了但"次次回源"会全绿(**这正是序 ㉔ `OBS-17` 判据③ 存在的理由**) |
| **F3** | **跨组不可解** | 夹具 CROSS:组 B 用自己密钥解组 A 的密文 | **认证失败** + `decryptRejected` **+1** + `crossGroupDenied` **不减少** | 保证"加密 ≠ 放松隔离";`E5`(跨组 0 穿透)不得退化 |
| **F4** | 🔴 **明文不出现**(+**反向证明 F4 有效**) | ① 用**唯一标记串**写一个块 ⇒ 在 **relay 进程可见面**(持久存储 / `journalctl` / `/status`)做字节级搜索 ⇒ **命中 0**;② **反向夹具**:关闭加密(OFF 段)⇒ 同一搜索 **必须命中 ≥ 1** | ① `0` ② **`≥ 1`** | 🔴 **只判 ① 是假绿风险** —— "没测到"和"真没有"必须可分(本线两处静默失效都栽在这里)。⇒ **② 是 ① 的有效性证明,不可省** |
| **F5** | **`E1` 主判据不退化** | 序 ㉔ 原口径:`N=4` 同组回源字节数 | **≤ 1 份 × 组数**(序 ㉔ 收口值 = **1.0000×**,⛔ 不许变成 4.00×) | 加密的最大风险就是"把块级共享打回去" |
| **F6** | **失败关闭(⛔ 不静默降级)** | 密钥缺失 / 权限错 / 组名不配 / epoch 超窗口 四种情形各跑一次 | 每种 **都**有**具名**判别器留痕 + 计数 +1;⛔ **不许**出现"以为加密了其实没加" | 本线反复复发的那类病("配错了"伪装成"不通") |
| **F7** | **零回归** | `npm.cmd test`|`--scene all --table`|`--table` | 不退化(§2-7 现核基线 + 探针 +1 行) | — |
### `OBS-23` 的形状(进 `scripts/overlay-probe.cjs`;编号须现核顺延)
- **三判据**:① `content.crypto` 计数键**齐全且皆 `number`**(⛔ 缺一即 FAIL —— 这正是"静默放行"的机器判据)② **F1 + F2** ③ **F4①**(明文不出现)。
- ⛔ **缺行即 FAIL**,并把 `journalctl` 的**原文字节数**一并打出(`0 字节` = 读取失败/`>0` = 真没有该行,**二者必须可分**)。
- ⚠️ **值格必须是纯数字**(参数表规则)—— 加密相关键里凡"模式名"性质的一律**只作信息键**,⛔ 不进数值判据。
---
## §6 回滚
**回滚 = 关掉加密层(缺省即关闭)⇒ 秒级,且不丢数据。**
### 路 A · 配置回滚(推荐,秒级)
- 组密钥文件**移除 / 改名**(或把启用开关置回缺省)⇒ `crypto.ts` 走"不启用"分支 ⇒ 行为**逐字回到**序 ㉔ 现状。
```bash
ssh -p 22 bt-server 'mv /etc/dshs/content-group-key.json /etc/dshs/content-group-key.json.off && systemctl restart dshs-relay dshs'
ssh -p 22 test106 'mv /etc/dshs/content-group-key.json /etc/dshs/content-group-key.json.off && systemctl restart dshs-relay dshs-worker'
```
- **验收**:`overlay-probe --table` 的 `OBS-23` 从 PASS → **SKIP 或 FAIL-并点名"未启用"**;`npm test` 不退化。⚠️ **`OBS-23` 的"未启用"必须是 SKIP 还是 FAIL 要在 S6 就定死**(建议 **SKIP + 留痕**,因为"缺省不启用"是合法状态;⛔ 但**不许**既不是 SKIP 也不是 FAIL 的"静默绿")。
- ⚠️ **已有的密文块会解不开** ⇒ 回滚后这些块按"认证失败"被丢弃(`decryptRejected +1`)⇒ **清一次 `CONTENT_STORE_MAX_BYTES` 的缓存目录**即可(属可逆操作:块能从 origin 重取)。
### 路 B · 产物回滚(秒级)
```bash
ssh -p 22 bt-server 'cp /opt/dsh/backups/seq28-<ts>/*.js /opt/dshs/lib/net/relay/content/ && cp /opt/dsh/backups/seq28-<ts>/relay/*.js /opt/dsh-relay/lib/net/relay/content/ && systemctl restart dshs-relay dshs'
```
- **回滚点**:`/opt/dsh/backups/seq28-<ts>/`(**S7 部署前创建**,旧 md5 记入 §8)。⛔ **没有回滚点不准上线。**
### 🔴 回滚路径硬禁令
⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径** —— 它会**自锁**(实测 **121–123 s 无切换**);判定见 §9-1。
---
## §7 决策点 · 取舍 · 影响面
### 7.1 决策点
- **已定(用户)**:加密档位 = **C 组密钥加密**(2026-09-17 23:2x,23:3x 未变)。
- **已定(本棒,可推翻)**:
- 「组」定义 = **复用序 ㉔ 的 `(network, group)`**(依据 §2-1,`peer.ts:61`),⛔ 不新造。
- 算法栈 = **AES-256-GCM**(依据 §2-5,与既有 `src/crypto.ts` 同源),⛔ 不引 ChaCha 等第二栈。
- 探针编号 = **`OBS-23`**(`OBS-22` 归单 A;执行时现核顺延)。
- **待定 = 空**。⛔ 本单无上抛项。
### 7.2 密钥从哪来(**复用四层模型,⛔ 零新信任根**)
**复用/扩展的对应关系**(照 `identity.ts:14-30` 的四层表逐层落位):
| 四层原本 | 本单如何复用/扩展 |
|---|---|
| **根(离线)** | **不动** —— 它只授权/撤销"签名者",⛔ **不签发组密钥**(守住既有用途边界) |
| **签名者(在线,47)** | **扩展职责**:原"签发节点入网凭据" ⇒ 追加"**签发组密钥凭据 `(group, epoch, keyId)`**"。**同一把钥匙、同一条签名链、同一个验签入口**。 |
| **节点密钥(每机一把)** | **不动**(仍是 `ed25519`,仍只做签名/身份)。🔴 **注意**:`ed25519` **不能做公钥加密**(`identity.ts:197/318` 硬校验)⇒ 见下方"分发" |
| **会话(内存)** | **扩展**:原本只标"隧道传输加密" ⇒ 追加"**内容组加密**"这一档:`组密钥(落盘 0600 + 内存)` |
**分发(本阶段)**:
- ✅ **密钥本体不经网络、不经 relay** —— 由运维按既有 `0600` 惯例落文件(与节点密钥同处 `/etc/dshs/`),链路 = 既有部署通道(`scp` / drop-in)。理由:① 节点密钥是 `ed25519`,**做不了公钥包裹**;② 任何"经 relay 传密钥"的做法都等于**把密钥交给中继**,前功尽弃。
- ✅ 签名者只**广播** `(group, epoch, keyId)` **三元组**(⛔ 不含密钥),节点据此确认"当前 epoch 是几",防"版本错配"。
- ⚠️ **规模化的自动分发 ⇒ 登记为回头条件**(§9-7):届时需给节点密钥**并行加一把 `x25519`**(或做 Ed25519→X25519 派生)才能上"公钥包裹"。⛔ **本单不做**(那是新增密钥类型,属另立单)。
### 7.3 🔴 本单最关键的取舍:块 id 用**明文哈希**还是**密钥相关哈希**
> 两者**竖排成段**,逐项列「优点 / 缺点」。⛔ 不横排、⛔ 不做成表格的列。
**选项 α —— 块 id = 明文哈希(`sha256(明文块)`,⛔ 序 ㉔ 现状一行不改)**
优点:
- 序 ㉔ 的 `chunker.ts` / `OBS-17` / 既有 `E1` 判据**零改动**,改动面最小;
- **轮换组密钥不改变块 id** ⇒ 轮换**不触发全量回源**(这是它唯一真实的、且不小的优点)。
缺点:
- 🔴 **它承诺的"跨组共享同一块"在本方案下不成立**(§0.2 已取证:组外没有组密钥 ⇒ 拿到密文也解不开;且 `E5` 本就要求跨组 0 穿透)⇒ 该"优点"**净收益 ≈ 0**;
- 🔴 **把"哪些块存在 + 内容指纹"暴露给中继**:relay 进程持有 `ContentPeerGroup` 注册表(§2-6 实测)⇒ 中继可用**已知明文**对 id 做**存在性确认**("这个包在这张网里存在吗")⇒ 与用户"数据安全"的口径**直接相悖**;
- 明文 id ↔ 密文存储还要额外维护一张映射 ⇒ **中继侧多一张可被探测的索引表**(更糟)。
**选项 β' —— 块 id = 密钥相关/密文相关哈希(`sha256(密文)`,因 S2 已保证组内密文一致)**
优点:
- 🔴 **组外不可见**:中继只看到**不透明 id**,连"是否存在该块"都判不出来(**这正是"数据安全"要的那一半**);
- **组内完全兼容**:S2 的确定性加密已保证"同组 + 同明文 ⇒ 同密文" ⇒ 组内 id 稳定 ⇒ **按哈希共享块仍然成立**(**完全满足用户选 C 档位的原意**);
- 与 `E5`(跨组 0 穿透)**天然一致** —— 它不损失任何**今天存在**的收益。
缺点:
- 🔴 **轮换组密钥 ⇒ 全部块 id 改变 ⇒ 一组一次全量回源**(正面撞序 ㉔ 主判据 `E1`);
- 签名目录 / 索引必须携带 `epoch`,否则新旧 id 无法共存(⇒ S5 的双 epoch 过渡态**不可省**);
- 组外**永久**失去块级复用(⚠️ 但见 §0.2:这在加密之后本就无意义)。
**我的推荐 = β'(密钥相关/密文相关哈希)**,理由三条:
1. **用户要的是"中继看不到明文载荷"** —— α 虽然也加密了载荷,但**仍把块存在性与内容指纹交给中继**,属"做了一半";
2. **α 的"跨组共享"优点在本方案下已被取证判定为不成立**(§0.2)⇒ α 剩下的**只有缺点** ⇒ 按判据("只有缺点的候选自己拍掉")**不选 α**;
3. β' 是**唯一**同时满足"组内共享成立 + 组外不可见"的选项,且与既有 `E5` 语义**同向**。
⚠️ **β' 的代价必须如实接受并写进参数**:**轮换 = 一次全量回源** ⇒ 轮换频率要有**下限**(参数化)+ 且**优先安排在版本发布窗口**(那时本来就要重灌)⇒ §9-8 回头条件。
### 7.4 影响面与关系
- **影响面**:**内容面(首屏包分发)** —— 只影响 `local`/`peer`/`edge`/`region`/`origin` 五档链路的**字节形态**与**块 id**;⛔ **不影响**实例生命周期、路由、切流、`dsh_hosts`、lease、端口分配(与单 A **完全无交集**,两单可并行但**不共用文件**)。
- **与 presence / room 层**:无交集。
- **与 `E1`**:见 F5;β' 下**轮换窗口**是唯一会让 `E1` 短暂退化的时刻。
---
## §8 回报格式(**留空给执行棒**)
> 逐节回填,每节必须给**命令 + 原始输出(或原文摘录)+ 判定**。⛔ 不许只写结论。
> **本棒身份**:`覆盖网络线-单B执行棒-组密钥加密`(automation `65d2fc24-770e-4f33-93a3-db6e739aded7`)· 一次性、无人值守。
> **时间**:开工 `2026-09-18 06:22`(抢锁成功)→ 收口 `2026-09-18 07:2x`。
> **证据落盘**:全部原始输出在 `E:\ProgramData\AI技能\aliyun-dsh-server\_tmp_seq31\`(下文各节标注文件名)。
### 8.1 开工对账(§2 八条)
**锁**:`bash D:/github/dsh_shenxian/dsh-server-docs/scripts/handoff-guard.sh --claim-exec "覆盖网络线-单B执行棒-组密钥加密"` ⇒ **`✓ 已持全局执行锁`**(06:22,一次抢到,⛔ 无等待重试)。
| # | 核实内容 | 实测(原文摘录) | 判定 |
|---|---|---|---|
| 1 | 组定义现状 | 组键 = **`<network>`\|`<group>`**(`peer.ts#groupKeyOf`);平台侧缺省 `'local'`(`web/server.ts`)、relay 侧 `'relay'`(`main.ts`) | ✅ 与期望一致 ⇒ **组 = (network, group) 二元组,直接复用**(⛔ 未新造) |
| 2 | 块 id 算法现状 | `blockIdOf = sha256(字节).slice(0,32)`,**对明文** | ✅ 开工时为 **α(明文哈希)** ⇒ 本单**未被别人做过**,可继续 |
| 3 | 现有加密面 | `grep -rniE "encrypt\|cipher\|gcm\|aes\|chacha" src/net/relay/ --include=*.ts \| wc -l` = **0** | ✅ 加密层**纯新增** |
| 4 | 密钥模型 | 四层:根(离线)→ 签名者(47 `/etc/dshs/overlay-signer-key.pem`,`0600`)→ 节点密钥(每机 `0600`)→ 会话内存;节点密钥 = **`ed25519`**(`identity.ts:197/318`) | ✅ 与期望一致;🔴 **ed25519 纯签名、不能公钥加密** ⇒ 决定"密钥本体只能走文件/scp" |
| 5 | 既有对称封装 | `src/crypto.ts` = **AES-256-GCM** + `deriveKey = sha256(secret)` | ✅ 本单算法栈照此复用(⛔ 未引第二栈) |
| 6 | 中继能看到块 id 集合 | `peer.ts` 计数键含 `declarations` / `withdrawn`;relay 进程持 `ContentPeerGroup` 注册表 | ✅ ⇒ §7.3 的取舍**有现实意义** |
| 7 | 基线三件套 + 参数表指纹 | `npm.cmd test` ⇒ **201 tests / 200 pass / 0 fail / 1 skip**(与单内基线逐字同);`overlay-failover-drill --scene all` ⇒ **12 PASS / 0 SKIP / 0 FAIL**;`overlay-probe --table` ⇒ ⚠️ 开工那一刻**演练正在跑**(relay 被反复起停)⇒ **18 PASS / 1 SKIP / 3 FAIL**(红 = `OBS-01`/`OBS-04`/`OBS-08`,**演练暂态**);参数表 §10 指纹现核 = **`3b295a8c43aafc4a62c6f1a59ea0b43a`** | ⚠️ 探针开工值**不是**单内快照的 `19P/1S/1F` —— 原因是"序㉙ 已把 `OBS-21` 转绿(22P 稳态)"+"开工时刻撞上演练暂态(3 红)";**收口前的稳态基线 = 22 PASS / 0 SKIP / 0 FAIL**(`S7-probe-1.txt` 实测)⇒ **本棒目标 = 23P/0S/0F** |
| 8 | `OBS` 编号现核 | `grep -oE 'OBS-[0-9]+' scripts/overlay-probe.cjs \| sort -u \| tail -1` = **`OBS-22`** | ✅ **`OBS-23` 未被占用**,按单内预留使用(⛔ 未顺延) |
**判定**:八条**全部相符** ⇒ 未触发"停下报告";三条硬门(D1 / R5 / R7)自开工起全程有效(见 8.11)。
---
### 8.2 组密钥凭据(签名者签发 + 验签通过 + 篡改必失败)
**① 生成组密钥(本机)** —— `node scripts/overlay-keyring.cjs init-group-key --group relay --epoch 1 --out _tmp_seq31/content-group-key.json`(`init-group-key.txt`):
```
✓ 组密钥文件:E:/ProgramData/AI技能/aliyun-dsh-server/_tmp_seq31/content-group-key.json(0600)
✓ group=relay epoch=1 keyId=3560130eb0e5e4aa(⚠️ 密钥本体只在文件里,⛔ stdout 不打印)
```
- 文件六键 = `version / network / group / epoch / key(44 B base64 = 32 B) / previous([])`;`keyId = sha256(key) 前 16 hex`。
- ✅ **stdout 只出 keyId**(⛔ 无密钥本体、⛔ 无 base64 片段)。
**② 签名者签发三元组(47,既有签名者)** —— 命令口径 `sign-group-key --signer-key /etc/dshs/overlay-signer-key.pem --network ops --group relay --epoch 1`。凭据原文(`group-key-credential.json`,**274 B**):
```json
{ "doc": { "version":1, "network":"ops", "group":"relay", "epoch":1,
"keyId":"3560130eb0e5e4aa", "issuedAt":"2026-09-17T22:54:40.439Z" },
"sig": "I2KOPpUMntkR1cNuXpR0zpUIayVeLRuEtPjRxym71Dh6vlkHB9vadDQPfVrMH+6bgX6fC8ipekdSuRjm76VtAg==" }
```
- 🔴 **载荷内不含密钥本体**(逐字核):`凭据含 "key" 字段 = False`、`凭据含密钥片段(key 前16字符) = False` ⇒ 三元组就是三元组。
- **公钥 / keyId**:签名者公钥 = `bad464dfd53048efe7b8531029b3030eda49bc12930703d3a2a8f60e3a7daddf`(47 `/etc/dshs/overlay-signer-key.pem.pub`)⇒ 与**既有**信任表 `/etc/dshs/overlay-signers.json` 的 `signers[0]` **逐字一致**(⛔ 未新根、⛔ 未新信任链);组 `keyId` = `3560130eb0e5e4aa`。
**③ 验签(`S8-8.2.txt`)**:
```
### 正例(原文凭据)
✓ 验签通过:network=ops group=relay epoch=1 keyId=3560130eb0e5e4aa rc=0
### 篡改例(epoch 1→2,sig 不变)
✗ 验签失败:signature-mismatch rc=1
### 交叉核对失败例(epoch 传 9)
✗ ⛔ epoch 不配:凭据 epoch=1 ≠ 传入 9 rc=1
```
- **判定**:✅ 签发 ⇒ 验签通过;✅ **篡改 epoch 必失败(失败关闭)**;✅ 凭据不载荷密钥本体。
---
### 8.3 确定性加密(**同明文两次 ⇒ 密文 md5 相等**;换 epoch ⇒ 不等且旧密文认证失败)
**F1(`S7-f-evidence.txt`)**:
```
明文 md5 = 24d6ed6ce80d7311db80fc63c67bf49a (3776 B)
密文#1 md5 = 3385e9d00ae5c5dbb880ddad90391756 (3804 B)
密文#2 md5 = 3385e9d00ae5c5dbb880ddad90391756 (3804 B)
⇒ 逐字节相同 = true | 长度 = 明文 + 28(iv 12 + tag 16)
⇒ keyId = 5ed7f6b62a053af1(夹具口径)| groupKey = ops|relay epochs=1
```
- 派生口径(实现):`iv = HMAC(sha256,key,"iv"‖plain)[:12]`、`k = HMAC(sha256,key,"k"‖iv)`、`AAD = "<network>|<group>|<epoch>"`(⇒ 解密侧只需 `iv` 即可复原 `k`,故**确定性**成立)。
**F1-b(换 epoch)**:
```
epoch=2/keyB 密文 md5 = 3cab76cda498790fa4ba1329b3f0f53a ⇒ 与 epoch=1 不同 = true
用 epoch=2 解 epoch=1 的密文 ⇒ undefined(认证失败)|该 cipher 计数 decrypts=0 decryptRejected=1
用本 epoch 解自己写的 ⇒ 还原一致 = true |decrypts=1
```
- **判定**:✅ 同组 + 同明文 ⇒ **密文逐字节相同**("按哈希共享块"的前提成立);✅ 换 epoch ⇒ 不等 + 旧密文**在认证阶段被拒**(⛔ 不是"解出乱码")。
---
### 8.4 块 id 口径 + 轮换代价的**原始读数**(⛔ 不得粉饰)
**β′ 口径(采单内推荐项)**:`blockId = sha256(密文).slice(0,32)`(落地为前 16 hex 展示)。原始读数(`S7-f-evidence.txt`):
```
块数 = 4(块大小 1024 B)
α ids(明文哈希) = 1d9e81b64e2fc8a2 3c929675a21f4ac9 9c43515ef7057734 28d85d5bd0b6c6f0
β′ ids(密文哈希) = 9fc1bb4b8d0ec432 a8f5dea758c1d709 66f4e823723fae3c ebe427b90b28ce55
⇒ α 与 β′ 逐位不同 = true
⇒ β′ 两次独立切块一致 = true(组内稳定 ⇒ 按哈希共享块成立)
⇒ 换组后 β′ 全变 = true
⇒ contentId(整份):α = 1cd1b222c4eb46dd7203538baa1497e8 | β′ = 1fd60cf381a79bea8f6ae4a0cefe3152
```
- 🔴 **轮换代价(如实留档,⛔ 不粉饰)**:**换组密钥(或换 epoch)⇒ 该组 100% 块 id 改变 ⇒ 全员一次全量回源**。这是 β′ 的**固有代价**,不是缺陷;本棒**未测得"轮换后首轮回源字节数"**(§10-2 未验证项 ⇒ 记 8.13)。
- ⚠️ 与 `E1` 的关系:**稳态下**不退化(见 8.9 F5);**唯一退化时刻 = 轮换窗口**(单 §7.4 已预告)⇒ 轮换须排进发布窗口(§9-8)。
---
### 8.5 `store` / `source` / `peer` 接入(含 `decryptRejected` 计数)
| 面 | 落法 | 证据 |
|---|---|---|
| `store` | 只改**文档**(`put`/`id`/`get` 全程只处理**密文**字节,⛔ 未改既有语义) | 单测 `F1-d`:缺省(不传 transforms)⇒ 与序㉔ **逐字一致** |
| `source` | **唯一解密点** = `crypto.decodeBlock`;调用位 ① `source.ts` 五档链的**统一返回点** ② `chunker.ts#reassemble` 的夹具/工具路径(⛔ 无"local 档不解密"的双口径) | `F2`:`put → 链上取回两次 ⇒ local 命中递增且解密成功`(`local=6`) |
| `peer` | **仍只认 `sameGroup`**;新增 `epoch` 一致性:epoch 不一致 **不作候选** + **单独计数**(⛔ 不混进 `crossGroupDenied`) | `F3-b` / `F3-c` 两例 ✅;`crossGroupDenied` 语义**未变** |
| 计数 | 十键常量 `CONTENT_CRYPTO_COUNTER_KEYS = encrypts / decrypts / decryptRejected / epochs / epoch / epochExpired / detChecks / detMismatches / plainScans / plainLeaks` | 探针 `OBS-23` 判据① **判别器齐全**(⛔ 缺一即 FAIL) |
- **`decryptRejected` 读数**:夹具 CROSS 段 = **1**(跨组被拒);夹具 ROT-EXPIRED 段 = **1**(超窗口被拒);🔴 **线上稳态 = 0**(`/status.content.crypto` 实测 `decrypts:3, decryptRejected:0`)⇒ 未触发 §9-6 回头条件。
- ✅ `F4-c`:**不启用加密 ⇒ `crypto` 键整体缺席**(⛔ 不是补零)⇒ 探针才能如实记 `SKIP`。
---
### 8.6 夹具六段(OFF / ON / CROSS / ROT + ROT-EXPIRED / SCAN-MISSING)+ **先红后绿**
> 单内写"四段",本棒按 §5 的判据面**扩到六段**(补 `ROT-EXPIRED` = 超窗口、`SCAN-MISSING` = 扫描腿缺失),⛔ 均为夹具、⛔ 不触真机。原文见 `S7-fixtures.txt`。
| 段 | 预期 | 实测原文(尾段) | 判定 |
|---|---|---|---|
| **OFF**(无加密键) | SKIP + 留痕 | `⚠️ FIXTURE SKIP OBS-23 ⛔ 未启用组密钥加密(content.crypto 块缺席)⇒ SKIP + 留痕(缺省不启用是合法状态,⛔ 非静默绿)` | ✅ |
| **ON**(同组) | PASS | `FIXTURE PASS … detChecks=1 detMismatches=0|decrypts=6 decryptRejected=0 local+peer 命中=5|plainScans=1 plainLeaks=0|epoch=1/1 epochExpired=0` | ✅ |
| **CROSS**(跨组) | FAIL | `FIXTURE FAIL … decryptRejected=1(须 0)` | ✅ |
| **ROT**(双 epoch) | PASS | `FIXTURE PASS … epoch=2/2 epochExpired=0|decrypts=4` | ✅ |
| **ROT-EXPIRED** | FAIL | `FIXTURE FAIL … epochExpired=1` | ✅ |
| **SCAN-MISSING** | FAIL | `FIXTURE FAIL … plainScans=0(阈值 ≥ 1)` | ✅ |
**先红后绿(代码层)**:把 F1 的确定性 `iv` 改成随机 `iv` ⇒ **10 例红**(`F1`/`F1-b`/`F1-c`/`F2`/`F4`/`F5` 一族全线点名)⇒ 还原 ⇒ 内容面 **44/44 全绿**(`S7-content-test.txt`);
`npm test` **201 tests / 200 pass / 0 fail / 1 skip** ⇒ **原 27 例零退化**(F 组 16 例为**纯追加**)。
**判定**:✅ 六段**逐段与预期一致**,且 `OFF`(SKIP)/`CROSS`(FAIL)/`ROT`(PASS)**三段互不混淆**。
---
### 8.7 真机先红(新 `OBS-23` 对旧生产)
- **对未启用加密的旧形态**(`S6-probe-oldprod.txt` / `S7-probe-1.txt`):
`SKIP OBS-23 ⛔ 未启用组密钥加密(content.crypto 块缺席)⇒ SKIP + 留痕`
⇒ ⚠️ **本棒与单文有一处必须点明的偏差**:单 §4/S6 写"对旧生产 ⇒ **FAIL**",但 §5 的 `OBS-23` 形状又写"**缺省不启用 = SKIP + 留痕(合法状态)**"。**本棒按 §5 实现 = SKIP**(更保守:既不静默绿、也不误报红)。⇒ "先红"改由**下面那条腿**取得(见 8.13-①)。
- **真机先红(实取,`S7-probe-redmode.txt`)**:把 **47** 的密钥文件权限由 `0600` 改 `0640` ⇒
```
FAIL OBS-23 … 密钥文件 /etc/dshs/content-group-key.json 权限须=600:47=640 106=600
❌ 1 项红:OBS-23
```
rc = **1**;**全量仅 1 红**(⛔ 无波及);**点名可区分**(直接打出"47=640 106=600",⛔ 不是笼统失败)。**原文字节数 = 5,827 B**(⛔ 非 0 ⇒ "没采到"与"确无"可分)。
- **复绿**:还原 `0600` ⇒ `S7-probe-restored.txt` = **23 PASS / 0 SKIP / 0 FAIL**(rc=0)✅
---
### 8.8 部署对账 + 密钥文件权限取证
**① 产物(18 文件 × 四处)**:`npm run build` rc=0 ⇒ 铺 `/opt/dshs/lib`(47)+`/opt/dsh-relay/lib`(47)+`/opt/dshs-cluster/lib`(106)+`/opt/dsh-relay/lib`(106)。
- 本机 md5(`deploy-local-md5.txt`,18 行;另三处**逐文件 diff = 0 不一致**):
| 文件 | md5 | 文件 | md5 |
|---|---|---|---|
| `content/chunker.d.ts` | `7fb3fb442cbaddc62a3ae432a7f9e889` | `content/source.d.ts` | `39063c59528ad5ccc11aa3724118b3e5` |
| `content/chunker.js` | `0edd97f2bb59fc1b4f3ae8996dfed467` | `content/source.js` | `2aaae850c8a5d08dce5a39644190f08f` |
| `content/crypto.d.ts` | `cac34d85ed9b5a368a3c67f7aa742b75` | `content/store.d.ts` | `3f74bbc26c2657cf7434473cf6a2d449` |
| `content/crypto.js` | `4893aa35c0e727c7d54c3f2720e24359` | `content/store.js` | `1a4a95ed509937a2eba559c702ecd8aa` |
| `content/peer.d.ts` | `5ed40ea5e7585fda8b00a38281d345e9` | `identity.d.ts` | `fc3e1a74e0bdc84f8b19b7585808a799` |
| `content/peer.js` | `52e141133a5b7b554ee63b1dd410e01d` | `identity.js` | `77eadb32405fdeb3d0911f00e48b1d81` |
| `content/runtime.d.ts` | `921239ed101615eb6d314902cef56c60` | `index.d.ts` | `6782b7ab87aeb33521428d360824b3a1` |
| `content/runtime.js` | `7fe548c42b71b9e4785f139561fe8925` | `index.js` | `0b33a15f20c40f66796b2291c672e660` |
| `main.js` | `205f65b5252faccd9fc1f4c7d12f2d1f` | `web/server.js` | `88259233ec5fac88a3085b5180fb538b` |
✅ **不一致条数 = 0**(四处 × 18 = 72 个点位全部同值;🔴 relay 类产物**两边都铺**,⛔ 未出现序㉖ 那类"`dshs` 侧落后一版")。
- 四处 `node --check` / `require` 自检通过。
**② 密钥文件(两机同名同权限)**:
```
47 : 600 root:root 148 /etc/dshs/content-group-key.json
106 : 600 root:root 148 /etc/dshs/content-group-key.json
(md5 两机同 = 42d01c359abd59136d707ae89fb6ee7b)
```
**③ 启用开关(relay 侧 drop-in,两机同铺)**:`41-content-crypto.conf`(`2134 B`、纯 LF、md5 `1b2b530a7e3892fe54fd0d294ba694c1`);**只加 1 行 env**:
```
[Service]
Environment="DSHS_CONTENT_GROUP_KEY_FILE=/etc/dshs/content-group-key.json"
```
⛔ 不含任何既有生产键;⚠️ 注释里**刻意不写** `RELAY_FAILOVER_COOLDOWN_MS=0` 字面量(否则 §9-1 的 `grep -c` 判据会脏)。
**④ 重启与时刻**:`daemon-reload` + `restart dshs-relay` ⇒ **47 = 06:55:59 / 106 = 06:56:02**;两机日志均出
`[content-crypto] 组密钥已装载 file=/etc/dshs/content-group-key.json group=relay epoch=1 epochs=1 keyId=3560130eb0e5e4aa perms=0600 ✓`;
`/status.content.crypto` = `{encrypts:3, decrypts:3, decryptRejected:0, epochs:1, epoch:1, epochExpired:0, detChecks:1, detMismatches:0, plainScans:1, plainLeaks:0}`。
**环境收口**(按序㉙ 顺序):`restart dshs-worker` → `restart dshs` → 一次真实访问(`mksess.cjs guest` + `POST /api/dsh/enter`,Host = `alotbuy.com`)⇒ 端点表 `19000/21000` 全 `True`(**孤儿端口未复发**)。
**⑤ 回滚点**:`/opt/dsh/backups/seq28-20260918-065104/`(47 与 106 各 4 处旧产物;旧 md5 **全留档** —— `backup47.txt` / `backup106.txt`;⚠️ 其中 `content/crypto.*` 与 47/106 平台的 `content/runtime.*` 开工为 **`MISSING`** ⇒ 回滚 = 删文件,见 §6 路 B)。
---
### 8.9 判据 F1–F7(逐条原文)
| # | 判据 | 原文(命令 ⇒ 输出摘录) | 判定 |
|---|---|---|---|
| **F1** | 同组密文一致 | `node _tmp_seq31/f_evidence.mjs` ⇒ `密文#1 md5 = 密文#2 md5 = 3385e9d00ae5c5dbb880ddad90391756`、`逐字节相同 = true` | ✅ **通过** |
| **F2** | 共享不退化 | 探针夹具 ON ⇒ `decrypts=6 decryptRejected=0 local+peer 命中=5`;单测 `F2 共享不退化… local 命中递增且解密成功` | ✅ **通过** |
| **F3** | 跨组不可解 | `F3 跨组不可解:组 B 用自己的密钥解组 A 的密文 ⇒ 拒 + decryptRejected +1`;夹具 CROSS ⇒ `decryptRejected=1`;`F3-b 跨组判定不被放松` ✅ | ✅ **通过**(`crossGroupDenied` 语义未变) |
| **F4** | 明文不出现(+反向证明) | ① 密文里扫标记 = **命中 0**(样本 3804 B);② 反向:不加密字节里扫同标记 = **命中 ≥ 1**;正向 `plainScans=1 plainLeaks=0`、反向 `plainScans=1 plainLeaks=1`(⛔ 非 0 ⇒ 这条腿**有牙**) | ✅ **通过**(① ② 都在,⛔ 未踩 §9-4) |
| **F5** | `E1` 主判据不退化 | `F5/E1 口径:加密前后"回源份数"不变(同组 N 次取用 ⇒ 全部 local 命中、零回源)`;断言 `4 轮 × 3 块,全部 local 命中(= E1 的"回源 1 份"口径)` = `source.local = 12`、`F2-b 同一份内容重复 put ⇒ cache 不翻倍` | ✅ **通过**(⛔ 未从 `1.0000×` 退回 `4.00×`) |
| **F6** | 失败关闭(具名) | 七例逐例具名:`no-file` / `bad-perms` / `parse-error` / `group-mismatch` / `bad-key` / `bad-epoch` / `prev-key-bad`(+正例);例:`bad-perms ⇒ 权限 666(必须 0600)`、`group-mismatch ⇒ 文件 group=relay ≠ 运行时 group=local` | ✅ **通过** |
| **F7** | 零回归 | 见 8.10 | ✅ **通过** |
---
### 8.10 零回归三件套
全部带 `--table "E:/ProgramData/AI技能/aliyun-dsh-server/参数表_覆盖网络_20260917.md"`;**收口稳态**(`S7-npmtest.txt` / `S7-drill.txt` / `S7-probe-terminal.txt`):
| 件 | 命令 | 基线(开工现核) | 收口实测 | 判定 |
|---|---|---|---|---|
| 单元测试 | `npm.cmd test`(Node 22) | 201 tests / 200 pass / 0 fail / 1 skip | **201 / 200 / 0 / 1** | ✅ 逐字不退化 |
| 演练 | `node scripts/overlay-failover-drill.cjs --scene all --table "<参数表>"` | 12 PASS / 0 SKIP / 0 FAIL | **12 PASS / 0 SKIP / 0 FAIL** | ✅ |
| 探针 | `node scripts/overlay-probe.cjs --table "<参数表>"` | 稳态 22 PASS / 0 SKIP / 0 FAIL | **23 PASS / 0 SKIP / 0 FAIL(rc=0)** | ✅ **净 +1 行**(新增 = `OBS-23` PASS) |
`OBS-23` 终态原文(**关键尾段**):
```
PASS OBS-23 组密钥加密(GCM 确定性)判别器 齐全|F1 确定性 detChecks=1(阈值 ≥ 1) detMismatches=0(须 0)
|F2 共享 decrypts=3(阈值 ≥ 1) decryptRejected=0(须 0) local+peer 命中=2(阈值 ≥ 1)
|F4① 明文扫描 plainScans=1(阈值 ≥ 1) plainLeaks=0(须 0)|epoch=1/1 epochExpired=0
|密钥文件 /etc/dshs/content-group-key.json 权限须=600:47=600 106=600
```
---
### 8.11 边界自证
| 边界 | 证据 | 判定 |
|---|---|---|
| ⛔ 未改任何生产值(**D1**) | 新 drop-in **只 1 行 env**;⛔ 未动 `RELAY_FAILOVER_*` / `HB_SEC` / burst / `PRESENCE_*` / `DSHS_CONTENT_STORE_*` / `DSHS_OVERLAY_*`;`40-content-probe.conf` 两机 md5 同 = `9e7ccf9a8e59fc88367cea0a07c3d8b8`(**未被动**) | ✅ |
| ⛔ 未新增公网监听口 | 仅新增文件落点 `/etc/dshs/`;`ss -lntp` 面未见新口(relay 仍只绑回环 `20080`) | ✅ |
| ⛔ 未改 `nft` / `nginx` | 全程零 `nft` / `nginx` 命令(**R5**) | ✅ |
| ⛔ 未放松跨组 | `F3-b` / `F3-c` ✅;`crossGroupDenied` 语义未变;`E5` 无退化 | ✅ |
| 🔴 `RELAY_FAILOVER_COOLDOWN_MS=0` 计数 | `grep -rIls "RELAY_FAILOVER_COOLDOWN_MS=0" /etc/systemd/system /etc/dshs \| wc -l` ⇒ **47 = `0` / 106 = `0`** | ✅ **= 0** |
| 🔴 密钥本体 ⛔ 不经网络、不经 relay | 只走 `0600` 落文件 + `scp`;中继侧只广播 `(network, group, epoch, keyId)` 三元组(凭据逐字核:不含 `key`);relay 消息面 ⛔ 零新增密钥字段 | ✅ |
| 🔴 未改 `bwrap` 参数(47 = bubblewrap 0.4.0) | 本单**未触碰**任何 banner/沙箱参数(文件集见 §3.1) | ✅ |
| ⛔ 未 commit / 未 push(**R7**) | `git rev-parse HEAD` = **`04776af4b130014b0f4db10de89b0ae982300afc`**;`git ls-remote origin refs/heads/master` = **同值**(⇒ 未推);`git status --porcelain \| wc -l` = **35** | ✅ |
| ⛔ 未建 04 孪生 / 未补占空号 | 本单**不落** 04 孪生(与本线全部 `交接单_*.md` 一致);`63` 仍为空号 | ✅ |
---
### 8.12 指纹
- **参数表 §10 口径指纹**:`3b295a8c43aafc4a62c6f1a59ea0b43a`(序㉚ 收口)→ **`08c2835a7e9f0faef20f9c1d3e619d55`**(本棒收口;变更 = §3.9 新增 **6 键** `CONTENT_KEY_FILE` / `CONTENT_KEY_FILE_MODE` / `CONTENT_EPOCH_GRACE_MS` / `CONTENT_CRYPTO_DET_MIN` / `CONTENT_CRYPTO_DECRYPT_MIN` / `CONTENT_CRYPTO_SCAN_MIN` + §6 新增 `OBS-23` 一行;口径 = `sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum`)。
- **本单 §8 前前缀**(口径 `sed '/^## §8 回报格式/,$d' <本单> | md5sum`)= **`149596460288ca1a5b7abddc490f7909`** —— ✅ **回填后复跑逐字不变**(§8 是截断点)。
- **全文 md5 / 行数**:⛔ **不内嵌**(自指)⇒ 现算 `md5sum "E:/ProgramData/AI技能/aliyun-dsh-server/交接单_组密钥加密_20260918.md"`;回填前快照 = `0412daf3dc8edf96c2fbe87bdefbdb29`(**356 行**)。
- **归档号**:**130**(本单孪生号;执行为 §11.1 记录,⚠️ 执行棒收官建档案时须**重新现核取号**)。
---
### 8.13 如实留档(**必填,哪怕"没有"**)
① ⚠️ **单文一处内部不一致(本棒按 §5 实现,与 §4/S6 的措辞不同)**:§4/S6 写"新 `OBS-23` 对旧生产 ⇒ **FAIL**",而 §5 的 `OBS-23` 形状写"**缺省不启用 = SKIP + 留痕(合法状态)**"。本棒**取了 SKIP**(理由:既不静默绿、也不把"合法的没启用"误报成红)。⇒ **"真机先红"改由权限腿取得**(`0600→0640` ⇒ `FAIL` 且点名 `47=640 106=600`,rc=1,全量仅 1 红),复绿后 **23P/0S/0F**。🔴 这条**不是缺陷**,是单文两处口径需统一 —— 建议下一棒在单里定稿(倾向保留 §5 的 SKIP 口径 + 把"先红"腿写清为**权限腿**)。
② ⚠️ **签名动作的命令未逐字留痕**:`sign-group-key` 用的是 47 上**既有签名者私钥**,但 47 上**没有** `/opt/dshs/scripts/overlay-keyring.cjs`、`/tmp` 亦无残留 ⇒ 本棒只能自证"**凭据不载荷密钥本体 + 验签通过 + 篡改必失败**",**无法自证"私钥未出机"**。⇒ 建议:把"组密钥签发"固化成 47 上的**正式子命令**(⛔ 不靠临时投放脚本)。**另立小单,本棒不动**。
③ ⚠️ **未测得"轮换后首轮回源字节数"**(§10-2):本棒只证得"换组 ⇒ 100% 块 id 改变"(**定性**),**未造 4 组 × 1 份包的轮换前后 `E1` 读数**(**定量**)⇒ §10-2 **仍未验证**,⛔ 未编数。
④ ⚠️ **未测得加密性能开销**(§10-1:N ≥ 5、p50/p95):⛔ **无数据**,⛔ 不报单次。
⑤ ⚠️ **`OBS-23` 修过一处真缺陷(本棒发现并已修)**:密钥文件权限检查门原为 `cryptoFx === undefined` ⇒ 只给 `--content-fixture`(不给 `--content-crypto-fixture`)时会**偷偷 ssh 到生产机**(与"夹具模式绝不 ssh"的契约面不一致)。已改为门 = `!fixture`。⇒ **属本单范围内的真缺陷修复**,已复测六段夹具全符合预期。
⑥ ⚠️ **两处环境态本就存在、本棒未动**:47 `users/` 权限、106 残留 scope `dsh-100002-844b8c29.scope`(属既有 user `4092b965` 的实例,转由认领机制管理);`OBS-22` 的 47 认领面 `journalctl 原文 0 字节` = **读取失败**(⛔ 不是"确无 summary")。
⑦ ✅ **临时 session 已清干净**:`DELETE FROM sessions WHERE user_agent='poc-curl2'` ⇒ `DELETE 3`,残留 **0**。
⑧ ✅ **`_tmp_seq31/` 为本次临时证据目录**(含夹具、脚本、原始输出),⛔ 未删除(待用户确认后再清);**夹具 `fx-*.json` 仅本机**,⛔ 未铺任何服务器。
---
### 8.14 序 ㉛ 执行棒回报(2026-09-18 07:33–07:5x · §10 未验证项补齐 + 口径定稿 + §8.13-② 结清)
> 🔬 **开工门**:`sed '/^## §8 回报格式/,$d' <本单> | md5sum` = **`149596460288ca1a5b7abddc490f7909`** ⇒ **逐字一致,✅ 通过**(本条写在 §8 之后 ⇒ ⛔ **不影响该不变量**)。
> 📁 **证据目录** = 工作区根 **`_tmp_seq32/`**(脚本 `.mjs/.sh` + 原始输出 `.txt`,逐项对应下方);⚠️ 临时产物,⛔ **未删除**(待确认后再清)。
> ⚠️ **本棒性质 = 补测 + 小额固化** ⇒ ⛔ 未扩范围、⛔ 未改生产值、⛔ 未动代码(`src/**` 零改动)。
#### 8.14.1 主题 ① 加密性能开销(§10-1)—— ✅ **有原始读数**
**口径**:同一台机、同一份首屏包 **11,363,655 B / 11 块**;**先预热 2 次再测**,**n = 11**;每轮 = `切块 + 加密 + put + get + 解密` **全链总墙钟**;两臂跑同一夹具;分位取 **nearest-rank**(⛔ 不插值、⛔ 不报单次)。
| 臂 | p50 | p95 | 吞吐 | 密文固定开销 | `origin` 命中 |
|---|---|---|---|---|---|
| 纯明文 | **22.636 ms** | **23.020 ms** | 502.02 MB/s | — | **0** |
| 组密钥加密 | **42.782 ms** | **43.599 ms** | 265.62 MB/s | **+28 B/块** | **0** |
| **增量 / 比值** | **+20.146 ms / 1.890×** | **+20.579 ms / 1.894×** | — | — | — |
- 🔴 **口径边界(⛔ 不许当端到端承诺)**:单核、页缓存全热、两臂均**零回源** ⇒ 这是"**加密层自身成本**"的参照读数,⛔ **不是线上 `E1` 读数**。
- 原始输出 = **`_tmp_seq32/p01-perf.txt`**(脚本 `p01-perf.mjs`)。
#### 8.14.2 主题 ② 轮换代价量化(§10-2)—— ✅ **结清 §8.13-③**
**夹具**:**4 组 × 4 节点**,包 **5,255,225 B / 6 块**;组成员**按需取块**(`local → peer → origin`,命中即缓存);**另设「不轮换」对照腿**(⛔ 无对照腿 ⇒ 无法断言"回源是轮换引起的")。
| 阶段 | 回源字节 | 相对包 | `local/peer/origin` | 块 id 改变 |
|---|---|---|---|---|
| 冷启动(轮换前) | **21,021,572 B** | **4.0001 × 包 = 1 × 包/组** | `0 / 72 / 24` | — |
| 轮换后**首轮** | **21,021,572 B**(**比值 1.0000**) | 同值 | — | **24 / 24 = 100%** |
| 轮换后**第二轮** | **0** | — | — | — |
| **对照腿**(不轮换)**第 3 轮** | **0** | — | — | — |
| 稳态(两腿) | **0** | — | — | — |
- ⇒ **"轮换 = 一次完整冷启动(1 × 包/组)"已量化**(与 §10-2 预期逐字吻合);**"回源由轮换引起"可断言**(对照腿第 3 轮 = 0)。
- 🔴 **附带代价(本棒实测,原本未在册)**:旧 epoch 密文**变垃圾且不自清** —— `storeBytes` **5,255,393 → 10,510,786**(≈ 翻倍)。⇒ 回头条件 8(轮换须约在发布窗口)**多了一条理由**:不只是回源,还有**存储占用翻倍**直到旧块被逐出。
- 原始输出 = **`_tmp_seq32/p02-rotate.txt`**(脚本 `p02-rotate.mjs`;⚠️ v1 夹具曾因"包预置进 local"造假绿 `postOverPre = NaN`,**已重写**后取上表读数)。
#### 8.14.3 主题 ③ 低熵明文的定性结论(§10-3)—— ✅ **定性成立,⛔ 不写"很安全"**
| 模型 | 读数 | 结论 |
|---|---|---|
| **A:持组密钥者** | 字典枚举 1000 条 = **11.644 ms**(≈ **85,878 条/秒**)、命中 **1**、**恢复正确**(域 **2³²** ⇒ 全量 ≈ **14.7 h**) | **枚举可行** ⇒ 低熵明文对**持钥者**无保护 |
| **B:无钥者 / 中继** | **自造字典命中 0 / 1000**;**持有 1 对明文-密文也预测不出别的**(**0 / 999**) | **枚举不可行** |
| **B 的旁路:相等性泄露** | **10,000 块 / 100 值域 ⇒ 仅 100 种密文**(收敛加密的固有性质) | **重复率 / 相等性可观测** |
| **反向腿(高熵对照)** | **1000 / 1000 无碰撞** | ⛔ 证明"没测到 ≠ 真没有" |
- 🔴 **定稿口径**:**"中继看不到明文" 成立** / **"内容不可推断" 不成立**(收敛加密对低熵必然泄露**相等性**)。⛔ **不写"很安全"**。
- ⚠️ **夹具口径修正(本棒发现)**:初版语料用 LCG 低位取模(`x % 100`)**只出 37 个不同值** ⇒ 已改 **xorshift32 取高位 `(x >>> 8) % 100`** ⇒ **100 / 100**,口径干净后取上表读数。
- 原始输出 = **`_tmp_seq32/p03-lowentropy.txt`**(脚本 `p03-lowentropy.mjs`)。
#### 8.14.4 主题 ④ `CONTENT_EPOCH_GRACE_MS` 三档混存夹具(§10-6)—— ✅ **三档曲线齐**
**夹具**:混存 **20 块**(写 epoch **4** + 历史 **1 / 2 / 3**),`retiredAt` = **T0 / T0+2h / T0+12h**,**时钟注入 `now`**(⛔ 不许真等)。
| 档 | 取值 | 归零时刻 | 窗口内 `epochExpired` | 超窗行为 |
|---|---|---|---|---|
| 小 | **60 s** | **t = +24 h** | **0** | 逐 epoch **点名** + `decryptRejected` **+1** |
| 中(**当前值**) | **24 h** | **t = +48 h** | **0** | 同上 |
| 大 | **7 d** | **t = +8 d** | **0** | 同上 |
- 曲线形状 = **阶梯衰减**(最晚 `retiredAt` + 本值 = 归零);**写 epoch 恒 5 / 5 可解**(⛔ 旧密文过期不影响写侧)。
- ⇒ 定稿见 **§11.5 定稿三**(**当前值 24 h 维持**,⛔ 本棒未改任何值)。
- 原始输出 = **`_tmp_seq32/p04-epoch.txt`**(脚本 `p04-epoch.mjs`)。
#### 8.14.5 主题 ⑤ 单文口径定稿 —— ✅ **写进 §11.5(⛔ §4 / §6 正文零改动)**
- **定稿一**:`OBS-23` 对"缺省不启用"取 **`SKIP + 留痕`**;**§4/S6 那句「对旧生产必 FAIL」作废**;**"真机先红"腿由「权限腿」承载**。
- ⚠️ **§8.13-① 的口径张力至此结清**(⛔ 未打穿 §8 前前缀:修正**一律写在 §8 之后**)。
- **权限腿读数(单 B 实测,本棒复核同值)**:`0600 → 0640` ⇒ `OBS-23` **FAIL** 并**点名** `47=640 106=600`、rc=1、全量仅 **1 红** ⇒ 还原即复绿。**⇒ §10-4(`0600` 运维一致性)由此覆盖**(逐机实读权限 + "权限错必失败关闭"两条腿都有)。
#### 8.14.6 主题 ⑥ 组密钥签发固化(结清 **§8.13-②** 缺口)—— ✅ **已落正式路径**
| 项 | 证据 |
|---|---|
| 落点(**两处**) | **`/opt/dsh-relay/scripts/overlay-keyring.cjs`**(**就地更新**:旧版 **12,915 B**、`grep -c sign-group-key` = **0**)+ **`/opt/dshs/scripts/overlay-keyring.cjs`**(**此前不存在** = §8.13-② 的缺口) |
| 字节一致 | 两处 md5 **均 `f1855eaef30f71fbca00ac0a92865c0e`**(与本机同值) |
| **在机验证** | `sign-group-key` ⇒ **rc = 0**,凭据 **274 B**(`ops/relay epoch=1 keyId=3560130eb0e5e4aa`),**密钥本体出现 0 次**|`verify-group-key` ⇒ **rc = 0**|**篡改 epoch** ⇒ **`signature-mismatch` rc = 1**|`--epoch 9` 交叉核对 ⇒ **rc = 1** |
| 回滚点 | 47 **`/opt/dsh/backups/seq31-20260918-073857/`** |
| ⛔ 未重启 | **纯 CLI**,⛔ 未重启任何 unit(⛔ 未中断任何用户) |
#### 8.14.7 零回归三件套(三者均带 `--table "E:/ProgramData/AI技能/aliyun-dsh-server/参数表_覆盖网络_20260917.md"`)
| 项 | 结果 | 与开工基线 |
|---|---|---|
| `npm.cmd test`(Node 22) | **201 tests / 200 pass / 0 fail / 1 skipped** ⇒ 注入脚本「结论:全部合格 ✅」 | **逐字相同** ✅ |
| `overlay-failover-drill.cjs --scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**(rc = 0) | 同值 ✅ |
| `overlay-probe.cjs --table` | **23 PASS / 0 SKIP / 0 FAIL**(**rc = 0**) | **与开工基线 23P/0S/0F 一致** ✅ |
- 🔬 **本棒三件套的"先红 → 后绿"过程(如实留档)**:环境收口期间(重排实例落点)曾出现 **19P/1S/3F**(`OBS-01 used=1` / `OBS-04 identityOk=1` / `OBS-08 端点表 0 条`)与 **22P/1S/0F**(`OBS-09` SKIP,因实例暂落端口 **21001**)。**未编数、未凑绿** —— 依据序 ㉙ 留档的机制(**孤儿端点条目不自愈,靠"重新登记同一端口"覆盖**),用 `/api/dsh/stop` + `/api/dsh/enter` 把实例落回 **21000** ⇒ **`OBS-09` 转 PASS**(`w-106:39027=401`)、**`OBS-21` 复绿**(两路径 **count = 3**)⇒ **23P/0S/0F**。
- 原始输出 = `_tmp_seq32/base-npmtest.txt` / `final-drill.txt` / `final-probe-1..4.txt`。
#### 8.14.8 恢复形态核实 + 参数表 & 日志
- **参数表**:§3.9 `CONTENT_EPOCH_GRACE_MS` 行**说明列**补序㉛ 三档曲线|§6 `OBS-23` 行末补「口径定稿」|§7 补「序㉛ 复算:仍为 `待测` **0 项**(⛔ 未新增键、⛔ 未改任何值)」|**新增 §11.3 补记**(六条)。
- 🔴 **§10 指纹**:`08c2835a7e9f0faef20f9c1d3e619d55` → **`52e59df229102d5afa8c8bf01a960398`**(**本棒收口现算 + 已同步**;口径 = 先 `sed '/^## §10 指纹/,$d'` 截断再 md5)。⛔ **本轮零键零值变更** ⇒ 探针取阈值不受影响。
- 本单全文 md5:⛔ **不写死** ⇒ 现算 `md5sum "E:/ProgramData/AI技能/aliyun-dsh-server/交接单_组密钥加密_20260918.md"`。
#### 8.14.9 边界自证(本棒)
| 边界 | 证据 | 判定 |
|---|---|---|
| ⛔ 未 commit / 未 push(**R7**) | HEAD 仍 **`04776af`**;⛔ 未执行任何 `git commit` / `git push` | ✅ |
| ⛔ 未改任何生产值(**D1**) | 两机 env / drop-in **零改动**;⛔ 未动 `RELAY_FAILOVER_*` / `HB_SEC` / burst / `PRESENCE_*` / `DSHS_CONTENT_*` | ✅ |
| ⛔ 未新增公网监听口、⛔ 未改 `nft` / `nginx`(**R5**) | 全程零 `nft` / `nginx` 命令;`OBS-11` 多出 **0** | ✅ |
| 🔴 `RELAY_FAILOVER_COOLDOWN_MS=0` | ⛔ **全程未出现在任何夹具 / 脚本 / 演练路径**(本棒新增文件逐字核 = **0**) | ✅ **= 0** |
| ⛔ 未改 `bwrap` 参数(47 = 0.4.0) | 本棒**未触碰**任何沙箱参数 | ✅ |
| 🔴 密钥本体 ⛔ 不经网络 / 不经 relay | 签发**只在 47 本机**(凭据 **274 B**、**不含密钥本体**、⛔ 未 `scp` 任何密钥) | ✅ |
| 🔴 Ed25519→X25519 派生 | ⛔ **本棒未执行**(不在写死主题内 ⇒ 未扩范围) | ✅ 未越线 |
#### 8.14.10 ⚠️ 如实留档(本棒"没有"的部分,**必填**)
① ⚠️ **§10-5(Ed25519→X25519 派生预研)本轮未做**:它**不在**本棒写死的六项主题内(用户明令「⛔ 不许顺手扩大范围」)⇒ **无读数、⛔ 未编数**。它服务的对象是 **§9 回头条件 7**("规模上来了要自动分发组密钥")⇒ **触发条件 = 规模上来**,⛔ 不是当前执行项。
② ⚠️ **§10-4 未单独跑"铺发后逐机 `ls -l` / `stat`"**:读数改由 **`OBS-23` 权限腿**承载(`47=600 106=600` + `0600→0640 ⇒ FAIL 并点名`)⇒ **判据覆盖成立**,但**取证形态不同**(探针读 vs 手工 `stat`)。
③ ⚠️ **性能读数是本机、单核、页缓存全热**(§8.14.1 口径边界)⇒ ⛔ **不得外推为线上 `E1` 承诺**。
④ ⚠️ **轮换夹具的存储翻倍**(§8.14.2)是**发现的新代价**,⛔ **未做"旧块何时被逐出"的量化**(⇒ 记为待观察,⛔ 不冒充已验证)。
⑤ ✅ **临时 session 已清干净**:`DELETE FROM sessions WHERE user_agent='poc-curl2'` ⇒ 「DELETE 行数 = 1」、**残留 = 0**。
⑥ ✅ **`_tmp_seq32/` 为本棒临时证据目录**,⛔ **未删除**(待用户确认后再清)。
---
## §9 回头条件(**一出现必须回头**)
1. 🔴 **⛔ 任何一棒都不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进回滚 / 演练 / 夹具路径**(自锁:实测 **121–123 s 无切换**)。本单验收里 `grep -c` 该值必须 = **0**。
2. **`E5` 退化**(跨组穿透 > 0,或 `crossGroupDenied` 语义被放松)⇒ **立刻回滚 + 报告**。
3. **`F1` 不成立**(同组两次加密密文不同)⇒ 停下报告("按哈希共享块"已失效,继续做只会放大回源)。
4. **`F4` 只绿其①、②未做**(没有反向夹具证明"没测到 ≠ 真没有")⇒ 判据**不算通过**,停下补证。
5. **需要超出 §3.1 在册文件集**(尤其要动 `wire.ts` 帧格式 / `keys.ts` 既有语义)⇒ 停下报告(**R7**)。
6. **`decryptRejected` 在稳态下持续增长**(⇒ 有节点密钥/epoch 不一致)⇒ 停下报告,⛔ 不许"重启一次看看"。
7. **规模上来了要"自动分发组密钥"** ⇒ 停下报告(需先给节点密钥加 `x25519` 或做 Ed25519→X25519 派生 ⇒ **属新增密钥类型,另立单**)。
8. **轮换窗口出现在非发布期**(⇒ 一次全量回源砸在用户活跃时段)⇒ 停下报告,把轮换**改约到发布窗口**。
9. **性能不达标**(加密后 `E1`/吞吐显著变差)⇒ 先量化再决定,⛔ 不许凭感觉回滚。
---
## §10 未验证项(**一个都不许编数**)
| # | 未验证项 | 为什么重要 | 怎么验(口径) |
|---|---|---|---|
| 1 | **加密的性能开销** | 决定"兼得"是否真的兑现 | 同一台机、同一份 10.8 MB 首屏包,测 **N ≥ 5** 次的"切块 + 加密 + put + get + 解密"总墙钟与纯明文对照;⚠️ 记 **p50 / p95**,⛔ 不报单次 |
| 2 | **轮换的实际代价** | β' 的核心代价是"轮换 = 一次全量回源" | 造 4 组 × 1 份包,轮换前后各跑一次 `E1` 口径 ⇒ 记录回源字节(**预期:轮换后首轮 ≈ 1 份 × 组数**,之后回落到 1×) |
| 3 | **收敛加密对低熵内容的安全性** | 收敛加密对**低熵**明文有已知的确认攻击面(可枚举) | 用低熵样本(如小文本块)验证:能否由密文反推明文集合;口径 = 只做**定性**结论,⛔ 不写"很安全" |
| 4 | **`0600` 密钥文件的运维一致性** | 本线有"属主被改 ⇒ 实例全崩"的先例 | 铺发后逐机 `ls -l` + `stat`;并验"权限错时**必须失败关闭**而不是静默明文" |
| 5 | **Ed25519→X25519 派生可行性**(为回头条件 7 预研) | 决定"自动分发"能不能不新增密钥类型 | 纯本地实验:从 ed25519 私钥导出 raw scalar ⇒ 构造 x25519 `KeyObject` ⇒ ECDH 往返成功?(⚠️ 只做实验、⛔ 不进本单代码) |
| 6 | **双 epoch 过渡窗口的取值** | 太长 ⇒ 旧密钥留太久(安全);太短 ⇒ 老节点掉线 | 用 `CONTENT_EPOCH_GRACE_MS` 三档(小/中/大)各跑一次混存夹具,记录"旧密文可解的比例随时间" |
---
## §11 指纹与状态
### 11.1 归档号(**本棒已原子占号**)
- **现核最大号**(⛔ 不写死;取号命令):`ls 04-调整方案/ | grep -oE '^[0-9]+' | sort -n | tail -1` ⇒ **本棒实测 = `128`**。
⚠️ **prompt 写的"归档编号至 112"已过期**(现核 128)⇒ 引用时**必须复跑取号**。
- **占号动作(已执行)**:`mkdir 04-调整方案/.lock-130` ⇒ **成功** ⇒ 本单归档号 = **130**(单 A = 129)。
- 🔴 **63 是空号,⛔ 不补占**(已核:`ls 04-调整方案/ | grep -E '^63'` = 空)。
- ⚠️ **本棒不落 04 孪生**(本线 `交接单_*.md` **均无** 04 孪生;带孪生的是**方案类**文档 —— 逐条比对 16 份单 = 0 命中)⇒ 占号窗口在落单后即释放,执行棒收官建档案时**须重新现核取号**。
### 11.2 指纹
- **§8 前前缀**(口径:`sed '/^## §8 回报格式/,$d' <本单> | md5sum`):见下方"落单后实测"。
- ⚠️ **§8 回填后前缀指纹不变、全文 md5 会变**。
- **落单后实测**(2026-09-18 00:5x,序 ㉘ 规划棒):
- **§8 前前缀** = **`149596460288ca1a5b7abddc490f7909`**(⚠️ 本值**稳定** —— §11 在截断点之后)
- **全文 md5(§11 本段回填前快照)** = **`ef7aad0cf65f4d83221f46e60caa96ed`**
- **全文 md5(§11 本段回填后最终)** = **`见下方 §11.4`**(写完本段后现算)
- **行数** = **344**(快照时刻;回填 §8 后会增长)
### 11.3 状态
- **本单状态**:**✅ 已执行收官**。拆两棒:**单 B 执行棒**(2026-09-18 06:22–07:2x)= 代码 + 部署 + 判据 F1–F7 ⇒ 证据见 **§8.1–§8.13**;**序 ㉛ 执行棒**(2026-09-18 07:33–07:5x)= **§10 未验证项补齐 + 口径定稿 + §8.13-② 缺口结清** ⇒ 证据见 **§8.14** / 定稿见 **§11.5**。
- **本棒(序 ㉘ 规划棒)边界自证**:⛔ **零代码改动** · ⛔ **零服务器改动** · ⛔ 未 commit / 未 push · ⛔ 不改任何生产值 · ⛔ 未实现任何加密(本单**只出方案 + 判据**)。
- **两单关系**:单 A(归档号 129)与单 B(130)**文件集零交集**,可并行;⚠️ 但**同一时刻只挂一个接续棒**(用户 2026-09-18 00:3x 明令)⇒ **执行顺序由下游定序理由决定**(见入口 §2 的定序段)。
### 11.4 全文 md5 的口径(⛔ **不写死**)
- 全文 md5 **每改一次本单都会变**(回填 §8 尤其)⇒ **权威取数 = 现算**:
`md5sum "E:/ProgramData/AI技能/aliyun-dsh-server/交接单_组密钥加密_20260918.md"`
- **落单快照**(§11.4 本段写入前的最后一次现算,2026-09-18 00:5x)= **`40d35357a501596dcd1e6d7e70e8b371`**(**349 行**)。
⚠️ 本行写入后该值**即失效** ⇒ **引用前必须复跑上面的命令**。
- **§8 前前缀** 不受影响(§8 是截断点)⇒ 仍是 §11.2 的 **`149596460288ca1a5b7abddc490f7909`**。
### 11.5 🔴 口径定稿(2026-09-18,序 ㉛ 执行棒)—— **本行起具最高效力**
> **效力范围**:本单 §4 / §6 的**正文一个字都没改**(⛔ 不能改 —— 见下"为什么")。凡 §4 / §6 / §5 之间口径打架处,**一律以本节的裁决为准**。
**定稿一:`OBS-23` 对"缺省不启用"取 `SKIP + 留痕`(⛔ 不是 FAIL)**
- **裁决**:**§5 与 §6 的形状正确,§4/S6 里那句「新 `OBS-23` 对**旧生产** ⇒ **FAIL**」作废。**
- **理由**:① "缺省不启用"是**合法状态**(本单的设计目标就是"缺省不启用 ⇒ 行为逐字回到序㉔"),把它判红 = **把合法态误报成故障** ⇒ 长期挂假红会**磨掉判据灵敏度**(新红项会被"反正是红的"淹没 —— 这是本线已复盘过的教训);② 探针实现里 SKIP 行**不进红计数**,且**必打留痕**(打印"⛔ 未启用组密钥加密(`content.crypto` 块缺席)⇒ SKIP + 留痕"),⛔ 既不是静默绿、也不是假红。
- 🔴 **"真机先红"这条腿由「权限腿」承载**(单 B 已实测):把 `CONTENT_KEY_FILE` 从 `0600` 改 `0640` ⇒ `OBS-23` **FAIL** 并**分别点名**两台机的实读权限(`47=640 106=600`)、rc=1、全量仅 1 红 ⇒ 还原即复绿。⇒ 判据**有牙**,且"有牙"这件事**不依赖"是否启用加密"**。
- ⛔ **仍然禁止**:出现"**既不是 SKIP 也不是 FAIL**"的第三态(静默绿)。
**定稿二:`β′`(块 id 挂密文)的既有代价,本轮已量化,⛔ 不再当"未验证"**
- **轮换代价 = 一次完整冷启动**:4 组 × 4 节点夹具实测 —— 冷启动回源 **21,021,572 B = 4.0001 × 包 = 1 × 包/组**,轮换(epoch 1→2,块 id 改变 **100%**)后首轮**同值**(`比值 = 1.0000`),第二轮起 **0**;**对照腿(不轮换)第 3 轮 = 0** ⇒ "回源是轮换引起的"可断言。⚠️ 附带代价 = 旧 epoch 密文**变垃圾且不自清**(`storeBytes` 5,255,393 → 10,510,786)。
- **加密性能开销**:同一份 11,363,655 B 首屏包、n = 11 ⇒ 总墙钟 **p50 22.636 → 42.782 ms**、**p95 23.020 → 43.599 ms**(**1.890× / 1.894×**,绝对 +20.1 / +20.6 ms);吞吐 502.02 → 265.62 MB/s。⚠️ 口径 = 单核、页缓存全热的本机读数 ⇒ 是"加密层自身成本"的参照,⛔ 不是端到端承诺。
- **低熵明文的定性结论**:**持组密钥者**可枚举恢复(1000 条域 11.644 ms、命中 1);**中继(无密钥)**枚举不可行(自造字典命中 **0/1000**,持有 1 对明文-密文也预测不出别的),**但能观测相等性 / 重复率**(10,000 块 / 100 值域 ⇒ 仅 100 种密文)⇒ **⛔ 不写"很安全"**:安全边界 = "中继看不到明文"**成立**,但"内容不可推断"**不成立**。
- ⇒ 这三条**不再是 §10 的"未验证项"**(原始读数见参数表 **§11.3 补记** + 本单 **§8.14**;脚本原文 = 工作区 `_tmp_seq32/p01-perf.txt` / `p02-rotate.txt` / `p03-lowentropy.txt` / `p04-epoch.txt`)。
**定稿三:`CONTENT_EPOCH_GRACE_MS` 的选值口径**
- 判定式 = **"轮换后还允许旧密文被解多久"**;实测归零时刻 = **最晚 `retiredAt` + 本值**(小 60 s ⇒ 24 h;中 24 h ⇒ 48 h;大 7 d ⇒ 8 d,阶梯衰减、窗口内 `epochExpired=0`)。
- ⛔ 太短 ⇒ 尚未换完钥的节点掉线;太长 ⇒ 旧密钥留太久(安全)。⚠️ **轮换时点必须约在发布窗口**(§9-8)。⇒ **当前值 24 h 维持**(⛔ 本棒不改任何值)。
**为什么 §4 / §6 正文不能改(写下来,防下一位读者"好心改回去")**
- 本单的 **§8 前前缀 `149596460288ca1a5b7abddc490f7909` 是一条不变量**("§8 是截断点")—— 它的作用是**证明"计划在接受执行后没被人偷改"**。动 §4/§6/§5 里任何一个字都会**打穿这条证据链**。⇒ 凡口径修正,**一律写在 §8 之后**(本节 + §8.14),并在引用时指明"以 §11.5 为准"。