bookworm-smart-assistant/scripts/patches/verify-jsonl-chain.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

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