#!/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); }