File size: 2,783 Bytes
662ceed | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import { sha256, computeEntryHash } from './hash.js';
import type { WormEntry, Verdict, AgentName } from './types.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const WORM_DIR = path.join(__dirname, '..', '..', 'worm');
const WORM_FILE = path.join(WORM_DIR, 'holy_agents.jsonl');
function ensureDir(): void {
if (!fs.existsSync(WORM_DIR)) fs.mkdirSync(WORM_DIR, { recursive: true });
}
export function readEntries(): WormEntry[] {
ensureDir();
if (!fs.existsSync(WORM_FILE)) return [];
const content = fs.readFileSync(WORM_FILE, 'utf-8');
const lines = content.split('\n').filter(Boolean);
const entries: WormEntry[] = [];
for (let i = 0; i < lines.length; i++) {
try {
entries.push(JSON.parse(lines[i]));
} catch {
console.error(`WORM: corrupt entry at line ${i + 1}, skipping`);
}
}
return entries;
}
function appendLine(line: string): void {
ensureDir();
fs.appendFileSync(WORM_FILE, line + '\n', 'utf-8');
}
export function appendEntry(event_id: string, agent: AgentName, action: string, verdict: Verdict, citations: string[]): WormEntry {
const entries = readEntries();
const seq = entries.length + 1;
const prevHash = entries.length === 0 ? 'GENESIS' : entries[entries.length - 1].hash;
const timestamp = new Date().toISOString();
const entry: WormEntry = {
seq,
prev_hash: prevHash,
hash: '',
timestamp,
event_id,
agent,
action,
verdict,
citations
};
entry.hash = computeEntryHash({ seq, prev_hash: prevHash, event_id, agent, action, verdict, timestamp });
appendLine(JSON.stringify(entry));
return entry;
}
export function getEntries(): WormEntry[] {
return readEntries();
}
export function verifyChain(): { valid: boolean; errors: string[] } {
const entries = readEntries();
const errors: string[] = [];
if (entries.length === 0) return { valid: true, errors: [] };
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (entry.seq !== i + 1) errors.push(`entry ${i}: expected seq ${i + 1}, got ${entry.seq}`);
const expectedPrev = i === 0 ? 'GENESIS' : entries[i - 1].hash;
if (entry.prev_hash !== expectedPrev) errors.push(`entry ${i}: prev_hash mismatch`);
const recomputed = computeEntryHash({
seq: entry.seq, prev_hash: entry.prev_hash, event_id: entry.event_id,
agent: entry.agent, action: entry.action, verdict: entry.verdict, timestamp: entry.timestamp
});
if (recomputed !== entry.hash) errors.push(`entry ${i}: hash mismatch`);
}
return { valid: errors.length === 0, errors };
}
|