remem/retrieval/graph/
types.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub enum GraphTraversalStatus {
3 Ready,
4 MissingTable,
5 EmptyGraph,
6 NoSeed,
7 NoExpansion,
8}
9
10impl GraphTraversalStatus {
11 pub const fn disabled_reason(self) -> Option<&'static str> {
12 match self {
13 Self::Ready => None,
14 Self::MissingTable => Some("graph_edges table is unavailable"),
15 Self::EmptyGraph => Some("graph_edges table is empty"),
16 Self::NoSeed => Some("no eligible FTS/vector graph seeds"),
17 Self::NoExpansion => Some("no eligible trusted graph expansion"),
18 }
19 }
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
23pub enum GraphPathKind {
24 Supersedes,
25 Mentions,
26 TouchesFile,
27}
28
29impl GraphPathKind {
30 pub const fn as_str(self) -> &'static str {
31 match self {
32 Self::Supersedes => "supersedes",
33 Self::Mentions => "mentions",
34 Self::TouchesFile => "touches_file",
35 }
36 }
37}
38
39#[derive(Debug, Clone, PartialEq)]
40pub struct GraphTraversalHit {
41 pub memory_id: i64,
42 pub hop_count: u8,
43 pub path_kind: GraphPathKind,
44 pub min_confidence: f64,
45 pub seed_rank: usize,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct GraphTraversalLimits {
50 pub max_seeds: usize,
51 pub max_degree_per_node: usize,
52 pub max_edges_scanned: usize,
53 pub max_candidates: usize,
54}
55
56impl Default for GraphTraversalLimits {
57 fn default() -> Self {
58 Self {
59 max_seeds: 32,
60 max_degree_per_node: 64,
61 max_edges_scanned: 2_048,
62 max_candidates: 120,
63 }
64 }
65}
66
67impl GraphTraversalLimits {
68 pub fn for_search(fetch_limit: i64) -> Self {
69 Self {
70 max_candidates: usize::try_from(fetch_limit.max(1)).unwrap_or(120),
71 ..Self::default()
72 }
73 }
74
75 pub(super) fn validate(self) -> anyhow::Result<()> {
76 anyhow::ensure!(self.max_seeds > 0, "graph max_seeds must be positive");
77 anyhow::ensure!(
78 self.max_degree_per_node > 0,
79 "graph max_degree_per_node must be positive"
80 );
81 anyhow::ensure!(
82 self.max_edges_scanned > 0,
83 "graph max_edges_scanned must be positive"
84 );
85 anyhow::ensure!(
86 self.max_candidates > 0,
87 "graph max_candidates must be positive"
88 );
89 Ok(())
90 }
91}
92
93#[derive(Debug, Clone, Copy)]
94pub struct GraphTraversalRequest<'a> {
95 pub seed_memory_ids: &'a [i64],
96 pub project: Option<&'a str>,
97 pub memory_type: Option<&'a str>,
98 pub branch: Option<&'a str>,
99 pub include_inactive: bool,
100 pub reference_time_epoch: i64,
101 pub limits: GraphTraversalLimits,
102}
103
104#[derive(Debug, Clone, Default, PartialEq, Eq)]
105pub struct GraphTraversalDiagnostics {
106 pub edges_scanned: usize,
107 pub candidates_considered: usize,
108 pub targets_filtered: usize,
109 pub diagnostic_hint_edges: usize,
110 pub extracted_from_edges: usize,
111 pub ignored_trusted_edges: usize,
112}
113
114#[derive(Debug, Clone, PartialEq)]
115pub struct GraphTraversalOutcome {
116 pub status: GraphTraversalStatus,
117 pub hits: Vec<GraphTraversalHit>,
118 pub diagnostics: GraphTraversalDiagnostics,
119}
120
121impl GraphTraversalOutcome {
122 pub(super) fn empty(status: GraphTraversalStatus) -> Self {
123 Self {
124 status,
125 hits: Vec::new(),
126 diagnostics: GraphTraversalDiagnostics::default(),
127 }
128 }
129}