fix: P2-24 memory dedup + P2-25 audit logging + P3-02 whiteboard unification
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
P2-24: Add content_hash column to memories table with index. Before INSERT, check for existing entry with same normalized content hash within agent scope; merge importance and bump access_count. P2-25: Add hand_executed/hand_approved/hand_denied/skill_executed event types to security-audit.ts. Insert audit logging calls in kernel-hands.ts triggerHand/approveHand and kernel-skills.ts executeSkill execution paths. P3-02: SceneRenderer now imports WhiteboardCanvas component instead of inline SVG rendering, gaining chart/latex support. Deleted 27 lines of duplicated renderWhiteboardItem code. Update DEFECT_LIST.md: P1-01 ✅ (Fantoccini confirmed), P3-02 ✅, add P2-24/P2-25 entries. Active count: 48→50 fixed, 3→1 remaining.
This commit is contained in:
@@ -163,6 +163,14 @@ impl SqliteStorage {
|
||||
.execute(&self.pool)
|
||||
.await;
|
||||
|
||||
// P2-24: Migration — content fingerprint for deduplication
|
||||
let _ = sqlx::query("ALTER TABLE memories ADD COLUMN content_hash TEXT")
|
||||
.execute(&self.pool)
|
||||
.await;
|
||||
let _ = sqlx::query("CREATE INDEX IF NOT EXISTS idx_content_hash ON memories(content_hash)")
|
||||
.execute(&self.pool)
|
||||
.await;
|
||||
|
||||
// Create metadata table
|
||||
sqlx::query(
|
||||
r#"
|
||||
@@ -426,12 +434,54 @@ impl VikingStorage for SqliteStorage {
|
||||
let last_accessed = entry.last_accessed.to_rfc3339();
|
||||
let memory_type = entry.memory_type.to_string();
|
||||
|
||||
// P2-24: Content-hash deduplication
|
||||
let normalized_content = entry.content.trim().to_lowercase();
|
||||
let content_hash = {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
normalized_content.hash(&mut hasher);
|
||||
format!("{:016x}", hasher.finish())
|
||||
};
|
||||
|
||||
// Check for existing entry with the same content hash (within same agent scope)
|
||||
let agent_scope = entry.uri.split('/').nth(2).unwrap_or("");
|
||||
let existing: Option<(String, i32, i32)> = sqlx::query_as::<_, (String, i32, i32)>(
|
||||
"SELECT uri, importance, access_count FROM memories WHERE content_hash = ? AND uri LIKE ? LIMIT 1"
|
||||
)
|
||||
.bind(&content_hash)
|
||||
.bind(format!("agent://{agent_scope}/%"))
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(|e| ZclawError::StorageError(format!("Dedup check failed: {}", e)))?;
|
||||
|
||||
if let Some((existing_uri, existing_importance, existing_access)) = existing {
|
||||
// Merge: keep higher importance, bump access count, update last_accessed
|
||||
let merged_importance = existing_importance.max(entry.importance as i32);
|
||||
let merged_access = existing_access + 1;
|
||||
sqlx::query(
|
||||
"UPDATE memories SET importance = ?, access_count = ?, last_accessed = ? WHERE uri = ?"
|
||||
)
|
||||
.bind(merged_importance)
|
||||
.bind(merged_access)
|
||||
.bind(&last_accessed)
|
||||
.bind(&existing_uri)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| ZclawError::StorageError(format!("Dedup merge failed: {}", e)))?;
|
||||
|
||||
tracing::debug!(
|
||||
"[SqliteStorage] Dedup: merged '{}' into existing '{}' (importance={}, access_count={})",
|
||||
entry.uri, existing_uri, merged_importance, merged_access
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Insert into main table
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT OR REPLACE INTO memories
|
||||
(uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed, overview, abstract_summary)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
(uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed, overview, abstract_summary, content_hash)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(&entry.uri)
|
||||
@@ -444,6 +494,7 @@ impl VikingStorage for SqliteStorage {
|
||||
.bind(&last_accessed)
|
||||
.bind(&entry.overview)
|
||||
.bind(&entry.abstract_summary)
|
||||
.bind(&content_hash)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| ZclawError::StorageError(format!("Failed to store memory: {}", e)))?;
|
||||
|
||||
Reference in New Issue
Block a user