From 465c03aa11596a22a3bfea17c79f21fcad5452ef Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Wed, 18 Mar 2026 22:57:12 -0400 Subject: [PATCH] Add find-deleted diagnostic tool Lists nodes that are currently deleted with no subsequent live version. Useful for diagnosing accidental deletions in the memory store. Co-Authored-By: Claude Opus 4.6 (1M context) --- poc-memory/src/bin/find-deleted.rs | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 poc-memory/src/bin/find-deleted.rs diff --git a/poc-memory/src/bin/find-deleted.rs b/poc-memory/src/bin/find-deleted.rs new file mode 100644 index 0000000..d83d9d7 --- /dev/null +++ b/poc-memory/src/bin/find-deleted.rs @@ -0,0 +1,56 @@ +// Find all deleted nodes that have no subsequent non-deleted version +// (i.e., nodes that are currently dead). +// +// Also checks: is there a live node under the same key with a different UUID? +// If not, the deletion was terminal — the node is gone. + +use std::collections::HashMap; +use std::io::BufReader; +use std::fs; +use capnp::{message, serialize}; +use poc_memory::memory_capnp; +use poc_memory::store::Node; + +fn main() { + let path = std::env::args().nth(1) + .unwrap_or_else(|| { + let dir = poc_memory::store::nodes_path(); + dir.to_string_lossy().to_string() + }); + + let file = fs::File::open(&path).unwrap(); + let mut reader = BufReader::new(file); + + // Collect ALL entries, tracking latest version per key + let mut latest_by_key: HashMap = HashMap::new(); + let mut all_entries = 0u64; + + while let Ok(msg) = serialize::read_message(&mut reader, message::ReaderOptions::new()) { + let log = msg.get_root::().unwrap(); + for node_reader in log.get_nodes().unwrap() { + all_entries += 1; + let node = Node::from_capnp_migrate(node_reader).unwrap(); + let dominated = latest_by_key.get(&node.key) + .map(|n| node.version >= n.version) + .unwrap_or(true); + if dominated { + latest_by_key.insert(node.key.clone(), node); + } + } + } + + // Find keys where the latest version is deleted + let mut dead: Vec<&Node> = latest_by_key.values() + .filter(|n| n.deleted) + .collect(); + dead.sort_by(|a, b| a.key.cmp(&b.key)); + + eprintln!("Scanned {} entries, {} unique keys", all_entries, latest_by_key.len()); + eprintln!("{} live nodes, {} deleted (terminal tombstones)\n", + latest_by_key.len() - dead.len(), dead.len()); + + for node in &dead { + println!("{:<60} v{:<4} {}b prov={}", + node.key, node.version, node.content.len(), node.provenance); + } +}