From 5a0a3d038b4990c51f153d9ee5dcb3bc0fa600e8 Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Wed, 11 Mar 2026 01:42:32 -0400 Subject: [PATCH] fsck: add cache vs log consistency check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Load store from both cache (rkyv/bincode) and raw capnp logs, then diff: missing nodes, phantom nodes, version mismatches. Auto-rebuilds cache if inconsistencies found. This would have caught the mysterious the-plan deletion — likely caused by a stale/corrupt snapshot that silently dropped the node while the capnp log still had it. Co-Authored-By: Kent Overstreet --- poc-memory/src/main.rs | 39 +++++++++++++++++++++++++++++++-- poc-memory/src/store/persist.rs | 20 +++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/poc-memory/src/main.rs b/poc-memory/src/main.rs index 7fe2e30..4e82b06 100644 --- a/poc-memory/src/main.rs +++ b/poc-memory/src/main.rs @@ -844,6 +844,41 @@ fn install_default_file(data_dir: &std::path::Path, name: &str, content: &str) - fn cmd_fsck() -> Result<(), String> { let mut store = store::Store::load()?; + // Check cache vs log consistency + let log_store = store::Store::load_from_logs()?; + let mut cache_issues = 0; + + // Nodes in logs but missing from cache + for key in log_store.nodes.keys() { + if !store.nodes.contains_key(key) { + eprintln!("CACHE MISSING: '{}' exists in capnp log but not in cache", key); + cache_issues += 1; + } + } + // Nodes in cache but not in logs (phantom nodes) + for key in store.nodes.keys() { + if !log_store.nodes.contains_key(key) { + eprintln!("CACHE PHANTOM: '{}' exists in cache but not in capnp log", key); + cache_issues += 1; + } + } + // Version mismatches + for (key, log_node) in &log_store.nodes { + if let Some(cache_node) = store.nodes.get(key) { + if cache_node.version != log_node.version { + eprintln!("CACHE STALE: '{}' cache v{} vs log v{}", + key, cache_node.version, log_node.version); + cache_issues += 1; + } + } + } + + if cache_issues > 0 { + eprintln!("{} cache inconsistencies found — rebuilding from logs", cache_issues); + store = log_store; + store.save().map_err(|e| format!("rebuild save: {}", e))?; + } + // Check node-key consistency let mut issues = 0; for (key, node) in &store.nodes { @@ -893,8 +928,8 @@ fn cmd_fsck() -> Result<(), String> { } let g = store.build_graph(); - println!("fsck: {} nodes, {} edges, {} issues, {} dangling", - store.nodes.len(), g.edge_count(), issues, dangling); + println!("fsck: {} nodes, {} edges, {} issues, {} dangling, {} cache", + store.nodes.len(), g.edge_count(), issues, dangling, cache_issues); Ok(()) } diff --git a/poc-memory/src/store/persist.rs b/poc-memory/src/store/persist.rs index a55ec4f..ca608c7 100644 --- a/poc-memory/src/store/persist.rs +++ b/poc-memory/src/store/persist.rs @@ -102,6 +102,26 @@ impl Store { Ok(store) } + /// Load store directly from capnp logs, bypassing all caches. + /// Used by fsck to verify cache consistency. + pub fn load_from_logs() -> Result { + let nodes_p = nodes_path(); + let rels_p = relations_path(); + + let mut store = Store::default(); + if nodes_p.exists() { + store.replay_nodes(&nodes_p)?; + } + if rels_p.exists() { + store.replay_relations(&rels_p)?; + } + let visits_p = visits_path(); + if visits_p.exists() { + store.replay_visits(&visits_p)?; + } + Ok(store) + } + /// Replay node log, keeping latest version per UUID. /// Tracks all UUIDs seen per key to detect duplicates. fn replay_nodes(&mut self, path: &Path) -> Result<(), String> {