Skip to main content

Relations

A relation is a directed edge that refers to its endpoints by ID.

relation.source_id
relation.target_id
relation.kind # RelationKind
relation.metadata # dict[str, object]

The 12 kinds

KindFrom → ToProduced by
CONTAINSproject → module, module → filestructure
DECLARESfile → declarationstructure
IMPORTSfile → importstructure
RESOLVES_TOimport → module or external symbolstructure
DEPENDS_ONproject → dependencystructure
CALLSfunction → calleeresolution
REFERENCESdeclaration → symbol read or writtenresolution
HAS_TYPEdeclaration → its annotated typeresolution
INHERITS_FROMtype → base type or traitresolution
EXPOSESfunction → boundaryboundaries
CONSUMESfunction → boundaryboundaries
COMMUNICATES_WITHservice → serviceGraph.link_boundaries()

The split matters when reading a graph: the first group exists in any run, the second only when resolution ran, the third comes from boundary extraction.

COMMUNICATES_WITH is the one edge no adapter produces on its own — it needs two services in the same graph. Merge them, then call link_boundaries().

Metadata

Resolution records where the edge was written:

relation.metadata["span"] # the occurrence's span
relation.metadata["access"] # 'read' | 'write', on REFERENCES

Boundary edges carry the contract:

relation.metadata["mechanism"] # 'http' | 'grpc' | 'queue' | 'temporal'
relation.metadata["key"] # 'GET /users/{}'
relation.metadata["role"] # 'server' | 'client'
relation.metadata["confidence"] # 1.0 for a literal, less when inferred
relation.metadata["line"], relation.metadata["col"]

Plus per-mechanism detail: method and path for HTTP, service and method for gRPC, topic for queues, activity for Temporal.

Duplicates are meaningful — when they carry metadata

Relations are a list, not a set. Two CALLS between the same pair of nodes from two different call sites are two edges, distinguished by their span metadata. Diffing respects that: the edge key includes canonicalized metadata, so neither the duplicates collapse nor a metadata-only change goes unnoticed.

A structural edge is the opposite case, and the rule is exactly one per (source, target, kind). It has no metadata, so a second copy would say nothing the first did not: a visitor emits one DECLARES per occurrence while the node it points at is deduplicated by qualified name, which meant a variable assigned eight times produced eight identical edges — 7.7% of all edges on real projects. Every adapter drops those before returning. An edge that carries metadata is exempt, because there the duplicate records where it was observed.