fix(native): write dynamic_kind for sink edges in native orchestrator path#1698
Conversation
… path `insert_call_edge_rows` in pipeline.rs mapped ComputedEdge→EdgeRow but silently dropped dynamic_kind, so native sink edges landed in the DB with dynamic_kind IS NULL. The benchmark filter added in 39b9a00 (`OR e.dynamic_kind IS NULL`) then matched those NULL-kind rows and counted them as false positives, breaking precision for dynamic-javascript, dynamic-typescript, and dynamic-scala. Two-layer fix: 1. edges.rs: add dynamic_kind: Option<String> to EdgeRow and update do_insert_edges to a 6-column INSERT (CHUNK 199→165 to stay under SQLite's 999 bind-parameter limit). None binds as SQL NULL for all non-sink edges; Some(dk) writes the kind string for sink edges. 2. pipeline.rs: pass e.dynamic_kind.clone() in insert_call_edge_rows so ComputedEdge.dynamic_kind reaches the DB insert. 3. resolution-benchmark.test.ts: add AND tgt.kind != 'file' to extractResolvedEdges — the correct semantic filter since sink edges are the only calls that ever target the file node, and no legitimate resolution does. This filter is correct regardless of dynamic_kind being set, removing the dependency on the back-fill path.
Greptile SummaryThis PR fixes a silent field-drop bug in the native orchestrator path where
Confidence Score: 5/5Safe to merge — the change is a targeted one-field fix in the Rust native orchestrator path with no behavioural changes to the JS/WASM path, and the benchmark filter gains a semantically-correct authoritative guard. The root-cause field drop is definitively fixed in No files require special attention — all three touched code locations (edges.rs, pipeline.rs, resolution-benchmark.test.ts) are clean and consistent with each other. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Extractor as Rust Extractor
participant Builder as build_edges (Rust)
participant Pipeline as pipeline.rs
participant EdgeRepo as edges.rs (do_insert_edges)
participant DB as SQLite edges table
Extractor->>Builder: "ComputedEdge { dynamic_kind: Some(eval) }"
Builder->>Pipeline: "insert_call_edge_rows(&edges)"
Pipeline->>Pipeline: map ComputedEdge to EdgeRow
note over Pipeline: was: dynamic_kind dropped
note over Pipeline: now: dynamic_kind: e.dynamic_kind.clone()
Pipeline->>EdgeRepo: "do_insert_edges(conn, &edge_rows)"
EdgeRepo->>DB: INSERT INTO edges (source_id, target_id, kind, confidence, dynamic, dynamic_kind)
note over EdgeRepo,DB: 165 rows x 6 params = 990 under 999 limit
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Extractor as Rust Extractor
participant Builder as build_edges (Rust)
participant Pipeline as pipeline.rs
participant EdgeRepo as edges.rs (do_insert_edges)
participant DB as SQLite edges table
Extractor->>Builder: "ComputedEdge { dynamic_kind: Some(eval) }"
Builder->>Pipeline: "insert_call_edge_rows(&edges)"
Pipeline->>Pipeline: map ComputedEdge to EdgeRow
note over Pipeline: was: dynamic_kind dropped
note over Pipeline: now: dynamic_kind: e.dynamic_kind.clone()
Pipeline->>EdgeRepo: "do_insert_edges(conn, &edge_rows)"
EdgeRepo->>DB: INSERT INTO edges (source_id, target_id, kind, confidence, dynamic, dynamic_kind)
note over EdgeRepo,DB: 165 rows x 6 params = 990 under 999 limit
Reviews (3): Last reviewed commit: "fix(benchmark): add comment explaining c..." | Re-trigger Greptile |
| AND tgt.kind != 'file' | ||
| AND (e.confidence IS NULL OR e.confidence > 0 OR e.dynamic_kind IS NULL) |
There was a problem hiding this comment.
Redundant filter after Rust fix
With dynamic_kind now correctly written to the DB for sink edges, the new AND tgt.kind != 'file' guard already excludes all sink edges semantically. The residual third clause OR e.dynamic_kind IS NULL was the original root-cause condition and is now effectively dead for correctly-built databases — any edge with confidence = 0 and tgt.kind != 'file' that also has dynamic_kind IS NULL would be a DB anomaly rather than a sink edge.
Both filters together are harmless and the belt-and-suspenders approach is reasonable as defence-in-depth, but a brief comment clarifying why the compound condition is kept (e.g., for databases built before the Rust fix) would prevent future confusion.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Added a 4-line comment above the two guards explaining: (1) tgt.kind != 'file' is the authoritative semantic guard for sink edges (the only calls edges that target file nodes), and (2) the compound confidence/dynamic_kind clause is kept as belt-and-suspenders for databases built before the Rust fix in #1698 where dynamic_kind was NULL for sink edges.
…xtractResolvedEdges (#1698) The tgt.kind != 'file' guard is the authoritative semantic exclusion for sink edges; the confidence/dynamic_kind clause is kept as belt-and-suspenders for databases built before the native fix where dynamic_kind was NULL.
Summary
insert_call_edge_rowsinpipeline.rsmappedComputedEdge → EdgeRowbut silently droppeddynamic_kind. Native sink edges (eval, computed-key, unresolved-dynamic) were inserted withdynamic_kind IS NULL. The benchmark filter added in 39b9a00 (OR e.dynamic_kind IS NULL) then included these rows as false positives, breaking precision fordynamic-javascript,dynamic-typescript, anddynamic-scala.dynamic_kind: Option<String>toEdgeRow; updatedo_insert_edgesto a 6-column INSERT (CHUNK 199→165 to stay under SQLite's 999 param limit); wiree.dynamic_kind.clone()throughinsert_call_edge_rowsso the field reaches the DB.AND tgt.kind != 'file'toextractResolvedEdges— the correct semantic filter since sink edges are the onlycallsedges that ever target the file node, removing the dependency ondynamic_kindbeing set.Test plan
npm testpasses locally (all 206 tests green, including the 4 previously failing dynamic-* precision gates)dynamic-javascriptprecision 100%, recall ≥ 75%dynamic-typescriptprecision 100%, recall ≥ 75%dynamic-scalaprecision 100%, recall 100%