Skip to content

A v2-exclusive CLI that lives in the v2-only world, and the door the self-host step stops in front of - #11474

Merged
briansrls merged 3 commits into
mainfrom
v2-native-cli
Sep 17, 2026
Merged

briansrls merged 3 commits into
mainfrom
v2-native-cli

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Stacked on #11467 — its commit is the first of the two here, and this change adds a row to the instrument registry that PR introduces. Review the second commit.

src/v2/cli/compile_cli.dag is the compiler's own front door, written in the compiler's own language, declaring a fourth std.compiler_entry driver: NativeCliDriver. gunbc test //gunbc/instruments:v2-native-cli emits and builds its closure.

Why a second CLI is not a second route to one fact

gunbc is the seed's CLI — RetainedHostCliKernel, a clap surface dispatching into hand-authored host Rust (v1_compiler.cli_run) — so its verbs are unreachable from a self-emitted binary by construction, not by omission: an emitted crate does not contain that module. That is exactly where the self-host step stops, and //gunbc/instruments:self-host's own row says so: the second generation is the built binary emitting a closure, and it refuses because v2.compiler.compile declares SourceRootEvalDriver, whose rendered main answers census and adjudicate and has no compile mode. This is the entry that has one.

Where the host boundary is — measured, not chosen

A .dag fold cannot read a file. Filesystem.Read off an ordinary fn is a located compiler refusal (receiver type Primitive(Filesystem) establishes no method surface), because resource operations resolve only inside a workflow function and are realized by the seed interpreter, which an emitted binary is not running under.

So "v2 native" cannot mean the .dag does IO. It means the .dag decides everything — the verb, every refusal, what to emit, the process exit — and the rendered main performs only the four operations no fold can, handing each back as a value. That is deliberately the capability gunbc.source_root_eval_driver_seed_growth names as the trigger retiring its row ("the rendered main becomes one call into that fold"), so this door adds a CLI without adding seed debt: the rendered main is ~25 lines against its neighbour's ~500.

What the new instrument already found

The two closures are different rather than nested: this one reaches modules v2.compiler.compile's does not, v2.extdeps.languages.rust among them. So the self-host green never compiled the rows that define how v2 emits Rust, and the first entry to pull them in refuses with 19 rustc errors across five modules that have never been emitted and built:

