feat(aura-runner, aura-cli, aura-registry): one raw axis namespace end to end
Reconciles the sweep-axis namespace (closes #328): the raw form <node>.<param> (splice paths keep their interior path) is now the only user-facing axis name — printed by --list-axes, accepted by --axis on BOTH sweep routes, recorded in the synthetic run manifest, discovered by graph introspect --params (bound params included, default= lexicon), and validated by campaign documents. The wrapped <blueprint>.<...> form is retired with translation refusals on both seams. Mechanics: - classify_axis_intake (aura-runner axes.rs): explicit raw-first acceptance predicate shared by both --axis intake routes. Deliberately not built on raw_matches_wrapped, whose equality branch would have accepted the retired form silently (skeptic finding, minuted on #328). Wrapped hit -> translation refusal naming the raw candidate (shared prose builder wrapped_axis_refusal, one literal for both routes). - The synthetic route resolution moves to the raw frame the real route (bind_axes) already uses: override_paths/wrapped_bound_overrides_of match via raw_matches_wrapped (also removing a latent slicing panic), blueprint_sweep_family translates raw names onto wrapped SweepBinder slots, manifest.params records raw keys (name-only reshaping, zip is positional). The two sweep routes now share one convention. - reproduce translates recorded manifest names raw-or-wrapped onto the wrapped space: old registered artifacts stay replayable (C29), new raw manifests round-trip. - introspect --params gains the bound default= lines via the same render_value the --list-axes bound pass uses - the two discovery surfaces are byte-identical by construction. - AxisNotInParamSpace carries raw_candidate; ref_fault_prose renders the did-you-mean iff present. Sweep-terminal bind errors (MissingKnob/KindMismatch) render the raw knob name (render seam only; acceptance criterion: no user-facing surface prints a wrapped axis name). - Downstream consequence fixes: scaffold quickstart example spoke wrapped; ~90 wrapped test literals converted to raw across six test files (deliberately-bogus refusal names and the untouched internal walkforward wrapped route kept, with comments). Fork decisions and the skeptic correction are minuted on #328 (comments 2026-07-24/25). Grounding-check PASS first attempt (12 assumptions ratified). Review (opus): translation ambiguity cleared (uniform single prefix per blueprint), reproduce both-shapes traced; repair pass added the synthetic-route refusal e2e + the shared prose builder. Verified: cargo test --workspace green (99 targets, 0 failures), clippy -D warnings clean. refs #246, refs #319, refs #331
This commit is contained in:
@@ -12,6 +12,7 @@ use aura_engine::{
|
||||
BindOpError, BlueprintDoc, CompileError, Composite, GraphSession, LoadError, Op, OpError,
|
||||
Scalar, ScalarKind,
|
||||
};
|
||||
use aura_runner::runner::render_value;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::research_docs::resolve_id_prefix;
|
||||
@@ -864,7 +865,11 @@ fn resolve_blueprint_text(target: &str, env: &aura_runner::project::Env) -> Resu
|
||||
/// line per open param of the RAW composite (no harness wrap) — the
|
||||
/// campaign-axis namespace `validate_campaign_refs` checks axes against. The
|
||||
/// kind renders via `ScalarKind`'s `Debug` (`I64`/`F64`/`Bool`/`Timestamp`),
|
||||
/// the same form `introspect --node` already uses for param kinds.
|
||||
/// the same form `introspect --node` already uses for param kinds. Followed
|
||||
/// by one `{name}:{kind:?} default={value}` line per BOUND param (#328):
|
||||
/// `bound_param_space()`'s names are already RAW (#203), so no blueprint-name
|
||||
/// concatenation is needed — line-identical to the reconciled `--list-axes`
|
||||
/// bound pass (`list_blueprint_axes`, main.rs), same `render_value` lexicon.
|
||||
fn params_lines(target: &str, env: &aura_runner::project::Env) -> Result<String, String> {
|
||||
use std::fmt::Write as _;
|
||||
let text = resolve_blueprint_text(target, env)?;
|
||||
@@ -875,6 +880,9 @@ fn params_lines(target: &str, env: &aura_runner::project::Env) -> Result<String,
|
||||
for p in composite.param_space() {
|
||||
let _ = writeln!(out, "{}:{:?}", p.name, p.kind);
|
||||
}
|
||||
for b in composite.bound_param_space() {
|
||||
let _ = writeln!(out, "{}:{:?} default={}", b.name, b.kind, render_value(&b.value));
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
|
||||
+109
-47
@@ -45,7 +45,7 @@ use aura_measurement::information_coefficient;
|
||||
// `blueprint_axis_probe_reopened` is production code only from `verb_sugar.rs`
|
||||
// (a sibling module reaching it via `crate::blueprint_axis_probe_reopened`,
|
||||
// the crate-root re-export this `use` gives it), not from this module itself.
|
||||
use aura_runner::member::{blueprint_axis_probe, blueprint_axis_probe_reopened, run_signal_r, wrapped_bound_names, RunData};
|
||||
use aura_runner::member::{blueprint_axis_probe, blueprint_axis_probe_reopened, run_signal_r, RunData};
|
||||
use aura_runner::{TapPlan, TapSubscription};
|
||||
#[cfg(test)]
|
||||
use aura_runner::member::{run_blueprint_member, wrap_r, SYNTHETIC_PIP_SIZE};
|
||||
@@ -813,17 +813,67 @@ impl IcReport {
|
||||
/// fully closed) — not only the fully-closed case: `--axis`/`override_paths`
|
||||
/// accepts a bound name regardless of how many knobs happen to be open
|
||||
/// alongside it, so the discovery surface must list it regardless too. Names
|
||||
/// are exactly what `--axis` binds.
|
||||
/// are RAW `<node>.<param>` paths (#328: the wrapped `<blueprint>.<node>.<param>`
|
||||
/// form is retired from the surface) — exactly what `--axis` binds.
|
||||
fn list_blueprint_axes(doc: &str, env: &aura_runner::project::Env) {
|
||||
let space = blueprint_axis_probe(doc, env).param_space();
|
||||
for p in &space {
|
||||
println!("{}:{:?}", p.name, p.kind); // ScalarKind Debug -> I64/F64/Bool/Timestamp
|
||||
// Strip the wrapper's one leading node segment (#328): open names print
|
||||
// RAW, matching `--axis`'s own accepted namespace.
|
||||
println!("{}:{:?}", aura_runner::axes::wrapped_to_raw_axis(&p.name), p.kind); // ScalarKind Debug -> I64/F64/Bool/Timestamp
|
||||
}
|
||||
let signal = blueprint_from_json(doc, &|t| env.resolve(t))
|
||||
.expect("doc parse-validated at the dispatch boundary");
|
||||
let bp = signal.name().to_string();
|
||||
for b in signal.bound_param_space() {
|
||||
println!("{bp}.{}:{:?} default={}", b.name, b.kind, render_value(&b.value));
|
||||
// `bound_param_space()`'s `.name` is already RAW (#203) — no blueprint-name
|
||||
// concatenation (#328: the blueprint name stays out of axis paths, C23).
|
||||
println!("{}:{:?} default={}", b.name, b.kind, render_value(&b.value));
|
||||
}
|
||||
}
|
||||
|
||||
/// The one-sentence prose for a refused WRAPPED `--axis` name (#328), shared
|
||||
/// by both intake routes so the wording carried by `refuse_wrapped_synthetic_axes`
|
||||
/// (the synthetic-sweep preflight) and `validate_and_register_axes` (the
|
||||
/// real-route/campaign preflight) cannot drift into two copies. Returns the
|
||||
/// message WITHOUT the leading `aura: ` — each caller prepends that itself
|
||||
/// (the synthetic route via its own `eprintln!`, the real route via
|
||||
/// `exit_axis_register_error`'s shared `eprintln!("aura: {m}")`), so the
|
||||
/// rendered bytes on both routes stay byte-identical.
|
||||
fn wrapped_axis_refusal(n: &str, raw: &str) -> String {
|
||||
format!(
|
||||
"axis \"{n}\": axis names are raw node.param paths — \
|
||||
use \"{raw}\" (the wrapped --list-axes form was retired, #328)"
|
||||
)
|
||||
}
|
||||
|
||||
/// `--axis` intake preflight for the SYNTHETIC (no `--real`) blueprint-sweep
|
||||
/// route (#328): this route bypasses `validate_and_register_axes` entirely (no
|
||||
/// project/registry touched), so it needs its own copy of the same WRAPPED-name
|
||||
/// refusal — the shared [`aura_runner::axes::classify_axis_intake`] predicate,
|
||||
/// intercepting only a `WrappedRetired` hit (echoing the translation pointer,
|
||||
/// exit 2, before the sweep ever runs). A name matching neither namespace is
|
||||
/// deliberately left unrefused HERE: `blueprint_sweep_family`'s own
|
||||
/// `override_paths` rejects it downstream with today's unchanged prose, so this
|
||||
/// preflight does not duplicate that message under a second wording.
|
||||
fn refuse_wrapped_synthetic_axes(
|
||||
doc: &str,
|
||||
env: &aura_runner::project::Env,
|
||||
axes: &[(String, Vec<Scalar>)],
|
||||
) {
|
||||
let wrapped_open = blueprint_axis_probe(doc, env).param_space();
|
||||
let raw_bound: HashSet<String> = blueprint_from_json(doc, &|t| env.resolve(t))
|
||||
.expect("doc parse-validated at the dispatch boundary")
|
||||
.bound_param_space()
|
||||
.into_iter()
|
||||
.map(|b| b.name)
|
||||
.collect();
|
||||
for (n, _) in axes {
|
||||
if let aura_runner::axes::AxisIntake::WrappedRetired(raw) =
|
||||
aura_runner::axes::classify_axis_intake(n, &wrapped_open, &raw_bound)
|
||||
{
|
||||
eprintln!("aura: {}", wrapped_axis_refusal(n, &raw));
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1661,14 +1711,17 @@ enum AxisRegisterError {
|
||||
NoProject(String),
|
||||
}
|
||||
|
||||
/// Validate every `--axis` name against the blueprint's WRAPPED probe namespace
|
||||
/// (`blueprint_axis_probe`/`--list-axes`) — an axis naming a BOUND param (#246:
|
||||
/// re-openable, same as every already-open knob) passes exactly like an open
|
||||
/// one; only a name matching NEITHER space is refused — then canonicalize +
|
||||
/// register the blueprint by topology hash and strip every axis name to the RAW
|
||||
/// campaign namespace (the sweep sequence, #210 c0110). A raw-form or
|
||||
/// fat-fingered axis name is refused here, echoing exactly what the user typed,
|
||||
/// before the archive is touched or the blueprint is registered — shared by
|
||||
/// Validate every `--axis` name against the RAW campaign-axis namespace only
|
||||
/// (#328: `--list-axes`'s own namespace, open-or-bound) — a WRAPPED name (the
|
||||
/// retired `<blueprint>.<node>.<param>` form) is refused with a translation
|
||||
/// pointer to its raw candidate; a name in neither namespace gets today's
|
||||
/// unmatched-axis prose, unchanged. [`aura_runner::axes::classify_axis_intake`]
|
||||
/// is the shared predicate (deliberately not `raw_matches_wrapped`, whose own
|
||||
/// equality branch would silently accept a wrapped name — see its doc
|
||||
/// comment). Then canonicalize + register the blueprint by topology hash — no
|
||||
/// further strip: the axes are already raw by the time this returns them (the
|
||||
/// old wrapped->raw strip is gone, since a splice path's own dots, e.g.
|
||||
/// `anchor.sess.period_minutes`, must not be mangled a second time). Shared by
|
||||
/// every campaign-path dispatcher (sweep/generalize/walkforward/mc all landed
|
||||
/// the identical block; #220 slice-1 deferred this dedup to "once wf/mc land
|
||||
/// the same block", rule-of-three now exceeded 4x). The override set itself is
|
||||
@@ -1682,17 +1735,24 @@ fn validate_and_register_axes(
|
||||
axes: &[(String, Vec<Scalar>)],
|
||||
env: &aura_runner::project::Env,
|
||||
) -> Result<(String, Vec<(String, Vec<Scalar>)>), AxisRegisterError> {
|
||||
let space = blueprint_axis_probe(doc, env).param_space();
|
||||
let wrapped_open = blueprint_axis_probe(doc, env).param_space();
|
||||
let blueprint = blueprint_from_json(doc, &|t| env.resolve(t))
|
||||
.expect("doc parse-validated at the dispatch boundary");
|
||||
let bound = wrapped_bound_names(&blueprint);
|
||||
let raw_bound: HashSet<String> =
|
||||
blueprint.bound_param_space().into_iter().map(|b| b.name).collect();
|
||||
for (n, _) in axes {
|
||||
if !space.iter().any(|p| &p.name == n) && !bound.contains(n) {
|
||||
return Err(AxisRegisterError::UnknownAxis(format!(
|
||||
"axis \"{n}\" is not one of this blueprint's \
|
||||
sweepable axes — run 'aura sweep <bp> --list-axes' \
|
||||
to see them"
|
||||
)));
|
||||
match aura_runner::axes::classify_axis_intake(n, &wrapped_open, &raw_bound) {
|
||||
aura_runner::axes::AxisIntake::Raw => {}
|
||||
aura_runner::axes::AxisIntake::WrappedRetired(raw) => {
|
||||
return Err(AxisRegisterError::UnknownAxis(wrapped_axis_refusal(n, &raw)));
|
||||
}
|
||||
aura_runner::axes::AxisIntake::Unknown => {
|
||||
return Err(AxisRegisterError::UnknownAxis(format!(
|
||||
"axis \"{n}\" is not one of this blueprint's \
|
||||
sweepable axes — run 'aura sweep <bp> --list-axes' \
|
||||
to see them"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if env.provenance().is_none() {
|
||||
@@ -1709,11 +1769,7 @@ fn validate_and_register_axes(
|
||||
let topo = topology_hash(&blueprint);
|
||||
reg.put_blueprint(&topo, &canonical)
|
||||
.map_err(|e| AxisRegisterError::Registry(e.to_string()))?;
|
||||
let raw_axes: Vec<(String, Vec<Scalar>)> = axes
|
||||
.iter()
|
||||
.map(|(n, v)| (aura_runner::axes::wrapped_to_raw_axis(n).to_string(), v.clone()))
|
||||
.collect();
|
||||
Ok((canonical, raw_axes))
|
||||
Ok((canonical, axes.to_vec()))
|
||||
}
|
||||
|
||||
/// Exit-map for a `validate_and_register_axes` failure — the stderr line and
|
||||
@@ -2326,8 +2382,8 @@ fn dispatch_sweep(a: SweepCmd, env: &aura_runner::project::Env) {
|
||||
// strategy ref resolves against this one write; a second
|
||||
// put under `content_id_of(&canonical)` would target the
|
||||
// identical key and is not needed.
|
||||
// Axis validation + canonicalize/register + the wrapped->raw
|
||||
// strip are single-sourced in `validate_and_register_axes`
|
||||
// Axis validation (RAW-only, #328) + canonicalize/register are
|
||||
// single-sourced in `validate_and_register_axes`
|
||||
// (data-free, so this fires before the archive is touched).
|
||||
let (canonical, raw_axes) = validate_and_register_axes("sweep", &doc, &axes, env)
|
||||
.unwrap_or_else(|e| exit_axis_register_error(e));
|
||||
@@ -2361,6 +2417,10 @@ fn dispatch_sweep(a: SweepCmd, env: &aura_runner::project::Env) {
|
||||
);
|
||||
std::process::exit(2);
|
||||
}
|
||||
// #328: this route bypasses `validate_and_register_axes` (no
|
||||
// project/registry involved), so it needs its own WRAPPED-name
|
||||
// refusal ahead of the sweep.
|
||||
refuse_wrapped_synthetic_axes(&doc, env, &axes);
|
||||
run_blueprint_sweep(
|
||||
&doc, &axes, &name, persist,
|
||||
DataSource::from_choice(data, env), env,
|
||||
@@ -2418,8 +2478,8 @@ fn dispatch_walkforward(a: WalkforwardCmd, env: &aura_runner::project::Env) {
|
||||
eprintln!("aura: {m}");
|
||||
std::process::exit(2);
|
||||
});
|
||||
// Axis validation + canonicalize/register + the wrapped->raw
|
||||
// strip are single-sourced in `validate_and_register_axes`
|
||||
// Axis validation (RAW-only, #328) + canonicalize/register are
|
||||
// single-sourced in `validate_and_register_axes`
|
||||
// (data-free, so this fires before the archive is touched).
|
||||
let (canonical, raw_axes) = validate_and_register_axes("walkforward", &doc, &axes, env)
|
||||
.unwrap_or_else(|e| exit_axis_register_error(e));
|
||||
@@ -2532,8 +2592,8 @@ fn dispatch_mc(a: McCmd, env: &aura_runner::project::Env) {
|
||||
eprintln!("aura: {}", usage());
|
||||
std::process::exit(2);
|
||||
}
|
||||
// Axis validation + canonicalize/register + the wrapped->raw
|
||||
// strip are single-sourced in `validate_and_register_axes`
|
||||
// Axis validation (RAW-only, #328) + canonicalize/register are
|
||||
// single-sourced in `validate_and_register_axes`
|
||||
// (data-free, so this fires before the archive is touched).
|
||||
let (canonical, raw_axes) = validate_and_register_axes("mc", &doc, &axes, env)
|
||||
.unwrap_or_else(|e| exit_axis_register_error(e));
|
||||
@@ -3532,11 +3592,11 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// #246: a fully-bound (closed) blueprint IS sweepable — an axis naming a
|
||||
/// bound param re-opens it (the bound value is the default), so the retired
|
||||
/// "fully bound; nothing to sweep" refusal must not resurface. Inverse
|
||||
/// guard: an axis naming NEITHER an open nor a bound param gets the one
|
||||
/// clear boundary message (not a terse `UnknownKnob` debug leak).
|
||||
/// #246/#328: a fully-bound (closed) blueprint IS sweepable — a RAW axis
|
||||
/// naming a bound param re-opens it (the bound value is the default), so
|
||||
/// the retired "fully bound; nothing to sweep" refusal must not resurface.
|
||||
/// Inverse guard: an axis naming NEITHER an open nor a bound param gets
|
||||
/// the one clear boundary message (not a terse `UnknownKnob` debug leak).
|
||||
#[test]
|
||||
fn blueprint_sweep_family_overrides_a_bound_param_and_names_unknown_axes() {
|
||||
let env = aura_runner::project::Env::std();
|
||||
@@ -3544,9 +3604,9 @@ mod tests {
|
||||
let closed = load_closed_r_sma();
|
||||
let doc = blueprint_to_json(&closed).expect("serializes");
|
||||
|
||||
// (a) override axis: two members, no refusal
|
||||
// (a) override axis (RAW name, #328): two members, no refusal
|
||||
let axes = vec![(
|
||||
"sma_signal.fast.length".to_string(),
|
||||
"fast.length".to_string(),
|
||||
vec![Scalar::i64(2), Scalar::i64(4)],
|
||||
)];
|
||||
let fam = blueprint_sweep_family(&doc, &axes, &DataSource::Synthetic, &env)
|
||||
@@ -3554,7 +3614,7 @@ mod tests {
|
||||
assert_eq!(fam.points.len(), 2);
|
||||
|
||||
// (b) unknown axis: the boundary message, no UnknownKnob leak
|
||||
let bad = vec![("sma_signal.nope".to_string(), vec![Scalar::i64(1)])];
|
||||
let bad = vec![("nope".to_string(), vec![Scalar::i64(1)])];
|
||||
let err = blueprint_sweep_family(&doc, &bad, &DataSource::Synthetic, &env)
|
||||
.expect_err("an axis matching neither space is refused");
|
||||
assert!(err.contains("names no param"), "boundary message, got: {err}");
|
||||
@@ -3562,25 +3622,27 @@ mod tests {
|
||||
assert!(!err.contains("UnknownKnob"), "must not leak the debug render: {err}");
|
||||
}
|
||||
|
||||
/// #249: an axis-reopened bound param flows through `params` ("what
|
||||
/// #249/#328: an axis-reopened bound param flows through `params` ("what
|
||||
/// varied") and must NOT also appear in `defaults` ("what was held") — the
|
||||
/// two are disjoint by construction. Sweeping `fast.length` on the fully
|
||||
/// bound r_sma example leaves `slow.length`/`bias.scale` untouched (they
|
||||
/// stay in `defaults`), while `fast.length` moves to `params` and drops
|
||||
/// out of `defaults` entirely.
|
||||
/// two are disjoint by construction. Sweeping `fast.length` (RAW, #328) on
|
||||
/// the fully bound r_sma example leaves `slow.length`/`bias.scale`
|
||||
/// untouched (they stay in `defaults`), while `fast.length` moves to
|
||||
/// `params` and drops out of `defaults` entirely. `manifest.params` keys
|
||||
/// are RAW (#328); `manifest.defaults` (a different field, `run`'s own
|
||||
/// stamp of untouched bound params — untouched this cycle) stays WRAPPED.
|
||||
#[test]
|
||||
fn sweep_override_excludes_the_reopened_default_from_the_manifest() {
|
||||
let env = aura_runner::project::Env::std();
|
||||
let closed = load_closed_r_sma();
|
||||
let doc = blueprint_to_json(&closed).expect("serializes");
|
||||
let axes = vec![("sma_signal.fast.length".to_string(), vec![Scalar::i64(2)])];
|
||||
let axes = vec![("fast.length".to_string(), vec![Scalar::i64(2)])];
|
||||
let fam = blueprint_sweep_family(&doc, &axes, &DataSource::Synthetic, &env)
|
||||
.expect("a bound param is a default — the axis overrides it");
|
||||
assert_eq!(fam.points.len(), 1);
|
||||
let manifest = &fam.points[0].report.manifest;
|
||||
|
||||
let param_names: Vec<&str> = manifest.params.iter().map(|(n, _)| n.as_str()).collect();
|
||||
assert!(param_names.contains(&"sma_signal.fast.length"), "swept axis is a param: {param_names:?}");
|
||||
assert!(param_names.contains(&"fast.length"), "swept axis is a raw param: {param_names:?}");
|
||||
|
||||
let default_names: Vec<&str> = manifest.defaults.iter().map(|(n, _)| n.as_str()).collect();
|
||||
assert!(
|
||||
|
||||
@@ -462,8 +462,14 @@ pub(crate) fn ref_fault_prose(f: &RefFault) -> String {
|
||||
RefFault::StrategyUnloadable { id, error } => {
|
||||
format!("strategy {id} cannot be loaded: {error}")
|
||||
}
|
||||
RefFault::AxisNotInParamSpace { strategy, axis } => {
|
||||
format!("strategy {strategy}: axis \"{axis}\" is not in the param space")
|
||||
RefFault::AxisNotInParamSpace { strategy, axis, raw_candidate } => {
|
||||
let mut msg = format!("strategy {strategy}: axis \"{axis}\" is not in the param space");
|
||||
if let Some(candidate) = raw_candidate {
|
||||
msg.push_str(&format!(
|
||||
"; axis names are raw node.param paths — did you mean \"{candidate}\"?"
|
||||
));
|
||||
}
|
||||
msg
|
||||
}
|
||||
RefFault::AxisKindMismatch { strategy, axis } => {
|
||||
format!("strategy {strategy}: axis \"{axis}\" declares a kind that is not the param's kind")
|
||||
@@ -761,6 +767,7 @@ mod tests {
|
||||
ref_fault_prose(&RefFault::AxisNotInParamSpace {
|
||||
strategy: "9f3a".into(),
|
||||
axis: "nope".into(),
|
||||
raw_candidate: None,
|
||||
}),
|
||||
ref_fault_prose(&RefFault::AxisKindMismatch {
|
||||
strategy: "9f3a".into(),
|
||||
@@ -781,6 +788,39 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// #328: `ref_fault_prose` appends the did-you-mean clause, contiguous
|
||||
/// and verbatim, exactly when `raw_candidate` is present — the document-
|
||||
/// side refusal seam's mirror of the sweep `--axis` intake translation
|
||||
/// (`aura-runner`'s `axes.rs` classify predicate), sharing the same
|
||||
/// `axis names are raw node.param paths` clause pinned there.
|
||||
#[test]
|
||||
fn ref_fault_prose_renders_the_did_you_mean_when_a_raw_candidate_is_present() {
|
||||
let msg = ref_fault_prose(&RefFault::AxisNotInParamSpace {
|
||||
strategy: "9f3a".into(),
|
||||
axis: "graph.fast.length".into(),
|
||||
raw_candidate: Some("fast.length".into()),
|
||||
});
|
||||
assert_eq!(
|
||||
msg,
|
||||
"strategy 9f3a: axis \"graph.fast.length\" is not in the param space; \
|
||||
axis names are raw node.param paths — did you mean \"fast.length\"?"
|
||||
);
|
||||
}
|
||||
|
||||
/// #328 (negative): a rejected axis with no `raw_candidate` (stripping one
|
||||
/// leading segment yields no param-space hit, or the axis has no leading
|
||||
/// segment to strip at all) renders today's prose unchanged — no
|
||||
/// speculative suggestion.
|
||||
#[test]
|
||||
fn ref_fault_prose_omits_the_did_you_mean_when_no_raw_candidate() {
|
||||
let msg = ref_fault_prose(&RefFault::AxisNotInParamSpace {
|
||||
strategy: "9f3a".into(),
|
||||
axis: "nope".into(),
|
||||
raw_candidate: None,
|
||||
});
|
||||
assert_eq!(msg, "strategy 9f3a: axis \"nope\" is not in the param space");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// #194 (the prefix trap): a doc ref that carries the display `content:`
|
||||
/// prefix (a copy-paste from register/introspect output) is bare-only in
|
||||
|
||||
@@ -234,8 +234,9 @@ std vocabulary, anchored by `Aura.toml`. There is no crate and no build step.
|
||||
- Run: `aura run blueprints/signal.json` (the starter is closed — all
|
||||
params bound; bound values are defaults — any `--axis` may override them
|
||||
(#246))
|
||||
- Sweep: `aura sweep blueprints/signal.json --axis __NAME_SNAKE___signal.fast.length=2,4,8`
|
||||
(`aura sweep <bp> --list-axes` lists the open + bound-overridable axes)
|
||||
- Sweep: `aura sweep blueprints/signal.json --axis fast.length=2,4,8`
|
||||
(`aura sweep <bp> --list-axes` lists the open + bound-overridable axes, RAW
|
||||
`<node>.<param>` names — #328)
|
||||
- Native nodes: when the project needs its first project-specific node,
|
||||
`aura nodes new <name>` scaffolds a node crate beside this project and
|
||||
attaches it via `[nodes]` in `Aura.toml` (build it with `cargo build`).
|
||||
@@ -459,10 +460,11 @@ mod tests {
|
||||
let claude = render_project(CLAUDE_MD_PROJECT, "demo-lab");
|
||||
assert!(claude.contains("demo-lab"));
|
||||
// The CLAUDE.md sweep quickstart targets the closed starter itself
|
||||
// with the rendered blueprint-name axis prefix (#246: a bound param
|
||||
// is a default an axis overrides).
|
||||
// with the RAW axis name (#246: a bound param is a default an axis
|
||||
// overrides; #328: the axis namespace is raw, no blueprint-name
|
||||
// prefix).
|
||||
assert!(claude.contains("blueprints/signal.json"));
|
||||
assert!(claude.contains("demo_lab_signal.fast.length"));
|
||||
assert!(claude.contains("--axis fast.length"));
|
||||
}
|
||||
|
||||
/// #315: the scaffolded project CLAUDE.md teaches the execution semantics
|
||||
|
||||
Reference in New Issue
Block a user