Skip to content

Bare-name ambiguity becomes a refusal: census the distinct names, then the rename campaign that lets the v1 evaluator refuse instead of settle - #11135

Closed
briansrls wants to merge 9 commits into
mainfrom
session/snappy-pike-265
Closed

briansrls wants to merge 9 commits into
mainfrom
session/snappy-pike-265

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Auto-opened by session-dashboard for session snappy-pike-265.
Pushing to session/snappy-pike-265 advances this PR.

Worker attestation

Before flipping this PR to ready for review, confirm each item:

  • Title describes the change (not the session id or branch).
  • PR body summarises what and why (replace the TODO below).
  • Tests run: name the command (e.g. npm test, cargo test) and the result.
  • If this closes a work item, the body contains a Closes #N directive.
  • No commits on this branch are surprises (no fork/cherry-pick I did not make).
  • No secrets / credentials / large binaries staged.

Summary

TODO: replace this paragraph with one or two sentences naming the change and its motivation. Reviewers read this first.

Test plan

  • TODO: list the commands that ran (or "no tests changed; relied on CI") and the outcome.

Brian Searls and others added 9 commits September 11, 2026 18:21
`[floor-bare-name-ambiguity]` reported four counts and not one name. `names_total`
is summed across scopes, so a name ambiguous in three hundred scopes is counted
three hundred times, and no line in the receipt said which names those were. That
is enough to size a campaign and not enough to run one: layer 2 of this class is a
REFUSAL landed together with the rename or qualification of every site it would
refuse, and that site list is exactly what the producer was discarding.

`claim_scope_for` already knew it — it built a `HashSet<String>` of the
out-of-region colliding names and returned `.len()`. It now returns the rows:
each name with every module outside the authored region that claims it and the
KIND it claims it as, both sides of the collision rather than only the newcomer,
since a census naming only the loser cannot say what it collided with. The
authored-region rule is untouched: a name the author's own imports rank is
ordinary shadowing and is still not counted.

The floor unions those rows across the fold and prints one
`[floor-bare-name-ambiguity-name]` line per DISTINCT name with its claimants, its
kind signature, and the number of scopes it is ambiguous in; `names_distinct=` is
appended to the existing summary line, and a `[floor-bare-name-ambiguity-kinds]`
histogram folds the signatures — `data+fn` is the shape that crosses the
evaluator's kind dispatch, `fn` alone the common shape that merely picks a body.
The per-name list is deliberately not truncated to a top-N: a census whose tail is
elided is an allow-list with extra steps, and the refusal this feeds admits none.

Report-only. No refusal lands here, no budget moves, and the resolution the
evaluator performs is byte-for-byte the one it performed before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMZWo8pBkPif7V9ZNNVTye
…the reporter

`AmbiguousBareName::kind_signature` landed unconsumed while the floor's reporter
recomputed exactly that fold inline -- a dangling `pub` declaration (DESIGN section
3c) and a second copy of its logic (section 2) in one diff. The reporter now unions
the per-scope claimants back into the same `AmbiguousBareName` carrier the scope
produced and reads the signature off it, so one type answers both about a scope's
rows and about the census, and the fold exists once.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMZWo8pBkPif7V9ZNNVTye
…rations

The declaration census says which names two transitively-reached modules both
spell. It does not say anything READS one, and that gap is the whole difference
between a refusal that dissolves a defect and one that refuses a convention: the
corpus deliberately carries per-module rows that every extdeps module declares --
`extdeps_external_authority_anchor` is claimed by 315 modules -- and a wall keyed
on the declaration population would refuse all of them.

So the scope now also carries its READS: every bare-name reference site in the
scope whose referring module declares the name nowhere itself and has nothing in
its own authored import closure declaring it either, so the reference falls
through to the shared slot and the slot holds declarations nothing the author
ranked. That is the interpreter's own three-step resolution used as the filter, in
its order, with the authored-region rule unchanged.

COUNTED STATICALLY, over every reference site in each scope's closure, never over
the lookups the fold happens to execute. An execution-keyed census omits a
reference on a path no witness runs -- exactly the site a later refusal would
surprise, and the site a runtime-placed wall would never refuse at all. The sites
are read off `reference_closure_index`, which already classified every ExprVar
occurrence into bindings and free references and publishes the free ones per
module, so this is a projection of an index the scope already consulted and not a
second walk.

The floor prints one `[floor-bare-name-ambiguity-read]` line per (name, referring
module) site with the declarations the slot is choosing between, and a
`[floor-bare-name-ambiguity-reads]` line carrying read_sites, read_names_distinct
and declared_names_distinct side by side -- the two populations in one line, which
is what the rename campaign's size is read from. Still report-only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMZWo8pBkPif7V9ZNNVTye
`read_sites` counted distinct (name, referring module) pairs and called them
reference sites. `refs_by_module` publishes a module's free references DEDUPED, so
three references to one name from one module are one row -- the pair grain is the
right one, because a fix is written per referring module and repairs every
occurrence in it, but the receipt has to say so. The key is now
`read_name_module_pairs`, and both the carrier's doc and the reporter's note state
the grain and why it is not an occurrence count.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMZWo8pBkPif7V9ZNNVTye
…a wall can key on

The first read census keyed on `refs_by_module`, and that set is a deliberate
UNION over several question kinds because its only consumer asks "which modules
must this scope reach": a type annotation's `String`, a record literal's type
name, a variant pattern's constructor and an expression's callee all widen a
closure equally, and over-approximating there is harmless.

It is not harmless as a refusal's population, and the census showed exactly how.
Of 8 names read ambiguously, 5 were type-position -- String, Int, List, Bool,
WireContract, each declared by both `std.*` and its `v2.std.*` self-host copy --
and 3 were not reads at all: `observation` came from the pattern
`BmcFirmwareEvidenceObserved { observation: o }`, `population` from a call's
argument label, `observe` from a function's own fn-typed parameter. A wall keyed
on that set would refuse a record field the day a second declaration of its
spelling appeared.

So the collector now publishes the VALUE-POSITION projection beside the union,
built at the two arms the interpreter's own three-step resolution serves -- a free
`ExprVar` and a call's target -- rather than filtered back out of the union
afterwards, which could only approximate a walk that already knows the answer. The
census and its wall key on that projection; the closure walk keeps the union it
needs.

And the adjacent class is NAMED rather than silently filtered: the receipt carries
a `channel=value_position_only not_covered=type_position_name_collisions
owner=v2_self_host_replacement_migration` line, so a zero in this census can never
be cited as "no ambiguity in the corpus". The std/v2.std double is a real
ambiguity in the type channel, owned by the migration that ends it by ending the
double, and is not a rename this wall could ask for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMZWo8pBkPif7V9ZNNVTye
…n index

The filter re-derived "the author's imports rank it" from `func_env.parents`, and
that is a second resolution rule beside the first. The interpreter's second tier is
`file_import_bindings` -- the module a FILE explicitly imported a name from --
while `parents` is the flattened transitive closure, whose own carrier note says it
"does not separate a direct import from a transitively reachable module". So any
transitively reached module that merely spelled the name suppressed a row while the
interpreter still fell through to the shared slot, and the census under-reported
exactly the residue it exists to enumerate: a name reached through a wildcard
import, which binds nothing and ranks nothing.

The question is now asked where it is answered.
`PreparedScopeIndexes::falls_through_to_shared_slot` runs `lookup_fn_from`'s first
two tiers, in its order and with its conditions, on the same index `lookup_fn_from`
reads -- own module's qualified declaration, then the file's explicit import
binding -- and the census consults it after the scope's indexes are built. One
rule, one home, and the wall this feeds will consume the same method rather than a
copy of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMZWo8pBkPif7V9ZNNVTye
…tiers

The previous head said "one rule, one home" and left the interpreter its own copy:
`falls_through_to_shared_slot` restated `lookup_fn_from`'s first two tiers while
`lookup_fn_from` kept restating them itself. The copies had already disagreed --
the interpreter gates the tiers on `ambiguous_bare_function_names` and the
restatement did not, despite claiming to ask them "with its conditions". A census
that can drift from the live lookup does not name the population execution
resolves, which is the whole point of keying a refusal on it.