module errors
std_change.rs E0310 ×8 (missing 'static on emitted generics)
v2_std_decl_index.rs, v2_std_node_reflection.rs E0618 ×4 + 4 unreachable
v2_std_compilers_semantic_decl_emission.rs E0282, E0308
v2_extdeps_languages_rust.rs E0308

These are pre-existing and are not touched here. The instrument terminates SubjectUnreached — the emit refused, so the subject was never reached, which is not an observation that the CLI cannot build. This is the notch the ratchet starts from.

Evidence

  • 16/16 claims pass, all sub-millisecond (v2.test.cli.v2_native_cli), plus 3 routing rows on the instrument witness.
  • Regression control: //gunbc/instruments:self-host still reports exit_status=0 warning_count=0 (600s).
  • Extraction control: the rendered eval-driver main.rs diffs against its pre-change bytes as exactly the intended move and nothing else — 4 lines out of read_ingest, the same 4 into each of run_census and run_adjudication.
  • cargo clippy --all-targets -- -D warnings clean; regen round converged in one stage.

The emit arm's inhabitance claim is a declared frontier

It was written, ran, and went red — and the diagnosis is the finding. Bisecting: the same ingest and admission handed to emit_compiler_import_closure_from_ingest directly, with the CLI out of the path, is red too. And the pre-existing claim for that fold, v2.test.long.self_host_module_emit_derisk, is red on this tree unchanged — both of its emit rows fail for shapes they assert Accepted for.

That file sits under test/claim/long/, which CI discovery excludes at directory grain, and its last commit is a repository-wide DESIGN edit. So the red has been unobserved — the next receipt for the retraction gunbc.ci_layer_roots already carries about that home. The trigger is stated as a capability and the claim lands with it.

Two earlier diagnoses were ruled out by execution, not argument: that the admission subject was the whole defect (it was a real defect — a constant symbol names no module — but fixing it moved the failure without removing it), and that the probe's module shape was at fault (switching to a shape derisk asserts emits changed nothing).

Failure-mode rows

  • closed_variant_partition_read_as_a_default_arm — the class recurred. emit_main_rs still ended if is_source_root_eval { … } return direct_ingest_main, so direct-ingest was reached by falling off the end. A fourth driver made that residue wrong rather than fragile: the new reading is recognised, so the unknown-driver wall admits it and never fires, and a NativeCliDriver entry would have rendered the direct-ingest main. Closed by naming the arm; the row's own next-rung trigger is still unmet.
  • accepted_source_emits_uncompilable_target — a third specimen. v2.std.text declares type String = FreeMonoid<Char> while the primitive String emits to the target's native string. The front end accepted string_eq(…) with zero blocking diagnostics; the emitted crate refused at cargo build with E0308. The emitted alias has zero users in every crate emitted to date, so the fork was dormant rather than absent.

🤖 Generated with Claude Code

gunbai-bot Bot pushed a commit that referenced this pull request Sep 16, 2026
… (explicit-contract callability on ordinary fns); main is the composition root and the #11474 shim is interim; exact effect homes named; Clock is its own cut; NativeEffectRealizationAdmission total carrier; per-cut instruments; D11 terminal in D10 and dissolution; silence count removed

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 16, 2026
…root_eval_driver_main_rs under the seed-growth row; #11474 cited as an open PR's receipt, not tree symbols; §3 consumes the row (review 66930)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbc-ci-auto-heal and others added 2 commits September 16, 2026 21:24
…ed binary

`gunbc test //gunbc/instruments:self-host` runs the generation this repository can
perform today: the seed emits v2's compiler closure, assembles it as a crate, and
builds it. Measured here at 553s with exit_status=0 warning_count=0 under
RUSTFLAGS="-D warnings".

WHY THE ROUTE MOVED. The step first landed as `claim_executor --self-host`, and
that binary is the wrong home by construction: its ~20 flags are hand-parsed in a
2783-line main over 44,451 lines of cli_run.rs with NO .dag authority at all,
while `gunbc`'s own surface IS modeled (gunbc.cli_dispatch_surface ->
gunbc_cli_dispatch_generated.rs). The seam that already exists for exactly this
is gunbc.target_binding TargetProducer bound to a gunbc.instrument_targets label,
and its host note states the extension shape outright: adding one is a row in
instrument_registry and an arm here; it is not a new route. The flag is WITHDRAWN
in this change rather than left beside the target, because two routes to one fact
is the section 3 fork this seam exists to prevent.

WHAT THE SEAM BUYS, and it is not tidiness. The label carries its own refusal
vocabulary, so an unknown or pattern operand REFUSES rather than reporting a pass,
and the three terminations stay distinct -- 0 held, 1 an observation that did not
hold, 2 no observation -- for every instrument at once rather than per flag. That
split is load-bearing here: a refusal from prepare_emitted_compiler is the subject
never having been reached (the emit refused, the crate would not write, cargo could
not run) and NOT an observation that the seed cannot build v2. Collapsing them
would report a broken bench as a broken compiler, which is the absorbing answer
DESIGN section 5 forbids.

run_self_host returns SelfHostHeld rather than printing and returning unit, so the
instrument seam decides the termination once. A producer that only printed would
make every caller re-derive the verdict from stderr, which is the second
representation section 3 forbids.

