Skip to content

fix: persist deleted-export advisories so check survives purge ordering#2068

Open
carlos-alm wants to merge 1 commit into
fix/issue-1929-bare-super-constructor-calls-are-neverfrom
fix/issue-1938-follow-up-checknodeletedexportsinuse-misses
Open

fix: persist deleted-export advisories so check survives purge ordering#2068
carlos-alm wants to merge 1 commit into
fix/issue-1929-bare-super-constructor-calls-are-neverfrom
fix/issue-1938-follow-up-checknodeletedexportsinuse-misses

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

checkNoDeletedExportsInUse (#1806) detects an exported function/method/class lost when a file is deleted in its entirety, but only by querying the current DB for that file's rows. Once any codegraph build purges the deleted file's nodes/edges rows — detectChanges does this unconditionally for any file no longer found on disk, regardless of whether codegraph check has run yet — the predicate has nothing left to query and the violation silently goes undetected. This is purely a function of external invocation ordering (e.g. this repo's own update-graph.sh hook firing a plain codegraph build before pre-commit.sh's check runs), not something the predicate itself can control.

Closes #1938

Fix

Capture a durable, purge-order-independent snapshot instead of relying on live rows surviving until check runs:

  • New deleted_export_advisories table (migration v21, mirrored in both src/db/migrations.ts and crates/codegraph-core/src/db/connection.rs) stores, per deleted file, each lost export's cross-file consumers.
  • detectChanges (domain/graph/builder/stages/detect-changes.ts) captures this snapshot at the exact point it computes the removed-file set — before purging — via new recordDeletedExportAdvisories/clearDeletedExportAdvisories in db/repository/deleted-export-advisories.ts. A file that reappears under the same path has its stale advisory cleared before its nodes are (re)inserted, so a resolved deletion never misattributes a stale violation.
  • checkNoDeletedExportsInUse now queries live nodes first (unchanged behavior when nothing has purged yet) and falls back to the persisted advisory table only when a deleted file's live rows are already gone.
  • Extracted the shared "exported function/method/class definitions" and "cross-file consumers of a node" queries into db/repository/nodes.ts/edges.ts (findExportedDefinitions, findExternalConsumers) so both the live-DB path and the advisory-capture path use identical semantics.

Native engine parity: the native Rust orchestrator's incremental fast path (crates/codegraph-core/.../pipeline.rs's save_and_purge_changed) bypasses the JS detectChanges stage entirely for a plain incremental build — this is the default code path when the native engine is available. Mirrored the capture/clear logic there (record_deleted_export_advisories/clear_deleted_export_advisories in detect_changes.rs) so both engines produce identical results. Verified via a real buildGraph() end-to-end regression test that runs against both engines.

While implementing, found and fixed a real bug this surfaced: the Rust table_queryable existence-probe used query_row(...).is_ok(), which returns Err(QueryReturnedNoRows) — not Ok — for a table that exists but is still empty, causing the whole advisory capture to silently no-op on a freshly-migrated table. Caught by the new Rust unit tests; fixed by treating QueryReturnedNoRows as "exists".

