feat(aura-engine): param_space() injectivity as a compile invariant
A non-injective param_space() — two slots under one path-qualified name — is the by-name knob address space (C12/C19) being broken: no binding can select one slot without the other. This cycle makes it a structural compile error. One check, check_param_namespace_injective over the param_space() names, replaces the whole fan-in machinery (signature_of, leaf_has_param, check_fan_in_distinguishability, check_composite_fan_in) and runs from compile_with_params AND from both binders before resolve/resolve_axes — so the canonical by-name author sees the structural cure (CompileError::DuplicateParamPath, carrying the path and pointing at .named(...)) instead of the AmbiguousKnob symptom (#59). With an injective space guaranteed before resolve, no bound name can multi-match, so BindError::AmbiguousKnob is dead and removed (both resolver arms -> unreachable!). param_space() stays infallible; the invariant lives in the compile step (C23). The check is strictly the by-name-addressability property. The old fan-in predicate (equal signature AND >=1 param) also rejected two collisions that are NOT param_space duplicates — an asymmetric param/paramless collision and a role-vs-leg collision — which guarded render identity, dead since the renderer was retired in 0026. Both are intentionally dropped; a future node-/wiring-name check, if wanted, is decoupled from injectivity. The new invariant correctly rejected a pre-existing fixture (multi_output_composite's two unnamed Sma legs); cured in place with the .named(fast/slow) the invariant mandates. A new E2E test drives the cured cross through the by-name binder end-to-end (resolve + bootstrap + run to a populated exposure trace). C9/C12/C19/C23 ledger notes amended. Verified: cargo build/test --workspace green (0 failed), clippy --workspace --all-targets -D warnings clean. closes #59
This commit is contained in:
+42
-15
@@ -264,8 +264,15 @@ only — the **search-range is the run's, not the node's** (which subset / grid
|
||||
sweep covers is an experiment axis, #32/C20; the node declares the knob's existence
|
||||
and type, never its search interval). (2) Identity is **positional** (the slot,
|
||||
C23 "by index, not name"); the path-qualified `name` is a non-load-bearing debug
|
||||
symbol (like `FieldSpec.name`) — same-type siblings in one composite share a name,
|
||||
uniqueness is at the slot. A vector knob (`LinComb.weights`) expands to `N` flat
|
||||
symbol (like `FieldSpec.name`) — uniqueness is at the slot. **Refinement (cycle
|
||||
0032):** identity in the **compilat** stays positional (C23, unchanged), but the
|
||||
`param_space()` **name projection** — the authoring / by-name address space (the
|
||||
surface a sweep axis or single-run binding addresses) — must be **injective** for a
|
||||
blueprint to compile (a duplicated path is unaddressable; see C9). Two layers:
|
||||
positional wiring below (the compilat), an injective name address space above (the
|
||||
authoring boundary). So same-type siblings in one composite no longer silently
|
||||
share a name — they must be `.named(...)` apart, which is also what disambiguates a
|
||||
same-type fan-in (C9). A vector knob (`LinComb.weights`) expands to `N` flat
|
||||
`weights[i]` entries, `N` topology-fixed (C19). Permitted kinds are `i64`/`f64`/
|
||||
`bool` (a `timestamp` knob is a structural axis, C20, never a numeric sweep param).
|
||||
Binding a value to a slot landed in cycle 0016 (#31, see C19/C23); enumerating a
|
||||
@@ -364,19 +371,30 @@ non-load-bearing** (C23): they live at the blueprint boundary and in render but
|
||||
dropped at lowering — the compilat is wired by raw index. The full composite
|
||||
boundary signature (named inputs, multi-outputs) and the per-node param path are
|
||||
legible without changing the compilat.
|
||||
**Refinement (fan-in distinguishability, 2026-06-08; node-name keying, cycle 0031).**
|
||||
A fan-in node (>1 input) is well-formed only if its colliding sources — sources
|
||||
with equal recursive **node-name** signatures (each node contributes its name —
|
||||
the instance name, default = the lowercased type label — then its inputs'
|
||||
signatures in slot order, stopping at a named source) — do not hide an unnamed
|
||||
configuration axis: a collision is a `CompileError` (`IndistinguishableFanIn`)
|
||||
when at least one colliding source carries a **param** (a param-bearing same-name
|
||||
collision), resolved by giving the colliding nodes distinct names. Paramless
|
||||
interchangeable same-name sources (equal signatures, no param) stay legal.
|
||||
Construction-phase only; the compilat stays name-free (C23), and `signature_of`
|
||||
has no render consumer (the ascii-dag fan-in render was retired with the renderer
|
||||
in cycle 0026). Surfacing the disambiguating node identity in the WASM graph view
|
||||
is tracked follow-up, not yet realized.
|
||||
**Refinement (param-namespace injectivity, cycle 0032; supersedes the fan-in
|
||||
distinguishability check).** A blueprint compiles only if its `param_space()` name
|
||||
projection is **injective** — every path-qualified knob name is unique. A duplicated
|
||||
path is a knob no binding can select alone (it has no distinct by-name address,
|
||||
C12/C19), so it is a `CompileError::DuplicateParamPath` carrying the offending path;
|
||||
the cure is to give the colliding same-type sibling nodes distinct names with
|
||||
`.named(...)`, the same single act that qualifies their param paths. The
|
||||
param-bearing indistinguishable fan-in is *one instance* of a duplicated path (two
|
||||
default-named same-type legs share a leaf path). `signature_of`, `leaf_has_param`,
|
||||
and the fan-in-specific check (`check_fan_in_distinguishability` /
|
||||
`check_composite_fan_in`) are **retired** (cycle 0032), replaced by one structural
|
||||
`check_param_namespace_injective` over the `param_space()` names, run before name
|
||||
resolution in `compile_with_params` and both binders — so the canonical by-name
|
||||
author sees the structural cure rather than a downstream `AmbiguousKnob` symptom
|
||||
(that `BindError` arm is retired too: an injective space can never multi-match).
|
||||
Paramless interchangeable same-name sources stay legal (no path, no duplicate).
|
||||
The old signature-collision predicate had extra breadth — it also rejected an
|
||||
**asymmetric param/paramless** collision and a **role-vs-leg** collision, *neither*
|
||||
a path duplicate (each colliding configuration keeps a unique param path, or none).
|
||||
That breadth guarded **render identity**, dead since the renderer was retired in
|
||||
0026; both extra rejections are **intentionally dropped**. A future node-/wiring-name
|
||||
distinguishability check, if ever wanted (e.g. for the WASM graph view's #21
|
||||
thread), is decoupled from param-space injectivity. Construction-phase only; the
|
||||
compilat stays name-free (C23).
|
||||
|
||||
### C10 — Strategy output is an intent/exposure stream; position management is a decoupled derived layer; brokers are downstream nodes
|
||||
**Guarantee.** A strategy's primary, backtestable output is **not** an equity
|
||||
@@ -861,6 +879,15 @@ hoisting) are **deferred**, behaviour-preserving follow-on work, each gated by a
|
||||
sweep-level pass further presupposes per-node param declarations (C8 — landed in
|
||||
cycle 0015 as `name`+`kind`; the swept *range* still pending #32/C20) and the sweep
|
||||
orchestration itself (C12/C21).
|
||||
**Amendment (cycle 0032 — param-namespace injectivity is part of the compilation).**
|
||||
The bootstrap-as-compilation now includes one structural gate:
|
||||
`check_param_namespace_injective` over the `param_space()` name projection (see C9 /
|
||||
C12-C19), run before name resolution. It reads the **boundary** name projection — the
|
||||
authoring address space — and rejects a duplicated path (`DuplicateParamPath`). Node
|
||||
names stay **non-load-bearing**: they qualify the param path at construction and are
|
||||
dropped at lowering (the compilat is wired by raw index, unchanged). The check makes
|
||||
the by-name *authoring address space* injective; it does **not** make names
|
||||
load-bearing in the compilat.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user