Repository navigation
Conversation
…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
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
Side-chat ruling log — this PR is BLOCKED on #9192, and its bootstrap evidence is withdrawnRoot 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:
So this PR was faithfully publishing a type constructed wrongly upstream. It cannot be admitted against the current inference output. Landing sequence (operator ruling):
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 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 — |
There was a problem hiding this comment.
💡 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".
| 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) |
There was a problem hiding this comment.
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 👍 / 👎.
…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>
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 otherwiseThe approving review states "Stage0 mirror matches". Measured at the PR head
The required
2. With the mirror honest, the emitted compiler does not compileOne error, whole tree. The site:
3. Root, at the authority rather than the emitter
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
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>
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
The fix strips the cardinality before the child extraction, so 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 bootstrapA 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
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 totalTwo 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 Both frontier rows are deliberately left in place. They govern method lookup — whether 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 |
HOLD — this PR is a PublicSurfaceGrowth instance on BOTH readings of the undrawn boundaryMarking this draft rather than letting it merge at
New exported declarations (narrow reading) — 6, added 0 removed: Change to existing exported surface (broad reading) — 1: So this does not sit on the undrawn boundary the way Why I am not citing the existing admissions as precedent. Status: 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. |
…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>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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".
| 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 |
There was a problem hiding this comment.
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 👍 / 👎.
|
|
What this is
emit_shared_exprdispatches nineteen expression forms by delegating each to an injected per-target callback.ExprLambdawas the one arm that did not — it calledemit_lambda_paramsdirectly, whoseparam_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:
|a: T, b: U||a: _, b: T||a, b|languages.daghas bound Rust'slambda_param_typedthe whole time and the collection route already consumes it. The generic route simply could not reach it. SoExprLambdabecomes 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_paramsstill serves Python, Go and Dag. That is soundness, not scope discipline: Python bindslambda_param_typedto"{0}: {1}"over a"lambda {0}: {1}"template, so routing Python through it emitslambda 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 hereunavailable_stays_untypedistrueon the new route,falseon both existing callers. The collection route arrives with a real element type, so itsAbsentarm 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, soAbsentmeans 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
emit_shared_exprarityemit_rust_lambda_params_typedNoSuchFunctionemit_rust/inferNOT established — why this is a draft
Emitted output is unvalidated. A
.dagedit 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.dagproduced zero diagnostics.--source-root src/v1indexes those modules without compiling them (all 5600 diagnostics have paths underdag/, none undersrc/v1; they are orphans nothing imports). The run's own summary line namessrc/v1as a compiled root it never opened.Still owed, per the approved acceptance controls:
Known interaction
B1 renders every annotation through
render_rust_type, the same function measured emittingRc<CommutativeSemiring<Magnitude>>whereNatwas 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.