代码
- 内容分发块级寻址:新增 src/net/relay/content/{chunker,store,runtime,source,peer,crypto}.ts
- 组密钥(C 档)确定性加密:AES-256-GCM,块 id β′ = sha256(密文) 前 32 hex;双 epoch 过渡窗口
- 实例生命周期:三处 teardown() 不再杀实例(local/remote/leased-spawner);启动认领 + TCP 探活判孤儿
- 骨干选路:jitter 选路 + endpoint-target;relay client/server/wire/identity/directory/rendezvous/switcher 调整
- 工作台 src/web/server.ts、src/worker/relay-tunnel.ts 装配与候选链观测
脚本与测试
- scripts/overlay-{probe,keyring,jitter}.cjs 更新
- 探针新增 OBS-21(每连接候选数)/ OBS-22(teardown 静态守卫 + 认领面)/ OBS-23(组密钥加密)
- 新增 test/{orchestrator-teardown,orchestrator-rehydrate,overlay-content,overlay-jitter}.test.mjs;relay 两例更新
文档
- 新增交接单:覆盖网络-序24-内容分发块级寻址 / 序25-实例逐步拉起 / 序26-骨干稳定选路与加密
- INDEX.md、交接单/README.md、skills/dsh-auto-handoff-chain/SKILL.md 同步
验收(零回归,2026-09-18 08:0x 复核)
- npm test 201 tests / 200 pass / 0 fail / 1 skipped
- overlay-failover-drill --scene all --table 12 PASS / 0 SKIP / 0 FAIL
- overlay-probe --table 23 PASS / 0 SKIP / 0 FAIL (rc=0)
172 lines
9.2 KiB
JavaScript
172 lines
9.2 KiB
JavaScript
/**
|
||
* 覆盖网络线 序 ㉕ · 「逐步拉起」单测 —— **纯函数面**(零 IO、零 systemd、零生产副作用)。
|
||
*
|
||
* ## 为什么只测纯函数
|
||
* 被替换掉的是「启动即 `cleanAllStaleScopes()`」。它的输入是 **OS 层既有 scope**
|
||
* (`systemctl list-units` + `systemctl show -p Description`),在 Windows 开发机上
|
||
* 拿不到 ⇒ 真机部分由 §6/S6 在 106 上验收。
|
||
* 但**最容易出错、也最致命**的那一段恰好是纯的 —— **把 `Description` 解析回实例三要素**:
|
||
* 解析错 ⇒ 后续替换时定位到别人的实例(跨租户最坏情形)。故这一段必须穷举式钉住。
|
||
*
|
||
* 另加两条**源码级守卫**(照 序⑦ `T38` 先例):认领逻辑必须真的接在构造函数上、
|
||
* 且⛔ 不许把「认领」写成「什么都不做」(那会让档案 30 的孤儿失控)。
|
||
*
|
||
* 运行:`node --test test/orchestrator-rehydrate.test.mjs`(⚠️ **刻意不进 `npm test`** ——
|
||
* 本序要求零回归基线 `npm test` 176/175/0/1 **逐字不变**,加进去会改测试总数)。
|
||
*
|
||
* @module test/orchestrator-rehydrate
|
||
*/
|
||
|
||
import assert from 'node:assert/strict'
|
||
import { readFile } from 'node:fs/promises'
|
||
import { test } from 'node:test'
|
||
import {
|
||
decideScopeAction,
|
||
parseScopeDescription,
|
||
parseScopeUnitName,
|
||
} from '../lib/supervisor/orchestrator.js'
|
||
|
||
/**
|
||
* 夹具 = 106 上 **真实** 实例 `dsh-100002-ef8d1d12.scope` 的 `Description` 精简等价形态
|
||
* (保留全部关键 flag 与其真实相对顺序,省掉无关的 `--ro-bind-try` 白名单项)。
|
||
* ⚠️ 顺序刻意保留:`--chdir` 在 bwrap 段、`--profile/--port` 在 setpriv 之后。
|
||
*/
|
||
const REAL_DESC =
|
||
'/usr/bin/bwrap --ro-bind /usr /usr --tmpfs /etc ' +
|
||
'--bind /var/lib/dshs/users/4092b965-2f68-4977-9989-68b3966f7df0/tmp /tmp ' +
|
||
'--bind /var/lib/dshs/users/4092b965-2f68-4977-9989-68b3966f7df0 /var/lib/dshs/users/4092b965-2f68-4977-9989-68b3966f7df0 ' +
|
||
'--unshare-pid ' +
|
||
'--chdir /var/lib/dshs/users/4092b965-2f68-4977-9989-68b3966f7df0/ws ' +
|
||
'-- setpriv --reuid 100002 --regid 100002 --clear-groups ' +
|
||
'/usr/bin/dsh --profile web --host 127.0.0.1 --port 21000'
|
||
|
||
const UID = 100002
|
||
const USER = '4092b965-2f68-4977-9989-68b3966f7df0'
|
||
const FOLDER = `/var/lib/dshs/users/${USER}/ws`
|
||
|
||
/* ── R1–R5:scope 名解析(⛔ 只认本平台自己的形态) ─────────────────────────── */
|
||
|
||
test('R1 真机形态:dsh-100002-ef8d1d12.scope ⇒ uid 100002', () => {
|
||
assert.equal(parseScopeUnitName('dsh-100002-ef8d1d12.scope'), 100002)
|
||
})
|
||
|
||
test('R2 ⛔ 大写 hex / 非 scope / 别的单元一律不认', () => {
|
||
assert.equal(parseScopeUnitName('dsh-100002-EF8D1D12.scope'), undefined)
|
||
assert.equal(parseScopeUnitName('dsh-100002-ef8d1d12.service'), undefined)
|
||
assert.equal(parseScopeUnitName('dshs-relay.service'), undefined)
|
||
assert.equal(parseScopeUnitName('dsh-100002-ef8d1d12.scope.bak'), undefined)
|
||
assert.equal(parseScopeUnitName(''), undefined)
|
||
})
|
||
|
||
test('R3 ⛔ uid=0 / 缺段 一律不认(防误伤 systemd 自身与门户进程)', () => {
|
||
assert.equal(parseScopeUnitName('dsh-0-ef8d1d12.scope'), undefined)
|
||
assert.equal(parseScopeUnitName('dsh--ef8d1d12.scope'), undefined)
|
||
assert.equal(parseScopeUnitName('dsh-100002.scope'), undefined)
|
||
})
|
||
|
||
/* ── R6–R11:Description 解析(真机夹具) ──────────────────────────────────── */
|
||
|
||
test('R6 真机夹具:三要素全部解出且 userId 取自 --chdir(⛔ 不是取自 --bind)', () => {
|
||
const info = parseScopeDescription(REAL_DESC, UID)
|
||
assert.deepEqual(info, { userId: USER, role: 'main', port: 21000, folder: FOLDER })
|
||
})
|
||
|
||
test('R7 ⛔ uid 交叉校验不符 ⇒ 拒(scope 名与 argv 非同源 = 半截信息,必须停)', () => {
|
||
assert.equal(parseScopeDescription(REAL_DESC, 100003), undefined)
|
||
assert.equal(parseScopeDescription(REAL_DESC.replace('--reuid 100002', '--reuid 100009'), UID), undefined)
|
||
})
|
||
|
||
test('R8 ⛔ 缺 --profile 或 profile 非 web/headless ⇒ 拒(role 猜不得)', () => {
|
||
assert.equal(parseScopeDescription(REAL_DESC.replace('--profile web ', ''), UID), undefined)
|
||
assert.equal(parseScopeDescription(REAL_DESC.replace('--profile web', '--profile weird'), UID), undefined)
|
||
})
|
||
|
||
test('R9 ⛔ main 缺 --port / 端口非数字 / 越界 ⇒ 拒', () => {
|
||
assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', ''), UID), undefined)
|
||
assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', '--port abc'), UID), undefined)
|
||
assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', '--port 0'), UID), undefined)
|
||
assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', '--port 70000'), UID), undefined)
|
||
})
|
||
|
||
test('R10 ⛔ 缺 --chdir / 相对路径 / 路径里没有 /users/ 段 ⇒ 拒(拿不到 userId)', () => {
|
||
assert.equal(parseScopeDescription(REAL_DESC.replace('--chdir ' + FOLDER + ' ', ''), UID), undefined)
|
||
assert.equal(parseScopeDescription(REAL_DESC.replace(FOLDER, 'ws'), UID), undefined)
|
||
assert.equal(
|
||
parseScopeDescription(REAL_DESC.replace(FOLDER, '/srv/ws').replace(/\/var\/lib\/dshs\/users\//g, '/srv/'), UID),
|
||
undefined,
|
||
)
|
||
})
|
||
|
||
test('R11 watchdog:headless 且有端口 ⇒ 拒(不自洽);headless 无端口 ⇒ 解出但 role=watchdog', () => {
|
||
const headlessWithPort = REAL_DESC.replace('--profile web', '--profile headless')
|
||
assert.equal(parseScopeDescription(headlessWithPort, UID), undefined)
|
||
const headless = headlessWithPort.replace(' --port 21000', '')
|
||
const info = parseScopeDescription(headless, UID)
|
||
assert.equal(info?.role, 'watchdog')
|
||
assert.equal(info?.port, undefined)
|
||
assert.equal(info?.userId, USER)
|
||
})
|
||
|
||
/* ── R12–R15:处置判定(认领 vs 按旧行为停) ───────────────────────────────── */
|
||
|
||
test('R12 合法 main ⇒ adopt', () => {
|
||
const info = parseScopeDescription(REAL_DESC, UID)
|
||
assert.deepEqual(decideScopeAction(info, false), { kind: 'adopt' })
|
||
})
|
||
|
||
test('R13 ⛔ 同 uid 多 scope ⇒ 全部停(档案 30 的风险本体:多实例共 profile)', () => {
|
||
const info = parseScopeDescription(REAL_DESC, UID)
|
||
assert.deepEqual(decideScopeAction(info, true), { kind: 'stop', reason: 'dup-uid' })
|
||
})
|
||
|
||
test('R14 ⛔ 解析失败 ⇒ 停(宁可清掉,不留半截实例)', () => {
|
||
assert.deepEqual(decideScopeAction(undefined, false), { kind: 'stop', reason: 'unparsable' })
|
||
})
|
||
|
||
test('R15 ⛔ watchdog ⇒ 停(一次性 headless、无监听端口 ⇒ 无法确认健康,留着无收益)', () => {
|
||
const info = parseScopeDescription(REAL_DESC.replace('--profile web', '--profile headless').replace(' --port 21000', ''), UID)
|
||
assert.deepEqual(decideScopeAction(info, false), { kind: 'stop', reason: 'no-probe-target' })
|
||
})
|
||
|
||
/* ── R16–R18:源码级接线守卫(照 序⑦ T38 先例) ────────────────────────────── */
|
||
|
||
const SRC = await readFile(new URL('../src/supervisor/orchestrator.ts', import.meta.url), 'utf8')
|
||
|
||
test('R16 接线:构造函数必须调 rehydrateAdoptedScopes(⛔ 不是裸 cleanAllStaleScopes)', () => {
|
||
assert.ok(
|
||
/this\.portGuard = createPortGuard\(config\.portGuard\)[\s\S]{0,400}this\.rehydrateAdoptedScopes\(\)/.test(SRC),
|
||
'constructor 未接认领逻辑(仍是启动即清空)',
|
||
)
|
||
})
|
||
|
||
test('R17 ⛔ cleanAllStaleScopes 不许被删(回滚路径 + 非 account 回退都还在)', () => {
|
||
assert.ok(SRC.includes('private cleanAllStaleScopes(): void'))
|
||
assert.ok(
|
||
/private rehydrateAdoptedScopes\(\): void \{[\s\S]{0,400}this\.cleanAllStaleScopes\(\)/.test(SRC),
|
||
'非 account 回退丢了',
|
||
)
|
||
})
|
||
|
||
test('R18 ⛔ 认领不得写进 mains(写进去 ⇒ enter 复用分支拿不到 token ⇒ 503)', () => {
|
||
const body = SRC.slice(SRC.indexOf('private adoptOne('), SRC.indexOf('private probeAdopted('))
|
||
assert.ok(body.includes('this.adopted.set('), 'adoptOne 未登记到 adopted')
|
||
assert.ok(!body.includes('this.mains.set('), '⛔ adoptOne 把实例写进了 mains ⇒ 用户会被 503 挡住')
|
||
assert.ok(!body.includes('spawn('), '⛔ adoptOne 里出现了 spawn ⇒ 违反"不 spawn"')
|
||
const afterAdopt = body.slice(body.indexOf('this.adopted.set('))
|
||
assert.ok(!afterAdopt.includes('stopUnit('), '⛔ adopt 路径上还停了实例(认领应当只登记 + 探活)')
|
||
})
|
||
|
||
test('R19 ⛔ 认领/回收路径里不得出现凭据落盘(R11:安全维度不许净变差)', () => {
|
||
const body = SRC.slice(SRC.indexOf('private rehydrateAdoptedScopes('), SRC.indexOf('/** 档案 30:清掉指定 uid'))
|
||
for (const banned of ['writeFileSync', 'appendFileSync', 'launchToken']) {
|
||
assert.ok(!body.includes(banned), `rehydrate 路径里出现了 ${banned}`)
|
||
}
|
||
})
|
||
|
||
test('R20 ⛔ 节流:认领必须逐条经 setTimeout 排队(不得同步一次全跑)', () => {
|
||
const body = SRC.slice(SRC.indexOf('private rehydrateAdoptedScopes('), SRC.indexOf('private scanExistingScopes('))
|
||
assert.ok(body.includes('setTimeout('), '认领没有节流')
|
||
assert.ok(SRC.includes('DSHS_REHYDRATE_STAGGER_MS'), '节流阈值不是可配的环境键')
|
||
assert.ok(SRC.includes('DSHS_REHYDRATE_PROBE_MS'), '探活超时不是可配的环境键')
|
||
})
|