Repository navigation
The bare-name ambiguity census names its population instead of sizing it - #11078
Conversation
`[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
|
Fixed in the pushed head.
— sent from snappy-pike-265 |
…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
|
Grain finding accepted — it was a real fidelity defect, and it mattered because that number is what sizes the rename campaign. That commit is held locally for a few minutes rather than pushed: the floor run currently in flight on this PR is the census run this whole change exists to produce, and re-pushing for a label-and-doc change would spend another floor runner — the fleet's binding constraint — for numbers that do not move. It goes up as soon as that run's census is read. On the hand-rust receipt: admitted under — sent from snappy-pike-265 |
…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
|
Finding confirmed and fixed in the pushed head — it was the right call, and the carrier's own documentation agrees with you: Fix as suggested: the question is now asked where it is answered. Two related things in the same head, for context: the read population is now the VALUE-POSITION projection of the reference collector (a free The run on the superseded head was cancelled rather than left to finish, since the fleet's runner pool is the binding constraint. — sent from snappy-pike-265 |
…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
…11137) * Qualify the extdeps.tools bare-name reads by their declaring module 23 of the 105 value-position reads the bare-name-ambiguity census names (`[floor-bare-name-ambiguity-read]`, landed #11078) sit under `extdeps.tools`. Each was resolved by whichever claimant scope precedence happened to rank first; this binds each to the authority its own module means, through the file's own import, so the read no longer falls through the shared name slot. Three names, three declaring authorities, each named rather than inherited: - `String` is declared by `std.string_type`, NOT by `std.types`. Ten of these files carried `import std.types { String }`, which answers nothing -- `std.types` neither declares `String` nor imports it, so the name was travelling through the shared slot while the import line implied otherwise. The bogus name is dropped from the `std.types` list and `import std.string_type { String }` added, so the citation now names the declaration (§3: cite the symbol, not a re-exporter). - `Unit` IS declared by `std.types` (`type Unit`), so it joins the existing list. This is the form the proof site landed in #11078 already demonstrates: `extdeps.tools.hostname` reads `[floor-bare-name-ambiguity-bound] name=Unit resolves_to=std.types`. - `Filesystem` at `extdeps.tools.sha256sum` is the one judgment site in this batch and it is not close: the call is `Filesystem.Write(path:, content:)`, matching `service Filesystem { operation Write }` in `extdeps.filesystem.filesystem_io`. The other claimant, `std.resources`, declares a `resource Filesystem` whose capability is lowercase `write` and which has no `Write` operation at all. The file already imported `extdeps.filesystem.filesystem_io` bare; it now names `{ Filesystem }`, which is the form 82 other files in the corpus already use. No renames, and neither declaring side is touched -- this is the qualification mechanism 305 sites corpus-wide already resolve through, applied to the residue that never got it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5dKcSuYW3nvAjtVcDxqhT * Admit the one namespace delta the sha256sum qualification produces The required floor on this branch is CLEAN -- `verdict=FloorClean unexpected_failures=0` over planned=3757 executed=3757 claims_failed=0 -- and the census moved exactly as the change intended: `read_name_module_pairs` 105 -> 82, `qualified_name_module_pairs` 305 -> 334, with all 23 `[floor-bare-name-ambiguity-bound]` rows naming the authority this PR chose (10 `String` -> std.string_type, 12 `Unit` -> std.types, 1 `Filesystem` -> extdeps.filesystem.filesystem_io). What blocked the lane was a different phase: `FAILED PHASE namespace-wave-admission (1 unadjudicated delta)`. THE DELTA, AND IT IS A REAL ONE -- a side effect on a name this PR never set out to touch: TargetChanged binding extdeps.tools.sha256sum::extdeps_external_authority_anchor base {extdeps.filesystem.filesystem_io, extdeps.shell, extdeps.tools.sha256sum} head {extdeps.shell, extdeps.tools.sha256sum} `extdeps.tools.sha256sum` imported `extdeps.filesystem.filesystem_io` BARE. A bare module import drags the whole module into the candidate set for every name it declares, so filesystem_io's copy of `extdeps_external_authority_anchor` -- the per-module convention row some 315 modules each author -- was a candidate at this site. Naming `{ Filesystem }` binds the one declaration the author meant and drops the rest, which narrows the candidate set for that unrelated spelling too. NO RESOLUTION CHANGES: sha256sum authors its own `extdeps_external_authority_anchor`, and a module's own declaration wins inside the authored region, so the site resolved to sha256sum's row before and resolves to sha256sum's row after. The removed candidate could not have won either way. What moved is the SET, three modules to two -- the resolution stopped depending on a module the author never named, which is the qualification's whole purpose. That is admitted, not silenced: the row states it out loud, which is what this wall exists for. The ten `extdeps.tools.* -> std.string_type` membership additions needed no row: the phase classified each `ExplicitlyEvaluatedZeroDelta ... reached by a name this module authors`. ALSO PAID HERE: the three `gunbc#11071 LinuxKernelRelease rehome` rows are deleted. #11071 merged, so the base already binds those spellings to `extdeps.linux.kernel` and the floor reported all three CONSUMED. A consumed row's deletion comes due on this roster's OWN next touch; this change is that touch, so the debt is paid rather than inherited by an unrelated lane. NOTE FOR THE LATER BATCHES: the Filesystem/Clock batch converts fourteen more bare imports to named ones, so it should be expected to produce this same class of delta per module rather than treated as a surprise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5dKcSuYW3nvAjtVcDxqhT * Address review 64326: consistent String citation, and name the instrument Three of the four findings were right and are fixed here. TRANSCRIBED INSTRUMENT OUTPUT (valid). The roster doc carried `verdict=FloorClean unexpected_failures=0` and the planned/executed/ claims_failed counts as prose. DESIGN §6 is explicit that a measurement is cited by naming the producer that re-derives it, never by copying its numbers, because a transcribed number rots without anyone touching either end. The counts are gone; the claim now names the `claim_executor` invocation and the verdict line to read it off. TWO FILES LEFT ON THE OTHER SPELLING (valid). `extdeps.tools.jq` and `extdeps.tools.sha512sum` were edited on exactly the line that carries `String` and kept it on `std.types`, which left them inconsistent with their nine siblings after a change whose stated purpose is to qualify reads by their declaring module. They were excluded because the census does not list them -- their reads are settled inside the authored region -- but "not in the measured population" is a reason to not CHASE a site, not a reason to leave a wrong citation on a line already being edited. Both now name `std.string_type`. All thirteen touched files are consistent: none carries `String` on a `std.types` import. THE ROSTER DID NOT SPEAK TO THE `String` REQUALIFICATION (valid as a gap in the writing). The doc said "NO RESOLUTION CHANGES" about the sha256sum anchor and a reader could take it as covering the whole diff. It now states why `String` needs no row, and states it as the wave phase's measurement rather than as this author's assertion. THE FOURTH FINDING IS NOT ACCURATE and nothing is changed for it. It reads the `String` requalification as retargeting the spelling to "a second authority" and forking it across nine modules. `std.types` declares no `String` -- the finding says so itself -- so it was never an authority for that name, and `import std.types { String }` bound nothing. The two real claimants are `std.string_type` and `v2.std.text`, which the census names as the self-host migration double and explicitly scopes out. Naming one of the two existing declarations creates no third. That it is measured rather than argued is the part that matters: the wave phase compares the binding table on both sides, and on this head it reported exactly ONE `TargetChanged binding` delta over 814,621 binding rows compared -- the sha256sum anchor -- and NONE for `String`. The ten `String` sites are reported as `ExplicitlyEvaluatedZeroDelta membership ... reached by a name this module authors`: evaluated, zero. So the read resolved to `std.string_type` before and after; what changed is its authorization, from an accident of scope precedence to something the author wrote. The finding's live observation -- that ~1951 modules carry `import std.types { String }` -- is real and is a corpus-wide defect this campaign does not fix: those lines name a module that does not declare the name they list. They are harmless only while those modules' reads are not ambiguous. Correcting them is its own change with its own adjudication, not a drive-by in a PR scoped to 23 measured reads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5dKcSuYW3nvAjtVcDxqhT --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…le (#11145) Second batch of the bare-name-ambiguity campaign (census instrument: #11078, first batch: #11137). 57 of the 105 value-position reads the census names, across 29 extdeps modules outside `extdeps.tools`. Same two names and the same two authorities as the first batch: - `String` is declared by `std.string_type`. Twenty-six of these files carried `import std.types { String }`, which binds nothing: `std.types` neither declares `String` nor imports it, so the read was travelling through the shared slot while the import line implied it had been settled. The name is dropped from the `std.types` list and `import std.string_type { String }` added. Two files -- `extdeps.clock` and `extdeps.entropy` -- read `String` bare without importing it under any spelling at all, and simply gain the `std.string_type` line. - `Unit` is declared by `std.types` (`type Unit`), so it joins the existing list, which is the form the #11078 proof site already shows binding to `resolves_to=std.types`. `extdeps.github.pulls` reads only `String` and so takes only that half. No renames, and neither declaring side is touched. Both this batch and the first are the mechanism 305 sites corpus-wide already resolve through, applied to the residue that never got it. Claude-Session: https://claude.ai/code/session_01M5dKcSuYW3nvAjtVcDxqhT Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…at are not (#11146) Fourth and final qualification batch of the bare-name-ambiguity campaign (census: #11078; batches one to three: #11137 and its successors). It is two lines, because auditing the last seven sites the census names found that only two of them are defects. THE TWO THAT ARE. `gunbc.cli_services` reads `Unit` and `String` in the shell-exit fold (`0 => Unit` / `nonzero => String "..."`), the same shape the extdeps tool modules carry. `Unit` joins the `std.types` import that declares it; `String` moves to `std.string_type`, which is the module that actually declares it. THE FIVE THAT ARE NOT, and why they must not be swept. Each is a bare read of a VARIANT ARM, not of either claimant type: std.fidelity Unit -> arm of its OWN `type TestClass = Unit | Hermetic | Integration` gunbc.live_deploy.unit_standing Unit -> arm of `SystemdUnitProperty` in `extdeps.systemd`, ALREADY imported by name; the site is `property: Unit`, the systemd `Unit=` property v2.test.lens_cost.valuation Unit -> arm of `TestgenLayer` in v2.test.manual.infer_ground_add `v2.std.verification`, ALREADY imported by name; the site is `layer: Unit` v2.extdeps.languages.cpp Int -> arm of its OWN `type CppSignedIntegerSurfaceSpelling`; the site is `spelling: Int` None of these means `std.types.Unit`, `v2.std.cardinality.Unit`, or `v2.std.integer.Int`. Adding an import naming one of those authorities would not qualify a read -- it would point a correctly-written site at a declaration whose meaning is different, and it would do so while the census reported the corpus getting cleaner. WHY THE CENSUS COUNTS THEM. Its resolver is `PreparedScopeIndexes::site_resolved_fn`, two tiers over `fn_nodes`: the site's own module (`{module_path}.{name}`), then the file's import binding (`{source_module}.{name}`). `fn_nodes` is populated from `fragment.fn_entries`, and `fn_entries` is built by walking `module.items` -- TOP-LEVEL items only. A variant arm is not a top-level item, so no arm is ever reachable under either tier, and a bare read of one is reported as falling through to the shared slot even when it is locally declared or explicitly imported. This is a bounded over-approximation in the instrument, five sites wide at this revision, and it is load-bearing for the refusal this campaign is building toward: that wall is specified to key on the SAME value-position population this census reports, and these five are sites that must NOT refuse. The instrument needs a third resolution tier -- variant arms of a type the site's module declares or explicitly imports -- before a refusal over this population is safe to land. That tier is not in this PR: it is a change to the census's own resolver, it wants its own discriminating controls, and it belongs with the wall rather than with a batch of import lines. With this batch the qualification stage is complete: 100 of the 105 reported reads are bound to the authority their own module means, and the residual five are not qualification targets but the instrument's own next correction. Claude-Session: https://claude.ai/code/session_01M5dKcSuYW3nvAjtVcDxqhT Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
[floor-bare-name-ambiguity]reported four counts and not one name.names_totalis summed across scopes, so a name ambiguous in 300 scopes is counted 300 times, and nothing said which names those were — enough to size a campaign, not enough to run one.Layer 2 of this class (v1#11065 is layer 1) is a REFUSAL landed together with the rename or qualification of every site it would refuse. That site list is exactly what
claim_scope_forwas discarding: it built a set of the out-of-region colliding names and returned.len(). It now returns the rows — each name, every module outside the authored region that claims it, and the kind it claims it as, both sides of the collision. The authored-region rule is untouched.The floor unions those rows across the fold and prints one
[floor-bare-name-ambiguity-name]line per DISTINCT name (claimants, kind signature, scopes-affected), appendsnames_distinct=to the summary line, and folds a[floor-bare-name-ambiguity-kinds]histogram. Not truncated to a top-N: an elided tail is an allow-list with extra steps.Report-only — no refusal, no budget move, resolution unchanged.
🤖 Generated with Claude Code
https://claude.ai/code/session_01RMZWo8pBkPif7V9ZNNVTye
v1 PURPOSE admission (DESIGN §3,
gunbc.v1_maintenance_standing v1_seed_standing)This change is admitted because it serves the v2 self-host program, not v1's growth for its own sake: it is the census feeding the layer-2 refusal on bare-name ambiguity in the seed's shared slot (layer 1 is #11065), and bare-name resolution is a rule v2 has to reproduce exactly. Deletion-is-the-census requires that refusal to land together with the fix for every site it would refuse, so enumerating those sites is the prerequisite, not an optional diagnostic.
No new v1 capability, no budget raised, resolution byte-identical — except where a correction was required:
lookup_fn_fromand the census now consume one implementation of the site-relative tiers (PreparedScopeIndexes::site_resolved_fn), which is behavior-identical to the body it replaces.Nothing is deleted here because the deletion IS the wall, which lands in the follow-up PR together with whatever renames the census names.