WHERE IT STOPS IS THE STATUS, which is why no roster accompanies it. A list of
known-good entries beside this instrument would be a second representation of what
it demonstrates by running, and it drifts exactly when nobody updates it:
required_v2_emission_entries has changed once in repository history and that change
was a directory move. A green here is EMISSION AND COMPILATION and not behavioural
equivalence -- that half lives in the three behavioral-receipt instruments beside
it -- and it is not the second generation: the built binary emitting the same
closure is the v2 -> v2 boundary, and it refuses today because v2.compiler.compile
declares SourceRootEvalDriver, whose rendered main answers `census` and `adjudicate`
and has no compile mode. That refusal is the honest next position and belongs in
this instrument when a driver can reach it.

THE ROSTER DOCUMENTS ITSELF. An unknown-target refusal now lists every rostered
instrument, read from the same instrument_registry rows the lookup just failed
against -- so a reader who has forgotten the label is handed it by the refusal, and
a listing cannot disagree with what is invocable. A prose page naming them would be
the second representation sections 2 and 3 price, stale the first time someone adds
a row and does not think to edit prose. DESIGN's Building & checks carries the RULE
(the seam, and that a new instrument is a row rather than a flag); the roster itself
is never restated there.

THE WITNESS, with its red measured rather than asserted. Removing self_host_binding
while keeping its label -- the half-added row a registry actually suffers -- turns
the_self_host_operand_routes_through_the_live_registry and
every_rostered_label_carries_a_binding red while the subject invariant stays green,
because that row asserts a different property. The routing claim asserts the
PRODUCER ARM and not merely the absence of a refusal: a binding naming the wrong
producer routes successfully and then measures something else while reporting this
target's standing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…self-host step stops in front of

`src/v2/cli/compile_cli.dag` is the compiler's own front door written in the
compiler's own language, declaring a fourth `std.compiler_entry` driver,
`NativeCliDriver`. `gunbc test //gunbc/instruments:v2-native-cli` emits and
builds its closure.

WHY A SECOND CLI IS NOT A SECOND ROUTE TO ONE FACT. `gunbc` is the SEED's CLI --
`RetainedHostCliKernel`, a clap surface dispatching into hand-authored host Rust
(v1_compiler.cli_run) -- so its verbs are unreachable from a self-emitted binary
BY CONSTRUCTION and not by omission: an emitted crate does not contain that
module. That is exactly where the self-host step stops, and its own row says so:
the built binary emitting the same closure is the v2 -> v2 boundary, and it
refuses because v2.compiler.compile declares SourceRootEvalDriver, whose rendered
main answers `census` and `adjudicate` and has no compile mode. This is the entry
that has one. The two CLIs are the seed's and the emitted compiler's, and no
invocation can reach both.

