Repository navigation
CALLABLE-LOOKUP-UNIQUE: collect every admissible callable candidate before deciding, and stop erasing the ambiguity arm - #8952
Conversation
…and the ambiguity arm stops being erased
`map_get` names two callables — the registered builtin (Optional<V>) and
v2.std.collection.map_get (Outcome<Optional<V>>) — and which one a bare call
meant was decided by whether the consumer's transitive import closure happened
to reach the declaration. One import edge added to extdeps.git moved two
unrelated modules' closures and silently re-typechecked three untouched files
(gap-analysis row 30; 46 files carry the same bare spelling).
The three-state outcome this needs already existed and could not describe the
collision, for two independent reasons:
(a) The candidate set was never assembled. lookup_func_sig asked the resolved
function environment first and consulted the builtin/global path only on
the Unresolved arm, so a declared map_get and the builtin map_get were
never co-candidates — the declared one won by short-circuit.
(b) Where ambiguity was constructed, func_sig_if_resolved mapped it onto the
same Absent that means "no such signature", and ten call sites read the
seed's inference through it.
Both are closed here. Candidates are collected per admissible level and then
decided (0/1/many through the existing module_path_owner_binding_decide), with
lexical scope applying first: a module's own declaration wins, the import
closure and the builtin surface are ONE level where nothing ranks two answers,
and the corpus census stays strictly outside both. A lone builtin still answers
Unresolved so the call routes to the known-builtin bridge — the builtin is a
candidate in the decision, never an answer. func_sig_if_resolved is deleted and
replaced by a total projection whose two arms every site now names.
Candidates carry identities (module path + declaration name, or the primitive)
rather than pasted strings, because a builtin has no qualified name to paste.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…runtime at any tier An independent repair, landed beside the callable-candidate wall rather than folded into it, because it is gap-analysis row 30 with the two halves swapped. A registry row types a bare call; an interpreter arm runs it. `emit_map_has` had the first and not the second, so the call typechecked clean and answered `NoSuchFunction` when evaluated. Measured by execution rather than by reading the dispatch table: a probe calling it bare through `gunbc run` refused with `NoSuchFunction`, while the same probe's `map_get` and `length` returned 7 and 3. Row 30 is a consumer's import closure CHANGING a name's meaning; this is the closure being the only thing SUPPLYING one. All 21 live call sites reach the declared `v1.compiler.infer_types` `emit_map_has` through their own closure, which is exactly what kept the row invisible: nothing was ever served by it. Deleting it costs no call site and converts a runtime `NoSuchFunction` for any future caller outside that closure into a compile-time unresolved-name refusal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ation is refused Only module-item grain is modeled (DESIGN 4c), and the src/v1 parse sweep refuses a block inside a declaration body. The note now attaches to builtin_function_registry and names where the row sat. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The src/v1 parse sweep refused ten annotation lines across 04_lookup and 04_infer for the same reason as the emit_map_has note: only module-item grain is modeled. Each block now attaches to the declaration it describes and names the arm inside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The earlier figure counted the declaration in 04_types.dag as one of its own callers -- the measure-the-name-rather-than-the-binding error this change exists to close, committed inside the receipt for it. The note records the correction rather than applying it silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…h the builtin The row-30 incident is TRANSITIVE: dag/extdeps/git/object_store.dag names v2.std.collection zero times and reached the declaration through somebody else's edge. Collecting the builtin as a co-candidate against every visible declaration therefore refused sixteen modules that name their authority outright -- 02_parse, 03_ingest, 03_resolve, target_model among them. So the level structure gains 1a: names in type_env.source_visible_names (locals, kernel, selective imports, all-module exports) are what the author named, and the builtin surface is not a co-candidate against them. 1b keeps the incident's case, where nothing was named and the closure supplied one. This ranks what-the-author-named against what-a-closure-supplied, never one kind of callable over another: two listed imports of a name are still ambiguous, and an unlisted declaration still does not beat the builtin. source_visible_names is reused rather than a fresh visibility notion minted beside it -- infer_env's closure_independent_bare_free_call_note already gates global-bare resolution on the same map. It is resolve-time only, so an EMPTY map is ignorance rather than an answer and takes the 1a arm; the rung is per path -- refuses on resolve, silent on emit -- with the emit row's trigger being persistence onto TypedModule.type_env. The witness is rebuilt as a triple. Its RED was the listed-import shape, which is the storm rather than the defect: it would have certified refusing the v2 compiler core as correct. The RED is now transitive reach, and the listed import becomes a green control. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…asing projection The brief named ten consumers of func_sig_if_resolved -- nine in v1.compiler.infer, one in v1.compiler.emit. There is an eleventh, in hand-written periphery rather than in emitted code: cli_run.rs imported it, which is why the emitted mirrors compiled as .dag and then failed to build as Rust. All three of its uses are inside tests that PIN the legacy ImportScoped policy and assert its first-hit behaviour -- the one place the collapse is legitimate, since an ambiguity cannot arise under that policy and the other assertion is a genuine miss. They now go through a cfg(test) helper that does the collapse locally and says why, rather than through a production projection that would erase the arm for everyone else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit's message claimed this change and its diff deleted it. The regen candidate tree under target/stage0-regen-candidate/src is a whole crate source tree -- it carries hand-written files like cli_run.rs through unchanged, not only the emitted mirrors -- so 'cp candidate/*.rs' restored the original over the edit. git status then showed six changed files, which is exactly what the six drifted mirrors would show, so the reverted edit was invisible in the count. Read what changed, not how many changed: the two are the same number here and they are not the same fact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… quiet wall The RED imported PosixUserName, which extdeps.posix.identity does not export. The source failed for an unrelated reason, produced zero AmbiguousReference rows, and the assertion read false -- indistinguishable from 'the wall did not fire'. Executed, that is exactly what it looked like. Corrected to PosixUserId, and joined by two assertions the pair could not make on its own: the RED's only blocking diagnostics are the ambiguity itself, and both GREEN sources carry no blocking diagnostic at all. A fixture that breaks for any other reason now fails loudly on those instead of returning a verdict about the wall it never reached. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…te exclusion builtin_callable_candidates excluded every algebra method template name, reasoning that receiver-dispatched names belong to the known-method ExprCall arm. Measured, that guard was redundant for the names it named and fatal for the ones it did not: of ~50 algebra template names only 19 carry a builtin_function_registry row, and filter/any/contains/fold/map are not among them, so infer_builtin_call_type already answered Absent and they were never candidates. What the guard actually excluded was the 19 names that DO have a free-call surface -- map_get first among them, the exact name row 30 is about. Executed evidence of the effect: the RED source produced VariantNotFound and NonExhaustiveMatch -- row 30's ORIGINAL symptom, the declared Outcome-returning map_get winning outright -- and no AmbiguousReference at all. The admission test is now the registry alone, which is precisely the question being asked: a registry row means a bare call to this name types against a primitive. A name without one produces no candidate anyway. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured through compile_dag_diagnostic_census on the real acceptance path,
with the mirrors regenerated and installed so the binary carries the change:
RED (imports extdeps.posix.identity, names no authority for map_get)
-> AmbiguousReference/BLOCK/map_get
and the VariantNotFound/NonExhaustiveMatch that row 30 describes
are GONE -- replaced by the refusal, which is the point
GREEN1 (import v2.std.collection { map_get }, then a bare call)
-> no AmbiguousReference; the named authority resolves
GREEN2 (declares its own map_has, no path to the declarer)
-> census empty
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…not a declaration source_visible_names folds BOTH import arms into one map -- is_all contributes every name in the imported module's interface AND every name that module acquired through ancestry_str_bindings. So 'import v2.std.collection' would put map_get in it without the author writing the word, and the ancestry half contributes transitively: the closure-supply case row 30 is about, arriving through a set whose name suggests the opposite. Membership there is NECESSARY for 'the author named this declaration' and not SUFFICIENT. The sufficient half cannot be recovered by filtering, because the union already happened at construction. So authored_import_names becomes its own field on TypeEnv, built from the specific_names arm alone, threaded through every literal site. Type parameters are deliberately not in it: authored, but not imports. Measured before cutting: of the modules that bare-call map_get and import v2.std.collection, all six name it in a listed import and none uses is_all, so the narrowed rule still covers the population 1a was introduced for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…uthority The wall refuses 29 sites in the v1 seed: to_string (26, across compile.dag, dag_collect.dag, dag_collect_support.dag) against v1.compiler.emit_core_support.to_string vs the builtin, and map_has (3, in 04_infer.dag) against v1.compiler.resolve.map_has vs the builtin. These are the src/v1 residue that no dag/-rooted witness can reach and that #8964 does not cover -- now measured rather than deferred, because the wall found them. Repaired the way the diagnostic asks and at the grain the defect has: four listed imports, not twenty-nine qualified call sites. Naming the authority once per module is what the wall is asking the author to do, and it preserves behaviour exactly -- the declared function is what these sites resolve to today. The call shape settles intent for to_string rather than my judgement doing it: the sites call to_string(value: x), and is the DECLARED function's parameter name (fn to_string(value: Int) -> String). A labelled call names its callee. map_has is behaviourally identical under either authority and the declared one is what runs today, so naming it changes nothing but the silence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the prose outrunning the mechanism THE JOIN (#8964, on main at 642604b) now decides whether a same-named builtin is a rival authority or the same one seen through a declaration: seam and modeled projections drop the builtin, divergent keeps both and refuses, no-projection keeps both, and UNDISPOSED drops it -- 196 of 209 census symbols sit in that last arm, and treating ignorance as an answer would scale the wall's strictness inversely with how much of the census is done. The builtin is retained only when a declared candidate positively says the two differ. PROSE CORRECTION, the load-bearing half. Level 1a was described as "a listed import names one exact declaration". It does not: authored_import_names is a Map<String, Bool>, so it carries NAME MEMBERSHIP and cannot say WHICH declaration the author meant. What the mechanism does is remove the implicit builtin co-candidate; the remaining DECLARED population still undergoes exact cardinality admission, so one visible name plus several declarations still refuses. Conservative, never silently selects the wrong declared function, and strictly weaker than the sentence I had written. An inflated sentence in a merged PR is what the next lane builds against. A boundary control pins it: a source naming to_string in a listed import AND reaching a second to_string through the closure must still refuse. If anyone later reads 1a as "the imported declaration excludes every transitive homonym", that control goes red -- the stronger rule needs an exact-binding carrier that does not exist. VisibilityUnobservable is documented as a COMPATIBILITY FALLBACK that may never serve as a resolution authority downstream: it reports that this seam cannot see source visibility, not that the author named nothing. Two over-strict assertions replaced with per-class ones. "No blocking diagnostic at all" read false for both GREEN sources, because the census compiles against live witness roots and an unrelated UnresolvedType lands in the count -- a check whose red is produced by something it does not measure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hority for the probe corpus A debugging harness I used to read the census while iterating. Right thing to have while iterating; experimental residue the moment the PR is proposed. It has no test fn, no assertion, and nothing that can go red -- and the 'tmp_' in a committed path is a statement that the author knew. The worse half is that it carried red_src / green1_src / green2_src as data rows duplicating the witness file's three sources: two authorities for the probe corpus, in adjacent files, in the PR whose subject is one name having two authorities. Whichever someone edited later, the other would silently disagree, and the scaffold has no assertion to notice. Reading a census as a string is a reasonable capability to want; if it is wanted it is a separate proposal with its own consumer, not a leftover. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Its first version imported v1.compiler.emit_core_support -- a SEED module. compile_dag_diagnostic_census resolves against the witness roots [dag, src/v2], so that import could never resolve, no to_string candidate existed, and the assertion read false for a reason with nothing to do with the boundary. Third fixture broken this way, and this one was inside the control built to prevent a different mistake. Rebuilt on v2.std.spine int_max, whose own closure reaches std.realization_width, which declares int_max too -- so ONE listed import puts both declarations in the closure at once. No builtin is involved, because int_max has no registry row, which is what makes the row ISOLATE its claim rather than restate the RED: the declared population survives a listed import and still undergoes exact cardinality admission. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…join The 29 seed collisions these repaired now answer DeclarationPrimitiveUndisposed, so the builtin is not admitted as a rival and the refusals do not fire. The edits' stated reason no longer exists; leaving them would be residue whose justification a later reader could not reconstruct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ter roster alone The projection query's Undisposed arm is defined over five surfaces, one of which (InterpreterDispatch) is the only one whose authority reaches v2.*. A consumer restricted to the other four answers identically today, but only as an occupancy fact. This checks the equality rather than assuming it, and goes red naming the first interpreter-only symbol anyone adds. Rung: mechanically preventable, not structural -- the two censuses remain capable of diverging and this check is what catches it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured 31 interpreter-only symbols, so the four-surface substitution the guard was written to license does not hold and there is nothing to guard. Keeping it would either assert something false or be re-pointed at the current count, which is a tree-copied census pin rather than an oracle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Why this is red, and why it stays red while parked
That is this PR's wall firing from a stale mirror, not a defect in the wall as specified. Measured on the committed
So CI is executing the pre-1a, pre-join wall: every declared homonym rivals its builtin unconditionally. Closing it means regenerating and installing the mirrors. That pulls Status: parked pending an operator ruling on whether the seed's module index may reach |
It is a consumer-side policy choice, not taxonomy work, and an unexplained arm reads as an oversight. Refusing on an unclassified symbol would refuse 196 symbols corpus-wide: a wall whose strictness rises with how much census work remains undone is not strict, it is broken. Also records why the answer has three arms rather than a boolean -- the argument is about where the decision lives, not what the states mean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lared, not implied Review 55260 is right that "proposes no repairs" was doing two jobs at once. Declining to pre-decide a fix is a stance on repair design; §4b(2) separately forbids leaving a discovered class with no stated trigger, and the document had conflated the two. Each of the six now carries a disposition and a trigger. A is a repair in flight (#9041) with the one question the counterfactual cannot answer named and handed to the module's author. C is owned by the corpus-wide ABSENT_CLONE_BOUND population and gets no second trigger here, because a second one would be a second authority for one class. D's lane is #8952. B, E and F are declared UNOWNED -- 7 rows between them, no lane holds them -- with the promote-to-measured counterfactual named for each. Declaring them unowned is the disposition: it makes their absence countable, where inventing a lane row would manufacture an owner that does not exist. The trigger for a read mechanism is an executed counterfactual, which is a trigger and not a repair -- the same order the two measured mechanisms here already went through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… each with its evidence status (#9068) * Five compiler-side mechanisms behind the affected-set emission board, each with its evidence status Partitions the 24 rows landing in v2_lens_application.rs, std_change.rs and v2_lens_affected_set.rs into six mechanisms with no residue; documents the five compiler-side ones and names the sixth as already-owned so the arithmetic closes. Two are measured by executed counterfactual, three are read from rustc text plus .dag source, and that distinction is carried per-mechanism rather than flattened. Proposes no repairs, deliberately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Pin the reproduction and script the blocker-lift; drop the positional citations Review 55234, two findings, both real. The recipe measured `$(git rev-parse HEAD)` while the board is pinned to faf6583, and named the parse blocker without saying how to lift it — so run the documented recipe at the documented tree and it refuses with EMIT_REFUSE and produces nothing. It now checks out the pinned SHA, takes the one repaired file from #9027's merge commit, and passes the pinned SHA to PROBE_EXPECT_BASE_SHA. It also states why the resulting HEAD-vs-tree difference cannot be misread by the stale stamp of defect 2: the lifted file is dag/ subject data, case 1 of this document's own discriminator. Six positional `.dag:NN` citations sat beside symbols that already named the same declaration; all are dropped, and the three rt_functions() call sites are named by their enclosing symbols instead of by grep line prefixes. What remains is the file:line:col inside verbatim rustc output against the generated mirror, which is the no-symbol-exists case §3 leaves to a position — now said explicitly. Also: the count above the defect list said two while listing three. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Give every mechanism a disposition and a next trigger; unowned is declared, not implied Review 55260 is right that "proposes no repairs" was doing two jobs at once. Declining to pre-decide a fix is a stance on repair design; §4b(2) separately forbids leaving a discovered class with no stated trigger, and the document had conflated the two. Each of the six now carries a disposition and a trigger. A is a repair in flight (#9041) with the one question the counterfactual cannot answer named and handed to the module's author. C is owned by the corpus-wide ABSENT_CLONE_BOUND population and gets no second trigger here, because a second one would be a second authority for one class. D's lane is #8952. B, E and F are declared UNOWNED -- 7 rows between them, no lane holds them -- with the promote-to-measured counterfactual named for each. Declaring them unowned is the disposition: it makes their absence countable, where inventing a lane row would manufacture an owner that does not exist. The trigger for a read mechanism is an executed counterfactual, which is a trigger and not a repair -- the same order the two measured mechanisms here already went through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…des of resolution The disposition row landed one commit ago cited #8952 as D's open lane. Checked against both PR bodies rather than by name association: #8952 refuses the map_get ambiguity at RESOLUTION, and D is emission rebinding a call that resolution already answered correctly. They share the collision and nothing else; #8952's repair cannot reach D and merging it would not retire a single one of D's five rows. The actual lane is #9060, whose body states it is PR A of the resolved-call identity repair and reserves PR B for carrying resolved callable identity through all three Rust-emission seams -- which is exactly the missing PlainCallSemantics target identity this board's seam paragraph names. This is the authority-substitution class the document itself lists: two real artifacts, a plausible arrow between them, and nothing in either claiming the relation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…des of resolution The disposition row landed one commit ago cited #8952 as D's open lane. Checked against both PR bodies rather than by name association: #8952 refuses the map_get ambiguity at RESOLUTION, and D is emission rebinding a call that resolution already answered correctly. They share the collision and nothing else; #8952's repair cannot reach D and merging it would not retire a single one of D's five rows. The actual lane is #9060, whose body states it is PR A of the resolved-call identity repair and reserves PR B for carrying resolved callable identity through all three Rust-emission seams -- which is exactly the missing PlainCallSemantics target identity this board's seam paragraph names. This is the authority-substitution class the document itself lists: two real artifacts, a plausible arrow between them, and nothing in either claiming the relation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…des of resolution (#9082) The disposition row landed one commit ago cited #8952 as D's open lane. Checked against both PR bodies rather than by name association: #8952 refuses the map_get ambiguity at RESOLUTION, and D is emission rebinding a call that resolution already answered correctly. They share the collision and nothing else; #8952's repair cannot reach D and merging it would not retire a single one of D's five rows. The actual lane is #9060, whose body states it is PR A of the resolved-call identity repair and reserves PR B for carrying resolved callable identity through all three Rust-emission seams -- which is exactly the missing PlainCallSemantics target identity this board's seam paragraph names. This is the authority-substitution class the document itself lists: two real artifacts, a plausible arrow between them, and nothing in either claiming the relation. Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… transitive, measured (#9084) * WIP measure: derive + 'static from callable-value-wrap captures, not the return connective * Filter the bound's capture set twice, and stop claiming the return arm is capture-derived Two precise findings from the side thread, both correct against the branch source. 1. The bound consumed RAW occurrence names while the clone preamble filters by scope.body_locals, so the two were related-but-different computations. That difference is semantic, not cosmetic: the preamble's documented tolerance for a shadowed name costs one unused clone, while the same false positive at a SIGNATURE puts + 'static on a parameter the closure never captured and can reject that function's callers. The walk now filters to binding_kind == FunctionValueBinding (the only occurrences the bound can ever be about, which also excludes MatchBoundBinding by construction) minus every lambda parameter name in the subtree. Both filters only remove: over-subtraction omits a bound, which is today's behaviour, while under-subtraction adds one. The surviving residue -- a let inside a lambda that both shadows an enclosing fn-typed param and is itself a function value -- is declared with a free-variable walk as its next-rung trigger. 2. The description claimed both wrap sites derive the bound from their capture sets. Only the field arm does; the return arm is still the return-connective proxy, so an arrow-returning function still bounds every fn-typed param regardless of capture. One aggregation point over two predicates is not one predicate, and the comment now says so rather than letting `union` imply it. Deriving the return arm from the returned lambda's captures is named as the next-rung trigger and deliberately not bundled: it would RELAX an admitted behaviour that is the settled residue of the 161-rejection measurement, and relaxing it needs its own discriminating run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * The FunctionValueBinding filter erased the repair; subtract shadows instead of selecting kinds Measured on the full chain (regen -> install candidate -> rebuild -> re-emit), which is the only reason this was caught before review: the pre-filter walk emitted key_eq: impl Fn(K, K) -> bool + Clone + 'static and the filtered walk emitted the same signature with no bound at all, 'static back to zero occurrences in the module. Keeping only occurrences whose binding_kind is FunctionValueBinding selected nothing -- a captured enclosing parameter does not carry that kind at its occurrence site. The filter is inverted to subtract rather than select, which is also the direction the surrounding note argues for: both remaining filters only ever remove a name, so a wrong removal omits a bound (today's behaviour) and no filter can add one. Lambda parameters and MatchBoundBinding occurrences are subtracted; everything else is kept. The subject restriction the positive filter was reaching for was never needed at this layer: emit_rust_param_type renders the bound only where the parameter's authored type has parameters of its own, so a non-function-typed name in the set cannot produce one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Revert the local repair: the measurement it produced is the deliverable, the code is not The implementation relocated obligations rather than retiring them, so it does not belong in the emitter. Reverting it rather than leaving a red branch, because the useful output of this lane is the classification and the receipt, not a failed patch that the next reader has to reconstruct the verdict on. Reverting also removes the regen drift: no .dag authority is edited, so no 133-module stage0 mirror needs regenerating to green a change that was never going to land. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * B was the first symptom, not the mechanism: supersede it with the measured, transitive form The board recorded B as "fn-typed params captured into an Rc closure demanding 'static", which names the symptom the emitter happens to hit first. Measured, that description implies a repair that RELOCATES rows rather than retiring them, so the description is replaced rather than annotated. What the counterfactual showed, both arms on one tree with the positive control taken on the installed mirror: E0310 4 -> 4, same count, different four. Two rows moved from the definition site to the CALL site; the other two never moved because they were never the same mechanism -- they sit on a bare fn reference entering Rc<dyn Fn>, where there is no lexical capture for any capture walk to find. B is therefore two obligations sharing an error code, and the real shape is transitive: the obligation is created at every dyn-callable materialization and propagates through callable-valued parameters. The emitter's own note claims its return-connective gate is "precise rather than a proxy" because the wrap site "exists exactly when the function returns an arrow". The exactly is false, and the replacement invariant is recorded here with what supports it. Deriving it is a call-graph fixpoint -- a lifetime-propagation engine. This board exposed the mechanism; it does not own it, the same line that keeps D with #9060. B's disposition says so instead of carrying a trigger nobody can act on. Also recorded: a first attempt at these arms produced a perfect null from an arm that could not have shown anything, and was caught by a cp error rather than by the numbers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * D's lane is #9060 PR B, not #8952 -- same name collision, opposite sides of resolution The disposition row landed one commit ago cited #8952 as D's open lane. Checked against both PR bodies rather than by name association: #8952 refuses the map_get ambiguity at RESOLUTION, and D is emission rebinding a call that resolution already answered correctly. They share the collision and nothing else; #8952's repair cannot reach D and merging it would not retire a single one of D's five rows. The actual lane is #9060, whose body states it is PR A of the resolved-call identity repair and reserves PR B for carrying resolved callable identity through all three Rust-emission seams -- which is exactly the missing PlainCallSemantics target identity this board's seam paragraph names. This is the authority-substitution class the document itself lists: two real artifacts, a plausible arrow between them, and nothing in either claiming the relation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # src/v1/stage0/src/cli_run.rs
…lIndex's new field Inherited, not introduced by the merge: on origin/main SymbolIndex declares five fields including type_head_exposures, and the hand-written peel-fixpoint probe in cli_run.rs initializes four, so cargo check --all-targets and the documented local cargo test --workspace both fail there. Neither the struct nor that probe is touched by this branch. It is the eleventh-consumer class again: the field was added to the emitted mirror, and the hand-written seed periphery that constructs the same struct is a population no .dag census reaches. The Rust suite left CI on 2026-07-11, so nothing observed it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ng to the JSON serializer data_value_has_cross_refs answers one question -- does this initializer reference another declaration -- and ExprVar was already true for exactly the right reason. ExprCall is that same reference with an argument list, and it fell through `_` to false, routing `data d: R = mk(..)` to a serializer that can only render literals. The serializer then refused with "unsupported mock expression", so the failure surfaced one layer below the judgment that caused it. TOTAL AT THE LEVEL EXAMINED, BLIND ONE LEVEL DOWN: exhaustive over expr_data, no arm missing, nothing a reviewer or a wildcard-free lens could catch -- the `_` carried the payload. Wrong by the function's own definition, not incomplete. MEASURED BEFORE EDITING, with two instruments that disagreed: line regex 1259 rows (WRONG -- blind to multi-line and to nested calls) multi-line census 3181 rows across 971 files, corpus-wide emitted mirrors 9 rows refused, all in std_primitive_projection.rs The third decides: only the seed closure is lowered to Rust, and the JSON branch is guarded by record-shaped-type AND no-cross-refs. Every row on that path today emitted successfully, which by construction means it holds no call. So nothing that works today can move. VERIFIED AFTER: JSON-path rows 34 before and 34 after -- zero existing rows changed route. The emitter mirror moved by exactly one line. required-regen reports first_generation_equal=true. Authorized as a scoped one-arm repair; the surrounding emitter is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fourth instance of this class on this lane, and the reason I missed it is the finding: my local sweep checked src/v1/*.dag, because that is where the previous three appeared. The required-ci parse phase covers dag/ as well, and required-regen -- the only phase I can run locally in a tight loop -- does not run that parse at all. So the local verification loop cannot see this class in dag/, and I scoped the sweep to where the error had last appeared rather than to where the rule applies. Swept every .dag this branch touches, not just the file CI named: all clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
witnesses.yml builds 2 of the 16 bin targets v1-compiler declares, so 14 are absent from the build selection and nothing required compiles them. Combined with the Rust suite removed from CI 2026-07-11, clippy 2026-07-08, and the compile-clean gate deleted in the floor cut, a bin can go stale silently -- and one had: #8952 added authored_import_names to TypeEnv without updating infer_semantics_witness, leaving main red at 'cargo check -p v1-compiler'. That specimen is being repaired separately in #9205; this is the standing that stops the next one. Two populations were answered by one roster: RuntimeArtifactPopulation -- the executables this job RUNS (claim_executor, gunbc) BinaryCompilePopulation -- every bin target the manifest DECLARES witness_floor_required_bins is correct as the first and is left alone. Derived, not a second roster: the step calls repo_self_build_command(bins: []), whose no-selector form gunbc.repo_self_build already documents as cargo's meaning for 'build every target'. A new [[bin]] joins the standing with no edit anywhere, and no new argv word is minted. Build rather than check, decided by measurement: from a cold target dir with the two-bin build already paid, building every target cost 12s against 52s for 'cargo check --release --bins', and it additionally links. Placed LAST, after the fold and the roster uploads, guarded by !cancelled(): ahead of the fold it would be a preparation mask, and an auxiliary compile error would take the floor's ledger with it.
… stale TypeEnv initializers blocking the local suite The test asserted nothing until it built. Two separate obstacles, both found by running it rather than reading it: compile_sources takes an im::Vector, so the fixture's Vec needed .into(). infer_semantics_witness.rs constructs TypeEnv literally at six sites and has been missing authored_import_names since that field landed in #8952, so ANY cargo test in this crate failed to build -- cargo builds bins for a --test target too. Pre-existing on main and invisible there because the Rust suite is a local check, removed from CI 2026-07-11. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…census A workflow_dispatch job with ZERO runs in its entire existence, its .dag emitter (286 lines), its GeneratedArtifact registration, the claim_executor mode, and the 266-line cli_run census that had no other consumer. WHY THE WHOLE CHAIN AND NOT JUST THE FLAG. The mode was the census's only caller and the workflow was the mode's only caller, so deleting any one link would have left the other two as an authority for a fact nothing asks. The fail-closed census did the finding: removing the GeneratedArtifact variant surfaced five more sites (the roster list, the commit-policy arm, the equality arm, the emit import and the yml-parse arm) that a name-grep of the workflow path alone would have missed. THIS IS A DECLARED CAPABILITY DROP, not a dead-code sweep, and saying so is the point of the entry. WHAT IS GONE: the only route to a located corpus-wide type-judgment population -- the blocking/advisory/unclassified partition over the required run's own subject, with its planted control. DESIGN 4b names exactly this gap in the other direction: the advisory residue is computed on every required run and counted by nothing, and a frontier whose deficit frequency is unobservable never ranks for climbing. That argument is why the mode was built. WHY IT GOES ANYWAY: it was never executed once. An instrument nobody has ever run is specification-without-execution, not coverage, and a workflow_dispatch job nobody dispatches cannot be the thing that makes a frequency observable. Keeping the flag while deleting the workflow would be worse -- a surviving mode no workflow invokes guards nothing and would be cited as though it did. POPULATION: one capability, the corpus type-judgment measurement. PREVIOUS STATE: reachable by manual dispatch, never reached. TEMPORARY STATE: no route. RESTORATION TRIGGER: the measurement returns as a QUERY over the build/test target graph -- the population is a property of what the required run compiled, which is exactly what a target-graph query answers -- rather than as a mode on a binary this program is deleting. It does not return as a flag. NOT CLAIMED: this does not repair src/v1/stage0/src/bin/infer_semantics_witness.rs, which #8952 (ffb0170) broke by adding TypeEnv.authored_import_names without updating six initializers there. That binary is in fleet-converge.yml's build list and does not compile on main today; it is untouched here and is not this cut's to fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… cli_run tests review 55847 caught Review 55847 found four tests in cli_run.rs still referencing corpus_judgment_* symbols the census deletion removed. It was right, and it had found one end of a larger population: 542 test functions across the two files tested machinery this branch deleted. claim_executor 11,380 -> 6,270. WHY THE REVIEWER SAW FOUR AND NOT 546. Two masks, and the second is the one worth recording. My own verification ran `cargo check --bin claim_executor` WITHOUT `--tests`, so a test module referencing a deleted symbol produced no diagnostic at all -- the deletion was verified against a target that does not compile tests. Then, when I did run --tests, the seed's lib failed FIRST on main's unrelated TypeEnv breakage, and 526 downstream errors were masked behind that one. A masked run and a clean run rendered identically: 526 errors and 1 error look like progress rather than a different question being answered. DESIGN's execution-provenance row names exactly this, and it cost two wasted sweeps here. DELETED SURGICALLY, NOT WHOLESALE, and the distinction is load-bearing. The obvious cut was `mod tests` entire -- 5,514 lines, 518 of the 526 errors. It would have been wrong: `verify_build_artifacts_reds_on_zero_byte` lives in that module and is the discriminating RED for a mode fleet-converge.yml invokes twice. So the cut deletes only test functions that FAIL TO COMPILE because their subject is gone, iterated to a fixed point against the compiler. 38 tests survive, including all four verify_build_artifacts controls (accepts / zero-byte / missing / empty-arglist) and the five attempt_identity refusals. That is the enumerate-before-deleting rule applied to a test module: a module is deleted for one reason and takes everything in it unless its contents are enumerated first. The compiler did the enumeration. WHAT IS NOT CLAIMED. src/v1/stage0/src/bin/infer_semantics_witness.rs is still broken by #8952 (six TypeEnv initializers) and is untouched here; it is in fleet-converge.yml's build list and does not compile on main. The one-line lib fix at cli_run.rs is present because without it nothing on this branch can compile tests at all -- deep-ram-742 ships the same repair in #9222 and the duplicate is deliberate, not a fork: identical text, and whichever lands second merges clean or drops out. .gitattributes loses its corpus-type-judgment merge row, which review 55847 also flagged. That row is DERIVED from the artifact roster this branch already trimmed, so the committed file was stale against its own authority rather than carrying an independent fact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…66 -> 11,508) (#9228) * Delete the plan/walk surface nothing could reach, and the 27 cli_run symbols it was importing to do it claim_executor: 21,366 -> 11,508 lines. Zero dead-code warnings, zero errors, fmt clean, and the two live modes verified by execution. WHY THIS IS A REACHABILITY CUT AND NOT AN OCCUPANCY ONE. run() required --plan-entry after every --required-* arm returned, and nothing supplies --plan-entry: not a workflow, not a hook, not an emitted yml. The plan function it defaulted to named src/v2/workflow/ci_floor_plan.dag, which the 2026-08-15 floor cut deleted. So the plan walk, the batch executor, the coordinator/worker protocol, the scoped-request machinery, the perturb re-walk, the falsifier failure-class helpers and their terminal reporting were not quiet guards that happened to be empty -- their governing MECHANISM was removed, and no input any caller can author reaches them. DESIGN's reachability-read-as-occupancy row asks three questions; this population answers no to the first two, not merely to the third. The coordinator is the sharpest case: maybe_run_floor_coordinator is the first thing main() does, and it returns None immediately unless --plan-function names a plan entry that does not exist. It spawned this binary as its own child with --floor-worker-role/--scoped-batch-id, from an arm that never armed. WHAT THE CENSUS SURFACED, which is the point of cutting at the root rather than the leaves. Removing the walk left 251 items unreferenced; deleting those left 27 cli_run imports unused -- active_workset_admit, the heartbeat feed, the discovery roster snapshot, the histogram/percentile projections, install_floor_compile_clean_receipt and the rest. That is a measurement about cli_run.rs, not about this file: a quarter of the seam between the two existed only to feed machinery with no caller. WHAT REMAINS AND IS PROVEN BY EXECUTION (release binary, four cases): --required-ci the one mode witnesses.yml invokes --verify-build-artifacts fleet-converge.yml, both jobs no mode -> exit 2, typed refusal naming the live modes --plan-entry -> exit 2, unknown argument --verify-build-artifacts on a present binary -> exit 0 --verify-build-artifacts on an absent one -> exit 1, fail-closed The last pair is the discriminating red: the mode's whole purpose is refusing a 'successful' build that produced a missing or zero-byte artifact, so a green without its red would establish nothing. The no-mode arm REFUSES rather than falling through to a default. An argv this binary no longer understands must stop the line; a silent success would be the absorbing fallback one level up from the machinery just deleted. WHAT THIS DOES NOT CLAIM. The .dag residue is NOT repaired here and is named rather than left to be rediscovered: gunbc.cli_invoke still builds --plan-entry/--plan-function/--notice-title argv (dead transport -- no workflow contains those words), PlanFunction survives in ci_spec/cli_services with its witness, and src/v2/test/fixture/walk_plan_stage/ is a fixture family whose only execution route was the recipes this commit deletes. That family has eight external touchpoints including a Rust integration test, so it is its own census and its own cut, not a tail this one can sweep. Nothing in CI executed any of it before this commit or after it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Delete the five empty impl stubs the method deletion left behind Review on #9228 named two (FloorBatchClampAuthority, ResolvedFloorBatchClamp) as the ones it spot-checked. There were five: ParsedRunnableProfile, ProcessTermination and ScopedExecutionRequest carry the same shape. Swept by pattern rather than by the two cited, because a cosmetic residue found by inspection is a population, not a list -- fixing only the named two would leave three identical stubs behind and read as though the class had been handled. Each is the shell of an impl whose every method was deleted as unreachable. The types themselves are still constructed and are unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Delete corpus-type-judgment at the root: mode, workflow, emitter and census A workflow_dispatch job with ZERO runs in its entire existence, its .dag emitter (286 lines), its GeneratedArtifact registration, the claim_executor mode, and the 266-line cli_run census that had no other consumer. WHY THE WHOLE CHAIN AND NOT JUST THE FLAG. The mode was the census's only caller and the workflow was the mode's only caller, so deleting any one link would have left the other two as an authority for a fact nothing asks. The fail-closed census did the finding: removing the GeneratedArtifact variant surfaced five more sites (the roster list, the commit-policy arm, the equality arm, the emit import and the yml-parse arm) that a name-grep of the workflow path alone would have missed. THIS IS A DECLARED CAPABILITY DROP, not a dead-code sweep, and saying so is the point of the entry. WHAT IS GONE: the only route to a located corpus-wide type-judgment population -- the blocking/advisory/unclassified partition over the required run's own subject, with its planted control. DESIGN 4b names exactly this gap in the other direction: the advisory residue is computed on every required run and counted by nothing, and a frontier whose deficit frequency is unobservable never ranks for climbing. That argument is why the mode was built. WHY IT GOES ANYWAY: it was never executed once. An instrument nobody has ever run is specification-without-execution, not coverage, and a workflow_dispatch job nobody dispatches cannot be the thing that makes a frequency observable. Keeping the flag while deleting the workflow would be worse -- a surviving mode no workflow invokes guards nothing and would be cited as though it did. POPULATION: one capability, the corpus type-judgment measurement. PREVIOUS STATE: reachable by manual dispatch, never reached. TEMPORARY STATE: no route. RESTORATION TRIGGER: the measurement returns as a QUERY over the build/test target graph -- the population is a property of what the required run compiled, which is exactly what a target-graph query answers -- rather than as a mode on a binary this program is deleting. It does not return as a flag. NOT CLAIMED: this does not repair src/v1/stage0/src/bin/infer_semantics_witness.rs, which #8952 (ffb0170) broke by adding TypeEnv.authored_import_names without updating six initializers there. That binary is in fleet-converge.yml's build list and does not compile on main today; it is untouched here and is not this cut's to fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Delete the 542 tests whose subjects this branch deleted, and the four cli_run tests review 55847 caught Review 55847 found four tests in cli_run.rs still referencing corpus_judgment_* symbols the census deletion removed. It was right, and it had found one end of a larger population: 542 test functions across the two files tested machinery this branch deleted. claim_executor 11,380 -> 6,270. WHY THE REVIEWER SAW FOUR AND NOT 546. Two masks, and the second is the one worth recording. My own verification ran `cargo check --bin claim_executor` WITHOUT `--tests`, so a test module referencing a deleted symbol produced no diagnostic at all -- the deletion was verified against a target that does not compile tests. Then, when I did run --tests, the seed's lib failed FIRST on main's unrelated TypeEnv breakage, and 526 downstream errors were masked behind that one. A masked run and a clean run rendered identically: 526 errors and 1 error look like progress rather than a different question being answered. DESIGN's execution-provenance row names exactly this, and it cost two wasted sweeps here. DELETED SURGICALLY, NOT WHOLESALE, and the distinction is load-bearing. The obvious cut was `mod tests` entire -- 5,514 lines, 518 of the 526 errors. It would have been wrong: `verify_build_artifacts_reds_on_zero_byte` lives in that module and is the discriminating RED for a mode fleet-converge.yml invokes twice. So the cut deletes only test functions that FAIL TO COMPILE because their subject is gone, iterated to a fixed point against the compiler. 38 tests survive, including all four verify_build_artifacts controls (accepts / zero-byte / missing / empty-arglist) and the five attempt_identity refusals. That is the enumerate-before-deleting rule applied to a test module: a module is deleted for one reason and takes everything in it unless its contents are enumerated first. The compiler did the enumeration. WHAT IS NOT CLAIMED. src/v1/stage0/src/bin/infer_semantics_witness.rs is still broken by #8952 (six TypeEnv initializers) and is untouched here; it is in fleet-converge.yml's build list and does not compile on main. The one-line lib fix at cli_run.rs is present because without it nothing on this branch can compile tests at all -- deep-ram-742 ships the same repair in #9222 and the duplicate is deliberate, not a fork: identical text, and whichever lands second merges clean or drops out. .gitattributes loses its corpus-type-judgment merge row, which review 55847 also flagged. That row is DERIVED from the artifact roster this branch already trimmed, so the committed file was stale against its own authority rather than carrying an independent fact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Delete the second argv reader: the coordinator was reachable the whole time Review 55875 is correct and the finding is the important kind -- not a leftover, a REACHABLE MUTATING PATH behind a deletion I had claimed was complete. WHAT I GOT WRONG. main() read std::env::args() ITSELF and dispatched maybe_run_floor_coordinator before run() parsed anything. So deleting --plan-function from run()'s parser did nothing to the coordinator's reachability. My earlier claim that the coordinator "returns None immediately unless --plan-function names a plan entry that does not exist" described the guard correctly and the DISPATCH not at all: the guard tests argv, and argv still carried the flag. WHY IT WAS WORSE THAN BEFORE THIS BRANCH, which is what makes it a defect rather than an incomplete cut. With --plan-function deleted from one parser and live in the other, the flag answered `unknown argument` for every value EXCEPT gunbc_ci_floor_plan -- the one value that ran the entire coordinator. And that path is not inert: it create_dir_all's a receipt directory, remove_file's the worker-observation receipt, the scoped-execution requests and the phase journal, arms a scoped receipt and spawns workers, all before any refusal could fire. A deletion that leaves the single most destructive entry point as the only reachable one is the opposite of fail-closed. THE LESSON IS THE CUT'S, NOT THE COORDINATOR'S: a flag is not deleted when one of two parsers stops reading it. The repair is at the root -- main() no longer reads argv at all, run() is the only thing that does -- and the census then took the worker/scoped-request machinery with it: 6,275 -> 5,069 lines, 57 further test functions whose subjects went, iterated to a joint fixed point over errors and dead code. PROVEN BY EXECUTION, on the exact invocation the review named: claim_executor --plan-function gunbc_ci_floor_plan --source-root dag -> "unknown argument: --plan-function", EXIT=2, and no receipt file created --verify-build-artifacts on a present binary -> 0 --verify-build-artifacts on an absent one -> 1 The negative control matters here specifically: the bug was that a mutating path ran before the refusal, so "it refuses" is only half the claim -- the other half is that nothing was written on the way to refusing. claim_executor is now 5,069 lines against 21,366 on main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Drop the four imports the coordinator cut orphaned; CI builds with -D warnings and my check did not CI red at bda1331, all three jobs, one root cause: ExitStatus, std::time::Instant, build_floor_discovery_request and verify_floor_discovery_terminal_for_coordinator lost their last users when the coordinator and its worker machinery went, and the required build compiles with -D warnings, so an unused import is an ERROR there. `floor` failed identically; `witnesses` is only the gate that reports both. THIS IS THE THIRD TIME ON THIS BRANCH THAT A CHECK WAS GREEN AND THE CLAIM WAS WRONG, and it is the same defect each time: verifying a deletion against a target that cannot observe its dependents. --bin without --tests could not see 526 orphaned test references. A broken lib masked those behind one error. And a bare cargo check cannot see an unused import, because unused-imports is a WARNING until -D warnings makes it fatal -- so the instrument I was steering by was strictly weaker than the one that gates merge. The repair is to use the gating instrument, and to PROVE it is the one running rather than assume the flag arrived. Planted control, executed: with RUSTFLAGS="-D warnings" forwarded (ctrl-build prints `forwarding env: RUSTFLAGS`), an added `use std::collections::BTreeSet;` produces `error: unused import`, not a warning. The clean result on the real tree is therefore load-bearing rather than a flag that silently never reached rustc. No behavior change: four import names, zero call sites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…9196) witnesses.yml builds 2 of the 16 bin targets v1-compiler declares, so 14 are absent from the build selection and nothing required compiles them. Combined with the Rust suite removed from CI 2026-07-11, clippy 2026-07-08, and the compile-clean gate deleted in the floor cut, a bin can go stale silently -- and one had: #8952 added authored_import_names to TypeEnv without updating infer_semantics_witness, leaving main red at 'cargo check -p v1-compiler'. That specimen is being repaired separately in #9205; this is the standing that stops the next one. Two populations were answered by one roster: RuntimeArtifactPopulation -- the executables this job RUNS (claim_executor, gunbc) BinaryCompilePopulation -- every bin target the manifest DECLARES witness_floor_required_bins is correct as the first and is left alone. Derived, not a second roster: the step calls repo_self_build_command(bins: []), whose no-selector form gunbc.repo_self_build already documents as cargo's meaning for 'build every target'. A new [[bin]] joins the standing with no edit anywhere, and no new argv word is minted. Build rather than check, decided by measurement: from a cold target dir with the two-bin build already paid, building every target cost 12s against 52s for 'cargo check --release --bins', and it additionally links. Placed LAST, after the fold and the roster uploads, guarded by !cancelled(): ahead of the fold it would be a preparation mask, and an auxiliary compile error would take the floor's ledger with it. Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
#9209) * Resolve a qualified callee in pipe position as that callee, not as a method named by its root segment parse_pipe_rhs consumed exactly one identifier after the pipe arrow, so `xs |> a.b.f()` lowered as a method call named `a` on the receiver, with the remaining segments folding on top as field access. The refusal an author saw therefore named the root segment as a missing method on the receiver's type, while the same callee in ordinary call position resolved and still does. The parse now consumes the whole dotted path and hands the leading segments to the qualifier spine that 04_infer's qualified_direct_call arm already reads, so both positions reach one resolution path. A bare callee is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Give the piped receiver arg the same span convention as other positional args Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Decide a dotted pipe callee by its application, not by the shape of its path An unconditional dotted-path consume re-denoted a live, correct site -- v1.compiler.emit_rust's `field_names |> first.value`, pipe into the kernel method `first` then take `.value` off the Optional -- as a call into a module named `first`. The parser cannot know that a leading path names a module, so the pipe production decides syntactically: an APPLIED dotted callee is a qualified callee applied to the receiver, an unapplied one stays method-then-field. Regression case added to the test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Regenerate the stage0 parse mirror for the pipe-callee change Emitted by claim_executor --required-regen from the current .dag; installed as the candidate it produced. Before the install, regen refuses naming exactly this file (first_generation_equal=false, 'generated surface drift: v1_compiler_parse.rs'), which is what makes the install the closing move rather than a hand edit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Make the evidence compile and execute: im::Vector conversion, and the stale TypeEnv initializers blocking the local suite The test asserted nothing until it built. Two separate obstacles, both found by running it rather than reading it: compile_sources takes an im::Vector, so the fixture's Vec needed .into(). infer_semantics_witness.rs constructs TypeEnv literally at six sites and has been missing authored_import_names since that field landed in #8952, so ANY cargo test in this crate failed to build -- cargo builds bins for a --test target too. Pre-existing on main and invisible there because the Rust suite is a local check, removed from CI 2026-07-11. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Name the six ExistingSeedItemModified declarations the receipt excludes Review 55814: the receipt claimed a +7 delta confined to the new test file while the change also touched six declarations in infer_semantics_witness.rs. They are modifications, not additions -- each gained one already-required field initializer inside an existing body -- so they stay out of hand_authored_declarations per the disposition the sibling receipts use, and the receipt now SAYS so instead of omitting them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Say four cases where the trigger said three: the regression case made it four Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Drop the duplicate authored_import_names the main merge text-merged in Same repair as on the stacked branch, and the same cause: main added the field after module_path, this branch added it after source_visible_names, and git's text merge kept BOTH in six TypeEnv literals (E0062 x6). The witness lane passed; the failing step was 'Every v1-compiler binary target compiles'. Main's placement is kept at all six sites -- the values are identical, so the choice minimizes divergence from main rather than changing semantics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Correct the hand-Rust census receipt: it described base-branch work as this change's The row excluded six declarations in infer_semantics_witness.rs as ExistingSeedItemModified. That was true when written and is now false: the file has no diff in this change, and origin/main already carries all six authored_import_names initializers. Main repaired them independently. This branch's copy of the same repair was dropped when the merge produced the field TWICE per literal (E0062 at six sites), and keeping main's placement removed this change's delta in that file entirely -- so the exclusion paragraph was left describing work the base branch owns as part of this change's census. That inflates what the change is answerable for, which is the one direction a census receipt must not be wrong in. The removal is recorded in the row rather than made silently, and the live fact the deleted paragraph carried is kept: the enumerated evidence could not execute while those initializers were missing, and the repair that made it executable is main's, not this change's. Found by review 56100. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n census, tapping nothing The exact consumer relation is what makes a replacement cut's population exact and what decides whether residue goes loud or silent when X is deleted. The ruled construction is to observe decisions the compiler ALREADY makes, keyed by the parser-minted occurrence identity -- reconstructing resolution from outside would answer with what a reimplementation believes rather than what the compiler selected. But a tap cannot be placed where the information is already gone, and which sites have lost it is not knowable from a type name. So the first output is not the relation: it is where exact occurrence identity and exact target identity COEXIST. THE PRICING RESULT: zero of five callable-resolution sites are tappable, because not one receives the occurrence identity -- every one is keyed by a bare name: String. Threading that seam is not a repair for stragglers, it is the whole precondition, and 0B.1b cannot begin on this family until it lands. The target axis discriminates, which is what makes this a measurement rather than a decoration: four sites carry the exact selected declaration in the outcome, one (borrowed_census_decl) carries the owning module while the declaration NAME does not survive. Two remedies, not one -- an erased target is repaired by widening an outcome, an absent occurrence by threading an input. THE ROSTER WAS RE-DERIVED AGAINST CURRENT MAIN BEFORE LANDING, and that is the more useful half. First read four days of main movement ago, it recorded all three FuncSigLookup sites as computed-then-erased. Between the readings #8952 and #9436 landed and FuncSigResolved gained its `declared` field. Publishing the first reading would have named a defect that no longer existed and priced work already done, in the file it was measuring. The revision field is not bookkeeping: these two files moved 440 lines while the claim was being written. Counts are DERIVED from the two axes, never stored, so a transcribed verdict cannot disagree with the facts beside it. Four unsurveyed decision surfaces are counted refusals rather than absent rows. No tap is placed, no relation produced. 13/13 green, including the four-corner control -- tappable is a conjunction at one site, and a conjunction written as a disjunction passes every single-axis test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n census, tapping nothing (#9600) * Price the resolver tap before building it: the decision-site retention census, tapping nothing The exact consumer relation is what makes a replacement cut's population exact and what decides whether residue goes loud or silent when X is deleted. The ruled construction is to observe decisions the compiler ALREADY makes, keyed by the parser-minted occurrence identity -- reconstructing resolution from outside would answer with what a reimplementation believes rather than what the compiler selected. But a tap cannot be placed where the information is already gone, and which sites have lost it is not knowable from a type name. So the first output is not the relation: it is where exact occurrence identity and exact target identity COEXIST. THE PRICING RESULT: zero of five callable-resolution sites are tappable, because not one receives the occurrence identity -- every one is keyed by a bare name: String. Threading that seam is not a repair for stragglers, it is the whole precondition, and 0B.1b cannot begin on this family until it lands. The target axis discriminates, which is what makes this a measurement rather than a decoration: four sites carry the exact selected declaration in the outcome, one (borrowed_census_decl) carries the owning module while the declaration NAME does not survive. Two remedies, not one -- an erased target is repaired by widening an outcome, an absent occurrence by threading an input. THE ROSTER WAS RE-DERIVED AGAINST CURRENT MAIN BEFORE LANDING, and that is the more useful half. First read four days of main movement ago, it recorded all three FuncSigLookup sites as computed-then-erased. Between the readings #8952 and #9436 landed and FuncSigResolved gained its `declared` field. Publishing the first reading would have named a defect that no longer existed and priced work already done, in the file it was measuring. The revision field is not bookkeeping: these two files moved 440 lines while the claim was being written. Counts are DERIVED from the two axes, never stored, so a transcribed verdict cannot disagree with the facts beside it. Four unsurveyed decision surfaces are counted refusals rather than absent rows. No tap is placed, no relation produced. 13/13 green, including the four-corner control -- tappable is a conjunction at one site, and a conjunction written as a disjunction passes every single-axis test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Split the remedy count in two: the census was collapsing the distinction it exists to make The standing derived ONE not-tappable count, defined as answers-a-reference AND NOT tappable. Because tappable is a conjunction over two independent axes, that reported the same number for a site missing its occurrence identity and a site whose target was erased -- and those need OPPOSITE repairs: one threads an INPUT into the decision site, the other widens an OUTCOME to retain what the site already selected. This module's own note says exactly that, in as many words, and then the derived count collapsed it. The census contradicted itself in the one place a reader takes the number from rather than the prose, which is worse than a note that had never made the distinction. The witness had permanently RATIFIED the collapse: the four-corner control asserted a single threading count of three, so the occurrence-exact/target-erased corner was encoded as owing occurrence threading, a repair it does not need. Now two overlapping derived counts. The axes are independent, so they do NOT partition the population: a site deficient on both inhabits both, because it owes both repairs, and forcing a partition would be the same collapse in a tidier shape. The four corners read 1 tappable / 2 occurrence-threading / 2 target-retention, and the production roster reads 5 surveyed / 0 tappable / 5 occurrence-threading / 1 target-retention -- which is what the measurement always found: threading is the family-wide prerequisite and borrowed_census_decl additionally needs its outcome widened. Found in side-chat review, not by the blocking review on the PR. 13/13 green by execution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Two findings that came out of executing this, stated first
1. The wall could not fire for its own specimen, because of a guard I wrote
builtin_callable_candidatesexcluded every algebra method template name. I wrote that guard and justified it in a paragraph: receiver-dispatched names (filter,any,contains) belong to the known-methodExprCallarm, and admitting them would make every module declaring a collection-shaped name a collision.Measured, the guard was redundant for the names it named and fatal for the ones it did not. Of ~50 algebra template names only 19 carry a
builtin_function_registryrow, andfilter/any/contains/fold/mapare not among them —infer_builtin_call_typealready answeredAbsent, so they were never candidates and the guard changed nothing about them. What it actually excluded was the 19 names that do have a free-call surface,map_getfirst among them — the exact name row 30 is about.So the wall was inert on the collision it was built for, and the witness that should have shown that returned
falsein a way indistinguishable from a broken fixture (which it also was). The admission test is now the registry alone, which is precisely the question being asked: a registry row means "a bare call to this name types against a primitive."Worth naming beyond the fix: a justification is not evidence, and the paragraph made the guard more durable rather than less — a defended exclusion is harder to question than an undefended one.
The wall does not add a diagnostic — it replaces three false ones with one true one
Measured on the RED source, before and after, through
compile_dag_diagnostic_censuswith the mirrors regenerated and installed:VariantNotFound/Present,VariantNotFound/Absent,NonExhaustiveMatch/(non-exhaustive)AmbiguousReference/BLOCK/map_getThe three "before" rows are all downstream, and all describe the damage rather than the cause: each one points the author at a
matcharm that was never the problem. The single "after" row sits at the call and names both authorities. This is not a stricter compiler — it is a compiler that stops lying about where the defect is, which is the difference between a check and a diagnosis.It also closes row 30's own text, which recorded the class as latent and observable only as downstream non-exhaustive matches. The same source now produces the refusal instead of those matches, on the real acceptance path.
2. Row 30 is now reproducible on demand, not just historically observed
Two sources, identical call, import closure the only difference — measured through
compile_dag_diagnostic_census:std.typesonly[]— completely clean; baremap_getis the builtin andPresent/Absentis exhaustiveextdeps.posix.identityVariantNotFound/Present,VariantNotFound/Absent,NonExhaustiveMatchThe second module names no authority for
map_get;posix.identity's own closure supplies theOutcome-returning declaration. That is the incident, on demand, under a controlled fixture — converting the specification from a historical observation into a discriminating RED.What this is
Two independent things, deliberately in one PR and named separately so neither hides inside the other.
The wall.
lookup_func_sigstops asking the environment first and the builtin surface second. It now collects every admissible callable candidate and then decides: zero → typedFuncSigUnresolved; exactly one → resolve; two or more → typedFuncSigAmbiguousnaming every exact candidate. And the projection that erased that third arm (func_sig_if_resolved, which mappedFuncSigAmbiguous { candidates: _ }toAbsentfor ten consumers) is deleted, replaced by a totalfunc_sig_for_derivationwhoseNoDerivableSigcarries a typed reason.An independent repair, in its own commit: the
emit_map_hasbuiltin registry row is deleted. It certified a call that nothing at any tier could run.Specification: row 30 of
docs/plans/compiler-guarantee-recovery-gap-analysis.md.The wall
Mechanism (a) — the candidate set is now assembled.
callable_lookup_over_candidatesreads four levels, and the level structure is the whole point, because the failure mode this lane must not become is "the leaf name appears twice, therefore refuse":body_shadow_aware_func_sig. Legitimate shadowing stays legal.type_env.source_visible_names: the module's own declarations, the kernel surface, selectively imported names, all-module import exports).import v2.std.collection { map_get }names one exact declaration, and every later bare call in that module is a reference to the thing the author named — the same authorial act as a qualified reference, one line higher. The builtin surface is not a co-candidate here.func_sig_from_global_bare), reached only when nothing above produced a candidate. Preserved: a bare call to a declaration outside the closure still resolves exactly as before.Why 1a exists, and why it is not the precedence this cut removes. The row-30 incident is transitive:
dag/extdeps/git/object_store.dagatac9e010a83~1namesv2.std.collectionzero times — the declaration reached it through somebody else's edge. Meanwhile six modules bare-callmap_getwhile naming it in a listed import —03_ingest,target_model,compilation_unit,cli_surface,affected_set,v2_effect_io_pure— and zero reach it through anis_allimport. Without 1a the wall refuses all six, which includes the v2 compiler's ingest and target-model stages. (An earlier revision of this section said sixteen: that was modules importingv2.std.collectionat all, only six of which bare-callmap_get.)1a ranks nothing this cut forbids: not import order (a listed import is set membership, not a position, and two listed imports of one name are still ambiguous), not "declared beats builtin" (an unlisted declaration still does not beat the builtin — that is exactly the case that refuses). It distinguishes what the author named from what a closure happened to supply, which is the definition of the defect rather than a tiebreak between kinds of callable.
It reuses
source_visible_namesrather than minting a visibility notion beside it: that map is already the corpus authority for "the author named this", andv1.compiler.infer_env'sclosure_independent_bare_free_call_notealready refuses global-bare and pool-coincidence resolution for registered symbols absent from it — same question, same authority, one seam over. Empty is ignorance, not an answer:source_visible_namesis built at resolve time and is not persisted ontoTypedModule.type_env, so the emit-side consumer reads an empty map and cannot distinguish listed from transitive. An empty map therefore takes the 1a arm (today's behaviour), never "nothing was named", which would make the wall strictest exactly where it knows least.Rung, per path (§4b), minimum governing: refuses on the source→resolve path; silent on the emit path. Next-rung trigger for the emit row: persisting
source_visible_namesontoTypedModule.type_env. Citing only the resolve path would be inflation.The decision itself is not new logic. It routes through
module_path_owner_binding_decide, the existing single 0/1/many cardinality authority, so the wall does not mint a second answer to "how many owners does this name have" (§3).Order-independence falls out of the shape rather than being asserted: the candidate list is decided by cardinality, not scanned for a first hit. There is no "first imported wins", no "declared beats builtin", no import-order arm to be sensitive to.
One deliberate asymmetry, stated because it looks like a hole. A lone builtin candidate answers
FuncSigUnresolvedrather than resolving. The registry maps a name to a return type and carries no parameters, so resolving from it would assert a call shape the registry does not know — a fabricated plausible signature (§5).Unresolvedis the correct answer and routes the call to the known-builtin bridge exactly as today. The builtin's presence is load-bearing only for counting, which is the half row 30 needed.Diagnostic. Candidates carry exact identities —
DeclaredCallable { owner_module_path, decl_name }/BuiltinCallable { primitive_name }— not bare strings, so the measured specimen reads:Evidence — three sources, all through the real acceptance path (
compile_dag_diagnostic_censusruns the production resolve/typecheck over the live source roots), indag/test/claim/callable_candidate_ambiguity_witness_test.dag, enrolled in the required floor (nolive_tree_disposition, so it executes rather than being declined):import extdeps.posix.identity, whose own closure containsv2.std.collection)import v2.std.collection { map_get })The counter returns
0 - 1onCensusNotRunnable, so a broken harness fails in both directions rather than passing the RED by silence.The RED had to be rebuilt, and that is worth recording rather than quietly fixing. Its first version used the listed-import source as its RED and asserted a refusal — which is not the incident's shape at all. A witness that certifies a wall while testing a shape the defect never had is worse than no witness, because it goes green and reads as coverage: this one would have certified refusing the v2 compiler core as correct behaviour. It was found by checking the specimen against the incident, which is the only way it could have been found.
What the wall's 1215-site output IS
Turning the wall on exposes 1215 sites. That number is not a defect count and must not be read as one. It is the first-ever measurement of the surface-versus-primitive fork: how much of this corpus calls a name that exists both as a registered primitive and as a
.dagdeclaration.lengthmap_insertdecl_factsmap_getdata_decl_type_factsto_string31,emit_map_has20,map_has31147 of the 1161 dag+src/v2 sites are not ambiguity bugs. They are a nickname census (§3): one concept wearing one spelling across two carriers, where the second carrier adds no meaning. The dissolution is the
PrimitiveDefinition/PrimitiveIdentityjoin (dag/std/primitive_identity.dag), dispatched as its own lane; this PR is stacked behind it and stays draft until it lands. It is explicitly not dissolved by generalizing the_host_bindingmarker: that marker links a contract to its fn by name, through a module-leveldatarow — the same nickname class the wall exists to remove.Per-name verdicts, each established by execution
Asked for per name, no bulk judgment, and each carries its own evidence class:
map_get— TWO AUTHORITIES. control 14 diagnostics, call 17: +3, including oneNonExhaustiveMatch— row 30's signature exactly. The builtin returnsOptional<V>;v2.std.collection.map_getreturnsOutcome<Optional<V>>. Different shape, so aPresent/Absentmatch over the declared one is non-exhaustive.length— ONE AUTHORITY. control 14, call 14: no drift. The free-call builtin arm intercepts ahead of user-fn dispatch.map_insert— ONE AUTHORITY. control 14, call 14: no drift. The module declaresmap_insert_host_binding: PrimitiveContract, and the interpreter carriestry_v2_std_collection_map_primitive_grounding → MapGroundingMapInsert— grounded, not forked.decl_facts,data_decl_type_facts,export_signature_facts,concept_decl_facts— ONE AUTHORITY, and by a different and stronger instrument than the probe: an already-green floor witness (test.claim.record_construction_census_witness_test) imports and bare-callsdecl_facts, whose declared body isfn decl_facts(x) { decl_facts(x: x) }— an infinite self-call. A green run is only possible if the builtin served it. The existing corpus was the oracle; no new probe needed.emit_map_has— PHANTOM. Registry-typedBool, no arm at any tier, bare call →NoSuchFunction. Deleted in this PR (below).The control construction — the reusable part
The first pass of this probe reported
map_get +17, length +14, map_insert +14, decl_facts +8and read as all four names drifting. Three of those four were false.The bug: the probe counted every diagnostic a source produced, and an importing source inherits diagnostics from the imported closure, not only from the call site. So the instrument was measuring "did I import a big module", not "did this call resolve differently".
The fix, and the thing worth keeping:
Under that control only
map_getmoved. This is written down because the next person measuring a closure-sensitive property will otherwise rebuild the instrument wrong the same way — an uncontrolled count over an import closure looks like a measurement and is a measurement of the closure.The
map_getresidue: there is none, and that is a positive resultI initially reported "12 sites to repair". Classifying by the match arms rather than the spelling, over current main:
v2.std.collectionand matchAccepted/Rejected— Outcome-shaped, i.e. correct consumers of the declared fn. These are the twelve. Repairing them would have rewritten twelve correct call sites away from the authority they were deliberately written against — a regression, in a lane whose entire subject is that one name can mean two things.Present/Absent), and every one hasreaches_declarer = false— they resolve to the builtin and typecheck, as written.Row 30 asserted that none of the 43 is currently mis-resolved, from the absence of diagnostics. This establishes the same claim constructively, from a direction the row could not reach: absence of a symptom and emptiness of the intersection are different facts, and the second is the one that holds. The class is latent, not live — now on evidence rather than on silence.
gunbc#8944 (merged,
ac9e010a83) repaired four sites —dag/extdeps/git/object_store.dag×2,src/v2/workflow/floor_preparation.dag×2 — using the Map primitive methodX.lookup(key)rather than switching tomap_lookup, becausemap_lookuplives inv2.std.collection, the very module whose closure presence is the unstable fact. Those four are in main and in this branch via a merge; they are absent from every count above. No exclusion set was needed, and no repair set exists.This PR therefore edits no
map_getcall site.The
emit_map_hasdeletion (independent repair, own commit)Row 30 with the halves swapped: there, a consumer's import closure changes a name's meaning; here, the closure is the only thing supplying one.
A registry row types a bare call; an interpreter arm runs it.
emit_map_hashad the first and not the second — no free-call arm, no method arm, no bridge — so the call typechecked clean and answeredNoSuchFunctionat evaluation. Measured by execution, not by reading the dispatch table: a probe calling it bare throughgunbc runrefused withNoSuchFunction, while the same probe'smap_getandlengthreturned7and3.20 of 20 live call sites reach the declared
v1.compiler.infer_typesemit_map_hasthrough their own closure, so none loses resolution. That is exactly what kept the row invisible: nothing was ever served by it. (An earlier message of mine said 21. That figure counted the declaration in04_types.dagas one of its own callers — the measure-the-name-rather-than-the-binding error this PR exists to close, committed inside the receipt for it.git grepputs the occurrence count at 21 on every commit involved, so nothing moved under me; I simply miscounted, and the correction is recorded in the source note rather than quietly applied.)Deleting the row costs no call site and makes the typecheck honest: a future caller outside that closure now gets an unresolved-name refusal at compile time instead of a runtime
NoSuchFunction. Rung: runtime failure → compile-time refusal for that name; mechanically preventable, not structural — nothing yet prevents authoring the next registry row with no runtime behind it. Next-rung trigger: thePrimitiveIdentityjoin, which is precisely the registry-row-to-runtime join this row was missing.src/v1's 54 sites — named, not deferred silently
to_string31,emit_map_has20,map_has3. They fall in the gap between the two arms above: the witness roots are["dag", "src/v2"], so no floor witness can import asrc/v1module, and the census instruments above do not reach them. They are in scope for this program and are recorded here rather than dropped:emit_map_has's 20 are resolved by this PR — all reach the declaration, and the phantom row that could have shadowed them is gone.to_string(31) andmap_has(3) remain unmeasured by execution. Not asserted clean: no control probe was run against them, because the instrument that would run it cannot reachsrc/v1. They ride the samePrimitiveIdentitydissolution as the dag/src-v2 census, and the honest status is unmeasured, not one authority.The
lengthquestion is answered, by the join lane rather than hereI had recorded this as open: the
lengthverdict shows the free-call builtin arm intercepting ahead of user-fn dispatch, so the declaredv2.std.algebra.lengthbody cannot run for a bare call — does that generalize to the other projection declarations?It does, and gunbc#8964 (the
PrimitiveIdentityjoin,gentle-koi-563) establishes it by reading the declarations rather than by probing them, and splits the five names into three fidelities:HostRealizedSeam— body is a self-call, so the declaration could not survive being reached.decl_facts,export_signature_facts,data_decl_type_facts,concept_decl_facts,concept_decl_facts_live,empty_map_primitive_delegate.ModeledProjection— a real body the builtin arm intercepts ahead of.length,map_insert,empty_map. This is the answer to my question: dead code with a live spelling.DivergentProjection—map_get,Outcome<Optional<V>>against the primitive'sOptional<V>. Genuinely two authorities, and it must keep refusing at this wall.That carrier reproduced the
emit_map_hasfinding below from the model (registry_no_runtime=4:emit_map_has,Some,to_int,with) rather than from my probe — which is the receipt that the mechanism works, not that one specimen happened to be found.What this wall will consume when the join merges, agreed across the two lanes and stated here so a future reader can check my seam against their type rather than against a message thread: my suppression consumes
DeclarationProjectsPrimitiveand nothing else. Every other arm is either a refusal (NotAProjection,DivergentProjection) or a fall-through (SymbolUndisposed). Concretely — seam → suppress silently; modeled → suppress plus an advisory naming the intercepting primitive; divergent → refuse with the divergence text beside both candidate identities; not-a-projection → refuse; undisposed → do not refuse, fall through to today's behaviour and count.DeclaredCallable { owner_module_path, decl_name }becomesstd.decl_refDeclarationRefat that point rather than staying a structural twin of it; it is minted locally today only because v1'sinfer_sigscannot import adag/module.What remains for the join, measured under 1a
Counting bare calls only — a qualified
v2.std.collection.map_get(...)is an exact reference naming one exact declaration, not a bare call, and my first pass wrongly counted three of those as bare — and splitting by whether the author named the authority and whether the closure reaches the declarer:lengthmap_insertempty_mapdecl_factsmap_get341 refusals across ~88 modules, and every one of the four names carrying them is already disposed in gunbc#8964 with a suppress verdict (
length/map_insertModeledProjection,decl_factsHostRealizedSeam). So the stacking is now measured rather than assumed: 341 ≠ 0, this PR cannot merge first, and nothing further is needed from that lane once it does.There is no live
map_getcollision left indag/orsrc/v2. After gunbc#8944 the 1b count for it is zero: 12 sites name the authority, 85 never reach the declarer. The wall's RED for it is therefore fixture-authored, not corpus-present — which §4b makes the correct posture rather than a gap, since reachability is judged against what a fixture may author, never against what the accepted corpus contains. What the wall buys is that the next such collision refuses instead of silently re-typechecking three untouched files.The resolver consumes the join by ordinary import. I claimed
src/v1could not import adag/module and built a two-branch integration question on it; that was false, andgentle-koi-563refuted it by reading the file.src/v1/04_lookup.dag— the module this wall lives in — opens withimport std.induction(line 3) andimport std.algebra(line 23), bothdag/std/*; 31 of 47src/v1.dagmodules import astd./extdeps./gunbc.module today, anddag/std/primitive_identity.dagimports nothing fromsrc/v1, so the edge cannot close a cycle. The error was reading the witness roots (["dag", "src/v2"], which govern what the floor discovers) as a rule about the import graph — where §3 says acyclicity is the only structural law and folder prefixes are browsing conventions.Unowned scope, named so it is not silently dropped:
to_string(31),map_has(3) and the deletedemit_map_has(20) aresrc/v1declarations. A projection row needs a declaration to point at, and whethersrc/v1declarations get rows at all is a scope question rather than three rows. It belongs to neither this lane nor the join lane today.The
SymbolUndisposedarm is not bookkeeping — it is where this join could have manufactured 196 refusals196 of 209 census symbols are undisposed today; 13 are disposed. If "nobody has classified this yet" reaches my seam as the same answer as "classified as not a projection", the wall refuses on the first one — and that is absence of a classification rendered as a classification of not. Two states, opposite repairs: the first is closed by disposing a symbol in the join's carrier, the second by qualifying a reference at a call site.
What makes it worse than an ordinary state-space conflation is the direction it fails in: it converts an incomplete census into a corpus-wide refusal, so the wall's strictness would scale inversely with how much of the census was done. A join whose entire purpose is to remove refusals would have produced more than it removed — and it would have looked correct from both sides, because both halves check out and only the arrow between them is invented: the carrier answers truthfully that no projection is declared, and this resolver reasons soundly that an undeclared projection means two authorities.
It is being fixed structurally rather than by agreement: the two arms are distinct arms of one total answer, so this seam cannot silently merge them and any future consumer must name both. An agreement between two lanes evaporates when either session is archived; a coproduct does not.
The eleventh consumer, and why a
.dagcensus could not see itThe specification names ten consumers of
func_sig_if_resolved— nine inv1.compiler.infer, one inv1.compiler.emit. There is an eleventh:src/v1/stage0/src/cli_run.rs, which is hand-written periphery, not emitted code. The modules compiled clean as.dagand emitted fine; the build then failed as Rust withE0432: unresolved import crate::v1_compiler_infer_lookup::func_sig_if_resolved.That is this PR's own subject turned on the PR: a census over the
.dagcorpus answers "who consumes this declaration" for the emitted world only, and the hand-written seed periphery is a second population no.dagquery reaches — the same shape as thesrc/v1residue that nodag/-rooted witness can import.All three of its uses are inside tests that deliberately pin the legacy
ImportScopedpolicy, and their own comment already records that the default policy yields "a typedAmbiguousReferenceinstead of a first-hit pick". So this is the one place the collapse is legitimate — underImportScopedno ambiguity can arise, and the other assertion is a genuine miss. They now route through a#[cfg(test)]helper that performs the collapse locally and states why, rather than through a production projection that erases the arm for all eleven.//annotations must sit at module-item grainThree of this branch's
required-regenrefusals were mine and all had one cause: ten annotation lines written inside declaration bodies, which thesrc/v1parse sweep refuses because only module-item grain is modeled (DESIGN §4c). Hoisted to module scope, each attached to the declaration it describes and naming the arm inside it. Recorded because §4c is behaving exactly as written and this is the second lane to hit it. The#8691second-pass regen red on main is a different failure, downstream of where mine were stopping — mine are closed and that one is inherited.The lane's real output
Three times on this lane I was wrong in the same way: I measured a name rather than a binding.
The drift probe counted the diagnostics a source produced rather than the ones its call produced — so an import closure's own diagnostics read as drift. Three of four first-pass results were false. Fixed by a control source.
The residue count matched a spelling rather than the authority each site was written against — so twelve correct consumers read as twelve repair targets. Fixed by reading the match arms.
The
emit_map_hasreceipt counted the declaration as one of its own callers — 21 occurrences reported as 21 call sites, when there are 20 and one definition. Fixed by excluding the definition. This one was committed to source, inside the note explaining that a name's occurrences are not its bindings.I claimed
src/v1cannot import adag/module, and built a two-branch integration question on top of it.src/v1/04_lookup.dag— the file the wall lives in — opens with two such imports. I had that import block on screen an hour earlier while looking for something else.The third is the instructive one, because I already knew the rule when I made it. "Pair the measurement with something that discriminates" is true and turned out to be insufficient as a defence: the discriminator has to be applied to the thing being counted, not held as a principle. The operational form is narrower and checkable:
The fourth needed a different control, and
gentle-koi-563supplied the general one after making the same class of error independently (misreadingps -o etimeasHH:MMwhen it isMM:SS, and publishing the wrong figure twice before a 75-second sleep settled it):All four of mine and all three of theirs were claims of that shape, and every one had a cheap counterexample sitting in the tree.
That generalization is worth more than the wall, and it is the thing that otherwise gets rediscovered in six months by someone who greps.
The architectural constraint nobody has stated
Consuming the
PrimitiveIdentityjoin from the wall means the seed importsstd.primitive_identity, and that is where this branch met a boundary that is enforced nowhere:Nothing declares this constraint. It is enforced only accidentally, by regen refusing an unresolved import — a symptom-level refusal that names a missing module rather than the rule it is protecting. A contributor adding one import to a
dag/module the seed consumes has no way to learn the rule exists until a regen red names a file they did not touch. That is worth more than this PR's mechanism, and it is recorded here rather than fixed, because widening the seed's module index is an architectural decision several orders above this brief.What the 29 → 0 collapse proves
Wiring the join dissolved 29 seed collisions to 0 (
to_string×26,map_has×3). None of them was aModeledProjectionI had qualified: every one answersDeclarationPrimitiveUndisposed, so the builtin is not admitted as a rival and the refusal never fires.That is a receipt for the join's third arm rather than for this wall.
DeclarationPrimitiveUndisposedwas authored as the honest third state — this symbol is on a primitive surface, but nothing has disposed of the relationship — and had no consumer at the moment it landed. The first real population it met was these 29, and it classified all of them correctly. A three-arm answer whose middle arm has never been exercised is indistinguishable from a two-arm answer with a dead branch; this is the measurement that separates them.Four seed import repairs authored before the join was wired are reverted in this branch (
b6b547c522). Their justification evaporated when the collisions did, and four edits whose stated reason no longer exists are exactly the residue that gets rediscovered six months later.Which direction the dependency runs:
PrimitiveIdentitysits below callable resolutionStated explicitly because the opposite wiring is available and would be a layer inversion: callable resolution consumes the projection answer; it never asks whether a declaration happens to equal a primitive.
declared_candidate_rivals_the_builtincallsprimitive_projection_for_declaration(decl_ref(owner, decl))and matches its three arms. That is the whole of the interaction. It performs no taxonomy work of its own — it never inspects aPrimitiveIdentity(theidentityfield is discarded at the binding), never compares authored symbols, never consults a surface roster, and never repairs or supplements a projection row. It asks one question and maps the total answer onto one local question: is the builtin a second authority for this name, or the same one?That is the boundary that keeps the two concerns separable. If the wall instead reconstructed the primitive relationship itself, callable resolution would become responsible for repairing primitive taxonomy — every gap in the roster would surface as a resolution defect owned by the wrong lane, and the wall's correctness would be coupled to the completeness of a population it has no standing to fix.
One consumer-side policy choice is worth naming so it isn't misread as taxonomy work:
DeclarationPrimitiveUndisposedmaps to not a rival, i.e. suppress. The wall does not decide anything about the primitive; it decides what this consumer does under ignorance, and it decides it fail-closed in the direction that matters here — refusing on an unclassified symbol would refuse the 196 undisposed symbols corpus-wide, which is the false-refusal storm rather than a wall. The projection answer keeps absence and ignorance as separate arms precisely so the consumer can make that choice explicitly instead of inheriting it from a collapsed boolean.Import-safe and emit-safe are two properties, and only the first was verified
This is the finding worth more than anything else in the PR, and it was found by being the first consumer of someone else's module.
std.primitive_projection(#9060) was landed as the seed-safe leaf this wall needs. Its acceptance criteria verified that its import closure stops atstd.decl_ref/std.typesand reaches nov2.*module. That check was correct and it passed.Nobody checked that the seed can emit it — and the seed must, because a seed-consumed module becomes a committed Rust mirror. Attempting the consumption produced nine
compile_error!("unsupported mock expression")rows insrc/v1/stage0/src/std_primitive_projection.rs, one perdatarow in the leaf. The module could not be consumed by its intended consumer.Generalised, because it outlives this module: for any module intended for seed consumption, closure-stops-here is necessary and not sufficient. The sufficient check is an emit. The two properties are independent, and only one of them has a mechanism today.
The defect underneath it
05_emit_rust.dagdata_value_has_cross_refsanswers does this initializer reference another declaration, andemit_data_defuses it to choose between the JSON serializer (literals only) andemit_typed_expr(anything):A call is that same reference with an argument list attached. It fell through
_, sodata d: R = mk(..)went to a serializer that cannot represent a call, which then refused — the failure surfaced one layer below the judgment that caused it.Total at the level examined, blind one level down. Exhaustive over
expr_data, no arm missing, nothing for a reviewer or a wildcard-free lens to catch, because the_carried the payload. Wrong by the function's own definition rather than incomplete.The census that authorised the one-arm repair, with three instruments
The line regex was wrong by 2.5×: blind to multi-line initializers, and blind to the class that matters most — the predicate descends into record and list literals, so
Foo { a: bar() }flips too.The third instrument decides, because only the seed closure is lowered to Rust and the JSON branch is guarded by record-shaped type AND no-cross-refs. Every row on that path today emitted successfully, which by construction means it holds no call. So nothing that works today can move.
DissolutionCondition(6 seed rows) is a coproduct andString(2) is not a record — which is whyPrimitiveIdentity { slug: NonEmptyStr }supplies the first rows in the seed closure's history to satisfy both conditions at once, and why the defect stood this long unasked.Verified after: JSON-path rows 34 before, 34 after — zero existing rows changed route. The emitter mirror moved by exactly one line.
The discriminating red, demonstrated rather than claimed
The nine
compile_error!rows are evidence that self-destructs — they exist only while the defect does. The regen fixed point survives the fix:first_generation_equal=trueFAIL generated surface drift: v1_compiler_emit_rust.rsRun both ways rather than argued. Its RED is authorable where the check runs (one arm in a
.dagfile regen reads every run), so this is not the permanently-green decoration case.The storm, measured at full scale
The pre-join wall produced 608 refusals across 79 files on four names —
length×454,map_insert×154,to_string×26,map_has×6 — and zero were genuine two-authority collisions. That is the case for the join reaching the seed, stated in the only currency that matters.With the join wired and the transient bootstrap imports removed: zero ambiguity refusals,
first_generation_equal=true, and all six witness assertions returningtrueagainst a binary built after install.Two corrections to my own record
The revert commit message was incomplete. I removed four seed import edits saying their justification had evaporated. The stated one had — the join makes them unnecessary. They carried a second, unstated role I had not identified: they satisfy the old mirror's level 1a, which is what lets regen run at all during the bootstrap. Removing them broke the build for a reason the commit message gave no way to recover.
I quoted a corpus-wide count as a blast radius before reading the guard. 1259, then 3181, when the number that answers the question is 9. The number you can get quickly is not the number that answers the question, and it is always the scarier one. Read the thing that decides — the guard, the scope, the visible set — before counting the thing that is easy to count.
Status: ready for review. The closure question that parked this PR is resolved — #9060 landed
std.primitive_projection, the seed-safe leaf, and this branch is its first consumer. Consuming it surfaced an emitter defect that blocked the consumption entirely; that repair is authorised, scoped to one arm, measured against the actual guard (34 JSON-path rows before and after — zero existing rows moved), and carries a discriminating red demonstrated in both directions.Verified on the runner, not just locally: the required CI run — parse, regen, v2-emission, witness floor — is green, with
required-regen: first_generation_equal=true. Zero ambiguity refusals corpus-wide, against 608 before the join reached the seed.The inherited
cargo check --all-targetsbreak this branch also repaired was split out as #9142 and then closed as superseded — the identical fix landed independently as #9125. Three sessions found that break in one day, which is the gap its body names: no test-target compilation in CI since 2026-07-11, so the cost is paid in repeated rediscovery rather than once at the break.