bookworm-smart-assistant/scripts/patches/verify-jsonl-chain.js

103 lines
3.1 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* verify-jsonl-chain.js JSONL 完整性链验证
*
* 用法:
* node scripts/patches/verify-jsonl-chain.js # 验证所有目标
* node scripts/patches/verify-jsonl-chain.js --quiet # 仅退出码
* node scripts/patches/verify-jsonl-chain.js --rebuild # 重建 baseline (信任当前)
*
* 退出码:
* 0 = 全部 OK
* 1 = 至少一个 drift 检测到warn 模式仅记录不阻断业务
* 2 = baseline 缺失首次需运行 patch-p1-2-jsonl-baseline-init.js
*
* warn 模式: 所有 drift 写入 evolution-log.jsonl 作为 jsonl-hmac.violation 事件
*/
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..', '..');
const TARGETS = [
{ jsonl: path.join(ROOT, 'evolution-log.jsonl'), label: 'evolution-log' },
{ jsonl: path.join(ROOT, 'debug', 'route-feedback.jsonl'), label: 'route-feedback' },
];
const QUIET = process.argv.includes('--quiet');
const REBUILD = process.argv.includes('--rebuild');
let mod;
try {
mod = require(path.join(ROOT, 'hooks', 'lib', 'jsonl-hmac.js'));
} catch (e) {
process.stderr.write('[FAIL] jsonl-hmac.js lib not deployed\n');
process.exit(2);
}
function log(s) { if (!QUIET) process.stdout.write(s + '\n'); }
function err(s) { process.stderr.write(s + '\n'); }
let exitCode = 0;
const violations = [];
for (const { jsonl, label } of TARGETS) {
const baselinePath = jsonl + '.hmac-baseline.json';
if (!fs.existsSync(jsonl)) {
log('[SKIP] ' + label + ': file not found');
continue;
}
if (REBUILD) {
const r = mod.writeBaseline(jsonl, baselinePath);
if (r.ok) {
log('[REBUILD] ' + label + ' baseline updated lines=' + r.baseline.lineCount);
} else {
err('[REBUILD-FAIL] ' + label + ': ' + r.error);
exitCode = 1;
}
continue;
}
if (!fs.existsSync(baselinePath)) {
err('[NO-BASELINE] ' + label + ': run patch-p1-2-jsonl-baseline-init.js first');
if (exitCode === 0) exitCode = 2;
continue;
}
const v = mod.verifyChain(jsonl, baselinePath);
if (v.ok) {
log('[OK] ' + label + ' baseline=' + v.baselineLineCount + ' current=' + v.currentLineCount + ' appended=' + v.appended);
} else {
err('[DRIFT] ' + label + ' drift=' + v.drift + (v.atLine ? ' atLine=' + v.atLine : ''));
violations.push({ label: label, drift: v.drift, detail: v });
if (exitCode === 0) exitCode = 1;
}
}
// warn 模式: 把 violation 记录到 evolution-log
if (violations.length > 0) {
try {
const evtFile = path.join(ROOT, 'evolution-log.jsonl');
for (const v of violations) {
const entry = {
ts: new Date().toISOString(),
type: 'jsonl-hmac.violation',
target: v.label,
drift: v.drift,
detail: v.detail,
mode: 'warn',
};
// 追加 (不依赖 safe-append避免循环)
fs.appendFileSync(evtFile, JSON.stringify(entry) + '\n');
}
err('[WARN-LOGGED] ' + violations.length + ' violations recorded to evolution-log.jsonl');
} catch (e) {
err('[WARN-LOG-FAIL] ' + e.message);
}
}
process.exit(exitCode);