claude-desktop/src-tauri/src/audit.rs
Eddy f51241efa6 Phase 10 Sprach-Interface + Phase 9 Nacharbeiten
Voice (Phase 10):
- voice.rs: OpenAI Whisper (STT) + TTS Backend
- ChatPanel: Mikrofon-Button, VAD (Pause 1.5s), Live-Pegel
- SettingsPanel: OpenAI-Key Konfiguration

Phase 9 Nacharbeiten:
- Auto-Extract vor Compacting (Entscheidungen/TODOs/Insights)
- get_tool_hints() - relevante KB-Eintraege bei Tool-Start
- activeKnowledgeHints Store, Anzeige im KnowledgePanel

Tech-Schulden:
- Dead-Code in memory.rs entfernt (MemorySystem struct)
- cargo-check Warnings behoben

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:24:28 +02:00

183 lines
5.3 KiB
Rust

// Claude Desktop — Änderungs-Log (Audit Trail)
// Protokolliert alle Änderungen an Einstellungen, Guard-Rails, Hooks, Skills, etc.
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Manager};
use crate::db;
/// Kategorie der Änderung
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuditCategory {
GuardRail, // Freigabe-Regeln
Pattern, // Vorgehensweisen
Hook, // Claude Hooks
Skill, // Claude Skills
Setting, // App-Einstellungen
MCP, // MCP-Server Konfiguration
Memory, // Gedächtnis-Einträge
}
/// Art der Aktion
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuditAction {
Create,
Update,
Delete,
Enable,
Disable,
}
/// Ein Audit-Eintrag
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEntry {
pub id: String,
pub timestamp: String,
pub category: AuditCategory,
pub action: AuditAction,
pub item_id: String,
pub item_name: String,
pub old_value: Option<serde_json::Value>,
pub new_value: Option<serde_json::Value>,
pub reason: Option<String>,
pub auto_corrected: bool,
pub session_id: Option<String>,
}
/// Audit-Log Manager (für zukünftige In-Memory-Nutzung)
#[allow(dead_code)]
#[derive(Debug, Default)]
pub struct AuditLog {
entries: Vec<AuditEntry>,
}
#[allow(dead_code)]
impl AuditLog {
pub fn new() -> Self {
Self { entries: vec![] }
}
/// Fügt einen Eintrag hinzu
pub fn log(&mut self, entry: AuditEntry) {
println!(
"📋 Audit: {:?} {:?} - {} ({})",
entry.action, entry.category, entry.item_name,
entry.reason.as_deref().unwrap_or("keine Begründung")
);
self.entries.push(entry);
}
/// Holt die letzten N Einträge
pub fn recent(&self, limit: usize) -> Vec<&AuditEntry> {
self.entries.iter().rev().take(limit).collect()
}
/// Holt Einträge nach Kategorie
pub fn by_category(&self, category: &AuditCategory) -> Vec<&AuditEntry> {
self.entries
.iter()
.filter(|e| std::mem::discriminant(&e.category) == std::mem::discriminant(category))
.collect()
}
/// Holt auto-korrigierte Einträge
pub fn auto_corrected(&self) -> Vec<&AuditEntry> {
self.entries.iter().filter(|e| e.auto_corrected).collect()
}
/// Statistiken
pub fn stats(&self) -> AuditStats {
AuditStats {
total: self.entries.len(),
auto_corrected: self.entries.iter().filter(|e| e.auto_corrected).count(),
today: self.entries.iter().filter(|e| {
// Vereinfachte Prüfung - in echt: Datum vergleichen
e.timestamp.starts_with(&chrono::Local::now().format("%Y-%m-%d").to_string())
}).count(),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AuditStats {
pub total: usize,
pub auto_corrected: usize,
pub today: usize,
}
// Tauri-Commands
/// Holt die letzten Audit-Einträge
#[tauri::command]
pub async fn get_audit_log(app: AppHandle, limit: Option<usize>) -> Result<Vec<AuditEntry>, String> {
let limit = limit.unwrap_or(50);
let state = app.state::<Arc<Mutex<db::Database>>>();
let db_lock = state.lock().unwrap();
db_lock.load_audit_log(limit).map_err(|e| e.to_string())
}
/// Fügt einen Audit-Eintrag hinzu
#[tauri::command]
pub async fn add_audit_entry(
app: AppHandle,
category: String,
action: String,
item_id: String,
item_name: String,
old_value: Option<serde_json::Value>,
new_value: Option<serde_json::Value>,
reason: Option<String>,
auto_corrected: bool,
) -> Result<(), String> {
let category = match category.as_str() {
"guard_rail" => AuditCategory::GuardRail,
"pattern" => AuditCategory::Pattern,
"hook" => AuditCategory::Hook,
"skill" => AuditCategory::Skill,
"setting" => AuditCategory::Setting,
"mcp" => AuditCategory::MCP,
"memory" => AuditCategory::Memory,
_ => return Err(format!("Unbekannte Kategorie: {}", category)),
};
let action = match action.as_str() {
"create" => AuditAction::Create,
"update" => AuditAction::Update,
"delete" => AuditAction::Delete,
"enable" => AuditAction::Enable,
"disable" => AuditAction::Disable,
_ => return Err(format!("Unbekannte Aktion: {}", action)),
};
let entry = AuditEntry {
id: uuid::Uuid::new_v4().to_string(),
timestamp: chrono::Local::now().to_rfc3339(),
category,
action,
item_id,
item_name,
old_value,
new_value,
reason,
auto_corrected,
session_id: None,
};
// In SQLite speichern
let state = app.state::<Arc<Mutex<db::Database>>>();
let db_lock = state.lock().unwrap();
db_lock.save_audit_entry(&entry).map_err(|e| e.to_string())?;
println!("📋 Audit: {:?} {:?} - {}", entry.action, entry.category, entry.item_name);
Ok(())
}
/// Holt Audit-Statistiken
#[tauri::command]
pub async fn get_audit_stats(app: AppHandle) -> Result<AuditStats, String> {
let state = app.state::<Arc<Mutex<db::Database>>>();
let db_lock = state.lock().unwrap();
db_lock.audit_stats().map_err(|e| e.to_string())
}