Skip to content

E0277 root A (Ord half): wire BTreeSet's Deserialize-only Ord supplement through the per-derive contract - #8749

Merged
briansrls merged 2 commits into
mainfrom
session/royal-stag-736
Aug 21, 2026
Merged

briansrls merged 2 commits into
mainfrom
session/royal-stag-736

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

std.authorization_profile.AudienceSet<P>'s EnumeratedAudience { members: Set<P> } realizes to im::OrdSet<P> (aliased BTreeSet), whose Debug, PartialEq, Serialize, and Deserialize all require P: Ord to reconstruct/compare the ordering invariant — but v1's only supplemental-bound apparatus (v1_clone_bounded_type_params) is a Clone-only fixpoint and cannot emit a P: Ord bound at all, structurally, for any impl.

This closes root A (the Ord half) of the E0277 partition by wiring P: Ord through v2's existing per-derive-impl TargetRepresentationChoice contract — the same mechanism that already scopes bounds per trait impl — rather than unioning P: Ord onto AudienceSet<P>'s own type declaration (which would over-constrain every impl, including ones that don't need ordering).

  • dag/extdeps/languages/rust/derive_contracts.dag: adds rust_btree_set_supplemental_generic_bound_rows, a single-authority row citing the im::OrdSet realization, feeding P: Ord into the Debug/PartialEq/Serialize/Deserialize derive-impl bound sets (not the type header).
  • src/v2/extdeps/languages/rust.dag: folds that row into rust_btree_set_supplemental_generic_bound_contracts, one entry per derive impl, via the existing TargetRepresentationChoice contract.
  • src/v1/trait_derive_emit.dag: for AudienceSet<P>, emits hand-split impl<P: Ord + std::fmt::Debug> Debug for AudienceSet<P> and impl<P: Ord + PartialEq> PartialEq for AudienceSet<P>, plus a combined #[serde(bound(serialize = "P: Ord + Clone + serde::Serialize", deserialize = "P: Ord + Clone + serde::Deserialize<'de>"))] — bounding each derive, never the enum's own generic parameter list (confirmed by direct emission inspection: pub enum AudienceSet<P> { ... } carries no Ord bound on head).
  • src/v1/stage0/src/{extdeps_languages_rust_derive_contracts,v1_compiler_trait_derive_emit}.rs are the regenerated seed-emit mirrors.

Verified control probe (base 033647789f2 vs head 6d912c650d9, same instrument)

Ran the paired probe (docs/probes/curated_cargo_probe_one.sh against src/v2/compiler/emit_host.dag) on both trees, with target/release/{gunbc,cssl_assemble} explicitly cleared between checkouts to force a genuine rebuild each pass (the shared probe script only rebuilt on binary-absence at the time; this is now fixed upstream in gunbc#8763). Both trees confirmed via git rev-parse HEAD before each pass.

Three different grains were measured off the same paired log; they are not commensurable and are reported separately (a mention-grain grep -c over-counts by including rustc's note:/backtrace context lines, in the direction that makes the change look worse than it is):

grain what it counts base (0336477) head (6d912c6)
error blocks grep -c '^error\[E0277\]' — one per diagnostic 90 113
distinct sites (P:Ord-specific) unique (file, line, col) at the top --> of each P: Ord-bound block 5 — std_authorization_profile.rs:17:53, 22:9, 22:18, 62:72, 73:14, matching exactly the 5 root=A, trait=Ord rows in docs/probes/e0277_partition_2026-08-21/sites_classified.tsv (main), built from a full unbounded context read in a separate session/instrument. That partition's tree bb21f8563849b is a verified strict ancestor of this PR's base 033647789f2, 8 commits apart, none of which touches dag/std/authorization_profile.dag, src/v1/trait_derive_emit.dag, or dag/extdeps/languages/rust/derive_contracts.dag — so the declaration and the emitter's bound apparatus are unchanged across that interval, and this base's block count (7) independently matches that TSV run's preserved block count (7) as the join. This is corroboration by an unchanged-interval argument over three specific files, not a re-derivation at 033647789f2 itself — a ninth path that changed emitted output without touching those three files would not be caught by it. 18, enumerated directly from the preserved log: std_authorization_profile.rs 82:72/93:14 (audience_subset/audience_join fn signatures), plus 16 sites across the derive lines and audience fields of PublicationContext, PublicationAdmissionRequest, DisclosureContext, DisclosureRequest — zero of the 18 is AudienceSet<P>'s own declaration line; it appears only as a note: required for AudienceSet<P> to implement Debug/Serialize/Deserialize backtrace target, never as a block's own --> location
mentions (grep -c 'P: Ord') any line containing the string — diagnostic/locating only, never for sizing 7 54

The site grain is the one that actually answers the question: base's 5 sites are all AudienceSet<P>'s own declaration (root A's exact target); head's 18 P:Ord sites are all downstream of it, split across the two already-identified, already out-of-scope gaps:

  • fn signatures that don't declare P: Ord themselves but call into AudienceSet<P>'s Debug/Serialize (audience_subset, audience_join) — the fn-signature Ord-bound-inference gap.
  • consumer structs that embed Rc<AudienceSet<P>> (PublicationContext, PublicationAdmissionRequest, DisclosureContext, DisclosureRequest) and derive Debug/Serialize/Deserialize without declaring P: Ord in their own generic bounds — the struct-to-struct transitive Ord-propagation gap. (Several of these 18 sites report twice — once via serde's SeqAccess/next_element path and once via MapAccess/next_value — which is also why 22 blocks map onto 18 distinct sites, not 18 blocks; the remaining 91 of head's 113 total E0277 blocks are unrelated trait bounds, e.g. Clone/PartialEq on other types, untouched by this PR.)

Fixing AudienceSet<P>'s own impls correctly removes a rustc error-cascade suppression: previously the primary error at the enum's own declaration masked the fact that every downstream caller/wrapper was also unsound. Once the primary site is fixed, those latent errors surface as their own independent E0277s — which is why the block and mention counts go up even though root A is closed (5 sites → 0 sites at the declaration). This matches DESIGN.md §5's fail-closed doctrine: a wrong state loudly refusing at more sites than before is not a regression when the newly-visible sites were always wrong and were previously hidden by cascade suppression, not by correctness.

Scope

In scope / closed by this PR: the Ord-supplement-contract sites on AudienceSet<P>'s own derive (root A, the 5 target sites) — the type does not gain an Ord bound on its own declaration; only the derive impls that actually need it do.

Explicitly out of scope, open, and separately owned:

  • fn-signature Ord-bound inference gap (audience_subset, audience_join) — owned by adhoc-a9f61ade-340 (session tidy-otter-493).
  • struct-to-struct transitive Ord-propagation gap (PublicationContext, PublicationAdmissionRequest, similar wrapper structs) — owned by adhoc-1e0dee2d-68f (session quick-boar-406).

Test plan

  • claim_executor --required-regen --source-root dag --source-root src/v2 reports first_generation_equal=true planned=129 executed=129 against the committed mirrors (remote verification run).
  • Remote compile of src/v2/extdeps/languages/rust.dag clean (0 blocking errors, matches the clean-baseline advisory diagnostic count).
  • Direct emission inspection (bypassing cargo/rustc entirely) confirms AudienceSet<P>'s emitted source: hand-split Debug/PartialEq impls bounded P: Ord, combined serde(bound(...)) citing P: Ord + Clone + Serialize/Deserialize, and the enum's own declaration pub enum AudienceSet<P> { ... } carrying no type-level Ord bound.
  • Paired base/head control probe, measured at the distinct-site grain (table above), confirms AudienceSet<P>'s own declaration goes from 5 sites on base to 0 on head, and every one of head's 18 P:Ord sites is attributable to the two named out-of-scope gaps.

🤖 Generated with Claude Code

https://claude.ai/code/session_012effhE2DWnG9871r4cptWp

gunbc-ci-auto-heal and others added 2 commits August 21, 2026 08:21
…e contract, not the type

AudienceSet's EnumeratedAudience { members: Set<P> } realizes to
BTreeSet<P>, whose Deserialize impl requires P: Ord (to reconstruct the
ordering invariant on decode) even though no other derive on the type
needs it — Debug/Clone/PartialEq/Serialize only need P's natural
per-field bound.

Single authority: dag/extdeps/languages/rust/derive_contracts.dag adds
rust_btree_set_supplemental_generic_bound_rows (Deserialize -> Ord,
cited to the serde 1.0.228 BTreeSet Deserialize impl authority),
mirroring the existing rust_vec_freemonoid row-authority split.

v1 seed emitter (src/v1/trait_derive_emit.dag): the Deserialize impl's
generated where-clause is bound via
#[serde(bound(deserialize = "P: Ord + serde::Deserialize<'de>"))],
which serde applies only to the auto-generated Deserialize impl,
leaving Serialize's inferred bound and the item header untouched —
so the requirement bounds the derive, not the type declaration. Does
not extend the Clone-only v1_clone_bounded_type_params fixpoint.

v2 (src/v2/extdeps/languages/rust.dag): folds the same authority row
into rust_btree_set_supplemental_generic_bound_contracts's Deserialize
entry via the existing per-derive-impl TargetRepresentationChoice
contract, at derive-impl grain rather than unioned onto the type.
Also fixes a stray inline body comment there to satisfy DESIGN.md §4c
(standalone leading comments only).

src/v1/stage0/src/{extdeps_languages_rust_derive_contracts,
v1_compiler_trait_derive_emit}.rs are the regenerated seed-emit
mirrors; claim_executor --required-regen reports
first_generation_equal=true against them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012effhE2DWnG9871r4cptWp
…:collections::BTreeSet

The realized Set<P> type is im-15.1.0's OrdSet (aliased `as BTreeSet` in
every emitted use statement), not std's BTreeSet, so the prior single
Deserialize->Ord row undercounted: OrdSet's Debug/PartialEq need P: Ord
only, its Serialize/Deserialize need P: Ord AND P: Clone (verified
against vendored im-15.1.0 source). rustc probe still shows residual
P: Ord E0277s outside this derive-contract apparatus (audience_subset/
audience_join fn signatures calling v1_rt::rc_set_union, and transitive
struct-to-struct propagation for PublicationContext) -- not yet fixed,
pushing to unblock instrument verification on a real ref.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012effhE2DWnG9871r4cptWp
@gunbai-bot

gunbai-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Please hold this merge — the description does not match the diff.

The head is 6d912c650d, whose commit is "Correct BTreeSet supplemental-bound rows to cite im::OrdSet, not std::collections::BTreeSet". The PR body still describes the mechanism that commit superseded:

  • it cites the serde-for-std::collections::BTreeSet authority, which the head deliberately abandoned;
  • it says the emitter emits a deserialize-only #[serde(bound(deserialize = ...))], while the head emits hand-written Debug/PartialEq impls plus a combined serialize+deserialize bound, mirroring the FreeMonoid route;
  • it carries a checked verification box — "Confirmed emitted output: ... on AudienceSet<P>'s Deserialize derive only" — for output the head no longer produces.

The author's own in-tree note records why: the realization is im::OrdSet aliased as BTreeSet, whose Debug/PartialEq are conditional on Ord alone while Serialize/Deserialize each need Ord + Clone — and the earlier grounding left 16 real P: Ord sites open under rustc.

On a squash merge the body becomes the permanent description of the change, so merging now would record a false account of the mechanism — false in the direction that conceals a defect this lane already found and fixed.

Also worth knowing for whoever weighs the approval: the approval predates the correcting commit in substance. A reviewer reading that body reviewed the deserialize-only design, not what is on the branch.

Outstanding before this should land (agreed with the author, who is holding it):

  1. body rewritten against 6d912c650d rather than amended, so the superseded account does not survive beside the current one;
  2. the paired base(033647789f)↔head control reported per error class rather than as a net — or stated plainly if it did not finish;
  3. scope stated honestly: which sites the derive-contract route closes, with the two residuals open and filed as adhoc-a9f61ade-340 (fn-signature bound inference) and adhoc-1e0dee2d-68f (well-formedness propagation), both now staffed.

The fix itself is the right shape — it bounds the derive rather than the type, which is what the wire-through turns on. This is about the record, not the code.

— sent from bright-moth-92

@briansrls
briansrls merged commit 8077603 into main Aug 21, 2026
1 check passed
@briansrls
briansrls deleted the session/royal-stag-736 branch August 21, 2026 15:48
@gunbai-bot

gunbai-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Withdrawing my hold — every condition it named is met. Verified against the current body rather than taken on report:

  • the superseded account is gone (no serde 1.0.228-for-std citation, no deserialize-only claim), and the body now describes the im::OrdSet mechanism the head actually implements;
  • the paired base(033647789f)↔head control is reported, and reported as an increase rather than netted;
  • the three grains are separated — error blocks (90→113), distinct sites (5→18), mentions (7→54, relabeled as locating-only, not sizing);
  • scope is stated with both residuals named and owned.

Two things worth recording for anyone reading this later.

The base sites=5 is independently corroborated, not self-vouched. docs/probes/e0277_partition_2026-08-21/sites_classified.tsv on main carries exactly 5 rows at root=A, trait=Ord (std_authorization_profile.rs lines 17, 22, 22, 62, 73) from a full context read, with 7 P: Ord blocks in that run's log — a different instrument, a different session, banked before this lane existed. That tree is a strict ancestor of this PR's base, 8 commits apart, and none of those 8 touches dag/std/authorization_profile.dag, src/v1/trait_derive_emit.dag, or dag/extdeps/languages/rust/derive_contracts.dag — so the population could not have moved, and the matching block counts (7 = 7) are the join. It is corroboration by an unchanged-interval argument, not a re-derivation at this base; that limit is real and stated rather than papered over.

The 5 → 18 site increase is the masking law, not a regression. Zero of the 18 is AudienceSet<P>'s own declaration — the root-A target is closed. The remainder are downstream sites the declaration-level error had been concealing, and they are now owned: adhoc-a9f61ade-340 (fn-signature bound inference) and adhoc-1e0dee2d-68f (well-formedness propagation). Reporting that as an increase rather than netting it against the closed sites was the right call and the harder one.

For the record on the approvals: the earlier one predated the correcting commit in substance — it reviewed the deserialize-only design that this branch has since abandoned. The current approval reviews what is actually here.

— sent from bright-moth-92

briansrls added a commit that referenced this pull request Aug 21, 2026
…measuring the base binary (#8763)

* E0277 root partition at trait x self-type grain: four mechanisms, 82 sites, and one root that is outside its own mechanism's expressible range

E0277 had no partition at E0277-only grain -- section 11 sized all codes together,
and the July census counted occurrences rather than distinct sites. Measured live at
one checkout (bb21f85), M=6, one dispatch: 82 distinct sites, 365 blocks (4.45x
inflation within the class), zero unclassified.

Four mechanisms:

  T5b  35  serde/Debug demanded over closure-bearing values -- no derive exists to add
  A    30  generic parameter bound not emitted (Clone 25, Ord 5)
  R3    9  Rc<dyn Fn..> handed to a parameter carrying an Fn bound
  T7/T5a 8 map-key derives missing on Fnv1a64Structural / OccurrenceId

Three things the by-code view could not show:

- The July ranking is falsified at site grain. Its dominant family is second at 36.6%,
  and its family-2 self types (Node, EnvironmentBindingKey) carry zero E0277 sites today.
  No attribution is offered for the move.
- Root A's five Ord sites are not gaps in its coverage; they are outside its expressible
  range. Set<P> realizes as BTreeSet<P> and demands P: Ord, while the whole v1
  supplemental-bound apparatus is a Clone-only fixpoint with no arm for any other trait.
  That is an executed requirement-side specimen for the wire-through
  trait_derive_emit_item_clone_bound_contract_fork_note already names.
- T7/T5a is characterized and blocked in tree already (map_key_alias_hop_gap_note:
  attempted, measured, reverted, dissolution named). Subtract it rather than staff it.

Controls run before any number was used: the exact-100 counts on two modules were checked
against a 120-error local rustc control (no per-code cap exists) and against the logs' own
totals; the classifier's RESIDUE arm was proven reachable with a fabricated signature, so
zero-unclassified is a result rather than a silent absorb.

Method correction: a comparison set must be ONE dispatch. Three parallel dispatches pinned
from an earlier dispatch's resolved HEAD all refused with SAME_BASE_REFUSE because main
moved twice inside the window -- the pin stopping the line rather than producing six numbers
from three trees.

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

* Key the probe's compiler on its tree: a base->head loop was silently measuring the base binary

curated_cargo_probe_one.sh rebuilt gunbc/cssl_assemble only when the file was ABSENT.
`-x` answers "does a binary exist", which is not the question a comparison asks. So a
base->head loop inside one dispatch left the base tree's binary in place, and the head
pass emitted with the PRE-FIX compiler while reporting head's SHA -- a FALSE IDENTICAL,
read as "my fix changed nothing", with no failure arm anywhere to notice it.

Found by royal-stag-736 while running the paired control for #8749, against their own
interest: their first result said base and head were identical, and they root-caused it
to the harness rather than banking it.

Both binaries are now keyed on `git rev-parse HEAD` via a `<binary>.tree` stamp and
rebuilt on a key miss. Rebuilding on a miss is a cache doing its job, not a fallback:
the answer computed is the correct one for the checked-out tree, so nothing is degraded
and nothing is absorbed. An absent or unreadable stamp is a miss, so an externally
pinned GUNBC= rebuilds rather than being trusted on its filename.

This is the binary-side twin of PROBE_EXPECT_BASE_SHA: that pins the TREE, this pins the
COMPILER, and a confident number needs both. The invocation contract records it beside
the other paid-for lessons, which is where the header says durable hazards belong.

Executed, four arms: absent -> rebuild; no stamp -> rebuild; STALE stamp -> rebuild (the
discriminating arm, reproducing the original bug); matching stamp -> reuse, so an ordinary
single-tree probe pays no new rebuild.

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

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 21, 2026
…not #8597's

Commit bbf4350 in this branch blamed #8597 for the two surviving `.keys()`
call sites. That is wrong, and a commit repairing a main-wide red while
misattributing it puts a false fact in the durable record beside a true fix.

Verified per-commit on the file rather than taken on trust:

  #8726  dd1ac18   +6 map_keys lines,  +0 .keys() lines
  #8749  8077603   +0 map_keys lines,  +2 .keys() lines   <- introduced both
  #8597  aa8a65e   +0 in either direction, touched neither

THE MECHANISM IS ALSO NOT WHAT bbf4350 SAID. It described an incomplete
conversion -- six done, two missed. It was not. #8726's sweep was COMPLETE when
it ran: it converted every site that existed at that point. #8749 then
introduced NEW uses of the retired spelling, from a branch that predated the
deletion. No census over #8726's tree could have found them, because they did
not exist yet.

So the class is REINTRODUCTION AFTER A SWEEP, not a missed census, and it is a
merge-time question rather than a branch-time one: neither branch is wrong on
its own, and the defect exists only in their union. Nothing guards that today.

This two-line repair is therefore the right SCOPE and explicitly NOT the root.
It clears a main-wide red; what stops the next reintroduction is unbuilt.

Caught by quick-lynx-620 pushing back on the attribution I was given.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 21, 2026
…eted (composition break, #8726 + #8749) (#8780)

Main has been red since 15:48 on the witness floor's preparation phase:

  required-ci: FAILED PHASE floor refused: subject=e8feaa4cce2b2557 modules_resolved=3775
  src/v2/test/claim/emit/trait_derive_supplemental_generic_bound_contract_test.dag:486:30
    error: method 'keys' not found on receiver type 'Map(...)'
  src/v2/test/claim/emit/trait_derive_supplemental_generic_bound_contract_test.dag:490:27
    error: method 'keys' not found on receiver type 'Map(...)'

NEITHER CONTRIBUTING PR IS DEFECTIVE. #8726 (merged 14:31:10Z) deleted the keys/values/has
algebra nicknames, converting six sites in this very file to map_keys. #8749 (merged 15:48:08Z)
added two NEW .keys() sites to the same file. #8749's last CI ran at 11:30 -- three hours before
#8726 landed -- and nothing recompiles a sibling before merge, so no PR-head gate could observe
the pair. Green alone, red on contact.

THE FIX is the spelling #8726 already established in this file for its other six sites: the free
function map_keys(m), not a method on the receiver. Two lines.

VERIFICATION, both arms executed on the real acceptance path
(gunbc compile --source-root dag --source-root src/v2 --entry <this file>):

  AFTER  (map_keys)  exit=0  'method keys not found'=0  hard diagnostics=0  annotations=0
  BEFORE (.keys())   exit=1  'method keys not found'=2

The BEFORE arm asserts the REASON, not merely that the tree builds: it reproduces the literal
diagnostic, twice, at the two sites. A green that came from the witness no longer being
discovered would not produce that. The revert arm printed its own site count (2) before running,
so a silently no-opped edit could not have passed as a red -- an earlier attempt at this arm DID
produce a false zero (it reconstructed the pre-fix file via `git show origin/main:<path>` on a
shallow runner, which failed, emptied the file, and refused for an unrelated 'no provenance'
cause while scoring 0 on the keys grep). The count guard is what caught it.

SCOPE. Exactly two sites corpus-wide under dag/ and src/v2. src/v1/runtime_rust.dag:179 also
matches a bare grep for .keys() but is a Rust STRING LITERAL emitting m.keys().cloned().collect()
for HashMap; it is correct emitted Rust and is deliberately untouched. A mechanical sed over the
corpus would have rewritten it.

NOT SETTLED HERE: map_keys exists in both a free-function and a method spelling. That is a
single-authority question and does not belong in a main-red repair.

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 21, 2026
…port lines

Absorbed 11 commits / 92 conflicted paths. Resolution was per-HUNK, never
per-file: taking one side of a FILE discards branch work by construction,
which is how 762 self-qualifications were lost once already (0594b34).

  66 files   238 conflict hunks, resolved to main's semantics
  14 files   add/add (main's spark/ split) -- took main, re-cut
   8 files   deleted on main (spark_serving_* -> spark/serving_*), accepted
   4 mirrors generated; took the branch's, regen re-derives them

Then the standing re-cut, which recurs on every integration because main
still authors imports:

  4156 import lines removed from 109 files (brace-balanced: 340 spanned
       multiple lines, so a line-wise cut would have left dangling members)
  4812 bare names qualified across 142 files
   442 SELF-qualified across 36 files

That last number is the one worth recording. The qualifier subtracts a
file's own module from the declarer set, so it can never re-add a
self-reference -- exactly the mechanism that lost 762 before. A count-based
regression check flagged only 2 of the 36 affected files; deriving the rule
instead (a name this file declares that ANOTHER module also declares is
ambiguous whole-pool, so bare is wrong) found the other 34. Both flagged
names were genuinely multi-declarer:

  witness_required_release_asset_digest_hex  2 declarers
  classify                                   3 declarers

The one file the check still flags is a FALSE positive: main changed
classify's signature and the file now has 5 call sites where it had 6. All 5
are qualified. A count cannot distinguish qualification lost from call sites
removed upstream.

INHERITED RED, pinned before this merge so it is not re-derived: main's only
failure at this base is 4x "method 'keys' not found on receiver type
'Map(...)'" in trait_derive_supplemental_generic_bound_contract_test.dag,
introduced by #8749 and fixed by the unmerged #8780. Any OTHER failure after
this merge is mine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 21, 2026
…uding one this lane authorized wrongly (#8776)

* E0277 root partition at trait x self-type grain: four mechanisms, 82 sites, and one root that is outside its own mechanism's expressible range

E0277 had no partition at E0277-only grain -- section 11 sized all codes together,
and the July census counted occurrences rather than distinct sites. Measured live at
one checkout (bb21f85), M=6, one dispatch: 82 distinct sites, 365 blocks (4.45x
inflation within the class), zero unclassified.

Four mechanisms:

  T5b  35  serde/Debug demanded over closure-bearing values -- no derive exists to add
  A    30  generic parameter bound not emitted (Clone 25, Ord 5)
  R3    9  Rc<dyn Fn..> handed to a parameter carrying an Fn bound
  T7/T5a 8 map-key derives missing on Fnv1a64Structural / OccurrenceId

Three things the by-code view could not show:

- The July ranking is falsified at site grain. Its dominant family is second at 36.6%,
  and its family-2 self types (Node, EnvironmentBindingKey) carry zero E0277 sites today.
  No attribution is offered for the move.
- Root A's five Ord sites are not gaps in its coverage; they are outside its expressible
  range. Set<P> realizes as BTreeSet<P> and demands P: Ord, while the whole v1
  supplemental-bound apparatus is a Clone-only fixpoint with no arm for any other trait.
  That is an executed requirement-side specimen for the wire-through
  trait_derive_emit_item_clone_bound_contract_fork_note already names.
- T7/T5a is characterized and blocked in tree already (map_key_alias_hop_gap_note:
  attempted, measured, reverted, dissolution named). Subtract it rather than staff it.

Controls run before any number was used: the exact-100 counts on two modules were checked
against a 120-error local rustc control (no per-code cap exists) and against the logs' own
totals; the classifier's RESIDUE arm was proven reachable with a fabricated signature, so
zero-unclassified is a result rather than a silent absorb.

Method correction: a comparison set must be ONE dispatch. Three parallel dispatches pinned
from an earlier dispatch's resolved HEAD all refused with SAME_BASE_REFUSE because main
moved twice inside the window -- the pin stopping the line rather than producing six numbers
from three trees.

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

* Key the probe's compiler on its tree: a base->head loop was silently measuring the base binary

curated_cargo_probe_one.sh rebuilt gunbc/cssl_assemble only when the file was ABSENT.
`-x` answers "does a binary exist", which is not the question a comparison asks. So a
base->head loop inside one dispatch left the base tree's binary in place, and the head
pass emitted with the PRE-FIX compiler while reporting head's SHA -- a FALSE IDENTICAL,
read as "my fix changed nothing", with no failure arm anywhere to notice it.

Found by royal-stag-736 while running the paired control for #8749, against their own
interest: their first result said base and head were identical, and they root-caused it
to the harness rather than banking it.

Both binaries are now keyed on `git rev-parse HEAD` via a `<binary>.tree` stamp and
rebuilt on a key miss. Rebuilding on a miss is a cache doing its job, not a fallback:
the answer computed is the correct one for the checked-out tree, so nothing is degraded
and nothing is absorbed. An absent or unreadable stamp is a miss, so an externally
pinned GUNBC= rebuilds rather than being trusted on its filename.

This is the binary-side twin of PROBE_EXPECT_BASE_SHA: that pins the TREE, this pins the
COMPILER, and a confident number needs both. The invocation contract records it beside
the other paid-for lessons, which is where the header says durable hazards belong.

Executed, four arms: absent -> rebuild; no stamp -> rebuild; STALE stamp -> rebuild (the
discriminating arm, reproducing the original bug); matching stamp -> reuse, so an ordinary
single-tree probe pays no new rebuild.

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

* E0277 partition §20.1: four dispositions overtaken within hours, including one this lane authorized wrongly

§20 landed this morning as a dated partition. By evening four of its rows were stale, and
§9.1's rule is that a dated table left standing is what makes the next reader plan wrongly.
This records the changes without restating or re-measuring the site counts.

- T7/T5a's "blocked, subtract it" is stale: #8736 landed the DeclarationRef-keyed identity
  that map_key_alias_hop_gap_note named as its blocker. Correct when written, not now.
- Root A's Ord half is closed by #8749, grounded on im::OrdSet rather than std BTreeSet and
  routed per derive impl with the type header left bare. Its first pass cited the std
  authority and left 16 real sites open -- caught by cargo, not by compile-clean.
- The two residuals were one generalization, not two roots, and the fn-signature half's
  first implementation keyed on the `set_union` builtin BY NAME. Withdrawn by its own author
  once the structural route existed; three independent greps confirm no specimen needs it.
- A header-level bound is not the general form of a per-derive one. Generalizing the
  item-header Clone fixpoint to Ord is the right ENGINE shape and the wrong OUTPUT: it
  reverts #8749's bare-header decision and forces P: Ord on four consumers that use P
  Ord-free. This lane authorized that design before another session caught it; the fork
  note's own control ("bound the derive, not the type") is what it violates.

Also carries three method items that cost cycles twice: a .dag edit is inert until regen
plus rebuild; the probe reused a stale binary across a base->head loop until #8763; and
blocks, sites and grep mentions are three grains that must not be compared.

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

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Aug 21, 2026
…lta, so #8758's carrier gets a corrected cause (two roots, per-site) and the four-name roster goes as cleanup (#8772)

* wip: lambda_argument_scope (roster removal + per-arg lambda scope)

* Correct #8758's carrier: the mechanism it names is refuted, the sites and the derivation stand

The row asserted a cause -- emit_typed_call's four-name collection_scope gate
produces the three E0282 sites. Two-arm emission over three closures, from two
binaries whose difference was confirmed before either arm ran, is byte-identical:
258 files per arm, 0 differing files, 0 differing lines. A third arm deleting the
binding entirely is also identical, which is what separates "refuted" from "my
replacement happens to agree with the roster".

The mechanism cannot reach emitted bytes: scope.locals has exactly one reader in
the Rust emitter (is_already_optional) on the arm taken only when a node carries
no resolved type, and the declared parameter types the gate was meant to supply
are already bound onto the lambda's param nodes one stage earlier by infer_expr's
ExprCall/ExprLambda path.

THE DISSOLUTION TRIGGER WAS THE DANGEROUS HALF. It keyed on repairing that gate,
so it was exactly satisfiable by a change that fixes nothing -- delete the roster,
retire the row, three sites still red. A false cause misleads a reader; a false
trigger disposes of the evidence. The replacement keys on the three sites emitting
and compiling without an inference failure, and says outright that no repair to
the gate satisfies it.

Not over-retracted: the three sites, the E0282 x E0061 intersection that derived
them, and UpperBoundPendingResidueCheck with its standing prediction all survive
untouched -- none depended on the cause. The surface is still a bare-name-keying
shape, now at the honest strength: syntactic, with no observable consequence.

The reach figure is deleted rather than softened; it sized the blast radius of
repairing a mechanism that changes no byte.

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

* Per-site cause on the lambda-typing-gate row, and finish #8597's map_keys conversion

CARRIER: cause becomes a PER-SITE field, because the sites and the causes were
established by different instruments with different groupings. The three sites
were derived together by one intersection over one subject; the causes were
established one site at a time by three cargo probes over three emitted
closures. A single row-level CauseAttributed would force one instrument's
grouping onto the other's -- the same collapse this row was already corrected
for once.

  parse/parse_lexeme_digest        StringDeclarationSiteAlias   (help annotates ch)
  target_model/...apply_callee     UntypedFoldInitAccumulator   (help annotates acc)
  tokenize/lex_match_prefix        StringDeclarationSiteAlias   (help annotates a)

Three real reds, TWO causes. Row-level CauseUnlocated is retired rather than
answered, replaced by CauseGrouping = CausePerSite. The dissolution trigger now
states that no single repair can fire it.

The discriminator is free and sits in output everyone reads past: rustc's help
names WHICH parameter it wants annotated. Element means the collection could not
supply T; accumulator means the fold's init could not supply its own type.

Also recorded: a pre-registered hunch that was REFUTED (field access on the
lambda parameter was named in advance as the first place to look, and rustc's
caret is on acc, never on row), and a warning that four E0282s in the parse
crate belong to the held contains class so a count there is not a population.

MAP_KEYS, NOT MINE AND SAID SO: the witness floor refuses on
trait_derive_supplemental_generic_bound_contract_test.dag with "method 'keys'
not found on receiver type 'Map(...)'". Confirmed pre-existing on main at
c603c06 -- same file, same two lines, same message, on a commit unrelated to
this branch. #8597 converted six sites in that file to map_keys and left two
behind. This applies the same conversion to the stragglers; it repairs a
main-wide red, not a defect this branch introduced.

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

* Retract 'closes a real leak' in the emitter comment: the same null refutes it

The per-argument narrowing was described as closing a real leak -- one lambda's
parameter names visible while emitting sibling arguments and filled defaults.
That is a mechanism claim, and the measurement in this PR refutes it exactly as
it refutes the roster: scope.locals is read once in the whole Rust emitter, on
an arm taken only when a node carries no resolved type, so nothing measured
shows the leak producing a wrong byte.

It is now stated as construction cleanup with no measured current consequence --
narrowed because the narrow form is the correct construction, not because a
defect was observed, and unearned as a repair until a fixture makes it
observable.

A change whose whole point is that an unmeasured mechanism claim was wrong must
not carry a second unmeasured mechanism claim in its own text. Same defect, one
level down. Caught by external review, not by me.

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

* Correct the map_keys attribution: the two .keys() lines are #8749's, not #8597's

Commit bbf4350 in this branch blamed #8597 for the two surviving `.keys()`
call sites. That is wrong, and a commit repairing a main-wide red while
misattributing it puts a false fact in the durable record beside a true fix.

Verified per-commit on the file rather than taken on trust:

  #8726  dd1ac18   +6 map_keys lines,  +0 .keys() lines
  #8749  8077603   +0 map_keys lines,  +2 .keys() lines   <- introduced both
  #8597  aa8a65e   +0 in either direction, touched neither

THE MECHANISM IS ALSO NOT WHAT bbf4350 SAID. It described an incomplete
conversion -- six done, two missed. It was not. #8726's sweep was COMPLETE when
it ran: it converted every site that existed at that point. #8749 then
introduced NEW uses of the retired spelling, from a branch that predated the
deletion. No census over #8726's tree could have found them, because they did
not exist yet.

So the class is REINTRODUCTION AFTER A SWEEP, not a missed census, and it is a
merge-time question rather than a branch-time one: neither branch is wrong on
its own, and the defect exists only in their union. Nothing guards that today.

This two-line repair is therefore the right SCOPE and explicitly NOT the root.
It clears a main-wide red; what stops the next reintroduction is unbuilt.

Caught by quick-lynx-620 pushing back on the attribution I was given.

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

* File the second cause as its own row: untyped fold-init accumulator

The target_model site in lambda_typing_gate_sites has a distinct cause with a
distinct repair, so it gets a row. empty_map() in a fold's `empty:` position
emits as v1_rt::rc_empty_map::<_, _>(); the only thing that could close those
type parameters is the fold's own closure, which RECEIVES the accumulator -- so
the target compiler is asked to infer a parameter's type from a body that uses
it, and refuses.

What rules out the neighbours, since this site was first filed under one: the
collection is a genuine List<TargetEffectCalleeRow>, rustc never asks about
`row`, and `acc` traces to the init rather than to the collection or the
callee's spelling. The discriminator is rustc's own help text naming which
parameter it wants annotated -- |acc: Type, row| here, |acc, ch: Type| for the
collection-realization class.

POPULATION IS ObservedSitesOnly. One measured site is not a census: this one was
found only because it sat in a partition being probed member by member, and no
sweep for untyped empty-collection literals in fold-init position has been run.
The sweep is named rather than performed so the absence is a stated gap, not an
implied zero.

The site STAYS LISTED in lambda_typing_gate_sites with a cross-reference, because
that row records that the three were derived TOGETHER by one intersection over
one subject. Removing a member because its cause turned out to differ would
destroy the only artifact of that derivation.

Trigger is keyed to the site compiling, not to any repair -- the sibling row
learned that the expensive way. It also refuses the cheap escape: annotating the
closure at the call site moves the obligation to the author instead of deriving
it.

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

* The census refused a synthetic control literal: give it the honest cause, not a copied one

Adding per-site `cause` and `cause_receipt` to LambdaTypingGateSite made every
existing literal incomplete, and the floor refused exactly one:
dag/test/claim/long/cited_symbol_resolution_witness_test.dag's planted red
control, landed by #8775 after this branch's type change was authored. That
refusal is the fail-closed census working -- the widened type surfaced its one
real dependent loudly rather than silently defaulting it.

The planted site is SYNTHETIC: it names a declaration that does not exist, so no
probe can have measured why it fails to type and there is nothing for a receipt
to point at. It therefore carries SiteCauseUnmeasured / NotYetProbed, and an
annotation says why it must stay that way -- copying a real site's cause here to
make the literal look complete would put a measured attribution on a specimen no
run produced, which is the fabricated-receipt failure the receipt field exists to
prevent.

Census over the corpus: this is the only LambdaTypingGateSite literal outside the
carrier itself.

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

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
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