diff --git a/docs/specs/0032-param-namespace-injectivity.md b/docs/specs/0032-param-namespace-injectivity.md index 02e00d7..33bb772 100644 --- a/docs/specs/0032-param-namespace-injectivity.md +++ b/docs/specs/0032-param-namespace-injectivity.md @@ -377,13 +377,19 @@ positional filter per invocation). 4. **Unchanged green** `interchangeable_fan_in_allowed` (~1531): two **param-less** `Pass1` into a `Join2` → still `compile().is_ok()`. Paramless nodes contribute no `param_space()` path, so no duplicate; the C9 interchangeability survives. -5. **New** `asymmetric_node_name_collision_compiles` — pins the intentional drop: - a param-bearing `Sma` (default name `"sma"`) and a **paramless** `Pass1` - `.named("sma")`, both on role `price`, fanning into one `Sub`. Equal node-name +5. **New** `asymmetric_node_name_collision_compiles` — pins the intentional drop. + The asymmetric collision must sit **inside a composite** for the old check to + reach it (`check_fan_in_distinguishability` only descends into composites; a + root-level fan-in is never checked), so it reuses test 2's nested shape: an inner + composite `asym` holding a param-bearing `Sma` (default name `"sma"`), a + **paramless** `Pass1` `.named("sma")`, and a `Sub`, with both leaves on role + `price` fanning into the `Sub`; `asym` sits under a source-bound `root` + (`Some(ScalarKind::F64)`), exactly like `sma_cross_under_root`. Equal node-name signatures + one param → **rejected today** (`IndistinguishableFanIn`); after the - change `param_space()` is a single entry `["sma.length"]` (injective), so - `compile_with_params(&[Scalar::I64(2)])` is `Ok`. RED-green flip is the *reverse* - of test 3 — what the old check rejected, the new one admits, by design. + change `param_space()` is the single entry `["asym.sma.length"]` (injective — the + paramless leg contributes no path), so `compile_with_params(&[Scalar::I64(2)])` + is `Ok`. The RED-green flip is the *reverse* of test 3 — what the old check + rejected, the new one admits, by design. 6. **The #59 test** `by_name_bootstrap_of_unnamed_cross_reports_duplicate_path`: `bp.with("sma_cross.sma.length", 2).bootstrap()` on an unnamed cross → `Err(BindError::Compile(CompileError::DuplicateParamPath("sma_cross.sma.length".into())))`,