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
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:
1 parent
1f7a266497
commit
7194f69a6d
2 files changed
+140
-49
No files matched your search
@@ -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" => {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user