Skip to content

fix(extract): stop cross-project edges from platform imports and shared npm deps (#3237) - #3262

Open
abhay-codes07 wants to merge 1 commit into
Graphify-Labs:v8from
abhay-codes07:fix/monorepo-import-collisions
Open

fix(extract): stop cross-project edges from platform imports and shared npm deps (#3237)#3262
abhay-codes07 wants to merge 1 commit into
Graphify-Labs:v8from
abhay-codes07:fix/monorepo-import-collisions

Conversation

@abhay-codes07

Copy link
Copy Markdown
Contributor

Closes #3237 (classes 1 and 2).

The problem

Two mechanisms produced confident (EXTRACTED) cross-project edges between symbols that merely share a name — 20 of the 25 cross-project edges the reporter audited:

  1. JVM/Android platform imports emit a repo-collidable bare-stem target. _import_kotlin/_import_java target _make_id(last_segment). For import java.util.UUID that is the byte-exact id of the npm dependency node another project's package.json mints for uuid — the reporter's five-file UUID case reproduces verbatim. And when nothing owns the bare id, it can still ride build.py's pre-migration alias index (Silent data loss: doc files with the same name in different directories produce colliding node IDs across extraction chunks #1504) onto whichever unrelated same-stem file uniquely claims it — import android.graphics.Color in project A binds to project B's ui/theme/Color.kt file node. Both paths reproduce on 0.9.53 from four-file corpora.

  2. package.json dependency entries mint a global bare target node per package name (_make_id(key) in the json extractor), so two projects that independently install typescript were joined through it — including the reporter's pair pinned to different majors.

The change

Both sites get the ref external namespace — the same cure _resolve_js_import_target already applies to unresolvable JS bare specifiers (#1638), and the J-4 convention extends/$ref values already use:

A side effect worth having: a TS import { v4 } from "uuid" already targets _make_id("ref", "uuid") (#2457) — it now lands on the manifest's dependency node instead of dangling, so code links to the manifest that declares the package.

Class 3 (minified-bundle symbol nodes) is a separate detection question and is not touched here; the non-imported Result-supertype case in class 1 is the type-stub fragmentation tracked in #3252.

Tests

tests/test_monorepo_import_collisions.py — 7 tests: the UUID byte-collision and the Color alias-ride (built end-to-end from the issue's shapes) no longer cross projects; a repo-local Kotlin FQN import still resolves (#2526 control); Kotlin and Java platform imports emit ref_-namespaced targets while repo-shaped imports keep the bare stem; two manifests share exactly one type=module registry node with no direct project-to-project edge; and the TS bare-specifier→manifest bind exists. With the fix reverted, 6 of 7 fail (the #2526 control rightly keeps passing). Related suites unchanged: dedup/kotlin/java/import suites (234 passed) and the json/config/manifest slice (332 passed); the full suite matches a fresh same-version v8 baseline.

Copilot AI lite review requested due to automatic review settings September 1, 2026 18:20

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) tested, no difference found (not proven).


Graphify review — findings

Routes JVM/Android platform and Kotlin-stdlib imports (roots like java., android., kotlin.) to the external ref namespace via _JVM_PLATFORM_PACKAGE_ROOTS in _import_java and _import_kotlin, so their bare last-segment targets can no longer byte-collide with unrelated repo nodes or ride the alias index onto same-stem files in a monorepo; unresolvable platform imports drop as external instead of dangling. Mints package.json dependency entries as a shared type="module" node under _make_id("ref", key) in extract_json, so two manifests naming the same npm package collapse to one anchor and JS bare specifiers land on it rather than being joined across projects. Exempts equal-labelled module-vs-module id collisions from the collision warning in _report_id_collision since those shared anchors collapse losslessly by design.

Worth a look

  • package.json dependency node ids changed from bare package ids to ref-prefixed idsgraphify/extractors/json_config.py:200 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2000 functions depend on the 304 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 524 callers, 43 callees
  • new: _rebuild_code() — 113 callers, 50 callees
  • new: deduplicate_entities() — 63 callers, 21 callees
  • new: build_merge() — 62 callers, 13 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 123 callees
  • new: build() — 42 callers, 4 callees
  • …and 31 more — each is listed as a finding

Verification — 2000 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1787 function(s) in the blast radius were not formally verified this run

Formal verification

No difference found (not proven): No behavior difference found in \_report\_id\_collision (not a proof).

The verifier ran both versions of \_report\_id\_collision on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify \_import\_java.

The verifier did not have enough to check \_import\_java, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_import\_kotlin.

The verifier did not have enough to check \_import\_kotlin, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify extract\_json.

The verifier did not have enough to check extract\_json, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 39 more finding(s) on lines outside this diff (see the check run).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses monorepo cross-project edge pollution caused by name-colliding import targets and shared npm dependency nodes, by introducing a non-collidable external ref namespace for platform imports and registry package anchors.

Changes:

  • Namespace JVM/Android/Kotlin platform import targets under ref to prevent collisions with repo-local nodes during build-time alias remapping.
  • Namespace package.json dependency targets under ref and mark them as type="module" to model registry packages as shared external anchors.
  • Add an end-to-end regression test suite covering the reported collision scenarios and expected resolution behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
tests/test_monorepo_import_collisions.py Adds regression tests reproducing and preventing cross-project binding via platform imports and shared npm deps.
graphify/extractors/json_config.py Changes dependency target node IDs to ref_* and marks them as type="module" to create a shared registry anchor node.
graphify/extract.py Routes JVM/Android/Kotlin platform imports to ref_* targets to prevent repo-collidable bare-stem IDs.
graphify/dedup.py Suppresses collision warnings for intentional type="module" duplicates with the same normalized label.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +215 to +216
add_node(dep_nid, key, line, file_type="concept",
node_type="module")
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.

Monorepo cross-project name collisions: platform imports, npm package names, and minified-bundle symbols bind to unrelated same-named nodes

2 participants