Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/tracedecay-code-index/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ lang-lean = ["tracedecay-code-extraction/lang-lean"]
[dependencies]
ast-grep-core = "0.44"
flate2 = "1"
base64 = "0.22"
hex = "0.4"
hotpath.workspace = true
ignore = "0.4"
Expand Down
76 changes: 50 additions & 26 deletions crates/tracedecay-code-index/src/graph_projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ use tracedecay_graph_db::{
GraphCancellation, GraphConflictContextV1, GraphDbError, GraphEntity, GraphEntityId,
GraphEntityRef, GraphGenerationId, GraphGenerationManifest, GraphGenerationManifestIdentity,
GraphIdempotencyKey, GraphLabel, GraphNamespace, GraphProjectionId, GraphProjectionIdentity,
GraphProjectorRevision, GraphProperty, GraphPropertyName, GraphServingEnginePin,
GraphTraversalDirection, GraphWatermark, SourceGeneration, TraversalRequest,
VerifiedGraphSnapshot,
GraphProjectorRevision, GraphProperty, GraphPropertyName, GraphRelation, GraphRelationId,
GraphRelationKind, GraphServingEnginePin, GraphWatermark, MAX_VERIFIED_GENERATION_RELATIONS,
SourceGeneration, VerifiedGraphSnapshot,
};

mod builder;
Expand Down Expand Up @@ -61,9 +61,9 @@ const CURRENT_GENERATION_ENTITY: &str = "code-current-generation";
const CURRENT_GENERATION_PROPERTY: &str = "current-generation";
const PROJECTION_NODE_COUNT_PROPERTY: &str = "projection-node-count";
const EDGE_RECORD_PROPERTY: &str = "edge-record";
const EDGE_LABEL: &str = "CodeRelationEvidence";
const FILE_SYMBOL_EDGE_KIND: &str = "CodeFileContainsSymbol";
const TARGET_EDGE_KIND: &str = "CodeRelationTarget";
/// Starts per adjacency fan-out batch; the store bounds one batch's starts.
const ADJACENCY_SEED_CHUNK: usize = 4_096;
/// Names the shape of the rows this projector emits for one sealed code
/// generation; the graph generation id is derived from it, so a revision
/// bump seals a new graph generation from the same code generation rather
Expand All @@ -73,8 +73,11 @@ const TARGET_EDGE_KIND: &str = "CodeRelationTarget";
/// v7 carries unresolved receiver-call limitations on each source symbol. v8
/// widens those limitations to bare TypeScript calls whose import the seal
/// could not bind to project code. v9 names each source relation for its edge
/// kind, so adjacency filters kinds without decoding the edge.
pub const CODE_GRAPH_PROJECTOR_REVISION: &str = "code-graph-projector.v9";
/// kind, so adjacency filters kinds without decoding the edge. v10 stores each
/// code edge as one relation from its source symbol to its target symbol that
/// carries the edge record, instead of an edge entity between two relations,
/// and stores every record as compact text (`schema::compact_record`).
pub const CODE_GRAPH_PROJECTOR_REVISION: &str = "code-graph-projector.v10";

/// Every semantic edge kind, at its [`relation_edge_kind_index`].
const RELATION_EDGE_KINDS: [RelationEdgeKindV1; 9] = [
Expand Down Expand Up @@ -113,28 +116,28 @@ const _: () = {
}
};

/// The kind of the relation from an edge's source symbol to its edge entity:
/// one per edge kind, so a fan-out names the kinds it admits.
fn source_edge_kind(kind: RelationEdgeKindV1) -> &'static str {
/// The relation kind of a code edge row, from its source symbol to its
/// target symbol: one per edge kind, so a fan-out names the kinds it admits.
fn code_edge_kind(kind: RelationEdgeKindV1) -> &'static str {
match kind {
RelationEdgeKindV1::Calls => "CodeRelationSource.calls",
RelationEdgeKindV1::Uses => "CodeRelationSource.uses",
RelationEdgeKindV1::TypeOf => "CodeRelationSource.type_of",
RelationEdgeKindV1::Contains => "CodeRelationSource.contains",
RelationEdgeKindV1::Implements => "CodeRelationSource.implements",
RelationEdgeKindV1::Extends => "CodeRelationSource.extends",
RelationEdgeKindV1::Annotates => "CodeRelationSource.annotates",
RelationEdgeKindV1::Returns => "CodeRelationSource.returns",
RelationEdgeKindV1::Receives => "CodeRelationSource.receives",
RelationEdgeKindV1::Calls => "CodeEdge.calls",
RelationEdgeKindV1::Uses => "CodeEdge.uses",
RelationEdgeKindV1::TypeOf => "CodeEdge.type_of",
RelationEdgeKindV1::Contains => "CodeEdge.contains",
RelationEdgeKindV1::Implements => "CodeEdge.implements",
RelationEdgeKindV1::Extends => "CodeEdge.extends",
RelationEdgeKindV1::Annotates => "CodeEdge.annotates",
RelationEdgeKindV1::Returns => "CodeEdge.returns",
RelationEdgeKindV1::Receives => "CodeEdge.receives",
}
}

