feat(aura-cli): surviving-surface prose — scaffold template, concepts help, sweep-vehicle tests
Slice 5 of the #319 retirement. The scaffolder's project CLAUDE.md and the CLI concepts help now teach only the surviving surface (exec over both document classes, --override, graph introspect --params for discovery; traces via exec --tap / presentation.persist_taps). Sweep vehicles moved onto campaign documents driven by exec (project_new's starter quickstarts, cli_broken_pipe — whose bare-sweep EPIPE probe was latently vacuous and now streams a real 4-member campaign, project_sweep_campaign's real-data leg, graph_construct's gang-axis parity pair as paired one-cell campaigns); pure discovery sites became graph introspect --params, and the line-identity test kept its literal --params expectations minus the sweep half. Help pins rewritten; the scaffold template's in-src pin follows the new bytes. refs #319
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
mod common;
|
||||
use common::{fresh_project_with_data, ScratchGuard, ScratchPath};
|
||||
|
||||
const BIN: &str = env!("CARGO_BIN_EXE_aura");
|
||||
|
||||
/// Run `aura <args>` with `stdin_doc` piped in; return (stdout, stderr, success).
|
||||
@@ -714,26 +717,25 @@ fn graph_params_lists_raw_axis_namespace() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Property (#328, cycle-close tidy fix): `graph introspect --params` and
|
||||
/// `aura sweep --list-axes` are two independently-dispatched discovery
|
||||
/// surfaces over the SAME raw axis namespace — the open pass shares one
|
||||
/// wrap-strip convention, the bound pass shares one `bound_param_space()` +
|
||||
/// `render_value` lexicon (`list_blueprint_axes`, main.rs; `params_lines`,
|
||||
/// this crate). Pinned here as a same-fixture LINE EQUALITY (not just two
|
||||
/// independently-asserted shapes) so the duplicated formatting cannot
|
||||
/// silently drift apart across an edit to either surface.
|
||||
/// Property (#328, #319 retirement residue): `graph introspect --params`
|
||||
/// prints the raw axis namespace — one `{name}:{kind:?}` line per open param,
|
||||
/// then each bound param with its default (`list_blueprint_axes`, main.rs;
|
||||
/// `params_lines`, this crate). This test used to also pin `aura sweep
|
||||
/// --list-axes` as a byte-identical SECOND discovery surface over the same
|
||||
/// namespace (guarding the two independently-dispatched surfaces against
|
||||
/// drift); `sweep` is retired, so only `graph introspect --params`'s own
|
||||
/// literal output survives here — see `graph_params_lists_raw_axis_namespace`
|
||||
/// above for the sibling pin over the same fixture.
|
||||
#[test]
|
||||
fn graph_params_and_sweep_list_axes_are_line_identical() {
|
||||
let dir = temp_cwd("params-vs-list-axes");
|
||||
fn graph_params_prints_raw_axis_names() {
|
||||
let dir = temp_cwd("params-raw-axis-names");
|
||||
let bp = fixture("r_sma_open.json");
|
||||
let (params_out, params_err, params_code) =
|
||||
run_in(&dir, &["graph", "introspect", "--params", &bp]);
|
||||
assert_eq!(params_code, Some(0), "graph introspect --params: {params_out} {params_err}");
|
||||
let (axes_out, axes_err, axes_code) = run_in(&dir, &["sweep", &bp, "--list-axes"]);
|
||||
assert_eq!(axes_code, Some(0), "sweep --list-axes: {axes_out} {axes_err}");
|
||||
assert_eq!(
|
||||
params_out, axes_out,
|
||||
"the two discovery surfaces must print byte-identical lines for the same blueprint"
|
||||
params_out, "fast.length:I64\nslow.length:I64\nbias.scale:F64 default=0.5\n",
|
||||
"the raw open params, then the raw bound param with its default"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1262,13 +1264,85 @@ fn open_r_breakout_fixture_lists_its_axis_namespace() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The minimal executable pipeline (one sweep stage) — copied verbatim from
|
||||
/// `research_docs.rs`'s constant of the same name.
|
||||
const SWEEP_ONLY_PROCESS_DOC: &str = r#"{
|
||||
"format_version": 1,
|
||||
"kind": "process",
|
||||
"name": "sweep-only",
|
||||
"pipeline": [ { "block": "std::sweep", "metric": "sqn_normalized", "select": "argmax" } ]
|
||||
}"#;
|
||||
|
||||
/// Register `doc` as a process document in `dir`'s project store; returns its
|
||||
/// id — copied verbatim from `research_docs.rs`'s recipe of the same name.
|
||||
fn register_process_doc(dir: &std::path::Path, file: &str, doc: &str) -> String {
|
||||
std::fs::write(dir.join(file), doc).expect("write process doc");
|
||||
let (out, err, code) = run_in(dir, &["process", "register", file]);
|
||||
assert_eq!(code, Some(0), "process register failed: {out} {err}");
|
||||
out.lines()
|
||||
.find(|l| l.starts_with("registered process "))
|
||||
.expect("register line")
|
||||
.trim_start_matches("registered process ")
|
||||
.split(' ')
|
||||
.next()
|
||||
.expect("id")
|
||||
.trim_start_matches("content:")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// A one-strategy campaign document over `bp_id`/`proc_id`, with a raw
|
||||
/// `axes_json` object literal spliced in verbatim — a campaign document
|
||||
/// refuses an empty `axes` map ("axes is empty"), so a "closed" comparison
|
||||
/// cell (nothing to vary) passes a single-value axis over one of its OWN
|
||||
/// already-bound params at its own default (a no-op reopen, #246: every
|
||||
/// bound param is an equally re-openable axis). #319: campaigns are `exec`'s
|
||||
/// surviving surface for binding a genuinely OPEN param to a value —
|
||||
/// `--override` only reopens an already-BOUND one
|
||||
/// (`aura_runner::member::override_paths` skips a name already in the open
|
||||
/// space), so the gang-axis parity tests below run both the closed example
|
||||
/// and its open-plus-axis twin through this SAME campaign machinery, over
|
||||
/// the same synthetic archive window, keeping their metrics directly
|
||||
/// comparable.
|
||||
fn one_cell_campaign_doc(bp_id: &str, proc_id: &str, window: (i64, i64), axes_json: &str) -> String {
|
||||
format!(
|
||||
r#"{{
|
||||
"format_version": 1,
|
||||
"kind": "campaign",
|
||||
"name": "gang-axis-vehicle",
|
||||
"data": {{ "instruments": ["SYMA"], "windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
|
||||
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }}, "axes": {{{axes_json}}} }} ],
|
||||
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
|
||||
"seed": 7,
|
||||
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
|
||||
}}"#,
|
||||
from = window.0,
|
||||
to = window.1,
|
||||
)
|
||||
}
|
||||
|
||||
/// Execs a one-cell campaign document (written to `dir.join(file)`) and
|
||||
/// returns its single member's `report.metrics`.
|
||||
fn one_cell_campaign_metrics(dir: &std::path::Path, file: &str) -> serde_json::Value {
|
||||
let (out, err, code) = run_in(dir, &["exec", file]);
|
||||
assert_eq!(code, Some(0), "campaign exec failed: {out} {err}");
|
||||
let line = out
|
||||
.lines()
|
||||
.find(|l| l.starts_with(r#"{"family_id":"#))
|
||||
.unwrap_or_else(|| panic!("no member line: {out} {err}"));
|
||||
let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON");
|
||||
v["report"]["metrics"].clone()
|
||||
}
|
||||
|
||||
/// Property (#61 + #159 cut 2): binding the shipped open example's ganged
|
||||
/// `channel_length` axis through the REAL `aura sweep` CLI pipeline (argv ->
|
||||
/// `parse_axes` -> `compile_with_cells`'s gang expansion) reproduces the exact
|
||||
/// metrics of running the shipped CLOSED example, whose `channel_hi`/
|
||||
/// `channel_lo` are separately hardwired to the same value. The equivalence
|
||||
/// between a gang and its member-bound twin is already pinned at the builder/
|
||||
/// engine level (aura-engine's `gang_e2e.rs`, and this crate's
|
||||
/// `channel_length` axis through a REAL campaign axis (#319: the surviving
|
||||
/// surface — `compile_with_cells`'s gang expansion, same as the retired
|
||||
/// `aura sweep --axis`) reproduces the exact metrics of running the shipped
|
||||
/// CLOSED example, whose `channel_hi`/`channel_lo` are separately hardwired
|
||||
/// to the same value. Both legs run as one-cell campaigns over the same
|
||||
/// synthetic archive window, so the comparison is data-source-agnostic (only
|
||||
/// the axis-vs-hardwire binding differs). The equivalence between a gang and
|
||||
/// its member-bound twin is already pinned at the builder/engine level
|
||||
/// (aura-engine's `gang_e2e.rs`, and this crate's
|
||||
/// `r_breakout_example_loaded_runs_identically_to_the_carved_signal`, which
|
||||
/// calls `run_signal_r` directly); this instead drives the actual public
|
||||
/// binary end-to-end on the actual shipped files, so a regression in the
|
||||
@@ -1278,25 +1352,53 @@ fn open_r_breakout_fixture_lists_its_axis_namespace() {
|
||||
/// equivalence still holds.
|
||||
#[test]
|
||||
fn open_r_breakout_fixture_gang_axis_matches_the_closed_example() {
|
||||
let closed_dir = temp_cwd("r-breakout-gang-axis-closed");
|
||||
let (closed_stdout, closed_stderr, closed_code) = run_in(&closed_dir, &["exec", &example("r_breakout.json")]);
|
||||
assert_eq!(closed_code, Some(0), "closed run stderr: {closed_stderr}");
|
||||
let closed: serde_json::Value =
|
||||
serde_json::from_str(closed_stdout.trim()).expect("closed run report parses as JSON");
|
||||
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),
|
||||
ScratchPath::File(dir.join("breakout-gang.process.json")),
|
||||
ScratchPath::File(dir.join("breakout-gang-closed.campaign.json")),
|
||||
ScratchPath::File(dir.join("breakout-gang-open.campaign.json")),
|
||||
]);
|
||||
|
||||
let sweep_dir = temp_cwd("r-breakout-gang-axis-sweep");
|
||||
let (sweep_stdout, sweep_stderr, sweep_code) = run_in(
|
||||
&sweep_dir,
|
||||
&["sweep", &fixture("r_breakout_open.json"), "--axis", "channel_length=3"],
|
||||
);
|
||||
assert_eq!(sweep_code, Some(0), "sweep stderr: {sweep_stderr}");
|
||||
let lines: Vec<&str> = sweep_stdout.lines().collect();
|
||||
assert_eq!(lines.len(), 1, "one grid point for a single-value axis: {sweep_stdout}");
|
||||
let member: serde_json::Value = serde_json::from_str(lines[0]).expect("member line parses as JSON");
|
||||
let (reg_closed_out, reg_closed_err, reg_closed_code) =
|
||||
run_in(&dir, &["graph", "register", &example("r_breakout.json")]);
|
||||
assert_eq!(reg_closed_code, Some(0), "register closed: {reg_closed_out} {reg_closed_err}");
|
||||
let closed_id = registered_id(®_closed_out);
|
||||
|
||||
let (reg_open_out, reg_open_err, reg_open_code) =
|
||||
run_in(&dir, &["graph", "register", &fixture("r_breakout_open.json")]);
|
||||
assert_eq!(reg_open_code, Some(0), "register open: {reg_open_out} {reg_open_err}");
|
||||
let open_id = registered_id(®_open_out);
|
||||
|
||||
let proc_id = register_process_doc(&dir, "breakout-gang.process.json", SWEEP_ONLY_PROCESS_DOC);
|
||||
|
||||
// The shared SYMA window used throughout the suite's campaign vehicles
|
||||
// (inside `fresh_project_with_data`'s Jan-Aug 2024 synthetic archive).
|
||||
let window = (1709251200000, 1719791999999);
|
||||
std::fs::write(
|
||||
dir.join("breakout-gang-closed.campaign.json"),
|
||||
// No-op axis: `delay.lag` reopened at its own bound default (1).
|
||||
one_cell_campaign_doc(&closed_id, &proc_id, window, r#""delay.lag": { "kind": "I64", "values": [1] }"#),
|
||||
)
|
||||
.expect("write closed campaign doc");
|
||||
std::fs::write(
|
||||
dir.join("breakout-gang-open.campaign.json"),
|
||||
one_cell_campaign_doc(
|
||||
&open_id,
|
||||
&proc_id,
|
||||
window,
|
||||
r#""channel_length": { "kind": "I64", "values": [3] }"#,
|
||||
),
|
||||
)
|
||||
.expect("write open campaign doc");
|
||||
|
||||
let closed_metrics = one_cell_campaign_metrics(&dir, "breakout-gang-closed.campaign.json");
|
||||
let open_metrics = one_cell_campaign_metrics(&dir, "breakout-gang-open.campaign.json");
|
||||
assert_eq!(
|
||||
member["report"]["metrics"], closed["metrics"],
|
||||
"channel_length=3, bound via the real sweep CLI, must fan out to both channel_hi \
|
||||
open_metrics, closed_metrics,
|
||||
"channel_length=3, bound via the campaign axis, must fan out to both channel_hi \
|
||||
and channel_lo identically to the closed example's hardwired channel=3"
|
||||
);
|
||||
}
|
||||
@@ -1330,39 +1432,69 @@ fn open_r_meanrev_fixture_lists_its_axis_namespace() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Property (#61 + #159 cut 3): a sweep over TWO axes at once — the ganged
|
||||
/// `window` knob (fusing `mean_window.length`/`var_window.length`) alongside
|
||||
/// the independent, un-ganged `band.factor` knob — binds both correctly
|
||||
/// through the real CLI grid, matching the shipped closed example's hardwired
|
||||
/// window=3/band=2.0. This is a distinct risk from the r_breakout gang test
|
||||
/// (a lone gang axis): a mis-scoped gang-expansion map that shifts sibling
|
||||
/// param positions could silently mis-bind the co-present un-ganged axis
|
||||
/// instead (or vice-versa) even though each axis alone still resolves.
|
||||
/// Property (#61 + #159 cut 3): a campaign over TWO axes at once (#319: the
|
||||
/// surviving surface for what `aura sweep --axis` used to do inline) — the
|
||||
/// ganged `window` knob (fusing `mean_window.length`/`var_window.length`)
|
||||
/// alongside the independent, un-ganged `band.factor` knob — binds both
|
||||
/// correctly through the real CLI grid, matching the shipped closed
|
||||
/// example's hardwired window=3/band=2.0. This is a distinct risk from the
|
||||
/// r_breakout gang test (a lone gang axis): a mis-scoped gang-expansion map
|
||||
/// that shifts sibling param positions could silently mis-bind the
|
||||
/// co-present un-ganged axis instead (or vice-versa) even though each axis
|
||||
/// alone still resolves. Both legs run as one-cell campaigns over the same
|
||||
/// synthetic archive window, so the comparison is data-source-agnostic.
|
||||
#[test]
|
||||
fn open_r_meanrev_fixture_gang_plus_plain_axis_matches_the_closed_example() {
|
||||
let closed_dir = temp_cwd("r-meanrev-gang-axis-closed");
|
||||
let (closed_stdout, closed_stderr, closed_code) = run_in(&closed_dir, &["exec", &example("r_meanrev.json")]);
|
||||
assert_eq!(closed_code, Some(0), "closed run stderr: {closed_stderr}");
|
||||
let closed: serde_json::Value =
|
||||
serde_json::from_str(closed_stdout.trim()).expect("closed run report parses as JSON");
|
||||
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),
|
||||
ScratchPath::File(dir.join("meanrev-gang.process.json")),
|
||||
ScratchPath::File(dir.join("meanrev-gang-closed.campaign.json")),
|
||||
ScratchPath::File(dir.join("meanrev-gang-open.campaign.json")),
|
||||
]);
|
||||
|
||||
let sweep_dir = temp_cwd("r-meanrev-gang-axis-sweep");
|
||||
let (sweep_stdout, sweep_stderr, sweep_code) = run_in(
|
||||
&sweep_dir,
|
||||
&[
|
||||
"sweep", &fixture("r_meanrev_open.json"),
|
||||
"--axis", "window=3",
|
||||
"--axis", "band.factor=2.0",
|
||||
],
|
||||
);
|
||||
assert_eq!(sweep_code, Some(0), "sweep stderr: {sweep_stderr}");
|
||||
let lines: Vec<&str> = sweep_stdout.lines().collect();
|
||||
assert_eq!(lines.len(), 1, "one grid point for two single-value axes: {sweep_stdout}");
|
||||
let member: serde_json::Value = serde_json::from_str(lines[0]).expect("member line parses as JSON");
|
||||
let (reg_closed_out, reg_closed_err, reg_closed_code) =
|
||||
run_in(&dir, &["graph", "register", &example("r_meanrev.json")]);
|
||||
assert_eq!(reg_closed_code, Some(0), "register closed: {reg_closed_out} {reg_closed_err}");
|
||||
let closed_id = registered_id(®_closed_out);
|
||||
|
||||
let (reg_open_out, reg_open_err, reg_open_code) =
|
||||
run_in(&dir, &["graph", "register", &fixture("r_meanrev_open.json")]);
|
||||
assert_eq!(reg_open_code, Some(0), "register open: {reg_open_out} {reg_open_err}");
|
||||
let open_id = registered_id(®_open_out);
|
||||
|
||||
let proc_id = register_process_doc(&dir, "meanrev-gang.process.json", SWEEP_ONLY_PROCESS_DOC);
|
||||
|
||||
let window = (1709251200000, 1719791999999);
|
||||
std::fs::write(
|
||||
dir.join("meanrev-gang-closed.campaign.json"),
|
||||
// No-op axis: `mean_window.length` reopened at its own bound default (3).
|
||||
one_cell_campaign_doc(
|
||||
&closed_id,
|
||||
&proc_id,
|
||||
window,
|
||||
r#""mean_window.length": { "kind": "I64", "values": [3] }"#,
|
||||
),
|
||||
)
|
||||
.expect("write closed campaign doc");
|
||||
std::fs::write(
|
||||
dir.join("meanrev-gang-open.campaign.json"),
|
||||
one_cell_campaign_doc(
|
||||
&open_id,
|
||||
&proc_id,
|
||||
window,
|
||||
r#""window": { "kind": "I64", "values": [3] }, "band.factor": { "kind": "F64", "values": [2.0] }"#,
|
||||
),
|
||||
)
|
||||
.expect("write open campaign doc");
|
||||
|
||||
let closed_metrics = one_cell_campaign_metrics(&dir, "meanrev-gang-closed.campaign.json");
|
||||
let open_metrics = one_cell_campaign_metrics(&dir, "meanrev-gang-open.campaign.json");
|
||||
assert_eq!(
|
||||
member["report"]["metrics"], closed["metrics"],
|
||||
"window=3 (ganged) + band.factor=2.0 (plain), bound via the real sweep CLI, must \
|
||||
open_metrics, closed_metrics,
|
||||
"window=3 (ganged) + band.factor=2.0 (plain), bound via the campaign axes, must \
|
||||
match the closed example's hardwired window=3/band=2.0"
|
||||
);
|
||||
}
|
||||
@@ -2076,10 +2208,10 @@ fn graph_build_use_docless_source_refuses_with_the_c29_shape_exit_1() {
|
||||
|
||||
/// The worked acceptance flow's last leg (spec §Concrete code shapes): a
|
||||
/// pattern registered with an open param splices under an instance, and
|
||||
/// `aura sweep --list-axes` on the CONSUMER shows the path-qualified bare
|
||||
/// (unbound) axis `graph.<instance>.<node>.<param>:<kind>` — the existing
|
||||
/// nested-composite param-prefix discipline, reached through `use` this
|
||||
/// time, not a Rust-authored nested composite.
|
||||
/// `aura graph introspect --params` on the CONSUMER shows the path-qualified
|
||||
/// bare (unbound) axis `graph.<instance>.<node>.<param>:<kind>` — the
|
||||
/// existing nested-composite param-prefix discipline, reached through `use`
|
||||
/// this time, not a Rust-authored nested composite.
|
||||
#[test]
|
||||
fn graph_build_use_end_to_end_axes() {
|
||||
let dir = temp_cwd("use-end-to-end-axes");
|
||||
@@ -2114,8 +2246,8 @@ fn graph_build_use_end_to_end_axes() {
|
||||
std::fs::write(&consumer_bp, &build.stdout).expect("write consumer envelope");
|
||||
|
||||
let (axes_out, axes_err, axes_code) =
|
||||
run_in(&dir, &["sweep", consumer_bp.to_str().unwrap(), "--list-axes"]);
|
||||
assert_eq!(axes_code, Some(0), "list-axes: {axes_out} {axes_err}");
|
||||
run_in(&dir, &["graph", "introspect", "--params", consumer_bp.to_str().unwrap()]);
|
||||
assert_eq!(axes_code, Some(0), "graph introspect --params: {axes_out} {axes_err}");
|
||||
assert_eq!(
|
||||
axes_out, "trend.sma.length:I64\n",
|
||||
"the spliced instance's open param surfaces path-qualified, bare (unbound) RAW form (#328)"
|
||||
|
||||
Reference in New Issue
Block a user