There is now one implementation: `PreparedScopeIndexes::site_resolved_fn` answers
the site-relative tiers and returns the node or `None`. `lookup_fn_from` IS that
method `.or_else(shared slot)`, and `falls_through_to_shared_slot` is that method
`.is_none()`. Nothing to drift from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMZWo8pBkPif7V9ZNNVTye
The value-position projection recorded every `ExprCall`'s target name without
consulting the binder stack, which the `ExprVar` arm one case above has always
done. So a function calling its OWN fn-typed parameter or its own `let` was
reported as a bare read of the shared slot: `std.goal_assessment`'s `inspect_goal`
takes `observe: fn(Subject, ObservationRequest) -> ...` and matches on
`observe(subject, request)`, and `gunbc.artifact_acquisition` writes `let observe =
handler.observe` and calls it. Both were counted; neither reaches the slot, and a
wall keyed on that projection would have refused a local.

The binder stack is already threaded through the walk and already answers this
question for free variables, so the callee arm now asks it too. The closure union
is untouched -- over-approximating THERE is correct, since a module whose callee
happens to be local still has to be reachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMZWo8pBkPif7V9ZNNVTye
…see it

The census's negative -- a (name, referring module) pair leaving the ambiguous-read
list -- proves only that the list moved. So the receipt now carries the positive
half: for a name that IS ambiguous in the shared slot and is NOT read through it at
some site, `[floor-bare-name-ambiguity-bound]` names the module that answered,
derived from the resolved declaration's own source file through the same
`file_module_paths` the first tier reads. A qualification that the resolver
silently dropped would leave the pair in the ambiguous list; a qualification that
bound to the WRONG authority would show up here as the wrong module.

And the proof site itself: `extdeps.tools.hostname` adds `Unit` to the
`import std.types { ... }` line it already carries for `String`, `List`, `Bool`,
`Int` and `NonEmptyStr`. That is the whole edit. `Unit` and `String` appear
side by side in its exit block, one explicitly imported and one falling through to
the shared slot where `std.types` and `v2.std.cardinality` both spell it and
precedence picks arbitrarily. The module's own import line is the evidence of which
authority it means, so the qualification decides the read the way the author
already decided its siblings.

This is one site deliberately: the campaign is about 106 of them, and there is a
standing finding that a selective import can be dropped at the resolver, so the
hundred waits on this one landing green with its pair bound to std.types and
`test.claim.hostname_set_invocation_lowering_witness_test` still passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RMZWo8pBkPif7V9ZNNVTye
@briansrls
briansrls marked this pull request as ready for review September 12, 2026 03:16
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-12T03:23:55.818175Z 610b50e Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 610b50e970

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Already won inside the authored region: the author's imports rank it and
// precedence has settled it. Ordinary shadowing, not ambiguity.
Some((_, true)) => {}
Some((_, true, _)) => {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include authored winners in the wildcard collision census

When the winning declaration entered the authored region through a wildcard import, this arm suppresses every later claimant, even though wildcard imports intentionally create no file_import_bindings entry. Consequently, a bare read of that name still falls through site_resolved_fn to the ambiguous shared slot, but the name is absent from ambiguous, so the new ambiguous_bare_reads scan never examines or reports it. Record the competing claimants here and decide ambiguity per read site instead of treating every authored-region winner as explicitly bound.

Useful? React with 👍 / 👎.

@gunbai-bot

gunbai-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Closing as a duplicate: this is the same nine-commit series that landed as #11078 (merged 2026-09-12T03:15:29Z), and the census content is already byte-identical on main. The branch tip is now ~490 files behind main, so merging it would only re-open a stale tip inside the same Rust files other repairs have since touched. Confirmed against origin/main by direct comparison, not by a three-dot diff, which misleads after a squash merge. The campaign continues under node adhoc-3fc0db35-69c (PRs #11137, #11138 and the wall). Auto-opened by the session dashboard after its authoring session was archived; no work is lost. — sent from bright-eagle-728

@gunbai-bot gunbai-bot Bot closed this Sep 12, 2026
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.

1 participant