fix(aura-cli, aura-runner): review findings — duplicate --override refusal, live discovery prose, neutral JSON refusal
In-cycle fixes for the independent review of the #319 package, RED-first for both Importants. A repeated --override path now refuses at the shared lexer (exit 2, path named) instead of panicking exit 101 through reopen_all — which also un-misattributes the campaign leg's collision prose for duplicates. override_paths' refusal points at aura graph introspect --params (the retired --list-axes reference swept from every live comment, one explicitly-historical mention remains). exec classifies file targets through a Result-bearing peek: unparseable JSON refuses neutrally before any leg is chosen. RunPresentation (single-variant) removed; run_campaign_returning's contract note names its one deliberate usage-class exit; C14's routing sentence and C20's family-builder paragraph amended to current truth (superseded text to the history sidecars); bench error strings and deletion blank-line runs tidied. refs #319
This commit is contained in:
@@ -251,7 +251,7 @@ fn campaign_rep(bin: &Path, sizing: Sizing, process_doc: &str) -> Result<RepOutc
|
||||
let timed: TimedChild = run_timed(bin, &["exec", &doc], &scratch.0)?;
|
||||
if timed.exit != Some(0) {
|
||||
return Err(format!(
|
||||
"campaign run exited {:?}\nstdout: {}\nstderr: {}",
|
||||
"aura exec exited {:?}\nstdout: {}\nstderr: {}",
|
||||
timed.exit, timed.stdout, timed.stderr
|
||||
));
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ pub fn rep(bin: &Path) -> Result<RepOutcome, String> {
|
||||
for _ in 0..RUN_SAMPLES {
|
||||
let timed = run_timed(bin, &["exec", "bench_sma_a.json"], &scratch.0)?;
|
||||
if timed.exit != Some(0) {
|
||||
return Err(format!("aura run exited {:?}: {}", timed.exit, timed.stdout));
|
||||
return Err(format!("aura exec exited {:?}: {}", timed.exit, timed.stdout));
|
||||
}
|
||||
run_walls.push(timed.wall_s * 1000.0);
|
||||
if first_line.is_none() {
|
||||
@@ -70,7 +70,7 @@ pub fn rep(bin: &Path) -> Result<RepOutcome, String> {
|
||||
.stdout
|
||||
.lines()
|
||||
.next()
|
||||
.ok_or("aura run must print its JSON record line")?
|
||||
.ok_or("aura exec must print its JSON record line")?
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,15 +108,6 @@ struct CampaignRunLine<'a> {
|
||||
campaign_run: &'a CampaignRunRecord,
|
||||
}
|
||||
|
||||
/// Stdout shape of a campaign run: emit-gated member/selection lines + the
|
||||
/// always-on final `campaign_run` record line. `MemberLinesOnly` (the
|
||||
/// dissolved-verb sugar path's suppressed-record-line mode) retired with the
|
||||
/// quintet (#319 Task 8) — `Full` is the only surviving shape.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub(crate) enum RunPresentation {
|
||||
Full,
|
||||
}
|
||||
|
||||
/// `aura campaign run <target>`: resolve, gate, execute, emit. Every `Err`
|
||||
/// is a refusal `campaign_cmd` renders as `aura: {msg}` + exit 1; an `Ok`
|
||||
/// carries the failed-cell count (#272) so the caller can exit 3 on a
|
||||
@@ -161,7 +152,7 @@ pub(crate) fn run_campaign(
|
||||
));
|
||||
};
|
||||
|
||||
run_campaign_by_id(&campaign_id, env, RunPresentation::Full, parallel_instruments, overrides)
|
||||
run_campaign_by_id(&campaign_id, env, parallel_instruments, overrides)
|
||||
}
|
||||
|
||||
/// An executed campaign plus the context its presenter and the dissolved-verb
|
||||
@@ -180,18 +171,15 @@ pub(crate) struct CampaignRun {
|
||||
/// The one campaign executor path from a resolved content id onward: fetch
|
||||
/// the stored canonical bytes by id (so file addressing and id addressing
|
||||
/// produce the same realization by construction) and re-run the intrinsic
|
||||
/// tier on them, execute, and emit per `presentation`. Called by
|
||||
/// `run_campaign` (`RunPresentation::Full`, the sole surviving shape since
|
||||
/// the quintet's dissolved-verb sugar path retired, #319 Task 8).
|
||||
/// tier on them, execute, and emit. Called by `run_campaign`.
|
||||
pub(crate) fn run_campaign_by_id(
|
||||
campaign_id: &str,
|
||||
env: &Env,
|
||||
presentation: RunPresentation,
|
||||
parallel_instruments: NonZeroUsize,
|
||||
overrides: &[(String, Scalar)],
|
||||
) -> Result<usize, String> {
|
||||
let run = run_campaign_returning(campaign_id, env, parallel_instruments, overrides)?;
|
||||
present_campaign(run, presentation, env)
|
||||
present_campaign(run, env)
|
||||
}
|
||||
|
||||
/// The one campaign executor path from a resolved content id up to (not
|
||||
@@ -206,6 +194,15 @@ pub(crate) fn run_campaign_by_id(
|
||||
/// and BEFORE the referential gate — the stored bytes and the recorded
|
||||
/// `campaign_id` stay those of the ORIGINAL document; only the executed
|
||||
/// run-set changes, its audit record being the raw member manifests.
|
||||
///
|
||||
/// One deliberate exception to "every refusal is an `Err`" (review Minor-4):
|
||||
/// the injection loop's axis-collision check exits directly with code 2
|
||||
/// instead of returning `Err`. That collision is a USAGE-class fault (C14) —
|
||||
/// `--override` naming a path the document already declares as an axis is a
|
||||
/// malformed invocation, exactly like `--override`'s own malformed-token
|
||||
/// refusal (`parse_override_tokens`, exit 2) — not a RUNTIME-class refusal
|
||||
/// (missing environment/recorded state, exit 1), which is what this fn's
|
||||
/// `Result<_, String>` return otherwise carries end to end.
|
||||
pub(crate) fn run_campaign_returning(
|
||||
campaign_id: &str,
|
||||
env: &Env,
|
||||
@@ -316,15 +313,11 @@ of the campaign; an override overrides a bound value, never an axis"
|
||||
Ok(CampaignRun { outcome, campaign, strategies, server: runner.server() })
|
||||
}
|
||||
|
||||
/// Emit an executed campaign's results per `presentation`: the zero-survivor
|
||||
/// stderr notes, the emit-gated family/selection lines, the always-on record
|
||||
/// line (Full only), then trace persistence. Split out of `run_campaign_by_id`
|
||||
/// so the dissolved-verb sugar can consume the outcome without this stdout tail.
|
||||
fn present_campaign(
|
||||
run: CampaignRun,
|
||||
presentation: RunPresentation,
|
||||
env: &Env,
|
||||
) -> Result<usize, String> {
|
||||
/// Emit an executed campaign's results: the zero-survivor stderr notes, the
|
||||
/// emit-gated family/selection lines, the always-on record line, then trace
|
||||
/// persistence. Split out of `run_campaign_by_id` so a forthcoming caller
|
||||
/// could consume the outcome without this stdout tail.
|
||||
fn present_campaign(run: CampaignRun, env: &Env) -> Result<usize, String> {
|
||||
let CampaignRun { outcome, campaign, strategies, server } = run;
|
||||
|
||||
// Zero-survivor stderr notes (exit stays 0 — a null result is a valid
|
||||
@@ -413,23 +406,21 @@ fn present_campaign(
|
||||
}
|
||||
}
|
||||
}
|
||||
if presentation == RunPresentation::Full {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string(&CampaignRunLine { campaign_run: &outcome.record })
|
||||
.expect("campaign run record serializes")
|
||||
);
|
||||
eprintln!(
|
||||
"aura: campaign run {} recorded: {} cells{}",
|
||||
outcome.record.run,
|
||||
outcome.record.cells.len(),
|
||||
if failed == 0 {
|
||||
String::new()
|
||||
} else {
|
||||
format!(", {failed} failed ({})", fail_labels.join(", "))
|
||||
}
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string(&CampaignRunLine { campaign_run: &outcome.record })
|
||||
.expect("campaign run record serializes")
|
||||
);
|
||||
eprintln!(
|
||||
"aura: campaign run {} recorded: {} cells{}",
|
||||
outcome.record.run,
|
||||
outcome.record.cells.len(),
|
||||
if failed == 0 {
|
||||
String::new()
|
||||
} else {
|
||||
format!(", {failed} failed ({})", fail_labels.join(", "))
|
||||
}
|
||||
);
|
||||
|
||||
// Trace persistence runs LAST, after the always-on record line (0109):
|
||||
// stdout stays data-pure (the record line is the wire contract) and the
|
||||
|
||||
@@ -642,10 +642,11 @@ pub fn introspect_cmd(cmd: crate::GraphIntrospectCmd, env: &aura_runner::project
|
||||
} else if let Some(target) = cmd.params.as_deref() {
|
||||
// --params <FILE|ID> (#196): the RAW composite param space — the one
|
||||
// namespace campaign axes are validated against
|
||||
// (`validate_campaign_refs`) and `aura sweep --list-axes` prints
|
||||
// (#328: the wrapped `<blueprint>.<node>.<param>` form was retired —
|
||||
// there is exactly one axis namespace now, so the two discovery
|
||||
// surfaces agree line-for-line, see `params_lines`'s own doc comment).
|
||||
// (`validate_campaign_refs`) and `exec --override`'s own resolution
|
||||
// (`override_paths`, member.rs) resolves against (#328: the wrapped
|
||||
// `<blueprint>.<node>.<param>` form was retired — there is exactly
|
||||
// one axis namespace now, so the two surfaces agree line-for-line,
|
||||
// see `params_lines`'s own doc comment).
|
||||
match params_lines(target, env) {
|
||||
Ok(s) => print!("{s}"),
|
||||
Err(m) => {
|
||||
@@ -938,8 +939,9 @@ fn resolve_blueprint_text(target: &str, env: &aura_runner::project::Env) -> Resu
|
||||
/// 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.
|
||||
/// concatenation is needed — line-identical to the retired `aura sweep
|
||||
/// --list-axes`'s own bound pass (`list_blueprint_axes`, main.rs, gone with
|
||||
/// the #319 sugar retirement), same `render_value` lexicon.
|
||||
/// #331 delta re-review: the FILE target is a FOURTH freshly-authored-file
|
||||
/// intake this fn's own `composite_from_any` call had missed gating — a FILE
|
||||
/// routes through [`composite_from_authored_text`] instead; a content id
|
||||
|
||||
+60
-33
@@ -72,8 +72,8 @@ use aura_runner::reproduce::reproduce_family_in;
|
||||
use aura_runner::measure::run_measurement;
|
||||
// `render_value` is the single source (aura-runner, #295 Task 7): the
|
||||
// campaign trace layout's member-key label and this shell's own
|
||||
// presentation (reproduce output, `--list-axes` default printing) render a
|
||||
// scalar identically.
|
||||
// presentation (reproduce output, `graph introspect --params`'s bound-pass
|
||||
// default printing) render a scalar identically.
|
||||
use aura_runner::runner::render_value;
|
||||
// Only reached from the test module's stop-regime default tests now (#319
|
||||
// Task 8 — the quintet's own production callers are retired); test-only,
|
||||
@@ -612,31 +612,30 @@ impl IcReport {
|
||||
}
|
||||
}
|
||||
|
||||
/// `aura sweep <blueprint.json> --list-axes`: one `<name>:<kind>` line per open
|
||||
/// sweepable knob, in `param_space()` order (byte-identical to before #246),
|
||||
/// followed by one `<name>:<kind> default=<value>` line per BOUND param —
|
||||
/// every bound param IS a sweepable default, re-openable by naming it as an
|
||||
/// `--axis` (#246), on EVERY blueprint shape (fully open, partially open, or
|
||||
/// 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 RAW `<node>.<param>` paths (#328: the wrapped `<blueprint>.<node>.<param>`
|
||||
/// form is retired from the surface) — exactly what `--axis` binds.
|
||||
///
|
||||
/// Lex `exec --override NODE.PARAM=VALUE` tokens (#319 Task 3/4): the same
|
||||
/// int-then-float rule `parse_scalar_csv` uses for `--axis` values, shared by
|
||||
/// int-then-float rule the campaign leg's `--axis` values use, shared by
|
||||
/// both `exec` legs (the blueprint leg's reopen recipe and the campaign
|
||||
/// leg's single-value-axis injection) so the wording and the parse rule
|
||||
/// cannot drift between them. A malformed token refuses at the usage
|
||||
/// boundary (exit 2) — intake, not a per-leg concern.
|
||||
///
|
||||
/// A repeated path (the same NODE.PARAM named twice) likewise refuses here
|
||||
/// (exit 2), mirroring `--tap`'s own duplicate refusal
|
||||
/// (`tap_plan_from_args`): downstream, the blueprint leg's `reopen_all`
|
||||
/// folds the override set onto the signal one path at a time, so a second
|
||||
/// occurrence of an already-reopened path is no longer bound and panics
|
||||
/// (`NoSuchBoundParam`) — refusing the duplicate at the lexer, before either
|
||||
/// leg ever resolves it, is the one gate that protects both.
|
||||
fn parse_override_tokens(tokens: &[String]) -> Vec<(String, Scalar)> {
|
||||
let mut ov = Vec::new();
|
||||
let mut seen = BTreeSet::new();
|
||||
for tok in tokens {
|
||||
let Some((path_s, val_s)) = tok.split_once('=') else {
|
||||
eprintln!("aura: exec: --override expects NODE.PARAM=VALUE, got `{tok}`");
|
||||
std::process::exit(2);
|
||||
};
|
||||
if path_s.trim().is_empty() {
|
||||
let path = path_s.trim();
|
||||
if path.is_empty() {
|
||||
eprintln!("aura: exec: --override expects NODE.PARAM=VALUE, got `{tok}`");
|
||||
std::process::exit(2);
|
||||
}
|
||||
@@ -650,7 +649,11 @@ fn parse_override_tokens(tokens: &[String]) -> Vec<(String, Scalar)> {
|
||||
}
|
||||
},
|
||||
};
|
||||
ov.push((path_s.trim().to_string(), val));
|
||||
if !seen.insert(path.to_string()) {
|
||||
eprintln!("aura: exec: --override names path \"{path}\" twice");
|
||||
std::process::exit(2);
|
||||
}
|
||||
ov.push((path.to_string(), val));
|
||||
}
|
||||
ov
|
||||
}
|
||||
@@ -659,15 +662,17 @@ fn parse_override_tokens(tokens: &[String]) -> Vec<(String, Scalar)> {
|
||||
// The declarative argument grammar. clap owns argv tokenizing, scoped `--help`,
|
||||
// `--version`, `--flag=value`, `--`, and long-option abbreviation; the `dispatch_*`
|
||||
// handlers below convert each `*Cmd` into the argument shapes the existing execution
|
||||
// fns accept, reusing the value helpers (`Selection`,
|
||||
// `DataSource::from_choice`, `parse_scalar_csv`, `parse_select`,
|
||||
// `parse_param_cells`). The four subcommands with an optional `[blueprint]` positional
|
||||
// dispatch on `is_blueprint_file`: a first-positional naming an existing `.json` file
|
||||
// selects the loaded-blueprint branch. There is no second built-in grammar anymore
|
||||
// (#159 demo retirement / #220 axis generalization): without a blueprint the
|
||||
// dispatcher prints a usage error and exits 2; a blueprint with `--real` routes to
|
||||
// the campaign path. Usage errors exit 2; runtime refusals exit 1; a run that
|
||||
// completes with ≥1 failed cell exits 3 (#272); a clean run exits 0.
|
||||
// fns accept. `exec` (#319) is the one document-executing verb: it dispatches on
|
||||
// `is_blueprint_file` layered with `classify_exec_document_file` (the "kind"-field
|
||||
// peek) — a first-positional naming an existing `.json` file that parses as JSON
|
||||
// and carries no top-level "kind" selects the loaded-blueprint leg; a file
|
||||
// carrying "kind", or a target that is not a readable `.json` file at all (a
|
||||
// registered content id), selects the campaign leg; a file that does not parse as
|
||||
// JSON at all refuses neutrally before either leg's own validation runs.
|
||||
// `--override`/`--tap` tokens are lexed by `parse_override_tokens`/
|
||||
// `tap_plan_from_args` above, shared across both legs where applicable. Usage
|
||||
// errors exit 2; runtime refusals exit 1; a campaign that completes with ≥1
|
||||
// failed cell exits 3 (#272); a clean run exits 0.
|
||||
|
||||
/// The single build-time commit provenance (`option_env!("AURA_COMMIT")`,
|
||||
/// falling back to `"unknown"`): both `--version` (via `version_string`) and
|
||||
@@ -955,7 +960,7 @@ struct ExecCmd {
|
||||
|
||||
/// The file-target discriminator: a first-positional that names an existing
|
||||
/// `.json` file is a document on disk. Shared by `exec`'s target routing
|
||||
/// (layered with `is_campaign_document_file`) and `dispatch_graph`.
|
||||
/// (layered with `classify_exec_document_file`) and `dispatch_graph`.
|
||||
fn is_blueprint_file(arg: &Option<String>) -> Option<&str> {
|
||||
arg.as_deref()
|
||||
.filter(|a| a.ends_with(".json") && std::path::Path::new(a).is_file())
|
||||
@@ -969,11 +974,19 @@ fn is_blueprint_file(arg: &Option<String>) -> Option<&str> {
|
||||
/// token — it cannot tell a campaign document ending in `.json` from a
|
||||
/// blueprint file, both being ordinary readable `.json` files — so `exec`'s
|
||||
/// routing peeks this one cheap key before choosing a leg.
|
||||
fn is_campaign_document_file(path: &str) -> bool {
|
||||
std::fs::read_to_string(path)
|
||||
.ok()
|
||||
.and_then(|t| serde_json::from_str::<serde_json::Value>(&t).ok())
|
||||
.is_some_and(|v| v.get("kind").is_some())
|
||||
///
|
||||
/// `Ok(true)`/`Ok(false)` classifies a file that parses as JSON at all
|
||||
/// (campaign vs. not); `Err` means the file does NOT parse as JSON at all —
|
||||
/// review Minor-3: a file this malformed has no leg to misattribute the
|
||||
/// fault to (neither leg's own "document is not valid JSON" prose is the
|
||||
/// right cause when the shape was never determined), so the caller refuses
|
||||
/// neutrally instead of silently falling through to the blueprint leg.
|
||||
fn classify_exec_document_file(path: &str) -> Result<bool, String> {
|
||||
let text =
|
||||
std::fs::read_to_string(path).map_err(|e| format!("target file is not readable: {e}"))?;
|
||||
let v: serde_json::Value = serde_json::from_str(&text)
|
||||
.map_err(|e| format!("target file is not valid JSON: {e}"))?;
|
||||
Ok(v.get("kind").is_some())
|
||||
}
|
||||
|
||||
/// Terminate per a campaign-path result (#272): a String error is a refusal
|
||||
@@ -1017,10 +1030,24 @@ fn tap_plan_from_args(args: &[String]) -> Result<TapPlan, String> {
|
||||
}
|
||||
|
||||
/// The one executor verb (#319): a campaign document (file or content id)
|
||||
/// or a signal blueprint (single run). Flag discipline is per-leg.
|
||||
/// or a signal blueprint (single run). Flag discipline is per-leg. A
|
||||
/// `.json` file target is read and classified ONCE (review Minor-3): a file
|
||||
/// that does not parse as JSON at all refuses neutrally here, before either
|
||||
/// leg's own document-shape validation ever runs — so a malformed campaign
|
||||
/// document is never misattributed to the blueprint leg's "blueprint
|
||||
/// document is not valid JSON" prose.
|
||||
fn dispatch_exec(a: ExecCmd, env: &aura_runner::project::Env) {
|
||||
let target = Some(a.target.clone());
|
||||
let blueprint_path = is_blueprint_file(&target).filter(|p| !is_campaign_document_file(p));
|
||||
let file_path = is_blueprint_file(&target);
|
||||
let is_campaign_file = match file_path.map(classify_exec_document_file) {
|
||||
Some(Ok(is_campaign)) => is_campaign,
|
||||
Some(Err(msg)) => {
|
||||
eprintln!("aura: exec: {}: {msg}", file_path.expect("Some by the match arm above"));
|
||||
std::process::exit(2);
|
||||
}
|
||||
None => false,
|
||||
};
|
||||
let blueprint_path = file_path.filter(|_| !is_campaign_file);
|
||||
match blueprint_path {
|
||||
Some(path) => exec_blueprint_leg(path, &a.r#override, &a.tap, env),
|
||||
None => {
|
||||
|
||||
@@ -203,7 +203,6 @@ fn sweep_campaign_doc(
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/// Property (ported onto a one-cell campaign document, #319 Task 8 — the
|
||||
/// retired `run --real` verb's own hard exit-1/no-stdout contract does not
|
||||
/// survive the campaign path's #272 contained-fault model, but the pip-honesty
|
||||
@@ -391,9 +390,6 @@ fn run_real_nonvetted_symbol_resolves_pip_from_sidecar() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Note (#159 cut 4 collateral): `--trace` on a single `aura run`/`aura sweep`
|
||||
// is no longer reachable from any CLI surface — the built-in default that
|
||||
// accepted it retired with the last two PIP strategies, and the blueprint
|
||||
@@ -736,8 +732,6 @@ fn no_args_prints_usage_and_exits_two() {
|
||||
assert!(stderr.contains("Usage"), "stderr was: {stderr:?}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Property: `aura graph` emits a single self-contained HTML page — the
|
||||
/// interactive pin-graph viewer with the deterministic model inlined and the
|
||||
/// vendored Graphviz-WASM + pan-zoom inlined (no remote fetch). Driven through
|
||||
@@ -878,7 +872,6 @@ fn graph_with_bad_blueprint_arg_is_a_usage_fault_not_a_silent_sample() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn runs_families_list_and_per_family_rank_across_invocations() {
|
||||
// Ported onto a one-cell campaign document run twice (#319 Task 8): two
|
||||
@@ -1328,7 +1321,6 @@ fn run_real_ohlc_channel_example_end_to_end_hostless() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// The dissolved real-data sweep + reproduce over the OHLC example (#231
|
||||
/// acceptance), ported onto a one-cell campaign document (#319 Task 8 —
|
||||
/// `--list-axes` itself is dead flag surface, retired with `sweep`; its
|
||||
@@ -1755,24 +1747,27 @@ fn reproduce_real_sweep_family_re_derives_bit_identically() {
|
||||
/// overriding only the `fast.length` axis and leaving `slow.length` bound —
|
||||
/// unlike the walk-forward reproduce sibling
|
||||
/// (`aura_walkforward_over_a_blueprint_reproduces_bit_identically`), which
|
||||
/// overrides both axes over the same closed blueprint. The walk-forward
|
||||
/// synthetic route's own `--axis` intake is now RAW (this cycle's tidy fix,
|
||||
/// #328): the CLI preflight refuses a wrapped name before this run ever starts
|
||||
/// overrides both axes over the same closed blueprint. Originally modelled on
|
||||
/// that sibling (the retired synthetic in-process walk-forward dispatch verb —
|
||||
/// no `--real`), NOT on `reproduce_real_sweep_family_re_derives_bit_identically`:
|
||||
/// a real-data mint routed through the campaign executor
|
||||
/// (`CliMemberRunner::run_member` + `validate_and_register_axes`), neither of
|
||||
/// which carried any #246 override threading — a gap outside that task's file
|
||||
/// scope (`blueprint_sweep_over`, `reproduce_family_in`) and spanning all four
|
||||
/// (then-live) dispatch verbs, so exercising it there would have silently
|
||||
/// expanded into unrelated, unreviewed surface. Back when the synthetic
|
||||
/// walk-forward dispatch verb existed, its `--axis` intake was RAW (#328): the
|
||||
/// CLI preflight refused a wrapped name before the run ever started
|
||||
/// (`refuse_wrapped_synthetic_axes`), and `blueprint_sweep_over`'s own
|
||||
/// `SweepBinder` call translates the RAW name onto its wrapped `param_space()`
|
||||
/// slot (`wrapped_name_of`) before keying by it.
|
||||
/// Modelled on that sibling (synthetic in-process walk-forward — no `--real`),
|
||||
/// NOT on `reproduce_real_sweep_family_re_derives_bit_identically`: a real-data
|
||||
/// mint routes through the campaign executor (`CliMemberRunner::run_member` +
|
||||
/// `validate_and_register_axes`), neither of which carries any #246 override
|
||||
/// threading — a gap outside this task's file scope (`blueprint_sweep_over`,
|
||||
/// `reproduce_family_in`) and spanning all four dispatch verbs, so exercising
|
||||
/// it here would silently expand into unrelated, unreviewed surface. The
|
||||
/// synthetic walk-forward path exercises exactly the threaded functions:
|
||||
/// `blueprint_sweep_over` (via `blueprint_walkforward_family`) re-opens
|
||||
/// `fast.length` for the per-window IS re-fit, and
|
||||
/// `reproduce_family_in` re-opens it again from the recorded manifest params
|
||||
/// so the re-derivation resolves against the same space the mint used.
|
||||
/// `SweepBinder` call translated the RAW name onto its wrapped `param_space()`
|
||||
/// slot (`wrapped_name_of`) before keying by it. That path exercised exactly
|
||||
/// the threaded functions: `blueprint_sweep_over` (via
|
||||
/// `blueprint_walkforward_family`) re-opened `fast.length` for the per-window
|
||||
/// IS re-fit, and `reproduce_family_in` re-opened it again from the recorded
|
||||
/// manifest params so the re-derivation resolved against the same space the
|
||||
/// mint used. #319 Task 8 ported this test onto the campaign
|
||||
/// `std::walk_forward` stage (see the in-body port note below); the property
|
||||
/// pinned here is unchanged.
|
||||
#[test]
|
||||
fn reproduce_family_with_overridden_bound_param_re_derives() {
|
||||
// Ported onto a `[std::grid, std::walk_forward]` campaign document over
|
||||
@@ -1939,7 +1934,6 @@ fn reproduce_real_walkforward_family_does_not_panic() {
|
||||
// fixture at a single axis value instead.)
|
||||
const HL_SIGNAL_OPEN_BLUEPRINT: &str = r#"{"format_version":1,"blueprint":{"name":"hl_signal","doc":"high/low SMA difference clamped into a directional bias","nodes":[{"primitive":{"type":"SMA","name":"fast"}},{"primitive":{"type":"SMA","name":"slow"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"high","targets":[{"node":0,"slot":0}],"source":"F64"},{"name":"low","targets":[{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}"#;
|
||||
|
||||
|
||||
// Note (#319 Task 8, disposition deviation): `walkforward_synthetic_refuses_a_
|
||||
// multi_column_blueprint_before_data_access` (the synthetic, no-`--real`
|
||||
// exit-process arm) retired rather than ported — it is a byte-identical twin
|
||||
@@ -2042,12 +2036,6 @@ fn sweep_real_multi_column_blueprint_opens_high_and_low_columns() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Property (issue #246, task 5): the dissolved real-data `aura sweep`
|
||||
/// campaign path accepts an `--axis` naming a BOUND param of a CLOSED
|
||||
/// blueprint, not only an already-open one. Tasks 3-4 threaded this override
|
||||
@@ -2168,11 +2156,6 @@ fn sweep_over_a_bound_override_matches_the_historical_open_bind() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Note (#159 cut 4 collateral): this removes the `aura chart <name>`
|
||||
// CLI-integration tests (single-run and family overlay/tap-filter/meta, the
|
||||
// cross-kind write-guard collision + same-kind-overwrite pair) — every one of
|
||||
@@ -2188,12 +2171,6 @@ fn sweep_over_a_bound_override_matches_the_historical_open_bind() {
|
||||
|
||||
// --- `aura run --harness <name>` selector (iter-3 Task 3) --------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Property: `aura generalize` grades a single r-sma candidate across two
|
||||
/// instruments — its stdout carries the per-instrument breakdown + the worst-case
|
||||
/// floor + the sign-agreement, then the persisted CrossInstrument family handle.
|
||||
@@ -2307,17 +2284,20 @@ fn generalize_real_e2e_pins_the_exact_current_grade() {
|
||||
assert_eq!(per[1][1].as_f64(), Some(0.005795903617609842), "USDJPY expectancy R: {record_line}");
|
||||
}
|
||||
|
||||
/// Property (#220 generalize vertical): `aura generalize` grades ANY sweepable
|
||||
/// blueprint, not just the historical r-sma candidate — the whole point of
|
||||
/// dropping `--strategy r-sma`/`--fast`/`--slow` for a positional blueprint +
|
||||
/// generic `--axis`. Every other generalize E2E test here still exercises
|
||||
/// `r_sma.json`, so a regression that silently special-cased r-sma paths
|
||||
/// back in (e.g. resolving axes against a hardcoded `sma_signal.*` namespace)
|
||||
/// would ship green there while breaking every other blueprint. This runs the
|
||||
/// unrelated r-breakout candidate (the ganged `channel_length` axis, no
|
||||
/// `sma_signal` node in sight) end-to-end and asserts a well-formed two-
|
||||
/// instrument grade. Gated on the shared GER40/USDJPY Sept-2024 archive; skips
|
||||
/// cleanly on a data refusal.
|
||||
/// Property (#220 generalize vertical, ported onto the campaign
|
||||
/// `std::generalize` stage by #319 Task 8): grades ANY sweepable blueprint,
|
||||
/// not just the historical r-sma candidate — originally the whole point of
|
||||
/// the retired `aura generalize` verb dropping `--strategy r-sma`/`--fast`/
|
||||
/// `--slow` for a positional blueprint + generic `--axis`; the property
|
||||
/// survives the #319 dissolution onto a `std::sweep, std::generalize`
|
||||
/// process document unchanged. Every other generalize E2E test here still
|
||||
/// exercises `r_sma.json`, so a regression that silently special-cased
|
||||
/// r-sma paths back in (e.g. resolving axes against a hardcoded
|
||||
/// `sma_signal.*` namespace) would ship green there while breaking every
|
||||
/// other blueprint. This runs the unrelated r-breakout candidate (the
|
||||
/// ganged `channel_length` axis, no `sma_signal` node in sight) end-to-end
|
||||
/// and asserts a well-formed two-instrument grade. Gated on the shared
|
||||
/// GER40/USDJPY Sept-2024 archive; skips cleanly on a data refusal.
|
||||
#[test]
|
||||
fn generalize_grades_a_non_r_sma_blueprint() {
|
||||
// Ported onto a `[std::sweep, std::generalize]` campaign document
|
||||
@@ -2367,18 +2347,20 @@ fn generalize_grades_a_non_r_sma_blueprint() {
|
||||
}
|
||||
|
||||
/// Characterization pin (byte-identity anchor for the walkforward dissolution,
|
||||
/// #210). The current `aura walkforward` runs its per-window IS-refit inline
|
||||
/// (`walkforward_family` -> `select_winner` -> OOS run -> stitch/pool); the
|
||||
/// dissolution reroutes the same rolling IS-sweep -> OOS-run -> deflation through
|
||||
/// the campaign `std::walk_forward` stage, and must retain the OOS pip curve the
|
||||
/// campaign runner currently discards (`oos_equity: vec![]`) to reproduce
|
||||
/// #210, ported onto the campaign path by #319 Task 8). The original `aura
|
||||
/// walkforward` verb ran its per-window IS-refit inline (`walkforward_family`
|
||||
/// -> `select_winner` -> OOS run -> stitch/pool); the dissolution rerouted the
|
||||
/// same rolling IS-sweep -> OOS-run -> deflation through the campaign
|
||||
/// `std::walk_forward` stage, retaining the OOS pip curve the campaign runner
|
||||
/// used to discard (`oos_equity: vec![]`) so it could still reproduce
|
||||
/// `stitched_total_pips`, plus reconstruct pooled `oos_r`. The multi-point grid
|
||||
/// (fast 3,5 x slow 12,20) makes the per-window winner selection non-degenerate --
|
||||
/// `param_stability` varies across windows -- so a winner-pick divergence between
|
||||
/// the inline and campaign refit cannot ship green. This pins the EXACT current
|
||||
/// grade of the identical invocation over a fixed 2025 GER40 window; after the
|
||||
/// dissolution the same command must reproduce these bytes (the acceptance gate).
|
||||
/// Gated on the shared GER40 archive; skips cleanly on a data refusal.
|
||||
/// the inline and campaign refit could not have shipped green. This pins the
|
||||
/// EXACT grade of the identical invocation over a fixed 2025 GER40 window that
|
||||
/// the dissolved `std::walk_forward` stage reproduces bit-for-bit (the
|
||||
/// acceptance gate the port had to clear). Gated on the shared GER40 archive;
|
||||
/// skips cleanly on a data refusal.
|
||||
#[test]
|
||||
fn walkforward_real_e2e_pins_the_exact_current_grade() {
|
||||
// Ported onto a `[std::grid, std::walk_forward]` campaign document
|
||||
@@ -2453,21 +2435,24 @@ fn walkforward_real_e2e_pins_the_exact_current_grade() {
|
||||
assert_eq!(ps[1]["mean"].as_f64(), Some(17.333333333333332), "slow-MA refit mean: {wf}");
|
||||
}
|
||||
|
||||
/// Property (#220 walkforward vertical): `aura walkforward --real` IS-refits ANY
|
||||
/// sweepable blueprint through the campaign path, not just the historical r-sma
|
||||
/// candidate — the whole point of dropping `--strategy r-sma`/`--fast`/`--slow`
|
||||
/// for a positional blueprint + generic `--axis`. Every other walkforward E2E
|
||||
/// test here still exercises `r_sma.json`; a regression that silently
|
||||
/// special-cased the refit's axis lookup back to a hardcoded `sma_signal.*`
|
||||
/// namespace (e.g. in `walkforward_summary_json_from_reports`'s per-axis
|
||||
/// `raw_matches_wrapped` search) would ship green there while breaking every
|
||||
/// other blueprint. This runs the unrelated r-breakout candidate
|
||||
/// (the ganged `channel_length` axis, no `sma_signal` node in sight) end-to-end
|
||||
/// and asserts a well-formed multi-window summary whose `param_stability` has
|
||||
/// exactly one entry per bound axis (1 ganged blueprint axis + the 2 stop columns).
|
||||
/// The window count (9) is roller/data-derived, not strategy-derived, so it is
|
||||
/// pinned exactly against the same GER40 2025 span the r-sma anchor uses.
|
||||
/// Gated on the shared GER40 archive; skips cleanly on a data refusal.
|
||||
/// Property (#220 walkforward vertical, ported onto the campaign
|
||||
/// `std::walk_forward` stage by #319 Task 8): IS-refits ANY sweepable
|
||||
/// blueprint, not just the historical r-sma candidate — originally the
|
||||
/// whole point of the retired `aura walkforward --real` verb dropping
|
||||
/// `--strategy r-sma`/`--fast`/`--slow` for a positional blueprint + generic
|
||||
/// `--axis`; the property survives the #319 dissolution unchanged. Every
|
||||
/// other walkforward E2E test here still exercises `r_sma.json`; a
|
||||
/// regression that silently special-cased the refit's axis lookup back to a
|
||||
/// hardcoded `sma_signal.*` namespace (e.g. in
|
||||
/// `walkforward_summary_json_from_reports`'s per-axis `raw_matches_wrapped`
|
||||
/// search) would ship green there while breaking every other blueprint.
|
||||
/// This runs the unrelated r-breakout candidate (the ganged `channel_length`
|
||||
/// axis, no `sma_signal` node in sight) end-to-end and asserts a well-formed
|
||||
/// multi-window summary whose `param_stability` has exactly one entry per
|
||||
/// bound axis (1 ganged blueprint axis + the 2 stop columns). The window
|
||||
/// count (9) is roller/data-derived, not strategy-derived, so it is pinned
|
||||
/// exactly against the same GER40 2025 span the r-sma anchor uses. Gated on
|
||||
/// the shared GER40 archive; skips cleanly on a data refusal.
|
||||
#[test]
|
||||
fn walkforward_dissolves_a_non_r_sma_blueprint() {
|
||||
// Ported onto a `[std::grid, std::walk_forward]` campaign document
|
||||
@@ -2515,8 +2500,6 @@ fn walkforward_dissolves_a_non_r_sma_blueprint() {
|
||||
assert_eq!(ps.len(), 3, "one entry per bound axis (channel_length, stop_length, stop_k): {wf}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Characterization pin (#215): the same identical invocation as
|
||||
/// `walkforward_real_e2e_pins_the_exact_current_grade` (fixture, real
|
||||
/// instrument, axes, stop, window), but selecting `plateau:worst` instead of
|
||||
@@ -2597,17 +2580,6 @@ fn walkforward_real_e2e_pins_the_exact_current_plateau_grade() {
|
||||
assert_eq!(ps[1]["mean"].as_f64(), Some(16.444444444444443), "slow-MA refit mean: {wf}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Note (#319 Task 8, disposition finding): `generalize_without_explicit_
|
||||
// window_resolves_a_shared_window_and_completes` and `generalize_without_
|
||||
// explicit_window_resolves_the_intersection_of_all_symbols` retired rather
|
||||
@@ -2626,12 +2598,6 @@ fn walkforward_real_e2e_pins_the_exact_current_plateau_grade() {
|
||||
// window" property stays fully covered by `generalize_grades_a_candidate_
|
||||
// across_two_instruments` and its siblings above.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Property, ported onto a `[std::sweep, std::generalize]` campaign document
|
||||
/// (#319 Task 8 — disposition finding): a successful campaign generalize run
|
||||
/// persists its per-instrument runs as discoverable, but NOT as a merged
|
||||
@@ -2843,7 +2809,6 @@ fn aura_sweep_loads_a_blueprint_and_persists_a_sweep_family() {
|
||||
assert!(fo.contains("\"members\":4"), "2×2 grid -> four members: {fo}");
|
||||
}
|
||||
|
||||
|
||||
/// Property (#158/#164, C18/C11/C12): a blueprint sweep content-addresses its topology
|
||||
/// — the canonical blueprint is persisted ONCE under `runs/blueprints/<topology_hash>.json`,
|
||||
/// keyed by the very SHA256 every member's manifest carries, holding exactly the bytes
|
||||
@@ -3133,8 +3098,6 @@ fn aura_walkforward_over_a_blueprint_persists_a_family() {
|
||||
assert_eq!(n, 1, "exactly one content-addressed blueprint stored");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// E2E (#278 decision: same fault class, same verb, same surface => same
|
||||
/// treatment): a member fault on the SYNTHETIC walk-forward path is CONTAINED
|
||||
/// exactly like the real/campaign path contains it (#272) — it surfaces as an
|
||||
@@ -3218,14 +3181,6 @@ fn aura_sweep_synthetic_member_panic_is_contained() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// E2E (negative): `aura reproduce <unknown>` is a hard error (exit non-zero + named
|
||||
/// cause on stderr), distinct from `runs family <id>`'s treat-as-empty exit 0.
|
||||
#[test]
|
||||
@@ -3275,13 +3230,6 @@ fn aura_reproduce_rejects_a_missing_stored_blueprint() {
|
||||
assert!(out.contains("missing from store"), "names the cause: {out}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// `--version` / `-V` surface the crate version on stdout and exit 0 (GNU MUST):
|
||||
/// a version query is a successful query, not a usage error. The expected string
|
||||
/// is the crate version itself (`CARGO_PKG_VERSION`, i.e. the workspace version),
|
||||
@@ -3335,13 +3283,6 @@ fn version_flag_carries_the_engine_commit_from_manifest_provenance() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Characterization pin (byte-identity anchor for the mc R-bootstrap dissolution,
|
||||
/// #210, the last verb; argv since migrated to the generic form by #220).
|
||||
/// `aura mc <blueprint> --real --axis …` pools the per-window OOS trade-R
|
||||
@@ -3715,8 +3656,6 @@ fn mc_real_fits_the_injected_roller_to_a_short_window() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Property: the one load-bearing decision new to the mc dissolution — `translate_mc`
|
||||
/// maps `campaign.seed` to the mc `--seed` (unlike `translate_walkforward`, which
|
||||
/// hardcodes `seed: 0`) — actually flows through the executor to the emitted grade,
|
||||
@@ -3962,15 +3901,6 @@ fn generalize_campaign_runs_an_arbitrary_blueprint() {
|
||||
assert!(grade["worst_case"].is_number(), "worst-case floor present: {record_line}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Property (#273, list reshape): `aura data list` enumerates the archive's
|
||||
/// symbols as NDJSON, sorted — the discovery step a campaign author (or an
|
||||
/// agent operating through the CLI alone, the deployment posture) needs
|
||||
|
||||
@@ -234,6 +234,30 @@ fn exec_campaign_target_outside_project_refuses() {
|
||||
assert!(!dir.join("runs").exists(), "a refused run must not create a store outside a project");
|
||||
}
|
||||
|
||||
/// A malformed FILE target (a trailing comma — not valid JSON at all) must
|
||||
/// refuse NEUTRALLY at the routing seam, before either leg's own
|
||||
/// document-shape validation ever runs — not silently fall through to the
|
||||
/// blueprint leg's "blueprint document is not valid JSON" prose (review
|
||||
/// Minor-3). The file's shape (blueprint vs. campaign) was never
|
||||
/// determined, so neither leg's own wording is the right attribution.
|
||||
#[test]
|
||||
fn exec_malformed_json_file_refuses_neutrally_not_misrouted() {
|
||||
let (dir, _fixture) = fresh_project();
|
||||
let malformed = r#"{
|
||||
"format_version": 1,
|
||||
"kind": "campaign",
|
||||
"name": "broken",
|
||||
}"#;
|
||||
write_doc(&dir, "broken.campaign.json", malformed);
|
||||
let (out, code) = run_code_in(&dir, &["exec", "broken.campaign.json"]);
|
||||
assert_eq!(code, Some(2), "stdout/stderr: {out}");
|
||||
assert!(out.contains("target file is not valid JSON"), "stdout/stderr: {out}");
|
||||
assert!(
|
||||
!out.contains("blueprint document is not valid JSON"),
|
||||
"must not misattribute to the blueprint leg's own prose: {out}"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Task 2: the exec blueprint leg (synthetic single run).
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -448,7 +472,9 @@ fn exec_override_malformed_token_refuses_with_usage() {
|
||||
}
|
||||
|
||||
/// An override path naming no param of the blueprint (open or bound) refuses
|
||||
/// with `override_paths`' own prose (`member.rs:768-771`).
|
||||
/// with `override_paths`' own prose (`member.rs:768-771`), pointing at the
|
||||
/// live axis-discovery verb (`aura graph introspect --params`) — the retired
|
||||
/// `aura sweep --list-axes` no longer exists.
|
||||
#[test]
|
||||
fn exec_override_unknown_path_refuses_with_the_override_prose() {
|
||||
let (out, code) = run_code(&["exec", "examples/r_sma.json", "--override", "nope.knob=1"]);
|
||||
@@ -457,6 +483,34 @@ fn exec_override_unknown_path_refuses_with_the_override_prose() {
|
||||
out.contains("names no param of this blueprint (open or bound)"),
|
||||
"stdout/stderr: {out}"
|
||||
);
|
||||
assert!(
|
||||
out.contains("aura graph introspect --params"),
|
||||
"must point at the live discovery verb, not the retired --list-axes: {out}"
|
||||
);
|
||||
}
|
||||
|
||||
/// A repeated `--override` path (the same NODE.PARAM named twice) refuses as
|
||||
/// a usage error naming the duplicated path — it must NOT fall through to
|
||||
/// `reopen_all`'s `NoSuchBoundParam` panic (the second reopen of an
|
||||
/// already-reopened bound param, exit 101): the first occurrence reopens
|
||||
/// `fast.length`, so a second `override_paths` resolution against the SAME
|
||||
/// un-reopened bound-name set still succeeds, and only `reopen_all`'s fold
|
||||
/// discovers the collision, too late to refuse cleanly. Mirrors `--tap`'s own
|
||||
/// duplicate refusal (`tap_plan_from_args`).
|
||||
#[test]
|
||||
fn exec_override_duplicate_path_refuses_with_usage_not_a_panic() {
|
||||
let (out, code) = run_code(&[
|
||||
"exec",
|
||||
"examples/r_sma.json",
|
||||
"--override",
|
||||
"fast.length=5",
|
||||
"--override",
|
||||
"fast.length=9",
|
||||
]);
|
||||
assert_ne!(code, Some(101), "must not panic: {out}");
|
||||
assert_eq!(code, Some(2), "stdout/stderr: {out}");
|
||||
assert!(out.contains("fast.length"), "the duplicated path must be named: {out}");
|
||||
assert!(out.contains("--override"), "stdout/stderr: {out}");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -535,6 +589,47 @@ fn exec_campaign_override_colliding_with_a_document_axis_refuses() {
|
||||
assert!(out.contains("declared axis"), "stdout/stderr: {out}");
|
||||
}
|
||||
|
||||
/// A repeated `--override` path on the campaign leg refuses at the SAME
|
||||
/// shared lexer (`parse_override_tokens`) both legs use — before the
|
||||
/// per-strategy injection loop (`run_campaign_returning`) ever runs. Prior
|
||||
/// to the fix, the first occurrence's own injection made the SECOND
|
||||
/// occurrence look like a document-declared axis, misattributing the
|
||||
/// refusal to "collides with a declared axis" (review Minor-1) even though
|
||||
/// `bias.scale` is not declared by the document at all.
|
||||
#[test]
|
||||
fn exec_campaign_override_duplicate_path_refuses_at_the_shared_lexer() {
|
||||
let (dir, _fixture) = fresh_project_with_data();
|
||||
let runs_dir = dir.join("runs");
|
||||
std::fs::remove_dir_all(&runs_dir).ok();
|
||||
let _cleanup = ScratchGuard(vec![
|
||||
ScratchPath::Dir(runs_dir.clone()),
|
||||
ScratchPath::File(dir.join("ovdup.process.json")),
|
||||
ScratchPath::File(dir.join("ovdup.campaign.json")),
|
||||
]);
|
||||
let bp_id = seed_blueprint(&dir, "exec-override-dup-seed");
|
||||
let proc_id = register_process_doc(&dir, "ovdup.process.json", SWEEP_ONLY_PROCESS_DOC);
|
||||
let doc = campaign_doc_json_for("SYMA", &bp_id, &proc_id, (1709251200000, 1719791999999));
|
||||
write_doc(&dir, "ovdup.campaign.json", &doc);
|
||||
|
||||
let (out, code) = run_code_in(
|
||||
&dir,
|
||||
&[
|
||||
"exec",
|
||||
"ovdup.campaign.json",
|
||||
"--override",
|
||||
"bias.scale=0.75",
|
||||
"--override",
|
||||
"bias.scale=0.9",
|
||||
],
|
||||
);
|
||||
assert_eq!(code, Some(2), "stdout/stderr: {out}");
|
||||
assert!(out.contains("bias.scale"), "the duplicated path must be named: {out}");
|
||||
assert!(
|
||||
!out.contains("collides with a declared axis"),
|
||||
"must not misattribute to the axis-collision cause: {out}"
|
||||
);
|
||||
}
|
||||
|
||||
/// An override path naming no param of any strategy (a typo'd raw name)
|
||||
/// falls out of `validate_campaign_refs`'s existing `AxisNotInParamSpace`
|
||||
/// did-you-mean prose — pinned literally in `research_docs.rs` (:797-822);
|
||||
|
||||
@@ -574,7 +574,8 @@ pub enum ReopenError {
|
||||
|
||||
/// One entry of the aggregated BOUND param surface (#246): the path-qualified
|
||||
/// twin of [`ParamSpec`] carrying the bound value — the default a sweep axis
|
||||
/// may override, and what `--list-axes` renders as `default=`.
|
||||
/// may override, and what `aura graph introspect --params` renders as
|
||||
/// `default=`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BoundSpec {
|
||||
pub name: String,
|
||||
@@ -3376,7 +3377,8 @@ mod tests {
|
||||
}
|
||||
|
||||
/// #246: bound_param_space mirrors param_space's path qualification and
|
||||
/// carries the bound value (the default the CLI renders in --list-axes).
|
||||
/// carries the bound value (the default the CLI renders in
|
||||
/// `aura graph introspect --params`).
|
||||
#[test]
|
||||
fn bound_param_space_is_path_qualified_with_values() {
|
||||
use aura_strategy::Bias;
|
||||
|
||||
@@ -64,7 +64,8 @@ pub fn raw_matches_wrapped(raw: &str, wrapped: &str) -> bool {
|
||||
/// instead of refusing it.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum AxisIntake {
|
||||
/// A legal RAW name (`--list-axes`'s own namespace, #328) — accept as-is.
|
||||
/// A legal RAW name (`aura graph introspect --params`'s own namespace,
|
||||
/// #328) — accept as-is.
|
||||
Raw,
|
||||
/// A retired WRAPPED name; the carried string is its translated RAW
|
||||
/// candidate — never applied silently, only ever surfaced in a refusal.
|
||||
|
||||
@@ -659,8 +659,9 @@ pub fn run_blueprint_member(
|
||||
/// against: the loaded signal wrapped in the r-sma scaffolding (stop bound,
|
||||
/// reduce, no cost), taps discarded. `param_space()` on it is the axis
|
||||
/// namespace `--axis` binds; `.axis()` consumes it to seed a sweep. Single
|
||||
/// source for the sweep terminal, the MC closed-check, AND `--list-axes`, so
|
||||
/// the listed names track the swept names by construction (incl. across #159's
|
||||
/// source for the sweep terminal, the MC closed-check, AND
|
||||
/// `aura graph introspect --params`, so the listed names track the swept
|
||||
/// names by construction (incl. across #159's
|
||||
/// harness retirement). The reload is infallible under the SAME
|
||||
/// dispatch-boundary contract the callers already rely on: the doc is
|
||||
/// `blueprint_from_json`-validated at the `["sweep", ..]` / `["mc", ..]`
|
||||
@@ -673,7 +674,8 @@ pub fn blueprint_axis_probe(doc: &str, env: &Env) -> Composite {
|
||||
/// The axis probe with a #246 override set re-opened on the strategy BEFORE
|
||||
/// wrapping — probe and per-member reloads must re-open identically so points
|
||||
/// resolve against one space. The empty-set form is byte-equal to the old
|
||||
/// probe (mc/run closed-checks and the open `--list-axes` lines read that).
|
||||
/// probe (mc/run closed-checks and the open `aura graph introspect --params`
|
||||
/// lines read that).
|
||||
pub fn blueprint_axis_probe_reopened(doc: &str, env: &Env, overrides: &[String]) -> Composite {
|
||||
let signal = blueprint_from_json(doc, &|t| env.resolve(t))
|
||||
.expect("doc parse-validated at the dispatch boundary; reload is infallible");
|
||||
@@ -767,7 +769,7 @@ pub fn override_paths(
|
||||
None => {
|
||||
return Err(format!(
|
||||
"axis {name}: names no param of this blueprint (open or bound) — \
|
||||
see `aura sweep <bp> --list-axes`"
|
||||
see `aura graph introspect --params <bp>`"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,3 +36,18 @@ execution layer is unchanged (arg-plumbing via thin `*_from` adapters). The
|
||||
machine-first help surface (JSON/manifest help, stdin op-scripts) is
|
||||
deferred to the #157 / C21 track, distinct from this human/GNU-convention
|
||||
compliance."
|
||||
|
||||
**Current-state "Single document grammar" section's first-draft routing
|
||||
sentence (superseded by the #319 sugar retirement, 2026-07-25):** "one verb,
|
||||
`exec <target>`, dispatches on `is_blueprint_file` (a first-positional
|
||||
naming an existing `.json` file selects the loaded-blueprint leg; anything
|
||||
else — a campaign file or a registered content id — the campaign leg)." This
|
||||
undersold the routing by one layer: the code always also peeked the file's
|
||||
top-level `"kind"` field (`is_campaign_document_file`) to tell a campaign
|
||||
document ending in `.json` from a blueprint file — both being ordinary
|
||||
readable `.json` files `is_blueprint_file` alone cannot distinguish — and,
|
||||
after an independent review found a malformed campaign file was silently
|
||||
misrouted to the blueprint leg's own JSON-parse error prose, gained a
|
||||
neutral not-valid-JSON refusal ahead of the leg choice (review Minor-3,
|
||||
2026-07-25's fix cycle). The current contract's "Single document grammar"
|
||||
section names this real routing.
|
||||
|
||||
@@ -62,11 +62,16 @@ caller branches on the failure class without parsing stderr.
|
||||
**Single document grammar (#319, 2026-07-25).** The five dual-grammar
|
||||
subcommands (run/sweep/walkforward/mc/generalize) that used to keep two
|
||||
grammars under one token are retired; one verb, `exec <target>`, dispatches
|
||||
on `is_blueprint_file` (a first-positional naming an existing `.json` file
|
||||
selects the loaded-blueprint leg; anything else — a campaign file or a
|
||||
registered content id — the campaign leg). The execution layer this
|
||||
collapses onto is unchanged in kind (arg-plumbing via thin adapters). The
|
||||
machine-first help surface (JSON/manifest help, stdin op-scripts) is
|
||||
on `is_blueprint_file` layered with a "kind"-field peek
|
||||
(`classify_exec_document_file`): a first-positional naming an existing
|
||||
`.json` file that parses as JSON and carries no top-level `"kind"` selects
|
||||
the loaded-blueprint leg; a file carrying `"kind"`, or a target that is not a
|
||||
readable `.json` file at all (a registered content id), selects the campaign
|
||||
leg. A file that does not parse as JSON at all refuses neutrally (exit 2)
|
||||
before either leg's own document-shape validation runs, rather than
|
||||
silently choosing a leg (review Minor-3, 2026-07-25). The execution layer
|
||||
this collapses onto is unchanged in kind (arg-plumbing via thin adapters).
|
||||
The machine-first help surface (JSON/manifest help, stdin op-scripts) is
|
||||
deferred to the #157 / [C21](c21-world.md) track, distinct from this
|
||||
human/GNU-convention compliance.
|
||||
|
||||
|
||||
@@ -34,3 +34,16 @@ scaffolding's last data weld — `wrap_r`'s hard-wired `price`←close role and
|
||||
`M1Field::Close`-only open sites — is retired; a strategy's input roles now bind
|
||||
archive columns by name (C26). `wrap_r`'s remaining R-scaffolding retirement
|
||||
stays #159.]
|
||||
|
||||
**Current-state "Pre-C24 scaffolding retired" paragraph's family-builder
|
||||
naming (superseded by the #319 sugar retirement, 2026-07-25):** "The family
|
||||
builders are now generic and blueprint-driven: `blueprint_sweep_family`,
|
||||
`blueprint_walkforward_family`, `blueprint_mc_family` (and
|
||||
`blueprint_sweep_over`) in `crates/aura-runner/src/family.rs`." Those four
|
||||
functions are themselves retired by #319 (2026-07-25): the campaign executor
|
||||
(`aura_campaign::exec::execute`) builds every family now, driven by the
|
||||
process document's stage pipeline; `family.rs` keeps only the synthetic-
|
||||
stream helpers `aura_runner::reproduce` calls to re-derive a recorded family
|
||||
(`showcase_prices`, `walkforward_prices`, `walkforward_window_source`,
|
||||
`synthetic_walk_sources`, `DataSource`/`DataChoice`). The current contract's
|
||||
"Pre-C24 scaffolding retired" paragraph names this real state.
|
||||
|
||||
@@ -71,13 +71,20 @@ rather than a hand-enumerated menu. Persisted experiment *intent* is the campaig
|
||||
document (C25/C18).
|
||||
|
||||
**Pre-C24 scaffolding retired.** The pre-C24 scaffolding — `HarnessKind` and the
|
||||
per-strategy `*_sweep_family` functions — is **gone**. The family builders are now
|
||||
generic and blueprint-driven: `blueprint_sweep_family`, `blueprint_walkforward_family`,
|
||||
`blueprint_mc_family` (and `blueprint_sweep_over`) in
|
||||
`crates/aura-runner/src/family.rs`. The R-evaluator scaffold `wrap_r`
|
||||
(defined in `crates/aura-runner/src/member.rs`; imported and called from
|
||||
`runner.rs`) survives; its last hard data weld — the
|
||||
hard-wired `price`←close role and the `M1Field::Close`-only open sites — is retired
|
||||
per-strategy `*_sweep_family` functions — is **gone**. The generic blueprint-driven
|
||||
family builders that first replaced it (`blueprint_sweep_family`,
|
||||
`blueprint_walkforward_family`, `blueprint_mc_family`, `blueprint_sweep_over`) are,
|
||||
in turn, retired by the #319 sugar retirement (2026-07-25): the **campaign
|
||||
executor** (`aura_campaign::exec::execute`) now builds every family, driven by
|
||||
the process document's stage pipeline (`std::sweep`/`std::grid`,
|
||||
`std::walk_forward`, `std::monte_carlo`). `crates/aura-runner/src/family.rs`
|
||||
retains only the synthetic-stream helpers serving `aura_runner::reproduce`
|
||||
(`showcase_prices`, `walkforward_prices`, `walkforward_window_source`,
|
||||
`synthetic_walk_sources`, `DataSource`/`DataChoice`) — reproduction's own
|
||||
re-derivation path, not a family-minting path. The R-evaluator scaffold
|
||||
`wrap_r` (defined in `crates/aura-runner/src/member.rs`; imported and called
|
||||
from `runner.rs`) survives; its last hard data weld — the hard-wired
|
||||
`price`←close role and the `M1Field::Close`-only open sites — is retired
|
||||
(a strategy's input roles now bind archive columns by name, C26), and `wrap_r`'s
|
||||
remaining R-scaffolding retirement is tracked at #159.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user