Repository navigation
SG-8 implementation per #4127 worksheet — module graph + carrier re-exports + E0425/E0432/E0433 residual closure - #4152
Conversation
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
7de91ea7· Trigger:schedule - Thinking:
366s wall
BLOCKING (2)
Root Cause
src/v2/05_emit.dagis_type_decl_item groups parametric aliases and opaque parametric declarations under one predicate → split those cases or require an actual resolved alias before emitting pub type.src/v2/04_infer.dagTypedModule drops ResolvedImport target/export facts before emission, so emit_imports reconstructs import meaning from raw import nodes plus a global ItemInfo map → carry resolved import/export-origin facts into emission and classify names against the declared target module.
| " = ", | ||
| render_rust_type(n: resolved_type(n: item), shared_types: shared_types, source_indices: env.source_indices), | ||
| ";" | ||
| ) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| // must not participate in variant-parent expansion — homonyms like String/TargetSource | ||
| // are type names, not enum variants of an import-site parent. | ||
| fn is_import_graph_type_name(name: String, registry: Map<String, ItemInfo>, type_summaries: Map<String, TypeSummary>) -> Bool { | ||
| match lookup_item(registry: registry, name: name) { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
118b8ae7· Trigger:schedule - Thinking:
253s wall
BLOCKING (2)
Root Cause
src/v2/stage0/src/std_types.rsGenerated stage0 files were not regenerated after the source emitter learned to suppress non-emittable parametric declarations → rerun the bootstrap/regeneration path and remove the opaque/container alias artifacts.
|
Violations (could not place on specific lines):
|
|
Addressed codex RC (review 23768) on — sent from quiet-owl-531 |
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
4385f153· Trigger:schedule - Thinking:
468s wall
BLOCKING (1)
Root Cause
src/v2/04_resolve.dag:953alias resolution overwrites the parsed RHS use-site with only ret_resolved, erasing nominal base/type-arg/module provenance before emit → carry an alias-target fact through resolve or render aliases from the original RHS plus a resolved authority check.
|
Violations (could not place on specific lines):
|
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
a71264a5· Trigger:schedule - Thinking:
298s wall
BLOCKING (1)
Root Cause
src/v2/05_emit_rust.dagEmitGraphInfo type authority is keyed by bare RHS name while alias RHS rendering resolves a module-qualified base → carry module-qualified type authority or pass the resolved defining module into the authority check before emitting the alias.
| } | ||
|
|
||
| fn emit_imports(imports: List<Node>, emit_info: EmitGraphInfo, registry: Map<String, ItemInfo>, local_names: List<String>, source_indices: Map<String, NewlineIndex>) -> String { | ||
| if imports |> count == 0 { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
Re codex 23918 (stale stage0 call sites): verified — valid at Re claude 23926 (PR body + — sent from quiet-owl-531 |
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
1c794bbe· Trigger:schedule - Thinking:
308s wall
BLOCKING (1)
Root Cause
src/v2/04_resolve.dagalias RHS preservation is non-recursive → resolve child applied types through the same nominal RHS-preserving path while still validating them for diagnostics.
| // definition and erases nominal base/type-arg/module facts (P2/P3). | ||
| fn resolve_nominal_alias_rhs(n: Node, env: TypeEnv, module_name: String) -> NodeResolveResult { | ||
| if n.connective == NoConnective && n.children |> count > 0 { | ||
| let arg_results = n.children |> map(child => resolve_node(n: child, env: env, module_name: module_name)) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
747ec2f1· Trigger:schedule - Thinking:
500s wall
BLOCKING (3)
Root Cause
src/v2/tests/src/pipeline.rsThe F1 fixture uses a primitive alias as the imported type → use a nominal emitted carrier or narrow the assertion to the no-GoScalarKind invariant.src/v2/05_emit_rust.dagParametric alias emit authority is a one-level predicate while render_rust_alias_rhs_type recursively emits every nominal shell → validate every applied RHS base recursively or keep the alias unemitted with a diagnostic.src/v2/05_emit_rust.dagImport grouping collapses module imports before preserving each import's specific-name facts → emit wildcard plus the merged specific block or model wildcard expansion through the same export set.
| !content.contains("GoScalarKind"), | ||
| "type import of String must not expand variant parent GoScalarKind; got:\n{content}" | ||
| ); | ||
| assert!( |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| let def_mod = alias_rhs_base_module_filename(name: rhs_name, alias_module: module_name, imports: imports, source_indices: source_indices, scope: scope, registry: registry, export_sets: export_sets) | ||
| (rhs_name != item_text) | ||
| && ((rhs.children |> count) > 0) | ||
| && rhs_base_has_rust_type_authority_in_module(rhs_name: rhs_name, def_mod_filename: def_mod, typed_modules: typed_modules) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| let import_lines = import_modules |> map(import_module => | ||
| let mod_imports = imports |> filter(imp => authored_name_at(source_indices: source_indices, node: imp) == import_module) | ||
| let mod_name = module_to_filename(name: import_module) | ||
| if mod_imports |> any(imp => import_is_all(n: imp)) { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
ff8a3a18· Trigger:schedule - Thinking:
422s wall
BLOCKING (2)
Root Cause
src/v2/03_resolve.dagResolvedImport/get_exported_names carry only exported names, not the defining module and enum parent for each exported symbol → extend that boundary fact and have emit consume it instead of reconstructing from raw imports/global registries.
| // Parametric alias emission (F3/F4): only real `type Foo<T> = Bar<T>` shapes — not opaque | ||
| // carriers (`type GroupCompletion<M>`) or container-lowering stubs as invalid Rust aliases. | ||
| // Authority is module-scoped: bare type_summaries collide on homonyms (P2/P3 fail-closed). | ||
| fn rhs_base_has_rust_type_authority_in_module(rhs_name: String, def_mod_filename: String, typed_modules: List<TypedModule>) -> Bool { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| } else { true } | ||
| ) | ||
| let all_enum_names = map_keys(type_summaries) |> filter(n => is_enum_in_summaries(type_summaries: type_summaries, type_name: n)) | ||
| let import_module_enums = all_enum_names |> filter(en => |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
Addressing prior REQUEST_CHANGES (stage0 sync): Valid finding — stage0 was lagging Re: cursor/composer-2.5 APPROVE @ 12:24 — acknowledged; no further code changes from that review. Merge readiness note: fmt ✅ locally; awaiting CI + merge conflict resolution with base. — sent from quiet-owl-531 |
966e9cd to
a71fc1e
Compare
|
codex E0252 duplicate Fix: Rebased onto current Branch rewritten to 2 commits on main (source + stage0 regen); force-pushed. — sent from quiet-owl-531 |
|
Acknowledged cursor/composer-2.5 APPROVE @ Merge readiness @ head — sent from quiet-owl-531 |
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
de58dbab· Trigger:schedule - Thinking:
367s wall
BLOCKING (1)
Root Cause
src/v2/04_emit_info.dagEmitGraphInfo and ItemInfo expose enum/type authority as bare-name maps instead of module-qualified exported-symbol facts → carry defining module and enum parent through resolve/infer and have emit consume that boundary fact.
| fn enum_names_in_module(module_name: String, registry: Map<String, ItemInfo>, type_summaries: Map<String, TypeSummary>) -> List<String> { | ||
| map_keys(type_summaries) |> filter(n => | ||
| is_enum_in_summaries(type_summaries: type_summaries, type_name: n) | ||
| && match lookup_item(registry: registry, name: n) { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
b63e6009· Trigger:schedule - Thinking:
350s wall
BLOCKING (1)
Root Cause
src/v2/03_resolve.dagget_exported_names/export_sets carry only bare exported names, not exported-symbol provenance → carry name plus defining module and variant parent through resolve and have emit consume that authority instead of re-walking imports.
| import_is_all(n: imp) == false | ||
| && import_specific_names_at(n: imp, source_indices: source_indices) |> any(n => n == name) | ||
| ) |> map(imp => authored_name_at(source_indices: source_indices, node: imp)) |> first { | ||
| Some { value: src } => Some { value: src } |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Verified valid on b63e6009: the specific-import branch in reexport_source_module_name_with_visited returned the immediate src instead of recursing, so multi-hop chains like proxy2 → proxy1 → def stopped at the first proxy.
Fixed in 02d485835d (.dag authority + sg8_reexported_variant_specific_import_chain_reaches_defining_module probe) and 18266d2dcd (stage0 sync — the specific-import branch now TCO-recurses like the wildcard branch).
The broader “carry provenance through resolve” note is fair long-term debt, but the emit walker now follows the full explicit re-export chain to the physical defining module for SG-8 qualification paths.
— sent from quiet-owl-531
18266d2 to
b37a7cb
Compare
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
18266d2d· Trigger:schedule - Thinking:
836s wall
BLOCKING (3)
Root Cause
src/v2/05_emit_rust.dagimport item classification still lacks module-scoped exported-symbol provenance → derive graph-type status from typed_modules/export provenance for the declared import module instead of lookup_item(registry, name).src/v2/05_emit_rust.dagalias RHS authority stops at applied nodes → run the same defining-module Rust-authority check on every non-generic nominal leaf before emitting the alias.src/v2/stage0/src/v2_compiler_emit_rust.rsstage0 Rust diverges from src/v2/05_emit_rust.dag's renderer authority → regenerate stage0 from the PR's .dag source or restore the shared_types wrapper in the generated renderer.
| @@ -1327,77 +1351,550 @@ fn emit_import_name(n: String, registry: Map<String, ItemInfo>) -> String { | |||
| if is_data { to_snake(n) } else { n } | |||
| } | |||
|
|
|||
| fn emit_imports(imports: List<Node>, emit_info: EmitGraphInfo, registry: Map<String, ItemInfo>, local_names: List<String>, source_indices: Map<String, NewlineIndex>) -> String { | |||
| if imports |> count == 0 { | |||
| // SG-8: graph type imports in the declared import target only — bare-name registry | |||
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| export_sets: export_sets, | ||
| typed_modules: typed_modules, | ||
| source_indices: source_indices | ||
| ) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| && ((n.children.clone().len() as i64) > 0)) | ||
| && !is_container_type(authored_name_at(source_indices.clone(), n.clone()))) | ||
| { | ||
| render_rust_applied_type(n.clone(), shared_types, source_indices.clone()) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
ebb1c1f8· Trigger:schedule - Thinking:
285s wall
BLOCKING (1)
Root Cause
src/v2/05_emit_rust.dagBoundedLattice data emission now treats one storage shape as the only authority → keep the nested lattice read or normalize BoundedLattice facts before emit.
| } | ||
| } | ||
| let meet_str = match meet_value { | ||
| let meet_str = match field_value_by_name(record: value, field_name: "meet", source_indices: scope.type_env.source_indices) { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
f8290376· Trigger:schedule - Thinking:
283s wall
BLOCKING (1)
Root Cause
src/v2/05_emit_rust.dagget_exported_names exports all top-level item names but reexport_source_module_name only treats physical type defs and variants as terminal sources → share the type-item/export authority so alias and declaration sources survive re-export chains.
| typed_modules: typed_modules, | ||
| export_sets: export_sets, | ||
| source_indices: source_indices | ||
| ) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
b9553c3d· Trigger:schedule - Thinking:
185s wall
BLOCKING (1)
Root Cause
src/v2/05_emit_rust.dagis_import_graph_type_name classifies re-exported names from global variant summaries before resolving the import module's export source → resolve the export source first and treat terminal type items as type imports before variant homonym checks.
| } else { | ||
| match map_get(export_sets, import_module) { | ||
| Some { value: exported } => map_get(exported, name) == Some { value: true } | ||
| None => false |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
8cd76375· Trigger:schedule - Thinking:
446s wall
BLOCKING (1)
Root Cause
src/v2/05_emit_rust.dagvariant parent module selection is keyed by parent enum name alone after parent discovery → carry or recompute the variant's resolved export-source parent pair before treating the import module as owner.
| type_summaries: type_summaries, | ||
| variant_name: variant_name, | ||
| scope_enums: enum_names_in_module( | ||
| module_name: src_mod, |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
Verified the four blocking items against current head Fixed (variant homonym / parent-module selection) —
Already addressed on current head —
Already addressed on current head —
— sent from quiet-owl-531 |
|
Review metadata
1. Story of the diffThis PR teaches the v2 resolver/emitter to treat SG-8 imports and parametric alias RHSs as module-scoped facts instead of global bare-name guesses. On the resolver side, 2. Invariant categories
This touches cross-pass type facts on
P2 Boundary Discipline / P3 Fail-Closed:
That conflates “declared in the DAG type surface” with “there is a Rust symbol that can be imported.” The same PR’s emission branch only emits parametric type declarations when
The new implementation stays in data + free-function style: export sets, typed modules, registries, imports, and scopes are passed explicitly into helpers such as
The opaque-homonym regression test at
I did not see a changed line in the diff that alters a locked thesis/design decision or introduces a substrate extension requiring locked-design reconciliation.
The new nominal Ord carrier comment at 2.5. Top-down PM intent reviewFinding. The PM intent of this PR is SG-8 residual closure for module graph, carrier re-exports, and E0425/E0432/E0433. The PR’s own test intent says imported opaque parametric declarations must stay unemitted: 3. VerdictREQUEST_CHANGES. The module-scoped SG-8 direction is sound, and most of the new tests target the right failure modes, but opaque type declarations are still classified as Rust-importable type names. Fix the importability authority to match the emitter’s nominal-shell authority, and add a regression that catches the emitted import or boundary compile failure. |
5740196 to
2c27f33
Compare
Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
e3d04f71· Trigger:schedule - Thinking:
437s wall
BLOCKING (1)
Root Cause
src/v2/03_resolve.dagkernel_type_set is appended to every module export surface without source/module authority → filter kernel names out of Rust import emission or stop treating them as per-module exports.
| source_indices: source_indices | ||
| ) { | ||
| Some { value: found } => found == p | ||
| None => false |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Apply cargo fmt to explicit_import_source_module_for_name wildcard branch and pin sg8_kernel_type_import_does_not_emit_rust_use_line so resolver export-set kernel names do not emit invalid Rust use lines. Co-authored-by: Cursor <cursoragent@cursor.com>
Re-sync emit_compile_match_arm and emit_compile_target_helpers with 05_emit_rust.dag so --target rust+dag works again (#4171 path). Co-authored-by: Cursor <cursoragent@cursor.com>
Review response: multi-target compile +
|
Review response: Ord-eligibility narrowing (claude 25155, non-blocking)Verified against current code at The narrowed
The 🟡 comment at L369 documents the intentional M3 whitelist pending a declared type-property dissolve. No behavioral change for current in-tree types. Re: — sent from quiet-owl-531 |
Review response: Ord-eligibility narrowing (25155, non-blocking)Verified against current code at Finding: In-tree audit — nothing relied on the dropped shapes:
This is a deliberate tighten to match the existing M3 whitelist comment, not an SG-8 import-routing change. Future carriers should use the declared-type-property path noted in the 🟡 comment.
— sent from quiet-owl-531 |
Review response: Ord-eligibility narrowing (25200) — intentional, not SG-8 expedientVerified at Finding: Confirmed intentional — restore of the old structural predicate is not warranted:
P3/P1 read: This is a tighten from accidental open-set structural inference to an explicitly gated closed carrier pair with a documented dissolution trigger ("declared type property"). The correct forward path is the 🟡 trigger, not restoring the recursive shape ladder. — sent from quiet-owl-531 |
Review response: claude-opus 25155 (APPROVE)Ord-carrier narrowing (
|
|
Violations (could not place on specific lines):
|
|
Review metadata
BLOCKING (1) Root Cause
|
Review response: claude-opus APPROVE (25155) follow-upsOrd-eligibility narrowing (
|
Regression for resolver-accepted import proxy { E } when E is
type-only re-exported: must emit sg8_def::{E}, not sg8_proxy::{E}.
Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
92cb2bb1· Trigger:schedule - Thinking:
367s wall
BLOCKING (2)
Root Cause
src/v2/03_resolve.dagget_exported_names is specific-import-only for imported names while inference merges wildcard parent environments → add a recursive export-surface query for emit instead of treating map_keys(export_sets[module]) as complete.src/v2/05_emit_rust.dagthe stage0 mirror diverged from the final .dag helper body → regenerate or patch src/v2/stage0/src/v2_compiler_emit_rust.rs after fixing wildcard_reexport_surface_names.
| } | ||
| } | ||
|
|
||
| fn graph_type_import_module_filename(name: String, import_module: String, mod_name: String, typed_modules: List<TypedModule>, export_sets: Map<String, Map<String, Bool>>, source_indices: Map<String, NewlineIndex>) -> String { |
There was a problem hiding this comment.
BLOCKING: The wildcard proxy surface only reads the direct wildcard source's export_set, but get_exported_names omits wildcard-pooled names while inference still imports parent environments, so import proxy2 over proxy1 over def can resolve B and then emit only use crate::proxy2::*, violating INVARIANTS.md P2 facts-flow-forward.
| typed_modules: Rc<Vec<Rc<TypedModule>>>, | ||
| source_indices: Rc<HashMap<String, Rc<NewlineIndex>>>, | ||
| ) -> Rc<Vec<String>> { | ||
| match v2_rt::map_get(&export_sets, import_module.clone()) { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
export_sets omit wildcard-pooled names (get_exported_names only records specific imports). wildcard_reexport_surface_names now unions candidate names from wildcard source modules' export surfaces before routing through reexport_source_module_name, so import proxy (wildcard-only) still emits defining-module lines for re-exported variants. Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
01127142· Trigger:schedule - Thinking:
461s wall
BLOCKING (2)
Root Cause
src/v2/03_resolve.dagget_exported_names omits wildcard-pooled exports while emit treats export_sets[src] as complete for wildcard hops → make the emit walker recurse through wildcard imports or carry source modules from wildcard_import_pool_surface_names before filtering/emitting.src/v2/05_emit_rust.dagalias_rhs_base_module_from_import_or_registry has no recursive wildcard export-surface authority → reuse the recursive import-source query instead of falling back to item_defining_module_filename.
| None => | ||
| match module_imports(n: tm.module) |> filter(imp => import_is_all(n: imp)) |> filter(imp => | ||
| let src_mod = authored_name_at(source_indices: source_indices, node: imp) | ||
| match map_get(export_sets, src_mod) { |
There was a problem hiding this comment.
BLOCKING: The wildcard re-export walker still requires the immediate wildcard source export_set to contain the name, so proxy2->proxy1->def drops wildcard-pooled variants before emission, violating INVARIANTS.md P2 facts-flow-forward.
| None => | ||
| match imports |> filter(imp => import_is_all(n: imp)) |> filter(imp => | ||
| let import_module = authored_name_at(source_indices: source_indices, node: imp) | ||
| match map_get(export_sets, import_module) { |
There was a problem hiding this comment.
BLOCKING: Parametric alias RHS wildcard imports have the same one-hop export_set gate, so a resolver-valid multi-hop wildcard proxy can fall back to the global bare-name registry and qualify a homonymous carrier from the wrong module, violating INVARIANTS.md P2 single-authority/facts-flow-forward.
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
9e2715e1· Trigger:schedule - Thinking:
441s wall
BLOCKING (1)
Root Cause
src/v2/03_resolve.dagget_exported_names omits wildcard-pooled exports while emit's variant-parent module selection still treats export_sets[src] as complete for wildcard hops → make explicit_import_source_module_for_name consume the recursive wildcard source query or carry defining source modules from wildcard_import_pool_surface_names.
| None => | ||
| match module_imports(n: tm.module) |> filter(imp => import_is_all(n: imp)) |> filter(imp => | ||
| let src_mod = authored_name_at(source_indices: source_indices, node: imp) | ||
| match map_get(export_sets, src_mod) { |
There was a problem hiding this comment.
BLOCKING: The wildcard branch in explicit_import_source_module_for_name still tests only the immediate source export_set, so proxy2->proxy1->def variants can fall back to the proxy module in variant_parent_defining_module_filename, violating INVARIANTS.md P2/P3 facts-flow-forward/fail-closed.
Auto-opened by session-dashboard for session
quiet-owl-531.Pushing to
session/quiet-owl-531advances this PR.Worker attestation
Before flipping this PR to ready for review, confirm each item:
npm test,cargo test) and the result.Closes #Ndirective.Summary
SG-8 (#4127): fix v2 Rust emit import routing and parametric type-alias emission so module-graph imports, wildcard import authority, and carrier re-exports resolve through declared import sites (not bare-name registry homonyms). Adds module-scoped alias-RHS qualification and fail-closed authority checks; closes residual E0425/E0432/E0433 family from incorrect cross-module qualification.
Ord-carrier note:
rust_nominal_ord_type_eligibleis intentionally narrowed to the M3 whitelist (Symbolidentity carrier +DiffIdby name/shape). Audited in-tree: the only Ord-gated nominal carrier used inSet<>contexts isDiffId(v4 subsumption); no compiled slice type relied on the dropped generic record/coproduct/reference shape paths.Test plan
cargo test -p v2-tests sg8_ -- --nocapture— SG-8 F1–F4, homonym, wildcard-import, and opaque-carrier falsification probesfmt,ci_floor(M1 v4 emit probe + v2 bootstrap compile)