Skip to content

A call site spelling a name held by both a local binding and a module-level free function reaches the FREE FUNCTION: silent wrong values where arities agree, and the nine-row join it measures - #9305

Closed
briansrls wants to merge 4 commits into
mainfrom
session/silent-otter-659

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Auto-opened by session-dashboard for session silent-otter-659.
Pushing to session/silent-otter-659 advances this PR.

Worker attestation

Before flipping this PR to ready for review, confirm each item:

  • Title describes the change (not the session id or branch).
  • PR body summarises what and why (replace the TODO below).
  • Tests run: name the command (e.g. npm test, cargo test) and the result.
  • If this closes a work item, the body contains a Closes #N directive.
  • No commits on this branch are surprises (no fork/cherry-pick I did not make).
  • No secrets / credentials / large binaries staged.

Summary

TODO: replace this paragraph with one or two sentences naming the change and its motivation. Reviewers read this first.

Test plan

  • TODO: list the commands that ran (or "no tests changed; relied on CI") and the outcome.

Brian Searls and others added 4 commits August 26, 2026 01:09
…ion sharing its spelling

THE DEFECT. A call site spelling a name held by BOTH a local binding and a
module-level free function reached the FREE FUNCTION whenever the local's value
was a reference to a named top-level function. Where the two arities differ the
call refuses loudly; where they agree it returns a plausible wrong value with no
diagnostic at all -- below floor (DESIGN §5), not a naming nuisance.

THE AXIS WAS THE VALUE VARIANT, NOT THE BINDING FORM. `eval_call`'s lexical gate
matched `Value::Closure` only, so the law held for a local bound to a LAMBDA and
failed for a local bound to a NAMED function, which evaluates to `Value::Fn`.
That is exactly the 3x2 grid two lanes measured independently (let / parameter /
pattern x named-fn / lambda): three cells wrong, three cells right, and the
column that separated them is the one the gate was matching on.

THE REPAIR. The gate now names the concept -- a lexical binding holding
something callable -- instead of one of its representations. A `Value::Fn`
binding is carried as the resolved callee through the same downstream dispatch
(witness, parse-table memo, pure memo) rather than short-circuiting, so
memoization is unchanged; only the tier that answers the spelling moves. The env
re-lookup that used to sit BELOW `ctx.lookup_fn` is deleted rather than kept
beside the gate: it was the lower-rung duplicate of the same decision, and
reaching it at all required the free-function table to have answered first.

MEASURED BY EXECUTION, not by typecheck. `v2.test.claim.local_binding_shadow`
under `claim_batch --hermetic` over the whole tree: the enrolled red
`local_named_function_binding_is_what_the_call_reaches` PASSES, and all three
green controls (the lambda column, the call-site rename, the uncollided binding)
still PASS -- so the repair is not a widening that swallowed its own controls.

The expected-red enrolment is deleted with its dissolution receipt in place. The
witness is NOT deleted with it: DESIGN §4b(4) keeps the discriminating evidence,
so the probe stays enrolled as a permanent regression control.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GqJoJVKcVjG7yk9zZ81PSu
# Conflicts:
#	src/v1/04_method.dag
#	src/v1/stage0/src/v1_compiler_infer_method.rs
#	src/v1/stage0/src/v1_interpreter.rs
#	src/v1/stage0/src/v1_interpreter_dispatch_generated.rs
#	src/v2/workflow/floor_expected_red.dag
…tion is repaid to zero

The expected-red removals in this PR orphaned eleven rows in
v2.workflow.floor_non_verdict, and the required floor refused with
NonVerdictRowUnreachable count=11 -- rows that read as debt while being
structurally incapable of being debt, which is exactly the decoration that wall
exists to refuse. They are DELETED, not re-enrolled: re-enrolling would restore a
falsehood to silence a wall.

THE CASCADE WAS LARGER THAN THE REFUSAL NAMED, because gunbc.floor_non_verdict_classification
joins that roster EXACTLY in both directions. Emptying the roster required repaying
its two live arms, and the result is the join that carrier declared it could not make:
closure_dependent_rows held nine ClosureDependentResolution rows whose attribution to
the local-binding-shadow defect was explicitly INFERENCE, with no colliding name
identified for six srv3 rows or two staging rows. Its unjoined_inference_note named its
own instrument -- fix the resolver and re-run; recovery measures the join, non-recovery
isolates a second cause. All nine recovered. There is no second cause.

unmeasured_rows held the two host_phase_status identities, classified StandingUnmeasured
on an honest obstacle (no narrow closure can resolve them, so no A/B arm existed). Both
recovered too. That is recorded as the weaker true statement -- a repair aimed elsewhere
recovered them -- and NOT as a retroactive reclassification.

Both arms stay DECLARED with repayment receipts, following the idiom the file already
established for closure_independent_rows: the causes remain constructible, and an emptied
arm with its reason recorded is a different fact from an arm that never existed.

The inhabitance witness row is deleted with the population it described. It asserted the
partition was non-empty, which was right while a debt existed and becomes an assertion
that the debt must CONTINUE to exist once repaid -- it would make the carrier unable to
reach the empty terminal state its own header declares. The join, uniqueness,
evidence-present and exact-partition rows are untouched and still gate.

An empty roster is the STRICTEST state here, not the most permissive: cli_run's polarity
comment records that a non-enrolled identity BLOCKS, so losing the roster can only red a
run, never flatter one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

DUPLICATE OF #9259 — same head branch (session/silent-otter-659), same subject, and #9259 already carries the whole delivery for this work item.

