103 lines
3.1 KiB
JavaScript
103 lines
3.1 KiB
JavaScript
|
|
#!/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);
|