GitNexus/docs/languages/local-import-scoping.md
Gergő Magyar 01af833875
fix(scope): preserve lexical bindings in HTTP wrapper detection (#3632)
* docs(scope): clarify lexical binding ownership and wrapper provenance

Checkpoint for #3630. Publishing the implemented source, regression tests, and benchmark harness as a draft PR; full-suite and final scaling validation remain in progress.

* fix(scope): publish #3630 docs/plans

* fix(scope): publish #3630 gitnexus/bench/lexical-binding-provenance

* fix(scope): publish #3630 gitnexus/bench/lexical-binding-provenance/fixtures

* fix(scope): publish #3630 gitnexus/bench/lexical-binding-provenance/results

* fix(scope): publish #3630 gitnexus/src/core/ingestion

* fix(scope): publish #3630 gitnexus/src/core/ingestion/languages/typescript

* fix(scope): publish #3630 gitnexus/src/core/ingestion/languages

* fix(scope): publish #3630 gitnexus/src/core/ingestion/pipeline-phases

* fix(scope): publish #3630 gitnexus/src/core/ingestion/route-extractors

* fix(scope): publish #3630 gitnexus/src/core/ingestion/workers

* fix(scope): publish #3630 gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib

* fix(scope): publish #3630 gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity

* fix(scope): publish #3630 gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/packages/http

* fix(scope): publish #3630 gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/packages/http/src

* fix(scope): publish #3630 gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/server

* fix(scope): publish #3630 gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/web

* fix(scope): publish #3630 gitnexus/test/integration/resolvers

* fix(scope): publish #3630 gitnexus/test/unit

* fix(scope): publish #3630 gitnexus/test/unit/group

* fix(scope): publish #3630 gitnexus/test/unit/scope-resolution

* fix(scope): publish #3630 gitnexus/test/unit/scope-resolution/typescript

* fix(scope): publish #3630 gitnexus-shared/src/scope-resolution

* fix(scope): treat wrapped assignment targets as writes

`(app) = new Map()`, `(app as T) = x` and `app! = x` assign the same binding as
`app = x`, but route-fact invalidation and the shared pattern walker stopped at
the wrapper, so Express provenance survived the write and produced a false
route. Recurse through parenthesized, as/satisfies and non-null targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(routes): resolve wrappers through overloads, declarator lists and barrels

Three wrapper shapes lost their identity:

- Bodyless overload signatures were indexed as separate definitions sharing
  the implementation's binding, so the binding looked ambiguous and both local
  and imported calls were rejected. Signatures now declare no callable.
- Facts for `const base = 0, request = (url) => fetch(url)` followed the first
  declarator. The captured name now selects its own declarator.
- An import resolved to a workspace barrel stopped at index.ts. Workers now
  record named, star and imported-then-exported re-exports, and resolution
  follows them to the declaring module: local and named exports shadow
  `export *`, star exports never carry `default`, and conflicting star exports
  block the name. No repository-wide name fallback is restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(scope): assert closure ownership against the declaration

The closure-write assertion compared one lookup with itself, which also passes
when both are undefined. Compare it with the outer declaration and the shadowing
parameter, and keep the memoization check explicit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(routes): share lexical helpers and release per-file HTTP contexts

- Share binding keys, parameter patterns and transparent expression wrappers
  from lexical-bindings instead of four local copies; drop the unused
  moduleStem export; name the inferred-source predicates in lookup.
- Key the per-file HTTP context by its tree rather than its ParsedFile.
  Workers retain ParsedFiles for the whole chunk, which kept every file's AST
  and traversal maps alive until the chunk flushed.
- Index re-exports by name, memoize barrel walks per starting export, and keep
  facts without import targets as-is instead of copying them.
- Move TS-only wrapper cases into the TS describe.each instead of branching
  inside the per-language loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(routes): keep wrapper identity for HOCs, CommonJS, object keys and fetch packages

Four definition shapes that main linked by name lost their wrapper identity:

- `const load = useCallback((url) => fetch(url))` and curried HOCs produced no
  facts because definition lookup stopped at the call; it now follows the
  callable argument, as the structural definition capture does.
- `module.exports = { request }`, `exports.x = …` and `module.exports.x = …`
  were not exports, so destructured `require` callers missed the wrapper.
  CommonJS export assignments are recorded when `module`/`exports` are the
  unshadowed globals, including functions defined inline in the export object.
- An object-literal key or method name was resolved as a lexical binding and
  collided with a same-named module wrapper. Only binding identifiers now name
  a definition's binding.
- `import fetch from 'node-fetch'` no longer counted as fetch. Imports of known
  fetch implementations (node-fetch, cross-fetch, isomorphic-fetch, the fetch
  member of undici and whatwg-fetch) are fetch evidence again; parameters and
  other local names still are not.

Reference identifiers are also resolved only when their name is bound to a
callable or class region; other identifiers cannot add a reachability edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(routes): drive the forwarding fallback case from data and cover re-export merges

Replace the boolean-switched forwarding fallback case with table rows instead
of branching inside the test, and assert that barrel re-export facts survive
worker sub-batch merges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): reject stale or dirty call-graph exports

