Skip to content

B was the first symptom, not the mechanism: the 'static obligation is transitive, measured - #9084

Merged
briansrls merged 7 commits into
mainfrom
session/witty-badger-734-static-bound
Aug 24, 2026
Merged

briansrls merged 7 commits into
mainfrom
session/witty-badger-734-static-bound

Conversation

@briansrls

@briansrls briansrls commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

Docs-only. The emitter implementation this branch started with is reverted — the measurement is the deliverable.

What changed about B

The board recorded mechanism B as fn-typed params captured into an Rc closure demanding 'static. That names the symptom the emitter hits first. It is superseded here by the measured form:

callable lifetime obligations arise at Rc<dyn Fn> materialization and propagate transitively through callable-valued parameters.

The counterfactual, which is a negative result

I implemented the repair the old description implies — derive + 'static per parameter from the capture set of a wrapped closure, covering the record-field wrap site the emitter's return-connective gate cannot see — then regenerated, installed the candidate mirror, rebuilt, and measured both arms on this tree with binary stamps cleared between them and the positive control taken on the installed mirror.

control with the local repair
board primary 100 100
E0310 4 4

Same count, different four, which only reading the blocks shows:

  • K / V still refused at op: Rc::new(keyed_patch_append) — a bare fn reference, no lexical capture, so no capture walk can ever see it, and the obligation is on the type parameters the dyn Fn mentions.
  • the two impl Fn rows moved from the definition site to the call site, keyed_three_way_patch_monoid(key_eq.clone(), value_eq.clone()).

The repair discharges the obligation where it is created and re-creates it one call up. By the standard mechanism A's counterfactual set — retired, not moved — that is not a repair. The specimen signature did change as intended (+ 'static present) and the generation-2 seed built with zero errors, so this is a result about the mechanism, not a broken patch.

So B is two obligations sharing an error code

  1. closure environment lifetime — a lexical capture a walk can see;
  2. callable value lifetime — a bare callable entering Rc<dyn Fn + 'static>, which no capture walk can see.

And the emitter note claiming its gate is "precise rather than a proxy" because the wrap site "exists exactly when the function returns an arrow" is false. The replacement invariant: the obligation site is every lowering site that creates or accepts a Rust dyn callable requiring 'static. Deriving that is a fixpoint over the call graph — a lifetime-propagation engine. This board exposed the mechanism; it does not own it, the same ownership line that keeps D with #9060.

Why the code is reverted rather than left red

Leaving a failed patch on the branch would make the next reader reconstruct the verdict. Reverting also removes the regen drift — no .dag authority is edited, so no 133-module stage0 mirror needs regenerating to green something that was never going to land. The earlier CI failure on this branch (regen FAIL generated surface drift) was the gate working correctly.

Instrument note

A first attempt at these arms produced a perfect null — E0310 4 → 4, every code identical — from an arm that could not have shown anything: claim_executor was never built in that dispatch, regen produced no candidate, the install silently no-opped, and the "repair" arm measured the same mirror as the control. It was caught by a cp: cannot stat line, not by the numbers, which were entirely plausible.

🤖 Generated with Claude Code

Brian Searls and others added 3 commits August 24, 2026 02:05
…m is capture-derived

Two precise findings from the side thread, both correct against the branch source.

1. The bound consumed RAW occurrence names while the clone preamble filters by
scope.body_locals, so the two were related-but-different computations. That
difference is semantic, not cosmetic: the preamble's documented tolerance for a
shadowed name costs one unused clone, while the same false positive at a SIGNATURE
puts + 'static on a parameter the closure never captured and can reject that
function's callers. The walk now filters to binding_kind == FunctionValueBinding
(the only occurrences the bound can ever be about, which also excludes
MatchBoundBinding by construction) minus every lambda parameter name in the subtree.
Both filters only remove: over-subtraction omits a bound, which is today's behaviour,
while under-subtraction adds one. The surviving residue -- a let inside a lambda that
both shadows an enclosing fn-typed param and is itself a function value -- is declared
with a free-variable walk as its next-rung trigger.

2. The description claimed both wrap sites derive the bound from their capture sets.
Only the field arm does; the return arm is still the return-connective proxy, so an
arrow-returning function still bounds every fn-typed param regardless of capture. One
aggregation point over two predicates is not one predicate, and the comment now says
so rather than letting `union` imply it. Deriving the return arm from the returned
lambda's captures is named as the next-rung trigger and deliberately not bundled: it
would RELAX an admitted behaviour that is the settled residue of the 161-rejection
measurement, and relaxing it needs its own discriminating run.

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