Test plan

  • cargo test --lib (crates/codegraph-core) — 649 passed, including 8 new tests for record_deleted_export_advisories/clear_deleted_export_advisories
  • npm test — 4128 tests, 1 pre-existing unrelated failure (node-order non-determinism in checkNoNewCycles, filed as test: checkNoNewCycles reports the same cycle with non-deterministic node order across calls #2067), everything else green including 30 new tests across tests/unit/deleted-export-advisories.test.ts, the new checkNoDeletedExportsInUse advisory-fallback describe block in tests/integration/check.test.ts, and a new end-to-end regression test (tests/integration/issue-1938-deleted-export-advisory-persistence.test.ts) exercising both WASM and native engines via a real buildGraph() → delete → rebuild → checkData flow
  • npm run lint — clean (only a pre-existing unrelated warning in an untouched fixture file)
  • codegraph diff-impact --staged -T — 28 symbols changed, 13 callers affected across 6 files, no cycles, no dead exports

Out-of-scope findings filed separately

Stacked on #1937/#1929 (base branch fix/issue-1929-bare-super-constructor-calls-are-never) — only the check/detect-changes/migrations/test diff described above is this issue's change.

checkNoDeletedExportsInUse (#1806) only ever queried the live nodes/edges
tables, so once any codegraph build purged a deleted file's rows -
detectChanges does this unconditionally, independent of whether
codegraph check has run yet - the violation silently went undetected.

Capture a durable snapshot of each deleted file's exported defs and
their external consumers at the exact point detectChanges/the native
orchestrator computes the removed-file set, BEFORE purging. check now
falls back to this deleted_export_advisories table whenever the live
nodes for a deleted file are already gone. Advisory rows are cleared
whenever a file reappears under the same path, so a resolved deletion
never misattributes a stale violation.

Mirrored in the native Rust orchestrator (record/clear_deleted_export_
advisories in detect_changes.rs, wired into pipeline.rs's
save_and_purge_changed), since the native incremental fast path bypasses
the JS detectChanges stage entirely.

Impact: 28 functions changed, 13 affected
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR persists deleted-export evidence so signature checks can survive graph purges. The main changes are:

  • Adds a deleted_export_advisories table in TS and Rust migrations.
  • Records and clears advisory rows during JS and native build purge flows.
  • Reuses shared exported-definition and external-consumer queries.
  • Falls back to advisory rows in checkNoDeletedExportsInUse after live rows are gone.
  • Adds unit and integration coverage for the advisory path.

Confidence Score: 4/5

The advisory persistence path can still drop evidence after repeated builds of the same deleted file.

  • First build records the advisory and purges the deleted file's live rows.
  • A later build can detect the same removed file again and delete the saved advisory.
  • The check fallback then has no live rows and no advisory rows, so the violation is missed.

src/db/repository/deleted-export-advisories.ts and crates/codegraph-core/src/domain/graph/builder/stages/detect_changes.rs

Important Files Changed

Filename Overview
src/db/repository/deleted-export-advisories.ts Adds advisory record, clear, and read helpers; the record helper can erase existing rows on repeated removed-file builds.
crates/codegraph-core/src/domain/graph/builder/stages/detect_changes.rs Adds native advisory capture and clear helpers with matching query semantics and the same replacement-state concern.
src/features/check.ts Adds live-first advisory fallback for deleted-export checks.
src/domain/graph/builder/stages/detect-changes.ts Wires advisory clear and capture into JS scoped and incremental build flows before purge.
crates/codegraph-core/src/domain/graph/builder/pipeline.rs Wires advisory clear and capture into the native incremental fast path before purge.
src/db/migrations.ts Adds the TS migration for the advisory table and file index.
crates/codegraph-core/src/db/connection.rs Adds the matching Rust migration for the advisory table and file index.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
  participant B1 as Build N
  participant DB as graph.db
  participant B2 as Build N+1
  participant C as codegraph check
  B1->>DB: Capture advisory while deleted file nodes exist
  B1->>DB: Purge deleted file nodes and edges
  B2->>DB: Detect same removed file again
  B2->>DB: Delete existing advisory
  B2->>DB: Find no live nodes, insert no advisory
  C->>DB: Live query returns empty
  C->>DB: Advisory fallback returns empty
  C-->>C: Deleted export violation is missed
Loading
%%{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 B1 as Build N
  participant DB as graph.db
  participant B2 as Build N+1
  participant C as codegraph check
  B1->>DB: Capture advisory while deleted file nodes exist
  B1->>DB: Purge deleted file nodes and edges
  B2->>DB: Detect same removed file again
  B2->>DB: Delete existing advisory
  B2->>DB: Find no live nodes, insert no advisory
  C->>DB: Live query returns empty
  C->>DB: Advisory fallback returns empty
  C-->>C: Deleted export violation is missed
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix: persist deleted-export advisories s..." | Re-trigger Greptile

const tx = db.transaction(() => {
const now = Date.now();
for (const file of removedFiles) {
deleteStmt.run(file);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Repeat Build Erases Advisory

When the same deleted file is seen by a later incremental build, the prior advisory is deleted before a replacement is known to exist. Because the first build already purged that file's live nodes, the second capture finds no exported definitions and inserts nothing, so a later codegraph check has neither live rows nor an advisory to report the deleted export.

Fix in Claude Code

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

28 functions changed13 callers affected across 6 files

  • DeletedExportAdvisoryEntry.file in src/db/repository/deleted-export-advisories.ts:23 (0 transitive callers)
  • DeletedExportAdvisoryEntry.name in src/db/repository/deleted-export-advisories.ts:24 (0 transitive callers)
  • DeletedExportAdvisoryEntry.kind in src/db/repository/deleted-export-advisories.ts:25 (0 transitive callers)
  • DeletedExportAdvisoryEntry.line in src/db/repository/deleted-export-advisories.ts:26 (0 transitive callers)
  • DeletedExportAdvisoryEntry.consumers in src/db/repository/deleted-export-advisories.ts:27 (0 transitive callers)
  • DeletedExportAdvisoryRow.file in src/db/repository/deleted-export-advisories.ts:31 (0 transitive callers)
  • DeletedExportAdvisoryRow.name in src/db/repository/deleted-export-advisories.ts:32 (0 transitive callers)
  • DeletedExportAdvisoryRow.kind in src/db/repository/deleted-export-advisories.ts:33 (0 transitive callers)
  • DeletedExportAdvisoryRow.line in src/db/repository/deleted-export-advisories.ts:34 (0 transitive callers)
  • DeletedExportAdvisoryRow.consumer_name in src/db/repository/deleted-export-advisories.ts:35 (0 transitive callers)
  • DeletedExportAdvisoryRow.consumer_file in src/db/repository/deleted-export-advisories.ts:36 (0 transitive callers)
  • DeletedExportAdvisoryRow.consumer_line in src/db/repository/deleted-export-advisories.ts:37 (0 transitive callers)
  • hasAdvisoryTable in src/db/repository/deleted-export-advisories.ts:47 (8 transitive callers)
  • recordDeletedExportAdvisories in src/db/repository/deleted-export-advisories.ts:71 (4 transitive callers)
  • clearDeletedExportAdvisories in src/db/repository/deleted-export-advisories.ts:117 (4 transitive callers)
  • getDeletedExportAdvisories in src/db/repository/deleted-export-advisories.ts:140 (3 transitive callers)
  • findExternalConsumers in src/db/repository/edges.ts:220 (7 transitive callers)
  • findExportedDefinitions in src/db/repository/nodes.ts:246 (7 transitive callers)
  • handleScopedBuild in src/domain/graph/builder/stages/detect-changes.ts:605 (3 transitive callers)
  • handleIncrementalBuild in src/domain/graph/builder/stages/detect-changes.ts:641 (3 transitive callers)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant