Skip to content

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

Open
abhay-codes07 wants to merge 2 commits 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 2 commits into
Graphify-Labs:v8from
abhay-codes07:fix/monorepo-import-collisions

Conversation

@abhay-codes07

@abhay-codes07 abhay-codes07 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Closes #3237 (classes 1 and 2). Rebased onto v8 (0.9.55).

Scope note after the rebase: 0.9.55's json_config fix independently landed the ref-namespaced dependency id and re-sourced the dep edge at the manifest, which covers part of what this PR originally carried. What remains here is everything that fix does not reach: the JVM/Android platform-import side of class 1, and the shared-anchor model that stops N manifests from being salted apart (and warned about) per file.

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 byte-collides with any unrelated node collapsing to uuid, and even with nothing owning the bare id it can 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. Reproduces on 0.9.55 from a four-file corpus.

  2. N manifests declaring the same npm package still fragment. With 0.9.55's ref_<pkg> ids, two package.json files now mint the same id from different files, so _disambiguate_colliding_node_ids salts them apart per file and dedup prints a "minted by two different files … dropping" WARNING — for a collapse that is exactly the intent (one registry package).

The change

A side effect worth having: a TS import { v4 } from "uuid" (which targets _make_id("ref", "uuid") since #2457) 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; both manifests wire to exactly one shared type=module registry node with no direct project-to-project edge; and the TS bare-specifier→manifest bind exists. Against 0.9.55 without this change, 4 of 7 fail (the other three cover the ground 0.9.55's json_config fix already holds, kept as regression guards). Related suites unchanged; the full suite matches a fresh same-version v8 baseline.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q

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")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Kept file_type="concept" deliberately: it's been this node's classification since it was introduced (0.9.55's json_config fix kept it too), and the node models an external registry package, not repo code. The anchor behavior doesn't ride on file_type — imports bind by node id, and both the disambiguation and dedup exemptions gate on type == "module" + label only. Switching to code would surface npm packages in code-centric views, which seems like the wrong trade for this PR's scope.

abhay-codes07 and others added 2 commits September 7, 2026 23:10
v8 independently landed the ref-namespaced dependency id and re-sourced the
dep edge at the manifest file (Graphify-Labs#1764 self-loop fix), so the shared-anchor
test now asserts both manifests wire to the one type=module node from their
file nodes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q
@abhay-codes07
abhay-codes07 force-pushed the fix/monorepo-import-collisions branch from 3dc2d31 to eee8284 Compare September 7, 2026 17:42

@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/Kotlin-stdlib platform imports (roots like java., android., kotlin.) to the external ref namespace instead of a bare last-segment id, so import java.util.UUID no longer byte-collides with an npm uuid dependency node or rides build's alias index onto an unrelated same-stem file. Stamps package.json dependency ref nodes with type="module" so manifests declaring the same package share one anchor under the #1327 module exemption, and suppresses the id-collision warning in _report_id_collision for equal-labelled module-vs-module survivors. Repo-local FQN imports still resolve to their real node.

Worth a look

  • Absolute source paths make cross-project direct-edge check ineffectivetests/test_monorepo_import_collisions.py:232 · 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 — 2089 functions depend on the 316 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 548 callers, 43 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: deduplicate_entities() — 71 callers, 22 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, 124 callees
  • new: build() — 42 callers, 4 callees
  • …and 34 more — each is listed as a finding

Verification — 2089 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: 1876 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

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

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