store: protected nodes, explicit provenance in mutations

- Add protected_nodes config list - blocks delete/rename of core nodes
- Remove current_provenance() env var lookup, pass provenance explicitly
- delete_node, rename_node, set_link_strength now take provenance param
- Fix new_relation calls in admin.rs to pass "system" provenance

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-15 01:40:18 -04:00
commit 6ec7fcb777
4 changed files with 159 additions and 38 deletions

View file

@ -91,10 +91,10 @@ pub fn memory_links(store: &Store, _provenance: &str, key: &str) -> Result<Vec<L
Ok(links)
}
pub fn memory_link_set(store: &Store, _provenance: &str, source: &str, target: &str, strength: f32) -> Result<String> {
pub fn memory_link_set(store: &Store, provenance: &str, source: &str, target: &str, strength: f32) -> Result<String> {
let s = store.resolve_key(source).map_err(|e| anyhow::anyhow!("{}", e))?;
let t = store.resolve_key(target).map_err(|e| anyhow::anyhow!("{}", e))?;
let old = store.set_link_strength(&s, &t, strength).map_err(|e| anyhow::anyhow!("{}", e))?;
let old = store.set_link_strength(&s, &t, strength, provenance).map_err(|e| anyhow::anyhow!("{}", e))?;
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(format!("{}{} strength {:.2}{:.2}", s, t, old, strength))
}
@ -107,13 +107,19 @@ pub fn memory_link_add(store: &Store, provenance: &str, source: &str, target: &s
Ok(format!("linked {}{} (strength={:.2})", s, t, strength))
}
pub fn memory_delete(store: &Store, _provenance: &str, key: &str) -> Result<String> {
pub fn memory_delete(store: &Store, provenance: &str, key: &str) -> Result<String> {
let resolved = store.resolve_key(key).map_err(|e| anyhow::anyhow!("{}", e))?;
store.delete_node(&resolved).map_err(|e| anyhow::anyhow!("{}", e))?;
store.delete_node(&resolved, provenance).map_err(|e| anyhow::anyhow!("{}", e))?;
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(format!("deleted {}", resolved))
}
pub fn memory_restore(store: &Store, provenance: &str, key: &str) -> Result<String> {
let result = store.restore_node(key, provenance).map_err(|e| anyhow::anyhow!("{}", e))?;
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(result)
}
pub fn memory_history(store: &Store, _provenance: &str, key: &str, full: Option<bool>) -> Result<String> {
let key = store.resolve_key(key).unwrap_or_else(|_| key.to_string());
let full = full.unwrap_or(false);
@ -171,9 +177,9 @@ pub fn memory_weight_set(store: &Store, _provenance: &str, key: &str, weight: f3
Ok(format!("weight {} {:.2}{:.2}", resolved, old, new))
}
pub fn memory_rename(store: &Store, _provenance: &str, old_key: &str, new_key: &str) -> Result<String> {
pub fn memory_rename(store: &Store, provenance: &str, old_key: &str, new_key: &str) -> Result<String> {
let resolved = store.resolve_key(old_key).map_err(|e| anyhow::anyhow!("{}", e))?;
store.rename_node(&resolved, new_key).map_err(|e| anyhow::anyhow!("{}", e))?;
store.rename_node(&resolved, new_key, provenance).map_err(|e| anyhow::anyhow!("{}", e))?;
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(format!("Renamed '{}' → '{}'", resolved, new_key))
}
@ -184,13 +190,43 @@ pub fn memory_supersede(store: &Store, provenance: &str, old_key: &str, new_key:
.map_err(|e| anyhow::anyhow!("{}", e))?
.map(|n| n.content)
.ok_or_else(|| anyhow::anyhow!("node not found: {}", old_key))?;
// Transfer links from old node to new node (if new_key exists)
let mut links_transferred = 0;
if store.contains_key(new_key).unwrap_or(false) {
// Get old node's neighbors
let old_neighbors = store.neighbors(old_key).unwrap_or_default();
// Get new node's existing neighbors (to avoid weakening existing links)
let new_neighbors: std::collections::HashMap<String, f32> = store.neighbors(new_key)
.unwrap_or_default()
.into_iter()
.collect();
for (neighbor_key, old_strength) in old_neighbors {
// Skip self-links
if neighbor_key == new_key { continue; }
// Only add/strengthen link if new node doesn't have a stronger one
let current = new_neighbors.get(&neighbor_key).copied().unwrap_or(0.0);
if old_strength > current {
if store.set_link_strength(new_key, &neighbor_key, old_strength, provenance).is_ok() {
links_transferred += 1;
}
}
}
}
let notice = format!("**SUPERSEDED** by `{}` — {}\n\n---\n\n{}",
new_key, reason, content.trim());
store.upsert_provenance(old_key, &notice, provenance)
.map_err(|e| anyhow::anyhow!("{}", e))?;
store.set_weight(old_key, 0.01).map_err(|e| anyhow::anyhow!("{}", e))?;
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(format!("superseded {}{} ({})", old_key, new_key, reason))
if links_transferred > 0 {
Ok(format!("superseded {}{} ({}), transferred {} links", old_key, new_key, reason, links_transferred))
} else {
Ok(format!("superseded {}{} ({})", old_key, new_key, reason))
}
}
/// Convert a list of keys to ReplayItems with priority and graph metrics.
@ -396,7 +432,7 @@ pub fn graph_communities(store: &Store, _provenance: &str, top_n: Option<usize>,
Ok(out)
}
pub fn graph_normalize_strengths(store: &Store, _provenance: &str, apply: Option<bool>) -> Result<String> {
pub fn graph_normalize_strengths(store: &Store, provenance: &str, apply: Option<bool>) -> Result<String> {
use crate::store::{StoreView, RelationType};
let apply = apply.unwrap_or(false);
@ -459,7 +495,7 @@ pub fn graph_normalize_strengths(store: &Store, _provenance: &str, apply: Option
if apply {
for (source, target, new_strength) in to_update {
store.set_link_strength(&source, &target, new_strength)?;
store.set_link_strength(&source, &target, new_strength, provenance)?;
}
writeln!(out, "\nApplied {} strength updates.", changed).ok();
} else {