bookworm-smart-assistant/scripts/patches/patch-c2-safe-append-lock.js
Bookworm Admin b7a8e29d21 release: v6.7.0 - OTA E2E test release
- VERSION file as authoritative version source
- export.mjs reads VERSION with package.json fallback
- bw-ota.ps1 DryRun mode for safe testing
- auto-setup.ps1 bumped to v3.2.0 (Phase 8 OTA)
2026-04-27 17:59:44 +08:00

139 lines
6.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
/**
* Patch C2: 强制 evolution-log / route-feedback 走 safeAppendJsonl({useLock:true})
* 并将 stop-dispatcher 的 dedup 从 Batch1 移到 Batch3 尾部,避免 dedup 后被
* 后续 Batch append 抢占覆盖丢失条目。
* 幂等: sentinel C2_SAFE_APPEND_v1 / C2_DEDUP_TAIL_v1
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..', '..');
const DISP = path.join(ROOT, 'hooks', 'stop-dispatcher.js');
const ROUTE_AUDITOR = path.join(ROOT, 'hooks', 'route-auditor.js');
const ADAPTIVE = path.join(ROOT, 'scripts', 'adaptive-disambiguator.js');
const SAFE_APPEND_REQUIRE = "require('./lib/safe-append.js')";
const SENTINEL_APPEND = 'C2_SAFE_APPEND_v1';
const SENTINEL_DEDUP = 'C2_DEDUP_TAIL_v1';
function bak(file, src, tag) {
const dir = path.join(path.dirname(file), 'archive');
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, path.basename(file) + '.bak.' + tag + '.' + Date.now()), src);
}
function write(file, content) {
const tmp = file + '.tmp.' + process.pid;
fs.writeFileSync(tmp, content);
fs.renameSync(tmp, file);
}
// --- 1) stop-dispatcher: evolution-log 写入加锁 + dedup 移尾部 ---
function patchDispatcher() {
const src = fs.readFileSync(DISP, 'utf8');
let patched = src;
if (patched.includes(SENTINEL_APPEND) && patched.includes(SENTINEL_DEDUP)) {
console.error('[patch-c2] dispatcher already applied'); return;
}
// (a) evolution-log append 改 safeAppendJsonl (CRLF-tolerant, 只匹配 appendFileSync 行)
if (!patched.includes(SENTINEL_APPEND)) {
const oldEvoLine = " fs.appendFileSync(evoLog, (needsNewline ? '\\n' : '') + JSON.stringify(entry) + '\\n');";
if (!patched.includes(oldEvoLine)) { console.error('[patch-c2] evo anchor miss'); process.exit(3); }
const newEvoLine =
" // " + SENTINEL_APPEND + ": 文件锁避免并发半写\n" +
" if (needsNewline) { try { fs.appendFileSync(evoLog, '\\n'); } catch {} }\n" +
" try {\n" +
" const { safeAppendJsonl } = require('./lib/safe-append.js');\n" +
" safeAppendJsonl(evoLog, entry, { useLock: true });\n" +
" } catch {\n" +
" try { fs.appendFileSync(evoLog, JSON.stringify(entry) + '\\n'); } catch {}\n" +
" }";
patched = patched.replace(oldEvoLine, newEvoLine);
}
// (b) dedup 从 Batch1 移除 → Batch3 尾部 (CRLF-tolerant: 不含行尾)
if (!patched.includes(SENTINEL_DEDUP)) {
const oldDedupInBatch1 = " race('dedup-errors', () => deduplicateHookErrors(), 500),";
if (!patched.includes(oldDedupInBatch1)) {
console.error('[patch-c2] dedup batch1 anchor miss'); process.exit(4);
}
patched = patched.replace(
oldDedupInBatch1,
" // " + SENTINEL_DEDUP + ": 已迁移到 Batch3 尾部 (避免被后续 append 抢占覆盖)"
);
// 将 dedup 插入到 auto-git-push 之后 (只匹配单行关键语句CRLF 安全)
const tailSingle = " }, 5000);";
// 找最后一个 ` }, 5000);` (auto-git-push 的结尾)
const lastIdx = patched.lastIndexOf(tailSingle);
if (lastIdx === -1) { console.error('[patch-c2] tail anchor miss'); process.exit(5); }
const before = patched.slice(0, lastIdx + tailSingle.length);
const after = patched.slice(lastIdx + tailSingle.length);
patched = before +
"\n // " + SENTINEL_DEDUP + ": dedup 放最后,确保所有 append 已完成" +
"\n await _budgetRace('dedup-errors', () => deduplicateHookErrors(), 500);" +
after;
}
if (patched === src) { console.error('[patch-c2] dispatcher no change'); return; }
bak(DISP, src, 'c2');
write(DISP, patched);
console.error('[patch-c2] dispatcher patched');
}
// --- 2) route-auditor: route-feedback 写入加锁 ---
function patchRouteAuditor() {
if (!fs.existsSync(ROUTE_AUDITOR)) return;
const src = fs.readFileSync(ROUTE_AUDITOR, 'utf8');
if (src.includes(SENTINEL_APPEND)) { console.error('[patch-c2] route-auditor already applied'); return; }
// 只处理 route-feedback.jsonl 路径;保留其他 log
const old = " fs.appendFileSync(logFile, JSON.stringify(entry) + '\\n');";
if (!src.includes(old)) { console.error('[patch-c2] route-auditor anchor miss, skip'); return; }
const replacement =
" // " + SENTINEL_APPEND + ": 使用 safeAppendJsonl 文件锁防并发损坏\n" +
" try {\n" +
" const { safeAppendJsonl } = require('./lib/safe-append.js');\n" +
" safeAppendJsonl(logFile, entry, { useLock: true });\n" +
" } catch {\n" +
" try { fs.appendFileSync(logFile, JSON.stringify(entry) + '\\n'); } catch {}\n" +
" }";
const patched = src.replace(old, replacement);
if (patched === src) return;
bak(ROUTE_AUDITOR, src, 'c2');
write(ROUTE_AUDITOR, patched);
console.error('[patch-c2] route-auditor patched');
}
// --- 3) adaptive-disambiguator: evolution-log append 加锁 ---
function patchAdaptive() {
if (!fs.existsSync(ADAPTIVE)) return;
const src = fs.readFileSync(ADAPTIVE, 'utf8');
if (src.includes(SENTINEL_APPEND)) { console.error('[patch-c2] adaptive already applied'); return; }
const old = " fs.appendFileSync(evolutionLog, JSON.stringify(logEntry) + '\\n');";
if (!src.includes(old)) { console.error('[patch-c2] adaptive anchor miss, skip'); return; }
const replacement =
" // " + SENTINEL_APPEND + ": 与 stop-dispatcher consistency-sentinel 共享 evolution-log必须加锁\n" +
" try {\n" +
" const { safeAppendJsonl } = require(require('path').join(__dirname, '..', 'hooks', 'lib', 'safe-append.js'));\n" +
" safeAppendJsonl(evolutionLog, logEntry, { useLock: true });\n" +
" } catch {\n" +
" try { fs.appendFileSync(evolutionLog, JSON.stringify(logEntry) + '\\n'); } catch {}\n" +
" }";
const patched = src.replace(old, replacement);
if (patched === src) return;
bak(ADAPTIVE, src, 'c2');
write(ADAPTIVE, patched);
console.error('[patch-c2] adaptive patched');
}
patchDispatcher();
patchRouteAuditor();
patchAdaptive();