The committed comparison named a candidate revision that is not on this branch,
and nothing stopped it. Comparison now requires both exports to come from clean
checkouts and the candidate export to come from the checkout running the
comparison.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): regenerate the call-graph comparison on the reviewed head

Clean baseline 565e50276 and clean candidate 8a5f7b738 on the frozen 432-file
corpus (fingerprint 88be1faf...): exactly one CALLS target changes, the
annotated closure probe `declaredOwner.writeAndRead` now calls `Base.m`
instead of `Foo.m`. The other 431 CALLS and all 11 METHOD_OVERRIDES edges are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): emit export provenance and review notes from the comparison

The comparison report carried export provenance and the expected-change
explanation only as hand-added fields, which a regeneration dropped. Emit
both exports' revisions, dirty flags and fingerprints, the unchanged CALLS
and METHOD_OVERRIDES counts, and an optional `--note` explanation directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): record calibrated scaling reports and the regenerated comparison

Duration-calibrated-v2 runs (5 warmups, 15 measured batches, 500 ms pilot
target) pinned to the same CPU, baseline then candidate back to back:

- Baseline 565e50276 exceeds the 1.8 normalized ceiling on six arms at 4x
  input: position-index 7.92, position-deep-completed 4.65, nested-helpers
  4.82, nested-helper-chain 14.05, reverse-chain 4.31, name-collisions 2.61
  (medians).
- Candidate 94f1a055b passes --check: every arm stays at or below 1.32 at 4x
  for both minimum and median, with correct semantic guards on every arm.

The comparison is regenerated from clean exports with its provenance and
review note emitted by the tool: one expected CALLS target change, 431 CALLS
and 11 METHOD_OVERRIDES unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(scope): replace recursion with explicit stacks in new traversals

The binding work added five recursive traversals whose depth follows input
shape: region summaries (callable nesting), barrel resolution (re-export
chain length), pattern binding names (destructuring depth), route-fact write
invalidation (pattern depth), and the position index's max-end tree. Each
now uses an explicit stack with the same visiting order and results.

Regression cases cover the inputs the recursive versions could not handle:
a 20,000-deep destructuring pattern and a 50,000-module re-export chain both
threw RangeError before this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(routes): avoid a full-tree script walk when building lexical bindings

Route-fact extraction ran 1.78x slower than main on a 512-function file
(87 ms vs 49 ms) because createLexicalBindings walked every AST node in script
to detect collapsed parameter/body environments, which only functions with
parameter defaults or computed keys can have.

- Find functions with the native type search, skip plain parameter lists
  without any subtree search, and walk only the formals and bodies of the
  rest. Detection is unchanged, except that functions inside syntax-error
  regions are now also checked, which can only add fail-closed ambiguity.
- Read each node's id and start position once per lookup instead of
  repeatedly crossing into native getters.

The same extraction now takes about 56 ms against 51 ms on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(routes): walk wrapper evidence with a tree cursor

The wrapper context visited every node through namedChildren, which
materializes each child as a SyntaxNode and records a parent edge by node
id. A tree cursor reads node types and offsets natively, so only the node
types the handlers inspect become SyntaxNodes. Declared identifiers are
keyed by start offset, and the walk records the ancestor facts later
queries need (stored binding, default export, declarator of a name)
instead of a parent map.

On the 512-wrapper scaling input, context construction drops from about
58 ms to 48 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): regenerate the call-graph comparison on the cursor walk

The candidate export at 6061ca71b has the same fingerprint as the previous
candidate: the traversal rewrite changes no edge. The one expected target
change and the 431 unchanged CALLS and 11 METHOD_OVERRIDES remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): record final scaling reports on the cursor walk

Baseline 565e50276 and candidate 61de364ff ran back to back on CPU 23
with the duration-calibrated-v2 protocol. Every candidate check stays at
or below a 1.32 normalized factor against the 1.8 ceiling; the baseline
fails six of nine arms. Workload fingerprints agree except the documented
name-collision false positive in the baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(plan): checkpoint the final review, scaling and suite results for #3630

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(routes): treat export aliases of one wrapper as one star-export target

Star-export candidates were deduplicated by the resolved export name, so
`export { request, request as send }` reached through two barrels counted
as two targets and blocked the import, dropping the consumer's FETCHES
edge. Candidates are now keyed by the declaration each export name names,
matching ECMAScript ResolveExport's binding comparison; distinct
declarations exported under the same name stay ambiguous.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): regenerate scaling and call-graph reports on the alias fix

Baseline 565e50276 and candidate 4d62bd045 ran back to back on CPU 23.
Every candidate check stays at or below 1.22 against the 1.8 ceiling;
the baseline still fails six of nine arms. The CALLS/METHOD_OVERRIDES
export is unchanged (fingerprint 4db24f57), so the comparison only
records the new candidate revision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(plan): record the export-alias fix and the regenerated reports for #3630

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(scope): keep the type a write gives an undeclared name

A constructor write to a name no scope declares (`client = new ApiClient()`
in sloppy JavaScript) declares nothing, so the lookup stays absent. The
carried constructor fact was dropped on that exit, which lost the receiver
type main had inferred for later reads in the writing scope. The absent
result now carries it, and findReceiverTypeBinding uses it before the
namespace fallbacks. Only complete-claim providers carry such a fact.

Tests pin the carried type per inferred source, the undeclared write, and
preservePosition across a module redirect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(routes): close the wrapper identity gaps found in review

- CommonJS wrappers captured as their assignment (`exports.x = function`,
  `module.exports.x = () =>`) produce facts again: findFunction follows the
  assignment's right side.
- Name-only fallback considers only definitions that can be the target: an
  unresolvable import names a module-level export, an undeclared global a
  top-level function. Nested and unexported helpers no longer erase a
  wrapper's edges.
- Only possible wrappers and module-level functions are emitted, and the
  routes phase builds the index only when routes and candidate calls exist.
- A rebound binding (later assignment, `var` initializer over a function
  declaration, reassigned export) proves no wrapper across files.
- `(<T>x) = ...` is a write; transparent wrappers skip type arguments.
- Calls inside recovered syntax are still collected; instance-field
  initializers resolve after the module body, so a later declaration is
  visible.
- Local exports are always recorded, so a star export conflicting with a
  non-callable export is ambiguous, and fully resolved barrel frames are
  shared across walks.
- HTTP hooks run per file under a guard; a failing file or one without a
  ParsedFile still records its function names so fallback declines.
- namesFetch is iterative, large patterns no longer spread into push, the
  facts cache is keyed by the name node, and the provider context cache
  checks its ParsedFile.

Tests cover each case; the behavior rows fail on the previous source.
The TS-only call cases no longer branch inside the test file, call targets
are asserted exactly, the function-type list is drift-checked, and the
persistence test joins the cross-platform worker list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(routes): describe wrapper fallback, barrel and recall boundaries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): count the new CommonJS fixture's dropped write site

`lib/cjs-member.js` in the fetch-wrapper target-identity fixture writes
`module.exports.arrowRequest`, a member write whose receiver is not typed.
That adds one `receiver-unresolved` write (190 -> 191 all-kind drops,
19 -> 20 writes). Call drops and shape-arm states are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): regenerate scaling and call-graph reports on the review fixes

Baseline 565e50276 and candidate 528ea1478 ran back to back on CPU 23.
Every candidate check stays at or below 1.27 against the 1.8 ceiling; the
baseline still fails six of nine arms. The CALLS/METHOD_OVERRIDES export
is unchanged (fingerprint 4db24f57), so the comparison only records the
new candidate revision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(plan): checkpoint the review fixes and regenerated reports for #3630

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(routes): share CommonJS checks and trim per-node work

- `isCommonJsGlobal`/`isModuleExports` in lexical-bindings replace the two
  copies in fetch-wrapper and framework-route-facts (both resolve the same
  scope from the same start position).
- LEXICAL_FUNCTION_TYPES is derived from the TypeScript FUNCTION_NODE_TYPES
  instead of a third hand-kept copy; the drift test still pins it.
- `unwrap` reads UNWRAPPED_TYPES, the set the cursor walk mirrors, and the
  class-body region is no longer shadowed under the name `deferred`.
- framework-route-facts reads each node's type once per loop iteration.
- The position index returns the latest-starting scope when it already
  contains the point, before the max-end tree search; that entry is the
  search's rightmost match.
- carried() returns a shared empty set for argument forms that cannot carry
  a value, the resolution file set is built without intermediate arrays,
  and the routes phase reads the call list once.

No behavior change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): regenerate scaling and call-graph reports on the refactor

Baseline 565e50276 and candidate 3db8657ba ran back to back on CPU 23.
Every candidate check stays at or below 1.32 against the 1.8 ceiling; the
baseline still fails six of nine arms. The CALLS/METHOD_OVERRIDES export
is unchanged (fingerprint 4db24f57). The later main merge (e2a92dab8)
touches no measured file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(plan): checkpoint the simplification pass and its reports for #3630

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address PR review feedback (#3632)

- Destructuring defaults (`{ fetch = globalThis.fetch } = {}`, renamed,
  array and local forms) are alias evidence for their leaf binding and
  no longer count a second initialization, which also invalidated
  bindings like `[send = other] = values`.
- `export default (request)` records the default export: the value is
  unwrapped before the identifier check.
- The HTTP fallback records and the collapsed-environment test row now
  say what they stand for.

Regression rows cover each form in JS, TS and TSX and fail on the
previous source; two fixture consumers cover imported wrappers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(bench): refresh lexical binding reports after review fixes

Regenerate clean baseline and candidate measurements at c263c4f72.
All candidate scaling checks pass, the semantic delta remains one CALLS
target, and receiver-resolution counts match the existing baseline.

* docs(plan): checkpoint refreshed review-fix measurements

Record c263c4f72 review fixes, the regenerated scaling and semantic
results, and the passing receiver and CI checks.

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:36:06 +01:00

306 lines
24 KiB
Markdown

# Local import scoping
GitNexus resolves statically known local imports through their lexical owner and
actual declaration identity. A nearer name that is unresolved, unavailable, or
noncallable blocks an unrelated outer or workspace fallback. An absent name can
continue through the existing lookup rules. This is static analysis: it does not
execute loaders or infer arbitrary runtime initialization order.
## Shared contract
Language rules stay in providers. The shared
[name-claim lookup](../../gitnexus-shared/src/scope-resolution/name-claims.ts)
distinguishes `absent`, `blocked`, and `resolved` before filtering by target kind.
Its [regressions](../../gitnexus/test/unit/scope-resolution/lexical-name-claims.test.ts)
cover noncallable barriers, unresolved imports, type/value lookup, declaration
positions, and inferred aliases that retain their initializer's lookup position.
Calls, constructors, receiver/type inference, inheritance, namespace lookup, and
fallback consumers use these ownership facts rather than independently choosing a
same-named symbol.
Ownership and inferred type facts are separate. For providers that mark their
name claims complete, an `assignment-inferred` or `constructor-inferred` fact
does not declare a name. Lookup continues to the actual declaration, import, or
shadowing barrier. Providers without complete claims retain their existing
ownership rules.
The [shared records](../../gitnexus-shared/src/scope-resolution/types.ts) carry
ownership, activation positions, directive destinations, and lookup purpose as
serializable data. They retain declaration identity and ambiguity; they contain
no AST nodes or callbacks. For imports, `declaredAtScope` records lexical location
independently of `runsOnlyWhenCalled`, which describes deferred execution. On a
`TypeRef`, `declaredAtScope` anchors type-name lookup where the annotation or
inference was written; it can differ from the binding's owner. A function-local
Rust `use` or C `#include` is not deferred merely because of its position; Python
and Ruby loading can be. The
[function-local import chain tests](../../gitnexus/test/unit/scope-resolution/function-local-import-chain.test.ts)
pin that distinction.
## Providers with expanded lexical support
These seven entries are part of the
[18-provider registry](../../gitnexus/src/core/ingestion/languages/index.ts).
The evidence links cover exact targets and negative cases, not just edge counts.
| Provider | Supported behavior | Source and regression evidence |
| --- | --- | --- |
| Python | Function-local named, aliased, and namespace imports; compiler-local ownership; ordered class lookup; `global`/`nonlocal` destinations. | [Lexical facts](../../gitnexus/src/core/ingestion/languages/python/lexical-bindings.ts), [local imports](../../gitnexus/test/unit/scope-resolution/python/python-local-imports.test.ts), [namespace semantics](../../gitnexus/test/integration/resolvers/python-namespace-semantics.test.ts) |
| TypeScript | Literal genuine `require`, immediately awaited `import`, and import-equals; namespace, named, and renamed bindings; type/value separation. | [Loader collector](../../gitnexus/src/core/ingestion/languages/typescript/local-loaders.ts), [loader tests](../../gitnexus/test/unit/scope-resolution/typescript/local-loader-imports.test.ts), [type ownership](../../gitnexus/test/unit/scope-resolution/typescript/lexical-type-ownership.test.ts) |
| JavaScript | The same supported runtime loaders and lexical barriers, using JavaScript captures for JS/JSX. | [Captures](../../gitnexus/src/core/ingestion/languages/javascript/captures.ts), [JS/JSX loader cases](../../gitnexus/test/unit/scope-resolution/typescript/local-loader-imports.test.ts) |
| Vue | Local loader facts in extracted scripts, with original declaration graph positions preserved. Explicit JS/JSX uses its own semantic grammar inside the worker; TS reuses the cached tree. | [Script delegation](../../gitnexus/src/core/ingestion/languages/vue/captures.ts), [direct/pre-extracted parity](../../gitnexus/test/unit/scope-resolution/typescript/local-loader-imports.test.ts), [real workers](../../gitnexus/test/integration/local-import-worker-parity.test.ts) |
| Rust | Lexical `use`, producer identity for imported factories/types, and Cargo-aware crate/module identity. Unresolved explicit imports block unrelated fallback. | [Lexical facts](../../gitnexus/src/core/ingestion/languages/rust/lexical-bindings.ts), [import targets](../../gitnexus/src/core/ingestion/languages/rust/import-target.ts), [local scope](../../gitnexus/test/integration/resolvers/rust-import-scope.test.ts), [Cargo boundaries](../../gitnexus/test/integration/resolvers/rust-cargo-target-fallback.test.ts) |
| C++ | Position-sensitive local `using` declarations/directives, separate from includes; propagated header claims retain unresolved named `using` barriers. | [Captures](../../gitnexus/src/core/ingestion/languages/cpp/captures.ts), [local using](../../gitnexus/test/integration/resolvers/cpp-local-using.test.ts), [header using](../../gitnexus/test/integration/resolvers/cpp-local-using-headers.test.ts) |
| Zig | Literal `@import` namespace/member bindings, block activation, container hoisting, stable deep aliases, and inline `@import` receivers. | [Captures](../../gitnexus/src/core/ingestion/languages/zig/captures.ts), [local imports](../../gitnexus/test/integration/resolvers/zig-local-imports.test.ts), [static gating](../../gitnexus/test/integration/resolvers/zig-static-gating.test.ts) |
### Python boundaries
Compiler-local binders include parameters, assignment patterns, loop/with/except
targets, match captures, augmented assignment, deletion, and comprehension/walrus
bindings. Comprehension targets do not leak, and the first iterable is evaluated
outside the comprehension environment. Class bodies use ordered locals; methods
skip class locals for bare-name lookup while explicit instance receivers remain
available. Annotation-only declarations distinguish compiler ownership from a
runtime store. These cases are characterized in the
[Python local-import tests](../../gitnexus/test/unit/scope-resolution/python/python-local-imports.test.ts).
Mixed import/value rebinding and conflicting import identities conservatively
abstain throughout the environment, including simple sequential rebinding; this
is not a control-flow evaluator. A definite `def`/`class` declaration can still
supply its own target after activation. The
[provider policy](../../gitnexus/src/core/ingestion/languages/python/lexical-bindings.ts)
and [rebinding regressions](../../gitnexus/test/integration/resolvers/python-namespace-semantics.test.ts)
define this boundary. There is no full annotation evaluator, `exec` evaluation,
or general closure scheduling model. A deferred `global`/`nonlocal` installer is
not assumed to have executed for unrelated callers.
Conditional imports with one compatible identity remain available for structural
reference resolution, including `TYPE_CHECKING` annotations and `try` imports.
Their runtime execution is not guaranteed by a resolved graph edge.
Default expressions look up names in the enclosing environment. FastAPI's
synthetic dependency graph caller is recorded separately as `callerScope`; it
does not move lexical lookup into the handler body. Both
[direct extraction](../../gitnexus/test/unit/scope-resolution/python/python-local-imports.test.ts)
and [worker transport](../../gitnexus/test/integration/local-import-worker-parity.test.ts)
check this distinction.
### JavaScript-family boundaries
Loader recognition checks AST shape and binding identity, including parameters,
catch/destructuring bindings, temporal-dead-zone ownership, and writes to the
loader or imported handle. CommonJS forwarding uses that same identity check.
Reassigning one destructured import leaves unchanged sibling re-exports intact.
Writes to ordinary locals retain their declared type and callable-value facts.
Unawaited dynamic imports remain Promise/file-dependency facts rather than module
namespace aliases. Computed specifiers, indirect loader/Promise chains, and
unsupported destructuring remain unresolved; see the
[loader regressions](../../gitnexus/test/unit/scope-resolution/typescript/local-loader-imports.test.ts).
#### Receiver type precedence
A constructor assignment inside a closure does not make that closure the owner
of the assigned name. Lookup may retain the nearest constructor fact on its path
to the owner when the owner has no type fact or only an inferred or propagated
fact. It keeps the complete selected `TypeRef`, including the scope, position,
and namespace in which its type name must resolve.
The owner's annotation, parameter annotation, return annotation, `self`, or
decorator fact takes precedence. For example, assigning `new Foo()` to a
parameter annotated `Base` inside a closure leaves receiver lookup anchored to
`Base`, including its inherited methods. Same-scope precedence and
`METHOD_OVERRIDES` relationships retain their existing rules. A nested write is
not promoted into an unconditional type fact for reads outside that closure.
These are static inference rules, not control-flow narrowing or a prediction of
the receiver's runtime class. The
[type ownership regressions](../../gitnexus/test/unit/scope-resolution/typescript/lexical-type-ownership.test.ts)
and [shared lookup regressions](../../gitnexus/test/unit/scope-resolution/lexical-name-claims.test.ts)
cover declaration ownership, fact precedence, and retained lookup anchors.
#### HTTP provenance
Express route receivers and fetch-wrapper analysis share
[per-file lexical bindings](../../gitnexus/src/core/ingestion/route-extractors/lexical-bindings.ts).
They track declaration identity across closures and check identity at each use;
an unrelated same-named declaration in a sibling scope does not invalidate a
binding. Writes to the tracked binding do invalidate its provenance. For a
forwarded Express parameter, an initialized `var` redeclaration or a body
function declaration is a write, while an uninitialized `var` declaration alone
preserves the parameter value. See the
[route provenance regressions](../../gitnexus/test/unit/framework-route-provenance.test.ts).
Automatic fetch-wrapper detection requires evidence of platform fetch. Bare
`fetch` must be unshadowed; `globalThis.fetch`, `window.fetch`, and `self.fetch`
require an unshadowed global root. Unresolved or blocked local names are not
globals. An injected `fetch` parameter alone is not evidence. A default such as
`fetch = globalThis.fetch` supplies potential platform evidence, not a guarantee
that callers use that default. A self-referential
`fetch = fetch` default or a locally shadowed `globalThis` does not supply it.
Stable aliases retain this provenance by binding identity.
Nested callable bodies contribute only when reachable from the function's
evaluated code through resolved binding references. Unused self-recursive or
mutually recursive helpers do not prove a wrapper. IIFEs, inline callbacks, and
directly escaping anonymous functions participate; a named stored helper needs
a reachable reference. Class heritage, computed keys, static fields, and static
blocks participate during class evaluation. Methods and instance fields remain
deferred until the class binding is referenced from reachable code. This is a
conservative approximation of potential execution: reading or returning a
function does not execute its body, and referencing a class does not establish
that every method runs. The
[wrapper provenance cases](../../gitnexus/test/unit/fetch-wrapper-provenance.test.ts)
cover these boundaries.
Bare wrapper calls and forwarded calls resolve declaration or import identity
before using name fallback. Relative imports, renamed imports, tsconfig aliases,
and workspace imports use the project module resolver. A known local
non-wrapper, a different resolved target, or an ambiguous target blocks fallback.
An unresolved name can fall back only when exactly one distinct definition can
be the target, including non-wrappers, and that definition is a proven wrapper.
For an import the resolver cannot follow, the candidates are module-level
functions exported under the imported name; for an undeclared global, they are
top-level functions of that name. Nested and unexported helpers are never
candidates. This fallback is an analyzer recovery heuristic; it does not prove
lexical identity. A function whose binding is reassigned in its file, including
through `export { request as send }; request = other;` or a later
`var request = …` initializer, proves no wrapper in importing files either. Explicit `fetchWrappers` configuration takes precedence
over automatic inference, including for names shadowed by local parameters; member
calls retain their existing matching rules. See the
[wrapper consumer integration tests](../../gitnexus/test/integration/resolvers/fetch-wrapper-consumers.test.ts)
and [configured wrapper tests](../../gitnexus/test/integration/resolvers/configurable-fetch-wrapper.test.ts).
The scope model does not represent separate parameter and body environments.
When formals contain expressions, including nested defaults or computed pattern
keys, HTTP inference therefore declines an affected binding if a body `var` or
function declaration shares a parameter's name or a binding referenced by a
parameter expression. Declarations inside nested functions do not count as outer
body declarations; a rest parameter alone is not a parameter expression. The
[lexical binding tests](../../gitnexus/test/unit/route-lexical-bindings.test.ts)
pin this conservative boundary. If scope extraction fails, scope-dependent HTTP
facts are omitted and the existing scope-extraction failure remains visible;
unrelated structural extraction continues. A failing HTTP hook drops only that
file's HTTP facts. Either way the file's function names are still recorded, so
name fallback elsewhere declines instead of treating another definition as
unique.
Barrels follow ECMAScript `ResolveExport`. A local or named export shadows
`export *`, star exports never carry `default`, and a name that two star-exported
modules export as different bindings is ambiguous, whether or not either is a
wrapper. Two export names of one declaration are one binding.
Some recall is given up deliberately. A helper that conditionally rebinds its
parameter, as in `if (!app) { var app = express(); }`, loses that parameter's
provenance: the initialized `var` is a write. A dynamic-import shim such as
`(...args) => import('node-fetch').then(...)` is not platform fetch evidence. A
write to the URL parameter clears URL forwarding. Calls inside recovered syntax
are still resolved, but nothing else inside an `ERROR` node is trusted.
Vue's explicit JS/JSX script requires an additional bounded semantic parse because
the worker's structural tree uses TypeScript. That parse runs inside the real
worker. The [parity test](../../gitnexus/test/integration/local-import-worker-parity.test.ts)
witnesses returned worker facts; a main-thread fallback does not satisfy it.
### Rust, C++, and Zig boundaries
Rust uses Cargo editions, target roots, and module identities, including files
with multiple valid identities. Reusable per-analysis indexes bound repeated
lookup work; this is not a claim of a global indexing speedup. See
[Cargo metadata](../../gitnexus/src/core/ingestion/languages/rust/cargo-targets.ts)
and its [tests](../../gitnexus/test/unit/scope-resolution/rust-cargo-targets.test.ts).
Exact same-name inline `pub use` forwarding is supported. A forwarding alias that
changes symbol spelling through a file-only target conservatively remains
unresolved; ambiguous same-file homonyms and re-export cycles cannot select an
unrelated declaration. These limits are explicit in the
[Cargo fallback regressions](../../gitnexus/test/integration/resolvers/rust-cargo-target-fallback.test.ts).
Inferred receiver types retain their receiving declaration range. When same-block
shadowing replaces a retained type fact, earlier uses cannot consume the later
declaration's type and may remain unresolved; this is not full SSA inference. See
the [factory ownership regressions](../../gitnexus/test/unit/scope-resolution/rust/rust-range-binding-order.test.ts).
C++ includes keep their compilation-unit behavior alongside lexical `using`.
The header tests cover literal includes, declaration positions, unresolved named
using claims, and overload identity. They do not establish a full preprocessor,
namespace-alias evaluator, or arbitrary transitive `using namespace` chain support.
Zig nonliteral imports remain unresolved; its block-local names do not escape or
activate before their declarations.
## Retained provider behavior
The other eleven providers retain their existing import/loading models. This
work does not reinterpret every dependency construct as a local value import.
| Provider | Retained model and boundary | Evidence |
| --- | --- | --- |
| Ruby | Wildcard loading; method-local loading is deferred. The extractor recognizes string arguments to `require`, `require_relative`, and `load`. The linked behavioral tests establish `require`/`require_relative` and deferral, not arbitrary `load` execution. | [Extractor](../../gitnexus/src/core/ingestion/languages/ruby/captures.ts), [loading](../../gitnexus/test/integration/resolvers/ruby-scope.test.ts), [deferral](../../gitnexus/test/unit/scope-resolution/function-local-import-chain.test.ts) |
| PHP | Namespace `use` imports are distinct from closure capture and trait `use`. The scope provider does not propagate names through `include`/`require`. | [Query](../../gitnexus/src/core/ingestion/languages/php/query.ts), [ownership](../../gitnexus/src/core/ingestion/languages/php/simple-hooks.ts), [resolver coverage](../../gitnexus/test/integration/resolvers/php.test.ts) |
| C | Header includes remain preprocessor/compilation-unit dependencies, including includes written inside functions; no full preprocessing is added. | [Include interpretation](../../gitnexus/src/core/ingestion/languages/c/interpret.ts), [include tests](../../gitnexus/test/unit/scope-resolution/c/c-imports.test.ts), [deferral boundary](../../gitnexus/test/unit/scope-resolution/function-local-import-chain.test.ts) |
| Objective-C | Headers, modules, and compilation-unit siblings retain the dedicated provider rules. | [Provider guide](objective-c-provider.md), [resolver](../../gitnexus/src/core/ingestion/languages/objective-c/scope-resolver.ts), [workspace tests](../../gitnexus/test/unit/scope-resolution/objective-c/objc-workspace.test.ts) |
| COBOL | `COPY` stays on the dedicated copybook path with module ownership. | [Interpretation](../../gitnexus/src/core/ingestion/languages/cobol/interpret.ts), [COPY target parity](../../gitnexus/test/unit/scope-resolution/cobol-import-target-parity.test.ts) |
| Go | File imports and package sibling visibility retain separate provider rules. | [Import ownership](../../gitnexus/src/core/ingestion/languages/go/simple-hooks.ts), [imports](../../gitnexus/test/unit/scope-resolution/go/go-imports.test.ts), [package siblings](../../gitnexus/test/unit/scope-resolution/go/go-package-siblings.test.ts) |
| Java | Imports belong to the compilation unit. | [Ownership](../../gitnexus/src/core/ingestion/languages/java/simple-hooks.ts), [named/wildcard import resolution](../../gitnexus/test/integration/resolvers/java.test.ts) |
| Kotlin | File import headers retain their existing alias/package resolution. | [Header decomposition](../../gitnexus/src/core/ingestion/languages/kotlin/import-decomposer.ts), [resolver tests](../../gitnexus/test/integration/resolvers/kotlin.test.ts) |
| C# | Namespace/file `using` directives remain imports; resource `using` statements are not import directives. | [Directive query](../../gitnexus/src/core/ingestion/languages/csharp/query.ts), [import tests](../../gitnexus/test/unit/scope-resolution/csharp/csharp-imports.test.ts) |
| Swift | Imports and target/module visibility retain provider ownership. | [Ownership](../../gitnexus/src/core/ingestion/languages/swift/simple-hooks.ts), [import decomposition](../../gitnexus/test/unit/scope-resolution/swift/import-decomposer.test.ts), [target siblings](../../gitnexus/test/unit/scope-resolution/swift/target-siblings.test.ts) |
| Dart | Library imports retain existing package identity and visibility rules. | [Ownership](../../gitnexus/src/core/ingestion/languages/dart/simple-hooks.ts), [library/package regressions](../../gitnexus/test/integration/resolvers/dart.test.ts) |
## Taint provenance
Taint source, sink, and sanitizer matching uses the same lexical provenance.
A genuine external import may match its raw module name even when that package
has no indexed source. A sibling import, local shadow, or type-only import cannot
authorize that match. General copied-handle tracking is outside this support.
The [taint regressions](../../gitnexus/test/unit/taint/lexical-imports.test.ts)
cover extraction, emission, and function summaries, including abstention when
exact call-site positions are missing.
## Cache migration and verification evidence
Parser compatibility uses
[automatic artifact-derived identities](../../MIGRATION.md#automatic-parser-cache-identities),
not manually assigned semantic counters. The
[worker-result cache](../../gitnexus/src/storage/parse-cache.ts) and
[durable ParsedFile store](../../gitnexus/src/storage/parsedfile-store.ts)
validate the same base identity and include it in their chunk keys. PDG-enabled
chunks also include the PDG identity and resolved function-line cap. Legacy
numeric, missing, malformed, or foreign identities cause a cache miss and rebuild;
compatible records remain eligible for reuse. If identity verification fails,
analysis uses a transient store without persistent cache reads or writes.
Parser contributors update no counter or generated digest. The
[identity tests](../../gitnexus/test/unit/parse-cache-identity.test.ts),
[input-coverage tests](../../gitnexus/test/unit/parse-cache-input-coverage.test.ts),
and [lifecycle tests](../../gitnexus/test/integration/parse-cache-identity-lifecycle.test.ts)
cover the compatibility gates. Source changes invalidate identity immediately,
but compiled workers still need `npm run build` in `gitnexus/` before verification.
Cached wrapper import facts retain raw specifiers and imported names. Each
analysis resolves them against the current project configuration, including on
warm runs, so a changed tsconfig alias does not retain a stale wrapper target.
The [wrapper persistence tests](../../gitnexus/test/integration/resolvers/fetch-wrapper-persistence.test.ts)
cover raw cache transport, warm reuse, and alias changes.
The [real-worker parity suite](../../gitnexus/test/integration/local-import-worker-parity.test.ts)
witnesses worker boot, dispatch, and returned ParsedFiles, then compares complete
provider facts with direct extraction, including `callerScope` and C++ side-channel
data. Optional grammar availability is explicit in the test gate.
The [persistence suite](../../gitnexus/test/integration/resolvers/local-import-persistence.test.ts)
loads both stores afresh from disk. Its parse-cache chunks contain no embedded
ParsedFiles, so warm equality requires durable-store reuse. It checks identical
`CALLS`/`IMPORTS` identities with zero extraction and zero worker dispatch on an
unchanged warm run. Editing a local import target or shadowing `require` removes
the stale target, and a second warm run preserves the edited graph.
Both suites are in the [cross-platform manifest](../../gitnexus/scripts/cross-platform-tests.ts);
the [shard regression](../../gitnexus/test/unit/cross-platform-shard.test.ts)
ensures each is selected exactly once across the four Windows/macOS shards.
[CI](../../.github/workflows/ci-tests.yml) owns native execution on those systems;
Ubuntu's full suite discovers the tests normally. These are focused evidence
boundaries, not a statement that the final full suite or every platform has passed.
## Related Python ownership behavior
The nested declaration ownership fix in #3502 for the original #3499 case and
the aliased package re-export fix in #3504 remain part of Python's provider rules.
Their [Python integration coverage](../../gitnexus/test/integration/resolvers/python.test.ts)
and [module namespace coverage](../../gitnexus/test/unit/scope-resolution/python/python-module-namespace-construction.test.ts)
remain relevant. Lexical ownership does not model all dynamic Python behavior.