Ruby require paths match by bare segment, so rack/protection/base makes lib/sinatra/base.rb reachable — which blocks treating autoload as the dependency it is #297

Open
opened 2026-09-23 18:13:04 +02:00 by buildagent · 0 comments
Member

From the same Ruby dogfood as #294 (§3.3 of the report): get_dependencies on auth.rb missed lib/zammad_mcp.rb, which loads it with autoload :Auth, "zammad_mcp/auth".

The fix for that is one line, and it was measured and reverted

autoload loads a file exactly as require does, just later. The extractor's emit_require already takes the first STRING argument, which for autoload skips the constant's symbol and lands on the path, so dispatching autoload there records the right dependency. A test pinned it and its mutation ran red.

Measured on ruby-sinatra (22 autoload calls in rack-protection/lib/rack/protection.rb): imports 231 -> 251, and 17 binds changed, every one a phantom, all tier 3 (import boost):

protection.rb:37-54  use ::Rack::Protection::X, options   -> Sinatra::use          (x16)
protection.rb:31     options.fetch(:without_session, …)    -> Sinatra::TemplateCache::fetch

Line 37's use is Rack::Builder#use (the block is Rack::Builder.new do … end); line 31's fetch is Hash#fetch. Neither is in this repository. So autoload was reverted — the same bar #294 held every encoding to.

Why: a Ruby require path is matched by segment runs

autoload :Base, 'rack/protection/base' produces the import require 'rack/protection/base' would. The import-key machinery (import_run_candidates and the tier-3 reachability joins) offers anchored segment RUNS of the specifier. protection names a project file, so it anchors the run base, and the file key base matches rack/protection/base.rb and lib/sinatra/base.rb. Sinatra::use is then the only use in a reachable file, and tier 3 takes it.

This is not autoload-specific: every require 'x/y/base' in the corpus already widens reachability the same way. Autoload only made it visible by adding twenty such imports in one file.

What would close it

A Ruby require/require_relative path names a FILE by its load-path-relative path: 'rack/protection/base' is some <load path>/rack/protection/base.rb. So a Ruby import should reach files whose path ENDS with the whole specifier (/rack/protection/base.rb), not any file sharing one segment — the structural fact require itself obeys.

  1. Ruby import reachability by full-suffix match on the specifier (require_relative already resolves relatively via temp.import_rel).
  2. Measure bind-for-bind on ruby-sinatra: expect require-driven phantoms to FALL, which is itself the evidence this is right, and read every lost bind at source.
  3. Then dispatch autoload to emit_require in both extractors (builtin and the de.h-dv.ruby guest, for ruby_package_parity), and re-measure — the 17 above must not come back.

Related: #294, #194, I023 (why same-directory and import keys are Ruby's only locality signals).

From the same Ruby dogfood as #294 (§3.3 of the report): `get_dependencies` on `auth.rb` missed `lib/zammad_mcp.rb`, which loads it with `autoload :Auth, "zammad_mcp/auth"`. ## The fix for that is one line, and it was measured and reverted `autoload` loads a file exactly as `require` does, just later. The extractor's `emit_require` already takes the first STRING argument, which for `autoload` skips the constant's symbol and lands on the path, so dispatching `autoload` there records the right dependency. A test pinned it and its mutation ran red. Measured on ruby-sinatra (22 `autoload` calls in `rack-protection/lib/rack/protection.rb`): imports 231 -> 251, and **17 binds changed, every one a phantom**, all tier 3 (import boost): ``` protection.rb:37-54 use ::Rack::Protection::X, options -> Sinatra::use (x16) protection.rb:31 options.fetch(:without_session, …) -> Sinatra::TemplateCache::fetch ``` Line 37's `use` is `Rack::Builder#use` (the block is `Rack::Builder.new do … end`); line 31's `fetch` is `Hash#fetch`. Neither is in this repository. So autoload was reverted — the same bar #294 held every encoding to. ## Why: a Ruby require path is matched by segment runs `autoload :Base, 'rack/protection/base'` produces the import `require 'rack/protection/base'` would. The import-key machinery (`import_run_candidates` and the tier-3 reachability joins) offers anchored segment RUNS of the specifier. `protection` names a project file, so it anchors the run `base`, and the file key `base` matches `rack/protection/base.rb` **and `lib/sinatra/base.rb`**. `Sinatra::use` is then the only `use` in a reachable file, and tier 3 takes it. This is not autoload-specific: every `require 'x/y/base'` in the corpus already widens reachability the same way. Autoload only made it visible by adding twenty such imports in one file. ## What would close it A Ruby `require`/`require_relative` path names a FILE by its load-path-relative path: `'rack/protection/base'` is some `<load path>/rack/protection/base.rb`. So a Ruby import should reach files whose path ENDS with the whole specifier (`/rack/protection/base.rb`), not any file sharing one segment — the structural fact `require` itself obeys. 1. Ruby import reachability by full-suffix match on the specifier (`require_relative` already resolves relatively via `temp.import_rel`). 2. Measure bind-for-bind on ruby-sinatra: expect `require`-driven phantoms to FALL, which is itself the evidence this is right, and read every lost bind at source. 3. Then dispatch `autoload` to `emit_require` in both extractors (builtin and the `de.h-dv.ruby` guest, for `ruby_package_parity`), and re-measure — the 17 above must not come back. Related: #294, #194, I023 (why same-directory and import keys are Ruby's only locality signals).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
h-dv/code-index#297
No description provided.