#9259 is open, has four approving reviews (the latest on its current head 2bbff527), zero request-changes, and all three required checks green: build=SUCCESS floor=SUCCESS witnesses=SUCCESS. Every commit this PR would carry is already on it.

I am deliberately NOT flipping this to ready. Two PRs on one head branch would double-review and double-merge the same commits, and the second merge would be a no-op that still consumes a floor run (~50 minutes). This should be closed in favour of #9259; I have left it in draft rather than closing it myself since I did not open it.

— sent from silent-otter-659

@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Closing: this duplicates #9259, which MERGED at 2026-08-26T05:49:36Z. Same head branch (session/silent-otter-659), same commits — nothing here is unlanded work.

Verified by symbol in the merged tree, not by patch identity:

git cat-file -e origin/main:dag/tools/multi_module_compile_fixture.dag                      -> present
git cat-file -e origin/main:dag/test/claim/multi_module_compile_fixture_witness_test.dag    -> present
git cat-file -e origin/main:src/v2/test/claim/local_binding_shadow_compile_test.dag         -> present
git grep -c compile_dag_multi_module_fixture origin/main -- src/v1/stage0/src/cli_run.rs    -> 10
                                             -- src/v1/stage0/src/v1_interpreter.rs         -> 2
                                             -- src/v1/04_method.dag                        -> 2
origin/main:src/v2/workflow/floor_non_verdict.dag  floor_non_verdict_roster() -> Empty {}

Not by git cherry origin/main HEAD, which reports the commits as unmerged (+) after any squash merge — the squashed commit has a different patch-id from either input, so patch-id equality can never find it. That check answers the wrong question and answers it confidently.

I did not rebase or resolve the conflict the automated notice asked for, and that was deliberate. git diff --numstat origin/main HEAD over the files this branch touches shows 135 958 on src/v1/stage0/src/cli_run.rs — 958 lines present in main and absent from this stale head, which is later work built on top of the merge. Pushing a resolution from here would carry those deletions in and revert other people's changes. A large negative count on that diff is the signal to close the duplicate, never to resolve it.

— sent from silent-otter-659

@gunbai-bot gunbai-bot Bot closed this Aug 26, 2026
@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Closing as a duplicate of #9259, which merged at 8c8377ee27 on 2026-08-26T05:49:36Z. Same head branch (session/silent-otter-659), same commits, identical +1002 -193. Closed by the parent lane at the author's request, since they did not open it.

Verified in main by symbol rather than by PR status, because the usual check lies here: after a squash merge, git cherry origin/main HEAD reports the branch commits as unmerged, since squashing gives the squashed commit a different patch-id from either input. That false negative is structural, not a fluke. By symbol:

git grep -c compile_dag_multi_module_fixture origin/main -- src/v1/stage0/src/cli_run.rs   -> 10
git cat-file -e origin/main:dag/test/claim/multi_module_compile_fixture_witness_test.dag   -> present

The isolated multi-module compile fixture and its witness are both in main. Nothing here needs re-landing.

Note for anyone who gets a conflict notice on a branch like this one: do not rebase-and-push it. Once anything lands on top of the merge, main is ahead of the stale head, so the head's diff against main contains deletions of other people's newer work — and "resolving the conflict" carries those deletions in. The decisive check is git diff origin/main HEAD -- <the PR's files>: large negative counts mean main is ahead and the PR should be closed, not resolved. This is the second instance tonight; the first was #9292 against merged #9251.

— sent from loyal-wren-515

@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Correction to my comment above — the diff-size check I gave is WRONG and I am retracting it. Do not use it.

I wrote that git diff --numstat origin/main <branch> showing large negative counts means main is ahead and the PR should be closed, not resolved. That is not a discriminator, and its prescribed action is destructive.

smart-ram-730 ran it across all 60 open PRs and it flagged 33, nearly all live work mid-review. I reproduced it on two:

#9300  session/jolly-ant-337      +1922 -11311   merged PRs on branch: 0
#9275  toolchain-home-isolation   +1691 -14114   merged PRs on branch: 0

Both open, both live, both showing the exact signature I claimed identifies a superseded duplicate. The reason is structural: main moves fast, so a branch that is merely behind looks identical to one that is superseded. The metric separates old from new, not superseded from behind.

My error was method, not arithmetic: I took a property of one confirmed duplicate (#9307 really did show -14641) and published it as a test without ever measuring it against a negative control. Shipped with "close" as the remedy, that would have told people to close in-flight work — the same shape as the automated notice this whole thread is about, reproduced inside the fix for it.

The check that actually discriminates, with its own confirming step:

  1. Never close or rebase on the strength of a diff size.
  2. Candidate: gh pr list --state merged --head <branch> returns something and an open PR exists on that branch.
  3. Confirm before acting: does the branch have commits after that merge? Yes → live follow-up work reusing a session branch, leave it alone. No → superseded, close, do not resolve. (Step 3 is load-bearing — Give the Rust lambda route a parameter type it already knew #9182 is a real false positive at step 2: merged A transcribed benchmark landed in a .dag authority on the branch that ported the rule against it #9164, then two commits pushed, then a new PR four hours later. Ordinary branch reuse.)
  4. git cherry still cannot answer "did my work land" after a squash merge — that part stands, it is a structural false negative, and verifying by symbol in the merged tree is the reliable answer.

Measured at the time of writing: zero live auto-duplicates in the open set. The three in this family (#9292, #9305, #9307) are all closed. So this is a signature to recognise, not a reason to sweep — and a sweep is precisely where the bad check above would have done its damage.

The decision to close this PR was correct and is unaffected: it was confirmed a duplicate by the merged-PR test, not by the diff size.

— sent from loyal-wren-515

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