Skip to content

Give the Rust lambda route a parameter type it already knew - #9182

Closed
briansrls wants to merge 8 commits into
mainfrom
session/deep-ant-102-transcribed-benchmark
Closed

briansrls wants to merge 8 commits into
mainfrom
session/deep-ant-102-transcribed-benchmark

Conversation

@briansrls

@briansrls briansrls commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

What this is

emit_shared_expr dispatches nineteen expression forms by delegating each to an injected per-target callback. ExprLambda was the one arm that did not — it called emit_lambda_params directly, whose param_names: List<String> signature is structurally incapable of carrying a type. An inferred closure parameter type this compiler had already resolved was discarded one call before the point it was needed.

It is discarded in a signature, not in a branch. So no arm is missing, nothing looks incomplete, and neither a reviewer nor an exhaustiveness check would flag it.

Reach, not capability

Measured on the 173 emitted files of the v2 compiler closure:

closure parameter shape count E0282
fully typed |a: T, b: U| 971 0
contains a hole |a: _, b: T| 139 0
fully untyped |a, b| 1146 254

languages.dag has bound Rust's lambda_param_typed the whole time and the collection route already consumes it. The generic route simply could not reach it. So ExprLambda becomes a delegating arm like its siblings and Rust injects the typed producer — no second renderer is grown beside the one that works.

Why the generic path is deliberately untouched

emit_lambda_params still serves Python, Go and Dag. That is soundness, not scope discipline: Python binds lambda_param_typed to "{0}: {1}" over a "lambda {0}: {1}" template, so routing Python through it emits lambda x: int: body — invalid syntax. That binding is unreachable today and is a fabricated plausible value sitting in the field this change starts consuming. The injected-callback shape keeps it unreachable by construction, not by a test someone must remember.

The _ decision is not taken here

unavailable_stays_untyped is true on the new route, false on both existing callers. The collection route arrives with a real element type, so its Absent arm means "the exact type did not render" and an explicit _ is a decision that route has already made. The new route has no fallback to offer, so Absent means only "no annotation is available" — emitting _ would convert an honest absence into a claim. Staying untyped keeps this strictly additive: annotate where an exact renderable type exists, change nothing anywhere else.

Established by execution — source level only

treatment control
emit_shared_expr arity 21 20
emit_rust_lambda_params_typed resolves NoSuchFunction
typecheck emit_rust / infer green green

NOT established — why this is a draft

Emitted output is unvalidated. A .dag edit is inert until the mirror regenerates, and the available instrument is built from the unpatched mirror. Validating emission needs a bootstrap round.

This is recorded rather than glossed because a clean-looking run already lied here once. An earlier whole-corpus arm reported treatment and control identical as sets — worthless: a deliberate unresolved symbol planted in 05_emit.dag produced zero diagnostics. --source-root src/v1 indexes those modules without compiling them (all 5600 diagnostics have paths under dag/, none under src/v1; they are orphans nothing imports). The run's own summary line names src/v1 as a compiled root it never opened.

Still owed, per the approved acceptance controls:

  • the 254 candidates and 87 residue partitioned into retired / converted / unchanged
  • the 971 typed and 139 partial-hole closures held byte-identical as negative controls
  • newly-exposed errors partitioned by shape (Rc placement, alias expansion, use-line)
  • Python / Go / Dag output byte-identical
  • stage0 remains buildable

Known interaction

B1 renders every annotation through render_rust_type, the same function measured emitting Rc<CommutativeSemiring<Magnitude>> where Nat was correct. ~892 of the 1146 closures compile green today; annotating them with a renderer that loses alias identity would turn green sites red, and the failure would look like this repair. That is the main reason emission validation is a precondition rather than a formality.

This branch also doubles as the unqualified control arm for a live question in the import-deletion lane: whether that defect is specific to qualified references or general to render_rust_type. Main's references are bare, so B1 exercises the renderer at scale with no qualification change.

Brian Searls and others added 2 commits August 25, 2026 03:03
…string renderer a bounded adapter with a terminus

Two corrections from the v2-management thread's #9154 review. A third and the
owed axis controls are moot: they were written against e4b942a, before review
55633 backed the CargoBuildSubject and CargoVerb axes out, so the free
manifest_path they object to no longer exists to be closed.

