Skip to content

import deletion - #8277

Closed
gunbai-bot[bot] wants to merge 53 commits into
mainfrom
session/calm-ram-435-import-deletion-probe
Closed

gunbai-bot[bot] wants to merge 53 commits into
mainfrom
session/calm-ram-435-import-deletion-probe

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Auto-opened by session-dashboard for session calm-ram-435.
Pushing to session/calm-ram-435-import-deletion-probe advances this PR.

Worker attestation

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

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

Summary

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

Test plan

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

gunbc-ci-auto-heal and others added 30 commits August 10, 2026 19:18
#8084 and #8080 were closed as quarry, so the base session/valiant-boar-65 sits
at an archived head that will not move again. This is the lane's source
contribution replayed onto main: the eleven authored files, with every generated
artifact re-derived from the merged authority on this base rather than carried
across (plan, stage0, .gitattributes, ci.yml). None of CI2's diff comes with it.

Replay proven by execution, not assumed. regen_divergence_count=0 with build exit
checked first, and every witness re-run on this base: the declaration matrix
18/18, the import-deletion bridge 3/3, and the four sibling scoped-roster files
13/4/4/7/3 -- zero failures.

Contents, in the order they landed:

  Bridge real authored source into occurrence binding, with exposure grounding a
  parameter rather than a hardwired assumption.

  Commit B: pin the parser declaration-category denominator by execution. Every
  authored declaration form gets a row, including the forms that mint nothing --
  the zero rows are the load-bearing half.

  Commit C: mint type declarations, constructors and fields, role-directed in
  `stamp_parsed_node_children`. It cannot be shape-inferred per node: a product
  FIELD node carries `inferred` Present with a Present ident_span, and a payload
  VARIANT carries the same `connective: Conj` a product declaration does, so a
  shape-first rule turns fields into type declarations. The member kind is a fact
  about the parent.

  A pattern's field position was minted as a FieldOccurrence DECLARATION. It
  names which field is being destructured, so it is a reference; the binder
  beside it is the declaration. Two defects at one authored name, discriminated
  by three rows keyed on exact occurrence identity so neither could green by
  masking the other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The heal job failed because it regenerated a workflow path it is not credentialed
to push: `.github/workflows/ci.yml` was one derivation pass behind this branch's
emit plan. The single changed line is the heal job's own authored-conflict exclude
list, which is derived from the plan, so it was missing the three stage0 modules
this lane adds (the bridge witness, the declaration matrix, and its support
module).

Cause was mine and mechanical: `main_wet` derives ci.yml from the plan it reads at
START, so a change that adds emitted files needs a second pass. I ran one on this
base. Two passes now, and a third pass produces no further change, so the fixed
point is shown rather than assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolutions, by class rather than by file:

AUTHORED. `dag/gunbc/explicit_witness_admission.dag` conflicted on one row I had
deleted and main still carries — the `english_emit_add_prose_holds` promotion I
did as a favour for another lane. Took MAIN's side: that promotion belongs in its
own change, not carried inside an import-deletion PR where a wrong call would red
this branch for an unrelated reason.

GENERATED. `.gitattributes`, `ci.yml`, the emit plan, `lib.rs` and one emitted
witness were all regenerated from the merged authority rather than hand-resolved,
per the driver's own instruction. Taking main's whole self-consistent stage0 as
the bootstrap first mattered: a per-file `--ours` mixture built 44 errors, because
half the seed was mine and half was main's.

THE KEYSTONE GREENED, and its expect-red admission is deleted. Established in
BOTH directions rather than observed once: against main's parser
witness_import_less_reference_reaches_the_real_provider FAILS, against commit C's
it PASSES, and the provider-withheld control stays green in both — so the pass is
the new type-declaration population, not a closure that happens to carry the name.
Its file is now enrolled file-grain beside its two controls, uniformly green.
Scope unchanged: this is the SUPPLIED-PROVIDER claim, so provider discovery and
provider-file edge projection remain open.

That deletion also emptied `known_red_probe_v1_envelope_roster`, which fixes the
floor's arm-time refusal on the previous push. My keystone admission had enrolled
`gunbc_v1_claim_scoped_known_red_probe_batch` — a conditionally-enrolled batch —
without the companion positional row the clamp table requires, exactly the
ENROLLMENT COUPLING its own note warns about. Un-enrolling restores 6 positional
batches against 6 clamp rows, so no clamp row was added, no budget was raised, and
no operator signature is owed.

