bookworm-smart-assistant/scripts/poc/poc-h1-rename-atomicity.js

135 lines
4.7 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* PoC H1: Windows fs.renameSync 跨目录原子性验证
*
* 验证 4 种场景:
* A. 同分区同目录 rename (期望: 原子)
* B. 同分区跨目录 rename (期望: 原子)
* C. 跨分区 rename (期望: Node 自动 copy+unlink, 非原子)
* D. 目标文件存在时 rename (期望: 覆盖, Windows >=10 支持)
*
* 红队 red-team-logic A1 [CRITICAL]: 跨卷 rename 非原子 本测试量化验证
* 产出: ai-delivery-pipeline/_poc-sandbox/h1-report.json
*/
'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
const SANDBOX = path.join(__dirname, '..', '..', 'ai-delivery-pipeline', '_poc-sandbox');
const REPORT = path.join(SANDBOX, 'h1-report.json');
function mk(subdir) {
const d = path.join(SANDBOX, subdir);
fs.mkdirSync(d, { recursive: true });
return d;
}
function scenarioA_sameDir() {
const d = mk('h1-a');
const a = path.join(d, 'src.txt');
const b = path.join(d, 'dst.txt');
fs.writeFileSync(a, 'A-content', 'utf8');
const t0 = process.hrtime.bigint();
fs.renameSync(a, b);
const dt = Number(process.hrtime.bigint() - t0) / 1e6;
const ok = !fs.existsSync(a) && fs.readFileSync(b, 'utf8') === 'A-content';
return { name: 'A-sameDir', ok, ms: dt.toFixed(3) };
}
function scenarioB_crossDir() {
const d1 = mk('h1-b/src');
const d2 = mk('h1-b/dst');
const a = path.join(d1, 'foo.txt');
const b = path.join(d2, 'foo.txt');
fs.writeFileSync(a, 'B-content', 'utf8');
const t0 = process.hrtime.bigint();
fs.renameSync(a, b);
const dt = Number(process.hrtime.bigint() - t0) / 1e6;
const ok = !fs.existsSync(a) && fs.readFileSync(b, 'utf8') === 'B-content';
return { name: 'B-crossDir-sameVolume', ok, ms: dt.toFixed(3) };
}
function scenarioC_crossVolume() {
// 探测系统是否有第二卷; 无则跳过
const drives = [];
for (const d of ['D:', 'E:', 'F:']) {
try { fs.accessSync(d + '\\', fs.constants.W_OK); drives.push(d); } catch {}
}
if (drives.length === 0) {
return { name: 'C-crossVolume', ok: null, skip: '未检测到第二可写卷' };
}
const otherVol = drives[0];
const tmpOther = path.join(otherVol + '\\', 'bookworm-poc-' + Date.now());
try {
fs.mkdirSync(tmpOther, { recursive: true });
const a = path.join(SANDBOX, 'h1-c-src.txt');
const b = path.join(tmpOther, 'dst.txt');
fs.writeFileSync(a, 'C-content', 'utf8');
const t0 = process.hrtime.bigint();
fs.renameSync(a, b);
const dt = Number(process.hrtime.bigint() - t0) / 1e6;
const ok = !fs.existsSync(a) && fs.readFileSync(b, 'utf8') === 'C-content';
fs.unlinkSync(b);
fs.rmdirSync(tmpOther);
return { name: 'C-crossVolume', ok, ms: dt.toFixed(3), targetVol: otherVol };
} catch (e) {
return { name: 'C-crossVolume', ok: false, error: e.message, targetVol: otherVol };
}
}
function scenarioD_overwrite() {
const d = mk('h1-d');
const a = path.join(d, 'src.txt');
const b = path.join(d, 'dst.txt');
fs.writeFileSync(a, 'D-new', 'utf8');
fs.writeFileSync(b, 'D-old', 'utf8');
try {
fs.renameSync(a, b);
const content = fs.readFileSync(b, 'utf8');
const ok = !fs.existsSync(a) && content === 'D-new';
return { name: 'D-overwrite', ok, note: 'overwrote existing' };
} catch (e) {
return { name: 'D-overwrite', ok: false, error: e.code + ': ' + e.message };
}
}
function main() {
fs.mkdirSync(SANDBOX, { recursive: true });
const results = [
scenarioA_sameDir(),
scenarioB_crossDir(),
scenarioC_crossVolume(),
scenarioD_overwrite(),
];
const report = {
hypothesis: 'H1 · Windows fs.renameSync 跨目录原子性',
platform: os.platform() + ' ' + os.release(),
nodeVersion: process.version,
timestamp: new Date().toISOString(),
results,
verdict: (() => {
const a = results[0].ok === true;
const b = results[1].ok === true;
const d = results[3].ok === true;
const c = results[2];
if (a && b && d) {
const cState = c.ok === null ? '无法测试(无第二卷)' :
c.ok === true ? '跨卷也可 rename(Node 自动 copy)' :
'跨卷 rename 失败: ' + (c.error || 'unknown');
return { pass: true, summary: '同卷原子 ✅, 覆盖 ✅, ' + cState };
}
return { pass: false, summary: 'A/B/D 有失败场景, 不可用于生产' };
})(),
recommendation: '',
};
report.recommendation = report.verdict.pass
? '✅ 采用同卷 rename 作为 staging→delivery 主路径; 跨卷场景须 fallback 到 copy+fsync+unlink(非原子, 需上层补偿)'
: '❌ 方案不可行, 需重新设计';
fs.writeFileSync(REPORT, JSON.stringify(report, null, 2), 'utf8');
console.log(JSON.stringify(report, null, 2));
}
main();