store: strip .md suffix from all keys

Keys were a vestige of the file-based era. resolve_key() added .md
to lookups while upsert() used bare keys, creating phantom duplicate
nodes (the instructions bug: writes went to "instructions", reads
found "instructions.md").

- Remove .md normalization from resolve_key, strip instead
- Update all hardcoded key patterns (journal.md# → journal#, etc)
- Add strip_md_keys() migration to fsck: renames nodes and relations
- Add broken link detection to health report
- Delete redirect table (no longer needed)
- Update config defaults and config.jsonl

Migration: run `poc-memory fsck` to rename existing keys.

Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-03-08 19:41:26 -04:00
parent 77fc533631
commit 46f8fe662e
12 changed files with 289 additions and 132 deletions

View file

@ -606,6 +606,25 @@ pub fn health_report(graph: &Graph, store: &Store) -> String {
.filter(|k| graph.clustering_coefficient(k) < 0.1)
.count();
// Orphan edges: relations referencing non-existent nodes
let mut orphan_edges = 0usize;
let mut orphan_sources: Vec<String> = Vec::new();
let mut orphan_targets: Vec<String> = Vec::new();
for rel in &store.relations {
if rel.deleted { continue; }
let s_missing = !store.nodes.contains_key(&rel.source_key);
let t_missing = !store.nodes.contains_key(&rel.target_key);
if s_missing || t_missing {
orphan_edges += 1;
if s_missing && !orphan_sources.contains(&rel.source_key) {
orphan_sources.push(rel.source_key.clone());
}
if t_missing && !orphan_targets.contains(&rel.target_key) {
orphan_targets.push(rel.target_key.clone());
}
}
}
// Category breakdown
let cats = store.category_counts();
@ -658,6 +677,25 @@ Categories: core={core} tech={tech} gen={gen} obs={obs} task={task}",
task = cats.get("task").unwrap_or(&0),
);
// Orphan edges
if orphan_edges == 0 {
report.push_str("\n\nBroken links: 0");
} else {
let mut all_missing: Vec<String> = orphan_sources;
all_missing.extend(orphan_targets);
all_missing.sort();
all_missing.dedup();
report.push_str(&format!(
"\n\nBroken links: {} edges reference {} missing nodes",
orphan_edges, all_missing.len()));
for key in all_missing.iter().take(10) {
report.push_str(&format!("\n - {}", key));
}
if all_missing.len() > 10 {
report.push_str(&format!("\n ... and {} more", all_missing.len() - 10));
}
}
// Show history trend if we have enough data points
if history.len() >= 3 {
report.push_str("\n\nMetrics history (last 5):\n");