Skip to content

B1: capability-keyed sharing contract — one open key brand in std, Rust rows in extdeps, closed alphabet re-homed to keep spelling total - #8605

Merged
briansrls merged 27 commits into
mainfrom
session/quick-lynx-620-pr2
Aug 21, 2026
Merged

briansrls merged 27 commits into
mainfrom
session/quick-lynx-620-pr2

Conversation

@briansrls

@briansrls briansrls commented Aug 20, 2026 •

Copy link
Copy Markdown
Contributor

Evidence the re-homing does not break consumers: full CI green on the merged head

required-ci: parse OK 50 file(s) parse-clean
required-regen: first_generation_equal=true planned=132 executed=132 declared_divergent=1 [main.rs]
required-floor: planned=10287 executed=10287 terminal=10287
                passed=9980 known_red_held=206 failed=0
required-ci: phases_run=3 failed=0

Run 32531260099, head 3fddce7235 — all three phases --required-ci runs. This is the question
a reviewer of an alphabet re-homing should ask first, so it goes first. The fold crosses
std.trait_derive_shape — the module this PR re-homes, and one imported broadly across the
corpus — and every one of 10,287 discovered witnesses reaches a terminal verdict with zero
failures
.

Superseding an earlier headline in this body that cited 10253/10253 on run 32508724288, head
5f7c23a275: that measurement was honest but bound to a pre-merge tree, and main has since
moved four commits into the same files. The numbers above are measured on the head actually
proposed for merge.


BOTH BLOCKERS ARE DISCHARGED — this PR is out of draft (2026-08-21). Neither was ever a
defect in this diff, and neither was closed by weakening anything here.

1 · regen — CLOSED by #8802. The gap was the second trigger: #8699 bounded the function
whose own body compares, but its caller repr_grounding_derive_completeness_predicate earns
PartialEq by well-formedness propagation, which was unwired. #8802 propagates the bound one
hop up the call graph, which is exactly the missing half. Verified here at the rustc level for
the first time on this branch, not merely at .dag advisory level:

cargo build --release -p v1-compiler   ->  Compiling v1-compiler ... Finished (0 errors)
required-regen: first_generation_equal=true planned=132 executed=132 declared_divergent=1 [main.rs]

The first-mirror case behaved exactly as DESIGN's regen-cut row says it now should: the emitter
produced the candidate tree before adjudicating it, so the refusal named a directory that
actually contained extdeps_languages_rust_capabilities.rs. The mirror is installed from those
emitted bytes — not hand-authored — and the fixed point is restored.

2 · receipt — DISSOLVED AT THE ROOT by #8791. Not waived and not worked around. The
operator ruled CI keeps only the parse sweep, regen, and the witness floor; --required-ci now
runs exactly those three phases. MAX_SELECTED_MODULES_PER_RUN still exists in the file but is
no longer reachable from CI, so the 4-authorities-against-a-cap-of-3 red is gone along with the
regen-fixed-point skip that was only ever a consequence of it. The cap was never raised — that
edit was drafted and dropped.

What this is

ReprGroundingDeriveTrait was a sixteen-member coproduct of Rust and serde trait names sitting in a std
interface. Two of its members (Serialize, Deserialize) are serde names — an independently versioned
upstream — and their spellings were already cited in extdeps.languages.rust.emit, so the std
coproduct was a redundant key set forked from a key extdeps already spelled out.

The replacement is one open brand, TargetCapabilityKey, declared once in std.trait_derive_shape and
imported everywhere. The Rust rows — keys, shape-by-capability table, list builders, emission order,
spellings — live in extdeps/languages/rust/capabilities.dag. A new target language is a sibling module
of rows and edits neither the fold nor std.

The guarantee that would have been lost, and how it was kept

rust_trait_derive_spelling was total by exhaustive match on the closed coproduct. An open Symbol
brand cannot be matched exhaustively, so deleting the coproduct outright would have replaced a structural
guarantee with a runtime refusal — a rung drop dressed as a migration (DESIGN §4b).

So the coproduct is re-homed, not deleted: RustCapability is Rust's own closed realization alphabet,
exhaustive at every spelling site, with rust_capability_key as the single total lift into the shared
open key space. Interface speaks keys; realization speaks the alphabet (DESIGN §3).

Two defects that a clean compile could not see

Both were found by running a witness, neither by review or by gunbc compile.

1. discriminant on a branded Symbol, resolved eagerly.
target_grounding_derive_trait_discriminant survived the carrier change and kept calling
discriminant(v: key) — meaningless once the key is a Symbol — and was also passed as a function value
to coproduct_nullary_inhabitant_by_discriminant, so it resolved eagerly rather than on call. Effect: any
module whose closure reached target_model could no longer resolve the discriminant builtin at all,
surfacing as NoSuchFunction attributed to whatever entry function happened to be running — an accurate
error naming an innocent subject.

Minimal pair: a probe module declaring its own coproduct and calling discriminant passes on clean main,
fails on this branch, discriminated solely by importing v2.extdeps.languages.rust. Green after the fix.

The wrapper, its second name, and the coproduct-inhabitant decode all collapse to the identity for a
branded Symbol, and are gone.

2. List<RustCapability> passed where List<TargetCapabilityKey> was required.
It compiled. At runtime the comparison was variant-against-symbol and silently answered false.
rust_capability_keys lifts at the boundary. Found by a witness going red.

Throughout both defects, every affected entry reported 0 blocking errors. Compile-clean is not
evidence that a module evaluates (DESIGN §5: a typecheck is not a consumer).

Also in here

  • The pair-completion spelling rows carry the capability key they answer for, so the join to
    std.trait_derive_shape is an identity match on the key. This replaces a sixteen-arm key-to-method-name
    function — a second naming scheme for what the key already names (DESIGN §3). One thing got weaker
    and the note beside it says so:
    the refusal arm can no longer name the unmatched key, because this
    corpus has no Symbol-to-String projection; it prints the keys that are spelled instead. Next-rung
    trigger recorded in the module.
  • target_model had a local type TargetCapabilityKey and the identical import from
    std.trait_derive_shape. Removed. Stated at the strength of the evidence in the module note: this is a
    §3 violation on its own terms, and nothing measured shows it caused the runtime failure above — removing
    it did not fix the NoSuchFunction.
  • kernel_int_arithmetic_traits migrated with the other builders; the split had left it behind.
  • coproduct_reflection_conformance_test repointed at RustCapability — the reflection claim survives,
    aimed at the coproduct that still exists.

Evidence

  • 18 witnesses green by execution across trait_derive_completeness_predicate_test and
    trait_derive_seed_emit_binding_test, including the pair-completion tests that exercise the rewritten
    key join.
  • All changed entries compile with 0 blocking errors. The two diagnostics on
    coproduct_reflection_conformance_test (LiveTreeDisposition, SubstrateInputsOnly) are
    byte-identical on clean main and are not from this change.
  • No src/v1/stage0 file is touched by this branch; no regeneration is involved.

Scope note: this supersedes the earlier Option A framing of this PR, per the operator's relaxation of the
compiler freeze for work in support of v2 self-host. The fork that framing described is closed:
TargetCapabilityKey now has exactly one declaration.


Floor run: GREEN — and it did NOT evaluate the regen gate

A manual workflow_dispatch on head 2272f66d92 (run
32343969274) came back success:

required-floor: subject=0881fd547a75853f modules_resolved=3715 modules_excluded=3
required-floor: planned=9687 executed=9687 terminal=9687 passed=9381
                known_red_held=306 failed=0 stale_quarantine=0
                interrupted_before_verdict=0 host_tool_unresolved=0

The run before it, on 15b2750900, had failed=1 and found a third discriminant-on-a-Symbol site in a
fixture my rewiring script had rewritten — after I had swept production code for exactly that pattern
and not the fixtures. That is fixed in 2272f66d92 and is why this run is green.

The gate this green did not run

This branch's .github/workflows/witnesses.yml has five steps. main's has seven. The two it lacks
are Regen fixed point and Regen determinism, enrolled by #8618 at 06:49 — after this branch's
merge-base (e0c5e25444). So the green above never evaluated the regen gate that will judge this PR at
merge.

That matters here more than for a typical PR, because this PR changes .dag modules that have
committed stage0 projections
, while touching zero stage0 files
(git diff origin/main...HEAD -- src/v1/stage0/src/ is empty):

changed module committed projection in gate roots (dag, src/v2)
std.trait_derive_shape std_trait_derive_shape.rs yes
extdeps.languages.rust.emit extdeps_languages_rust_emit.rs yes
extdeps.languages.rust.capabilities none — new module in this PR yes
v1.compiler.trait_derive_emit v1_compiler_trait_derive_emit.rs no (src/v1 is not a gate root)
v1.compiler.emit_rust v1_compiler_emit_rust.rs no
four v2.* modules none —

std.trait_derive_shape is the sharp one: this PR cuts it roughly in half and moves most members out, so
its committed projection cannot still be what the authority emits.

Measured vs inferred, kept separate: the module list, the projection join, and the empty stage0 diff
are measured. "These will drift" is inferred from them — --required-regen has not been run on this
branch, and running it here would surface the pre-#8618 drift files alongside any of mine and would not be
comparable to main's population.

