UNPKG

chittycan

Version:

Your completely autonomous network that grows with you - DNA ownership platform with encrypted vaults, PDX portability, and ChittyFoundation governance

248 lines (212 loc) 6.25 kB
import fs from 'fs-extra'; import path from 'path'; import crypto from 'crypto'; import { CONFIG_DIR } from './config.js'; const AUDIT_DIR = path.join(CONFIG_DIR, 'audit'); export type AuditEvent = | 'pattern_learned' | 'pattern_invoked' | 'pattern_evolved' | 'dna_exported' | 'dna_imported' | 'dna_revoked'; export interface AuditEntry { timestamp: string; event: AuditEvent; pattern_hash?: string; confidence?: number; outcome?: 'success' | 'failure'; duration_ms?: number; metadata?: Record<string, any>; } export class AuditLogger { private static instance: AuditLogger; static getInstance(): AuditLogger { if (!AuditLogger.instance) { AuditLogger.instance = new AuditLogger(); fs.ensureDirSync(AUDIT_DIR); } return AuditLogger.instance; } /** * Log a learning event (privacy-preserving) */ async log(entry: AuditEntry): Promise<void> { const logPath = path.join(AUDIT_DIR, 'learning-events.jsonl'); const logEntry = { ...entry, timestamp: new Date().toISOString() }; // NEVER log raw content—only hashes fs.appendFileSync(logPath, JSON.stringify(logEntry) + '\n'); } /** * Hash sensitive data before logging */ hash(data: string): string { return crypto.createHash('sha256').update(data).digest('hex'); } /** * Log pattern learning */ async logPatternLearned(pattern: string, confidence: number): Promise<void> { await this.log({ timestamp: new Date().toISOString(), event: 'pattern_learned', pattern_hash: this.hash(pattern), confidence }); } /** * Log pattern invocation */ async logPatternInvoked( pattern: string, outcome: 'success' | 'failure', duration_ms: number ): Promise<void> { await this.log({ timestamp: new Date().toISOString(), event: 'pattern_invoked', pattern_hash: this.hash(pattern), outcome, duration_ms }); } /** * Log pattern evolution */ async logPatternEvolved(pattern: string, oldConfidence: number, newConfidence: number): Promise<void> { await this.log({ timestamp: new Date().toISOString(), event: 'pattern_evolved', pattern_hash: this.hash(pattern), metadata: { old_confidence: oldConfidence, new_confidence: newConfidence } }); } /** * Log DNA export/import */ async logPortability( event: 'dna_exported' | 'dna_imported', metadata: Record<string, any> ): Promise<void> { await this.log({ timestamp: new Date().toISOString(), event, metadata }); } /** * Log DNA revocation */ async logRevocation(): Promise<void> { await this.log({ timestamp: new Date().toISOString(), event: 'dna_revoked' }); } /** * Get all audit entries */ async getEntries(filter?: { event?: AuditEvent; since?: Date }): Promise<AuditEntry[]> { const logPath = path.join(AUDIT_DIR, 'learning-events.jsonl'); if (!fs.existsSync(logPath)) { return []; } const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n'); let entries = lines .filter((line: string) => line.trim()) .map((line: string) => JSON.parse(line) as AuditEntry); if (filter) { if (filter.event) { entries = entries.filter((e: AuditEntry) => e.event === filter.event); } if (filter.since) { entries = entries.filter((e: AuditEntry) => new Date(e.timestamp) >= filter.since!); } } return entries; } /** * Get audit statistics */ async getStats(): Promise<{ total_events: number; patterns_learned: number; patterns_invoked: number; patterns_evolved: number; exports: number; imports: number; revocations: number; success_rate: number; }> { const entries = await this.getEntries(); const invocations = entries.filter(e => e.event === 'pattern_invoked'); const successes = invocations.filter(e => e.outcome === 'success'); return { total_events: entries.length, patterns_learned: entries.filter(e => e.event === 'pattern_learned').length, patterns_invoked: invocations.length, patterns_evolved: entries.filter(e => e.event === 'pattern_evolved').length, exports: entries.filter(e => e.event === 'dna_exported').length, imports: entries.filter(e => e.event === 'dna_imported').length, revocations: entries.filter(e => e.event === 'dna_revoked').length, success_rate: invocations.length > 0 ? successes.length / invocations.length : 1.0 }; } /** * Verify audit integrity (check for tampering) */ async verifyIntegrity(): Promise<{ valid: boolean; errors: string[] }> { const logPath = path.join(AUDIT_DIR, 'learning-events.jsonl'); if (!fs.existsSync(logPath)) { return { valid: true, errors: [] }; } const errors: string[] = []; const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n'); let lineNumber = 0; for (const line of lines) { lineNumber++; if (!line.trim()) { continue; } try { const entry = JSON.parse(line); // Verify required fields if (!entry.timestamp) { errors.push(`Line ${lineNumber}: Missing timestamp`); } if (!entry.event) { errors.push(`Line ${lineNumber}: Missing event type`); } // Verify timestamp is valid ISO 8601 if (entry.timestamp && isNaN(Date.parse(entry.timestamp))) { errors.push(`Line ${lineNumber}: Invalid timestamp format`); } // Verify no raw content (only hashes) if (entry.pattern && !entry.pattern_hash) { errors.push(`Line ${lineNumber}: Raw pattern content detected (security violation)`); } } catch (e) { errors.push(`Line ${lineNumber}: Invalid JSON`); } } return { valid: errors.length === 0, errors }; } /** * Clear audit logs (for testing or ethical exit) */ async clear(): Promise<void> { const logPath = path.join(AUDIT_DIR, 'learning-events.jsonl'); if (fs.existsSync(logPath)) { fs.removeSync(logPath); } } }