Repository navigation
Make Rust emitter deterministic: correct variant owner-selection + sort import keys - #5879
Merged
Merged
Conversation
variant_fold in build_module_context iterated env.bindings (HashMap<i64,TypeBinding>) via map_values(), producing non-deterministic ordering. When multiple coproducts share a variant, the first-processed wins; HashMap iteration order varies per process, so 37 files diffed between two runs of the same corpus. Sort bindings by binding.name before folding so the alphabetically-first type name always wins — stable across runs. Also removes debug eprintln! instrumentation added during diagnosis (v1_compiler_emit_rust.rs effective_variant_parent and v1_compiler_infer_emit_info.rs derive_variant_to_enum). Oracle: two corpus emits produce empty diff. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rt emit imports - v1_compiler_infer.rs: replace dead imported_enum_names (per-binding) with imported_variants (per-child) disambiguation. Looks up variant→parent enum in the SOURCE module (parent_index), not the current module's transitive env. ApiKey in std.cache_interface is unambiguously AuthScope (not AuthScheme which transits in from std.types). writeThenCommit is ambiguous (both AtomicityModel and CacheWriteSemantics define it) → falls back to alpha sort. Sort is kept for determinism on ambiguous cases. - v1_compiler_emit_rust.rs: sort map_keys outputs at 3 export_sets call sites (wildcard_reexport_surface_names, wildcard_import_pool_surface_names, reexport_parents) to make pub use ordering deterministic. Residue (noted): WriteThenCommit owner-selection is correct-by-alpha-accident (AtomicityModel < CacheWriteSemantics alphabetically, which is the intended field type). This is a latent §3 collision; the imported_variants map returns None for it (ambiguous in source module) and alpha tie-break happens to win.
1. find_struct_name_by_fields (emit_rust.rs): candidates Vec came from HashMap iteration (map_values), making .first() non-deterministic when multiple record types share the same field names. Sort candidates by name before selecting first. This fixes emit alternating between AnthropicModelSpec vs OpenAiModelSpec (same field names, different model-field types) and PosixSubject vs FileOwnership (identical field sets uid/gid). 2. interp_string_family_cast_test (test update): #5818 removed the hollow Url=String alias from std.types (§3 violation). Update the Rust unit test to match string_family_cast_witness_test.dag — remove the Url import and string_to_url fn; Uri is a struct and does not participate in the string-cast family. Oracle after all fixes: double-emit of 609 dsl modules → EMPTY diff. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iple struct candidates share the same field names AnthropicModelSpec and OpenAiModelSpec have identical field names but different field types (model: AnthropicModel vs model: OpenAiModel). The previous sort-by-name fix was deterministic but wrong — it always picked AnthropicModelSpec for both modules, emitting invalid Rust (wrong constructor name). Now passes field_type_hints (field_name → inferred value type) from the call site; the function filters candidates by type compatibility before falling back to alpha sort. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
briansrls
added a commit
that referenced
this pull request
Jun 27, 2026
…v1-survivable) (#5880) Author the operator-funded follow-on to the acute floor-OOM fix (#5867 shared InternTable: whole-tree resolve 14.2 -> 5.5 GiB, 61% reclaimed). This lane is REPRESENTATION minimization at the root (distinct from the ALLOCATION lane, resource-aware-scheduler.md), repointing the orphaned tidy-wren-707 lane. Operator steer: minimal representation, attack root causes as much as we can, v1 is going away. Every item is gated on one discriminator -- does the fix live in the .dag authority (survives the v2 self-host) or is it a hand-edit to the doomed v1 Rust seed (dies with v1). Root-first sequencing: 1. Emitter determinism is THE GATE for Lever B -- points at PR #5879 (stern-fox-585) as the single authority for the gate's status/mechanism; this plan only sequences on it. 2. Lever C (stream/evict) -- biggest mover, no emitter dependency; one batching-and-eviction model shared with the prune-resolve/affected_set lane. 3. Lever B (variant-Node minimal representation) -- after the gate, modeled in std/ and emitted (so it regenerates, not hand-edited into the seed). 4. func_env.sigs single-authority -- resolve each sig once, share the Rc; hundreds-of-MiB (payloads already Rc-shared, established by code read). 5. Cheap v1-seed hygiene -- bank-if-cheap, NOT a pillar (dies with v1); Node.ident is NOT dead (execution-refuted a 0%-used reflection claim). Plumbing: registered in plan_registry_batch_g (code-graph inbound link); ROADMAP link line (doc-graph inbound link, one short line); docs/plans md is a generated projection (regenerated via main_wet, drift-gated). Gates verified green by execution: no-orphan-docs, no-dangling-links, plan dissolution+title. Co-authored-by: Brian Searls <briansrls@gunb.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Corroborating baseline non-determinism witness from #5884 lane. While verifying a separate PR (#5884, func_env.sigs dedup), I ran the unmodified origin/main
This is an independent repro from a different binary build, different PR context, same 50-file non-deterministic universe. The variant families match yours exactly. May help as additional evidence for reviewers. |
briansrls
added a commit
that referenced
this pull request
Jun 28, 2026
Tests 2+3 previously imported both AEarlyEnum and ZLaterEnum from a separate owner module. Since they share a variant, the pre-existing imported-both guard (curr_is_imported && prev_is_imported) fires a VariantCollision diagnostic, which would block the regen path. Fix: collapse each two-module scenario into a single module with locally- defined enums and no imports. imported_enum_names is empty → no collision possible. Discrimination is preserved via variant_locals_from_items last-write-wins: ZLaterEnum defined first, AEarlyEnum second → AEarlyEnum wins scope; the function return type (ZLaterEnum) triggers the expected- type override on the site that needs it. Verified: removing expected_type_override_enum → tests 2+3 go red (AEarlyEnum::SharedVarField emitted for make_late); restoring → green. Test 1 (local-vs-transitive, #5879 fix) unaffected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
briansrls
added a commit
that referenced
this pull request
Jun 28, 2026
…uthority) (#5891) * WIP: Variant-owner-by-expected-type §3 grounding (KEYSTONE, correctness wall) * WIP: Variant-owner-by-expected-type §3 grounding (KEYSTONE, correctness wall) * Ground variant owner by expected type at each site: fix + tests (#5891 prep) - v1_compiler_infer.rs (ExprVar): when the scope binds a shared variant to enum A (by import-order or alpha-sort), but the declared expected type at the call site is a different enum B that also contains the variant, prefer B. Both binding_kind and inferred resolved-type are updated so the emit path sees a consistent owner without needing its own fallback. - variant_owner_disambiguation_test.rs: two new §2 witnesses: · shared_variant_resolves_by_expected_type_not_alpha_order — AEarlyEnum and ZLaterEnum both define SharedVarAmbig; fn return type ZLaterEnum must emit ZLaterEnum::SharedVarAmbig not AEarlyEnum (alpha-first default). · shared_variant_resolves_per_site_independently — two functions in the same module with different declared return types each get the correct variant owner independently, proving the fix is per-site not per-module. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: Variant-owner-by-expected-type §3 grounding (KEYSTONE, correctness wall) * Add .dag discriminating witness: expected_type_picks_variant_owner_not_alpha_order Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: Variant-owner-by-expected-type §3 grounding (KEYSTONE, correctness wall) * Revert regen'd v1_compiler_infer.rs — restore hand-sync The regen activates pre-existing VariantCollision logic in 04_infer.dag (line 5815) that is a blocking diagnostic and breaks the corpus (e.g. std.cache_interface defines both AtomicityModel and CacheWriteSemantics sharing WriteThenCommit — regen fires VariantCollision there). This is a known X/Y modeling decision being escalated; the hand-sync is correct and faithful for the expected_type_override_enum grounding in this PR. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: Variant-owner-by-expected-type §3 grounding (KEYSTONE, correctness wall) * Redesign Rust disambiguation tests to avoid VariantCollision on import Tests 2+3 previously imported both AEarlyEnum and ZLaterEnum from a separate owner module. Since they share a variant, the pre-existing imported-both guard (curr_is_imported && prev_is_imported) fires a VariantCollision diagnostic, which would block the regen path. Fix: collapse each two-module scenario into a single module with locally- defined enums and no imports. imported_enum_names is empty → no collision possible. Discrimination is preserved via variant_locals_from_items last-write-wins: ZLaterEnum defined first, AEarlyEnum second → AEarlyEnum wins scope; the function return type (ZLaterEnum) triggers the expected- type override on the site that needs it. Verified: removing expected_type_override_enum → tests 2+3 go red (AEarlyEnum::SharedVarField emitted for make_late); restoring → green. Test 1 (local-vs-transitive, #5879 fix) unaffected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Brian Searls <briansrls@gunb.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
3 of 4 tasks
briansrls
pushed a commit
that referenced
this pull request
Jun 28, 2026
* Restore emit-path variant-owner determinism lost in #5873 regen (#5879 grounding) Port #5879's variant-owner disambiguation back into .dag authority and hand-sync the v1 Rust seed: imported_variants (source-module local items), alpha-sorted variant_fold bindings, sorted export-set map_keys, and field-type-hint struct disambiguation. Regen in #5873 overwrote these fixes; this re-lands them on current main (#5899) without disturbing expected_type_override_enum (#5891). Co-authored-by: Cursor <cursoragent@cursor.com> * Fix #5899 func_sigs merge regression in variant-owner restore Re-land imported_variants + sorted variant_fold on top of #5899's func_sigs/all_declared_sigs path (not the reverted imported_sigs fork). Restores compile-clean hand-sync; variant_owner_disambiguation 3/3 green. Co-authored-by: Cursor <cursoragent@cursor.com> * Ground emit-path variant-owner determinism in .dag authority with pure regen. Fix imported_variants map type so regen emits valid Rust (Map<String,String> for unambiguous owners only), then regen stage0 to match .dag fixed point. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls
added a commit
that referenced
this pull request
Jun 28, 2026
…x deepening. The arena/u32-index analysis is a deepening of Lever B (same lever: bytes-per-node; same gate: #5879), not a separate plan. Single-authority in representation_minimization.dag. - Lever B item in sequencing ol() updated to name two sub-moves: (i) minimal per-kind sum type [existing] + (ii) arena/index container [new], both gated on #5879, both landing in v2/.dag realization. - Added two-paired-axes note: Lever B attacks BYTES-PER-NODE; Lever C attacks MODULES-HELD-AT-ONCE; orthogonal, compose multiplicatively, neither blocks the other. - Added "Lever B (item 3) — arena/index deepening" section with: verified Node anatomy (size_of=144 B, Rc-wrapped 160 B/node, 12 pointer fields) reconciled with the existing 18-field/627k-nodes framing; arena/u32-index insight (ACYCLIC DAG = Rc pays for unused cycle-safety; u32 arena index = 4 B vs 16 B Rc ctrl, halves 12 ptr fields, ~40-55% node-graph cut); cheap recoverables (.dag-first, independent of emitter gate) with corrections: empty-Vec singleton win already landed (#5878), Node.ident is LIVE (u32-shrink fine, deletion refuted by execution). - Reverted incorrectly-created arena_node_representation.dag (§3 fork) and registry entry. - Regenerated docs/plans/representation-minimization.md via wet gate (drift-clean). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 tasks
briansrls
added a commit
that referenced
this pull request
Jun 28, 2026
The two-axes note incorrectly said both levers are gated on #5879; Lever C explicitly has NO dependency on the emitter gate (sequencing item 2 and the A3 disposition both state this). Corrected paragraph now precisely states: Lever B gated on #5879; Lever C deferred to v2 streaming-infer, no emitter gate. Regen'd representation-minimization.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
briansrls
added a commit
that referenced
this pull request
Jun 28, 2026
…rom .dag-path. The 'independent of emitter gate / .dag-first' framing was contradictory: kernel-slice sharing targets v1_compiler_infer.rs (doomed v1 seed, bank-if-cheap per plan's discriminator); ident u32-shrink and SourceSpan file-id are Node/SourceSpan representation changes whose .dag-authority path IS gated on #5879 like Lever B. Per-item gating now stated explicitly: - Empty-Vec singleton: already landed, remaining is bank-if-cheap v1-seed work - Ident u32-shrink / file-id side table: .dag path gated on #5879; v1-seed is bank-if-cheap - Kernel-slice sharing: v1-seed work, bank-if-cheap, NOT a root item - Node.name interning: item 5 bank-if-cheap, unchanged Decidability section corrected: sub-moves i/ii are RATCHETS gated on #5879; ident/file-id are representation changes gated on #5879 on the .dag path; kernel-slice is v1-seed bank-if-cheap (not WALL-NOW in .dag). Regen'd representation-minimization.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
briansrls
added a commit
that referenced
this pull request
Jun 29, 2026
…pening (#5923) * WIP: Write up arena/u32-index Node representation as a design note (plan proj * Enrich Lever B in representation_minimization.dag with arena/u32-index deepening. The arena/u32-index analysis is a deepening of Lever B (same lever: bytes-per-node; same gate: #5879), not a separate plan. Single-authority in representation_minimization.dag. - Lever B item in sequencing ol() updated to name two sub-moves: (i) minimal per-kind sum type [existing] + (ii) arena/index container [new], both gated on #5879, both landing in v2/.dag realization. - Added two-paired-axes note: Lever B attacks BYTES-PER-NODE; Lever C attacks MODULES-HELD-AT-ONCE; orthogonal, compose multiplicatively, neither blocks the other. - Added "Lever B (item 3) — arena/index deepening" section with: verified Node anatomy (size_of=144 B, Rc-wrapped 160 B/node, 12 pointer fields) reconciled with the existing 18-field/627k-nodes framing; arena/u32-index insight (ACYCLIC DAG = Rc pays for unused cycle-safety; u32 arena index = 4 B vs 16 B Rc ctrl, halves 12 ptr fields, ~40-55% node-graph cut); cheap recoverables (.dag-first, independent of emitter gate) with corrections: empty-Vec singleton win already landed (#5878), Node.ident is LIVE (u32-shrink fine, deletion refuted by execution). - Reverted incorrectly-created arena_node_representation.dag (§3 fork) and registry entry. - Regenerated docs/plans/representation-minimization.md via wet gate (drift-clean). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix gating contradiction: Lever C has no emitter-gate dependency. The two-axes note incorrectly said both levers are gated on #5879; Lever C explicitly has NO dependency on the emitter gate (sequencing item 2 and the A3 disposition both state this). Corrected paragraph now precisely states: Lever B gated on #5879; Lever C deferred to v2 streaming-infer, no emitter gate. Regen'd representation-minimization.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix stale kernel-binding citations: infer.rs -> v1_compiler_infer.rs, line 13854->13998. The live seed file is src/v1/stage0/src/v1_compiler_infer.rs, not infer.rs. seed_kernel_intern_table is at line 13998 (not 13854 as cited). kernel_bindings_base at 11695 and intern TABLE at 11641 are correct lines. Regen'd representation-minimization.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * CI retry (OOM on srv1-01 in batch 2, runner-level flake) * Rebucket cheap-recoverables: correct gating claim, separate v1-seed from .dag-path. The 'independent of emitter gate / .dag-first' framing was contradictory: kernel-slice sharing targets v1_compiler_infer.rs (doomed v1 seed, bank-if-cheap per plan's discriminator); ident u32-shrink and SourceSpan file-id are Node/SourceSpan representation changes whose .dag-authority path IS gated on #5879 like Lever B. Per-item gating now stated explicitly: - Empty-Vec singleton: already landed, remaining is bank-if-cheap v1-seed work - Ident u32-shrink / file-id side table: .dag path gated on #5879; v1-seed is bank-if-cheap - Kernel-slice sharing: v1-seed work, bank-if-cheap, NOT a root item - Node.name interning: item 5 bank-if-cheap, unchanged Decidability section corrected: sub-moves i/ii are RATCHETS gated on #5879; ident/file-id are representation changes gated on #5879 on the .dag path; kernel-slice is v1-seed bank-if-cheap (not WALL-NOW in .dag). Regen'd representation-minimization.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Brian Searls <briansrls@gunb.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This was referenced Jun 29, 2026
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Jun 29, 2026
Addresses claude-opus #33136 minor finding; scaffold eq/kind-bridge debt unchanged (Disposition + non-fold roster + #5879 hold per sketch checkpoint). Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls
added a commit
that referenced
this pull request
Jun 29, 2026
… (model sketch FIRST; arena/u32 container is (ii), separate; lands in v2/.dag not the Rust seed; escalate on load-bearing Node/build_type_env) (#5936) * WIP: Representation-min Lever B(i): minimal per-kind Node sum type in std/ (m * WIP: Representation-min Lever B(i): minimal per-kind Node sum type in std/ (m * WIP: Representation-min Lever B(i): minimal per-kind Node sum type in std/ (m * Fix node_minimal witness tests: parse-safe dag and green execution. Correct ResolvedGraph import in the Rust runner, fix dag syntax that blocked resolve, and tighten witnesses so Lever B(i) compiles and runs green. Co-authored-by: Cursor <cursoragent@cursor.com> * docs: add Lever B(i) per-kind Node sum type design sketch Design-first review artifact for stern-moth-225: v2 authority confirmation, variant split, NOT-edited boundary, witness checkpoint status, and explicit exclusion of sub-move (ii) arena/u32 container. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix CI: cargo fmt for node_minimal test runner. Co-authored-by: Cursor <cursoragent@cursor.com> * WIP: Representation-min Lever B(i): minimal per-kind Node sum type in std/ (m * Complete Lever B(i) superset field partition with coverage witness. Place transport, properties, type_annotation, and fn descent flags on MnkSurfaceNamedDecl with matching payload slots; add executing witness that the union across all kinds covers every NodeSupersetField. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix CI: parse-safe orphan-field witness (nested if, not multiline &&). Retriggers floor discovery on current HEAD; prior dashboard failure was at 83f843f before parse/fmt/partition fixes landed. Co-authored-by: Cursor <cursoragent@cursor.com> * Add executable three-bucket superset partition witness for Lever B(i). Split fields into placed (12 on kind arms), owed_placement (3 staged arms), and migrated_to_facts (3 to InferredFacts). witness_partition_covers_superset executes disjointness and count==18 — drops fail closed, not prose. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix CI: doc-graph inbound link and non-fold residue roster for Lever B(i). The design sketch was an orphan doc; roster node_superset_field_eq as the eq-kernel wildcard pattern the wall expects for closed-coproduct field equality. Co-authored-by: Cursor <cursoragent@cursor.com> * WIP: Representation-min Lever B(i): minimal per-kind Node sum type in std/ (m * Address review nits: derive superset field count, use unary negation. Replace literal 18 with count(node_superset_field_all) as single authority for NodeSupersetField arity; collapse if/else false/true to !any(...) per review feedback (claude-opus APPROVE #33119). Co-authored-by: Cursor <cursoragent@cursor.com> * Add roster-length guard per stern-moth-225 emit-integration follow-on. Rename node_superset_field_all → all_node_superset_fields; restore census node_superset_field_count=18 as independent authority; witness roster length and per-field partition coverage fold. Hold at sketch checkpoint. Co-authored-by: Cursor <cursoragent@cursor.com> * Cosmetic: return Bool witnesses directly, not if/else true/false. Addresses claude-opus #33136 minor finding; scaffold eq/kind-bridge debt unchanged (Disposition + non-fold roster + #5879 hold per sketch checkpoint). Co-authored-by: Cursor <cursoragent@cursor.com> * CI: retrigger floor after 36568ea failure fixes landed on branch. 36568ea failed discovery corpus (doc_graph orphan + non_fold roster). Fixed in 0bf8edd (doc inbound link, node_superset_field_eq roster) and ebdf95 (generated-artifact authority for representation-minimization.md). Local floor batches 1-2 green on HEAD. Co-authored-by: Cursor <cursoragent@cursor.com> * Resolve representation_minimization merge conflict: keep both sketch links. Merge #5935 func_env.sigs design-sketch link with PR #5936 Lever B(i) node-minimal sketch link in the .dag authority; regen generated md via main_wet. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Three non-determinism/correctness sources in the v1 Rust emitter, all fixed:
`variant_to_enum` HashMap owner-selection (`v1_compiler_infer.rs`): `variant_fold` iterated `env.bindings` (HashMap) without sorting, so the first-processed enum for a shared variant name varied per-process. Fixed by alpha-sorting `env.bindings` values before folding, and selecting the correct owner using `imported_variants` (built from the source module's locally-defined items, not the full transitive `type_env.bindings`).
`map_keys` on exported HashMap (`v1_compiler_emit_rust.rs`): three call-sites (`reexport_parents`, `wildcard_import_pool_surface_names`, `wildcard_reexport_surface_names`) iterated `HashMap::keys()` non-deterministically, producing non-deterministic USE statement ordering. Fixed by sorting each key-vec before iteration.
`find_struct_name_by_fields` record disambiguation (`v1_compiler_emit_rust.rs`): when multiple struct types share the same field names (e.g. `AnthropicModelSpec` and `OpenAiModelSpec` both have `model, model_id, context_window, max_output_tokens`), the previous sort-by-name fallback was deterministic but semantically wrong — it always picked `AnthropicModelSpec` for both modules, emitting `AnthropicModelSpec { model: OpenAiModel::Gpt4o }` = invalid Rust. Fixed by building `field_type_hints` (field_name → inferred value type) at the call site and filtering candidates by type compatibility before falling back to alpha sort.
Why the correct-owner fix matters (DESIGN §7)
The `ApiKey` variant appears in two coproducts:
`buildbuddy.dag` imports `ApiKey` from `std.cache_interface`. The correct owner is `AuthScope`.
With alphabetical sort alone, `AuthScheme` (h < o) would win — emitting `AuthScheme::ApiKey` where `AuthScope` is expected = compile error. So determinism alone would have frozen a real §3 bug (deterministic-but-wrong).
The fix: `imported_variants` uses only locally-defined items of the source module (via `parent_tm.items`) to build the variant→unique-owner map. This ensures `AuthScope` wins because `AuthScheme` is not locally defined in `std.cache_interface` — only `AuthScope` is.
Residual: PosixSubject / FileOwnership
`PosixSubject { uid: Int, gid: Int }` and `FileOwnership { uid: Int, gid: Int }` are distinct named Rust types with identical field names and identical field types — full-signature matching cannot distinguish them, so alpha sort applies. This is latent-safe (corpus builds green, no current construction site forces one into a field typed as the other), NOT structurally-impossible. If such a site appeared — e.g. a field typed `PosixSubject` constructed with a `FileOwnership` literal — it would be a live compile error, same trap as `AnthropicModelSpec/OpenAiModelSpec`. Precise §5 framing: wall-after-grounding, not never. The two types are a §3 nicknaming candidate for a future de-collision.
.dag authority follow-up (known, not fixed here)
This fix lives in the hand-maintained v1 Rust seed (`v1_compiler_infer.rs`, `v1_compiler_emit_rust.rs`) because `regen --verify` is deferred and the stage0 is hand-synced (see `regen_verify_gate.dag`). When regen un-defers (§7 fixed-point dissolution / v2 self-host), the same determinism + correct-owner-selection + field-type-disambiguation logic must be ported to the `.dag` emitter model or it will regress on regen. Tied to the §7 fixed-point trigger.
Oracle results (run locally on HEAD 12ff922)
Double-emit (609 dsl modules, 616 files): diff -r emit1 emit2 → EMPTY (deterministic)
Semantic correctness checks:
Discriminating witness
`variant_owner_disambiguation_test::shared_variant_resolves_to_locally_defined_owner_not_transitive` — verifies that `ZLocalOwner::SharedV` is emitted (not `ATransitiveOwner::SharedV`). Designed so the wrong owner sorts alphabetically first; confirmed RED on revert.