refactor(message_graph): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 18s
CI Pipeline / go-surface (push) Failing after 20s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 3s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / unit-tests (push) Canceled after 26s
Auto-version and Release / release (push) Canceled after 36s
CI Pipeline / canary (push) Canceled after 26s
CI Pipeline / type-check (push) Canceled after 12s
deploy / deploy (push) Canceled after 29s

- Kind / EdgeType enums: the node-kind and edge-type lattices are
  compiler-checked variants with one wire spelling each (parse once in
  add, exhaustive as_str match) — the stringly membership arrays are
  gone.
- Cue enum: the render-cue lattice (Replaces / BranchedFrom /
  SupersededBy / None) classified once, rendered by exhaustive match;
  first-supersedes-edge-wins and falsy-superseded_by laws stay pinned by
  the frozen battery.
- Zero-copy walks: lineage/history return Vec<&Value> on the graph's
  lifetime — no per-node clone churn on the hot render paths; the FFI
  boundary clones once for the wire.
- Battery untouched: 12 tests replay the same 68 frozen-oracle cases
  VERBATIM, green throughout the pass; live parity re-verified
  byte-identical after the refactor; clippy 0.
This commit is contained in:
Nick committed 2026-10-12 02:26:31 +01:00
1 parent 1f7a266497
commit 7194f69a6d
2 files changed
+140 -49

No files matched your search

+4 -2
View File
@@ -159,12 +159,14 @@ fn replay(group: &str, c: &Value) -> Result<(), String> {
}
"lineage" => {
let g = graph_from_wire(input).map_err(|e| format!("[{case_name}] {e:?}"))?;
let got = Value::Array(g.lineage(input["node_id"].as_str()));
let got = Value::Array(g.lineage(input["node_id"].as_str()).into_iter().cloned().collect());
expect_field(&case_name, "result", &got, &expected["result"])
}
"history" => {
let g = graph_from_wire(input).map_err(|e| format!("[{case_name}] {e:?}"))?;
let got = Value::Array(g.history(input["node_id"].as_str().unwrap_or_default()));
let got = Value::Array(
g.history(input["node_id"].as_str().unwrap_or_default()).into_iter().cloned().collect(),
);
expect_field(&case_name, "result", &got, &expected["result"])
}
"migrate_flat" => {
+136 -47
View File
@@ -49,13 +49,83 @@ use std::ffi::{CStr, CString};
use std::os::raw::c_char;
pub const SCHEMA: i64 = 1;
pub const KINDS: [&str; 6] = [
"message", "reply", "tool_call", "tool_result", "revision", "branch_head",
];
pub const EDGE_TYPES: [&str; 7] = [
"parent", "reply_to", "calls", "results_in", "revises", "supersedes",
"branches_from",
];
/// Language advantage: the node-kind / edge-type lattices are enums, not
/// stringly membership tests — `add` matches EXHAUSTIVELY (a new kind is
/// a compiler-checked variant, not another wire string), and the wire
/// spelling lives in exactly one place per variant.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
Message,
Reply,
ToolCall,
ToolResult,
Revision,
BranchHead,
}
impl Kind {
fn parse(s: &str) -> Option<Kind> {
Some(match s {
"message" => Kind::Message,
"reply" => Kind::Reply,
"tool_call" => Kind::ToolCall,
"tool_result" => Kind::ToolResult,
"revision" => Kind::Revision,
"branch_head" => Kind::BranchHead,
_ => return None,
})
}
fn as_str(self) -> &'static str {
match self {
Kind::Message => "message",
Kind::Reply => "reply",
Kind::ToolCall => "tool_call",
Kind::ToolResult => "tool_result",
Kind::Revision => "revision",
Kind::BranchHead => "branch_head",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeType {
Parent,
ReplyTo,
Calls,
ResultsIn,
Revises,
Supersedes,
BranchesFrom,
}
impl EdgeType {
fn parse(s: &str) -> Option<EdgeType> {
Some(match s {
"parent" => EdgeType::Parent,
"reply_to" => EdgeType::ReplyTo,
"calls" => EdgeType::Calls,
"results_in" => EdgeType::ResultsIn,
"revises" => EdgeType::Revises,
"supersedes" => EdgeType::Supersedes,
"branches_from" => EdgeType::BranchesFrom,
_ => return None,
})
}
fn as_str(self) -> &'static str {
match self {
EdgeType::Parent => "parent",
EdgeType::ReplyTo => "reply_to",
EdgeType::Calls => "calls",
EdgeType::ResultsIn => "results_in",
EdgeType::Revises => "revises",
EdgeType::Supersedes => "supersedes",
EdgeType::BranchesFrom => "branches_from",
}
}
}
/// The oracle's exception taxonomy, typed across the envelope.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -196,12 +266,10 @@ impl MessageGraph {
parent: Option<&str>,
edge: &str,
) -> Result<Value, MgErr> {
if !KINDS.contains(&kind) {
return Err(MgErr::value(format!("unknown kind '{kind}'")));
}
if !EDGE_TYPES.contains(&edge) {
return Err(MgErr::value(format!("unknown edge '{edge}'")));
}
let kind =
Kind::parse(kind).ok_or_else(|| MgErr::value(format!("unknown kind '{kind}'")))?;
let edge = EdgeType::parse(edge)
.ok_or_else(|| MgErr::value(format!("unknown edge '{edge}'")))?;
let parent_value = match parent {
Some(p) => json!(p),
None => match self.order.last() {
@@ -210,12 +278,14 @@ impl MessageGraph {
},
};
let edges = match parent {
Some(p) if !p.is_empty() => json!([{"type": edge, "to": p}]),
Some(p) if !p.is_empty() => {
json!([{"type": edge.as_str(), "to": p}])
}
_ => Value::Array(vec![]),
};
let mut node = Map::new();
node.insert("id".into(), json!(node_id));
node.insert("kind".into(), json!(kind));
node.insert("kind".into(), json!(kind.as_str()));
node.insert("role".into(), role);
node.insert("content".into(), content);
node.insert("ts".into(), json!(ts));
@@ -335,7 +405,9 @@ impl MessageGraph {
/// The context for a selected message: its ancestors (following parent
/// links) + itself, oldest first, skipping superseded nodes in favor of
/// their revisions. Defaults to the head. The seen-set guards cycles.
pub fn lineage(&self, node_id: Option<&str>) -> Vec<Value> {
/// Language advantage: borrowed walk — `Vec<&Value>` with the graph's
/// lifetime, no per-node clone churn (the FFI boundary clones once).
pub fn lineage(&self, node_id: Option<&str>) -> Vec<&Value> {
let mut cur = match node_id {
Some(id) => Some(id.to_string()),
None => self.order.last().cloned(),
@@ -343,7 +415,7 @@ impl MessageGraph {
let Some(mut cur) = cur.take() else {
return vec![];
};
let mut chain: Vec<Value> = Vec::new();
let mut chain: Vec<&Value> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
while !cur.is_empty() && !seen.contains(&cur) {
seen.insert(cur.clone());
@@ -357,7 +429,7 @@ impl MessageGraph {
continue;
}
}
chain.push(node.clone());
chain.push(node);
match node.get("parent").and_then(Value::as_str) {
Some(p) if !p.is_empty() => cur = p.to_string(),
_ => break,
@@ -370,7 +442,7 @@ impl MessageGraph {
/// Every revision of a message, oldest first (the provenance trail).
/// Walks supersedes edges back to the root, then forward through
/// superseded_by — so the FULL trail comes back from any point on it.
pub fn history(&self, node_id: &str) -> Vec<Value> {
pub fn history(&self, node_id: &str) -> Vec<&Value> {
let mut cur = node_id.to_string();
while let Some(node) = self.nodes.get(&cur) {
let prev: Vec<&str> = node
@@ -391,13 +463,15 @@ impl MessageGraph {
None => break,
}
}
let mut trail = Vec::new();
// Language advantage: borrowed provenance trail (same lifetime law
// as `lineage`) — the walk reads, the boundary serializes.
let mut trail: Vec<&Value> = Vec::new();
let mut cur = if cur.is_empty() { None } else { Some(cur) };
while let Some(c) = cur {
let Some(node) = self.nodes.get(&c) else {
break;
};
trail.push(node.clone());
trail.push(node);
match node.get("superseded_by").and_then(Value::as_str) {
Some(s) if !s.is_empty() => cur = Some(s.to_string()),
_ => break,
@@ -442,31 +516,12 @@ impl MessageGraph {
/// One quiet relationship line per node (no visual clutter).
pub fn cue(node: &Value) -> String {
if let Some(edges) = node.get("edges").and_then(Value::as_array) {
for e in edges {
match e.get("type").and_then(Value::as_str) {
Some("supersedes") => {
return format!(
"replaces {}",
take8(e.get("to").and_then(Value::as_str).unwrap_or(""))
);
}
Some("branches_from") => {
return format!(
"branched from {}",
take8(e.get("to").and_then(Value::as_str).unwrap_or(""))
);
}
_ => {}
}
}
match cue_kind(node) {
Cue::Replaces(to) => format!("replaces {}", take8(to)),
Cue::BranchedFrom(to) => format!("branched from {}", take8(to)),
Cue::SupersededBy(id) => format!("superseded by {}", take8(id)),
Cue::None => String::new(),
}
if let Some(s) = node.get("superseded_by").and_then(Value::as_str) {
if !s.is_empty() {
return format!("superseded by {}", take8(s));
}
}
String::new()
}
// -- wire -------------------------------------------------------------
@@ -480,6 +535,36 @@ fn take8(s: &str) -> String {
s.chars().take(8).collect()
}
/// The render-cue lattice: one quiet line per node. Exhaustive match in
/// `cue` — the first supersedes/branches_from edge in iteration order
/// wins, a falsy superseded_by ("") is no cue.
enum Cue<'a> {
Replaces(&'a str),
BranchedFrom(&'a str),
SupersededBy(&'a str),
None,
}
fn cue_kind(node: &Value) -> Cue<'_> {
if let Some(edges) = node.get("edges").and_then(Value::as_array) {
for e in edges {
match e.get("type").and_then(Value::as_str) {
Some("supersedes") => {
return Cue::Replaces(e.get("to").and_then(Value::as_str).unwrap_or(""));
}
Some("branches_from") => {
return Cue::BranchedFrom(e.get("to").and_then(Value::as_str).unwrap_or(""));
}
_ => {}
}
}
}
match node.get("superseded_by").and_then(Value::as_str) {
Some(s) if !s.is_empty() => Cue::SupersededBy(s),
_ => Cue::None,
}
}
/// Python json.loads type names (for the AttributeError messages).
fn py_type(v: &Value) -> &'static str {
match v {
@@ -660,7 +745,9 @@ pub extern "C" fn mg_branch(arg: *const c_char) -> *mut c_char {
pub extern "C" fn mg_lineage(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let g = graph_from_wire(&a)?;
Ok(Value::Array(g.lineage(opt_str(&a, "node_id"))))
Ok(Value::Array(
g.lineage(opt_str(&a, "node_id")).into_iter().cloned().collect(),
))
});
envelope(r)
}
@@ -669,7 +756,9 @@ pub extern "C" fn mg_lineage(arg: *const c_char) -> *mut c_char {
pub extern "C" fn mg_history(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let g = graph_from_wire(&a)?;
Ok(Value::Array(g.history(require_str(&a, "node_id")?)))
Ok(Value::Array(
g.history(require_str(&a, "node_id")?).into_iter().cloned().collect(),
))
});
envelope(r)
}