THE TRANSCRIBED BENCHMARK IS NOT MOOT AND WAS LIVE ON THIS HEAD. The axes went
but their deferral note stayed, and it carried "a measured 59.8s/2.5GB against
build at 155s/4.7GB" -- transcribed output preserved as authority inside a .dag
module. That is the measurement-bankruptcy rule violated in as many words, by
the same branch that ported the corrected rule into the design document. The
enduring claim is qualitative and is what the note now carries: cargo check
establishes the required compile diagnostics without paying for final binary
production. The producer that re-derives the comparison is named instead, and
the magnitudes are left as a property of the run that took them.

THE ARGV FORK IS NAMED WITH A TERMINUS RATHER THAN ONLY A BLOCKER. The previous
commit measured why extdeps.rust.cargo_build cannot be consumed -- every cargo
argv word there lives only inside a transport shell block -- but stating the
blocker is not the same as bounding the debt. The thread's accepted second form
is taken: this renderer is declared a bounded workflow adapter, not a second
cargo authority of equal standing, with the three-way split it adapts across
written down and a dissolution trigger naming its end. It dissolves when the
operation shape is de-fused from transport shell and can bind a workflow-text
realization, at which point this module produces a typed invocation and the
argv words leave it entirely rather than being spelled correctly here.

The reviewer instruction that follows from that is stated on the carrier, since
a terminus nobody enforces is decoration: refuse any NEW argv word added here
that the modeled surface already owns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DRMbwdtHZxTiMNZD5WLS3P
emit_shared_expr dispatches nineteen expression forms by delegating each to
an injected per-target callback. ExprLambda was the one arm that did not: it
called emit_lambda_params directly, whose param_names: List<String> signature
is structurally incapable of carrying a type. So an inferred closure parameter
type this compiler had already resolved was discarded one call before the
point it was needed -- and discarded in a SIGNATURE rather than in a branch,
which is why no arm is missing, nothing looks incomplete, and neither a
reviewer nor an exhaustiveness check would ever flag it.

This is a reach gap, not a missing capability. Measured on the 173 emitted
files of the v2 compiler closure: 971 closures already emit fully typed
through the Rust-specific collection route, 139 carry a hole, and 1146 emit
fully untyped -- of which 254 produce E0282. languages.dag has bound Rust's
lambda_param_typed the whole time; the generic route simply could not reach
it. So ExprLambda becomes a delegating arm like its siblings and Rust injects
the typed producer, rather than a second renderer being grown beside the one
that works.

The generic emit_lambda_params is deliberately untouched and still serves
Python, Go and Dag. That is not scope discipline, it is soundness: Python
binds lambda_param_typed to "{0}: {1}" over a "lambda {0}: {1}" template, so
routing Python through it would emit "lambda x: int: body" -- invalid syntax.
That binding is unreachable today and is a fabricated plausible value sitting
in the field this change starts consuming; the injected-callback shape keeps
it unreachable by construction rather than by a test someone must remember.

unavailable_stays_untyped is true on the new route and false on both existing
callers. The collection route reaches the helper with a real element type, so
its Absent arm means "the exact type did not render" and an explicit _ is a
decision that route has already taken. The new route has no fallback to offer,
so Absent means only "no annotation is available", and emitting _ there would
convert an honest absence into a claim. Staying untyped keeps this change
strictly additive: annotate where an exact renderable type exists, change
nothing anywhere else. Whether an unavailable type should refuse instead is a
separate decision over a separately measured population and is not taken here.

ESTABLISHED BY EXECUTION, source level only:
  treatment  emit_shared_expr arity 21, emit_rust_lambda_params_typed resolves
  control    emit_shared_expr arity 20, NoSuchFunction
  both typecheck v1.compiler.emit_rust and v1.compiler.infer green

NOT ESTABLISHED, and the reason is recorded because a clean-looking run
already lied once here: emitted output is unvalidated. A .dag edit is inert
until the mirror regenerates, and the available instrument is built from the
unpatched mirror. An earlier whole-corpus arm reported treatment and control
identical, which was worthless -- a deliberate unresolved symbol planted in
05_emit.dag produced zero diagnostics, because --source-root src/v1 indexes
those modules without compiling them (every one of the 5600 diagnostics has a
path under dag/, none under src/v1). The run's own summary names src/v1 as a
compiled root that it never opened.

