sinter_core/edge.rs
1use serde::{Deserialize, Serialize};
2
3use crate::node::{NodeId, Span};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
6pub enum Relation {
7 Calls,
8 Uses,
9 Imports,
10 Contains,
11 Implements,
12 Extends,
13 /// The source reads rows from the destination relation.
14 Reads,
15 /// The source can insert, update, or delete rows in the destination table.
16 Writes,
17 /// The source declares creation of the destination database object.
18 Creates,
19 /// The source changes the destination database object.
20 Alters,
21 /// The source declares removal of the destination database object.
22 Drops,
23}
24
25impl Relation {
26 pub fn as_str(self) -> &'static str {
27 match self {
28 Self::Calls => "calls",
29 Self::Uses => "uses",
30 Self::Imports => "imports",
31 Self::Contains => "contains",
32 Self::Implements => "implements",
33 Self::Extends => "extends",
34 Self::Reads => "reads",
35 Self::Writes => "writes",
36 Self::Creates => "creates",
37 Self::Alters => "alters",
38 Self::Drops => "drops",
39 }
40 }
41}
42
43/// What binds this edge to its target (R2: evidence or nothing).
44/// Global name uniqueness is not evidence and has no variant here.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
46pub enum Evidence {
47 /// Syntactic containment seen directly in the parse tree.
48 Structural,
49 /// Name visible in the reference's own file scope.
50 Scope,
51 /// An import statement binds the reference's path to the target.
52 Import,
53 /// A compiler-produced SCIP index binds reference to definition.
54 Scip,
55 /// An operator-declared binding from a workspace manifest (runtime
56 /// coupling like queue topics/HTTP routes that no static analysis can
57 /// see). Auditable in the manifest; never inferred.
58 Declared,
59 /// Dynamic-dispatch fan-out: a trait/interface method is assumed to
60 /// reach every implementation in the corpus. Conservative
61 /// over-approximation, deliberately excludable (`--certain`,
62 /// `--evidence` without "dynamic").
63 Dynamic,
64}
65
66impl Evidence {
67 pub fn as_str(self) -> &'static str {
68 match self {
69 Self::Structural => "structural",
70 Self::Scope => "scope",
71 Self::Import => "import",
72 Self::Scip => "scip",
73 Self::Declared => "declared",
74 Self::Dynamic => "dynamic",
75 }
76 }
77
78 /// Compiler-grade evidence is certain; heuristic-free but indirect
79 /// evidence (scope/import matching) is inferred.
80 pub fn confidence(self) -> Confidence {
81 match self {
82 Self::Structural | Self::Scip | Self::Declared => Confidence::Certain,
83 Self::Scope | Self::Import | Self::Dynamic => Confidence::Inferred,
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
89pub enum Confidence {
90 Certain,
91 Inferred,
92}
93
94/// Directed edge `src -> dst`. The graph is a multigraph: parallel edges
95/// that differ in relation, evidence, or confidence coexist.
96#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
97pub struct Edge {
98 pub src: NodeId,
99 pub dst: NodeId,
100 pub relation: Relation,
101 pub evidence: Evidence,
102 pub confidence: Confidence,
103 /// Byte span of the binding reference in the src node's file (the file
104 /// is derivable from `src`). None when no single site exists:
105 /// containment, dynamic fan-out, implements/extends pairing, declared
106 /// links. When several sites bind the same (src, dst, relation,
107 /// evidence), storage keeps ONE representative site — the field is
108 /// last so identity orders before site.
109 pub site: Option<Span>,
110}
111
112impl Edge {
113 /// Identity without the site: two edges equal here are the same
114 /// dependency fact observed at (possibly) different call sites.
115 pub fn identity(&self) -> (&NodeId, &NodeId, Relation, Evidence, Confidence) {
116 (
117 &self.src,
118 &self.dst,
119 self.relation,
120 self.evidence,
121 self.confidence,
122 )
123 }
124}