INHERITED, NOT MINE: main cannot derive its own generated artifacts. This carries
the `HasTrigger` heal (also opened standalone as #8117) because without it no plan
can be derived here at all; the second break, `SubstrateLongLaneRow`, is #8116's
and is deliberately not duplicated. Until both land, this branch cannot complete
its regen chain, and that is an inherited blocker rather than this lane's red.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#8116 carries both halves of main's #8059 skew break and is proven green --
main_wet EXIT=0 with zero drift -- so this branch's copy of the same 81 rows is
redundant. Pushing an identical repair from two directions into a merge is the
duplication itself, not a safety margin, and the manager owning #8116 asked
explicitly that it not happen.

CONSEQUENCE, stated rather than hidden: this branch cannot derive an emit plan
again until #8116 lands, so its three new emitted stage0 modules stay unregistered
and the regen chain stays red. That red is inherited from main, not produced here,
and it clears by merging main once #8116 is in. #8115 stays draft until then.

Also correcting arithmetic I put in the closed PR and in the merge commit before
it: 77 is the POPULATION of SubstrateLongLaneRow literals, not the count of broken
fields. The broken field count is 18, out of 21 dissolve_on spellings, three of
which are prose inside strings. The number I quoted answered a neighbouring
question and read as though it answered this one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#8116 landed both halves of the #8059 dissolution-carrier skew and #8110 was
reverted, so main derives its own artifacts again and this branch's inherited red
clears. Merged with no conflicts -- the previous commit had already reverted the
duplicated heal, which is what left nothing to collide.

The three new emitted stage0 modules are now registered by the derived plan rather
than by hand: the bridge witness, the declaration matrix, and its support module.
`main_wet` reaches a fixed point proven by BYTES rather than by a third quiet pass
-- md5 of .gitattributes, ci.yml and the emit plan is unchanged across a further
run -- and the two-round seed regen verifies at regen_divergence_count=0 with
build exit checked before the verify.

Evidence re-run on the merged tree, not carried over: all seven scoped-roster
witness files green -- the declaration matrix 18/18, the import-deletion bridge
3/3, and the four siblings 13/4/4/7/3. Zero failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The CI auto-heal merged main into this branch and pushed a regeneration
(c43a89d) while I was doing the same work locally. Its result is not
self-consistent and must not be taken: measured on that exact head,
`regen_stage0 --verify` reports regen_divergence_count=5, while this tree reports
0.

WHAT IT DROPPED, and how it is visible: the heal's emitted
`v1_compiler_parse.rs` contains ZERO occurrences of `parsed_module_item_declares_type_name`
while its own `src/v1/02_parse.dag` still contains commit C in full. So the source
claims role-directed minting and the seed does not implement it -- a source/artifact
divergence, committed. The cause is the heal regenerating with release-bins built
from a merge ref that predates commit C; an emitter that does not know about a
change cannot emit it, and its output then looks like a clean regeneration.

This merge is `-s ours` deliberately rather than a file-by-file resolution. The
remote's only unique content is those five stale generated files; its merge of main
is the same main commit (4bd738b) this branch already merged, so nothing
authored is discarded. Verified after the fact rather than assumed: divergence
stays 0 and all seven scoped-roster witness files stay green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Module identity does not delete an import. An import line names a FILE, so the
thing replacing it has to produce a file-grain edge -- the loader needs to know
which file to pull into the closure, and a claim that only says which MODULE a
reference belongs to leaves that unanswered. The keystone proved the reference
reaches the right module; these rows prove the same binding projects the provider
file, through the production projection rather than a copy of it.

Three new rows, and the pair is discriminating in both directions:

  the consumer names LiveTreeDisposition TWICE (parameter type, return type), so a
  correct projection yields exactly 2 binding-provenance rows -- derived from the
  fixture's own text, not read off the parser;

  both rows name the same provider file, so the file-grain projection must dedupe
  them to exactly 1 edge, asserted with exact consumer and provider paths and a
  no-self-edge clause. Two rows becoming two edges is a dedup failure that
  over-loads the closure; two becoming zero drops the dependency the loader needs.
  One is the only answer that is right for a reason.

  a control that a withheld provider projects NO provenance rows and NO edges,
  distinct from the keystone's own control -- without it both counts above could be
  satisfied by a projection emitting edges from an empty binding population, which
  is the direction that silently loads files nothing references.

PROVEN DISCRIMINATING, not assumed: perturbing the fixture to a single mention reds
both count rows while the dedupe row still reports 1 -- so the counts are
load-bearing and the dedupe row measures a different fact rather than echoing them.

NO NEW MACHINERY, which item 5 requires: the provenance population and the dedup
come from std.occurrence_binding_candidates, and the module-to-file index is built
from the closure's own module_files rows. Nothing walks a filesystem, scans names,
or builds a second module-to-file map.

`ModulePathFileRow` is declared as an import for the same reason as last time -- it
appears in a signature, so the emitted seed needs it, and that dependency is
invisible in an import list until the build says so. This lane's own subject.

regen_divergence_count=0, build exit checked first. 7/7 on this witness, 18/18 on
the declaration matrix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g it

THE DEFECT, which was mine and was the exact class this lane exists to delete.
The previous revision reached its 2-provenance/1-edge result through a FAIL-OPEN
ADAPTER: it matched only ReferenceBindingProjectionBound, discarded every other
outcome, then hand-constructed AllReferencesBound from the survivors. So a
population where one reference bound and another did not would have reported a
complete closure and emitted a provider-file edge -- the false-skip direction,
which silently under-loads. Every refusal arm (assembly, index, walk, binding,
file projection, unbound, ambiguous) also collapsed to an empty list, so the
withheld-provider control proved only that refusal and legitimate-zero are
represented identically.

THE REPAIR. One assembly, then the production total path and nothing local:
structural_binding_walk_selected_references returns a BoundReferencePopulation
that is AllReferencesBound or ReferencePopulationRefused carrying its first
failure, and reference_derived_dependency_projection turns that into Ready or a
typed binding/file refusal. This file no longer constructs AllReferencesBound,
folds projections, or uses [] to mean a stage refused. Outcomes are one coproduct
reusing the production refusal types; only two arms are local, and both are about
the fixture's own population (unknown candidate path, reference-count mismatch).

Not the top-level helper, deliberately: reference_derived_dependency_projection_for_assembled_closure
selects through the clause-e filter, which excludes TypeOccurrence, so it would
have selected zero references and reported Ready with zero rows -- every count
assertion passing vacuously. Moving TypeOccurrence into clause e to reuse that
walk is item 4's wrong turn and is not taken here.

PROVEN DISCRIMINATING BY REINSTATING THE DEFECT, not argued: with the fail-open
put back, exactly two rows red -- the withheld-provider and partial-population
controls -- and every other row, INCLUDING the 2-to-1 pair, passed. The positive
law never detected the fail-open and never could, because a fabricated success is
still a success. Those two controls stay enrolled as the executing evidence that
totality holds.

The planted partial-population control needed a second repair to have teeth: its
first version selected only the subject's references, so the unbindable one never
entered the population and the row passed against a Ready projection. Selection
now takes a name list and the control requires all three references, one of which
cannot bind.

Also in this commit, from the same review:

  idb_source_for's "every non-consumer path is the provider" default is gone,
  replaced by explicit CandidateSource rows. Under the default, an unrelated
  candidate silently received the real provider's text, so unrelated and actual
  provider were not representable as different contents and every later
  provider-discovery control would have been misleading. An unknown path now
  refuses.

  the keystone is renamed from real_provider to supplied_provider and the
  read-off-disk/live-corpus claims are gone. Both sources are embedded and the
  provider is handed in, which the note admitted while the name denied.

  new controls: pool-independence as an EQUALITY on binding identities and the
  edge set (not merely "still green"), same-file provenance retained with no
  self-edge, malformed source as a parse refusal, and selection refusal.

Item 3 is NOT complete: the ambiguity control needs candidate declaration
identities that ReferenceBindingProjectionAmbiguous does not currently carry, and
the review requires repairing that carrier rather than working around it here.
Missing/conflicting module-file controls are also still owed. 12/12 on this
witness, 18/18 on the declaration matrix, divergence 0 with build exit checked
first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… ambiguity and storage controls

ReferenceBindingProjectionAmbiguous named an unbindable reference and threw
away what it was ambiguous BETWEEN. resolve_reference_occurrence_binding
already returned OccurrenceAmbiguous { occurrence, candidates } and the
projection matched `candidates: _`, so a consumer could learn that two
declarations competed and never which two -- and two ambiguity outcomes of
equal cardinality over different declarations are different states with
different remedies. The carrier now carries the upstream
AmbiguousBindingCandidates<OccurrenceId> itself; no new type was minted,
because head+second+tail already makes a one-candidate ambiguity
unconstructible.

The same class was then found one layer out, by tightening a control rather
than by reading code: the bridge witness collapsed
AssembledCrossFileBindingClosureRefused to a bare marker, so a storage
contradiction could not be asserted at its identities. It carries the
refusal through now.

Controls added, each asserting identities rather than counts:

- two admissible providers of one name refuse as ambiguous, naming both
  competing declaration OccurrenceIds and joining each against the declaration
  population of the assembled transport
- two files claiming ONE module path refuse at closure assembly, naming the
  module path and both competing files. Which stage refuses is measured: an
  earlier version tolerated assembly OR index refusal, which is a two-answer
  assertion that would have passed had the contradiction moved between stages
- a bound population projected against an index with the provider module's row
  REMOVED refuses the file projection, so a dropped edge cannot read as success

Stale prose stripped: the offline recipe still named a `real_provider`
function that no longer exists, listed two other functions that no longer
exist, and described an expected red this file no longer carries. The
provider is SUPPLIED by the fixture, and the recipe now says so and stops
enumerating a roster the file-grain enrollment already expands.

15/15 green by execution. Terminal acceptance is unchanged and unreached:
imports, their grammar and their AST are still present, and no production
node is accepted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…mitter defect

Four generated artifacts conflicted (.gitattributes, ci.yml, the emit plan,
lib.rs). Resolved by regeneration, not by picking a side: main's bytes
predate this branch's three new src/v1 witness modules, so taking either
side would have produced a plan that omits or duplicates them.

Regeneration surfaced a real defect rather than only bytes. `regen_stage0
--verify` reported divergence 0 while `cargo build` failed with E0308: a
nested coproduct pattern through a reference-counted field emits
`projection.as_ref()` matched against a bare enum, so the pattern the v1
interpreter accepts does not survive emission. Byte-fidelity is not
target-language validity, and the verify step cannot see the difference.

The control is authored as two sequential matches -- semantically identical,
and the idiom the surrounding corpus already uses. That is not a repair, and
`idb_nested_rc_pattern_emitter_defect` says so at the site, names the emitted
file, and carries its dissolution: the Rc wrap-decision predicate. Changing a
load-bearing emitter is not this control's business.

15/15 green by execution after the split; seed rebuilds clean; verify at 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he measured namespace frontier on the roadmap

DECLARATION DENOMINATOR, three forms measured rather than assumed. A probe
rendered each population once and was then deleted rather than left behind as
a second instrument:

- a type ALIAS mints TypeOccurrence. It is the third named-type shape and the
  one a connective-keyed rule misses, so the whole-population row now asserts
  THREE named types, not two -- with two the alias hole was undetectable from
  this file while both existing rows passed.
- the pattern field position mints a FieldOccurrence REFERENCE at 394-411 whose
  containment locates it inside the matching function. This is the positive half
  of a pair whose other half only says no DECLARATION sits there -- satisfied
  equally by a correct reference and by nothing being minted at all, which
  would drop the edge silently. Asserting it required a references-side
  projection; the support authority now renders references at full identity too.
- a module-scope value declaration mints NOTHING. `data name: T = v` binds no
  name, so a bare cross-file reference to a module datum cannot bind and the
  derived closure is short by every module-value edge in the corpus.

The module-value row is enrolled EXPECTING RED, not written as a zero row in
the green file. A zero row asserting the empty population would be green today
and would make the defect the contract -- the shape DESIGN section 5 names when
it rules that a test asserting a degradation's own string enshrines it. The row
asserts population size and not category, because which OccurrenceCategory a
module member value binds is a genuinely open modelling question and a red row
is not where it gets decided. Cause located at parsed_occurrence_role_for_node,
which classifies a bodied node by the ABSENCE of a type annotation -- a defect
distinct from the three role-directed minting closed. Its admission carries the
companion positional clamp row the enrollment coupling requires.

ROADMAP, in this commit because the capability boundary moved. NO FRONTIER
ADVANCES and that is the correct reading: the provider is SUPPLIED by the
fixture, nothing discovers one from a source root, no ordinary compiled path
asks for any of it, and the closing contract's capability rows read Unavailable
under the same triggers as before. Editing an admission row to reflect fixture
evidence would be the rung inflation namespace_node_recut_note already priced.
What lands is the measured state, dated, naming both what fifteen executing
controls established and what they did not, plus the module-value hole so it is
not discovered late -- and the lane joins the focused page because it is being
worked. Imports, their grammar and their AST are all still present; the
hand-written edge producer is untouched; nothing is cut over.

20/20 green in the enrolled matrix, the red row red as declared, seed rebuilds
clean, verify at 0, and the clamp, focus, frontier, document, execution-contract
and roadmap gates all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ching clause (e)

Clause (e) excludes TypeOccurrence for a stated reason -- a parser-produced
annotation such as `Int` would enter the population and refuse it whole,
because std.types was never given to the closure. Both easy repairs are
forbidden. Reclassifying TypeOccurrence as clause-(e)-inducing makes that
clause say something its contract does not, and every consumer inherits the
change silently. Dropping an unbindable type reference to keep the projection
green renders "could not see the provider" as "nothing is depended on" -- the
empty-observation narrow, strictly worse than the widen DESIGN section 5
forbids, because a widen is expensive and a narrow is silently uncovered.

So dependency-inducement becomes its own authority with one arm per ROUTE,
COMPOSED over clause (e) rather than replacing it: it reads the clause-(e)
verdict first and inspects the category only inside the NotInducing arm, so
clause (e) remains the authority for its own question. Both routes then reach
the SAME BoundReferencePopulation through one fold over ONE candidate index --
the N2 typed entry already returns a ReferenceBindingProjection, so no second
binding authority was minted. Its index-supplied twin exists because the
inputs-taking entry rebuilds a candidate index per call, which is correct for
a single-reference API and quadratic inside a walk.

Six controls, and the load-bearing pair is measured at the EDGE:

- clause (e) STILL excludes type references. Its own row, because the point of
  adding an authority instead of editing the filter is that this stays true.
- the admission table is total over the closed coproduct: fields and methods
  stay excluded, carrying the category they excluded. Asserting only the type
  arm would let a change that admitted field references pass, and a field
  reference admitted as dependency-inducing makes every record access look
  like a file dependency.
- clause (e) ALONE derives ZERO edges for a type-only consumer, and the joined
  walk derives exactly ONE, to the provider file. An earlier draft asserted
  clause (e) selects nothing there; measurement refuted it -- it selects one
  same-file parameter mention, which is why the discriminator is the edge and
  not the population. The zero row alone is satisfied by a projector that
  produces nothing; the one-edge row alone by one that always emits an edge to
  any supplied provider. Together neither passes.
- three admitted references reach one population at an EXACT count; a join that
  dropped a route would satisfy any lower bound.
- an unsupplied type reference refuses and NAMES the occurrence. That name is
  the loader's work list, which is the whole reason the production switch waits.

The production projection deliberately does NOT consume this yet, and the
authority note says so: admitting type references makes the demand real, and
the demand is item 6's fixed point. A surface with no consumer is coverage by
illusion, so its dissolution is named -- when the switch lands,
reference_derived_dependency_binding_references has no caller and deletes.

21/21 in the bridge witness; the candidates, pool-independence and N2 type
binding witnesses stay green; seed rebuilds clean; verify at 0.

CI red on this branch remains INHERITED from main and is unchanged by this
commit: main @341d320 fails the same check with the same two classes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
module_path_file_row_note has said, since it was written, that
v2.compiler.source_authority ModuleStorageBinding / ModuleStorageIndex is the
single home for the path-to-module binding and that ModulePathFileRow is a
scaffold projection OF it. Nothing performed that projection. Every
ModulePathFileRow in the tree was a hand-authored literal inside a witness, so
the two representations coexisted with no derivation between them -- the exact
parallel-representation state the note claimed to be avoiding, and the reason
provider discovery had nothing to consume. This is the missing function, and it
lives with the binding because the binding is that module's fact.

TWO ARMS, because a module does not always have a file. ParsedFromSource carries
an Artifact whose file_path IS the storage realization. ProducedByBehavior means
a module the compiler produced: there is no file, and every path the projection
could invent -- the module name reshaped, the empty string, the producer's own
file -- is indistinguishable downstream from a real one, which is fabricated
plausible output. So it refuses, naming the module and its producer.

The index projection refuses WHOLE, and that is the load-bearing decision. The
tempting arm projects the file-backed bindings and drops the rest: every
positive row still passes, and the caller receives a row set it believes is the
whole storage picture with a module silently missing -- the empty-observation
narrow at set grain. A caller that legitimately wants only the file-backed
subset filters the index first, which is a statement of intent rather than a
silent loss.

Six controls. The positive row pins BOTH derived facts at exact values -- dotted
module path and the artifact's own path -- because asserting that some row
appeared would pass for a projection that answered about the wrong module or
invented a path from the name. The whole-refusal row plants an unprojectable
binding BETWEEN two projectable ones, so a dropping implementation would return
two rows and read as complete. Multiple failures are all reported, so the
deficit is countable rather than first-only. Order is asserted at both ends,
which is what a reversed fold swaps. And an empty index is Ready with no rows,
stated as its own row so "no input" stays distinguishable from "input whose
answer was dropped".

This is the first half of item 5: provider discovery needs a derived row
population, and until now there was none. The second half consumes the
whole-tree parsed manifest discover_source_root_ingest
--emit-module-binding-manifest already produces, supplied as modules in an
ephemeral overlay source root the way module_binding_supply_transport does --
so no new filesystem walker, name scanner or module-to-file map is required,
and the rows arrive as modules rather than through a Filesystem service, which
dissolves the placement contradiction stage0_emission_model_gap_note records.

6/6 green by execution; verify at 0; no generated artifact moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ur the prose-row wall

THE PROSE-ROW GATE WAS RIGHT AND IS NOW GREEN. It refused exactly one row --
module_path_file_row_projection_note in src/v2/compiler/source_authority.dag,
a path already migrated to authored // annotations -- and counted 26 further
rows as out-of-scope residue it reports without enforcing. The refused rationale
moves into a leading // block above the declaration it explains, which is what
DESIGN section 4c admits. Nothing was weakened to pass: the argument is
unchanged, only its carrier.

PROVIDER ROWS NOW COME FROM THE REAL TREE. The new witness projects the
whole-source-root PARSED module-to-path manifest through the authority, and the
point is what it does NOT add: no filesystem walker, no name scanner, no
module-to-file map, no second dependency producer. discover_source_root_ingest
--emit-module-binding-manifest already emits those rows via v1_compiler_parse,
and module_binding_supply_transport already overlays them as a MODULE in an
ephemeral source root, so they arrive as data through carriers that existed.
Arriving as a module is also what makes this reachable: a filesystem_read would
drag the Filesystem service into the emission closure, which under src/v1
produces an unregisterable dry_run.rs -- the placement contradiction the bridge
witness records.

Five controls, enrolled in that transport's claim list: the real rows project;
EVERY manifest binding projects, asserted as a count identity against the
manifest's own count, so a Ready answer is the claim that the whole observed
corpus is file-backed; a known module resolves to its known real path; an absent
module does not appear; and a REAL module bound to the WRONG file does not
appear either -- that last one distinct from the absent-module row because it is
the failure a projection reshaping names into paths would produce, and it would
pass the absent row. All are vacuity-guarded: run without the overlay, the
manifest is the committed Empty stub and an empty index projects to an
empty row set that is legitimately Ready, so every claim requires a populated
manifest and a non-empty projection first.

A PRODUCER DEFECT FOUND BY MEASUREMENT, named rather than routed around. A
second known pair in the dag/ root was authored and REMOVED: the manifest cannot
carry that root at all. The producer renders the artifact id and textual locus
file as a BARE symbol from the file stem, and dag/ holds dag/std/import.dag and
dag/extdeps/languages/go/module.dag, so a two-root emit writes `id: ^import` and
`file: ^module` -- keywords -- and the whole manifest fails to parse. Measured:
3517 rows written, then `for_each_parsed_module_binding: parse error ... expected
identifier or ( after ^, found keyword`. This matters to the lane rather than
being trivia: providers live in dag/ as well as src/v2, so reference-derived
closure over the real corpus cannot complete while the producer cannot describe
half of it. The limit is stated on the consuming witness and on the transport
with its dissolution -- the file path is already carried as a String in the same
row, so the stem-derived symbol is a second, weaker naming of a fact the row
already has.

Also corrected here: I had described this manifest as whole-TREE. It is
whole-source-ROOT, and the transport passes src/v2 only.

5/5 green through the transport against a real overlay; the sibling consistency
and crate-layout claims in the same gate are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d defect instead of restating it

TWO FIXES, one of them a correction to the previous commit.

THE WITNESS WAS DISCOVERED AND RUN WITHOUT ITS OVERLAY. CI ran six of my rows
through the corpus discovery walk -- which picks up any *_test.dag under a
source root, not only the three scan dirs -- and outside the transport
host_module_bindings is the committed Empty stub, so the vacuity guard refused
and every row returned false. The guard was working; the placement was wrong. It
now carries a WitnessExclusionRow in exactly the crate_layout posture, which is
the same manifest, the same Empty read and the same refusal, and it says
explicitly that the refusal is the guard working rather than a defect. The
witness keeps its executing consumer: the transport's ClaimRun roster.

This is the local-green-is-not-CI-path-green class, and I walked into it: the
five rows passed through the transport and passed against a hand-made overlay,
and neither of those is the admission path.

THE dag/-ROOT DEFECT WAS ALREADY DOCUMENTED, and the previous commit presented
it as a find. gunbc.commit_workflow heal_writer_claims_no_fixture_exemptions_note
records that discover_source_root_ingest mints edge identifiers from source-file
BASENAMES with no keyword escaping, and names all three colliding files --
dag/extdeps/bmc/capability.dag, dag/extdeps/languages/go/module.dag and
dag/std/import.dag, minting ^capability, ^module and ^import. I reproduced it
independently and found two of the three. The witness now CITES that authority
rather than standing a second description of the same fact beside it, which is
the nicknaming DESIGN section 3 forbids applied to prose, and it takes that
note's dissolution trigger instead of minting another. What is genuinely new is
kept and stated: the consequence for this lane, that providers live in dag/ too,
so reference-derived closure over the real corpus cannot complete while the
producer cannot describe half of it.

The exact-witness-admission gate is green with the new row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…brace that broke every gate

DISCOVERY/ENROLLMENT REPAIR, and the roster is now one authority. The five
overlay-dependent claims were inline in the transport's call, so the function
names existed only inside an expression no coverage accounting can read. They are
now `module_binding_supply_claims`, one declared List<ClaimRun> the transport
consumes, so execution and any consumer census read the SAME rows rather than two
lists that can drift. The names are not duplicated anywhere.

Discovery no longer executes them. The corpus walk picks up any *_test.dag under
a source root -- not only the three scan dirs -- and outside the overlay
host_module_bindings is the committed Empty stub, so the vacuity guard refuses
and every row returns false. Selection is removed with a WitnessExclusionRow in
exactly the crate_layout posture (same manifest, same Empty read, same refusal);
scan dirs are NOT broadened and no vacuity guard is weakened.

TWO CONTROLS I DELETED, RESTORED. Rewriting the dag/-root comment in the previous
commit replaced a span that swallowed msrs_an_absent_module_is_not_projected and
msrs_a_real_module_is_not_bound_to_the_wrong_file. It surfaced as `no such
function` when the roster called them, which is the one way it could surface --
the file still parsed and the remaining three still passed, so nothing else would
have said so. Both are back with their arguments intact.

A BRACE THAT BROKE EVERY GATE. #8148 landed a reason string containing
`Classified{LocalRead}`, and `{` opens interpolation in .dag, so the module
raised `undefined variable 'LocalRead'` and nothing that resolves
explicit_witness_admission could run -- which is every gate I needed. Escaped to
`\{LocalRead\}`. One character, no semantic change, and it is a base-side defect
fixed here only because it blocks this branch entirely.

Executed: transport gate true (11 claims, mine among them); bridge witness 21/21;
main_wet to a no-op fixed point; regen_stage0 re-emitted and the seed rebuilt
clean; regen --verify divergence 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e, third instance of one class

dag_compile_clean_gate_passes was the only red left after the discovery repair,
and its hard diagnostics were `unresolved type 'ArtifactIdentity'` in three cache
witnesses this branch never touched. Cause: two declarations of that name now
exist -- dag/std/cache_interface.dag's generic ArtifactIdentity<T>, and a new
non-generic ArtifactIdentity in src/v2/compiler/self_host/generation.dag that
arrived with main. Adding a name to the flat namespace is a corpus-wide edit, so
the generic uses stopped resolving in files unrelated to either.

The newer name yields, per single authority, and it is renamed rather than
suffixed because the old name was wrong for it: the type is
`ArtifactMaterialized | ArtifactNotMaterialized`, which is a MATERIALIZATION
STATE and not an identity at all -- the field holding it is already called
materialized_artifact. It becomes ArtifactMaterialization, with
artifact_identity_agrees following as artifact_materialization_agrees. Eight
sites across three files; no behaviour change.

THIS IS THE THIRD INSTANCE OF ONE CLASS IN TWO DAYS, and that is the finding
rather than the rename. gunbc#8151 fixed the same shape twice (Memory against
std.measure's Quantity variant, unsatisfied_capabilities against the auth fold),
and both times the break surfaced in a third file that neither PR touched. A
flat-namespace homonym is DECIDABLE, so it is a construction wall waiting on the
containment authority -- which is this lane's own terminal work -- not a matter
of diligence. Until that lands it recurs at roughly the rate two lanes mint the
same bare noun, and each recurrence costs an unrelated PR its CI.

Executed: the three cache witnesses and both self-host witnesses green; main_wet
to a no-op fixed point; regen_stage0 re-emitted; seed rebuilt clean; regen
--verify divergence 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ecutingConsumer

The admission refusal was mine and the classification was wrong: 5
UnclassifiedPathDeferral, "enrolled witness row(s) excluded from discovery name
zero executing consumer" (run 31567336073).

WHY OfflineLocalRecipe WAS THE WRONG ARM, having copied it from the crate_layout
siblings. witness_path_policy_execution_standing_in maps an offline-classified row
to LegacyFrozenPathDeferral only when gunbc.witness_deferral_freeze already names
it, and otherwise to UnclassifiedPathDeferral, which is refused -- the forward hole
the 2026-08-04 ruling opened deliberately. The siblings predate that ruling and are
held by the frozen baseline, which may only SHRINK, so a NEW witness cannot join
them and must not try. Copying a precedent's classification without checking which
arm it reaches is what produced this.

FixtureExplicitRoster is the honest arm and reaches WitnessHasExecutingConsumer,
because the executing consumer genuinely exists: the declared roster
module_binding_supply_claims, which the transport runs. That is the operator's
second classification done at the authority rather than as an exemption -- one
typed roster read by execution, and a classification the coverage fold already
understands, with no second list of function names anywhere.

Proven by calling the production fold rather than by rerunning the floor:
witness_path_policy_execution_standing for this entry and function now returns
WitnessHasExecutingConsumer, where it returned UnclassifiedPathDeferral before.
The freeze witness, the exact-admission witness and the exclusion-reconciliation
witness are all green; the frozen roster is untouched and did not grow.

main_wet to a no-op fixed point; regen_stage0 re-emitted; seed rebuilt clean;
regen --verify divergence 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e CI batch

witness_live_floor_discovery_batch_count_is_four went red, and measurement said
the live count is FIVE. The cause was mine: enrolling the module-value
expected-red row is v1-scoped, so it enrolls the conditional
gunbc_v1_claim_scoped_known_red_probe_batch, and that is a whole additional
corpus batch in the floor plan -- purchased for exactly one assertion, with a
companion clamp row to fund it.

I did NOT bump the literal to five. Editing an oracle so it agrees with my own
change is the change-detector shape, and here the change was not worth
defending: a fixture-only red row that moves no production boundary is precisely
what the anti-stall constraint rules out, and its cost is a per-PR batch every
other lane pays. So the row, its admission and its clamp companion are removed
and the count returns to four by construction rather than by re-pinning.

THE REQUIREMENT IS NOT LOST, which is the only reason removing evidence is
acceptable here: nothing climbed, so there is no rung to protect, and the finding
is already carried in gunbc.roadmap_authority
namespace_lane_measured_frontier_note -- a module-scope value declaration binds no
name, so a bare cross-file reference to a module datum cannot bind and the derived
closure is short by every module-value edge, with the cause located at
parsed_occurrence_role_for_node. When the loader needs module-value bindings the
assertion returns riding the loader's own witness, where it costs no extra batch.

Deleting the source also required deleting its emitted seed file: regen refused
with `committed generated stage0 file(s) are absent from the derived emission
plan`, which is the derivation working -- the plan is derived from the observed
module closure, so a removed module leaves its .rs orphaned and the gate says so
rather than leaving dead output committed.

Executed: witness_live_floor_discovery_batch_count_is_four and
ci_floor_plan_witnesses green; witness_floor_appends_one_selected_native_batch
green; the clamp-authority and exact-admission gates green; regen_stage0 down to
144 files; seed rebuilt clean; regen --verify divergence 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three conflicts, all resolved by taking main rather than by merging text.

MY RENAME IS WITHDRAWN. main fixed the flat-namespace collision itself, renaming
the self-host type to GeneratedArtifactIdentity where I had renamed it to
ArtifactMaterialization. Their name is the one on main, so mine is a second answer
to a question already settled -- exactly the fork this repository consolidates
later at cost -- and the self-host generation, promotion-admission and both
witness files are taken wholesale from origin/main. My reasoning for the other
name (the type is a materialization state, not an identity) is not worth a
divergence and is not re-litigated here.

ci.yml conflicted as a generated artifact and was regenerated from the authority
rather than hand-merged, leaving a one-line delta.

Executed: both self-host witnesses and the cache witness that the collision
originally broke are green; main_wet to a no-op fixed point; regen_stage0 at 144
files; seed rebuilt clean; regen --verify divergence 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…left behind

compile-clean's only hard diagnostic was `name 'ArtifactIdentity' not found in
module 'v2.compiler.self_host.generation'` at
dag/gunbc/self_host_artifact_materialization.dag. main's own rename to
GeneratedArtifactIdentity did not update that consumer, so the defect is on main
and arrived here with the merge -- the same class as the collision the rename was
fixing, one step later: a name change is a corpus-wide edit and this one missed a
file.

Three sites updated in that module. Nothing else needed touching: the remaining
ArtifactIdentity references in the tree belong to std.cache_interface's GENERIC
ArtifactIdentity<T> and are unrelated -- renaming those would have broken the
cache witnesses this class already broke once.

A TRAP WORTH RECORDING, because it nearly sent me chasing a phantom: running
dag_compile_clean_gate_passes standalone through `gunbc run` returns false
regardless of the tree, and its companion receipt says why -- `compile-clean
failure detail unavailable: no in-run compile receipt`. The gate CONSUMES the
executor's one in-run compile receipt; outside the executor there is none, so the
false is the invocation path, not a compile error. Verified the actual fix by
resolving the repaired module directly instead: it now reaches a call-contract
mismatch on a missing argument, which is proof the import resolved.

Executed: dag/gunbc/self_host_artifact_materialization.dag resolves;
self_host_artifact_materialization_witness_test green; main_wet to a no-op fixed
point; regen_stage0 at 144 files; seed rebuilt clean; regen --verify divergence 0.
The real-execution sibling refuses hermetically for wanting a real cargo build,
which is its declared posture and not affected by this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The loader fixed point: std.reference_derived_closure iterates an entry's file
closure out of accepted structural bindings, with no import list and no
hand-selected provider path anywhere on the path.

TWO POPULATIONS, DELIBERATELY NOT ONE, and this is the correction that made it
work. The candidate index is built over the WHOLE ordinary source-file universe;
the references BOUND are selected by the file they belong to, from the admitted
closure only. Collapsing them deadlocks: with the index over admitted files
alone, a bare reference to a not-yet-admitted provider has no declaration to
bind to, so it is Unbound, so no provider file is derived, so nothing is ever
admitted. A module-to-file index cannot stand in — storage realization says
where a module lives, never which module declares a name.

Selection is by the reference's own file, derived from its module path through
the module/file index, because assembly rekeys every occurrence into one
transport and the row boundary is gone by then. A reference whose file cannot be
derived REFUSES rather than being dropped; dropped, it would narrow the selected
population silently and the step would report the consumer as having fewer
dependencies than it has.

The executed trace, no provider-path input:

  consumer admitted
  -> LiveTreeDisposition binds to the exact declaration
  -> src/v2/std/live_tree.dag becomes pending
  -> provider admitted
  -> pending empty
  -> Settled with one provider edge, 2 iterations

Three outcomes and the absence of a fourth is the point: there is no arm
returning the edges for whichever references happened to bind. An unbound or
ambiguous reference in the selected population refuses the whole step, because a
closure short by one provider is not a smaller closure — every consumer
downstream reads it as complete.

joined_dependency_binding_walk_selected is factored out of the existing joined
walk (which now delegates to it with every reference selected), so clause-(e)
and type references still reach ONE population through one fold over one index.
No second closure, no second totality law.

Witnessed by execution, 8/8, src/v1/tests/claim/reference_derived_closure_witness_test.dag:
the keystone trace; the second step settling over the first step's own returned
state; the whole fixed point at exactly two iterations; the carrier law that
every admitted mention keeps its receipt while edges dedupe to one with no
self-edge (measured three provenance rows, not the two I first asserted); the
provider outside the universe refusing as Unbound; an entry outside the universe
refusing; two declaring files refusing as ambiguous naming both identities; and
an unreferenced universe file staying out of the closure, so universe breadth
does not become closure breadth.

Homed in dag/std beside its carrier rather than under src/v2: the witness needs
the v1 seed parser to produce real transports, and a src/v1 witness importing a
src/v2 module is invisible to the regen world — regen refused with
"function not found in scope" until the file moved.

production file/function changed: dag/std/reference_derived_closure.dag
  reference_derived_closure_step / reference_derived_closure_fixed_point;
  dag/std/occurrence_binding_candidates.dag joined_dependency_binding_walk_selected
focused command executed: claim_batch --entry
  src/v1/tests/claim/reference_derived_closure_witness_test.dag (8/8 PASS);
  regen_stage0 --verify regen_divergence_count=0
next production function: v1_compiler.cli_run
  source_root_ingest_artifact_id_for_path — take the basename out of the identity
  decision (mint through the string-taking Symbol authority on the repo-relative
  path the row already carries) so dag/std/import.dag,
  dag/extdeps/languages/go/module.dag and dag/extdeps/bmc/capability.dag are
  representable, then run this fixed point over dag + src/v2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…esentable

The module-binding manifest could not describe half the corpus, and the cause was
an identity derived from a lossy projection of a fact the row already carries.

source_root_ingest_artifact_id_for_path minted a `^stem` symbol literal from the
file BASENAME. That is wrong twice. It is UNREPRESENTABLE for any file whose stem
is a reserved .dag keyword -- dag/std/import.dag, dag/extdeps/languages/go/module.dag
and dag/extdeps/bmc/capability.dag emitted `^import`, `^module` and `^capability`,
so any manifest scope touching dag/ wrote a file that does not parse. And it is
not an IDENTITY at all: two files with the same basename in different directories
minted the same symbol, so the manifest's own rows collided silently -- a defect
that was never going to announce itself, since nothing about the output looks wrong.

The repair is not keyword escaping. The basename is out of the identity decision
entirely: the id interns the repo-relative PATH through the string-taking Symbol
authority (symbol_intern_lexeme), exactly the move the module-path half already
makes with qualified_name_from_string_segments. Keywords are inert because nothing
is ever spelled as an identifier, and same-named files in different directories
stop colliding because the path is what distinguishes them.
source_root_ingest_symbol_from_stem is deleted rather than fixed; there is no
sanitizing pass left to get wrong.

Measured end to end, not by reading the emitter: 3533 bindings over dag + src/v2,
manifest emitted, PARSED and resolved, and all nine row-supply projections green.
The wet gate run_module_binding_supply_gate now emits over BOTH ordinary source
roots and returns true, including its parse oracle.

The controls are the ones the removed comment promised, ONE PER KEYWORD rather
than one for the class -- each is a distinct file whose stem is a distinct
keyword, and a repair that fixed only the one a class-level assertion happened to
visit would pass it:

  std.import                    -> dag/std/import.dag
  extdeps.languages.go.module   -> dag/extdeps/languages/go/module.dag
  extdeps.bmc.capability        -> dag/extdeps/bmc/capability.dag

plus a population row asserting the index spans both roots at once, which the
per-pair rows cannot state: a producer invoked with one root would satisfy every
pair it happened to cover and fail that one.

The scope comment on the consuming witness dissolves with the defect (its stated
dissolve-on was exactly this), and the "dag/ root is not covered" paragraph is
replaced by the assertions rather than by a claim that it is now fine.

Why this lane needed it: providers live in dag/ as well as src/v2, so the
reference-derived closure landed in the previous commit could not be run over the
real corpus while the producer could not name half of it.

production file/function changed: v1_compiler.cli_run
  source_root_ingest_artifact_id_for_path / source_root_ingest_compilation_unit_for_path
  (source_root_ingest_symbol_from_stem deleted);
  v2.workflow.module_binding_supply_transport mbs_dag_source_root + mbs_emit_args
focused command executed: discover_source_root_ingest --source-root src/v2
  --source-root dag --emit-module-binding-manifest (3533 rows, zero `^` literals);
  claim_batch module_storage_row_supply_test 9/9 PASS over that manifest;
  run_module_binding_supply_gate = true; regen_stage0 --verify divergence 0
next production function: supply this manifest's rows as the loader's universe and
  run reference_derived_closure_fixed_point over dag + src/v2, then replace
  dependency_resolution_facts_live's body and differential its edge set against the
  import-derived one in both directions with a typed disposition per row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The loader now runs from the ordinary compile path over a real source-file
universe, and the first thing it produced was a refusal I could not act on. That
is a defect in my carrier, not in the corpus, so it is fixed here rather than
worked around at the reporting end.

A REFUSAL NAMING AN OccurrenceId IS NOT A LOCATED REFUSAL. The id is an allocator
ordinal: it means nothing to a reader, it means nothing to a tool, and assembly
rekeys it, so it is not even stable across universes and cannot be joined back to
the file it came from. The authored name and byte span sit in the assembled
transport's own index, one lookup away. Carrying them is not new observation -- it
is declining to discard what the step already holds.

ClosureBindingRefused now carries first_failure_locus (authored name + span) and
unbound_count. An occurrence the index cannot resolve gets its OWN arm,
ClosureRefusalNotLocatable: a population refusal whose subject cannot be located
is a different failure with a different cause -- the transport and its index
disagreeing -- and collapsing it into a missing locus would hide that.

reference_binding_projection_occurrence lands on the carrier that owns
ReferenceBindingProjection rather than as a private match in the loader (one
authority, ten arms, total over the closed coproduct). It answers Optional rather
than fabricating an id, because the four index-build refusal arms are failures of
the WHOLE transport, not of one occurrence, and answering them with some
particular occurrence would point a reader at an innocent site.

MEASURED, from the ordinary path, no fixture and no provider-path input:

  --source-root src/v2/std --entry src/v2/std/live_tree.dag
    universe=116 parse_refusals=0
    Refused after 2 iteration(s):
    ClosureBindingRefused: 'FreeMonoid' at bytes 269-279 unbound
    (25 unbound reference(s) in the selected population)

Two facts in that one line. The closure GREW on the real tree -- two iterations
means a provider was discovered from a binding and admitted -- and then it refused
precisely, on a name I can check. The refusal is CORRECT and the fault was my
input: FreeMonoid is declared in dag/std/algebra.dag and merely IMPORTED by
src/v2/std/algebra.dag, so the declaring file was outside the src/v2/std universe I
supplied. A loader that had narrowed instead of refusing would have reported a
closure short by every dag/-rooted provider and every consumer would have read it
as complete.

The witness rows that match this refusal now assert the LOCUS -- authored name
LiveTreeDisposition with a nonzero span and a nonzero unbound count -- so the
located half is proven by execution rather than merely typed. 8/8 green.

The probe entry point is deliberately not enrolled in the release-bins pack: no
transport addresses it, and enrolling a name nothing invokes would be a claim of
enrollment without a consumer.

production file/function changed: dag/std/reference_derived_closure.dag
  closure_binding_refusal / locate_occurrence (+ ClosureRefusalNotLocatable);
  dag/std/occurrence_binding_candidates.dag reference_binding_projection_occurrence;
  v1_compiler.cli_run reference_derived_closure_over_source_roots
focused command executed: reference_derived_closure_probe --source-root src/v2/std
  --entry src/v2/std/live_tree.dag (located refusal above);
  claim_batch reference_derived_closure_witness_test 8/8 PASS;
  regen_stage0 --verify divergence 0
next production function: reference_derived_closure_over_source_roots on the full
  dag + src/v2 universe (running; 3533 files parsed into one assembly is the cost
  question), then the same-file two-way differential against
  import_resolution_facts_live -- which needs this import-blind producer, because
  the host reference producer emits nothing for a file that still carries imports
  (measured: 800 of 3533 files are import-free, so those two producers partition
  the corpus and cannot disagree on any file).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review findings on d07bc0f, all four accepted. This is its commit 1.

THE UNIVERSE IS THE DENOMINATOR FOR EVERY BINDING DECISION, so building it now
refuses instead of counting. The previous version pushed unreadable and
parse-refused files onto a counter, omitted them, and ran the closure anyway. That
is unsound in the exact way this lane keeps closing, and worse than the review
stated: a dropped file may hold the ONLY declaration answering a reference, so its
omission renders as Unbound -- or, if a homonym survives elsewhere, the reference
binds uniquely to the WRONG declaration and nothing reports anything. No caller
could distinguish "the complete universe has no provider" from "the provider was in
a file we discarded".

Universe construction is total: SourceReadFailed, ParseRefused,
DuplicateCandidateFile and DuplicateModuleIdentity all refuse BEFORE the fixed
point starts. Duplicate file identity mattered independently: partition_universe
kept the last matching row while universe_row_for_file returned the first, so
overlapping source roots could make the answer depend on which helper read the
population. EntryOutsideUniverse is split out of the generic ClosureNoEntryAdmitted
-- "the entry is not in the universe" and "no entry was admitted" have different
causes.

ONLY A SETTLED CLOSURE IS A SUCCESSFUL RUN. Refused and Exhausted both returned
Ok(String) and the probe exited zero, which makes it useless as a gate and reads as
the ordinary path having ACCEPTED the closure. ReferenceDerivedClosureRun is typed,
is_settled() is true for exactly one arm, and the probe exits 0 settled / 1 refused
or exhausted / 2 bad invocation.

Three further repairs, each removing something that could lie:

closure_step_edges answered a refusal with [] and closure_step_state with Absent --
the absorbing fallback this lane removes elsewhere, exported and waiting for a
consumer. Both DELETED rather than reworked: a caller reading [] cannot tell "no
dependencies" from "the step refused", and matching the three arms is the only
reading that cannot lie.

The fabricated OccurrenceId { value: 0 } is gone. A population failure with no
occurrence-bearing arm is ClosurePopulationFailureHasNoOccurrence carrying the
population -- the comment two lines above it forbade inventing an id, and then it
invented one.

unbound_count is failure_count. population_failure_count counts EVERY projection
failure class, including ambiguity and index/category refusals, so the name and my
own report of "25 unbound references" both overstated what was measured.

CONTROLS EXECUTED, on a real source root through the ordinary host path:

  malformed candidate      -> CandidateUniverseRefused: ParseRefused{malformed.dag}, exit 1
  duplicate module identity -> CandidateUniverseRefused: DuplicateModuleIdentity
                               naming both files, exit 1
  same root, defect removed -> Settled: 2 files, 1 edge, 3 provenance rows,
                               2 iterations, exit 0

The positive control is the vacuity guard for the two refusals: without it, a
universe builder that refused everything would pass both.

One measured correction to my own control, kept because it cost real time: the
first positive control refused on 'CtlOne' unbound, and the fault was the fixture,
not the loader -- `type CtlSubject = CtlOne` with no leading pipe is an ALIAS whose
right side REFERENCES CtlOne, not a one-variant coproduct. The loader was right.

RUNG, corrected. What is landed is: the fixed-point mechanism executes over real
repository source files through a host entry point. What is NOT landed is ordinary
loader or compiler consumption -- dependency_resolution_facts_live is unchanged and
nothing on the compile path reads this closure. The in-code claim that said
otherwise is rewritten; the probe is an integration instrument, not the cutover.

STILL OPEN from the review, in its order, none of them started here: relational
exposure (a permanently cross-file-grounded file is wrong once it becomes an
admitted consumer with references of its own) with A->B->C and earlier/later-in-B
controls; sealing the candidate universe once instead of reassembling 3533 files
per iteration; module-value declarations; then the differential.

production file/function changed: v1_compiler.cli_run
  reference_derived_closure_over_source_roots (+ CandidateSourceFailure,
  ReferenceDerivedClosureRun), bin/reference_derived_closure_probe;
  dag/std/reference_derived_closure.dag closure_binding_refusal
  (+ ClosurePopulationFailureHasNoOccurrence, failure_count;
  closure_step_edges / closure_step_state deleted)
focused command executed: the three controls above; claim_batch
  reference_derived_closure_witness_test 8/8 PASS; regen_stage0 --verify
  divergence 0
next production function: the candidate-count instrument on the located occurrence
  -- the whole-corpus run refuses on 'String' with TWO visible module-root
  declarations, where the honest answer is ambiguous-naming-both, and "exposure
  rejected both candidates" versus "the name bucket never held them" are different
  defects with different fixes that no report currently distinguishes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbc-ci-auto-heal and others added 23 commits August 13, 2026 05:33
… two of them

A refusal saying a name did not bind means one of two opposite things, and the
report could not tell them apart: EITHER no file in the universe declares that name
(the universe is short and the loader is right) OR declarations exist and candidate
enumeration discarded them (the filter is wrong and the loader hides it behind the
same word). One count separates them, so the refusal now carries it.

Counted from the PRE-ASSEMBLY parses deliberately. The assembled transport is what
the filter read, so a count taken there would inherit whatever the filter did and
could never contradict it.

TWO OPPOSITE VERDICTS FROM ONE INSTRUMENT, which is why it discriminates rather
than confirming whatever I expected:

  universe=116 (src/v2/std)             'FreeMonoid' unbound
    [universe declares 'FreeMonoid' 0 time(s)]
    -> LOADER RIGHT. src/v2/std/algebra.dag IMPORTS FreeMonoid; the only
       declaration is dag/std/algebra.dag, outside that universe.

  universe=251 (dag/std + src/v2/std)   'String' unbound
    [universe declares 'String' 2 time(s) in
     dag/std/string_type.dag x1, src/v2/std/text.dag x1]
    -> LOADER WRONG. Both are module-root `type String = FreeMonoid<Char>` in files
       grounded CrossFileProviderExportedExposure, so the honest answer is
       ambiguous-naming-both. Candidate enumeration returned EMPTY against two
       present, exported declarations.

That is the review's static-grounding finding confirmed by measurement, and it is
live on the corpus today rather than latent at the second hop. It also retires my
own earlier reading of the whole-corpus run: that refusal was never evidence about
the corpus. The loader was reporting "nothing declares this" while two files
declared it -- not a widen, not a located refusal, but a WRONG VERDICT WEARING A
CORRECT-LOOKING WORD, which is the one failure shape worse than either.

The instrument stays enrolled for exactly that reason: it is what stops the word
from standing unchecked again.

Two process notes kept because both cost real time. The 20-minute whole-corpus rerun
produced nothing readable because I piped it through `tail -4` and the refusal
prints to stderr AHEAD of `time`'s output, so my own pipeline discarded the
measurement; the small two-declarer universe answers the same question in minutes
and is the right instrument size. And `exit=$?` after a pipe reports the LAST stage,
so an earlier `... | tail -2; echo exit=$?` printed 0 for a probe that had correctly
exited 1.

production file/function changed: v1_compiler.cli_run
  reference_derived_closure_over_source_roots (pre-assembly declaration count on the
  ClosureBindingRefused report)
focused command executed: reference_derived_closure_probe over src/v2/std (declares
  0) and over dag/std + src/v2/std (declares 2, reported unbound), exit 1 both
next production function: candidate_occurrence_ids_for_reference, and beneath it
  declaration_exposed_on_reference_chain vs ordinal_of_occurrence -- the authored-
  order gate is the leading suspect, because authored order is a WITHIN-FILE fact and
  assembly places the consumer's references before every provider declaration in one
  global ordinal space. Which of the two rejects gets instrumented before either is
  changed; that is the review's commit 2, now with a measured failure to satisfy
  instead of a hypothetical one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ot exist

RETRACTION, same day. There is no two-declarer defect in the closure. Measured with
the population summary this commit adds:

  universe=3    'CtlAlias'   2 declarers   population=Ambiguousx2   correct
  universe=255  'String'     2 declarers   population=Ambiguousx1   correct
  universe=116  'FreeMonoid' 0 declarers   Unbound                  correct

The loader answered correctly in every case. What was wrong was my report and my
reading of it: the refusal line printed a located authored name and a failure COUNT
but never the projection CLASS. I carried the word Unbound over from the earlier
FreeMonoid run -- which genuinely was unbound, its declaration being outside that
universe -- and built a nonexistent defect out of the inference, then carried it into
a message to another lane as a blocking finding.

A count is not a class, and a report that omits the discriminating field invites the
reader to supply it from memory. first_failure alone is a head projection; the tail
carried the answer.

The same class was measured independently in a sibling program the same night
(vivid-bat-377): parse_grammar_choice_overlap_residue is a constant prefix prepended
to every parse failure by rejected_with_pending, so every .head reading in that
program discriminated nothing while looking like a cause. Two labs, two carriers, one
shape -- read the head, get a stable-looking answer that was never about the input.

So the probe now renders EVERY member of a refused population by class
(population=Ambiguousx2), not the head and a count. The specific lie is no longer
available to whoever reads it next, including me.

WHAT THIS CHANGES ABOUT THE WORK, stated because the retraction moves a lane item
rather than merely deleting a note: String is declared in BOTH dag/std/string_type.dag
and src/v2/std/text.dag, two authorities for one concept, and the closure refusing it
as ambiguous is CORRECT behaviour. So the next unit is not a filter repair -- it is
consolidating forks like that one, which is ordinary import-deletion work. The
corpus-clean measurement will surface every such fork as an ambiguity, and each is a
decision about which authority survives, not a bug to fix.

production file/function changed: v1_compiler.cli_run summarize_population_failures
  (+ the population summary on the ClosureBindingRefused report)
focused command executed: the three rows above, each re-run against the rebuilt probe
next production function: unchanged in shape but not in content -- relational exposure
  with an A to B to C receipt, then sealing the candidate universe once. The filter
  repair I had queued ahead of them is withdrawn; there was nothing to repair.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reference-derived closure had a property no language may have: the accepted
declarations for one file changed with which file the walk started from. A
provider file's module members were ground to RootExposure, so a premature
same-module reference bound when its file was reached as a provider and refused
when it was the entry. Measured on identical bytes, both directions, before the
repair.

The cause was role-stamped exposure. CrossFileProviderExportedExposure and its
ModuleExposure => RootExposure promotion are deleted; every ordinary module
member grounds ModuleLocalMemberExposure whether its file is entry or provider.
The whole source-root universe stays the declaration DENOMINATOR and stops being
the visibility set: a globally unique leaf no longer binds bare from anywhere.

That closes the hole and needs a replacement naming path, which is the second
half here. A qualified reference is already ONE occurrence whose authored_name is
the whole dotted path -- the parser mints it that way and nothing new is minted --
so what was missing was only the resolution rule: the leaf selects the bucket,
the qualifier selects which declaring module may answer, by segment suffix over
the declaration's own module row. Exposure for a projected reference is
ModuleExposure for exactly that module: RootExposure does not answer a qualified
name, and the source-order gate is not applied, because order is a within-file
relation and two files have no authored order between them. Two modules sharing a
qualifier segment supply both declarations and fold to Ambiguous -- the refusal,
not a nearest-match heuristic.

Controls, green by execution:
  - later declaration refuses identically under both entries (same authored name,
    same span; OccurrenceId deliberately not compared across assemblies, which
    rekey into separate allocator scopes)
  - earlier declaration binds under both entries, so the row above is not
    satisfiable by refusing everything
  - bare cross-file reference is Unbound
  - qualified projection reaches the provider and admits its file
  - qualified A->B->C settles in 3 iterations, 3 files, 2 edges
  - two modules sharing the qualifier segment refuse as Ambiguous naming both

The keystone fixture moved off globally-bare LiveTreeDisposition, which was
testing the deleted model: it passed because exactly one declaration carried that
leaf corpus-wide, which is the reasoning this commit removes.

Corpus state, unresolved and stated rather than worked around: over dag + src/v2
(3533 files) the closure refuses on bare String, declared in both
dag/std/string_type.dag and src/v2/std/text.dag. It refused before this commit
too, as Ambiguous; it now refuses as Unbound. The corpus is authored in the
import-era bare style, so reference-derived closure over real files stays refused
until those references carry a qualifier or the naming authority reaches them.
That is the remaining work for import deletion, not a defect in this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…delete-imports-on-main

# Conflicts:
#	.github/workflows/ci.yml
#	ROADMAP.md
…n merge

Both conflicted paths are generated artifacts, so neither side of the conflict
was the answer: ci.yml is derived by gunbc ci and ROADMAP.md by the roadmap
authority. Resolved by taking main's side to close the merge and then
regenerating both, which is the only resolution that cannot encode a hand-picked
line. ci.yml picks up this branch's new emitted modules in the heal exclusion
list; ROADMAP.md re-derives the namespace lane rows this branch's focus
selection adds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ir subject

Two things, both consequences of the exposure change rather than new direction.

MODULE VALUES. A module-scope `data name: T = v` bound no name at all, so a
reference to any module datum could not bind and the derived closure was short by
every module-value edge in the corpus. The shape classifier cannot be widened
where the hole is -- it sees a node carrying both a body and a type annotation,
which is reachable from nodes that are not module items -- so the arm goes where
the question is decidable: parsed_module_item_role, asked only of a node already
known to be a module item.

The category is new rather than borrowed, and the two obvious reuses are both
wrong. LexicalValueOccurrence is explicitly NOT module-scope exposed, so it would
have declared every module datum unexported; CallableOccurrence would have
nicknamed a value as callable. ModuleValueOccurrence joins the closed coproduct as
module-scope exposed and clause-E dependency inducing, and a value reference is
admissible against it -- an expression variable cannot know whether its target is
a local binder or a module member, and the reference site is not where that is
decided. Measured on a fixture: three failing references become two, the residue
being type references genuinely outside the universe.

THE WITNESSES. Six discovery rows went red in CI that my local run did not catch,
and the reason is worth recording: I filtered a corpus run's output and read the
absence of FAIL from a filtered stream. A count of matched lines is not a count of
witnesses. Re-run unfiltered, with totals compared against the roster.

Of the six, two -- cross_file_provider_exported_exposure_at_{module_root,direct_child}_upgrades_to_root
-- had no subject left: they called the surviving grounding and demanded the
deleted RootExposure answer. They are deleted rather than inverted, because what
they would now regress is a behaviour the model no longer has, and the two
module_local_member_exposure rows beside them already assert the surviving answer
at both containment shapes. The other four are fixture-driven and were authored
bare: the clause-E consumer now references provider.helper, and the pool-
independence decoy moves from app.decoy to other.provider so it still COLLIDES
under a qualifier -- its own note predicted the collision would become a fixture
artifact under projection, and moving the module keeps it the ambiguity control it
was written to be rather than letting it pass for the wrong reason.

Green by execution: assembly 9/9, pool-independence 8/8, candidates 23/23,
closure 10/10, bridge 21/21.

FRONTIER NOTE REWRITTEN. namespace_lane_measured_frontier_note described a
supplied-provider era that no longer exists and would have sent the next reader
back to work already landed. It now records what moved (discovery, transitivity,
relational exposure, qualified type projection) and what is open at the grain that
decides dispatch: expression-position paths, the bare-authored corpus, no ordinary
compile-path consumer, and an unsealed universe. It also carries the obligation it
previously omitted -- rewire_type_env_import_str_binding_identity survives on main,
gunbc#8202 proved it cannot simply move pre-typecheck and gunbc#8227 only optimized
it, so its deletion belongs to the type-env single-authority cutover and must land
before grammar deletion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… file

An expression path arrives as a bare head reference plus a field projection --
p.first is a reference to p, not one dotted occurrence the way a TYPE path is --
and the head had nothing to resolve against: parse_module stamps a module once
carrying its WHOLE dotted name, so declarations_by_name, keyed by authored
spelling, has no entry for the last segment alone. Measured before: 'p' did not
bind, universe declares 'p' 0 times.

module_declarations_by_segment buckets NamespaceSegmentOccurrence declarations by
their last segment and supplies them to bare references beside the on-chain
population. Keyed by segment rather than full path because the ratified migration
form is the SHORTEST qualifying container path; two modules ending in the same
segment supply two candidates and fold to Ambiguous, which is the refusal rather
than a nearest match. The category is read off the DeclarationOccurrence, not
inferred from a dot in the text, so an ordinary declaration cannot impersonate a
module.

Three consequences, each deliberate rather than incidental. A module candidate
skips the on-chain exposure filter: module-sibling exposure gates on same-module
plus source order, both within-file relations, and a module name is visible across
files by design. A value reference is admissible against a namespace segment,
because the parser mints the head as ExprVar and the reference site genuinely
cannot know whether a bare name is a local binder, a module member or a module.
And `data name: T = v` needed its own child-stamping arm beside the lexical
binder's: the catch-all stamps every child Unclassified, which loses the reference
the value expression carries -- measured, the provider file's own constructor
reference stopped binding, so admitting a file did not admit what it declares.

Isolated receipt, no type reference to the provider anywhere in the fixture:

  module tmp.e.c
  type Str2 = TOne | TTwo
  data mine: Str2 = p.first        -- the only cross-file reference is the head

  Settled: 2 file(s) admitted, 1 edge(s), 2 iteration(s)

WHAT THIS IS NOT. It binds the HEAD, which is what a file dependency needs -- the
provider file follows from the module declaration's own file. It does not descend
to the member, so a bound head with a missing or ambiguous member is not
distinguished here; that belongs to the resolver cutover. The dissolution trigger
is the ReferencePath carrier both syntactic positions feed, at which point the
parser mints the head as a namespace reference and the category admission above
stops being needed.

MEASURED AND NOT FIXED, pre-existing: a constructor reference at module scope does
not bind. Constructors are module-scope exposed by category, but exposure derived
from containment gives them LexicalExposure scoped to the type body, because their
ancestors are [module, type] rather than [module]. Cause located at
declaration_exposure_from_containment; not repaired here because it changes the
exposure authority again and is independent of this commit.

parser_sibling_match_arm_pattern_binder_holds fails, and the evidence says it is
not from this change: main hardcodes the same ModuleLocalMemberExposure grounding
this branch passes explicitly, the fixture contains no module-value item, and no
module segment is named x. Its own note parks it, stating the property does not
hold from authored source because match-arm bodies stamp as siblings of pattern
binders.

Green by execution: closure 10/10, bridge 21/21, parser matrix 20/20.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A module-scope reference to a constructor did not bind. Constructors are declared
module-scope exposed by the category authority, but exposure derived from
containment gave them LexicalExposure scoped to the type body, because their
ancestors are [module, type] rather than [module]. So a provider file could be
admitted and still fail to supply what it declares.

Containment alone cannot decide this. A constructor and a field are both children
of a type node and derive identical ancestors, yet one is reachable at module scope
and the other is not, so shape carries strictly less information than the question
needs. The nested arm now asks occurrence_category_module_scope_exposure_verdict --
the authority that already owns module-scope membership -- rather than guessing.
This is not category substituted FOR exposure: the decision site still reads only
DeclarationExposure. It is the grounding chosen at derivation, which is what the
exposure authority already requires of its callers.

The discriminating control is a pair at identical depth: nested ConstructorOccurrence
derives ModuleExposure, nested FieldOccurrence stays LexicalExposure scoped to the
type body. Same containment, opposite answers, so the rule cannot be satisfied by
anything that merely widens exposure.

End to end on the realistic shape -- a provider declaring a type with constructors
and a datum initialised from one, reached by expression-position projection:

  module tmp.k.p                  module tmp.k.c
  type Str = SOne | STwo          type Local = LOne | LTwo
  data first: Str = SOne          data mine: Local = p.first

  Settled: 2 file(s) admitted, 1 edge(s), 2 iteration(s)

THREE FIXTURES WERE MISLABELLING THEIR OWN SUBJECT and are corrected rather than
accommodated. nested_containment_derives_lexical_exposure_holds and the seed
binary's two_ancestors_lexical_exposure both passed TypeOccurrence while calling
their subject a binder; a nested type IS module-scope exposed, so under the new
rule they failed for the right reason. Both now carry LexicalValueOccurrence,
which is what a binder is. The clause-E fixture's exposure rows now carry the same
category its declaration rows already declare. Changing a test because it broke is
usually the wrong repair; what distinguishes these is that the fixture disagreed
with itself, and the new constructor/field pair is what actually pins the behaviour.

Green by execution, each run to completion against its roster: closure 10/10,
bridge 21/21, type-ref context 8/8, cross-file assembly 9/9, pool independence 8/8,
candidates 23/23, parser matrix 20/20, and the compiled seed witness binary all
cases pass.

NOT CLAIMED: a clean whole-corpus local run. Two attempts were killed by their
timeout mid-file at 545 and 6455 of 7472 witnesses, and I quoted both as totals
before checking that either finished. Zero failures across ~5750 executed
witnesses is what those runs support. The corpus verdict comes from CI, which
completes; gunbc#8244 measures why the local shape does not (1,927s preparing
entry-private worlds for 148s of evaluation) and is the reason to stop paying it
per push rather than to pay it for longer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…defect

The note this replaces was itself written to supersede a stale one and went stale
within a day, so it is now dated and reads as a measurement rather than as a
standing description.

The substantive change is how expression-position work is characterised. It landed
at module-head grain only, and 'descent is unimplemented' understates the
consequence: a path whose member is absent or ambiguous still discovers a provider
file, so the resulting edge is justified by the module head rather than by an
accepted terminal declaration. Named as a provisional loading widen that may not
become the final dependency authority, with the ReferencePath carrier as its
closing condition.

Also records why the corpus migration must be derived rather than hand-judged: the
population moves while the lane works — 18,628 import lines across 2,884 files at
this measurement, and one in-flight PR moves it by +147.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The head-only widen named in the previous commit is closed, and it was worse than
"descent is unimplemented". Reproduced before the repair:

  module tmp.m.p                    module tmp.m.c
  type Other = OOne | OTwo          data mine: Local = p.no_such_member

  Settled: 2 file(s) admitted, 1 edge(s)

A file dependency was asserted against a provider that declares no such member.
The edge rested on the module head, which names only where to look.

THE MECHANISM, which is a category answering a question it was not asked. An
expression path is a FieldOccurrence reference whose base is its child, and
FieldOccurrence is excluded from clause-E dependency inducement -- correct for a
record field access. That exclusion was applied to the DEPENDENCY question and
silently answered the BINDING question too: an excluded reference is never added
to the required population, so it never had to resolve at all. Exclusion from
edge derivation had quietly become exemption from binding.

A projection off a module head is not a record field access, and the difference
is decidable at resolution rather than at the category: the base resolves to a
NamespaceSegmentOccurrence or it does not. When it does, the segment resolves
among that module's own members and the dependency is attributed to the TERMINAL
declaration, which is what a file dependency actually rests on.

Both directions, by execution:

  member exists   -> Settled: 2 files, 1 edge, 5 provenance rows
  member absent   -> Refused: 'no_such_member' Unbound, located at the segment

A second gate had to move for the positive case, and finding it required
distinguishing two states that look identical. Candidates WERE supplied for the
existing member and every one was filtered out by category admissibility --
FieldOccurrence admitted only FieldOccurrence -- which reads as Unbound and is
indistinguishable from a member that does not exist. Both fixtures refused for
what looked like one reason and was two. The arm now admits module-scope member
categories, narrow in effect rather than in statement: candidates for a
FieldOccurrence reference are supplied at exactly one site, reached only after the
base has resolved to a module, so an ordinary field access supplies nothing and
cannot reach a module member by widening.

DISSOLVE-ON, for both this and the head admission: the ReferencePath carrier gives
a projection segment its own category at derivation, at which point a segment stops
impersonating a field and both widenings are deleted rather than kept beside it.
This commit closes the fail-open; it does not yet unify the two syntactic
positions into one carrier.

Green by execution, each run to completion against its roster: closure 10/10,
bridge 21/21, candidates 23/23, pool independence 8/8, binding resolve 10/10,
cross-file assembly 9/9, type-reference context 8/8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
parser_sibling_match_arm_pattern_binder_holds cannot pass from authored source,
and the parser is the reason rather than the witness. v1.compiler.parse stamps a
match-arm pattern binder with the ARM's ancestors, so the binder is a SIBLING of
the arm body rather than an ancestor of it; its containment-derived LexicalExposure
has an exposing scope that does not contain the arm-body reference, so the
isolation asserted here -- each arm's reference binding its own binder and refusing
the other arm's -- cannot hold however the exposure authority is written.

The file's own note has said this since the B1 rollup. What it lacked was an
ADMISSION: a note is not enrollment, so the function stayed an ordinary
green-required test that happened to run only when something in its import closure
changed. gunbc#8115's dag/std/occurrence_binding_resolve change selected it for the
first time in this lane, and it read as this PR's regression. The CI history says
otherwise precisely: green at a4f6bbd, whose diff is a roadmap note and selects no
src/v1 witness, and red at b4f16ae, whose diff is in that closure.

Cost of the missing row, recorded because it is the argument for adding it: two
bisect cycles reverting correct code against a subject I had not confirmed was the
failing one. The CI log does not carry the failing witness name, so I substituted
the one failing locally -- which passes in CI -- and tested theories about it.
Identify the failing subject before forming a theory about the cause.

The equivalent property is discharged on a hand-built transport where the arm body
nests under the binder occurrence, by occurrence_binding_candidates_witness, so
this row loses no coverage. It stops a known-structural red from being rediscovered
as a new one. Dissolution is the parser nesting change the witness note already
names, at which point the row deletes and the function re-enrolls.

exact_witness_admission_witness_test 18/18.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…delete-imports-on-main

# Conflicts:
#	.gitattributes
#	.github/workflows/ci.yml
… main merge

Both conflicted paths are generated. The merge driver marked them unmerged without
markers, which is its documented behaviour for generated artifacts, so neither side
was the answer: resolved to main's side to close the merge, then regenerated. The
derived delta is this branch's new emitted modules entering the generated-artifact
merge list and the heal exclusion list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lment owes

Enrolling parser_sibling_match_arm_pattern_binder_holds in
known_red_probe_v1_envelope_roster appends one RunnableDiscoveryBatch to the
floor schedule. It is not a RunnableScopedWitnessBatch, so it carries no scoped
clamp and falls to the positional table, which still had six rows for seven
batches — claim_executor refused fail-closed at arm time, exactly as
v1_claim_scoped_known_red_probe_batch_note's ENROLLMENT COUPLING clause says it
would.

Row: overhead 240s, per_unit_ms 1000 (the signed family witness coefficient, so
a second enrolled probe re-denominates rather than being absorbed). Basis,
carried in gunbc_ci_v1_envelope_known_red_probe_clamp_basis_note: 95.6s measured
local x86 wall for the single enrolled row, deliberately marked as NOT a fleet
observation, which is why the margin is ~2.5x rather than the family's usual
1.6x spread factor.

witness_scoped_batch_is_singleton_and_outside_positional_clamps green by
execution; no generated-artifact drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nt terms

Landing the clamp row got the walk past arm time, and the corpus then named
three real reds — all the same shape, and all mine: the conditionally-appended
known-red v1-envelope probe batch was visible to the schedule side of three
laws and invisible to their declared side.

- gunbc_ci_floor_enrolled_runnable_labels_for omitted the probe entirely, so
  witness_floor_schedule_preserves_enrolled_set saw a set difference. Both
  conditional batches now carry a term, mirroring the native arm.
- witness_live_floor_discovery_batch_count_is_four pinned four discovery
  batches; the probe is a fifth. Renamed to _is_four_plus_enrolled_probes with
  the conditional as a named term, so four still reads as the unconditional
  population and an enrollment stays visible rather than folded into a constant.
- witness_floor_appends_one_selected_native_batch counted schedule + scoped + 1;
  the probe entered its constant. It now has its own term, so a claim about the
  native lane cannot red for a reason belonging to the probe.

ci_floor_plan_witnesses (the 33-member conjunction root),
witness_floor_schedule_preserves_enrolled_set,
witness_floor_batch_clamp_params_cover_schedule,
witness_live_floor_discovery_batch_count_is_four_plus_enrolled_probes,
witness_floor_appends_one_selected_native_batch and
witness_scoped_batch_is_singleton_and_outside_positional_clamps all green by
execution; no generated-artifact drift.

The fourth failure in that run, arrow_body_form_eval_value_vertical_holds
("no field 'root' on type 'Node'"), is inherited main red from #8242 — it is the
sole failure on main's own runs 31824195524 and 31824260033 — and is being
repaired by #8269. Nothing here touches it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…producer

WORK IN PROGRESS on the terminal draft — the carrier and producer land here
with no consumer yet; the resolver and the two call-site cutovers follow in this
same commit series, and only then do the two standing widenings dissolve. This
PR is not independently mergeable by construction, so this is an in-flight state
rather than an unconsumed artifact being landed.

ReferencePath is what both spellings of a projection fold into. A type path is
one occurrence carrying the whole dotted text; an expression path is a base
reference plus one FieldOccurrence node per segment, the base reachable as the
segment's containment child. The producer decides the dotted case from the
authored text alone and otherwise walks down containment children.

Two shapes are deliberate. The head is a NAME plus a span, not an occurrence
identity the type spelling never allocated. And a segment carries its own span
where the spelling gives it one and the whole path's span where it does not — an
honestly coarse location, never a fabricated offset into a blob.

Every failure arm refuses: an unindexed occurrence, a branching projection (more
than one containment child is a shape this producer does not model, so it names
the count rather than picking a child), empty segment text, and an unbounded
descent are four distinct typed reasons, not one absorbing "no path".

Established by execution before the shape was chosen, since the resolver's
descent rests on it: a module member's containment parent IS the module
declaration occurrence, so one containment-child step is the uniform segment
rule and the qualifier suffix-match becomes removable rather than being
generalised. The probe that established it was temporary and is not committed;
Commit A's own controls pin it discriminatingly (bound head/bound member, bound
head/missing member, bound head/ambiguous member with exact identities).

Compiles clean: 0 blocking errors on the dag target.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…uisite

The frontier paragraph described the head-only widen as live — p binds and
admits its module file while the member never resolves. That has not been true
since the one-segment terminal-member resolution landed, and the row went stale
inside a day. Recorded as a correction rather than a rewrite, because reading a
superseded frontier as current sends the next reader to repair a fail-open that
is already closed. Same correction applied to the PR body.

What is actually open is restated as a SHAPE obligation, not a safety one: the
one-segment repair keeps two temporary representations beside each other — a
projection segment impersonating a FieldOccurrence (so binding compatibility was
widened at a field reference, held safe by caller behavior rather than by
construction) and a module head decided twice, once by the reference fold and
again by a segment-index reconstruction. Both dissolve into ReferencePath, which
consumes the accepted head binding rather than re-deriving it.

Also names a prerequisite the row previously omitted. The match-arm binder
known-red admission is not a resolution: the parser stamps the binder and the
arm body as siblings, and when the structural resolver becomes the ordinary
resolver ordinary local lexical references must still bind. Repair parser
containment, green the witness, delete the admission along with the conditional
batch and clamp row that exist only to carry it, keep the witness as a permanent
control — rather than accumulating more CI accounting around a parser defect.

No production node accepted; imports, grammar and AST all still present.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e first half owed

THE CI FAILURE FIRST, because it was mine and the cause is worth stating: dag/std
is a stage0 self-compile input, so the carrier commit needed the two-pass regen
and I ran only the generated-artifact gate. That gate does not cover stage0
staleness — regen_verify_gate_passes does — so "no artifact drift" was a true
statement about the wrong population. Seed regenerated, rebuilt, and verified:
regen_divergence_count=0.

Rebuilding also surfaced an emitter limitation rather than a modeling one: a
fold whose element parameter is discarded emits a by-value closure where the
iterator yields a reference (E0631). Replaced with length, which is the simpler
spelling anyway; no behavior changes.

THE RESOLVER. resolve_reference_path binds the head exactly once through the
same on-chain-plus-named-modules rule an ordinary bare reference uses — there is
no second segment-index reconstruction of the head beside it, because two
derivations of one fact are two chances to disagree silently. Every later
segment is a containment-child step, uniform for the first segment and the
tenth, which is what the measured module-member containment relation bought: the
qualifier suffix-match is removable rather than something to generalise.

The cardinality rule is identical at every step and special-cased nowhere: zero
children Unbound, one descend, more than one Ambiguous naming every candidate —
never a nearest match, never first-wins.

WHY THIS IS A WALL AND NOT A CHECK: the only value carrying a terminal
declaration is produced by consuming every segment, so a head that bound while a
segment did not has no representation as a resolved path. A caller cannot read a
provider file off a partial descent, because a partial descent yields a refusal
and refusals carry no declaration.

CONTROLS, green by execution on real parsed source (not hand-built values), each
a different decision so they are not one claim written three ways:
- head and terminal both bind -> resolves to the TERMINAL declaration, asserted
  by the terminal's own authored spelling rather than the module head's;
- head binds, segment names nothing -> refuses AT THE SEGMENT. This is the
  discriminating red for the head-only widen: under that widen this same input
  produced a ready dependency;
- head names nothing -> refuses at the HEAD, before any descent, so head and
  segment failures stay distinguishable rather than collapsing to one Unbound.
The resolved case is the positive control the refusals are read against —
without it, refusals are satisfiable by a resolver that refuses everything.

Whole file 24/24, denominator checked against the test fn roster.

STILL OWED before the widenings can be deleted, stated rather than implied: the
expression-spelling parity case, multi-segment a.b.c, ambiguous member and
ambiguous head, and the ordinary record x.field negative that must never consult
module members. The resolver handles them by construction; nothing has executed
them yet, so nothing here claims they pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…delete-imports-on-main

# Conflicts:
#	.gitattributes
#	.github/workflows/ci.yml
#	dag/gunbc/ci_layer_roots.dag
#	dag/gunbc/stage0_emit_plan_generated.dag
#	src/v1/stage0/src/lib.rs
#	src/v1/stage0/src/v1_compiler_parse.rs
…dinary field access

Three more controls, all green by execution on real parsed source, and one of
them forced a carrier change rather than merely passing.

THE CARRIER CHANGE. A dot is not always a namespace step. x.field on an ordinary
record binder is a field access: it induces no cross-file dependency of its own
(the dependency came from the reference to x's TYPE) and its member is a field of
a type, not a member of a module. Answering it with SegmentUnbound would report a
correct field access as an unresolved name — the fabricated-plausible-output
failure aimed at a well-formed program — and answering it with a resolved path
would attribute a dependency to whatever declaration happened to match the field
spelling. So the head's own category decides: only a NamespaceSegmentOccurrence
head descends, and any other head with segments returns
ReferencePathNotANamespaceProjection carrying the head declaration. That is
deliberately neither success nor error: there are three outcomes, so callers
handle three.

That control also executes the EXPRESSION spelling of the producer end to end —
the field-access occurrence walked down to its base, the base bound, and the
category gate answered — which is partial type/expression parity evidence rather
than an assertion that parity holds.

AMBIGUITY AT BOTH GRAINS, since one is not evidence for the other:
- two modules whose last segment is live_tree refuse at the HEAD, before any
  descent, naming both candidates;
- one module declaring the same member twice refuses AT THE SEGMENT, naming both.
The duplicate-member fixture is deliberately not a well-formed program: the
subject is what the resolver does when a segment has two candidates, and no
well-formed provider had to be corrupted to ask it.

The control closure takes its provider SET as a parameter because cardinality is
a property of the assembled population, not of the reference text — neither case
is expressible by editing the consumer.

Whole file 27/27 against the roster; seed regenerated and verified at
regen_divergence_count=0.

STILL OWED: multi-segment a.b.c, and full type/expression parity as its own
assertion rather than as a by-product.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd two real defects

The last commit under the incremental strategy, per the operator ruling: this
completes the in-flight carrier work, and no further production resolver or
loader architecture lands on this branch.

THE SPLIT RULE WAS WRONG, and it looked right against the corpus. The producer
split at EVERY dot and took the FIRST segment as the head, which happens to work
for the import-era spelling live_tree.LiveTreeDisposition -- one qualifier
segment, one member -- and fails for everything else. It is now the LONGEST
PREFIX naming a module: segments are consumed into the head while they still
name one, and the rest is descent.

That CORRECTS what I reported earlier. I said a fully qualified reference could
not be represented and that the qualification policy was therefore coupled to
the producer. It is not: module_named_by_qualifier already admits both an exact
path and a suffix, so one rule answers both spellings. Proven by execution:
v2.std.live_tree.LiveTreeDisposition resolves to the same terminal declaration
as live_tree.LiveTreeDisposition. Full qualification needs no producer work.

MULTI-SEGMENT descent is real rather than synthetic. Modules are flat per file,
so a.b.c cannot be module-inside-module -- but a module member can have members,
so live_tree.LiveTreeDisposition.ReadsLiveTree is module, then type, then
variant: three segments, two containment steps, exact terminal.

TWO DEFECTS FOUND BY EXECUTION, neither in the semantics.

(1) A pathological rustfmt blowup that would have wedged CI's regen job. Seed
regeneration formats the whole stage0 crate, so ONE emitted file can stop it: my
fixture used a ~20-term string `+` chain and its emitted file did not format in
600 seconds, against 0.19s for the previous revision. Measured rather than
guessed -- I first flattened my own module on suspicion, which changed nothing
(0.166s either way), then bisected file by file. Rewritten as a single literal:
0.086s. The rule for emitted code is long binary-operator chains are superlinear
in rustfmt; prefer one literal.

(2) The known-red probe batch redded CI with FLOOR-BATCH-CLAMP-REFUSED. Its
SelectionApplied resolves selection machinery from the BATCH's roots, which are
deliberately dag plus src/v1, so affected_set_floor_runner had no provenance and
the run refused fail-closed -- the machinery correctly reporting it is outside
every root. The sibling scoped batch escapes this only by being a
RunnableScopedWitnessBatch with InheritedWalkSourceRoots; a plain discovery batch
has no such authority. Flipped to SelectionOff, which costs nothing: the roster
is a handful of explicit expect-red entries, so there is no population to skip.
Widening the roots was rejected -- it moves the subject envelope and invalidates
the measured clamp basis.

Bridge witness 29/29 against its roster; floor-plan conjunction green; seed
verified at regen_divergence_count=0; no generated-artifact drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

Closing: this branch was a throwaway measurement probe, not a merge candidate.

It was created to answer one question by execution — what breaks if every import is deleted right now — by stripping all 19,173 import statements across 2,955 files and compiling. That measurement is done and its result is recorded (1,142 hard diagnostics, 39 distinct unresolved names, 0 of them declared in the same file as their reference — so the corpus is underspecified rather than the resolver being broken). The strip itself was reverted locally and never pushed.

What remains on the branch is a duplicate of the frozen quarry head d35b4fec309, which is preserved on backup/8115-import-deletion-quarry and described on #8115. Nothing here is unique, so there is nothing to lose by closing, and leaving it open generates recurring merge-conflict noise against a branch nobody intends to merge.

Per DESIGN §5, branch-local experimentation is fully open and its implementation is deleted before merge — the durable output is the measurement, not the branch.

— sent from calm-ram-435

@gunbai-bot gunbai-bot Bot closed this Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants