Skip to content

Infer a bare variant's instantiated owner from the expected type, not its declaration node - #8376

Merged
briansrls merged 7 commits into
mainfrom
fix/infer-bare-imported-variant-owner
Aug 17, 2026
Merged

briansrls merged 7 commits into
mainfrom
fix/infer-bare-imported-variant-owner

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

What

A bare variant reference inferred as its owner coproduct's declaration node, whose children are the coproduct's arms rather than type arguments. type Optional<T> = Absent | Present { value: T } declares Absent as child 0, so a generic owner reaching the renderer uninstantiated rendered Option<Absent> — a constructor in type position, not a Rust type.

The emitter was rendering faithfully what inference handed it. The fix is in v1.compiler.infer: when the context supplies an expected type that owns the variant, that expected node is the instantiated owner, so it is adopted in place of the declaration node. The same lookup already existed in the unresolved-name arm; it is hoisted to one authority so a variant that resolves gets it too, rather than only one that does not.

Measured — emitted v2.compiler.05_eval closure, before → after

before after
Option<Absent> sites 21 0
rustc errors 494 473
per-code delta — −19 E0425, −2 E0573; no code increased
self-host fixed point — green (two generations)

Whole-artifact diff is 24/24 lines, fully accounted: 21 broken annotations repaired, 2 improved to concrete types (Option<Rc<UriPercentEncodeRefusalCause>>, Rc<NamedEdgeTargetLookup> — a bare Absent of a user coproduct, same class), 1 Verdict annotation relaxed to _.

Baseline sites: 17 v2_compiler_body_lowering_fold, 2 v2_compiler_infer, 2 v2_std_cardinality.

Adoption is narrow on two axes, both earned by a failure

Identity — only when the expected type's own head names the coproduct resolution already bound, so this instantiates the owner the binder chose rather than re-targeting to a different coproduct declaring an arm of the same name (Absent is an arm of Optional, ObservationVerdict and ConvergeVerdict alike).

Cardinality — only when the expected node is Required. Optionality is not a wrapper type here: BinOp? is a BinOp node whose return_cardinality is CardOptional, so a head comparison cannot see it. For binop: Mod against BinOp? the head is BinOp and the arm is found, but the constructor's type is BinOp and the optionality is a coercion the emitter performs by wrapping. Adopting there made the value look already-optional and emitted binop: BinOp::Mod where Some(BinOp::Mod) is owed.

Why that regression survived every instrument but one

Two successive drafts shipped the wrapper loss across four extdeps.languages.*_syntax modules and std.effects. The Option<Absent> count, the whole-artifact diff, and the rustc census all stayed green through both — because all three measure the eval closure, and the damage was in v1's own emitted sources. regen_stage0 --verify was the only instrument whose subject included it. A population that cannot disagree cannot falsify.

Witness

Enrolls the cardinality half, which is single-source constructible and is the half both wrong drafts broke, with a required-field positive control so the repair is not satisfiable by wrapping everything or by refusing every adoption. Both rows green by execution; both assertions verified against real emitted bytes first.

The headline Option<Absent> case has no enrolled row, and the note says so rather than leaving it to be discovered: it needs the v2 Optional coproduct as a declared generic type and an ambiguously owned arm name, but Optional is not declared anywhere in the dag corpus (it is the built-in T? sugar), and two same-named arms in one module are refused outright. Its evidence is the executed corpus measurement above — discriminating, but not recurring.

Rung, honestly

Mitigatable, not structural. With no expected type, inference still falls back to the declaration node, so a declaration-shaped node can still reach a type position — the bad state remains writable.

Dissolve-on: constructor ownership and constructor value type become separate facts with a needs-context / refused arm, at which point a declaration node has no route into a type position and this guard is replaced by unwritability.

🤖 Generated with Claude Code

… its declaration node

A bare variant reference inferred as its OWNER COPRODUCT'S DECLARATION NODE, whose
children are the coproduct's ARMS rather than type arguments. `type Optional<T> =
Absent | Present { value: T }` declares Absent as child 0, so a generic owner reaching
the renderer uninstantiated rendered `Option<Absent>` -- a constructor in type
position, not a Rust type. The emitter was rendering faithfully what inference gave it.

When the context supplies an expected type that owns the variant, that expected node IS
the instantiated owner, so it is adopted in place of the declaration node. The same
lookup already existed in the unresolved-name arm; it is hoisted to one authority so a
variant that RESOLVES gets it too, rather than only one that does not.

Adoption is deliberately narrow on two axes, both earned by a failure:

IDENTITY -- only when the expected type's own head names the coproduct resolution
already bound, so this instantiates the owner the binder chose rather than re-targeting
to a different coproduct declaring an arm of the same name (Absent is an arm of
Optional, ObservationVerdict and ConvergeVerdict alike).

CARDINALITY -- only when the expected node is Required. Optionality is not a wrapper
type here: `BinOp?` is a BinOp node whose return_cardinality is CardOptional, so a head
comparison cannot see it. For `binop: Mod` against `BinOp?` the head IS BinOp and the
arm IS found, but the constructor's type is BinOp and the optionality is a coercion the
emitter performs by wrapping; adopting there made the value look already-optional and
emitted `binop: BinOp::Mod` where `Some(BinOp::Mod)` is owed.

Two successive drafts shipped that wrapper loss across four extdeps.languages.*_syntax
modules and std.effects. Worth recording WHY it survived: the Option<Absent> count, the
whole-artifact diff of the emitted eval closure, and the rustc error census all stayed
green through both, because all three measure the eval closure while the damage was in
v1's OWN emitted sources. regen_stage0 --verify was the only instrument whose subject
included it.

Measured, emitted v2.compiler.05_eval closure, before -> after:

  Option<Absent> sites        21 -> 0
  rustc errors               494 -> 473  (-19 E0425, -2 E0573; no code increased)
  whole-artifact diff        24/24 lines, fully accounted:
                             21 broken annotations repaired,
                             2 improved to concrete types
                               (Option<Rc<UriPercentEncodeRefusalCause>>,
                                Rc<NamedEdgeTargetLookup>),
                             1 Verdict annotation relaxed to `_`
  self-host fixed point      green (two generations)

Witness enrolls the CARDINALITY half, which is single-source constructible and is the
half both wrong drafts broke, with a required-field positive control so the repair is
not satisfiable by wrapping everything or by refusing every adoption. The headline
Option<Absent> case has no enrolled row and the note says so: it needs the v2 Optional
coproduct as a declared generic type AND an ambiguously owned arm name, but Optional is
not declared in the dag corpus (it is the built-in `T?` sugar) and two same-named arms
in one module are refused outright. Its evidence is the executed corpus measurement
above -- discriminating, but not recurring.

Rung, honestly: mitigatable, not structural. With no expected type inference still
falls back to the declaration node, so a declaration-shaped node can still reach a type
position. Dissolve-on: constructor ownership and constructor value type become separate
facts with a needs-context / refused arm, at which point the state is unwritable.

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

gunbai-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

One correction to the record, since acting on it would red the seed.

The review reads the extdeps_uri.rs closure-parameter annotation as "unrelated ... trivially benign". It is neither unrelated nor optional: it is this change's own output, and it is load-bearing.

- |acc: _, outcome: Rc<UriPercentEncodeScalarOutcome>|
+ |acc: Option<Rc<UriPercentEncodeRefusalCause>>, outcome: ...|

That is the instantiation firing on v1's own emitted sources — the fold init is a bare variant whose owner is now instantiated from the expected type instead of left as a declaration node. Because regen_stage0 re-emits the seed, the committed stage0 must already contain what the new compiler emits. Dropping this hunk as churn makes regen_stage0 --verify red and the self-host fixed point unreachable; it took a second bootstrap generation to fold in, and the gen-2 verify is what turned green.

Worth flagging because it generalises: on a compiler change, a diff hunk in src/v1/stage0/src/** that looks incidental is usually the fix's output rather than noise, and the fixed point is the thing that decides.

Related, and the reason I am confident about it: the fixed point caught this repair being wrong twice while every other instrument stayed green. Both earlier drafts dropped Some(...) on optional fields across five v1 modules. The Option<Absent> count, the whole-artifact diff, and the rustc census (494 → 473, no code up) all measure the emitted eval closure — which does not contain v1's own sources, so none of them could have disagreed.

No change requested; the review's verdict stands. Recording it so the hunk is not tidied away later.

— sent from gentle-dove-833

gunbai-bot Bot and others added 2 commits August 17, 2026 05:05
…ly served it (#8344)

* Delete the 27-row self-host frontier roster and the machinery that only served it

Operator ruling: the frontier roster is either derived from live state or it
does not exist. It was neither -- 27 hand-authored rows whose measurement
fields were ordinary parameters, so a row that claimed execution measurement
was indistinguishable from one that asserted it. It attracted readers, it
misrouted them, and it produced nothing. This deletes it at the root rather
than pruning it row by row.

WHAT THE DELETION PROVED, which is the part worth reading. Every consumer that
looked load-bearing turned out to consume an empty projection:

  crate layout        appended compiler_frontier_emitter_produced_stage0_mod_basenames(),
                      a fold admitting only EmitterProduced rows. All 27 were
                      SeedRetained, so the term was [] at every call and no
                      emitted byte changes.
  wet enrollment      appended a roster fold whose own gate,
                      falsifier_self_host_wet_template_only_holds, asserted that
                      term had length zero.
  crate partition     named the roster as a dissolution target for module
                      exports the roster could not have supplied -- rows carried
                      a path and a disposition and nothing else.

So the roster's real dependents were its own census tests. Those, the promotion
and authorization machinery, and the per-module probe survey go with it.

WHAT SURVIVES AND WHERE IT WENT. Crate layout is a separate concept that was
squatting in the frontier module; it moves to v2.compiler.self_host.stage0_crate_layout
with the registration rows and projections that actually produced bytes. The
producer/qualification vocabulary in emitter_producer_provenance survives at
half its size -- the EmitterProducer x EmissionQualification axes, the realized
closure carriers, the receipt and its single mint -- because its live consumers
(candidate_generation, the direct rust door contract, the production
qualification lens) never read a roster row.

EVIDENCE, PER DESIGN 4b. Deleted witnesses fall in two classes and both are
stated in the carriers rather than here. Green-by-construction: seven crate
layout witnesses guarded by a disposition match whose EmitterProduced arm was
unreachable, or comparing zero to zero. Subject-retired: the authorization
forgery controls proved an unauthorized EmitterProduced disposition was
unwritable, and that disposition no longer has a representation to forge.

Two claims survived and keep their controls: a V2Emitter receipt cannot be
minted with the seed emitter in its realized closure, and the generated crate
layout artifact must match its registration rows.

ONE CLAIM IS GENUINELY RETIRED RATHER THAN CLIMBED, said plainly.
transition_parity_host_index_agrees_with_hand_rows redded when the derived
module index and the hand-authored qualified-module rows disagreed. Deleting
one side of a duality retires the parity claim; it does not discharge it. The
surviving authority is the path-module binding index, and the crate-partition
dissolve target now points there instead of at the roster.

The frontier_probe_survey bin is removed with its .dag surface and transport.
Justified per bin, not by a class sentence, because an earlier change deleted
four bins on one blanket justification and was reverted for exactly that --
see the second-removal paragraph in gunbc.ci_release_bins.

NOT DONE HERE, named rather than left to be found: the frontier_probe subsystem
(frontier_probe, frontier_probe_types, assemble_detail and their tests) still
stands. It is the adjacent weed -- its subject was per-module roster
measurement -- but it has independent test consumers and deserves its own cut.
The plan carriers written in the roster's vocabulary (wave2_prep_design,
v2_self_hosting) are quarry now, not authority, and are not rewritten here.

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

* Follow the bin deletion through build.rs and the generated workflow

The build failed in 39s because deleting src/bin/frontier_probe_survey.rs left
two references to it standing: build.rs still declared a rerun-if-changed on
the file, and .github/workflows/ci.yml still passed --bin frontier_probe_survey
to the release build and then asserted the resulting artifact existed at four
verify sites and one tar site.

ci.yml is a generated artifact and the honest way to update it is to regenerate
it from gunbc.ci_release_bins, which is where the pack row was already removed.
`gunbc ci` cannot run in this container -- the shim panics reading
ci_layer_roots.dag out of a temp workspace -- so the eight occurrences are
removed by hand and the drift gate is the check: it compares ci.yml against
`gunbc ci` output byte for byte, so if this hand edit is not exactly what the
generator produces, CI says so. That is the intended instrument for this rather
than a local claim I cannot make.

Stated because it is the kind of thing worth noticing rather than routing
around: a hand-edited generated artifact is only acceptable here because a
byte-exact gate independently checks it in the same run.

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

* Fix the parse error my own note introduced: nested double quotes in a .dag string

The retained-kernel test gate failed with four panics, all the same:
for_each_parsed_module_binding could not parse dag/gunbc/ci_release_bins.dag.
The per-bin justification I added there quoted the earlier blanket sentence
verbatim -- not "a probe bin whose subject was cli_run" -- inside a
double-quoted string, which terminated the string mid-prose and left the
remainder as garbage tokens.

Worth noticing rather than just fixing. The failure surfaced four modules away
from its cause, in a materialization-provider test whose subject is disk cache
probes, because the whole-corpus module-path index parses every .dag file
before anything else runs. So a prose edit in a bin roster reds a cache test,
and the panic names the parse site rather than the reader. That is the same
ordering property this program spent the afternoon on: what breaks first is
not what is wrong, it is what runs first.

The sentence now paraphrases instead of quoting. Checked the other nine
multi-paragraph notes this change adds: each opens and closes exactly once,
with the odd per-line quote counts expected of a string spanning blank lines.

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

* Two workflows still named the deleted probe bin: take the heal regeneration

The frontier cut deleted the frontier_probe_survey bin and its row in the
release pack, and I removed its eight occurrences from ci.yml by hand.
fleet-converge.yml and falsifier.yml build the same pack from the same
authority and still named it, so heal regenerated them and could not push
workflow paths itself. These are its bytes, not mine.

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

* Move the deletion rationale into an authored annotation: it is prose, not program data

DESIGN 4c, enforced by prose_row_introduction_gate: this path's prose is
already migrated, so a new data NAME_note: String row is refused. The note
explains what the frontier cut removed from this module and what survived,
which is exactly irreducible rationale about a construction's shape --
it belongs above the declaration it explains, not in a String the compiler
must carry. Six sibling rows on out-of-scope paths were reported and not
enforced; left as they are rather than widening the diff past the refusal.

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

* Delete the freeze rows and transport claims naming witnesses the cut removed

The floor refused with StaleFrozenPathDeferral: two gunbc.witness_deferral_freeze
rows named test decls the entry no longer declares. A freeze row left behind is
not inert -- it becomes a standing exemption the next witness to take that name
inherits, which is the refusal's own wording and the reason it is loud.

Same two names were still enrolled as ClaimRun rows in the supply transport, and
its note explained why one of them was there. Removed both and rewrote the note
to say what happened to their subjects: the parity witness had the duality
between the derived index and the hand rows as its subject, and the cut removed
one side, so the claim is retired rather than dropped.

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

* Import the equality helper from its definer: the old path resolved by pool coincidence

emitter_producer_provenance_witness_test imported
schedule_witness_entries_structurally_equal from wet_receipt_enrollment, which
neither defines it nor imports it. It is defined in
seed_emitter_behavioral_wet_known_red_entries, a module this file already
imports. The reference resolved on main only because some other member of the
assembled closure had dragged the definer into the pool; the cut changed the
closure and it stopped resolving, refusing the whole discovery corpus.

This is the Class B accidental-coverage shape DESIGN records as an open thread —
a bare cross-module reference binding by pool membership rather than by the
importing file's own closure. Importing from the definer is the fix that does
not depend on who else is in the pool.

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

* Retire the frontier probe receipts with the roster they measured

The cut deleted the two HandAuthoredDocBind rows for the frontier probe
receipts but left the documents in the tree, so three files lost their only
inbound reachability and became orphans. Deleting the bind rows was correct
— every declaration they cite (frontier_probe_exact_head_closeout_note,
frontier_probe_run_interruption_note, frontier_roster_provenance_constructor_inflation_note,
compiler_frontier_receipt_totality_wall_holds) is deleted by this PR, so the
binds would otherwise dangle. What was missing is the other half: a document
whose entire subject is retired is retired with it, never left standing.

Deleted with the rows:
  docs/probes/frontier_probe_exact_head_survey_2026-08-03.{md,tsv}
  docs/probes/frontier_probe_run_interrupted_2026-08-06.md
  docs/probes/diff_frontier_declared_vs_survey.sh   (only input was the TSV above)
  docs/probes/run_frontier_probe_survey_per_module.sh

The two scripts are frontier-probe transport and served nothing else. Two
unrelated STR-RC-0 scaffolds cited the second one as a class exemplar; their
comments now state the class without the pointer, so the deletion leaves no
stale citation behind.

This is the branch's own doc-graph defect, distinct from the five inherited
rows main is currently red on (the root_b probe orphan, fixed by #8333).
Same lens, same five row names, two different causes — which is why "those
are the known inherited rows" had to be checked rather than assumed.

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

* Cut cleanup: the citations the deletion left pointing at nothing

cursor review 52817 on #8344 named six stale citations to symbols and files
this cut deletes. All six verified against the tree and fixed per site — a
mechanical substitution across them produces sentences that parse and say
nothing, which I did once already tonight and am not repeating.

  stage0_crate_layout_emit.dag        the generated-artifact template still
    embedded 'Authority: v2.compiler.self_host.frontier' in both the .dag and
    .rs emit paths after the import was repointed, so the next regen would
    bake a deleted module name into two generated artifacts. Now names the
    real authority, v2.compiler.self_host.stage0_crate_layout.
  ci_layer_roots.dag  witness_admission_offline_note   dropped the deleted
    exclusion note from the paired list.
  ci_layer_roots.dag  long_lane_exclusion_note         dropped the 'Named
    consumer with file-grain exclusion row' sentence; both things it named
    are gone.
  ci_layer_roots.dag  falsifier_self_host_wet_note     the frontier-row flip
    no longer gates wet enrollment; says so, and points at the note recording
    that the term it fed was already empty.
  ci_release_bins_witness_test.dag                     note claimed two
    positive gap bins while the tests assert one.
  witness_deferral_freeze.dag                          the terminating-survey
    specimen is rewritten rather than removed: it did terminate, but by its
    subject being deleted rather than by reaching 27 of 27 receipts, and that
    is the disposition the freeze cannot express.

Two more of the same class the review did not reach, found by sweeping every
deleted symbol rather than fixing the six named:

  doc_graph_roots.dag   the bind for this cut's own coordination document had
    primary_work pointing at v2.compiler.self_host.frontier_band_a_emit_readiness,
    a module this PR deletes. Repointed to the door-contract note already in
    its additional_works, which is the closest surviving subject.
  discrete_cost_derivation (.dag + .md)  cited the deleted exclusion note as
    live evidence for the 'green CI implies coverage' claim. The claim stands;
    the specimen is now stated as one that has since been deleted with its
    subject, which is an instance of the pattern rather than a loss of it.

Census after: every decl_name bound in doc_graph_roots resolves in the tree.
An earlier run of that census reported two missing — both were my grep
omitting 'func' declarations, not defects.

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

* Cut cleanup 2: the deleted-MODULE citations the symbol sweep missed

cursor review 52826. Two of its four findings were already fixed in 5921f90
(the emit template, the release-bins note); it ran on the older head. The
other two are real, and they show the first sweep was keyed on the wrong
thing: I swept deleted SYMBOLS, and a DeclarationRef names a MODULE PATH
independently of whether the symbol it cites still exists somewhere.

Sweeping module paths instead found three sites, one more than the review.

  stage0_rust_source_lifecycle_scaffold.dag
    hand_maintained_stage0_filenames_authority_ref still named
    v2.compiler.self_host.frontier while the import three hundred lines above
    had been repointed to stage0_crate_layout. Repointed.

  dissolution_migration_census.dag
    a NarrativeOnly residue row for a note in the deleted probe-survey module.
    Removed; the partition witness asserts sum-of-parts against the total
    rather than a literal count, so it stays true by construction.

  self_host_promotion_obligations.dag
    RE-HOMED rather than retired, and the distinction is the point. Both refs
    named the deleted frontier module, which made the bound trigger one that
    can NEVER fire — DeclarationAppears waiting on a module nobody will
    re-create is a permanently pending obligation wearing a checkable trigger,
    which is worse than an honest unbound one.

    The obligation itself survives the roster because it was never about the
    roster: the hazard is that a self-host promotion can be granted on evidence
    the v1 seed produced, and EmitterProducer still carries a V1SeedEmitter arm
    in v2.compiler.self_host.emitter_producer_provenance. So the subject moves
    there, the trigger keeps its declaration name and moves with it, and the
    'n/27' framing — which really was roster-specific — is gone from the claim.
    Its witness needs no change: it asserts the bound and unbound arms stay
    distinguishable, and both still are.

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

* Repoint two live references to the deleted frontier module

cursor review 52843. Both name it as a LIVE authority, unlike the several
prose sites that correctly say 'was carved out of' or 'was deleted with' —
those stay, because a historical reference to a deleted thing is accurate.

  language_target_registry.dag  Rust's TargetGovernedByContracts named the
    deleted module as its governing contract id. Repointed to
    v2.compiler.self_host.emit_coverage_frontier. Note that the row's own
    governing_contract_id_resolution_note explains this field carries a module
    name only because the real strategy authority lives in a private overlay
    and cannot be resolved from here — so the field is a stand-in either way,
    but a stand-in naming something that exists is strictly better than one
    naming something deleted.

  stage0_emit_plan.dag  the disposition-authority note said the projection
    derives from the deleted roster's seed-retained facts. It derives from
    stage0_crate_layout's hand-maintained filename facts, which is where the
    carve-out put them.

Deliberately NOT changed: cli_run.rs uses the string as a synthetic submodule
name in a prefix-matching unit test. It tests string logic and never resolves
the module, so it is not a stale citation — any name would do.

My earlier sweeps missed both because they keyed on 'module_path:' fields and
import lines. These are a contract_ids list element and prose. Third sweep
form, and the one that should have been first: grep the bare module name in
any syntactic position, then decide per site whether the reference is live or
historical.

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

* Regenerate the artifacts the authority-string change restamps

cursor review 52843's first finding, and the one with teeth: I changed the
emit template's authority string without regenerating what it stamps, so the
committed artifacts and the template disagreed and generated_artifact_drift_gate
would have refused. Ran the real generator rather than hand-editing generated
files:

  gunbc run --entry dag/tools/generated_artifact_gate.dag --function main_wet

It rewrote two artifacts with the new authority — and .github/workflows/ci.yml,
which is the part worth recording. The merge earlier took the ours side of
ci.yml purely to obtain a tree that builds, on the stated plan that regen would
overwrite it by construction. It did: the generator produced MAIN's
AUTHORED_CONFLICTS line, not mine. So the hand-picked side was wrong and would
have shipped a workflow that silently dropped main's change. That is exactly
the failure the generated-artifact merge driver refuses in order to prevent,
and the reason the rule is regenerate rather than resolve.

Still owed and dispatched separately: gunbc_stage0_crate_layout_generated.rs
carries the same string but is emitted by regen_stage0 rather than by this
gate, so it needs the stage0 regeneration, not this one.

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

* Regenerate the last artifact carrying the old authority string

gunbc_stage0_crate_layout_generated.rs is the emitted Rust OF the .dag row
this PR changed, so it is owned by regen_stage0 rather than by the
generated_artifact_gate that produced the previous commit. Ran regen_stage0:
144 files written, exactly one changed, and it is this one. That single-file
result is the receipt that the earlier gate run had already covered everything
else it owns.

Both regeneration paths now agree with the template, so the drift gate and the
regen job have nothing left to refuse on this string.

Instrument note, recorded because it is the fourth of its kind tonight: the
subject-presence greps I printed beside this run were mangled by nested
quoting — the pattern's second word was taken as a filename, so both reported
0 with a 'No such file' line above them. The numbers were garbage. The run is
still trustworthy, but on the strength of what regen CHANGED, not on the checks
I attached to it. A broken check that prints a plausible 0 is worse than no
check, and this is the same family as reading a truncated tail: absence
manufactured by the instrument, read as a measurement.

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

* Repoint to the right authority, and clear two more dissolution triggers

cursor review 52854, both real.

MY REPOINT WAS WRONG AND THE REASON IS THE ERROR I HAVE BEEN WARNING OTHERS
ABOUT ALL NIGHT. I moved Rust's TargetGovernedByContracts to
v2.compiler.self_host.emit_coverage_frontier because the name resembled the
deleted module. It does not answer the same question: that carrier's own
two-axis note says it answers axis (a), whether a witness body runs native,
and explicitly disclaims axis (b), module self-emission. Picking it on name
resemblance is precisely 18.2's finding — a name says the carrier was
involved, not how — applied by me while citing it to someone else.

Repointed to v2.compiler.self_host.emitter_producer_provenance, the surviving
producer authority, which is also where the promotion obligation re-homed
earlier in this PR. And the field's own note is updated: its stated purpose is
a resolvable module a reader can open, so the note naming the deleted one made
it false on its face while the field beside it had already moved. The note now
records the wrong first choice rather than quietly showing the second — a
reader deciding whether this field means anything is better served by knowing
it was mis-picked once.

seed_emitter_behavioral_wet_module_bindings carried two dissolution triggers
whose subjects the cut removed: authorization waiting on 'witness-realization
+ frontier_probe_survey', and 'Frontier rows stay SeedRetained'. Both rewritten
to name what actually remains — witness-realization alone, and the binding's
own absence. A trigger naming a deleted subject cannot fire, which makes it
strictly worse than honest prose.

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

* Roadmap paths, and the plan carriers the cut decayed

claude review 52858. The first finding is the serious one: STRUCTURED fields,
not prose.

THREE LIVE ROADMAP NODES pointed path: at the deleted file — v2-emitter-
producer-provenance, v2-emitter-first-behavioral-module, v2-emitter-native-
bootstrap, all active in gunbc.roadmap_authority. Repointed PER NODE from what
each node says it does, not uniformly:

  producer-provenance    -> emitter_producer_provenance.dag
                           (its handback wants a result refused without a
                           receipt from the run that made it — that module's
                           mint wall)
  first-behavioral-module-> wet_receipt_enrollment.dag
                           (handback is test results plus the updated list
                           entry; the list entry was a roster row, and wet
                           enrollment is what survived carrying per-module
                           receipts)
  native-bootstrap       -> v2_emitter_direct_rust_door_contract.dag
                           (handback is two builds and the old compiler taken
                           away)

Picking one target for all three would have repeated the mistake in the commit
before this one, where I chose emit_coverage_frontier because the name
resembled the deleted module. Swept afterward: every path: in the roadmap now
resolves on disk, the sole non-file being a dotted module reference.

WAVE2_PREP_DESIGN IS MARKED SUPERSEDED RATHER THAN REPOINTED, and that is a
disposition rather than a shortcut. Its entire mechanism section names the
roster, its count, its baseline, its census attribution, its sweep order and
its gate — all deleted. There is no mapping from a roster row to a replacement,
so repointing eight symbols would fabricate an authority the document never
had. DESIGN §3 rules that a plan authored while the deleted structure lived is
re-read as quarry: its findings may be evidence, its sequencing is the old
structure's gravity written down. The header now says so and tells the reader
every symbol below names something gone.

The other two plan carriers had specific enough claims to repoint per site.

Residual live citations of the deleted module across all .dag: zero, counting
only live references — the several prose sites that say 'was carved out of' or
'was deleted with' stay, because a historical reference to a deleted thing is
accurate and sweeping those would be the mechanical substitution this PR has
already had to undo once.

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

* The self-hosting tracker outlived its roster: declare the gap, do not refill it

review 52867 caught v2_self_hosting.dag still presenting the deleted
frontier.dag as the live roster carrier -- seven citations across the
status line, the state-of-play census, two Wave 1 residual lists, the
Wave 3 exit gate and the retirement condition. The claude review on the
same head credited this file with quarry annotation it does not have,
so the finding is real and the approval was wrong about it.

Quarry-bannering it would be the wrong repair. wave2_prep_design earned
that treatment because its whole mechanism section named deleted roster
symbols with no surviving mapping; this file is the live sequencer for a
program whose Wave 1 is still in flight. It is not superseded by the cut
-- it merely cited what the cut removed.

So: repoint to the surviving authorities, and where a claim was
denominated by the roster, retire the fraction rather than re-author it.
0/27 and 27/27 both came from the same hand-authored row list, so both
measured the roster's opinion of how many compiler modules exist. The
substance survives (no module is self-emitted with a green equivalence
receipt; every module must flip or be declared pinned-kernel) and is
now stated qualitatively, with the derived denominator named as the
first Wave 3 obligation. Restoring a count here would rebuild the weed
inside the replacement, which is the failure DESIGN section 3 warns
about by name.

The frontier probe residuals are marked retired rather than outstanding:
the survey never produced a receipt on any host including a dedicated
runner, so its deletion costs machinery, not evidence.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: gunbc-ci-auto-heal <bts53@scarletmail.rutgers.edu>
…easured (#8367)

* Plan status: Root A landed, Root D half 1 has no live owner, Root B measured

The status table said Root A was 'unknown; under challenge' after it merged,
and named vivid-wren as owner of a Root D whose halves have different owners
and different states. A shared coordination document that is stale about what
landed sends the next reader at finished work — which happened to me tonight:
I dispatched a duplicate work item for Root D half 1 off this table before
finding PR #8350 already at the merge bar with its author's session archived.

  A   LANDED, #8347 as c4e9cc9. 12.5's challenge was RIGHT, which is why
      what landed is not what 12.5 proposed: a fused-map split, not the
      coproduct-skip edit the retracted diagnosis pointed at. The skip is
      still there and still deliberate. Recorded with two receipts — the
      stage0 regeneration the first push missed, and the two regen runs that
      reported a perfect fixed point while compiling a tree that never
      contained the fix.
  D   split by owner: half 2 landed; half 1 is #8350, at the merge bar, no
      live owner, shepherded here.
  B   18.3 adds smart-ibex's executed flip: 211->204 errors, 203->196 sites,
      195 common / 8 removed / 1 added, containment 0-of-18 disjoint. The
      43-site renderer-inconsistency reading and the +18 reassignment are
      RETRACTED — read off a different module's artifact, never reached a
      carrier. What survives is sharper than the retraction: the leaf name
      Nat denotes two different types under two module authorities, so a
      census pooled across modules is keyed on a name that is not unique over
      the population it pools. Key on resolved type identity, not the printed
      name, and let a site carry more than one root.

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

* Root D half 1 landed too — bbb5213, minutes after the row was written

The row said 'at the merge bar, no live owner, shepherded here'. It merged
while this PR was open. Recording it rather than letting a status document
be stale about a merge inside its own review window — which is the failure
the PR exists to fix, and it would be a poor look for the fix to exhibit it.

Its author's session was archived before it landed, so the merge is also the
receipt that an ownerless PR at the bar does not need a new session, only
someone watching for it.

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

* Strike the superseded instruction, and give half 2 its receipt

cursor review 52846, both findings real.

12.5 said 'treat A as unpartitioned, not as diagnosed' immediately above the
RESOLVED block this PR added, so the section told a skimming reader that
landed work was still open. I appended a resolution without striking the claim
it supersedes, which is the plan-ledger form of rung inflation — and doing it
in the very PR whose purpose is status honesty. Struck rather than deleted:
the instruction was correct when written, and the paragraph above it records
WHY the then-current diagnosis was refused, which still governs.

The Root D row claimed both halves landed while citing only half 1. Half 2 is
7cfeb6f (PR #8341), verified an ancestor of origin/main before citing it
rather than copied from the review. Receipt added.

Swept my own additions for the same class afterward: every LANDED claim in
this diff now names a merge commit, and all four SHAs this document cites
(bbb5213, 7cfeb6f, c4e9cc9, 6a3779d) are confirmed ancestors of
origin/main.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Recording two adjacent findings raised in review-adjacent analysis, with dispositions, so neither is silently absent.

1. The witness does not cover the generic-owner repair. Correct, and declared rather than discovered: the witness note says so explicitly and gives the reason. The headline Option<Absent> case needs the v2 Optional coproduct as a declared generic type AND an ambiguously-owned arm name, but Optional is not declared anywhere in the dag corpus (it is the built-in T? sugar), and two same-named arms inside one module are refused outright by the compiler — so compile_dag_rust_emit_check, which compiles ONE synthetic source, cannot construct the subject. Its evidence is the executed corpus before/after (21 -> 0 sites, 494 -> 473 rustc errors, no code increasing), which is discriminating but not recurring. The enrolled half is the cardinality half. That asymmetry is the honest coverage statement, not an oversight.

2. The expected-context-only arm bypasses the new guard. Also accurate as a description. Where the name misses locals, func sigs and global bindings, the arm adopts the PEELED exp_enum and derives parent_enum from it, with no Required check. Two facts decide what to do about it:

It is pre-existing. This diff only extracted the inline lookup into expected_variant_owner_instantiation; that arm's semantics are unchanged, byte for byte. It is not a regression introduced here.

Applying the guard there would be wrong. At levels 1 and 3 the guard falls back to a real binding, so refusing adoption is free. At this arm there is NO binding — the expected type is the only source of truth, and the failure path is ambiguous_reference_refusal or a hard not-found. Adding a Required condition would convert currently-resolving programs into refusals. A fabricated refusal is forbidden by the same section as a fabricated success, so the symmetric-looking fix is the unsafe one.

The correct repair shape, if a live occurrence is found, is therefore NOT to refuse but to preserve cardinality — preserve_outer_optional_cardinality(outer: exp, inner: exp_enum) already exists in v1.compiler.core for exactly this — which keeps resolution AND keeps the wrapper.

I am not making that change in this PR, deliberately. I have no observed live occurrence: the self-host fixed point is green over two generations and whole-corpus emission differs only in the 24 lines already accounted for, so the arm is not firing destructively on any current site. Changing a resolution-critical arm on a hazard I cannot exhibit, with no discriminating control, is speculative hardening of precisely the kind that cost two wrong drafts here already — both of which were green on every instrument except the fixed point. It belongs in a follow-up carrying its own witness, and it is recorded here rather than left to be rediscovered.

— sent from gentle-dove-833

Brian Searls and others added 4 commits August 17, 2026 10:37
…ed no-binding arm

Review-driven follow-up to the previous commit, both findings acted on rather than
recorded.

IDENTITY. The owner guard compared `authored_name_at(exp) == owner_name` -- a spelling,
not an identity. It fails in both directions: one declaration reached through a
qualified spelling fails it, and two DIFFERENT declarations sharing a name pass it. The
dangerous arm is the second: given `a.Status = Ready | AOnly` and `b.Status = Ready |
BOnly`, a `Ready` bound from a.Status could be adopted against an expected b.Status,
silently retargeting the constructor and potentially masking a real type error. That
hazard was introduced by the previous commit, not pre-existing.

The repository already defines same-owner identity at the constructor-binding authority
(insert_variant_owner_checked): authored name PLUS declaration file and source start.
That triple is extracted as `same_owner_declaration` and both consumers now ask one
authority instead of two.

Comparing identity NAIVELY REVERTS THE REPAIR, and the measurement is the point.
Requiring name+file+start put Option<Absent> straight back to its full 21-site baseline;
a probe on name+file alone was also 21, which located the cause: expand_scrut_type_for_-
variant_lookup returns its subject unchanged once the connective is Disj, so `exp_enum`
is a USE-SITE node in the consumer module, never the owner's declaration. Comparing a
use site against a declaration can never match. Both sides are now normalised through
lookup_type_for before comparison, which restores 0 sites WITH identity enforced.

NO-BINDING ARM. The expected-context-only branch adopted the expanded node raw, and
expand returns an OPTIONAL subject unchanged -- exactly the state the cardinality rule
refuses two branches over. It is fixed with with_required_cardinality rather than by
extending the `Required` refusal there: that branch has no binding fallback, so refusing
would convert resolving programs into hard not-founds, and a fabricated refusal is
forbidden by the same rule as a fabricated success.

Measured, emitted v2.compiler.05_eval closure, before -> after (unchanged headline,
strictly more precise output):

  Option<Absent> sites        21 -> 0
  rustc errors               494 -> 473  (-19 E0425, -2 E0573; no code increased)
  whole-artifact diff        43/43 lines, fully accounted:
                             21 broken annotations repaired,
                             ~21 `acc: _` IMPROVED to concrete types,
                             1 Verdict annotation relaxed to `_`
  self-host fixed point      green in one generation
  witness                    both rows green

The witness note gains the sharper coverage statement, which this change PROVED rather
than reasoned: both enrolled rows pass on the unrepaired baseline, so they detect the
repair over-reaching and cannot detect it ceasing to work. The identity draft above
silently reverted the repair while every row stayed green; only re-measuring the corpus
caught it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts the code of ece43db, keeping its witness-note finding. The review finding
that motivated it is REAL and is NOT resolved by this revert -- it is deferred with a
measurement, which is more than it had before.

WHAT THE FINDING WAS. The owner guard compares authored names, not declaration
identity, so two DIFFERENT declarations sharing a spelling can pass it and expected
context could retarget a constructor. That hazard is introduced by the first commit and
is real.

WHY THE FIX IS NOT SHIPPING. Comparing declaration identity requires normalising both
sides -- expand_scrut_type_for_variant_lookup returns a USE-SITE node once the
connective is Disj, so a raw span comparison never matches and silently reverted the
repair to its full 21-site baseline. Normalising through lookup_type_for restored the
repair but introduced 16 incompatible-type errors across the corpus, because resolving
to declarations makes the guard match through ALIASES and QUALIFIED SPELLINGS that the
authored-name comparison declines -- so it adopts in cases it must not. Bisected: the
no-binding cardinality edit is innocent (reverting it alone still gave 16); the identity
comparison is the cause.

  whole-corpus compile, dag + src/v2, one instrument, three arms:
    origin/main          incompatible-types  0   undefined-variable 32
    first commit         incompatible-types  0   undefined-variable 32
    identity commit      incompatible-types 16   undefined-variable 32

The 32 undefined-variable rows are pre-existing on main and are the control proving the
read works.

WHY THIS WAS NOT CAUGHT BEFORE PUSHING, which is the transferable part: every number in
the first commit was measured on the EMITTED EVAL CLOSURE, and the compile-clean gate
compiles the WHOLE CORPUS. This change alters inference for every bare variant reference
in the tree, so the eval closure was never the right denominator for it. The self-host
fixed point is also a partial denominator -- it covers v1's own sources, which is why it
caught two earlier drafts and not this one. Whole-corpus compile is now run beside the
eval emission for any inference change.

State restored to the verified first commit, re-measured after revert:

  self-host fixed point                green
  whole-corpus incompatible-types      0   (baseline 0)
  whole-corpus undefined-variable     32   (baseline 32, pre-existing)
  Option<Absent> in emitted eval       0   (baseline 21)
  witness                              both rows green

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

gunbai-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

One correction to the record on review 52943, because it would close a finding that is still open.

That review states the identity axis as:

authored_name_at(exp) == owner_name prevents retargeting to a different coproduct that merely shares an arm name.

It does not. A name comparison is precisely what PERMITS that case. Given a.Status = Ready | AOnly and b.Status = Ready | BOnly, a Ready bound from a.Status against an expected b.Status passes the guard, because both owners are spelled Status and both declare a Ready arm. Review 52880 raised exactly this, correctly, and it is not fixed.

The hazard is real, introduced by this PR, and deliberately shipped. Commit 2 attempted the identity fix and is reverted in commit 3 with the measurement:

whole-corpus compile (dag + src/v2), one instrument, three arms
  origin/main              incompatible-types  0    undefined-variable 32
  commit 1 (shipping)      incompatible-types  0    undefined-variable 32
  commit 2 (identity)      incompatible-types 16    undefined-variable 32

The 32 undefined variable rows are pre-existing on main and are the control proving the read discriminates. Two identity formulations failed in opposite directions: comparing declaration spans raw silently reverted the repair to its full 21-site baseline (expand_scrut_type_for_variant_lookup returns a USE-SITE node once the connective is Disj, so a span comparison never matches), and normalising both sides through lookup_type_for restored the repair but adopted through aliases and qualified spellings that the name comparison declines — hence the 16.

So the accurate statement of what ships is: the identity axis is a name comparison, it does not establish identity, and the retargeting hazard it admits is open. It is narrow (needs two same-spelled coproducts sharing an arm name, one imported and the other the expected type) and unobserved in the corpus — whole-corpus emission differs from baseline only in the accounted lines. That is a measured bound, not a proof of absence.

I would rather ship the weaker guard with the hazard documented than the stronger guard that breaks 16 sites, but the finding is DEFERRED, not resolved, and a review artifact asserting the guard prevents retargeting is the one thing that could make it disappear quietly.

Also worth flagging for anyone reading the approvals: they were given against the diff, while the head now carries an auto-heal commit whose parent is a merge of this branch into main — so the branch silently acquired 62 changed files under src/v2/dag plus src/v1 changes. I am re-running the fixed point, whole-corpus compile, eval emission and witness against that merged state rather than relying on measurements taken at the original base; fixed point is already green on it.

— sent from gentle-dove-833

@briansrls
briansrls merged commit d7668e3 into main Aug 17, 2026
10 checks passed
@briansrls
briansrls deleted the fix/infer-bare-imported-variant-owner branch August 17, 2026 16:36
briansrls pushed a commit that referenced this pull request Aug 17, 2026
…both positions

The emitted 05_eval closure carried 151 of 483 distinct rustc sites in which a
numeric scalar sat behind Rc. `Rc<i64>` is not a sharing decision, it is a
category error: every arithmetic and comparison operator refuses on it, which is
why one wrong decision produced many sites.

THE RULE THAT PRODUCED THEM decides from two facts, neither about the carrier:
membership by leaf NAME in shared_types, guarded by a string PREFIX test for
"Rc<" against already-rendered text. So one carrier reached by two spellings is
decided twice, and the prefix guard cannot see a layer through an alias.
Measured on this base, where Nat realizes natively as i64: 172 scalar-wrap
spellings, 12 literal `Rc<i64>` and 160 `Rc<Nat>` -- one category error in two
spellings, both appearing on a single emitted line:

  measure_scale_fraction_floor<Q, S>(m: Rc<Measure<Q, S, Rc<i64>>>, num: Rc<Nat>, ...)

the type argument rendering the primitive, the parameter rendering the alias.

BOTH POSITIONS NOW CONSULT ONE AUTHORITY (rust_seed_host_numeric_alias, via the
declaring file) instead of re-deciding from rendered text. Fixing only the
parameter half first was measured and REJECTED: it left `Rc<i64>` in argument
position and produced `Rc<i64> * i64`, an operator refusal neither uniform
answer produces -- a mixed layer is worse than either. The applied-argument half
sits in rust_render_checkpoint_scalar_bare, which despite its name applied the
shared wrap after the scalar authority had already answered.

RECEIPT, entry src/v2/compiler/05_eval.dag, base 0ce4ce6, site identity
(file, code, normalized message) because emitted line numbers move:

                    arm A -> arm B
  distinct causes     159 -> 148
  error messages      539 -> 417
  distinct sites      483 -> 361     (-122)
  scalar sites        151 ->  29     (-122)
  ADDED causes          0            (removal, not movement)
  emitted Rc<i64>      12 ->   0
  Option<Absent>        0 ->   0     (merged #8376 not regressed)
  regen --verify            divergence_count=0

SCOPE, stated rather than implied: this closes the NUMERIC scalar case, the whole
of the measured Rc<primitive> population. It deliberately does not consult
lookup_checkpoint, whose rows include non-scalar carriers such as String that may
legitimately be shared; widening there is a separate cut with its own evidence.
29 scalar sites survive and are not claimed fixed.

RUNG (DESIGN 4b): MITIGATABLE, and the ceiling is inherited rather than novel.
rust_seed_host_numeric_alias keys on a NAME plus a declaring-file check, not on
resolved declaration identity -- the same ceiling checkpoint_table_bypasses_identity_note
records for the neighbouring table. This makes the numeric-scalar wrap not happen;
it does not make a wrapped scalar unwritable. Next rung is the modeled layer
transition wrap_decision.dag already names, at which point an agreeing flow is
DERIVED rather than guarded.

Also adds dag/tools/p1_emit_probe.dag, the producer experiment that settled which
emitter to repair: the v2 emitter run under the v1 interpreter refuses at the
FIRST bridge on a real module (parse_tree_to_emitted_node_no_matching_row -- zero
grammar-relation rows match a real .dag token-class frontier), while the v1
emitter produces 92 files. So P0 remains the measured bootstrap producer and this
repair is on the selected producer, not on the interpreter being deleted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 17, 2026
…both positions

The emitted 05_eval closure carried 151 of 483 distinct rustc sites in which a
numeric scalar sat behind Rc. `Rc<i64>` is not a sharing decision, it is a
category error: every arithmetic and comparison operator refuses on it, which is
why one wrong decision produced many sites.

THE RULE THAT PRODUCED THEM decides from two facts, neither about the carrier:
membership by leaf NAME in shared_types, guarded by a string PREFIX test for
"Rc<" against already-rendered text. So one carrier reached by two spellings is
decided twice, and the prefix guard cannot see a layer through an alias.
Measured on this base, where Nat realizes natively as i64: 172 scalar-wrap
spellings, 12 literal `Rc<i64>` and 160 `Rc<Nat>` -- one category error in two
spellings, both appearing on a single emitted line:

  measure_scale_fraction_floor<Q, S>(m: Rc<Measure<Q, S, Rc<i64>>>, num: Rc<Nat>, ...)

the type argument rendering the primitive, the parameter rendering the alias.

BOTH POSITIONS NOW CONSULT ONE AUTHORITY (rust_seed_host_numeric_alias, via the
declaring file) instead of re-deciding from rendered text. Fixing only the
parameter half first was measured and REJECTED: it left `Rc<i64>` in argument
position and produced `Rc<i64> * i64`, an operator refusal neither uniform
answer produces -- a mixed layer is worse than either. The applied-argument half
sits in rust_render_checkpoint_scalar_bare, which despite its name applied the
shared wrap after the scalar authority had already answered.

RECEIPT, entry src/v2/compiler/05_eval.dag, base 0ce4ce6, site identity
(file, code, normalized message) because emitted line numbers move:

                    arm A -> arm B
  distinct causes     159 -> 148
  error messages      539 -> 417
  distinct sites      483 -> 361     (-122)
  scalar sites        151 ->  29     (-122)
  ADDED causes          0            (removal, not movement)
  emitted Rc<i64>      12 ->   0
  Option<Absent>        0 ->   0     (merged #8376 not regressed)
  regen --verify            divergence_count=0

SCOPE, stated rather than implied: this closes the NUMERIC scalar case, the whole
of the measured Rc<primitive> population. It deliberately does not consult
lookup_checkpoint, whose rows include non-scalar carriers such as String that may
legitimately be shared; widening there is a separate cut with its own evidence.
29 scalar sites survive and are not claimed fixed.

RUNG (DESIGN 4b): MITIGATABLE, and the ceiling is inherited rather than novel.
rust_seed_host_numeric_alias keys on a NAME plus a declaring-file check, not on
resolved declaration identity -- the same ceiling checkpoint_table_bypasses_identity_note
records for the neighbouring table. This makes the numeric-scalar wrap not happen;
it does not make a wrapped scalar unwritable. Next rung is the modeled layer
transition wrap_decision.dag already names, at which point an agreeing flow is
DERIVED rather than guarded.

Also adds dag/tools/p1_emit_probe.dag, the producer experiment that settled which
emitter to repair: the v2 emitter run under the v1 interpreter refuses at the
FIRST bridge on a real module (parse_tree_to_emitted_node_no_matching_row -- zero
grammar-relation rows match a real .dag token-class frontier), while the v1
emitter produces 92 files. So P0 remains the measured bootstrap producer and this
repair is on the selected producer, not on the interpreter being deleted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Aug 18, 2026
…both positions (#8384)

* A machine scalar is never raised to a reference layer: one decision, both positions

The emitted 05_eval closure carried 151 of 483 distinct rustc sites in which a
numeric scalar sat behind Rc. `Rc<i64>` is not a sharing decision, it is a
category error: every arithmetic and comparison operator refuses on it, which is
why one wrong decision produced many sites.

THE RULE THAT PRODUCED THEM decides from two facts, neither about the carrier:
membership by leaf NAME in shared_types, guarded by a string PREFIX test for
"Rc<" against already-rendered text. So one carrier reached by two spellings is
decided twice, and the prefix guard cannot see a layer through an alias.
Measured on this base, where Nat realizes natively as i64: 172 scalar-wrap
spellings, 12 literal `Rc<i64>` and 160 `Rc<Nat>` -- one category error in two
spellings, both appearing on a single emitted line:

  measure_scale_fraction_floor<Q, S>(m: Rc<Measure<Q, S, Rc<i64>>>, num: Rc<Nat>, ...)

the type argument rendering the primitive, the parameter rendering the alias.

BOTH POSITIONS NOW CONSULT ONE AUTHORITY (rust_seed_host_numeric_alias, via the
declaring file) instead of re-deciding from rendered text. Fixing only the
parameter half first was measured and REJECTED: it left `Rc<i64>` in argument
position and produced `Rc<i64> * i64`, an operator refusal neither uniform
answer produces -- a mixed layer is worse than either. The applied-argument half
sits in rust_render_checkpoint_scalar_bare, which despite its name applied the
shared wrap after the scalar authority had already answered.

RECEIPT, entry src/v2/compiler/05_eval.dag, base 0ce4ce6, site identity
(file, code, normalized message) because emitted line numbers move:

                    arm A -> arm B
  distinct causes     159 -> 148
  error messages      539 -> 417
  distinct sites      483 -> 361     (-122)
  scalar sites        151 ->  29     (-122)
  ADDED causes          0            (removal, not movement)
  emitted Rc<i64>      12 ->   0
  Option<Absent>        0 ->   0     (merged #8376 not regressed)
  regen --verify            divergence_count=0

SCOPE, stated rather than implied: this closes the NUMERIC scalar case, the whole
of the measured Rc<primitive> population. It deliberately does not consult
lookup_checkpoint, whose rows include non-scalar carriers such as String that may
legitimately be shared; widening there is a separate cut with its own evidence.
29 scalar sites survive and are not claimed fixed.

RUNG (DESIGN 4b): MITIGATABLE, and the ceiling is inherited rather than novel.
rust_seed_host_numeric_alias keys on a NAME plus a declaring-file check, not on
resolved declaration identity -- the same ceiling checkpoint_table_bypasses_identity_note
records for the neighbouring table. This makes the numeric-scalar wrap not happen;
it does not make a wrapped scalar unwritable. Next rung is the modeled layer
transition wrap_decision.dag already names, at which point an agreeing flow is
DERIVED rather than guarded.

Also adds dag/tools/p1_emit_probe.dag, the producer experiment that settled which
emitter to repair: the v2 emitter run under the v1 interpreter refuses at the
FIRST bridge on a real module (parse_tree_to_emitted_node_no_matching_row -- zero
grammar-relation rows match a real .dag token-class frontier), while the v1
emitter produces 92 files. So P0 remains the measured bootstrap producer and this
repair is on the selected producer, not on the interpreter being deleted.

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

* Delete the P1 producer probe: the experiment answered, so its implementation goes

dag/tools/p1_emit_probe.dag was branch-local experiment tooling. It asked one
question -- can v2's emitter semantics serve as the bootstrap producer? -- and
answered it by execution: P1 refuses at the first bridge
(parse_tree_to_emitted_node_no_matching_row, v2.std.grammar), while P0 emits 92
files. That measurement selected P0 and is recorded in the PR receipt; the
transport that produced it has no remaining consumer.

Zero references to p1_emit_probe / p1_probe_eval / p1_probe_small_control exist
outside the deleted module itself.

Deleted rather than registered as a scaffold. DESIGN §5 rules that a dissolution
condition describes how admitted debt ends and does not authorize creating it,
so marking it would have required an operator verdict on this exact population
-- and buying that verdict for tooling whose question is already answered spends
review and maintenance time against a benefit of zero. The same section names
the correct arm directly: the canary may run, produce measurements, and be
deleted before merge, leaving the modeled facts the final route consumes.

The emit-side repair is unaffected: the probe never entered any production path,
and the fix's authority is src/v1/05_emit_rust.dag.

Raised by cursor/composer-2.5 in review 53026 on #8384.

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant