# 0106 — namespaced-import internal names are silently bare-visible (over-permissive `isNameVisible`)

> **RESOLVED** (flow stdlib/B attempt-3 — root fix, no exemption). Two coupled
> changes:
>
> 1. **Tightened bare visibility to the flat edge set.** `isNameVisible` /
>    `isCImportVisible` now route through the unified `isVisible` predicate over
>    `user_bare_flat` / `c_import_bare` (both join over `flat_import_graph`, not
>    `import_graph`). A namespaced-only import's internal name is no longer
>    bare-visible — face #1 now errors `'<name>' is not visible; #import the
>    module that declares it`.
> 2. **Pin the defining-module context during pack/comptime monomorphization.**
>    The flat tightening alone broke `std.print` / `log.*`: a library metaprogram's
>    body (`#insert build_format(fmt)` comptime call + the `#insert "out(result);"`
>    inserted statement) was lowered under the CALL SITE's `current_source_file`,
>    so its bare names (`build_format`, `out`, `emit`) were policed against the
>    consumer's imports. **Root cause:** `monomorphizePackFn` (bare `print` /
>    `format`) and `lowerComptimeCall` (namespaced `std.print` / `log.*`, reached
>    via the field-access `hasComptimeParams` branch) lower the metaprogram body
>    without pinning the source context — unlike a normal function, which lowers
>    via `lowerFunctionBodyInto` pinning `func.source_file`. **Fix:** both paths
>    now save/set/restore `current_source_file` to the body's DEFINING module
>    before lowering the body (the call-site ARGS are lowered first, in the
>    caller's context, which is correct). The defining path is stamped onto each
>    function body node by `resolveImports` (`stampFnBodySource`, mirroring how a
>    declared function carries `Function.source_file`). So the metaprogram's bare
>    `build_format` / `out` / `emit` resolve in `std.sx` / `log.sx` naturally —
>    and a USER's `#insert <expr>` is still checked in the USER's context, so a
>    bare reach into a namespaced-only import there errors. **No `#insert`
>    exemption** (attempt-2's `in_insert_expansion` flag is deleted): the fix is
>    the absence of an exemption, not a narrower one.
> 3. **Substituted caller `$`-args resolve in the CALLER's context** (attempt-5).
>    The point-2 defining-module pin covers the metaprogram body's OWN code only.
>    A caller-provided comptime `$`-arg (e.g. a caller-owned helper passed to an
>    imported metaprogram) is spliced into the body by `substituteComptimeNodes`;
>    those nodes are CALLER-authored and must resolve in the caller's visibility
>    context, not the callee's. **Fix:** the `$`-arg node is stamped with the
>    caller's `source_file` at the `cpn` build site (`lowerComptimeCall` /
>    `monomorphizePackFn`, `stampCallerSource`), and `lowerExpr` switches
>    `current_source_file` to a node's `source_file` when present — so the
>    substituted subtree resolves against the caller while the surrounding callee
>    code keeps the defining-module pin. Regression:
>    `examples/0738-modules-comptime-arg-caller-context.sx` (caller-owned helper
>    as a comptime-only `$`-arg through a namespaced import; fail-before
>    "'caller_name' is not visible" → pass-after "hello world").
>
> Root cause: `isNameVisible` walked `import_graph` (flat AND namespaced edges)
> where a bare name should join only over `flat_import_graph`; and the pack /
> comptime monomorphizers lowered the metaprogram body under the wrong source
> context.
> Regressions: `examples/0736-modules-namespaced-only-bare-not-visible.sx` (+
> `0736-…/a.sx`) — face #1 pinned (exit 1 + the stderr);
> `examples/0737-modules-insert-bare-not-visible.sx` (+ `0737-…/a.sx`) — a USER
> `#insert secret()` into a namespaced-only import errors (fail-before exit 0 on
> the attempt-2 exemption / pass-after exit 1). Face #2 restored WITHOUT an
> exemption: `examples/0015 / 0700 / 0718 / 1030` pass again (`run_examples`
> 471 → 474, incl. the attempt-5 caller-context regression `0738`). Fix in `src/ir/lower.zig` (`monomorphizePackFn` +
> `lowerComptimeCall` source-context pin; exemption removed) + `src/imports.zig`
> (`stampFnBodySource`) + `src/ir/resolver.zig` (`VisibilityMode` modes, landed in
> attempt-1).

**Symptom.** A bare reference to a top-level name authored in a module that the
consumer imports **only namespaced** (`ns :: #import "m.sx"`) is silently
visible from the consumer. Observed: it compiles + runs. Expected: an error —
the name is reachable only as `ns.name`. Root: `Lowering.isNameVisible` walks
`program_index.import_graph`, which records BOTH flat (`#import`) and namespaced
(`ns :: #import`) edges; bare-name visibility should join only over FLAT edges
(`flat_import_graph`). This is the latent 0102-family visibility bug the Phase B
caller-mode audit (unified-resolver R5) was told to surface.

This **directly gates flow `stdlib/B`**: that step requires migrating
`isNameVisible`/`isCImportVisible` to the resolver's `user_bare_flat`/
`c_import_bare` modes (which walk `flat_import_graph`) **byte-identically**.
Switching the edge set drops `run_examples` from 471 → 467 (see face #2), so the
byte-identical requirement cannot hold until this bug is fixed.

## Reproduction — face #1 (user-facing over-permissiveness)

```sx
// m.sx
secret :: () -> i64 { 7 }
```

```sx
// main.sx
m :: #import "m.sx";
main :: () -> i32 {
    x := secret();   // bare; `secret` is only namespaced-imported as `m.secret`
    0
}
```

- **Observed** (current master): compiles, runs, exit `0` — bare `secret`
  wrongly resolves to `m`'s author.
- **Expected**: `error: 'secret' is not visible; #import the module that
  declares it` (it is reachable only as `m.secret`).

(With the Phase-B edge-set change applied — `isNameVisible` over
`flat_import_graph` — this repro correctly errors, confirming the diagnosis.)

## Reproduction — face #2 (library comptime entanglement: why a naive fix breaks std)

```sx
// main.sx
std :: #import "modules/std.sx";
main :: () -> i32 {
    std.print("hello\n");   // legit qualified call
    0
}
```

`print` in `std.sx` is a comptime metaprogram:

```sx
print :: ($fmt: string, ..$args) {
    #insert build_format(fmt);   // comptime call to a std-internal fn
    #insert "out(result);";       // inserts a bare call to a std-internal fn
}
```

The comptime call to `build_format` and the inserted `out(result)` are bare
names **authored in std.sx**, but they are visibility-checked in the
**consumer's** `current_source_file` context (comptime / `#insert` expansion
happens at the call site). Today they pass only because `import_graph[consumer]`
contains the namespaced `std` edge. Tightening bare visibility to
`flat_import_graph` makes them error
(`'build_format' / 'out' is not visible`). The same shape breaks `log` (`emit`).
Affected examples when the edge set is switched to flat:
`0015-basic-demo`, `0700-modules-import`, `0718-modules-cli-exit-json`,
`1030-errors-log-and-comptime` (467/471).

## Root cause (suspected area)

- `Lowering.isNameVisible` / `isCImportVisible` — `src/ir/lower.zig` (~1768-1840
  after the Phase-B refactor; `visibleOverEdges` / `nameVisibleOverEdges`). The
  cross-module join uses `import_graph` (flat **and** namespaced edges) where it
  should use `flat_import_graph` for a bare name.
- Comptime / `#insert` expansion context: the inserted/comptime-evaluated bare
  calls of a namespaced module's function are policed against the **consumer's**
  imports, not the **defining module's** own scope. The existing visibility
  check already exempts UFCS-alias rewrites and mangled local names as "compiler
  indirections" (`lower.zig` call site ~7284, identifier site ~3237); inserted /
  comptime-generated bare calls are the same kind of indirection and are not
  yet exempt — or, equivalently, the expansion should restore the defining
  module's `current_source_file`.

## Investigation prompt (paste into a fresh session)

> Fix issue 0106: `isNameVisible` over-permits bare references to a
> namespaced-only import's internal names. Two coupled changes, in this order:
>
> 1. **Library-internal context.** Ensure a namespaced/comptime-expanded
>    function's body — including `#insert`ed statements and comptime calls like
>    `build_format` inside `std.print` — is visibility-checked in its **defining
>    module's** context, OR exempt compiler-generated / `#insert`ed bare calls
>    from the visibility check (mirror the UFCS-alias / mangled-name exemptions
>    at `src/ir/lower.zig` ~7284 and ~3237). Verify with the face-#2 repro and
>    examples `0015 / 0700 / 0718 / 1030`.
> 2. **Tighten bare visibility to flat.** Change `isNameVisible` (and the
>    `c_import_bare` fall-through of `isCImportVisible`) to join over
>    `flat_import_graph` instead of `import_graph` — i.e. route them through the
>    resolver's `user_bare_flat` / `c_import_bare` modes (this is exactly Phase B
>    of the unified-resolver R5 plan; `src/ir/resolver.zig` already defines the
>    modes and `Lowering.visibleOverEdges` already takes a `.flat` / `.all`
>    selector). Verify the face-#1 repro now errors.
>
> Acceptance: the face-#1 repro errors ("not visible"); `bash tests/run_examples.sh`
> is back to 471 `ok` with bare visibility on the flat edge set; add the face-#1
> repro as a pinned regression (`issues/0106-…` with an `expected/` marker, or
> promote to `examples/07xx-modules-…`). Suspected files: `src/ir/lower.zig`
> (visibility + comptime/#insert expansion context), `src/ir/resolver.zig`
> (`user_bare_flat` / `c_import_bare`).