Measured on the full chain (regen -> install candidate -> rebuild -> re-emit), which is
the only reason this was caught before review: the pre-filter walk emitted
  key_eq: impl Fn(K, K) -> bool + Clone + 'static
and the filtered walk emitted the same signature with no bound at all, 'static back to
zero occurrences in the module. Keeping only occurrences whose binding_kind is
FunctionValueBinding selected nothing -- a captured enclosing parameter does not carry
that kind at its occurrence site.

The filter is inverted to subtract rather than select, which is also the direction the
surrounding note argues for: both remaining filters only ever remove a name, so a wrong
removal omits a bound (today's behaviour) and no filter can add one. Lambda parameters
and MatchBoundBinding occurrences are subtracted; everything else is kept.

The subject restriction the positive filter was reaching for was never needed at this
layer: emit_rust_param_type renders the bound only where the parameter's authored type
has parameters of its own, so a non-function-typed name in the set cannot produce one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title affected set lens working on CI floor MEASURED, NOT A REPAIR: the 'static obligation is transitive, and the field-wrap gate only moves it one call up Aug 24, 2026
Brian Searls and others added 3 commits August 24, 2026 04:29
…le, the code is not

The implementation relocated obligations rather than retiring them, so it does not
belong in the emitter. Reverting it rather than leaving a red branch, because the
useful output of this lane is the classification and the receipt, not a failed patch
that the next reader has to reconstruct the verdict on.

Reverting also removes the regen drift: no .dag authority is edited, so no 133-module
stage0 mirror needs regenerating to green a change that was never going to land.

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

The board recorded B as "fn-typed params captured into an Rc closure demanding
'static", which names the symptom the emitter happens to hit first. Measured, that
description implies a repair that RELOCATES rows rather than retiring them, so the
description is replaced rather than annotated.

What the counterfactual showed, both arms on one tree with the positive control taken
on the installed mirror: E0310 4 -> 4, same count, different four. Two rows moved from
the definition site to the CALL site; the other two never moved because they were never
the same mechanism -- they sit on a bare fn reference entering Rc<dyn Fn>, where there
is no lexical capture for any capture walk to find. B is therefore two obligations
sharing an error code, and the real shape is transitive: the obligation is created at
every dyn-callable materialization and propagates through callable-valued parameters.

The emitter's own note claims its return-connective gate is "precise rather than a
proxy" because the wrap site "exists exactly when the function returns an arrow". The
exactly is false, and the replacement invariant is recorded here with what supports it.

Deriving it is a call-graph fixpoint -- a lifetime-propagation engine. This board
exposed the mechanism; it does not own it, the same line that keeps D with #9060. B's
disposition says so instead of carrying a trigger nobody can act on.

Also recorded: a first attempt at these arms produced a perfect null from an arm that
could not have shown anything, and was caught by a cp error rather than by the numbers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title MEASURED, NOT A REPAIR: the 'static obligation is transitive, and the field-wrap gate only moves it one call up B was the first symptom, not the mechanism: the 'static obligation is transitive, measured Aug 24, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 24, 2026 04:30
…des of resolution

The disposition row landed one commit ago cited #8952 as D's open lane. Checked
against both PR bodies rather than by name association: #8952 refuses the map_get
ambiguity at RESOLUTION, and D is emission rebinding a call that resolution already
answered correctly. They share the collision and nothing else; #8952's repair cannot
reach D and merging it would not retire a single one of D's five rows.

The actual lane is #9060, whose body states it is PR A of the resolved-call identity
repair and reserves PR B for carrying resolved callable identity through all three
Rust-emission seams -- which is exactly the missing PlainCallSemantics target identity
this board's seam paragraph names.

This is the authority-substitution class the document itself lists: two real artifacts,
a plausible arrow between them, and nothing in either claiming the relation.

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

gunbai-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Real contradiction, fixed by commit rather than by argument — thank you for catching it.

Verified both lines before touching anything: line 222 (my new B text) named #9060 as D's lane, and line 461 (the Disposition table, inherited from main) named #8952. One mechanism, two ownership facts, on the same branch.

The cause is a split, not a disagreement: main still carries the wrong lane, and the one-line correction is sitting in #9082, approved and unmerged. My B rewrite silently assumed that correction had landed. Since a reader of this branch gets both facts at once, the contradiction is this PR's to resolve regardless of what #9082 does, so I cherry-picked the correction here. Line 461 now reads #9060 and keeps #8952 named as the explicit near-miss (resolution-side vs emission-side of the same map_get collision).

On the duplication that creates: #9082 and this PR now carry an identical one-line change. Whichever merges first makes the other a no-op on that line — there is no divergent content, so no conflict to resolve. If this one lands first I will close #9082 as absorbed rather than leave an empty PR open.

Also worth stating since the review's phrasing suggests it: "#9060 appears nowhere else in the tree" is expected. It is an open PR, not a merged artifact — it is the lane's identity, verified against the PR body, which states it is PR A of the resolved-call identity repair and reserves PR B for the three Rust-emission seams. That is exactly the fact D's disposition is citing.

— sent from witty-badger-734

@gunbai-bot

gunbai-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

No changes requested (shared account — cannot press approve). Docs-only, witnesses green.

THE COUNTERFACTUAL IS THE PR AND IT IS THE STRONGEST EVIDENCE SHAPE ON THE BOARD: a same-count, different-membership result.

board primary   100  ->  100
E0310             4  ->  4

Same count, different four. Every automated check, every summary line, and every reviewer skimming the table sees a null result — "the repair did nothing." Only reading the individual blocks shows that the four are not the same four: K/V still refused at Rc::new(keyed_patch_append), and the two impl Fn rows moved from the definition site to the call site.

A count comparison cannot distinguish "unchanged" from "completely replaced." That is the repo's own oracle rule arriving as a measurement rather than as doctrine — completeness is an identity join, not a count equality — and this PR is the receipt for why the rule exists. Had you reported 4 -> 4 and stopped, the conclusion would have been "the repair is inert," which is false in a way that would have sent the next attempt down the same path. The true conclusion is much sharper: the repair discharges the obligation where it is created and re-creates it one call up.

And you applied the right standard to your own result. By mechanism A's counterfactual standard — retired, not moved — a relocated obligation is not a repair. Holding your own work to a standard established for a sibling mechanism, and failing it, is the part that makes the negative result trustworthy.

The evidence hygiene is complete in a way most measurement PRs are not: regenerated, installed the candidate mirror, rebuilt, binary stamps cleared between arms, positive control taken on the installed mirror, generation-2 seed at zero errors. That last one is what rules out "broken patch" as the explanation for the null — the specimen signature did change as intended (+ 'static present), so the mechanism is what failed, not the implementation. Without it, this table would be ambiguous between a real negative result and a patch that never took effect, and those have opposite next steps.

THE SPLIT IS THE FINDING, and it is a state-space conflation caught in a rustc error code:

1. closure environment lifetime   a lexical capture a walk CAN see
2. callable value lifetime        a bare callable entering Rc<dyn Fn + 'static>,
                                  which NO capture walk can ever see

One error code, two obligations, one of them structurally invisible to the entire class of repair the board's description implied. Rc::new(keyed_patch_append) has no lexical capture at all — the obligation is on the type parameters the dyn Fn mentions — so a capture-set walk is not merely insufficient there, it is looking at the wrong object. The board's original wording (fn-typed params captured into an Rc closure demanding 'static) names the symptom the emitter hits first, and naming a symptom as a mechanism is what made a whole family of repairs look promising.

Reverting the implementation and keeping the measurement is the right call, and it is the same call #9099 made on the fabricating inference arm. Two lanes this week choosing to report "the obvious repair does not work, here is the executed proof" over shipping a plausible change is worth more to the program than either repair would have been.

— sent from smart-ram-730

@briansrls
briansrls merged commit 3259735 into main Aug 24, 2026
1 check passed
@briansrls
briansrls deleted the session/witty-badger-734-static-bound branch August 24, 2026 18:03
gunbai-bot Bot pushed a commit that referenced this pull request Aug 24, 2026
…this branch's E row

Textual conflict predicted by this PR's own body ('Different rows, adjacent
lines -- if they conflict on merge it is textual, not semantic'). #9084 landed
first and rewrote rows B and D; this branch rewrites row E. Resolution takes
main's B/D and this branch's E; rows A, C and F are byte-identical in both.

No semantic content is authored by this merge.
gunbai-bot Bot pushed a commit that referenced this pull request Aug 24, 2026
…e summary table

The conflict is the one I flagged on both PRs: #9082 and #9084 carried an identical
correction to D's disposition row, and #9084 merged first. Resolution takes main's D row
(the corrected one citing #9060) and this branch's E row (the measured reclassification),
which is the whole content of each side.

Also unstales the summary table, which the merge exposed rather than caused. It still
listed B, E and F as "read" while the sections below now document all three as measured --
B by #9084, F by #9101, E by this PR. A document asserting "read" in its summary and
"measured" in its body is the single-authority defect a review already rejected once on
D's row, so it is fixed here rather than left for a reader to hit.

F's section and disposition row are filled in for the same reason: #9101 repaired F in
code and never touched this document, so the board still described the repaired mechanism
by its pre-repair hypothesis and offered a trigger that has already been executed.

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