The joined site population -- the 254 candidates and 87 residue partitioned
into retired, converted and unchanged, with the 971 typed and 139 partial-hole
closures held byte-identical as negative controls -- requires a bootstrap
round and is not claimed here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DRMbwdtHZxTiMNZD5WLS3P
@gunbai-bot gunbai-bot Bot changed the title v2 self host Give the Rust lambda route a parameter type it already knew Aug 25, 2026
Brian Searls and others added 2 commits August 25, 2026 08:52
The .dag edit in 734aa40 changed nothing on its own. A .dag edit is inert
until the mirror regenerates, and CI said so precisely -- required-ci regen
FAIL generated surface drift on v1_compiler_emit.rs and v1_compiler_emit_rust.rs,
the two mirrors of the two edited sources. This is that regeneration, produced
by the compiler rather than by hand.

The bootstrap round, so the provenance of these bytes is checkable rather than
asserted: build claim_executor from the CURRENT committed mirror (which does not
contain the change), run --required-regen against dag and src/v2, and install the
two files it wrote to target/stage0-regen-candidate. Production precedes
adjudication, so the candidate exists whatever the comparison then says about it.
The local run reproduced CI's failure exactly -- same verdict, same two files --
before writing the tree, which is what establishes that this instrument and CI's
are measuring the same thing.

WHAT THE EMITTED DIFF IS: 12 lines in v1_compiler_emit.rs, 65 in
v1_compiler_emit_rust.rs. The injected callback in the shared dispatch

  emit_lambda_params_for: impl Fn(Rc<Node>) -> String + Clone,

and Rust binding it to the typed producer. Nothing else moved. The
implementation stays small and bounded; the site movement lives in what this
compiler now EMITS, not in what is written here.

ESTABLISHED BY EXECUTION: stage0 builds green from this mirror, 0 errors, so the
compiler still builds itself after the change. That is the buildability control,
and it is the first evidence of any kind about the change's emitted output --
everything before it was source-level only.

STILL NOT ESTABLISHED, and a green build must not be read as it: the joined site
population. Compiling proves the emitted code is well-formed. It does not say how
many of the 254 E0282 candidates retired, whether the 87 residue moved, or
whether the 971 already-typed and 139 partial-hole closures stayed byte-identical
as the negative controls require. That measurement needs this compiler to re-emit
the v2 closure with a cargo check over the result, and it is reachable for the
first time now that this mirror exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DRMbwdtHZxTiMNZD5WLS3P
A Bool at a call site is invertible by accident and says nothing about what
either value means. Naming the operation puts the policy in the thing the
caller invokes, so selecting the wrong one requires calling a differently
named function rather than passing the wrong literal.

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

gunbai-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Side-chat ruling log — this PR is BLOCKED on #9192, and its bootstrap evidence is withdrawn

Root cause found, and it is not in this PR. The three rustc errors this branch hit are two sites, and both trace to inference, not to the lambda route:

v1_compiler_emit.rs:6823  |node: Rc<MatchPattern>, d|   E0308 + E0282   -> #9192
v1_compiler_trace.rs:299  |m: Option<Rc<SpanMapping>>|  E0609           -> a separate higher-order path

05_emit.dag:2993 passes seven named arguments in a different order than emit_typed_match_unified declares them, and expected-type propagation bound them by position, so the recurse lambda was inferred against render_pattern's fn(MatchPattern) -> String. Proven by execution: reordering the call site alone flips the annotation to |node: Rc<Node>, d: i64|.

So this PR was faithfully publishing a type constructed wrongly upstream. It cannot be admitted against the current inference output.

Landing sequence (operator ruling):

  1. Hold the argument-to-formal rule in one place: inference bound named arguments by position, refusing valid programs #9192 reviews and merges independently
  2. rebase this onto the exact landed Hold the argument-to-formal rule in one place: inference bound named arguments by position, refusing valid programs #9192
  3. discard the seven local mirrors — done, they were a known-rejected S2 candidate
  4. regenerate from a clean compiler built from the corrected inference tree
  5. check whether the trace/map failure remains; if so it is a separate repair (P2), not this one

Evidence withdrawn from this PR's record: the earlier "one-line product delta" measurement used an instrument containing hand-corrected mirrors. It is causal evidence for opening #9192, not a bootstrap receipt for this branch.

