snapkitty
agents
python
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 };
}