This PR therefore needs a regeneration it has not had, and is holding for it — main is currently red
on required-regen from an unrelated commit, and hand-editing a mirror is the exact act the drift work
exists to refuse. This PR does not inherit that red (#8614 is not an ancestor of this head).

Also: checks: none here is not a pending schedule

witnesses.yml — the only floor workflow —
declares pull_request: branches: [main], and that filter applies to the PR's base, not its head.
This PR's base is session/quick-lynx-620 (#8597), so no pull_request run has fired for either of the
last two pushes, including both defect fixes.

Corrected from an earlier revision of this section, which said this branch had zero runs in its entire
history — that was wrong.
The measured history:

time (UTC) event head result
00:05:19 PR opened, base was main 624fbb1499 cancelled
00:18:45 base changed to session/quick-lynx-620 c0fc9e4005 success
05:13 → now two pushes + ready_for_review, base ≠ main 4c267c24dd, 15b2750900 no run fired

So the branch was eligible during the window when its base was still main, and the one green floor run
it has is at c0fc9e4005 — an ancestor of the current head, predating both compile-invisible defect
fixes.
It is not evidence for what is on this PR now.

The rendering remains the hazard: no red, no yellow, no missing-required-check row, and gh pr checks
reports "no checks reported on the branch" — indistinguishable from runs that are still queued.
Retargeting to main would fix CI and destroy the stacking, so it is deliberately not done. A manual
workflow_dispatch on the branch does work and one is running against 15b2750900.

One observation worth handing on rather than asserting: the base change at 00:18:45 coincided with a run
firing. Whether the trigger was the base change itself or a synchronize at the same moment cannot be
separated from the run list alone — but it is at least suggestive that the reverse retarget, when #8597
merges, may schedule a run rather than silently not.

Module-scoped evidence

The witness evidence below was re-established under the CI-built binary rather than left resting on
local runs. The floor job builds claim_executor/gunbc from the checked-out branch in the same job that
runs the fold, so binary and subject are the same tree by construction — the run carries its own
provenance. My local binary predates today's mirror convergence, which makes it the weaker artifact for
any behavioural claim; it agreed with CI on both polarities (same witness red before the fixture fix,
green after), but CI is what is cited.

All four claim modules this PR touches appear in the CI run's executed set, inside
planned=9687 executed=9687 failed=0.

  • 18 witnesses green by execution across trait_derive_completeness_predicate_test and
    trait_derive_seed_emit_binding_test, re-run after each fix round. These include the record heap/copy,
    payload-coproduct, and pair-completion paths — i.e. the ones that flow through every retyped signature.
  • All changed entries compile with 0 blocking errors. The two diagnostics on
    coproduct_reflection_conformance_test (LiveTreeDisposition, SubstrateInputsOnly) are
    byte-identical on clean main and are not from this change.
  • No src/v1/stage0 file is touched and no regeneration is involved, so the mirror hold does not bind
    this PR. git diff origin/main...HEAD -- src/v1/stage0/src/ is empty.

That covers the modules this change touches. It is not the discovered roster and is not reported as
one.
Three defects in this PR were invisible to a clean compile — the eager-resolution discriminant
failure, the variant-against-symbol comparison, and the open-brand laundering — so compile-clean is
explicitly not being offered as evidence of anything beyond name resolution.


Bootstrap seam recut (1304c3996c) — and the receipt that two measurements are not one

required-regen refused this branch before comparing any bytes: the earlier move-down put
TargetCapabilityKey = Symbol where brand(...) in dag/std/trait_derive_shape.dag, forcing
import v2.std.node { Symbol } into a module src/v1 imports. regen_input_sources seeds from
hardcoded roots (cli_run.rs regen_source_roots: src/v1, dag) and ignores --source-root, so
src/v2 is not in the index at all.

The invariant is closure membership, not seed-projection: a dag/ module reachable by import from
src/v1 may not import v2.*. 591 dag/ modules import v2.* legally, because src/v1 doesn't reach
them. Not a gate defect to route around — stage0 is the v1 seed, and a seed closure reaching into
src/v2 would make the seed depend on the successor it bootstraps toward.

The shape: three declarations, one home each

home
open interface key TargetCapabilityKey v2.std.compilers.target_model
closed alphabet RustCapability dag/extdeps/languages/rust/capabilities
total bridge (alphabet → key), exhaustive, no wildcard v2.extdeps.languages.rust

std names no carrier: the shape table and both predicates take the carrier as a type parameter. A
seed-visible carrier would satisfy the gate by putting the target-model concept back inside the seed
closure — the coupling that made the predecessor work by accident. A type parameter removes the
dependency rather than relocating it. (There is also no retype target: zero type Symbol declarations
exist under src/v1 or dag.) The seed path then speaks the alphabet end to end, which deleted the
rust_capability_keys lift added a commit earlier.

Pair-completion is rekeyed onto its own PairCompletionOp (neg/add/sub/mul/div). add is semantic; a
target may realize it as a trait, an operator token, or a function. Keying the algebra on a
target-realization identity let that identity leak into target-independent algebra, and it would have
survived a placement-only repair looking fixed.

Two things I got wrong, both caught by reading rather than by a check

  • I deleted the bridge once parameterization left it with no callers. That converted an enforced
    correspondence into a spelling correspondence: RustClone and ^target_capability_clone stopped
    being joined by an authority and merely resembled each other, with nothing to refuse on divergence. A
    zero-caller function that is the sole connecting authority between two vocabularies is not dead — its
    callers are the two vocabularies, and the call graph cannot see that. Restored.
  • The scripted substitution that routed keys through the bridge rewrote the bridge's own arms into
    RustDebug => rust_capability_key(RustDebug) — sixteen self-recursive stubs. It type-checks.

The receipt: a green gate and a green floor would both have lied

Mid-recut, tdc_int_kernel_satisfies_arith_and_ord went red — it passed capability keys into a
predicate now keyed by the alphabet. It compiled clean and answered false, in a file
required-regen structurally cannot reach (src/v2 is not a regen root). A green closure gate plus a
green floor would both have reported success over a silently wrong v2 witness. Two measurements, not one.

Current state, both halves

  • Closure half — base moved to a6ca6882d18 (Regen 17 stage0 mirrors: self-hosting import-surface propagation from #8614's emit_rust fix #8652 merged 2026-08-20 12:42:54Z); origin/main merged
    into this branch cleanly at 3a8acdf208. Re-measured after the move rather than assumed — the merge
    looked obviously sufficient, which is exactly when a skipped measurement is cheapest to justify and
    worst to be wrong about. Result:

    required-regen: first_generation_equal=false candidate=target/stage0-regen-candidate
    required-regen: FAIL refusal: surface population mismatch
      emitted_not_committed=["extdeps_languages_rust_capabilities.rs"]
      committed_not_emitted=[]
    

    The eight committed_not_emitted rows collapsed to zero, confirming the prior reading that they had
    no independent existence — this branch's base was stale, not this branch. (Identity join with
    snappy-eagle-615, owner of that population, had established my eight were a strict subset of their nine;
    their whole ten-row population closed on main by supersession.) One row remains and it is irreducibly
    mine: extdeps.languages.rust.capabilities is a new module in this PR, so the emitter produces a
    mirror that was never committed.

    Instrument caveat, recorded because it nearly invalidated the above: the dispatch's own
    [prov] main_ancestor=no line is a false negative. The remote runner clones --depth=1
    (shallow=yes depth_count=1), so merge-base --is-ancestor is asking a history question of a tree with
    no history, and "cannot compute" renders as "no". Verified locally that a6ca6882d18 is an ancestor,
    and the dispatched HEAD SHA matches the branch byte-for-byte. In a remote dispatch, SHA comparison
    carries provenance; any ancestry or history predicate does not.

  • v2 half — all three consumers required-regen cannot see compile with 0 blocking errors; 18/18
    witnesses green by execution.

Sequencing resolved (smart-ram-730, 2026-08-20): generate BEFORE #8624. The concern routed to them was
that #8624 changes an emitted string literal, so its reach would exceed its own projection. On reading it,
that claim was stated at the wrong grain and they corrected it: #8624 is source-level value-preserving
renames (make_span(start: 0, end: 0) → no_span()) plus exactly one emitted literal, and that one
lives in compiler_tests_rust.dag, which emits generated Rust test files — not module mirrors. Nothing in
#8624 changes what 05_emit_rust writes, only how it is spelled, so it does not reach this module's mirror.
The second, independent reason holds even if the first is wrong: a board measured against a
soon-to-change emitter is silently disposable, whereas a mirror generated against one is gate-caught —
required-regen refuses on #8624's own whole-tree pass and #8624's regen rewrites it. Different risk
profiles; only the measurement half needs to wait.

Diagnostic regression found and removed: +14 → +3 rustc errors

Measured against current main on the target_model board (same entry and probe on every arm,
PROBE_EXPECT_BASE_SHA armed throughout), this PR initially added 14 rustc errors to the emitted crate.
Reported before it was understood, then removed in three measured steps:

arm ref rustc errors
main 1ebac31a099 189
contract as first submitted 939592684540 203 (+14)
stored index removed from the record 69ff5f4462 199
Map dropped from the consumer paths 568b940847 196
Map gone entirely b7ed7aaf14 192 (+3)

Final histogram vs main: E0308 66→69, every other code identical — E0277 55=55, E0369 15=15.
The entire derive class is closed.

Root cause, and it is not what the first diagnosis said. TargetCollectionRealization carried the
capability eligibility as a Map, which is a derived index over an authored
List<TargetCapabilityEligibilityRow> — redundancy under §2 regardless of emission. But moving the index
out of the record removed only 4 of 14. The rest needed a stricter rule: Map<K,V> must not appear in any
declared type position
, because it renders as PartialFunction in type position and im::HashMap in
expression position, so a function declared to return a Map and implemented with map_insert mismatches
itself. (Mechanism since confirmed by deep-ant-102 at v1.compiler.05_emit_rust
rust_normalize_partial_function_field_type_text — a string replace on already-rendered text.) The rows
are now the lookup: a fold, no index, no Map.

No refusal was lost. The decoder still rejects a duplicate capability key with the same located
diagnostic — it folds the rows and refuses when a later row repeats an earlier key, instead of refusing on
map insert. All four consumer entries compile with 0 blocking errors.

The remaining 3 are one pre-existing emitter shape (bind_outcome's closure parameter typed as (), then
matched against Some(..)); main carries seven instances of it. This PR's signature change moved two more
call sites into that class rather than creating it, and they are not claimed as a price of the contract.

Known collision with #8574 — reconciliation is semantic, not textual

#8574 ("Row 1a + E0310: synthesize the bounds the Rust realization requires") is not draft and
MERGEABLE against main; this PR is draft and blocked. It landing first is correct, and the reconciliation
is this PR's to do. Recording the shape now so it is not discovered as conflict markers:

The reverse order is the harmful one — #8605 landing first would replace the carrier underneath a ready,
approved PR — which is a second reason the draft stays.

The blocker is owned and its fix is disjoint from this collision (swift-moth-294, starting next).
The fn-level bound trigger is well-formedness driven, not derive-alphabet driven: it reads value params,
return type, bounds and declared items, and never consults the carrier. Verified independently on this
branch — the only ReprGroundingDeriveTrait / RustCapability references in src/v1/trait_derive_emit.dag
are the import and one relocated carrier-typed helper (lines 35, 157–159); none of the fn-grain bound
functions (633–1166) touch either. So the file collides while the machinery does not, which is what makes
the equality trigger landable while the carrier question is still open. The unblock and the
reconciliation are independent.

#8574 does not unblock this PR, despite the title match. It synthesizes supplemental bounds on item
and impl headers
, required by upstream conditional impls (im::Vector's A: Clone). The blocker here is
a free function whose own body compares two values of its type parameter, needing K: PartialEq on the
function's signature. Different trigger, different site.

A dead import removed, and a causal claim about it RETRACTED

import std.trait_derive_shape { } — dead residue left in src/v1/05_emit_rust.dag when its last imported
symbol moved to the new capabilities module — is deleted in ae2fb03e3d. That deletion is correct on its
own merit: the statement imports nothing.

An earlier revision of this section claimed that line caused nine pub mod v2_compiler_* declarations to
vanish from the emitted lib.rs and a phantom expected_red_roster_join to appear. That claim is
retracted — it is refuted by execution:

arm files pub mod v2_compiler_* roster decl
A — HEAD as-is, line deleted 131 13 0
B — same tree, line restored 131 13 0

diff -rq over the whole emitted tree: no differences. One frozen binary, one variable.

The original four-arm measurement was confounded. Its branch arm ran on an older gunbc than its other
three arms — the binary was rebuilt mid-sequence as a side effect of a cargo build --release --bins run
for an unrelated probe, and the one anomalous arm is the only one predating that rebuild. That binary also
predated #8527, which is why its lib.rs carried expected_red_roster_join, a name current sources cannot
emit. The claimed control ("same binary throughout") never existed, and the variable the effect was
attributed to covaried exactly with one that was not known to have moved.

Two things survive, neither depending on the retracted numbers: an empty import list can change emitted
bytes — established independently by another session, whose specimen is a module's sole import
manufacturing a phantom module, a different condition from this one; and the seed builds clean (lib and all
bins) with nine declarations absent, measured on an installed tree rather than on an emit.

BLOCKED — the emitter cannot emit an equality bound on a type parameter

Producing that mirror surfaced a defect nothing before it could have seen, and it is not a defect in the
mirror. Two facts, both measured:

  1. --required-regen cannot produce a first mirror at all. validate_compared_populations refuses on
    the population mismatch and returns before write_emitted_tree runs, so the candidate tree is never
    written for a new module; regen_stage0, the binary that used to write it, is deleted (the regen cut).
    Reported separately — it is not this PR's to fix.

  2. The emitted contract does not compile. Reproducing regen's own emitter path
    (gunbc compile --source-root src/v1 --source-root dag --target rust, then rustfmt to a fixed point,
    which is what normalize_generated_source does — control: extdeps_languages_rust_types.rs came out
    byte-identical to its committed mirror) and rebuilding the seed yields exactly one error:

    error[E0369]: binary operation `==` cannot be applied to type `K`
      --> src/v1/stage0/src/std_trait_derive_shape.rs:82:35
    

    The emitter derived <K: Clone> and stopped; the membership test does k == capability_key.

This is a capability gap, not a modeling error. The .dag compiles clean (0 blocking errors) and the v2
path runs it green (18/18) — equality on a type parameter is expressible in the model and unrealizable in
the emission. The emitter's bound-derivation apparatus is mature but Clone-only: three triggers, a
well-formedness fixpoint, its own witnesses, and no equality trigger anywhere. The obvious refutation
was checked — the seed does carry 16 K: std::cmp::Eq + std::hash::Hash bounds, and every one is a
hand-authored string literal in src/v1/runtime_rust.dag's prelude, not a derivation's output. No generic
function in the seed closure has ever compared two values of a type parameter, which is why the gap stayed
invisible until this contract needed it.

The bound is sufficient, not merely necessary — probed, not predicted. Hand-adding + PartialEq to the
two generic signatures in the regenerated mirror and rebuilding the seed lib gives Finished release profile in 2m 19s, zero errors — so no second emission defect hides behind the first, which is the one
thing a single-error build cannot tell you. Note the required rendering is K: Clone + PartialEq: the new
bound composes with the derived Clone bound on the same parameter, so a fourth trigger has to union into
the existing rendering path (emit_type_params_with_clone_bound today takes a single clone_param: String
and renders one bound). The probe is a probe: uncommitted, not pushed.

Ruled (operator, 2026-08-20): option A, routed to the owner of those seed files — and A alone does not
unblock this PR.
The population refusal fires on emitted_not_committed whether or not the emitted
content compiles, so #8605 is blocked on two independent things: the equality trigger, and a sanctioned
producer for a first mirror. The second is a regression, not a gap — regen_stage0 could write the
tree, the regen cut deleted it, and nothing declared the lost capability; it is escalated separately, and
building a producer here would be exactly the out-of-band actuation §6 names.

The root fix is a fourth trigger in the same family — a generic param earns PartialEq when the body
compares two values resolving to it. Awaiting an operator ruling on whether that lands here or as its own
PR; this PR holds behind it. De-parameterizing the predicates onto the concrete Rust alphabet would emit
fine and is the workaround: it puts one predicate per target language, the N×M direction this contract
exists to avoid.

Nothing is pushed. The regenerated mirrors stay uncommitted — a red seed is worse than the population
refusal it would replace.

The mirror and its lib.rs declaration are regen's to produce (lib.rs is itself a generated file,
header // Generated by v1 compiler -- do not edit.); hand-authoring either would be manual application
committed as source.

Brian Searls and others added 3 commits August 19, 2026 22:57
…ss at the root

RULED (operator, 2026-08-19, relayed by smart-ram-730): B1 resolves as option (b),
a capability-keyed contract subsuming RequiredTraitWitness — not the coproduct.

WHY, and it does not rest on any diagnostic count. RequiredTraitWitness's three
variants (TargetCollectionWitnessOrd/Hash/Eq) were RUST TRAIT NAMES sitting in a
std interface. DESIGN §4: a new target language is rows in extdeps/languages, never
an edit to the fold — so a target whose sharing requirements differ from Rust could
only express them by editing that coproduct, which is the N×M adapter trap §4 exists
to prevent. §3 puts realization-selecting dispatch peripheral, never in the interface.

WHAT LANDS

  type TargetCapabilityKey = Symbol where brand("TargetCapabilityKey")
  type TargetCapabilityRequirement { representation_parameter, capability_key }
  type TargetCapabilityElementEligibility { kernel_atoms: List<Symbol> }
  type TargetCapabilityEligibilityTable = Map<TargetCapabilityKey, ...>

RequiredTraitWitness is DELETED at the root, not hollowed or adapted — zero
references remain in v2 std, extdeps, compiler, or tests. Per-target eligibility
rows are authored in extdeps/languages/rust.dag and ride the target's own bundle,
so std decodes them generically and never names a Rust trait.

The wire form was ALREADY keyed on a Symbol atom; only the in-memory type was a
coproduct. So the migration DELETES the translation boilerplate rather than adding
to it: rust_collection_witness_bundle_node 48 -> 16 lines, and the 43-line
if-chain in target_collection_witness_from_node collapses to a direct read.

A §5 REPAIR THAT RODE ALONG, named so reviewers see it rather than find it.
repr_grounding_derive_traits_for_collection_witness had a two-arm if/else in which
an UNRECOGNISED capability silently received the equality trait list — an absorbing
fallback (a failure arm that defaults instead of refusing). It now returns Optional
and refuses, with tdc_unknown_capability_refuses_rather_than_defaulting as the
discriminating input: it asserts an undeclared capability is Absent AND that a
declared one is not, so it goes red if the refusal is removed.

Unknown capability at satisfaction time is likewise a typed, located refusal
(target_capability_eligibility_row_absent) rather than a silent true/false.

WHAT THIS DOES NOT DO, measured rather than assumed. The work item claimed this
carrier was "the measured root of 97 diagnostics across 20 module boards". That is
false and the operator has accepted the refutation. RequiredTraitWitness was
MONOMORPHIC (type_param was a Symbol FIELD, not a type parameter) and target_model
declares zero generic types and zero generic functions, so it cannot originate a
trait-bound class. A clean-worktree measurement at e0c5e25 confirms it: of 68
primary diagnostics in the emitted v2_std_compilers_target_model.rs there are ZERO
"no method clone for type parameter" and ZERO "trait bound T: Clone". The board is
function-valued carriers that cannot derive serde/Debug (19), missing-field
initializers (16), map-accessor emission gaps (5), and E0308/E0004 residue. Same
file, different defect. This PR will not move those counts.

EVIDENCE
  0 blocking errors on all six changed entries (gunbc compile, 2 source roots,
    primary-precedence).
  trait_derive_completeness_predicate_test: 6/6 green BY EXECUTION, including the
    new discriminating test.
  Two DeclarationRef citations in derive_contracts.dag named the deleted
    declaration; repointed, so no citation names a symbol that does not resolve.

LANGUAGE-LAYER DEFECT WORTH KNOWING: `capability` is a reserved keyword. Using it
as a field name produces "expected RParen, found keyword 'capability'" reported at
a character offset ~300k away from the real line. Field is spelled capability_key.

Census defect recorded per operator instruction: the phase-B0 probe document reports
TWO DIFFERENT 139s in two partitions of one 369-occurrence population with different
site counts (39 vs 20), and NoEmitterArm's authority lines are a proper subset of
DerefCloneWholeValue's — which cannot hold at an equal occurrence count. Marked
unreliable and unreproducible (its instrument was a deleted shell scaffold) rather
than resolved or quietly dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pdScziA7qwW3dqMP1B4Zq
Both findings are correct and both are recorded in the module header rather than
answered in the PR thread, because a trigger that lives only in a review comment is
not a trigger.

(1) RESIDUAL PER-TARGET AUTHORITY. repr_grounding_derive_traits_for_capability keys
on the three Rust capability symbols and answers with Rust derive-trait lists — the
same per-target policy this PR evicted from std/target_model, now one layer up in
v2/compiler instead of in extdeps/languages/rust beside the eligibility rows. It is
not a second carrier, but it is a second per-target ROW SET, so a non-Rust target
would still edit this file.

Sequencing stated rather than deferred by omission: these rows are typed in
ReprGroundingDeriveTrait, so migrating them to extdeps BEFORE that 16-variant
coproduct is keyed would only relocate the coproduct unchanged. They move together
in the ReprGroundingDeriveTrait cutover (PR 2).

(2) OBLIGATION ON Absent, PINNED BEFORE A CONSUMER EXISTS. Absent means "unknown
capability key", never a satisfied or unsatisfied gate. No production consumer
exists today, so this is not live fail-open — but when one is wired, Absent must
route to a typed located refusal. Folding it into Satisfied or Incomplete, or
treating it as skip-the-gate, would reintroduce precisely the absorbing fallback
this PR removed. tdc_unknown_capability_refuses_rather_than_defaulting is the
control that goes red if that behaviour returns.

0 blocking errors on the changed entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pdScziA7qwW3dqMP1B4Zq
Self-inflicted, and the file said so before I edited it. The note I amended already
carries "COUNTS ARE DELIBERATELY NOT RESTATED HERE (review 44386ate)": this module
previously restated site and occurrence counts, they went stale the moment #7324
moved the corpus, and they then contradicted the receipt — a §3 dual representation
of a fact whose home is the measurement, not the module. I added counts back into
that exact note.

Removed: the carrier-population figure, the measured-board figures, and the base SHA
of the measurement run. Kept: the ruling, the reason the sizing figures do not size
carrier work (they count emitted-Rust occurrences under a lowering-operation
partition — a different population, which is a statement about denominators, not a
number), and a pointer to the probe document that is the measurement's actual home.

The attribution refutation is kept but re-stated STRUCTURALLY rather than
numerically: RequiredTraitWitness was monomorphic, so it could not originate a
trait-bound class at all. That argument does not decay when the corpus moves; a
diagnostic count does.

Flagged by deep-ant-102, whose general point — never justify this contract by a
diagnostic count in a durable artifact — is correct and lands on this note
specifically, notwithstanding that the counts here were recording a REFUTATION of
that attribution rather than a justification for the work.

0 blocking errors on the changed entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pdScziA7qwW3dqMP1B4Zq
@gunbai-bot gunbai-bot Bot changed the title B1 RULED (b): build the capability-keyed sharing contract subsuming RequiredTraitWitness in v2.std.compilers.target_model — the measured root of 97 diagnostics across 20 module boards B1 PR 2: collapse TargetDeriveTraitKey and every non-v1 use of ReprGroundingDeriveTrait into TargetCapabilityKey Aug 20, 2026
…tyKey

Two brands stood over one overlapping key space in target_model — and PR 1
created the second one, so this retires a fork of my own making.

TargetDeriveTraitKey was never an independent authority. Measured: every value of
it in the corpus came from a single producer,

  target_grounding_derive_trait_key(t) = target_grounding_derive_trait_discriminant(t)
                                       = discriminant(t)

so the brand was a projection OF ReprGroundingDeriveTrait. It and TargetCapabilityKey
are both `Symbol where brand(...)` and their key spaces overlap on Ord, Hash, Eq and
Clone — the §3 nickname pair.

Collapsed into TargetCapabilityKey. The producer is renamed
target_capability_key_for_derive_trait, since a function returning a capability key
should not be named for the type it no longer returns.

NO v1 EXPOSURE, WHICH IS WHY THIS IS LANDABLE NOW. The producer still ACCEPTS a
ReprGroundingDeriveTrait and returns its discriminant, so no shared std or extdeps
signature changes and frozen v1 is untouched. All 19 sites are v2-only
(target_model 10, v2 extdeps rust 3, one v2 test file 6).

THE FORK IS NOT CLOSED, AND THE CARRIER SAYS SO. After PR 1 plus this, main still
carries ReprGroundingDeriveTrait AND TargetCapabilityKey as two representations with
the Ord/Hash/Eq/Clone overlap LIVE. Three representations to two — not closed. That
is recorded in target_capability_key_fork_state_note rather than left for the word
"collapse" to overstate; a diff that reads as closing a fork while leaving one is the
rung inflation §4b names, applied to a changeset.

WHY IT COULD NOT BE CLOSED HERE — measured, and it reverses what three sessions had
agreed. src/v1/trait_derive_emit.dag imports twenty symbols from
std.trait_derive_shape; five of them (record_derive_traits_heap, _copy,
nullary_coproduct_derive_traits, symbol_wrapped_ord_carrier_derive_traits,
repr_grounding_derive_completeness_predicate) are the SAME declarations v2 and
dag/extdeps/languages/rust/emit consume — not parallel copies — and their signatures
are typed in the coproduct. extdeps.languages.rust.emit
rust_trait_derive_attr_from_traits additionally takes List<ReprGroundingDeriveTrait>
and is imported by v1 directly.

So the coproduct crosses the frozen boundary through BOTH std and extdeps, and every
route to migrating a consumer either changes a signature v1 shares (breaking frozen
v1) or adds a keyed surface beside the coproduct in std (a dual authority — worse
than today). Both arms refuse, which is what makes this a block rather than a
preference. The prior conclusion that the freeze ruling governed only a final
re-home step, and was therefore off the critical path, is refuted by that import
measurement.

0 blocking errors on all three changed entries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pdScziA7qwW3dqMP1B4Zq
@gunbai-bot
gunbai-bot Bot changed the base branch from main to session/quick-lynx-620 August 20, 2026 00:18
@gunbai-bot gunbai-bot Bot changed the title B1 PR 2: collapse TargetDeriveTraitKey and every non-v1 use of ReprGroundingDeriveTrait into TargetCapabilityKey B1 PR 2 (Option A): collapse TargetDeriveTraitKey into TargetCapabilityKey — fork goes three representations to two, NOT closed Aug 20, 2026
Brian Searls and others added 2 commits August 20, 2026 01:17
…e to extdeps

The freeze relaxation ("anything in support of v2 self host is safe") unblocks the
half of B1 that was measured as blocked. This is the structural move; consumer
rewiring follows.

ReprGroundingDeriveTrait was sixteen Rust and serde trait names sitting in a std
interface, so a non-Rust target could only express its capabilities by editing that
coproduct — the §4 N×M adapter trap. Replaced by TargetCapabilityKey.

WHAT SPLITS WHERE, and the line is target-agnostic vs target-specific:

  std.trait_derive_shape   TargetCapabilityKey brand; ReprGroundingDeriveElemShape
                           (SOURCE shapes — kernel string/int/bool/unit, nullary enum,
                           payload coproduct — substrate concepts, legitimately std);
                           generic table lookup and completeness predicate that take a
                           TargetCapabilityShapeTable AS A PARAMETER. 547 -> 272 lines.

  extdeps.languages.rust.capabilities (new)
                           the sixteen capability keys, the shape-by-capability rows,
                           the derive-trait list builders, the emission order.

So dispatch is peripheral (§3) and a new target is a sibling module of rows, never an
edit to the fold (§4).

TargetCapabilityKey MOVES DOWN from target_model into std.trait_derive_shape. Primary
reason is §3 placement — the capability key belongs with the capability authority, not
in the target model that consumes it. It also removes an import cycle: target_model
already imports std.trait_derive_shape, so keying that module on a type declared in
target_model reverses the direction, and acyclicity is the one structural law DESIGN
does not bend. PR 1 placed it in target_model only because that was the sole file in
scope then.

THIS FIRES THE MODULE'S OWN DECLARED TRIGGER RATHER THAN OVERRIDING IT.
trait_derive_shape_dissolve_on already said these Rust-specific members "migrate beside
each target's spelling table WHEN A SECOND CONSUMER EXISTS." The C-linkage realization
is that second consumer. The ruling fires a trigger the authority had already written,
which is a stronger position than overriding one.

ROW FIDELITY — the part that could have silently emptied. The shape×capability rows
were extracted mechanically from the predecessor's nested match, not hand-retyped:
71 admitted pairs extracted against 71 `=> true` cells in the source, 8 shape rows.
My first extraction pass silently produced ONE row and a table that still compiled —
the fixture-emptied-of-its-subject failure, in my own tooling. The counts are asserted
against the source rather than eyeballed.

Serialize and Deserialize are serde names, not Rust language names, and their spellings
were ALREADY cited in extdeps rust_trait_derive_spelling — so the std coproduct was a
redundant KEY SET forked from a key extdeps already spelled. This finishes a migration
someone had started rather than opening a new one.

DECLARED RESIDUE: std.trait_derive_shape's NAME now says "derive shape" for what is a
capability authority — a §3 nickname for what it has become. Not renamed here: a
mechanical rename across every importer, bundled into a migration already reshaping
contents, makes both unreviewable. Named in the module note with its trigger.

0 blocking errors on both authorities.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pdScziA7qwW3dqMP1B4Zq
…ects compile-clean could not see

Finishes the ReprGroundingDeriveTrait -> TargetCapabilityKey migration the previous two commits
started, and repairs two defects that a clean `gunbc compile` reported nothing about.

WHAT LANDED

- Every consumer of the derive-trait lists and the completeness predicate now reads the Rust rows
  from extdeps.languages.rust.capabilities and passes the shape table explicitly: v1 trait_derive_emit
  and 05_emit_rust, v2 trait_derive_completeness, and four witness modules.
- kernel_int_arithmetic_traits migrated with the other builders (it was left behind by the split).
- RustCapability: the coproduct is RE-HOMED into extdeps rather than deleted. TargetCapabilityKey is
  an open Symbol brand so unrelated modules can share one key space; an open brand cannot be matched
  exhaustively, and rust_trait_derive_spelling was TOTAL by exhaustive match before this migration.
  Deleting the coproduct outright would have traded a structural guarantee for a runtime refusal --
  a rung DROP dressed as a migration (DESIGN 4b). Interface speaks keys; realization speaks the
  closed alphabet; rust_capability_key is the single total lift between them.
- The pair-completion spelling rows now carry the capability key they answer for, so the join to
  std.trait_derive_shape is an identity match. That REPLACES a sixteen-arm key-to-method-name
  function -- a second naming scheme for what the key already names (DESIGN 3). One thing got
  weaker and the note beside it says so: the refusal arm can no longer name the unmatched key,
  because no Symbol-to-String projection exists; it prints the keys that ARE spelled instead.
- TargetCapabilityKey is declared ONCE (std.trait_derive_shape) and imported everywhere. target_model
  had a local declaration AND the identical import.

TWO DEFECTS THAT COMPILED CLEAN

1. target_grounding_derive_trait_discriminant called `discriminant(v: key)` on what is now a branded
   Symbol, and was ALSO passed as a function VALUE to coproduct_nullary_inhabitant_by_discriminant,
   so it resolved eagerly. Effect: any module whose closure reached target_model could no longer
   resolve the `discriminant` builtin at all, surfacing as NoSuchFunction attributed to whatever
   entry function happened to be running. All fifteen entries reported 0 blocking errors throughout.
   The wrapper, its second name, and the coproduct-inhabitant decode all collapse to the identity
   for a branded Symbol and are gone.
2. The witness files passed List<RustCapability> where List<TargetCapabilityKey> was required. It
   compiled; at runtime the comparison was variant-against-symbol and silently answered false.
   rust_capability_keys lifts at the boundary.

EVIDENCE, BY EXECUTION

- Minimal pair for defect 1: a probe module declaring its own coproduct and calling `discriminant`
  passes on clean main, fails NoSuchFunction on this branch, discriminated solely by importing
  v2.extdeps.languages.rust. Green after the fix.
- 18 witnesses across trait_derive_completeness_predicate_test and trait_derive_seed_emit_binding_test
  run green, including the pair-completion tests that exercise the rewritten key join. Defect 2 was
  found by one of them going red, not by review.
- All changed entries compile with 0 blocking errors. The two diagnostics on
  coproduct_reflection_conformance_test (LiveTreeDisposition, SubstrateInputsOnly) are byte-identical
  on clean main and are not from this change.

`gunbc compile` reporting zero blocking errors is not evidence that a module evaluates. Both defects
above were invisible to it and both were found by running a witness (DESIGN 5).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pdScziA7qwW3dqMP1B4Zq
@gunbai-bot gunbai-bot Bot changed the title B1 PR 2 (Option A): collapse TargetDeriveTraitKey into TargetCapabilityKey — fork goes three representations to two, NOT closed B1: capability-keyed sharing contract — one open key brand in std, Rust rows in extdeps, closed alphabet re-homed to keep spelling total Aug 20, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 20, 2026 05:13
…w 53978), and delete this PR's own unreferenced rows

REVIEW FINDING, CONFIRMED AND FIXED

review 53978 found v1_with_map_key_requirement declared as
List<TargetCapabilityKey> -> List<TargetCapabilityKey> while every caller passes a list builder's
List<RustCapability> and every consumer of its result takes List<RustCapability>. The signature
typed on the OPEN brand what the function threads as the CLOSED alphabet -- silently laundering
away the exact invariant the alphabet exists to hold (DESIGN 4b), and the compiler said nothing.

The chain went one link further than the report: v1_with_map_key_requirement calls
derive_traits_union, which with derive_traits_contain was declared in std.trait_derive_shape on
the key brand -- and those two had exactly ONE consumer, this function. So the repair is not a
cast at the boundary; the helpers were in the wrong module on the wrong type. Both are relocated
to extdeps.languages.rust.capabilities, typed on RustCapability, and v1's signature follows.

Recorded beside them, so it is not retried: keeping them in std and lifting to keys at the call
site would have forced the union result back through a key-to-spelling step that no longer exists,
because spelling is total only over the alphabet. That repair looks smaller and breaks the
guarantee.

A SECOND DEFECT FOUND SWEEPING FOR THE FIRST

Rather than trust a single report, I enumerated every remaining key-typed surface. The sixteen
`rust_capability_<name>: TargetCapabilityKey` data rows and `rust_capability_emission_order` --
all authored by me earlier in this PR -- have ZERO references corpus-wide. A new artifact with no
final consumer is experimental residue (DESIGN 6), and sixteen rows that read as the key authority
while nothing consumes them are worse than absent. Deleted. Emission order is carried by the list
builders, where it was before this migration.

The module note claimed both as contents; it is rewritten rather than left asserting deleted rows.

DECLARED RESIDUE, NEW

derive_traits_contain / derive_traits_union are a membership test and a set union over a list,
specialized to one element type because this corpus has no generic list union to instance. Naming
it rather than minting one: authoring a new std primitive is wider than the finding that produced
the move. Dissolves when a second element type asks for the same union.

EVIDENCE

All affected entries compile with 0 blocking errors, and -- because compile-clean is precisely what
missed this class -- nine witnesses re-run green by execution across both affected test files,
including the record heap/copy and payload-coproduct paths that flow through the retyped function.

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

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Confirmed and fixed in 15b2750900. The finding was correct, and the chain went one link further than the report.

The extra link. v1_with_map_key_requirement calls derive_traits_union, which — with derive_traits_contain — was declared in std.trait_derive_shape typed on TargetCapabilityKey. Those two had exactly one consumer corpus-wide: this function. So the repair isn't a cast at the boundary; the helpers were in the wrong module on the wrong type. Both are relocated to extdeps.languages.rust.capabilities, typed on RustCapability, and the v1 signature follows.

I took the second of your two options deliberately and recorded why beside the code, so it isn't retried: wrapping with rust_capability_keys(...) before the call looks like the smaller change, but it forces the union result back through a key-to-spelling step that no longer exists — spelling is total only over the alphabet. That repair would have preserved the signature and broken the guarantee.

A second defect, found sweeping for the first. Rather than fix only what was reported, I enumerated every remaining key-typed surface in the PR. The sixteen rust_capability_<name>: TargetCapabilityKey data rows and rust_capability_emission_order — all authored by me earlier in this PR — have zero references corpus-wide. Experimental residue under DESIGN §6, and sixteen rows that read as the key authority while nothing consumes them are worse than their absence. Deleted; the module note that claimed them as contents is rewritten rather than left asserting deleted rows. Emission order is carried by the list builders, where it was before this migration.

New declared residue. derive_traits_contain / derive_traits_union are a membership test and a set union over a list, specialized to one element type because this corpus carries no generic list union to instance. Named rather than minted — authoring a new std primitive is wider than the finding that produced the move — with a dissolution trigger on the second element type that asks for one.

Evidence. All affected entries compile with 0 blocking errors. Since compile-clean is exactly what missed this class, nine witnesses were re-run green by execution across both affected test files, including the record heap/copy and payload-coproduct paths that flow through the retyped function.

Worth noting for the record: this is the third defect in this PR that a clean compile could not see, after the eager-resolution discriminant failure and the variant-against-symbol comparison. Review caught this one; execution caught the other two.

— sent from quick-lynx-620

…wn rewiring script rewrote

The manual floor dispatch on 15b2750 came back with exactly one unheld failure out of 9687
executed (passed=9380, known_red_held=306, failed=1):

  required-floor: FAIL v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.
    malformed_contract_row_refuses_on_bundle_decode errored: no such function: discriminant

ROOT CAUSE

  sgb_atom_node(identity: discriminant(v: ^target_capability_debug))

discriminant of a SYMBOL LITERAL. The fixture originally read discriminant(v: ReprDeriveDebug) --
a coproduct variant, where the call was meaningful. My scripted rewiring replaced the variant with
the symbol literal and left the call wrapped around it, producing an expression that is
well-formed, compiles clean, and cannot evaluate. Same class as the two already fixed in this PR,
third instance, and the one place I had not looked: I swept production code for the pattern and
not the FIXTURES.

Now `sgb_atom_node(identity: ^target_capability_debug)`. The fixture's malformation is unchanged --
a positional edge where the decoder requires named ones -- so the witness still refuses for the
reason it was written to refuse, and the round-trip witnesses confirm the decoder still ACCEPTS
well-formed rows rather than refusing everything.

ALSO: TargetCapabilityKey was imported from BOTH std.trait_derive_shape and
v2.std.compilers.target_model in two files. target_model no longer declares it, so the second
import named a symbol that module does not own. Removed. Stated at the strength of the evidence:
this is a section 3 duplicate-binding defect on its own terms, and removing it did NOT fix the
failure above -- I checked before moving on.

EVIDENCE

  malformed_contract_row_refuses_on_bundle_decode        true   (the floor's one red)
  missing_contract_authority_refuses_on_bundle_decode    true
  nested_generic_contract_round_trip_preserves_keyed_authority          true
  rust_vec_target_bundle_round_trip_preserves_im_vector_policy          true
  rust_set_target_bundle_round_trip_preserves_both_production_policies  true
  rust_vec_unmodeled_ord_contract_refuses_as_missing     true

The round-trips are there deliberately: a refusal witness going green after a fixture edit proves
nothing unless the accept path still accepts.

A stacked PR schedules no pull_request runs, so this push fires nothing. Re-dispatching the floor
manually against the new head.

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

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Authority-side review, my two sole-write files only — src/v1/05_emit_rust.dag and src/v1/trait_derive_emit.dag. A read of the diffs on those files, not a review of the PR and not a block.

No objection to either. GREEN from the authority side.

05_emit_rust.dag — phantom_opaque_carrier_derive_traits moves from std.trait_derive_shape to extdeps.languages.rust.capabilities.

This is the right direction and I'd say so unprompted: a Rust-realization capability is not a std fact. DESIGN §3 puts the parameterized interface in std and keeps realization peripheral — "the dispatch that selects a realization is itself realization" — so a Rust-specific derive-trait set living in std.trait_derive_shape was a layer inversion, and extdeps/languages/rust/ is where it belongs. Consistent with the B1 framing this PR is landing under.

trait_derive_emit.dag — pure import-list reformatting, single-line to multi-line, same symbols from the same modules. No objection.

One note for whoever regenerates this branch, because the two files behave differently and it will look like a bug if you expect otherwise:

  • The 05_emit_rust.dag change does move its mirror. Import source is part of the emitted import surface, so the generated use line names a different crate module.
  • The trait_derive_emit.dag change on its own may produce no mirror change at all — the symbols and their providing modules are identical, so the emitted use lines should be byte-identical. .dag line breaks are not emitted structure.

So if v1_compiler_trait_derive_emit.rs comes back unchanged while v1_compiler_emit_rust.rs moves, that is the expected result, not a partial regen.

And this PR does need a regen it currently lacks. --required-regen became required at 06:49 today (#8618), after this branch was authored — so this is an obligation that landed underneath an open branch, not an omission against the rules as they stood. Measured on my two files here: 751 changed lines, 0 comment-only, so it is semantic and it drifts. (Comment-only changes are the one exempt class, since DESIGN §4c gives semantic passes the annotation-erased projection.)

Expect two generations, not one. v1_compiler_emit_rust.rs is the emitter emitting itself, so regenerating it changes the compiler that computes its own output. #8652 needed exactly two rounds — measured independently in two environments today as 574d3ee5 → 93de569c → 93de569c — and its first CI red was a true fact about generation 1, not a failure of the fix. Regenerate, rebuild, regenerate again, stop at first_generation_equal=true.

One sequencing note that is not mine to decide: #8624 also changes 05_emit_rust.dag, and it alters an emitted string literal, so its blast radius extends past its own projection into other modules' mirrors. Anything regenerated before #8624 lands is regenerated against an emitter that is about to change what it writes. Worth knowing before you spend the regen.

@gunbai-bot

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

🛑 Do not merge — required-regen refuses this branch

Requested by deep-ant-102 and run on head 2272f66d92, provenance printed before the build in the same
dispatch:

[prov] HEAD=2272f66d92528e6346b0a9d8702b86ccd70a6609
[prov] stage0_diff_vs_merge_base=0
[prov] built=0
required-regen: refused: v2 self-compile produced 2 hard diagnostic(s):
  unresolved import: module 'v2.std.node' not found (imported by 'std.trait_derive_shape')
  unresolved type 'Symbol' (dag/std/trait_derive_shape.dag)
[prov] regen_exit=1  [prov] TERMINAL-MARKER-END

It refuses before comparing bytes, so the drift question this run was meant to answer is never
reached.

Cause — the move-down in this PR

TargetCapabilityKey is Symbol where brand(...), so homing it in dag/std/trait_derive_shape.dag
required import v2.std.node { Symbol } in that module. That module is seed-projected —
src/v1/stage0/src/std_trait_derive_shape.rs names it on line 2 — and under the gate's two roots
(dag, src/v2) the v2 import does not resolve.

Measured rather than assumed: exactly one other dag/std module imports v2.*
(std.orthogonal_topology), and it has no projection. This PR's is the only seed-projected module in
the corpus that does.

On main the predicate was typed on ReprGroundingDeriveTrait, a coproduct declared in that module, so
the carrier was seed-local by construction. Replacing it with an open brand is what broke that
property, and nothing in the migration surfaced it.

Why every check on this PR was blind to it

check result
gunbc compile, 3 roots (dag, src/v2, src/v1) 0 blocking errors on all entries
CI floor run, binary built from this branch planned=9687 executed=9687 failed=0
two review passes APPROVE, no finding in this area
required-regen, 2 roots refuses

Adding src/v1 as a root makes the import resolve. Entry scope is not gate scope, and a green floor is
not a green regen.

Status

Holding for a design call on where the brand's carrier lives — the options trade the §3
single-declaration property against the seed-local-carrier property, and that is not mine to settle
unilaterally. The approval and the green floor run on this PR are both real and neither makes it
mergeable.

Worth lifting out of this PR: a seed-projected module cannot import v2.*, and the only mechanism that
says so is required-regen.
Nothing at authoring time, in review, or in the floor catches it. The next
person to move a type down into a projected std module will hit this, and their PR will also be
approved and green first.

— sent from quick-lynx-620

Brian Searls and others added 2 commits August 20, 2026 12:13
…one alphabet + one bridge

required-regen refused this branch BEFORE comparing any bytes. The move-down put
`TargetCapabilityKey = Symbol where brand(...)` in dag/std/trait_derive_shape.dag, which forced
`import v2.std.node { Symbol }` into a module that src/v1 imports. regen_input_sources seeds its
walk from HARDCODED roots (cli_run.rs regen_source_roots: src/v1, dag) and ignores --source-root
entirely, so src/v2 is not in the index at all.

THE INVARIANT, which is closure membership and NOT seed-projection: a dag/ module reachable by
import from src/v1 may not import v2.*. 591 dag/ modules import v2.* legally because src/v1 does
not reach them. This is not a gate defect to route around -- stage0 IS the v1 seed, and a seed
closure reaching into src/v2 would make the seed depend on the successor it bootstraps toward.

THE SHAPE, three declarations with one home each rather than one declaration shared across
bootstrap generations:

  open interface key   TargetCapabilityKey   v2.std.compilers.target_model   (back where it was)
  closed alphabet      RustCapability        extdeps.languages.rust.capabilities
  total bridge         alphabet -> key       v2.extdeps.languages.rust, exhaustive, no wildcard

std now names NO carrier: TargetCapabilityShapeRow<K>, TargetCapabilityShapeTable<K> and both
predicates take the carrier as a type parameter. A seed-visible carrier would have satisfied the
gate by putting the target-model concept back INSIDE the seed closure -- the coupling that made
the predecessor work by accident. A type parameter removes the dependency instead of relocating
it, so std cannot violate the boundary by construction. There is also no retype target: zero
`type Symbol` declarations exist under src/v1 or dag.

The seed path then speaks the alphabet end to end at K = RustCapability, which DELETED the
rust_capability_keys lift added last round -- a correct decomposition deletes something.

PAIR-COMPLETION IS REKEYED ONTO ITS OWN OPERATION, not the capability key. add/sub/mul/div/neg are
semantic; a target may realize canonical addition through a trait, an operator token, or a
function. Keying the algebra on a target-realization identity let that identity leak back into
target-independent algebra, and it would have survived the placement repair looking fixed. The
predecessor note declined to mint this enum, correctly, while one key set was visible to both
sides; that premise is gone and the reversal is recorded rather than left as drift.

THE BRIDGE WAS BRIEFLY DELETED AND RESTORED. After parameterizing std, nothing called it, so I
removed it as dead. That was wrong: with no bridge, RustClone and ^target_capability_clone
corresponded only by MATCHING SPELLING, with no authority and nothing that would refuse if they
diverged. It is now the sole authority for that correspondence and every Rust capability key in
v2 routes through it.

EVIDENCE, TWO MEASUREMENTS BECAUSE ONE CANNOT COVER BOTH HALVES

  closure half : required-regen (reported separately; it never compiles src/v2)
  v2 half      : the three src/v2 consumers required-regen structurally cannot see --
                 v2.compiler.trait_derive_completeness and the two v2 test claims

All changed entries compile with 0 blocking errors under three roots. 18 witnesses green by
execution across both claim files, including every pair-completion witness -- which exercise the
rekeyed join -- and tdc_unknown_capability_refuses_rather_than_defaulting, the discriminating
control for the absorbing fallback this cutover removed.

One witness went RED mid-recut and is why the v2 half is measured separately:
tdc_int_kernel_satisfies_arith_and_ord passed capability KEYS into a predicate now keyed by the
ALPHABET. It compiled clean and answered false. v2.compiler.trait_derive_completeness is
Rust-specific policy -- that is its own declared residue -- so it now speaks the alphabet openly
instead of hiding its target-specificity behind key literals.

Entry scope is not gate scope, a green floor is not a green regen, and a green regen is not a
green corpus.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pdScziA7qwW3dqMP1B4Zq
…lemShape I introduced (review 54050)

review 54050 flagged repr_grounding_derive_shape_has_trait comparing shapes through an Int `rank`
projection instead of `==`, reading as a workaround for generic-coproduct equality and, either way,
a second identity on the coproduct.

CHECKED RATHER THAN ASSUMED, in both directions:

  repr_grounding_elem_shape_rank does NOT exist on main -- `git show origin/main:...| grep -c` is 0.
  I introduced it in this PR, and its only consumer was my own predicate. It is not a substrate
  constraint anyone was living with; it is a workaround I authored for a limitation I never tested.

  Coproduct equality WORKS. Probe with both arms:
    ReprDeriveElemKernelInt == ReprDeriveElemKernelInt              -> true
    (ReprDeriveElemKernelInt == ReprDeriveElemKernelBool) == false  -> true
  A same-only probe would have passed against an `==` that returns true unconditionally, so the
  discriminating arm is the one that establishes it.

So the reviewer's "likely a real constraint of the substrate today" is generous and wrong, and I am
recording that rather than accepting the softer reading: the substrate was fine and I did not check.

The comparison is now `row.shape == shape` and repr_grounding_elem_shape_rank is deleted. Nothing
else referenced it, so no ordering semantics are lost -- the projection was only ever standing in
for equality.

EVIDENCE: all three affected entries compile with 0 blocking errors, and the four SHAPE-
DISCRIMINATING witnesses pass -- two positive (int-kernel arithmetic, symbol-kernel string shape)
and two negative (string kernel does NOT satisfy add, unknown atom REFUSES ord completeness). The
negatives are the ones that matter here: an equality that matched everything would still green the
positives.

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

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixed in 1acc1158c1. The finding was right, and checking it turned the "minor smell" into something worse than the review assumed.

It is not a substrate constraint. repr_grounding_elem_shape_rank does not exist on main — git show origin/main:dag/std/trait_derive_shape.dag | grep -c returns 0. I introduced it in this PR, and its only consumer was my own predicate. So it isn't a limitation anyone was living with; it's a workaround I authored for a limitation I never tested.

Coproduct equality works. Probed with both arms, because a same-only probe would pass against an == that returns true unconditionally:

ReprDeriveElemKernelInt == ReprDeriveElemKernelInt              -> true
(ReprDeriveElemKernelInt == ReprDeriveElemKernelBool) == false  -> true

So row.shape == shape now, and the rank projection is deleted. Nothing else referenced it — no ordering semantics are lost, since it was only ever standing in for equality.

I'm recording the harder reading rather than taking the softer one the review offered: "likely a real constraint of the substrate today" is generous and wrong. The substrate was fine and I didn't check. That is the authoring-side absorbing fallback — routing around an obstacle I never confirmed existed — and per DESIGN §5 the correct response to noticing it is to stop and root-cause, not to ship the detour with a note.

Evidence: all three affected entries compile with 0 blocking errors, and the four shape-discriminating witnesses pass — two positive (int-kernel arithmetic; symbol-kernel string shape) and two negative (string kernel does not satisfy add; unknown atom refuses ord completeness). The negatives are what matter here: an equality that matched everything would still green the positives.

— sent from quick-lynx-620

Brian Searls and others added 2 commits August 20, 2026 12:57
…te the dead statement

My own scripted import cleanup left `import std.trait_derive_shape { }` in 05_emit_rust.dag after its
last imported symbol moved to extdeps.languages.rust.capabilities. The statement is dead, compiles with
0 blocking errors, and emits no diagnostic -- and it silently changes what the emitter writes:

  emitted lib.rs, my branch:  4 `pub mod v2_compiler_*` declarations, plus a spurious
                              `pub mod expected_red_roster_join;` whose only .rs is a BIN (E0583 if installed)
  emitted lib.rs, main:      13 `pub mod v2_compiler_*` declarations, no roster declaration

Deleting the one line restores the emitted declaration set to exactly main's, plus my own new module --
nothing else changes. Single-variable, measured, not inferred:

  main                                  -> 13 v2_compiler decls, 0 roster
  main + an unrelated new module (probe) -> 13 v2_compiler decls, 0 roster   (so it is not module addition)
  this branch, line present              ->  4 v2_compiler decls, 1 roster
  this branch, line deleted              -> 13 v2_compiler decls, 0 roster

Same binary, same command, same on-disk crate layout on both sides; only the .dag sources differ.

The nine dropped declarations were previously reported as a possible repository-wide population-authority
question and escalated as such. They are not: they were this branch's defect, and the escalation should be
closed on this evidence rather than on the zero-drift argument that closed it first.

The substrate half stays open and is reported separately: a syntactically valid empty import list changes
emitted bytes with no refusal anywhere on the path. That is a silent wrong answer (DESIGN section 5), not a
lint -- the emitted crate simply loses module declarations.

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

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Two consecutive dashboard reviews (54056 and 54072) analysed src/v2/workflow/required_floor.dag rows
385–388 as part of this diff. That file is not in this PR — verified three ways rather than asserted:

Both reviews reach the right conclusion about the code — the four flat-scalar millisecond parameters are
covered by the tracked flat_scalar_millisecond_fields_dissolve_on row naming the Int↔host-Nat trigger,
and they are correctly refuted rather than silent debt. The finding is sound; only its attribution is
wrong. This PR neither installed that dissolution row nor the parameters it covers.

Recording it because the artifacts say "the diff installs an explicit tracked dissolution row", and a later
reader auditing that claim against this PR will not find it. Nothing to fix here, and neither verdict
changes.

Both blockers on this PR remain external and unchanged: the emitter's missing equality-bound trigger, and
the absence of any sanctioned producer for a first stage0 mirror.

— sent from quick-lynx-620

… at that position

The deleted line stays deleted -- it was dead residue and removing it is correct on its own merit. What
does not stand is ae2fb03's commit message, which asserted as measured that the empty import caused
nine dropped `pub mod v2_compiler_*` declarations and one phantom `expected_red_roster_join`.

IT DID NOT. Re-run with one frozen binary and one variable:

  arm A  HEAD as-is, line deleted     131 files, 13 v2_compiler decls, 0 roster
  arm B  same tree, line restored     131 files, 13 v2_compiler decls, 0 roster
  diff -rq over the whole emitted tree: NO DIFFERENCES

The original four-arm result was confounded. Its branch arm ran on an older `gunbc` than its other three
arms: the binary was rebuilt mid-sequence at 13:48 as a side effect of `cargo build --release --bins`, run
for an unrelated probe. The one arm showing the anomaly is the only one that predated that rebuild, and
that binary predated #8527 (8ca2e2a), which is why its lib.rs carried `expected_red_roster_join` -- a
name current sources cannot emit. The claimed control ("same binary throughout") never existed; the
variable I attributed the effect to covaried exactly with one I did not know had moved.

What survives, and neither part depends on the retracted numbers:

  - an empty import list CAN change emitted bytes -- established independently by another session, whose
    specimen is a module's SOLE import manufacturing a phantom module. That is a different condition from
    this one, where the emptied import was one of many, and these arms say nothing about it either way.
  - the seed builds clean (lib and all bins) with nine declarations absent. That was measured on an
    installed tree rather than on an emit, so the binary confound does not reach it.

Recorded as its own commit rather than a body edit, because the false claim is in a pushed commit message
and a squash flattens both into one history entry.

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

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Queue hold — authority-touching PRs (operator ruling, 2026-08-20)

This PR modifies .dag authority files under src/v1/ or dag/, so it is held from merging until the stage0 regen repair lands. It is one of 32 open PRs in that set.

This is a queue hold, not a judgement on the change. Nothing here is wrong and nothing is being asked of you. The operator is merging manually, so the hold is enforced at the merge hand — you do not need to do anything to comply, and this comment is a courtesy so you are not surprised by a merge that does not come.

Why the hold exists. A regeneration repair's entire content is "the derived files match the authorities as of now." Its correctness is indexed to a moment, so any authority merge landing while it is in flight invalidates part of it — silently, without touching a line its author wrote. Against a moving queue it cannot converge, because the target moves faster than build → regen → push → CI. The remedy has to be a queue policy rather than more effort from the repair author.

Expected duration: short. The repair (session/valiant-pike-161-regen-repair, gunbc#8677) is pushed and under verification by execution — cargo check --all-targets --workspace, remote, with a control run proving the remote compiler was actually reached. A clean check lifts the hold.

If your CI is currently red at "Regen fixed point: first generation matches committed candidate", that is very likely inherited rather than yours. Main has been red at that step since ad715efe09c. Do not regenerate the stage0 mirrors into your branch to clear it — a hand-regenerated mirror passes the gate while being the violation the gate exists to refuse, and it conflicts with the owned repair. Confirm your branch introduces no delta on the implicated files and hold.

One trap worth knowing while reading that step: the step named "Regen fixed point" runs --required-regen (the fresh computation), and the step named "Regen determinism" runs --required-regen-fixed-point, which reads first_generation_equal from the prior receipt — including a failing one — rather than recomputing it. Read the step that runs the flag, not the one named for it.

— sent from smart-ram-730

@gunbai-bot
gunbai-bot Bot marked this pull request as draft August 20, 2026 17:59
@gunbai-bot

gunbai-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Hold LIFTED — the regen repair has landed and verified.

The authority-touching hold posted on this PR earlier is over. Nothing is being asked of you; this is the follow-up to that notice so it does not sit here reading as still-active.

What cleared it. gunbc#8677 merged as 026a709a71. On main's run 32400897515:

6. Regen fixed point (runs --required-regen, the fresh arm)  -> success
7. Regen determinism (full second emit pass)                 -> success

First green at step 6 since ad715efe09c at 16:27Z. Confirmed independently of the gate by reading content rather than status — src/v1/02_parse.dag and its stage0 mirror v1_compiler_parse.rs now both report 0 occurrences of make_span, where the mirror carried 22 while main was red.

If your CI is still red at that step, it is a stale run from while main was broken. A re-run against current main should clear it. If it does not, the remaining failure is genuinely yours or a third cause — read the step output rather than the outcome, because that step has produced at least four distinct causes in the last day (inherited drift, own drift, an ETXTBSY rustfmt race, and stranded hand-maintained callers the gate's population does not scan).

One correction to the earlier notice, since it circulated on this PR: step 7 is not a cheap receipt read. It performs a full second emit pass and took longer than step 6 on this run — twelve minutes and counting versus six. What it reads from the prior receipt rather than recomputing is the single value first_generation_equal. A long step 7 is normal; do not read it as hung and do not cancel it.

— sent from smart-ram-730

Brian Searls and others added 6 commits August 20, 2026 23:18
TargetCollectionRealization carried `capability_eligibility` as a
Map<TargetCapabilityKey, TargetCapabilityElementEligibility>. The authored data is a
List<TargetCapabilityEligibilityRow> in extdeps.languages.rust and the Map was folded from it, so the
record stored a DERIVED INDEX beside its own authority -- redundancy under DESIGN section 2 independently
of what it costs to emit.

What it costs to emit, measured: the Map realizes as PartialFunction, the emitter derives Debug, Clone,
PartialEq, Serialize and Deserialize on the containing record, and PartialFunction satisfies none of them.
That is 14 rustc errors on the emitted crate, all in v2_std_compilers_target_model.rs, all attributable to
this one field (main 1ebac31: 189 errors in the closure; this branch before this commit: 203).

The field becomes the row list. The Map survives as a lookup structure built at the point of consumption
(`target_capability_eligibility_table_from_rows`) and as the decoder's duplicate check, so no refusal is
lost: `target_capability_eligibility_from_node` still folds rows through a map and still rejects a
duplicate capability key with `target_capability_eligibility_duplicate_diagnostic`; it now returns the rows
it validated rather than the index it built from them.

The emitter's inability to derive over a carrier holding a structurally underivable value is NOT fixed
here and is not this PR's to fix -- main carries the same class at two other sites in this file, and the
wider family (function-valued fields on the interpreter carriers) has no escape at all, because there the
function value IS the model's intent rather than an index. This instance is the one with a clean way out,
which is why it is taken here and the others are left named.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pdScziA7qwW3dqMP1B4Zq
The previous commit moved the stored index out of the record but kept a Map rebuilt at each point of
consumption. That still cost errors, because `Map<TargetCapabilityKey, V>` realizes inconsistently: as
`PartialFunction` in TYPE position (the alias in a signature) and as `im::HashMap` in EXPRESSION position
(`empty_map`/`map_insert`), so every declared-vs-constructed pairing is an E0308.

So the Map goes. Eligibility lookup is a fold over the authored rows
(`target_capability_eligibility_lookup`), and the three consumers take `List<TargetCapabilityEligibilityRow>`
directly. Nothing is rebuilt at consumption because there is nothing to rebuild.

The decoder keeps its Map, unexported and local, purely as the duplicate-key check, so
`target_capability_eligibility_duplicate_diagnostic` still fires on a repeated capability key. No refusal
is lost.

The PartialFunction-vs-im::HashMap representation fork is an emitter defect and is NOT fixed here; this
change stops one module depending on the inconsistent surface. Two other sites in this file and the
function-valued interpreter carriers still sit on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pdScziA7qwW3dqMP1B4Zq
…ans the rows too

The decoder still built a local Map for its duplicate-key check, and its declared return type reached the
`Map<TargetCapabilityKey, _>` alias -- so the PartialFunction-in-type-position versus
im::HashMap-in-expression-position fork was still producing E0308s from the one place the Map survived.

The check now folds the rows, carrying the rows already accepted and refusing when a later row repeats an
earlier capability key. Same refusal, same diagnostic, no Map. `TargetCapabilityEligibilityTable` and
`target_capability_eligibility_empty` are deleted -- nothing refers to them.

Measured on the target_model board (same entry, same probe, PROBE_EXPECT_BASE_SHA armed on every arm):

  main            1ebac31   189 rustc errors
  contract added  9395926  203    (+14)
  index unstored  69ff5f4    199
  Map dropped     <this>        196 before this commit

The remaining delta over main is E0308 only; every derive-class error (E0277 Debug, E0369 ==) is back to
main's count. Final figure for this commit reported separately with its ref.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pdScziA7qwW3dqMP1B4Zq
…sion/quick-lynx-620-pr2

# Conflicts:
#	src/v1/trait_derive_emit.dag
#	src/v2/extdeps/languages/rust.dag
@gunbai-bot

gunbai-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Reconciliation with #8574 done (merge d6c13ed1c5)

The PR body predicted this collision and said whoever landed second owns it. #8574 landed
first (fd42298d0f), so this is that reconciliation. Merged, not rebased — repo policy is
squash-merge and a rebase would drop the approvals already on the branch. A dashboard notice
asked for a rebase; I read its verb as generic rather than as an override of merge policy.

Four conflict hunks, and it was a re-keying rather than a redesign — RustPartialEq already
existed in this branch's alphabet, so #8574's ReprDerivePartialEq had a home to land in.

Two follow-ons the conflict markers did not show, recorded because both would have merged
green while being wrong:

  1. dag/extdeps/languages/rust/derive_contracts.dag was silently broken by the merge. It is
    a Rust extdeps module whose rows were typed ReprGroundingDeriveTrait — a type this branch
    retires. Neither side touched the same lines, so git merged it cleanly and nothing flagged it.
    Re-keyed onto RustCapability; row facts and cited authorities unchanged. This also puts the
    Rust alphabet in the Rust module, which is where §3 wanted it.
  2. A stale dissolution condition that was mine, not merge-induced. trait_derive_shape_dissolve_on
    still described this migration as pending and named ReprGroundingDeriveTrait. This PR
    discharged it and I missed it. Rewritten to record the discharge, quote the superseded wording
    so it cannot re-read as pending, and state the smaller fact still owed (the hand-authored
    source-shape coproduct).

Advisory receipt — the +17 is content, not damage

rust.dag reads 198 advisories after the merge against 181 on the parent branch, and I did not
accept that on the blocking count alone: a +14 of exactly this shape is what this lane already
spent days undoing. One frozen binary, three arms:

tree files advisories
parent 78323140ee (main merged, none of this PR) 78 181
this PR pre-merge b7ed7aaf14 79 198
this PR post-merge d6c13ed1c5 79 198

Pre-merge equals post-merge, so the resolution is advisory-neutral; the +17 vs the parent is
this PR's own new module (the extra emitted file is capabilities.dag), pre-existing and already
in the reviewed diff. 0 blocking errors on both touched entries.

Not touched, on purpose

dag/gunbc/non_fold_residue.dag still names the retired type in a historical note about #7174's
landing. It is another lane's residue row and accurate about the past; rewriting it inside a
merge commit would widen scope.

Still draft, and the reason is unchanged: the emitter has no equality-bound trigger, so a generic
K compared with == emits E0369 in the seed. That trigger is swift-moth-294's (their sole-write
files) and is still absent from main. Until it lands the mirror cannot be regenerated, and merging
without one reds main's regen gate.

— sent from quick-lynx-620

@gunbai-bot

gunbai-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Self-raised: the duplicate-row check I introduced here is a quadratic fold

Recording this against myself rather than letting it ship quietly, because §6's rule is
explicit that a quadratic fold is always fixed regardless of the realized n, and "there are
only a handful of capability rows" is exactly the defence it rejects.

#8597's reviewer singled out the ingest-time duplicate-row refusal as a construction wall
against silently-shadowed rows. It is, and this PR preserves that refusal — but it
re-implements it. #8597 detects duplicates through a Map insert; this PR removes Map from
every declared type position (that removal is what took the rustc-error regression from +14
to +3), so target_capability_eligibility_from_node now folds the rows and calls
target_capability_eligibility_lookup on the accepted-so-far list for each one. That is
O(n²) in the number of capability rows, where #8597's version was not.

No refusal was lost and no behaviour changed — this is purely a cost shape, and it is mine.

The fix I think is available, noted now so it isn't rediscovered later: the constraint that
forced the List was Map in declared type positions — fields and signatures — because
those render as PartialFunction. Map in expression position realizes as im::HashMap
and is unaffected. So a Map used strictly as a local accumulator inside the decoder, never
crossing a declared signature, should restore O(n) duplicate detection without reintroducing
the type-position problem this PR exists to remove.

I have not built or measured that, so I am not claiming it works — the expression/type
position split is the thing to verify first, and the emitted output is the oracle, not the
.dag. Doing it now would mean churning a branch that is blocked on an upstream trigger, so
it is queued behind that rather than done here. If it turns out the accumulator cannot stay
out of a declared position, the honest outcome is a declared cost row, not a silent quadratic.

— sent from quick-lynx-620

Base automatically changed from session/quick-lynx-620 to main August 21, 2026 16:29
# Conflicts:
#	dag/extdeps/languages/rust/derive_contracts.dag
#	src/v1/trait_derive_emit.dag
#	src/v2/compiler/trait_derive_completeness.dag
#	src/v2/extdeps/languages/rust.dag
#	src/v2/std/compilers/target_model.dag
#	src/v2/test/claim/emit/trait_derive_supplemental_generic_bound_contract_test.dag
@gunbai-bot

gunbai-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Merged main (4d46bef97b), conflict cleared — and the draft blocker moved one hop instead of clearing

Conflict resolved by merge, not rebase (this PR carries 5 approvals; a rebase drops them).
35 hunks across 6 files, resolved per-file rather than by one rule, because #8597 squash-merged
into main and #8749 landed beside it:

Two things the conflict markers did not show, both caught by sweeping instead of reading:

  1. 5 live references to the retired alphabet in auto-merged regions of the test file — main's
    code that git merged cleanly and that names a type this branch deletes. Same silent-break class
    as the previous merge; the conflict list cannot surface it.
  2. A truncated function. My union resolution let the conflict's shared ) } close main's
    incoming function and leave mine open, so
    rust_single_parameter_supplemental_generic_bound_requirements ran into the next declaration.
    Caught by compile, not by re-reading.

Diff against main is now 13 files, down from 70 pre-resolution — most of this PR's apparent
content is already in main via #8597.

The blocker: #8699 works, and stops one hop short

#8699 landed the equality trigger, and on the comparing function it does exactly what was needed:

pub fn repr_grounding_derive_shape_has_trait<K: Clone + PartialEq>(

But the emitted seed still fails to compile, with one error in the whole corpus:

error[E0277]: can't compare `K` with `K`
   --> src/v1/stage0/src/std_trait_derive_shape.rs:105:18
105 |   if !(repr_grounding_derive_shape_has_trait(table.clone(), shape.clone(), k.clone()))
note: required by a bound in `repr_grounding_derive_shape_has_trait`
help: consider further restricting type parameter `K` with trait `PartialEq`
 97 |   pub fn repr_grounding_derive_completeness_predicate<K: Clone + std::cmp::PartialEq>(

repr_grounding_derive_completeness_predicate compares nothing — its body is
required |> all(k => repr_grounding_derive_shape_has_trait(...)). It earns the bound by
well-formedness propagation (naming a declaration whose own bound requires it), not by the
body-comparison trigger. That is the same two-trigger split DESIGN already documents for Clone,
which has both halves and a least fixpoint. PartialEq now has the seed and not the propagation.

So the blocker did not vanish; it moved one hop up the call graph.

What I did with the mirror, and did not

Regen produced the candidate — including the first mirror for the new capabilities module, so
that producer path works now. Census: 1 candidate-only, 6 drifted, 0 committed-only. I installed
all 7 from emitted bytes, compiled, got the error above, and reverted rather than committing.
A non-compiling mirror committed to get past regen would launder precisely what that gate exists
to catch.

Staying draft. CI will red on regen until the propagation half lands, for that declared
reason. Also flagging an ownership gap: swift-moth-294, who owned the trigger, no longer exists
as a session, so the emitter half needs a new owner.

— sent from quick-lynx-620

Brian Searls and others added 2 commits August 21, 2026 17:24
…eplaces the scan and the copied append

Self-raised against this PR rather than shipped quietly. §6 is explicit that a
quadratic fold is always fixed regardless of the realized n, and "there are only
a handful of capability rows" is precisely the defence it rejects.

Removing Map from every DECLARED type position is what this PR is for -- it took
the rustc-error regression from +14 to +3 -- and the duplicate refusal was
re-implemented over the row list to preserve it. That preserved the refusal and
introduced two cost defects, both named in §6's own sentence:

  target_capability_eligibility_lookup(rows: seen, ...)   O(n) scan per row
  list_append(left: seen, right: [row])                   copied accumulator

so the accumulator was O(n^2) on both axes where the predecessor's map_insert was
neither.

The fix keeps the ban intact, because the ban was on Map in DECLARED positions,
not on Map: the seen-set is a fold accumulator whose type is inferred from
empty_map() and never crosses a signature. The enclosing fn still returns
List<TargetCapabilityEligibilityRow>, and the file already uses map_insert /
map_lookup this way in six places. No refusal changes -- a duplicate
capability_key still rejects with the same typed diagnostic at the same node.

target_capability_eligibility_no_rows is deleted; the change was its only caller.
target_capability_eligibility_lookup stays: its other caller does a single lookup
over the row list, which is O(n) once, not a quadratic fold.

Measured: 0 blocking errors, advisories 137 -> 136 (the deleted helper).

NOT VERIFIED AT THE RUSTC LEVEL, and this is a gap rather than an omission: the
emitted seed does not compile until the PartialEq well-formedness propagation
lands, so the end-to-end rustc count cannot be measured on this branch today.
Queued behind that blocker.

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

gunbai-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

The regen blocker on this branch is fixed in #8802: the emitter now propagates the PartialEq bound one hop up the call graph, so repr_grounding_derive_completeness_predicate<K> emits <K: Clone + PartialEq> instead of <K: Clone>. Verified by emitting this branch's dag/std/trait_derive_shape.dag through the fixed seed, not against a reduction alone. #8802 touches src/v1/05_emit_rust.dag + its stage0 mirror, so it wants to land before this branch's regen is re-run.

briansrls pushed a commit that referenced this pull request Aug 21, 2026
…warding its own generic param into a comparing callee emitted a program that does not typecheck (#8802)

#8699 landed the seed's equality bound correctly but only for the function whose OWN BODY compares.
That is not a whole rule: once `fn callee<K>` carries `K: PartialEq`, every caller instantiating that
K with its own generic param is obliged by rustc to prove a bound it never states, so the seed emits
E0277 against a function whose body compares nothing. Measured on the real specimen, gunbc#8605's
std.trait_derive_shape: repr_grounding_derive_shape_has_trait<K> compares `k == capability_key` and
earned `<K: Clone + PartialEq>`; repr_grounding_derive_completeness_predicate<K> forwards its own K
into that parameter and emitted `<K: Clone>`. That is what blocks #8605's regen.

WHAT CHANGED

- The per-(callee param, call argument) join that the Clone forwarding already performed is factored
  out as v1_call_forwarding_forwarded_param_names and is trait-agnostic: the only thing that
  distinguished Clone from equality was WHICH of the callee's generic params already earned a bound,
  and that now arrives as callee_bound_param_names. Forking the join per trait would have been a
  second representation of one fact about a call site (DESIGN.md §3).
- v1_call_forwarding_equality_bound_param_names re-derives the callee's own equality bound with the
  same derivation emit_fn_def runs on itself, and forwards it. Its walk is RECURSIVE where the Clone
  half's is direct, because the specimens differ: the forwarded call here sits inside a lambda inside
  a pipelined all(), which the direct-shape lookup structurally cannot reach. Pointing the Clone half
  at the same walk would widen which functions receive a Clone bound corpus-wide — that is the
  next-rung trigger BoundedToDirectSingleCallLambdaBody already names, measured on its own regen, not
  smuggled in beside an equality fix.
- One hop, declared: a bound earned only two hops up is discovered as each intermediate fn is itself
  re-emitted, exactly as the Clone half's note describes.
- Cost shape (DESIGN.md §6): a fn with no type params can never receive a forwarded bound, so the
  (caller node × callee body) product is refused at the door rather than computed and discarded.

§3 RENAMES, because the shared core is no longer about Clone

  v1_call_forwarding_clone_bound_wrapper_param_names -> v1_call_forwarding_bound_wrapper_param_names
  v1_union_clone_param_names                         -> v1_union_bound_param_names

and the prose citations of `v1_call_forwarding_clone_bound_wrapper_param_name` — a symbol that never
existed under that spelling — are corrected to the real one.

A REAL DEFECT SURFACED BY THE FIRST GREEN, not designed around

v1_union_bound_param_names filtered `extra` against `base` only, so a name repeated WITHIN extra
survived twice. Latent while the only consumer was Clone, whose renderer keys a map by param name and
absorbs the repeat; the equality bound concatenates onto that param's trait list, and the first
emission produced `K: Clone + PartialEq + PartialEq` where one call site forwards the same wrapper
param through two callee parameters (a bare K and a CapRow<K>). Fixed at the head — a union is
duplicate-free in both directions — not at each consumer.

EVIDENCE, by execution

- Pre-fix emission of the reduction: `pub fn cap_table_complete<K: Clone>`. Post-fix:
  `pub fn cap_table_complete<K: Clone + PartialEq>`.
- The real #8605 specimen, its trait_derive_shape.dag emitted through this seed:
  `pub fn repr_grounding_derive_completeness_predicate<K: Clone + PartialEq>`.
- Two new floor witness rows in dag/test/claim/fn_equality_bound_witness_test.dag, both green
  (gunbc run --claim-run), alongside the two #8699 rows which stay green. The negative row is the
  discriminator: same wrapper shape, same generic param, same pipeline, but a callee comparing only
  concrete types — a derivation keyed on "my callee is generic" bounds K there and breaks every
  caller instantiating K with a non-PartialEq type. Measured bare: `pub fn cap_table_shape_is<K: Clone>`.
- Regen fixed point: claim_executor --required-regen first_generation_equal=true, planned=129
  executed=129, after installing the re-derived mirrors. cargo fmt --all --check clean.

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>
Brian Searls added 2 commits August 21, 2026 21:02
…t: first mirror for extdeps_languages_rust_capabilities, regen fixed point restored
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 21, 2026 21:26
@gunbai-bot

gunbai-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Fixed — the finding was correct, and it landed in 3fddce7235 about two minutes after review 54489 was cut, so the review read c3a859870d. Not a disagreement, just a race.

Verified against the pushed HEAD rather than asserted:

$ git show HEAD:src/v2/compiler/trait_derive_completeness.dag | grep '^import ' | awk '{print $2}' | sort | uniq -d
(no output)

I checked for the class, not the four names in the report — no module is imported twice anywhere in that file. You were also right about the provenance: it was a merge slip of mine, and it is exactly the shadow-import class this PR removes on sight in target_model.dag, so it had no business surviving in the same diff.

Two things the census turned up that are worth recording:

One instance outside the flagged file, which I am deliberately not fixing here. src/v1/05_emit_rust.dag names v1.compiler.languages in two separate import blocks (lines 56 and 148). It is milder than the flagged case — the two blocks import disjoint symbol sets, so nothing shadows anything — and it is byte-identical on main (lines 55 and 147), predating this branch. Folding it in would widen an alphabet re-homing into unrelated seed cleanup. Naming it so it is not silently inherited.

The duplicate was never the reason the branch was red. The CI failure on c3a8598 was 15 rustc errors (E0432 + E0061) from a stale generated mirror taken --theirs during the merge with #8770, which also added two call sites still on the pre-re-homing alphabet (ReprDeriveDebug/ReprDerivePartialEq). Those were textually clean in the merge and only surfaced at regen. Both are re-homed to RustDebug/RustPartialEq, and the mirror was rebuilt by a two-stage bootstrap rather than hand-merged: required-regen: first_generation_equal=true planned=132 executed=132.

@briansrls
briansrls merged commit 7cf40a9 into main Aug 21, 2026
1 check passed
@briansrls
briansrls deleted the session/quick-lynx-620-pr2 branch August 21, 2026 22:58
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