WHERE THE HOST BOUNDARY IS, MEASURED RATHER THAN CHOSEN. A `.dag` fold cannot
read a file: `Filesystem.Read` off an ordinary `fn` is a LOCATED COMPILER REFUSAL
("receiver type 'Primitive(Filesystem)' establishes no method surface"), because
resource operations resolve only inside a workflow function and are realized by
the seed INTERPRETER, which an emitted binary is not running under. So "v2
native" cannot mean the .dag does IO. It means the `.dag` decides EVERYTHING --
the verb, every refusal, what to emit, the process exit -- and the rendered main
performs only the four operations no fold can, handing each back AS A VALUE.
That is deliberately the capability gunbc.source_root_eval_driver_seed_growth
names as the trigger retiring ITS row ("the rendered main becomes one call into
that fold"), so this door adds a CLI without adding the ~500-line seed debt its
neighbour carries: the rendered main is about twenty-five lines.

THE PROTOCOL IS TWO CALLS BECAUSE THE BOUNDARY FORCES IT. The host cannot know
which roots to walk until argv is parsed, and the entry cannot parse argv and
then read the roots itself. So `v2_cli_parse` returns a plan, the host asks
`v2_cli_plan_source_roots` what to walk -- never argv again, which would be two
readings of one fact free to disagree -- and `v2_cli_run` receives the ingest.
A refused plan names no roots, so the reader is handed an empty list and the
`.dag` answers with the PLAN'S located cause rather than a harness message.

THE RESIDUE ARM RECURRED AND IS CLOSED BY NAME. emit_main_rs still ended
`if is_source_root_eval { ... } return direct_ingest_main`, so direct-ingest was
reached by falling off the end. Adding a fourth driver made that residue WRONG
rather than merely fragile: the new reading is RECOGNISED, so the unknown-driver
wall admits it and never fires, and a NativeCliDriver entry would have rendered
the DIRECT-INGEST main -- the identical silent wrong answer
closed_variant_partition_read_as_a_default_arm was filed for, through the arm
that row's repair left standing. compiler_pipeline_entry_is_direct_ingest now
asks by name and the selection ends in a refusal. The rung is unchanged and the
row's own next-rung trigger is still unmet: the selector compares a String read
off the source span rather than matching the resolved variant.

THE SOURCE-ROOT WALK IS RENDERED ONCE. Two mains now need it, and the second is
the edit that turns a block inside one template into two copies of one program.
emit_host_source_root_read_rs is that single authority. It is TOTAL over its root
list -- the eval driver's empty-roots refusal is a fact about ITS modes, not about
reading files, and it is wrong for a CLI whose `.dag` already refuses a verb with
no root -- so the guard moved to that driver's own two call sites. CONTROL: the
rendered eval-driver main diffs against the pre-change bytes as exactly that move
and nothing else (four lines out of read_ingest, the same four into each of
run_census and run_adjudication), and //gunbc/instruments:self-host still reports
exit_status=0 warning_count=0.

WHAT THE NEW INSTRUMENT ALREADY FOUND, which is the reason it is a separate
label. The two closures are DIFFERENT rather than nested: this one reaches
modules v2.compiler.compile's does not, v2.extdeps.languages.rust among them. So
the self-host green never compiled the rows that define how v2 emits Rust, and
the first entry to pull them in refuses with 19 rustc errors in FIVE modules that
have never been emitted and built (std_change E0310 x8, v2_std_decl_index and
v2_std_node_reflection E0618 x4, semantic_decl_emission E0282/E0308,
extdeps_languages_rust E0308). Those are pre-existing and are NOT touched here.
The instrument terminates SubjectUnreached, which is the honest arm: the emit
refused, so the subject was never reached -- not an observation that the CLI
cannot build.

THE EMIT ARM'S INHABITANCE CLAIM IS A DECLARED FRONTIER, AND IT WAS MEASURED
RATHER THAN SKIPPED. The claim was written and ran RED. Bisecting it: the same
ingest and admission handed to emit_compiler_import_closure_from_ingest DIRECTLY,
with the CLI out of the path, is red too -- so the refusal is the fold's, not the
routing's. And the pre-existing claim for that fold,
v2.test.long.self_host_module_emit_derisk, is RED ON THIS TREE UNCHANGED: BOTH of
its emit rows fail for module shapes they assert Accepted for. That file sits
under test/claim/long/, which CI discovery excludes at directory grain, and its
last commit is a repository-wide DESIGN edit -- so the red has been unobserved,
which is the next receipt for the retraction gunbc.ci_layer_roots already carries
about that home. The trigger is stated as a capability and the claim lands with
it, not in a later change.

TWO EARLIER DIAGNOSES WERE WRONG AND WERE RULED OUT BY EXECUTION, not by
argument: that the admission's subject was the whole defect (it was a real defect
-- a constant symbol names no module, so every emit rejected -- and fixing it
moved the failure without removing it), and that the probe's module shape was at
fault (switching to the shape derisk asserts emits changed nothing). The
conversion at the argv/resolver boundary is now its own pair of green claims
rather than an inference.

A THIRD SPECIMEN FOR accepted_source_emits_uncompilable_target, found by this
work: v2.std.text declares `type String = FreeMonoid<Char>` while the primitive
`String` emits to the target's native string -- one name, two representations.
The front end accepted `string_eq(a: arg, b: cli_verb_emit)` with ZERO blocking
diagnostics and the emitted crate refused at cargo build with E0308, expected
`Rc<im::Vector<i64>>` found `std::string::String`. The emitted alias has zero
users in every crate emitted to date, so the fork was dormant rather than absent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…zed seam stops being a callee

Two emitter defects found by emitting the v2-native CLI's closure, which reaches
five modules `v2.compiler.compile`'s closure does not and so had never been built
as emitted Rust. Nineteen rustc errors, three root causes. MEASURED ON THAT
CLOSURE: 19 -> 8. A closes eight; C's call-site repair closes three; five are C's
declared residue and three are the `String` fork this does not touch.

AN EARLIER REVISION OF THIS MESSAGE SAID 19 -> 3 AND THAT FIGURE WAS PREDICTED,
NOT MEASURED. The 19 -> 2 reading it was extrapolated from came from a
configuration that included a body-grain seam repair which was later WITHDRAWN
(see below), and the withdrawal was assumed to restore the single E0599 it had
removed. It restores FIVE: the seam's result feeds onward, so the same sites also
produce two `unreachable call`, one `unreachable statement` and one E0282, each an
error under RUSTFLAGS="-D warnings". The corrected count is measured on the
committed configuration.

A. THE `'static` BOUND IS DERIVED FROM A FUNCTION'S OWN RETURN TYPE AND NEVER
FOLLOWED THE CALL EDGE. `std.change` declares `keyed_patch_monoid<K, V>` and
`keyed_patch_fold<K, V>` with IDENTICAL type parameters and the emitter gave the
first `K: Clone + 'static` and the second bare `K: Clone`. The rule is that a
record whose fields are arrows moves its generics into an `Rc<dyn Fn>`, which
defaults to `'static`; `keyed_patch_fold` returns `KeyedPatch`, which has no arrow
fields, but its body instantiates the monoid at its own `K, V`. Eight E0310s, one
missing edge.

IT HAD TO BE TRANSITIVE, AND THE ONE-HOP VERSION IS WHAT ESTABLISHED THAT rather
than an argument. Shipped one-hop first, it fixed two of eight and CREATED THE
SECOND HOP: giving `keyed_two_way_diff` the bound made `keyed_apply_two_way_diff`
-- which calls it and returns a plain `KeyedPatch` -- newly require one. A one-hop
rule cannot converge here by construction, since every hop it fixes manufactures
the next. The derivation now recurses with the `visited` cycle guard its Clone
sibling already uses, because `std.change`'s folds are recursive.

AND THE BOUND LANDS ON TWO SURFACES, NOT ONE. Type parameters take it through the
forwarding above; `impl Fn` VALUE parameters took it only from
`enclosing_returns_arrow`, which is local in the same way and left `value_eq` bare
while `key_eq` carried it. That parameter is renamed `fn_param_needs_static`: "the
enclosing function returns an arrow" was one CAUSE of the property and is no
longer the whole of it, so the old spelling had become a nickname for part of what
it decides.

C. AN UNREALIZED HOST SEAM WAS BUILT AS A CALLEE AND THEN HANDED ARGUMENTS. A
primitive whose body is a bare self-call declares that its body lives in the host,
and the emitter correctly refuses it with a diverging `panic!` rather than
emitting a function that compiles and returns to no caller. But the refusal was
placed as the CALLEE, rendering `panic!("...")(arg)` -- a `!` applied to an
argument list -- so rustc answered E0618 plus an `unreachable expression` per
argument and every diagnostic named the shape of the emission rather than the
missing realization. `panic!` diverges and needs no arguments; it is now the whole
call expression. Four E0618s and four unreachable-expression errors, gone.

A BODY-GRAIN COMPLETION WAS BUILT, MEASURED TWICE AND WITHDRAWN, and its finding
is recorded on the surviving predicate. One residue remains -- a seam whose RESULT
is consumed still renders `panic!("...").len()`, E0599, because `!` coerces in
value position while method resolution against it does not. Refusing the whole
FUNCTION would type-check, and CANNOT BE DECIDED FROM A BODY WALK: `rt_functions()`
is not the set of realizable primitives, since collection folds, pipelines and the
operators are rendered by dedicated arms carrying no bridge row. The CALL SITE is
safe precisely because it sits at the RESIDUE position after every other arm has
declined. The emitted seed refused `v1.compiler.tokenize source_code_point`, then
`v1.compiler.infer typecheck_module`, and panicked on its own first tokenize and
first typecheck. TRIGGER: the emitter answering "will any arm render this call" as
one predicate rather than positionally.

THE BRIDGE TEST MOVED INSIDE THE PREDICATE, which is the other half of that
lesson. `call_target_is_unrealized_primitive` was correct only because its one
caller pre-filtered with `is_rt`; the second consumer inherited the hole and
called every bridged builtin unrealized. A predicate correct only when each caller
remembers to filter it is a wall with a gap per consumer.

WHAT THIS DOES NOT DO. The three remaining errors are the `String` meaning fork --
`v2.std.text` declares `type String = FreeMonoid<Char>` while the primitive spelling
emits to the target's native string -- which is not an emitter defect but the
program `docs/plans/carrier-realization-arbiter-repair-design.md` owns, with two
repairs already refuted and a census as its prescribed first step. Untouched here
deliberately.

EVIDENCE. The regen round converged in one stage with exactly this mirror changed,
which also exercises the emitter over the whole 155-module seed closure: it is what
caught the body-grain over-fire, twice, by rebuilding the seed and running it.
`//gunbc/instruments:self-host` REPORTS exit_status=0 warning_count=0 at 514s, and
its closure identity bd2621756494477e9490ff6c76b34503682aede13ca17e2472cdb4a384e0f415
is BYTE-IDENTICAL to the pre-change run. That is the corpus-wide control: four
changes to how every generic function and every seam call emits, and the v2
compiler closure emits to the same bytes and still builds clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 4d3660d Sep 17, 2026
4 checks passed
@briansrls
briansrls deleted the v2-native-cli branch September 17, 2026 00:51
briansrls pushed a commit that referenced this pull request Sep 17, 2026
…rries a type, and three carrier conversions were missing

`gunbc test //gunbc/instruments:v2-native-cli` reports exit_status=0 warning_count=0 at 495s under
RUSTFLAGS="-D warnings". The closure went from NINETEEN rustc errors to zero across three changes;
this is the last two of them.

C -- AN UNREALIZED HOST SEAM NOW CARRIES ITS OWN TYPE. The refusal itself was already right: a
primitive whose body is a bare self-call has no behaviour on this target, and emitting that shape
verbatim yields a function that compiles and returns to no caller (DESIGN section 5). What `panic!`
alone cannot do is stand where its RESULT IS CONSUMED -- `!` coerces in value position but is not a
method receiver, so `panic!("...").len()` answered E0599 and everything after it `unreachable`. The
emitter now renders `v1_rt::unrealized_host_seam::<T>(msg)` with T taken from the call's OWN INFERRED
TYPE, which is how `empty_map` and `empty_set` in the same function already read `inferred`. Five
errors in two modules, gone, with the divergence intact: the helper has no return path.

WHERE THAT ARM MAY BE ASKED IS THE WHOLE LESSON, AND IT COST TWO FAILED ATTEMPTS. The predicate
answers "runtime primitive, no bridge, no projected declaration", and that is the right question ONLY
at the RESIDUE POSITION, after every earlier arm has declined -- because a family of primitives is
rendered by DEDICATED ARMS carrying no bridge row: `with`, `concat` past two arguments, the
collection folds, the pipelines.

  Attempt 1, body grain: refuse the whole FUNCTION when its body reaches a seam. Type-checks
  trivially. It called every bridgeless builtin unrealized, refused `v1.compiler.tokenize
  source_code_point` and then `v1.compiler.infer typecheck_module`, and the rebuilt seed panicked on
  its own first tokenize. Withdrawn.

  Attempt 2, one level too early: the typed refusal placed in emit_typed_call_expr, ahead of the
  delegation. It intercepted `with` in v1.compiler.infer, so `let typed_module = with(typed_base, ..)`
  took the seam's type instead of the record update's, and the SECOND GENERATION failed to compile
  (`expected Rc<Node>, found Rc<HashMap<_, _>>`). STAGE ONE BUILT CLEAN BOTH TIMES; only the
  fixed-point check caught it.

The landed arm is the one that already sat after `is_rt` and after the `call_target` match, now
taking `inferred` so it can type what it refuses. The constraint is recorded on the predicate, because
it is a fact about where the question may be asked rather than about what it computes.

B -- THREE CARRIER CONVERSIONS THAT WERE SIMPLY MISSING, AND NOT THE FORK THEY LOOKED LIKE.
`v2.std.compilers.lexing` declares `LiteralPattern { text: v2.std.text.String }` -- the QUALIFIED
spelling, so the field is the free-monoid char list on purpose -- and its own constructor converts
with `chars`. Two other constructors handed over an ordinary string unconverted, and
`semantic_decl_string_to_bundle_node` folded one through `from_code_point` without taking the
char-list view. Adding `chars(s: ..)` at the three sites is the fix.

THAT ATTRIBUTION WAS WRONG BEFORE IT WAS MEASURED, and the correction matters more than the fix. The
error signature (`expected Rc<im::Vector<i64>>, found String`) matches the String meaning fork, so
these were reported as instances of it and briefed to the self-host lane as blocked behind
docs/plans/carrier-realization-arbiter-repair-design.md -- 52 sites, arm A's 25 generic-carrier
errors, a location-valued identity key, two refuted repairs. None of that was load-bearing here.
These are that document's ARM B, the arm it states is closable constructor-side, and they close.
Arm A is real, unsolved, and lives in `01_tokenize`'s closure rather than this one.

EVIDENCE, all on the converged compiler rather than the one that produced the first reading.
`//gunbc/instruments:v2-native-cli` exit_status=0 warning_count=0 wall_s=495.
`//gunbc/instruments:self-host` exit_status=0 warning_count=0 wall_s=586 -- the corpus-wide control,
because this changes how EVERY unrealized-seam call emits. The regen round converges with one mirror
changed.

EVERY FIGURE ABOVE IS MEASURED ON THIS TREE, after #11474 squash-merged and this commit was rebased
onto it. Two earlier zeroes are SUPERSEDED rather than cited beside these: the first was measured
against a binary still carrying attempt 2's `with` defect, and the second against the pre-rebase tree.
The convergence run that discriminated attempt 2 reported convergence_stages=2 [stage-1, stage-2] --
stage two is where that attempt failed, so its clean build is the evidence that the residue placement
is right, and it is recorded here as history of the check rather than as a property of this tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Sep 17, 2026
…rries a type, and three carrier conversions were missing

`gunbc test //gunbc/instruments:v2-native-cli` reports exit_status=0 warning_count=0 at 495s under
RUSTFLAGS="-D warnings". The closure went from NINETEEN rustc errors to zero across three changes;
this is the last two of them.

C -- AN UNREALIZED HOST SEAM NOW CARRIES ITS OWN TYPE. The refusal itself was already right: a
primitive whose body is a bare self-call has no behaviour on this target, and emitting that shape
verbatim yields a function that compiles and returns to no caller (DESIGN section 5). What `panic!`
alone cannot do is stand where its RESULT IS CONSUMED -- `!` coerces in value position but is not a
method receiver, so `panic!("...").len()` answered E0599 and everything after it `unreachable`. The
emitter now renders `v1_rt::unrealized_host_seam::<T>(msg)` with T taken from the call's OWN INFERRED
TYPE, which is how `empty_map` and `empty_set` in the same function already read `inferred`. Five
errors in two modules, gone, with the divergence intact: the helper has no return path.

