lean_ctx_protocol/hive/
graph.rs1use serde::{Deserialize, Serialize};
22
23use super::{require_schema, validate_text};
24use crate::ValidationError;
25
26pub const GRAPH_SCHEMA: &str = "leanctx.hive.graph/v1";
27pub const MAX_GRAPH_NODES: usize = 20_000;
28pub const MAX_GRAPH_EDGES: usize = 60_000;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum GraphNodeKind {
33 Code,
35 Knowledge,
37 Work,
39 Actor,
41 #[serde(other)]
42 Unknown,
43}
44
45pub const GRAPH_EDGE_TYPES: [&str; 8] = [
48 "applies_to",
50 "contains",
53 "imports",
54 "calls",
55 "shows",
57 "changed",
59 "worked_on",
61 "questions",
63];
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66pub struct GraphNodeV1 {
67 pub id: String,
68 pub kind: GraphNodeKind,
69 pub label: String,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub struct GraphEdgeV1 {
74 pub from: String,
75 pub to: String,
76 #[serde(rename = "type")]
77 pub kind: String,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81pub struct GraphV1 {
82 pub schema: String,
83 pub vault_id: String,
84 pub revision: u64,
85 pub nodes: Vec<GraphNodeV1>,
87 pub edges: Vec<GraphEdgeV1>,
89}
90
91impl GraphV1 {
92 pub fn validate(&self) -> Result<(), ValidationError> {
93 require_schema(&self.schema, GRAPH_SCHEMA)?;
94 super::validate_vault_id(&self.vault_id)?;
95 if self.nodes.len() > MAX_GRAPH_NODES || self.edges.len() > MAX_GRAPH_EDGES {
96 return Err(ValidationError::new(format!(
97 "a graph has at most {MAX_GRAPH_NODES} nodes and {MAX_GRAPH_EDGES} edges"
98 )));
99 }
100 for (i, node) in self.nodes.iter().enumerate() {
101 validate_text(&node.id, 700, "node id", false)?;
102 validate_text(&node.label, 300, "node label", false)?;
103 if i > 0 && self.nodes[i - 1].id >= node.id {
104 return Err(ValidationError::new(
105 "nodes must be sorted by id without repeats",
106 ));
107 }
108 }
109 let known = |id: &str| {
110 self.nodes
111 .binary_search_by(|node| node.id.as_str().cmp(id))
112 .is_ok()
113 };
114 for (i, edge) in self.edges.iter().enumerate() {
115 if !known(&edge.from) || !known(&edge.to) {
116 return Err(ValidationError::new("an edge names a missing node"));
117 }
118 if !GRAPH_EDGE_TYPES.contains(&edge.kind.as_str())
119 && !super::knowledge::RELATION_TYPES.contains(&edge.kind.as_str())
120 {
121 return Err(ValidationError::new("unknown edge type"));
122 }
123 if i > 0 {
124 let prev = &self.edges[i - 1];
125 if (&prev.from, &prev.to, &prev.kind) >= (&edge.from, &edge.to, &edge.kind) {
126 return Err(ValidationError::new("edges must be sorted without repeats"));
127 }
128 }
129 }
130 Ok(())
131 }
132}