feat(cli,docs): ship the ganged open examples + document the gang construct (#61 tasks 7-8)
The two open examples now expose their author-intended single knobs — the closed builders always bound these pairs to one value by hand; the open forms falsely offered them as independent axes: - r_breakout_open: channel_hi.length + channel_lo.length -> channel_length (the Donchian channel is structurally ONE parameter) - r_meanrev_open: mean_window.length + var_window.length -> window; the band factor stays an independent axis Regenerated via the emitters (never hand-edited); the closed examples are byte-unchanged. The carve builders gang in the open branch only. Test migration: the two axis-namespace pins, five --real e2e invocations (single gang axis, the 10,20 diagonal for the campaign pair — the mismatched 20,40/10,20 grid was exactly the configuration space the gang retires), and the param_stability row counts (4 -> 3). The r-sma walkforward golden anchor is untouched (it never swept a ganged pair). Docs: authoring-guide gains the seventh op + the third param state (open/bound/ganged) + the gang wrap note; README op list + Axis concept; glossary gang entry; ledger C24 records the gang verb and the pre-ship Tier-2 dormancy (no format-version bump while no out-of-repo reader exists). Verified: full workspace suite 1104/0 (--real e2e included, local data present), clippy -D warnings clean, cargo doc clean; live acceptance: introspect --params prints channel_length:I64 alone / window:I64 + band.factor:F64. closes #61
This commit is contained in:
@@ -744,7 +744,8 @@ fn shipped_r_breakout_example_is_genuinely_closed() {
|
||||
}
|
||||
|
||||
/// Property (#159 cut 2): the shipped open example (`examples/r_breakout_open.json`)
|
||||
/// lists its two channel axes, in lowering order (per-node exposure; ganging is #61).
|
||||
/// lists its ONE ganged channel axis (#61: the two rolling windows are structurally
|
||||
/// one knob).
|
||||
#[test]
|
||||
fn shipped_r_breakout_open_example_lists_its_axis_namespace() {
|
||||
let dir = temp_cwd("r-breakout-example-open-params");
|
||||
@@ -752,8 +753,47 @@ fn shipped_r_breakout_open_example_lists_its_axis_namespace() {
|
||||
run_in(&dir, &["graph", "introspect", "--params", &example("r_breakout_open.json")]);
|
||||
assert_eq!(code, Some(0), "stdout: {stdout} stderr: {stderr}");
|
||||
assert_eq!(
|
||||
stdout, "channel_hi.length:I64\nchannel_lo.length:I64\n",
|
||||
"the raw open params, in order, from the public gallery copy"
|
||||
stdout, "channel_length:I64\n",
|
||||
"the ganged channel knob — one public axis for the two rolling windows (#61)"
|
||||
);
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// `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
|
||||
/// CLI's own axis-name resolution or grid-point binding (e.g. silently
|
||||
/// treating the gang as an unbound param, or fanning the value to only one
|
||||
/// member) would be caught here even if the internal builder-level
|
||||
/// equivalence still holds.
|
||||
#[test]
|
||||
fn shipped_r_breakout_open_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, &["run", &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 sweep_dir = temp_cwd("r-breakout-gang-axis-sweep");
|
||||
let (sweep_stdout, sweep_stderr, sweep_code) = run_in(
|
||||
&sweep_dir,
|
||||
&["sweep", &example("r_breakout_open.json"), "--axis", "r_breakout_signal.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");
|
||||
|
||||
assert_eq!(
|
||||
member["report"]["metrics"], closed["metrics"],
|
||||
"channel_length=3, bound via the real sweep CLI, must fan out to both channel_hi \
|
||||
and channel_lo identically to the closed example's hardwired channel=3"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -777,8 +817,45 @@ fn shipped_r_meanrev_open_example_lists_its_axis_namespace() {
|
||||
run_in(&dir, &["graph", "introspect", "--params", &example("r_meanrev_open.json")]);
|
||||
assert_eq!(code, Some(0), "stdout: {stdout} stderr: {stderr}");
|
||||
assert_eq!(
|
||||
stdout, "mean_window.length:I64\nvar_window.length:I64\nband.factor:F64\n",
|
||||
"the raw open params, in lowering order"
|
||||
stdout, "window:I64\nband.factor:F64\n",
|
||||
"the ganged EWMA window + the independent band knob, in lowering order"
|
||||
);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[test]
|
||||
fn shipped_r_meanrev_open_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, &["run", &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 sweep_dir = temp_cwd("r-meanrev-gang-axis-sweep");
|
||||
let (sweep_stdout, sweep_stderr, sweep_code) = run_in(
|
||||
&sweep_dir,
|
||||
&[
|
||||
"sweep", &example("r_meanrev_open.json"),
|
||||
"--axis", "r_meanrev_signal.window=3",
|
||||
"--axis", "r_meanrev_signal.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");
|
||||
|
||||
assert_eq!(
|
||||
member["report"]["metrics"], closed["metrics"],
|
||||
"window=3 (ganged) + band.factor=2.0 (plain), bound via the real sweep CLI, must \
|
||||
match the closed example's hardwired window=3/band=2.0"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user