WHERE THAT ARM MAY BE ASKED IS THE WHOLE LESSON, AND IT COST TWO FAILED ATTEMPTS. The predicate
answers "runtime primitive, no bridge, no projected declaration", and that is the right question ONLY
at the RESIDUE POSITION, after every earlier arm has declined -- because a family of primitives is
rendered by DEDICATED ARMS carrying no bridge row: `with`, `concat` past two arguments, the
collection folds, the pipelines.

  Attempt 1, body grain: refuse the whole FUNCTION when its body reaches a seam. Type-checks
  trivially. It called every bridgeless builtin unrealized, refused `v1.compiler.tokenize
  source_code_point` and then `v1.compiler.infer typecheck_module`, and the rebuilt seed panicked on
  its own first tokenize. Withdrawn.

  Attempt 2, one level too early: the typed refusal placed in emit_typed_call_expr, ahead of the
  delegation. It intercepted `with` in v1.compiler.infer, so `let typed_module = with(typed_base, ..)`
  took the seam's type instead of the record update's, and the SECOND GENERATION failed to compile
  (`expected Rc<Node>, found Rc<HashMap<_, _>>`). STAGE ONE BUILT CLEAN BOTH TIMES; only the
  fixed-point check caught it.

The landed arm is the one that already sat after `is_rt` and after the `call_target` match, now
taking `inferred` so it can type what it refuses. The constraint is recorded on the predicate, because
it is a fact about where the question may be asked rather than about what it computes.

B -- THREE CARRIER CONVERSIONS THAT WERE SIMPLY MISSING, AND NOT THE FORK THEY LOOKED LIKE.
`v2.std.compilers.lexing` declares `LiteralPattern { text: v2.std.text.String }` -- the QUALIFIED
spelling, so the field is the free-monoid char list on purpose -- and its own constructor converts
with `chars`. Two other constructors handed over an ordinary string unconverted, and
`semantic_decl_string_to_bundle_node` folded one through `from_code_point` without taking the
char-list view. Adding `chars(s: ..)` at the three sites is the fix.

THAT ATTRIBUTION WAS WRONG BEFORE IT WAS MEASURED, and the correction matters more than the fix. The
error signature (`expected Rc<im::Vector<i64>>, found String`) matches the String meaning fork, so
these were reported as instances of it and briefed to the self-host lane as blocked behind
docs/plans/carrier-realization-arbiter-repair-design.md -- 52 sites, arm A's 25 generic-carrier
errors, a location-valued identity key, two refuted repairs. None of that was load-bearing here.
These are that document's ARM B, the arm it states is closable constructor-side, and they close.
Arm A is real, unsolved, and lives in `01_tokenize`'s closure rather than this one.

EVIDENCE, all on the converged compiler rather than the one that produced the first reading.
`//gunbc/instruments:v2-native-cli` exit_status=0 warning_count=0 wall_s=495.
`//gunbc/instruments:self-host` exit_status=0 warning_count=0 wall_s=586 -- the corpus-wide control,
because this changes how EVERY unrealized-seam call emits. The regen round converges with one mirror
changed.

EVERY FIGURE ABOVE IS MEASURED ON THIS TREE, after #11474 squash-merged and this commit was rebased
onto it. Two earlier zeroes are SUPERSEDED rather than cited beside these: the first was measured
against a binary still carrying attempt 2's `with` defect, and the second against the pre-rebase tree.
The convergence run that discriminated attempt 2 reported convergence_stages=2 [stage-1, stage-2] --
stage two is where that attempt failed, so its clean build is the evidence that the residue placement
is right, and it is recorded here as history of the check rather than as a property of this tree.

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