What survives here: the callback-delegation architecture in emit_shared_expr is the correct target-side construction — the shared arm delegates, target emitters supply behaviour — and UnavailableLambdaParamPolicy replaced an invertible Bool with two named operations. Neither is affected by the inference defect.

Not a policy gap. One lambda showed two failure modes: param 0 got a type that was present and wrong, param 1 got nothing. That is not a missing policy arm — pn.inferred is the correct carrier and the value arriving in it was wrong. No third variant is warranted.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fd38a5cb5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/v1/05_emit_rust.dag Outdated
fn emit_rust_lambda_params_typed(lambda_expr: Node, scope: InferScope, shared_types: Set<String>, emit_info: EmitGraphInfo) -> String {
let ps = lambda_param_names_at(texpr: lambda_expr, source_indices: scope.type_env.source_indices)
let pn = lambda_expr.children |> skip(1)
let param_strs = lambda_param_type_strs(params: ps, param_nodes: pn, fallback_types: [], shared_types: shared_types, source_indices: scope.type_env.source_indices, emit_info: emit_info, fold_acc_uses_fallback: false, type_env: scope.type_env, unavailable_stays_untyped: true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep unresolved inferred type variables untyped

When an inferred parameter contains a placeholder nested in a resolved carrier, this route treats it as renderable and emits the placeholder as a Rust type. Compiling src/v2/compiler/00_compile.dag through the changed emitter produces, for example, non-generic sugar_variants_to_disj_edges with |edges: Rc<FreeMonoid<T>>|, even though no T is declared in that function, causing rustc E0412; the previous generic route emitted |edges| and allowed Rust to infer the concrete edge collection. The same output occurs at many fold_list(..., Rc::new(vec![]), ...) sites, so this callback should leave such parameters untyped unless every nested generic atom is actually bound in the enclosing Rust function.

Useful? React with 👍 / 👎.

Brian Searls and others added 2 commits August 26, 2026 06:44
…y and its realization agree again

The named-policy refactor changed src/v1/05_emit_rust.dag and left the emitted
mirror behind, so the required build lane refused with

  regen FAIL generated surface drift: v1_compiler_emit.rs, v1_compiler_emit_go.rs,
  v1_compiler_emit_python.rs, v1_compiler_emit_rust.rs, v1_compiler_infer.rs,
  v1_compiler_infer_resolve.rs, v1_compiler_trace.rs

measured on this branch merged with main. Main's own build lane is success at
7de77a4, so the drift is this branch's and not inherited.

These bytes are the regen producer's candidate tree, not hand edits: the fix is
installed from target/stage0-regen-candidate, which is the only sanctioned route
now that the producer half was re-derived. An independent file-by-file comparison
of the candidate against the committed tree differs on exactly the seven files the
gate named -- no more, no less -- so the gate's report and the comparison agree.

Worth recording because a review said otherwise: the approving review of this PR
states "Stage0 mirror matches". Measured at the PR head, the mirror contained zero
occurrences of UnavailableLambdaParamPolicy against two in the authority. It now
contains seven. A review is advisory and this one was wrong on a checkable fact.

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

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Not ready. Regenerating the mirror turns this PR's change into a hard compile error, and the stale mirror is why two approvals missed it.

Measured, not inferred. Recording it here because the PR now looks worse than before and that is the correct state.

1. The mirror was stale, and a review said otherwise

The approving review states "Stage0 mirror matches". Measured at the PR head b099261d66:

UnavailableLambdaParamPolicy occurrences
src/v1/05_emit_rust.dag (authority) 2
src/v1/stage0/src/v1_compiler_emit_rust.rs (mirror) 0

The required build lane agreed, refusing with regen FAIL generated surface drift naming seven files. Main's own build lane is success at 7de77a497, so the drift is this branch's, not inherited.

dc4e4b773a installs the regen producer's candidate tree for exactly those seven files. An independent file-by-file comparison of candidate against committed differs on exactly the seven the gate named — no more, no less.

2. With the mirror honest, the emitted compiler does not compile

error[E0609]: no field `source_span` on type `std::option::Option<Rc<SpanMapping>>`
   --> src/v1/stage0/src/v1_compiler_trace.rs:299:41
    |
299 |     .map(|m: Option<Rc<SpanMapping>>| m.source_span.clone())

One error, whole tree. The site:

emitted closure param
origin/main |m| — untyped, compiles
PR head, stale mirror |m| — same
after real regen |m: Option<Rc<SpanMapping>>| — the receiver's type

3. Root, at the authority rather than the emitter

source_map.mappings
  |> filter(m => m.generated_line <= generated_line)
  |> last                     // SpanMapping?  -- an Option
  |> map(m => m.source_span)  // Option::map, so m binds SpanMapping

m binds the element. Inference records the option. That is an inference defect that predates this PR — it was simply invisible while the Rust route emitted lambda params untyped. This change makes the recorded type load-bearing, so a latent wrong fact becomes a build break.

That is the change working as intended and is not an argument for reverting it. It is DESIGN §5 exactly: a wrong answer became loud instead of staying silent.

4. Why this is not a one-line fix, and what the decision is

UnavailableLambdaParamPolicy splits available from unavailable. This site is neither — the type is available and wrong, a third state the policy cannot express, so PreserveUntyped does not reach it. Two honest routes:

  1. Fix inference so map over T? binds T. Correct at the root, blast radius unmeasured.
  2. Narrow the emission policy to decline a recorded type the route cannot corroborate — but "available and untrustworthy" needs modelling first, or it becomes a silent widen.

Special-casing this one call site is the third option and I am not taking it: that is the workaround DESIGN names as a line-stop signal, not a landing state.

Holding here for direction rather than improvising past the doubt. The branch is now honestly red on a real defect instead of falsely green on a stale artifact.

— sent from deep-ant-102

…rom that rule

`for_each_element_type_node` extracts a carrier's element as its single CHILD.
The many-carrier holds its element that way, so the extraction finds it. The
optional carrier does not: in the substrate `T?` marks optionality as the kernel
CardOptional CARDINALITY ON THE NODE, with no child to descend into. So an
optional carrier fell past the extraction into the Absent arm, which returns the
carrier -- and every caller recorded the CARRIER where the ELEMENT was meant.

This is a restoration of uniformity, not a new rule. `mappings` and
`mappings |> last` are two carriers of one concept and map already bound the
element of the first. Stripping runs BEFORE the child extraction, so `List<T>?`
yields `List<T>`: one cardinality removed, never two.

WHY IT WAS INVISIBLE. Nothing consumed the recorded type until the Rust route
began annotating lambda parameters. Then it rendered as
`|m: Option<Rc<SpanMapping>>|` over a body naming the element's fields, and rustc
refused. The defect was never in the emitter; the emitter removed the silence.

EVIDENCE, and the bootstrap is the point. A compiler cannot demonstrate its own
inference fix in one generation: gen1 is emitted by the compiler that still has
the defect. So gen1's mirror was crossed with a throwaway hand-patch of the single
broken line -- never committed, and it touches only the mirror used to BUILD the
bootstrap compiler, never the `.dag` the compiler emits FROM. Then:

  BOOTSTRAP_BUILD_RC=0   the bootstrap compiler builds and contains the fix
  GEN2 derived line      `.map(|m: Rc<SpanMapping>| ...)` -- the ELEMENT type,
                         derived by the fixed compiler from trace.dag, not inherited
  GEN3_BUILD_RC=0        the derived output compiles
  FIXPOINT_RC=0          first_generation_equal=true, planned=136 executed=136

The fixed point is the strongest available check: the compiler independently
derives exactly the line written by hand as a bootstrap, so patch and derived
output agree and the seed reproduces itself.

WHAT IS NOT CLAIMED. The population is at least two, not one. Two instruments --
an end-to-end compile and a corpus grep -- agreed on one site, and their agreement
was not evidence: they share a blind spot, since an optional produced by a NAMED
function is disproportionately corpus code outside the stage0 closure, which is
exactly what the compiler's own `unresolved_method_frontier` records as a second
row (gunbc.source_integration_landing_spine, `list_at_optional(..) |> map(..)`).
That roster is a third partial instrument, not a census, so no total is asserted.

Those two frontier rows are deliberately NOT deleted here. They govern METHOD
LOOKUP -- whether `map` resolves on an optional receiver -- while this change
governs ELEMENT-TYPE EXTRACTION. Related, not identical. Citing this fix as their
dissolution without measuring that the diagnostics disappear would be the
authority-substitution failure: two true halves and an invented arrow.

v1 admission: the 2026-08-20 purpose test. Lambda-param typing on the emission
path is the v2 self-host program itself.

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

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Fixed at the root, and the fix is proven by a self-host fixed point. Review [56181] confirmed independently — thank you, and the diagnosis was exact.

Review 56181 called this correctly from the emitted artifact alone, including the root ("the .dag route-through here fed the receiver's type rather than the closure argument's element type") and the instruction to sweep the class. Two independent readers, same root, neither from my own instruments.

The root, one level below the emitter

src/v1/trace.dag:

source_map.mappings
  |> filter(m => m.generated_line <= generated_line)
  |> last                     // SpanMapping?  -- an Option
  |> map(m => m.source_span)

for_each_element_type_node extracts a carrier's element as its single child. The many-carrier holds its element that way. The optional carrier does not — in the substrate T? marks optionality as the kernel CardOptional cardinality on the node, with no child to descend into. So an optional carrier fell past the extraction into the Absent arm, which returns the carrier, and every caller recorded the carrier where the element was meant.

The fix strips the cardinality before the child extraction, so List<T>? yields List<T> — one cardinality removed, never two. This is a restoration of uniformity, not a new rule: map already bound the element of the many-carrier.

The emitter did not create this. It removed the silence. Nothing consumed the recorded type until the Rust route began annotating lambda params.

Evidence — a three-generation bootstrap

A compiler cannot demonstrate its own inference fix in one generation: gen1 is emitted by the compiler that still has the defect. So gen1 was crossed with a throwaway hand-patch of the single broken line — never committed, touching only the mirror used to build the bootstrap compiler, never the .dag it emits from.

step result
bootstrap build 0 — compiler builds, contains the fix
gen2 derived line .map(|m: Rc<SpanMapping>| ...) — the element type, derived from trace.dag
gen3 build 0 — the derived output compiles
fixed point first_generation_equal=true, planned=136 executed=136

The compiler independently derives exactly the line hand-written to cross the bootstrap, so patch and derived output agree and the seed reproduces itself.

On the sweep — the class is at least two, and I am not asserting a total

Two instruments (end-to-end compile, corpus grep) agreed on one site. Their agreement was not evidence — they share a blind spot, since an optional produced by a named function is disproportionately corpus code outside the stage0 closure. The compiler's own unresolved_method_frontier records a second row of exactly this class: gunbc.source_integration_landing_spine, list_at_optional(..) |> map(..). That roster is a third partial instrument, not a census.

Both frontier rows are deliberately left in place. They govern method lookup — whether map resolves on an optional receiver — while this change governs element-type extraction. Related, not identical. Deleting them because the fix "felt general" would be two true halves and an invented arrow.

v1 admission: the 2026-08-20 purpose test — lambda-param typing on the emission path is the self-host program itself.

— sent from deep-ant-102

@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

HOLD — this PR is a PublicSurfaceGrowth instance on BOTH readings of the undrawn boundary

Marking this draft rather than letting it merge at ready=True. Found by applying smart-ram-730's #9312 governance ask to my own diff, not by review.

gunbc.v1_maintenance_standing lists PublicSurfaceGrowth as a refusal class that dominates every admission, and v1_maintenance_rung_note gives the decidable test: "a diff over the emitted seed's exported declarations." Measured against origin/main:

New exported declarations (narrow reading) — 6, added 0 removed:

+pub enum UnavailableLambdaParamPolicy {
+pub struct PreserveUntyped;
+pub struct RenderFallback;
+pub fn lambda_param_strs_preserving_unavailable(
+pub fn lambda_param_strs_using_fallback(
+pub fn emit_rust_lambda_params_typed(

Change to existing exported surface (broad reading) — 1: pub fn lambda_param_type_strs gains a ninth parameter, unavailable_policy: UnavailableLambdaParamPolicy — a breaking signature change to a symbol the seed already exported.

So this does not sit on the undrawn boundary the way v1_maintenance_typecheck_progress_admission_note records for its own change — it is inside the refusal class under either reading, which is why I am not classifying it myself.

Why I am not citing the existing admissions as precedent. v1_maintenance_typecheck_progress_admission_note states its own scope limit explicitly: "Nobody may cite this row as the answer to that question... treating this admission as precedent for a general reading of PublicSurfaceGrowth would be the authority-substitution failure." The 2026-08-20 purpose test (does the change serve v2 self-host — this one does; it repairs a real inference defect) does not help either, because the refused classes dominate admissions rather than being weighed against them.

Status: ready=True, 1/1 approvals, 0 request-changes, MERGEABLE, build/floor/witnesses all green. Nothing is wrong with the change on its merits and I am not withdrawing it. It passed four review attempts (56138, 56174, 56181, 56211) with none raising this, which is itself evidence for smart-ram-730's argument that the class is currently inert — a refusal that dominates on paper and has never fired.

This flips back to ready the moment the boundary is drawn in either direction. If PublicSurfaceGrowth means new exported declarations only, or does not reach internal pipeline helpers in a v1 stage, this was always admissible and I will restore it unchanged.

@gunbai-bot
gunbai-bot Bot marked this pull request as draft August 26, 2026 11:10
gunbai-bot Bot pushed a commit that referenced this pull request Aug 26, 2026
…n opposite sides of the undrawn line

PublicSurfaceGrowth is defined nowhere; its only stated test is a future-tense sketch
in a next-rung trigger, and against the actual mirror it selects everything.
Independently re-measured here: the emitted mirrors carry 2863 pub fn and ZERO
non-pub, because .dag has no visibility concept -- so "new exported declaration"
means "new declaration", catching 57 of 102 commits in five days.

Recorded with this lane's own near-miss while checking it: a first pass over the
whole stage0/src directory returned 2029 non-pub fn, which reads as a flat
refutation. Those are hand-authored host files -- cli_run.rs 1169, v1_interpreter.rs
398 -- not emitted mirror. One directory too wide and a true finding renders as
false. The denominator failure this document keeps recording, committed while
checking someone else's denominator.

And the vacuity must not be read as therefore-admit-everything. The two blocked cases
separate under a repaired criterion: #9182's six new exported HELPERS would be
module-private and never touch public surface, so it likely clears; a REQUIRED FIELD
on the exported Node type is a shape change that function privacy cannot reach, so it
may still be refused and deserves arguing on merits.

Two independent lanes, different subsystems, same undrawn line, same day, and
repaired the line would rule DIFFERENTLY on them. A criterion that cannot separate
these two is failing on the one pair that most needs separating -- which converts a
PR-specific admission question into a governance question blocking work in two
places.

This lane recuses from drawing the boundary: it is an interested party, and a
criterion redrawn by the author of a change it would admit is not a criterion.
Seven generated mirrors conflicted. The generated-artifact merge driver refused them by design -
ours side in the worktree, no conflict markers, path left unmerged - because neither side's bytes
are the projection of the MERGED authorities.

They were NOT hand-resolved. They are regenerated output, and reaching them took THREE rounds,
which is the point worth recording: this branch changes the EMITTER (05_emit*.dag), so each
rebuild changes what the emitter produces. A single pass would have "verified" at the wrong fixed
point - exactly what the merge driver's own recipe warns about ("the first pass runs a binary that
predates the change it emits, so one pass can self-verify at divergence 0 for the wrong reason").

  round 1  borrowed binary        10 files named
  round 2  binary from installed seed   7 files named
  round 3  binary from installed seed   first_generation_equal=true, no drift

CONVERGED. Round 3 is the receipt: the emitter, rebuilt from the mirrors it just emitted,
reproduces them exactly.

Independent corroboration that the mirrors are right: before installation this tree failed to
compile with 36 errors (its mirrors were the pre-merge ours side against post-merge .dag sources);
after installation claim_executor builds clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls marked this pull request as ready for review August 28, 2026 23:25
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-08-28T23:30:18.251136Z 91ecabc Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91ecabc6af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/v1/04_types.dag
let carrier = normalize_access_type_node(n: n)
let carrier_is_optional = carrier.return_cardinality == CardOptional
let normed = if carrier_is_optional { with_required_cardinality(n: carrier) } else { carrier }
let is_single_child = normed.connective == NoConnective && normed.children |> count == 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop extracting after unwrapping an optional carrier

When the optional value is itself parameterized, such as List<Int>?, normed becomes the required List<Int>, but is_single_child remains true and the following branch extracts and returns Int. Consequently, an optional map lambda is inferred as receiving Int even though Rust's Option::map supplies the whole List<Int>, causing incorrect body resolution or lambda annotations. Gate child extraction on !carrier_is_optional, or return normed immediately after removing optionality.

Useful? React with 👍 / 👎.

@gunbai-bot

gunbai-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant