bookworm-smart-assistant/scripts/patches/patch-c1-atomic-reset.js

130 lines
5.1 KiB
JavaScript
Raw Permalink 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.

#!/usr/bin/env node
/**
* C1 补丁 — 2026-04-16 audit-2026-04-16 遗留 CRITICAL
*
* 目标: scripts/fusion-weight-learner.js 两处 unlinkSync 改为原子 tmp+rename
* - 位置 A: resetOnDegeneracy 分支 (原 L331)
* - 位置 B: resetWeights() 函数 (原 L354)
*
* 修复原理:
* 并发 readFileSync 与 unlinkSync 存在 TOCTOU 竞态,
* 改为写 DEFAULT_WEIGHTS 到 tmp 再 rename 覆盖,
* 读者永远看到合法 JSON (或旧值,不会 ENOENT)。
*
* 幂等: 若已打过补丁 (存在 atomicResetWeightsFile 函数名) 则跳过。
*/
'use strict';
const fs = require('fs');
const path = require('path');
const TARGET = path.join(__dirname, '..', 'fusion-weight-learner.js');
const SENTINEL = 'atomicResetWeightsFile';
function main() {
if (!fs.existsSync(TARGET)) {
console.error('[patch-c1] 目标文件不存在:', TARGET);
process.exit(1);
}
const before = fs.readFileSync(TARGET, 'utf8');
if (before.includes(SENTINEL)) {
console.log('[patch-c1] 已打过补丁 (发现 ' + SENTINEL + '),跳过');
process.exit(0);
}
// ==== 注入 1: 在 _directWriteFallback 后追加 atomicResetWeightsFile 助手 ====
const helperAnchor =
'function _directWriteFallback(weightsFile, data) {\n' +
' try {\n' +
' if (!fs.existsSync(path.dirname(weightsFile))) {\n' +
' fs.mkdirSync(path.dirname(weightsFile), { recursive: true });\n' +
' }\n' +
' fs.writeFileSync(weightsFile, JSON.stringify(data, null, 2) + \'\\n\');\n' +
' } catch {}\n' +
'}';
const helperInjection =
helperAnchor +
'\n\n' +
'/**\n' +
' * C1 原子重置助手 (patch-c1-atomic-reset, 2026-04-16)\n' +
' * 用 tmp+rename 覆盖为 DEFAULT_WEIGHTS避免 unlinkSync 与并发 readFileSync 的 TOCTOU 竞态。\n' +
' * 读者永远看到合法 JSON (旧值或新的 DEFAULT),不会 ENOENT。\n' +
' */\n' +
'function atomicResetWeightsFile(weightsFile, reason) {\n' +
' try {\n' +
' if (!fs.existsSync(path.dirname(weightsFile))) {\n' +
' fs.mkdirSync(path.dirname(weightsFile), { recursive: true });\n' +
' }\n' +
' const payload = {\n' +
' weights: { ...DEFAULT_WEIGHTS },\n' +
' resetAt: new Date().toISOString(),\n' +
' resetReason: reason || \'manual\',\n' +
' };\n' +
' const _tmp = weightsFile + \'.tmp.\' + process.pid;\n' +
' fs.writeFileSync(_tmp, JSON.stringify(payload, null, 2) + \'\\n\');\n' +
' fs.renameSync(_tmp, weightsFile);\n' +
' } catch {}\n' +
'}';
if (!before.includes(helperAnchor)) {
console.error('[patch-c1] 找不到 _directWriteFallback 锚点,源文件已变更');
process.exit(2);
}
let after = before.replace(helperAnchor, helperInjection);
// ==== 注入 2: 替换 resetOnDegeneracy 分支的 unlinkSync (原 L331) ====
const degenPattern = /process\.stderr\.write\('\[fusion-weight\] 连续 ' \+ consecutiveHits \+ ' 周期退化命中reset 到 DEFAULT_WEIGHTS\\n'\);\s*\n\s*if \(fs\.existsSync\(FUSION_WEIGHTS_FILE\)\) fs\.unlinkSync\(FUSION_WEIGHTS_FILE\);/;
const degenReplacement =
"process.stderr.write('[fusion-weight] 连续 ' + consecutiveHits + ' 周期退化命中reset 到 DEFAULT_WEIGHTS\\n');\n" +
' atomicResetWeightsFile(FUSION_WEIGHTS_FILE, \'degen-consecutive-\' + consecutiveHits);';
if (!degenPattern.test(after)) {
console.error('[patch-c1] 找不到退化分支 unlinkSync 锚点');
process.exit(3);
}
after = after.replace(degenPattern, degenReplacement);
// ==== 注入 3: 替换 resetWeights() 的 unlinkSync (原 L354) ====
const resetPattern = /function resetWeights\(\) \{\s*\n\s*if \(fs\.existsSync\(FUSION_WEIGHTS_FILE\)\) \{\s*\n\s*fs\.unlinkSync\(FUSION_WEIGHTS_FILE\);\s*\n\s*\}\s*\n\s*return \{ status: 'reset', weights: DEFAULT_WEIGHTS \};\s*\n\}/;
const resetReplacement =
'function resetWeights() {\n' +
' atomicResetWeightsFile(FUSION_WEIGHTS_FILE, \'manual-reset\');\n' +
' return { status: \'reset\', weights: DEFAULT_WEIGHTS };\n' +
'}';
if (!resetPattern.test(after)) {
console.error('[patch-c1] 找不到 resetWeights() 锚点');
process.exit(4);
}
after = after.replace(resetPattern, resetReplacement);
// ==== 校验 ====
if (after === before) {
console.error('[patch-c1] 替换无效果,中止');
process.exit(5);
}
const unlinkCount = (after.match(/fs\.unlinkSync\(FUSION_WEIGHTS_FILE\)/g) || []).length;
if (unlinkCount !== 0) {
console.error('[patch-c1] 仍有 ' + unlinkCount + ' 处 unlinkSync(FUSION_WEIGHTS_FILE) 未清理');
process.exit(6);
}
// ==== 备份并原子写回 ====
const backup = TARGET + '.bak.c1.' + Date.now();
fs.writeFileSync(backup, before);
const _tmp = TARGET + '.tmp.' + process.pid;
fs.writeFileSync(_tmp, after);
fs.renameSync(_tmp, TARGET);
console.log('[patch-c1] ✓ 补丁应用成功');
console.log('[patch-c1] 备份: ' + path.basename(backup));
console.log('[patch-c1] 变更: 新增 atomicResetWeightsFile + 替换 2 处 unlinkSync');
}
try {
main();
} catch (e) {
console.error('[patch-c1] 异常:', e.message);
process.exit(99);
}