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 登记
This commit is contained in:
1 parent
45b4999d24
commit
d2ef362a98
20 files changed
+2998
-183
No files matched your search
@@ -545,6 +545,9 @@ import {
|
||||
keyIdOf,
|
||||
loadGroupKeyFile,
|
||||
verifyGroupKeyCredential,
|
||||
// 🆕 序㊻ · C(域分离)
|
||||
BLOCK_ID_DOMAIN_TAG,
|
||||
deriveBlockIdKey,
|
||||
} from '../lib/net/relay/content/crypto.js'
|
||||
|
||||
/** 造一个确定性的组密钥(⛔ 不用随机 —— 用例必须可复现)。 */
|
||||
@@ -842,3 +845,204 @@ test('F5/E1 口径:加密前后"回源份数"不变(同组 N 次取用 ⇒
|
||||
assert.strictEqual(rt.snapshot().store.puts, 3, '同内容只入库 3 个块(去重)')
|
||||
assert.strictEqual(rt.snapshot().crypto.decryptRejected, 0)
|
||||
})
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// 组 G(序㊻ · C 域分离):块 id 的 **per-network keyed hash**
|
||||
//
|
||||
// 🔑 这一组是 `OBS-29` 的**离线等价体**(探针那条判据在同一批函数上跑)。
|
||||
// 两条腿**必须同时有**(缺一即视为未覆盖):
|
||||
// · **正腿** = 同字节 + **不同 network** ⇒ 块 id **不同**;
|
||||
// · 🔴 **负腿** = **去掉 per-network 维度**(`netKey` 取同值 / 取空)⇒ 同一谓词**必红**。
|
||||
// ⛔ 只给正腿 = 会漏掉"去了重"(把去重也一起干掉照样全绿);
|
||||
// ⛔ 没有负腿 = "上了个**无效的**域分离"会全绿 —— 本线老病根(装了但没生效 = 静默放行)。
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/** C 的取数夹具:**同一把组密钥** + 不同 network ⇒ 只有 network 维度在变。 */
|
||||
function keyingFixture(keySeed = 21) {
|
||||
const cipher = new ContentCipher({ groupKey: 'ops|content', epoch: 1, key: makeKey(keySeed) })
|
||||
const bytes = makeBytes(3 * 1024 * 1024 + 7, 5) // 4 块(⛔ 不用整块数 —— 边界块也一起验)
|
||||
// ⚠️ **同网复用同一个 runtime**(缓存):既省事,也让"同网 ⇒ 同一套 id / 同一份存储"这一
|
||||
// 语义在夹具层面就成立(`E1` 重取需要"同一个网内重复取用")。
|
||||
const cache = new Map()
|
||||
const rtOf = (network) => {
|
||||
if (!cache.has(network)) cache.set(network, new ContentRuntime({ network, group: 'content', cipher }))
|
||||
return cache.get(network)
|
||||
}
|
||||
return { cipher, bytes, rtOf }
|
||||
}
|
||||
|
||||
/** 🔴 正腿的**谓词本体**(放在函数里 ⇒ 正/负两腿断言的是同一个谓词,⛔ 不是两份写法)。 */
|
||||
const idDiffersAcrossNetworks = (planA, planB) => planA[0] !== planB[0]
|
||||
|
||||
test('G1 🔴 正腿:同字节 + 不同 network ⇒ 块 id 必须不同(且两侧都不是裸哈希)', () => {
|
||||
const { bytes, rtOf } = keyingFixture()
|
||||
const a = rtOf('ops').planContent(bytes).ids
|
||||
const b = rtOf('u:1').planContent(bytes).ids
|
||||
const bare = planOf(bytes).ids
|
||||
assert.strictEqual(idDiffersAcrossNetworks(a, b), true, '不同 network ⇒ 块 id 必须不同(域分离生效)')
|
||||
assert.notStrictEqual(a[0], bare[0], '⚠️ A 网不得等于裸哈希 —— 否则 = 域分离没生效("装了没生效"的本形)')
|
||||
assert.notStrictEqual(b[0], bare[0], '⚠️ B 网不得等于裸哈希(同上)')
|
||||
assert.strictEqual(a.length, 4, '4 块:正腿必须覆盖多块而不是只比第一块')
|
||||
for (let i = 0; i < a.length; i += 1) {
|
||||
assert.notStrictEqual(a[i], b[i], `第 ${i} 块:跨网必须不同`)
|
||||
}
|
||||
})
|
||||
|
||||
test('G1-b 正腿(id 函数直测):blockIdOf / contentIdOf 的 netKey 维度必须起作用', () => {
|
||||
const k = makeKey(21)
|
||||
const b = makeBytes(4096, 5)
|
||||
assert.notStrictEqual(blockIdOf(b, deriveBlockIdKey(k, 'ops')), blockIdOf(b, deriveBlockIdKey(k, 'u:1')))
|
||||
assert.notStrictEqual(contentIdOf(b, deriveBlockIdKey(k, 'ops')), contentIdOf(b, deriveBlockIdKey(k, 'u:1')))
|
||||
})
|
||||
|
||||
test('G2 🔴 正腿:同 network + 同字节 ⇒ 块 id 必须相同(**去重不得丢**)', () => {
|
||||
const { bytes, rtOf } = keyingFixture()
|
||||
const rt1 = rtOf('ops')
|
||||
const rt2 = rtOf('ops')
|
||||
assert.strictEqual(idDiffersAcrossNetworks(rt1.planContent(bytes).ids, rt2.planContent(bytes).ids), false)
|
||||
assert.deepStrictEqual(rt1.planContent(bytes).ids, rt2.planContent(bytes).ids, '同网同内容 ⇒ 计划逐字一致')
|
||||
// 去重仍然成立:同一份内容入库两次 ⇒ store 只入 4 个**唯一**块(3 整块 + 1 边界块)
|
||||
const put1 = rt1.putContent(bytes)
|
||||
const put2 = rt1.putContent(bytes)
|
||||
assert.deepStrictEqual(put2.plan, put1.plan, '同网重复写 ⇒ 计划 id 逐字一致(去重必须还在)')
|
||||
assert.strictEqual(rt1.snapshot().store.puts, 4, '同内容只入库 4 个块(⛔ 域分离不得把去重干掉)')
|
||||
assert.strictEqual(rt1.snapshot().store.putRejected, 0)
|
||||
})
|
||||
|
||||
test('G3 装配面:域分离启用后 store 两处复算 / 链取回 / 重组位三条路都必须过(= 调用点无漏改)', async () => {
|
||||
const { bytes, rtOf } = keyingFixture()
|
||||
const rt = rtOf('ops')
|
||||
const put = rt.putContent(bytes)
|
||||
// ① store 写侧复算(漏改 ⇒ 这里必然抛 / putRejected 涨)
|
||||
assert.strictEqual(rt.snapshot().store.putRejected, 0, 'store 写侧复算必须过')
|
||||
// ② 生产路径:优先级链取回(local 档命中 → 唯一解密点)
|
||||
const got = await rt.fetchContent(put.plan)
|
||||
assert.ok(got !== undefined && got.equals(bytes), '经优先级链取回必须逐字节等于原内容')
|
||||
assert.strictEqual(rt.snapshot().store.corruptReads, 0, 'store 读侧复算必须过')
|
||||
// ③ 工具路径:重组位(`chunker#reassemble` 也要同一个 netKey)
|
||||
const stored = new Map()
|
||||
for (const id of put.plan) stored.set(id, rt.store.get(id))
|
||||
const back = await rt.reassembleContent(stored, put.plan)
|
||||
assert.ok(back !== undefined && back.equals(bytes), '重组位必须逐字节等于原内容(⛔ 漏 netKey ⇒ 必抛校验失败)')
|
||||
// ④ D7 闸门:**跨网**取回的块(块本身完好)必须被丢弃 —— 域不同 ⇒ id 不等 ⇒ 不算命中
|
||||
const other = rtOf('u:1')
|
||||
const putOther = other.putContent(bytes)
|
||||
assert.notDeepStrictEqual(putOther.plan, put.plan, '两个域的块 id 必须不同')
|
||||
assert.strictEqual(rt.store.has(putOther.plan[0]), false, '跨域的块 id 不得在本地命中')
|
||||
})
|
||||
|
||||
test('G4 回滚路径:缺省 / 空 netKey ⇒ 回落裸 sha256(与序㉔ 逐字一致)', () => {
|
||||
const b = makeBytes(4096, 5)
|
||||
const bare = createHash('sha256').update(b).digest('hex').slice(0, BLOCK_ID_HEX_LEN)
|
||||
assert.strictEqual(blockIdOf(b), bare, '缺省必须恰为裸 sha256 前 32 hex')
|
||||
assert.strictEqual(blockIdOf(b, Buffer.alloc(0)), bare, '⚠️ 空 netKey 必须**等价于**不传(回滚路径)')
|
||||
assert.strictEqual(contentIdOf(b, Buffer.alloc(0)), bare)
|
||||
// ⛔ 反向:非空 netKey **必须**离出裸哈希(否则"回落"与"生效"不可分 ⇒ 判据无牙)
|
||||
assert.notStrictEqual(blockIdOf(b, Buffer.alloc(4, 0xab)), bare, '非空 netKey 必须走 HMAC 分支')
|
||||
// 不启用组密钥的 runtime ⇒ 恒回落裸哈希(= 缺省不启用 / 回滚路径成立)
|
||||
const plain = new ContentRuntime({ network: 'ops', group: 'content' })
|
||||
assert.strictEqual(plain.netKey, undefined)
|
||||
assert.strictEqual(plain.blockIdKeyId, undefined)
|
||||
assert.deepStrictEqual(plain.planContent(b).ids, planOf(b).ids, '不启用 ⇒ 计划逐字等于裸哈希口径')
|
||||
})
|
||||
|
||||
test('G5 🔴 负腿「netKey 取同值」:去掉 per-network 维度 ⇒ 正腿谓词必红(具名 flat-key-collapses)', () => {
|
||||
const { bytes, rtOf } = keyingFixture()
|
||||
// 正腿:两个**不同** network
|
||||
assert.strictEqual(
|
||||
idDiffersAcrossNetworks(rtOf('ops').planContent(bytes).ids, rtOf('u:1').planContent(bytes).ids),
|
||||
true,
|
||||
'正腿基线:不同 network ⇒ 谓词必须为真',
|
||||
)
|
||||
// 🔴 负腿:把 network 维度**去掉**(两侧取同一个 network ⇒ 同一把域密钥)
|
||||
const flatA = rtOf('ops').planContent(bytes).ids
|
||||
const flatB = rtOf('ops').planContent(bytes).ids
|
||||
assert.strictEqual(
|
||||
idDiffersAcrossNetworks(flatA, flatB),
|
||||
false,
|
||||
'⚠️ flat-key-collapses:域密钥取同值 ⇒ 谓词**必须为假**(判据有牙;若这里为真说明判据根本没看 network)',
|
||||
)
|
||||
// ⚠️ 且此时**仍然是"带密钥"的路径**(不等于裸哈希)—— 排除了"负腿其实是回落裸哈希"的混淆
|
||||
assert.notStrictEqual(flatA[0], planOf(bytes).ids[0], '负腿必须走 HMAC 路径(⛔ 不是回落裸哈希)')
|
||||
})
|
||||
|
||||
test('G6 🔴 负腿「netKey 取空」:回落裸哈希 ⇒ 同一正腿谓词必红(具名 empty-key-falls-back-to-bare-hash)', () => {
|
||||
const b = makeBytes(4096, 5)
|
||||
const k = deriveBlockIdKey(makeKey(21), 'ops')
|
||||
// 谓词本体在**函数级**再跑一遍(与 G1/G5 同一个谓词形状)
|
||||
const differs = (x, y) => x !== y
|
||||
assert.strictEqual(differs(blockIdOf(b, k), blockIdOf(b, k)), false, '取空(两侧同一把钥)⇒ 必为假')
|
||||
assert.strictEqual(differs(blockIdOf(b), blockIdOf(b)), false, '两侧都不传 ⇒ 必为假')
|
||||
// ⛔ 反向:只把**一侧**取空 ⇒ 谓词为真(说明"取空"确实会改变取值 ⇒ 判据对 netKey 敏感)
|
||||
assert.strictEqual(differs(blockIdOf(b, k), blockIdOf(b)), true, '一侧带钥一侧不带 ⇒ 必须不同')
|
||||
// ⚠️ 取空与生效**必须可分**:否则这条负腿没有判别力
|
||||
assert.notStrictEqual(blockIdOf(b, k), blockIdOf(b))
|
||||
})
|
||||
|
||||
test('G7 派生:确定性 + 只由 (组密钥, network) 决定 + 换钥必变 + 不等于密钥本体', () => {
|
||||
const k = makeKey(21)
|
||||
assert.strictEqual(deriveBlockIdKey(k, 'ops').length, KEY_LEN, '派生钥必须是 32 B(HMAC-SHA256 输出)')
|
||||
assert.ok(deriveBlockIdKey(k, 'ops').equals(deriveBlockIdKey(k, 'ops')), '确定性:同钥同网 ⇒ 同一把')
|
||||
assert.ok(!deriveBlockIdKey(k, 'ops').equals(deriveBlockIdKey(k, 'u:1')), 'network 维度必须起作用')
|
||||
assert.ok(!deriveBlockIdKey(k, 'ops').equals(deriveBlockIdKey(makeKey(22), 'ops')), '换组密钥 ⇒ 派生钥必变')
|
||||
assert.ok(!deriveBlockIdKey(k, 'ops').equals(k), '⛔ 派生钥不得等于组密钥本体(否则"派生"是假的)')
|
||||
assert.strictEqual(typeof BLOCK_ID_DOMAIN_TAG, 'string')
|
||||
assert.ok(BLOCK_ID_DOMAIN_TAG.length > 0, '域标签必须非空(它进 HMAC ⇒ 换标签 = 全部块 id 换代)')
|
||||
})
|
||||
|
||||
test('G8 观测面:未启用 ⇒ 两键整体缺席;启用 ⇒ 只放指纹(⛔ 不许出现域密钥本体)', () => {
|
||||
const plain = new ContentRuntime({ network: 'ops', group: 'content' })
|
||||
const sp = plain.snapshot()
|
||||
assert.ok(!('blockIdKeyed' in sp), '未启用域分离 ⇒ blockIdKeyed 必须**缺席**(⛔ 不补 false)')
|
||||
assert.ok(!('blockIdKeyId' in sp), '未启用域分离 ⇒ blockIdKeyId 必须缺席')
|
||||
|
||||
const { rtOf } = keyingFixture()
|
||||
const rt = rtOf('ops')
|
||||
const s = rt.snapshot()
|
||||
assert.strictEqual(s.blockIdKeyed, true)
|
||||
assert.match(String(s.blockIdKeyId), /^[0-9a-f]{16}$/, '指纹必须是 16 hex')
|
||||
assert.strictEqual(s.blockIdKeyId, keyIdOf(rt.netKey), '指纹必须 = keyIdOf(域密钥)')
|
||||
// 🔑 跨机口径一致(47 / 106 必须逐字相同;不同 ⇒ 跨机取块全判校验失败)
|
||||
assert.strictEqual(rtOf('ops').blockIdKeyId, rtOf('ops').blockIdKeyId, '同钥同网 ⇒ 口径指纹必须一致')
|
||||
assert.notStrictEqual(rtOf('ops').blockIdKeyId, rtOf('u:1').blockIdKeyId, '不同网 ⇒ 指纹必须不同')
|
||||
// 🔴 泄漏守卫:整份 /status 文本里不得出现域密钥本体(hex / base64 两种写法都扫)
|
||||
const text = JSON.stringify(s)
|
||||
assert.ok(!text.includes(rt.netKey.toString('hex')), '⛔ /status 不得出现域密钥(hex)')
|
||||
assert.ok(!text.includes(rt.netKey.toString('base64')), '⛔ /status 不得出现域密钥(base64)')
|
||||
})
|
||||
|
||||
test('G9 边界:域分离启用后"同内容不同域"的块**共享必然失效**(= 设计代价,⛔ 不是缺陷)', async () => {
|
||||
const { bytes, rtOf } = keyingFixture()
|
||||
const a = rtOf('ops')
|
||||
const b = rtOf('u:1')
|
||||
const putA = a.putContent(bytes)
|
||||
// B 域完全独立:既查不到 A 的块,也解不开 A 的密文(AAD 里就有 groupKey 的网名)
|
||||
assert.strictEqual(b.store.has(putA.plan[0]), false, '跨域不得命中')
|
||||
const gotB = await b.fetchContent(putA.plan)
|
||||
assert.strictEqual(gotB, undefined, '跨域取内容必须**整体失败**(⛔ 不许拼半截 / 不许静默回源代价)')
|
||||
})
|
||||
|
||||
test('G10 🔴 `E1` 基线**重取**(C 新 id 口径):同网重复取用 ⇒ 零增量回源;跨网 ⇒ 各 1 份', async () => {
|
||||
// ⚠️ `E1` 的**定义不重估**(回源字节 ≈ 1 份 × 组数)—— 只重取**基线读数**。
|
||||
const { bytes, rtOf } = keyingFixture()
|
||||
const A = rtOf('ops')
|
||||
const putA = A.putContent(bytes)
|
||||
const hit0 = A.snapshot().source.local
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
const got = await A.fetchContent(putA.plan)
|
||||
assert.ok(got !== undefined && got.equals(bytes), '第 ' + i + ' 轮取回必须逐字节等于原内容')
|
||||
}
|
||||
const s = A.snapshot()
|
||||
assert.strictEqual(s.source.local - hit0, putA.plan.length * 4, '4 轮 × 4 块 ⇒ 全部 local 命中')
|
||||
assert.strictEqual(s.source.origin, 0, '⚠️ 稳态回源必须 0("1 份"由首次写入承担 —— 新口径下不变)')
|
||||
assert.strictEqual(s.store.puts, putA.dedupIds.length, '同内容只入库**去重后**的块数')
|
||||
assert.strictEqual(s.store.putRejected, 0)
|
||||
// 跨网:B 网必须**各自一份**(id 不同 ⇒ 天然不去重 —— 这正是 C 的域收窄点)
|
||||
const B = rtOf('u:1')
|
||||
assert.strictEqual(B.snapshot().source.local, 0, 'B 网起点零命中(= 各自回源 1 份)')
|
||||
const putB = B.putContent(bytes)
|
||||
assert.notDeepStrictEqual(putB.plan, putA.plan, '两个网的块 id 序列必须不同')
|
||||
const gotB = await B.fetchContent(putB.plan)
|
||||
assert.ok(gotB !== undefined && gotB.equals(bytes), 'B 网自己那一份必须能取回')
|
||||
assert.notStrictEqual(putB.dedupIds[0], putA.dedupIds[0], '跨网不得共享块(= 设计意图,⛔ 不是缺陷)')
|
||||
})
|
||||
Reference in new issue
Block a user