/// The edge kind a source relation kind names, or `None` for any other
/// The edge kind a code edge relation kind names, or `None` for any other
/// relation.
fn source_edge_kind_edge(relation_kind: &str) -> Option<RelationEdgeKindV1> {
fn code_edge_kind_edge(relation_kind: &str) -> Option<RelationEdgeKindV1> {
RELATION_EDGE_KINDS
.into_iter()
.find(|kind| source_edge_kind(*kind) == relation_kind)
.find(|kind| code_edge_kind(*kind) == relation_kind)
}

#[derive(Clone, Debug, Error, PartialEq, Eq)]
Expand Down Expand Up @@ -1038,10 +1041,31 @@ fn symbol_entity_id(
GraphEntityId::new(stable_identity("symbol", occurrence.as_str())).map_err(Into::into)
}

fn edge_entity_id(
fn edge_relation_id(
edge: &CanonicalRelationEdgeV1,
) -> Result<GraphEntityId, CodeGraphProjectionError> {
GraphEntityId::new(stable_identity("edge", &hex::encode(serialize(edge)?))).map_err(Into::into)
) -> Result<GraphRelationId, CodeGraphProjectionError> {
GraphRelationId::new(stable_identity("edge", &hex::encode(serialize(edge)?)))
.map_err(Into::into)
}

/// The validated edge record a code edge relation row carries: its identity,
/// kind, and both endpoints must be the ones its payload derives.
fn edge_record(
relation: &GraphRelation,
) -> Result<CanonicalRelationEdgeV1, CodeGraphProjectionError> {
let edge: CanonicalRelationEdgeV1 =
deserialize_property(&relation.properties, EDGE_RECORD_PROPERTY)?;
validate_edge(&edge)?;
if edge_relation_id(&edge)? != relation.identity
|| code_edge_kind(edge.kind) != relation.kind.as_str()
|| symbol_entity_id(&edge.from_occurrence)? != relation.from
|| symbol_entity_id(&edge.to_occurrence)? != relation.to
{
return Err(CodeGraphProjectionError::Corrupt(
"code graph edge row does not match its payload".to_owned(),
));
}
Ok(edge)
}

#[cfg(any(feature = "test-helpers", feature = "eval-helpers"))]
Expand Down Expand Up @@ -1217,7 +1241,7 @@ fn load_symbol_entity_record(
"code graph symbol identity has the wrong label".to_owned(),
));
}
let record: SymbolRecordV1 = deserialize_property(&entity, SYMBOL_RECORD_PROPERTY)?;
let record: SymbolRecordV1 = deserialize_property(&entity.properties, SYMBOL_RECORD_PROPERTY)?;
validate_symbol_record(&record)?;
if symbol_entity_id(&record.occurrence)? != *identity {
return Err(CodeGraphProjectionError::Corrupt(
Expand Down
74 changes: 24 additions & 50 deletions crates/tracedecay-code-index/src/graph_projection/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ use super::schema::{
stable_identity,
};
use super::{
CodeGraphProjectionError, CodeGraphSymbolBindingV1, EDGE_LABEL, EDGE_RECORD_PROPERTY,
FILE_SYMBOL_EDGE_KIND, SealedCodeGraphRowsError, SymbolRecordV1, TARGET_EDGE_KIND,
CodeGraphProjectionError, CodeGraphSymbolBindingV1, EDGE_RECORD_PROPERTY,
FILE_SYMBOL_EDGE_KIND, SealedCodeGraphRowsError, SymbolRecordV1, code_edge_kind,
code_graph_manifest_identity, compare_edges, current_generation_entity, projection,
source_edge_kind, symbol_entity, symbol_entity_id, validate_edge,
symbol_entity, symbol_entity_id, validate_edge,
};

/// Builds a sealed generation's code graph from its on-disk file segments and
Expand Down Expand Up @@ -532,23 +532,20 @@ fn emit_code_graph_rows(
))
})?;
// Chunks bind symbols to files and spans above; they are not graph rows.
// No reader addresses a chunk through the graph, traversal alternates
// symbol and edge-evidence entities, and a symbol's binding already
// names its chunk, so projecting one entity plus one relation per chunk
// only multiplied every graph artifact by the chunk count.
// No reader addresses a chunk through the graph and a symbol's binding
// already names its chunk, so projecting one entity plus one relation per
// chunk only multiplied every graph artifact by the chunk count.
hotpath::measure_block!("code_index.seal.collect.emit", {
let mut entities = Vec::with_capacity(
batch
.files
.len()
.saturating_add(batch.imports.len())
.saturating_add(occurrences.len())
.saturating_add(retained_edges.len()),
.saturating_add(occurrences.len()),
);
let mut relations = Vec::with_capacity(
retained_edges
.len()
.saturating_mul(2)
.saturating_add(bindings.len())
.saturating_add(batch.imports.len()),
);
Expand Down Expand Up @@ -619,13 +616,9 @@ fn emit_code_graph_rows(
}
for window in retained_edges.chunks(row_window) {
check()?;
for (entity, source, target) in collect_graph_rows_ordered(window, |edge| {
edge_artifacts(projection, edge, &symbol_ids)
})? {
entities.push(entity);
relations.push(source);
relations.push(target);
}
relations.extend(collect_graph_rows_ordered(window, |edge| {
edge_relation(projection, edge, &symbol_ids)
})?);
}
Ok(EmittedRows {
entities,
Expand Down Expand Up @@ -657,47 +650,28 @@ fn endpoint_symbol_id(
}
}

/// One retained edge's entity plus both endpoint relations, sharing a single
/// serialization and identity derivation of the edge payload.
fn edge_artifacts(
/// One retained edge as a single relation row from its source symbol to its
/// target symbol, carrying the edge record as its payload.
fn edge_relation(
projection: &GraphProjectionIdentity,
edge: &CanonicalRelationEdgeV1,
symbol_ids: &BTreeMap<SymbolOccurrenceId, GraphEntityId>,
) -> Result<
(
GraphEntity,
GraphGenerationRelation,
GraphGenerationRelation,
),
CodeGraphProjectionError,
> {
) -> Result<GraphGenerationRelation, CodeGraphProjectionError> {
let payload = serialize(edge)?;
let identity = GraphEntityId::new(stable_identity("edge", &hex::encode(&payload)))?;
let entity = GraphEntity::new(
identity.clone(),
BTreeSet::from([GraphLabel::new(EDGE_LABEL)?]),
BTreeMap::from([(
GraphPropertyName::new(EDGE_RECORD_PROPERTY)?,
record_property(payload)?,
)]),
)?;
let identity = GraphRelationId::new(stable_identity("edge", &hex::encode(&payload)))?;
let from = endpoint_symbol_id(symbol_ids, &edge.from_occurrence)?;
let to = endpoint_symbol_id(symbol_ids, &edge.to_occurrence)?;
let source = GraphGenerationRelation::new(
GraphRelationId::new(stable_identity("source", identity.as_str()))?,
GraphGenerationRelation::new(
identity,
GraphEntityRef::new(projection.clone(), from),
GraphEntityRef::new(projection.clone(), identity.clone()),
GraphRelationKind::new(source_edge_kind(edge.kind))?,
BTreeMap::new(),
)?;
let target = GraphGenerationRelation::new(
GraphRelationId::new(stable_identity("target", identity.as_str()))?,
GraphEntityRef::new(projection.clone(), identity),
GraphEntityRef::new(projection.clone(), to),
GraphRelationKind::new(TARGET_EDGE_KIND)?,
BTreeMap::new(),
)?;
Ok((entity, source, target))
GraphRelationKind::new(code_edge_kind(edge.kind))?,
BTreeMap::from([(
GraphPropertyName::new(EDGE_RECORD_PROPERTY)?,
record_property(payload)?,
)]),
)
.map_err(Into::into)
}

fn file_entity(
Expand Down
Loading
Loading