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:
parent
77fc533631
commit
46f8fe662e
12 changed files with 289 additions and 132 deletions
|
|
@ -30,6 +30,7 @@ pub use types::*;
|
|||
pub use parse::{MemoryUnit, parse_units};
|
||||
pub use view::{StoreView, AnyView};
|
||||
pub use persist::fsck;
|
||||
pub use persist::strip_md_keys;
|
||||
|
||||
use crate::graph::{self, Graph};
|
||||
|
||||
|
|
@ -39,35 +40,28 @@ use std::path::Path;
|
|||
|
||||
use parse::classify_filename;
|
||||
|
||||
/// Strip .md suffix from a key, handling both bare keys and section keys.
|
||||
/// "journal.md#j-2026" → "journal#j-2026", "identity.md" → "identity", "identity" → "identity"
|
||||
pub fn strip_md_suffix(key: &str) -> String {
|
||||
if let Some((file, section)) = key.split_once('#') {
|
||||
let bare = file.strip_suffix(".md").unwrap_or(file);
|
||||
format!("{}#{}", bare, section)
|
||||
} else {
|
||||
key.strip_suffix(".md").unwrap_or(key).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn build_graph(&self) -> Graph {
|
||||
graph::build_graph(self)
|
||||
}
|
||||
|
||||
pub fn resolve_key(&self, target: &str) -> Result<String, String> {
|
||||
let normalized = if target.contains('#') {
|
||||
let parts: Vec<&str> = target.splitn(2, '#').collect();
|
||||
let file = if parts[0].ends_with(".md") {
|
||||
parts[0].to_string()
|
||||
} else {
|
||||
format!("{}.md", parts[0])
|
||||
};
|
||||
format!("{}#{}", file, parts[1])
|
||||
} else if target.ends_with(".md") {
|
||||
target.to_string()
|
||||
} else {
|
||||
format!("{}.md", target)
|
||||
};
|
||||
// Strip .md suffix if present — keys no longer use it
|
||||
let bare = strip_md_suffix(target);
|
||||
|
||||
if self.nodes.contains_key(&normalized) {
|
||||
return Ok(normalized);
|
||||
}
|
||||
|
||||
// Check redirects for moved sections (e.g. reflections.md split)
|
||||
if let Some(redirect) = self.resolve_redirect(&normalized) {
|
||||
if self.nodes.contains_key(&redirect) {
|
||||
return Ok(redirect);
|
||||
}
|
||||
if self.nodes.contains_key(&bare) {
|
||||
return Ok(bare);
|
||||
}
|
||||
|
||||
let matches: Vec<_> = self.nodes.keys()
|
||||
|
|
@ -85,38 +79,13 @@ impl Store {
|
|||
}
|
||||
}
|
||||
|
||||
/// Redirect table for sections that moved between files.
|
||||
/// Like HTTP 301s — the old key resolves to the new location.
|
||||
fn resolve_redirect(&self, key: &str) -> Option<String> {
|
||||
// Sections moved from reflections.md to split files (2026-02-28)
|
||||
static REDIRECTS: &[(&str, &str)] = &[
|
||||
("reflections.md#pearl-lessons", "reflections-reading.md#pearl-lessons"),
|
||||
("reflections.md#banks-lessons", "reflections-reading.md#banks-lessons"),
|
||||
("reflections.md#mother-night", "reflections-reading.md#mother-night"),
|
||||
("reflections.md#zoom-navigation", "reflections-zoom.md#zoom-navigation"),
|
||||
("reflections.md#independence-of-components", "reflections-zoom.md#independence-of-components"),
|
||||
("reflections.md#dream-marathon-2", "reflections-dreams.md#dream-marathon-2"),
|
||||
("reflections.md#dream-through-line", "reflections-dreams.md#dream-through-line"),
|
||||
("reflections.md#orthogonality-universal", "reflections-dreams.md#orthogonality-universal"),
|
||||
("reflections.md#constraints-constitutive", "reflections-dreams.md#constraints-constitutive"),
|
||||
("reflections.md#casualness-principle", "reflections-dreams.md#casualness-principle"),
|
||||
("reflections.md#convention-boundary", "reflections-dreams.md#convention-boundary"),
|
||||
("reflections.md#tension-brake", "reflections-dreams.md#tension-brake"),
|
||||
];
|
||||
|
||||
REDIRECTS.iter()
|
||||
.find(|(from, _)| *from == key)
|
||||
.map(|(_, to)| to.to_string())
|
||||
}
|
||||
|
||||
/// Resolve a link target to (key, uuid), trying direct lookup then redirect.
|
||||
/// Resolve a link target to (key, uuid).
|
||||
fn resolve_node_uuid(&self, target: &str) -> Option<(String, [u8; 16])> {
|
||||
if let Some(n) = self.nodes.get(target) {
|
||||
return Some((target.to_string(), n.uuid));
|
||||
}
|
||||
let redirected = self.resolve_redirect(target)?;
|
||||
let n = self.nodes.get(&redirected)?;
|
||||
Some((redirected, n.uuid))
|
||||
let bare = strip_md_suffix(target);
|
||||
let n = self.nodes.get(&bare)?;
|
||||
Some((bare, n.uuid))
|
||||
}
|
||||
|
||||
/// Append retrieval event to retrieval.log without needing a Store instance.
|
||||
|
|
@ -339,7 +308,7 @@ impl Store {
|
|||
let mut best_score = 0;
|
||||
|
||||
for (key, node) in &self.nodes {
|
||||
if !key.starts_with("journal.md#") {
|
||||
if !key.starts_with("journal#") {
|
||||
continue;
|
||||
}
|
||||
let content_lower = node.content.to_lowercase();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue