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:
2026-07-10 14:07:22 +02:00
parent bc88e18247
commit fc9cf23b87
9 changed files with 149 additions and 37 deletions
+16 -21
View File
@@ -1739,7 +1739,7 @@ fn generalize_real_e2e_pins_the_exact_current_grade() {
/// `r_sma_open.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 (`channel_hi`/`channel_lo` axes, no
/// 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.
@@ -1752,8 +1752,7 @@ fn generalize_grades_a_non_r_sma_blueprint() {
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"generalize", &fixture, "--real", "GER40,USDJPY",
"--axis", "r_breakout_signal.channel_hi.length=20",
"--axis", "r_breakout_signal.channel_lo.length=20",
"--axis", "r_breakout_signal.channel_length=20",
"--stop-length", "14", "--stop-k", "2.0",
"--from", FROM_MS, "--to", TO_MS,
])
@@ -1848,9 +1847,9 @@ fn walkforward_real_e2e_pins_the_exact_current_grade() {
/// 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
/// (`channel_hi`/`channel_lo` axes, no `sma_signal` node in sight) end-to-end
/// (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 (2 blueprint axes + the 2 stop columns).
/// 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.
@@ -1863,8 +1862,7 @@ fn walkforward_dissolves_a_non_r_sma_blueprint() {
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"walkforward", &fixture, "--real", "GER40",
"--axis", "r_breakout_signal.channel_hi.length=10,20",
"--axis", "r_breakout_signal.channel_lo.length=10,20",
"--axis", "r_breakout_signal.channel_length=10,20",
"--stop-length", "14", "--stop-k", "2.0",
"--from", FROM_MS, "--to", TO_MS,
])
@@ -1891,7 +1889,7 @@ fn walkforward_dissolves_a_non_r_sma_blueprint() {
assert_eq!(wf["windows"].as_u64(), Some(9), "window count (data/roller-derived): {grade_line}");
assert!(wf["oos_r"]["n_trades"].as_u64().is_some(), "pooled OOS trade count present: {grade_line}");
let ps = wf["param_stability"].as_array().expect("param_stability is an array");
assert_eq!(ps.len(), 4, "one entry per bound axis (channel_hi, channel_lo, stop_length, stop_k): {grade_line}");
assert_eq!(ps.len(), 3, "one entry per bound axis (channel_length, stop_length, stop_k): {grade_line}");
}
/// Fork A: the dissolved walkforward binds the stop as a single risk regime, so a
@@ -3882,7 +3880,7 @@ fn mc_r_bootstrap_real_e2e_pins_the_exact_current_grade() {
/// exercises `r_sma_open.json`; a regression that silently special-cased the
/// pipeline's axis lookup back to a hardcoded `sma_signal.*` namespace would
/// ship green there while breaking every other blueprint. This runs the
/// unrelated r-breakout candidate (`channel_hi`/`channel_lo` axes, no
/// unrelated r-breakout candidate (the ganged `channel_length` axis, no
/// `sma_signal` node in sight) end-to-end and asserts a well-formed
/// `mc_r_bootstrap` grade: the argv-echoed knobs (`block_len`/`n_resamples`)
/// pin exactly, `n_trades`/`e_r.mean`/`prob_le_zero` are asserted present and
@@ -3898,8 +3896,7 @@ fn mc_dissolves_a_non_r_sma_blueprint() {
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"mc", &fixture, "--real", "GER40",
"--axis", "r_breakout_signal.channel_hi.length=10,20",
"--axis", "r_breakout_signal.channel_lo.length=10,20",
"--axis", "r_breakout_signal.channel_length=10,20",
"--stop-length", "14", "--stop-k", "2.0",
"--block-len", "5", "--resamples", "1000", "--seed", "42",
"--from", FROM_MS, "--to", TO_MS,
@@ -4175,8 +4172,7 @@ fn walkforward_campaign_runs_an_arbitrary_blueprint() {
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"walkforward", &fixture, "--real", "GER40",
"--axis", "r_breakout_signal.channel_hi.length=20,40",
"--axis", "r_breakout_signal.channel_lo.length=10,20",
"--axis", "r_breakout_signal.channel_length=10,20",
"--stop-length", "3", "--stop-k", "2.0",
"--from", FROM_MS, "--to", TO_MS,
])
@@ -4207,9 +4203,9 @@ fn walkforward_campaign_runs_an_arbitrary_blueprint() {
let v: serde_json::Value = serde_json::from_str(grade_line).expect("summary line parses as JSON");
let wf = &v["walkforward"];
assert!(wf["windows"].as_u64().is_some_and(|w| w >= 1), "at least one window: {grade_line}");
// param_stability rows: channel_hi, channel_lo, stop_length, stop_k.
// param_stability rows: channel_length, stop_length, stop_k.
let ps = wf["param_stability"].as_array().expect("param_stability is an array");
assert_eq!(ps.len(), 4, "two channel axes + the stop pair: {grade_line}");
assert_eq!(ps.len(), 3, "one ganged channel axis + the stop pair: {grade_line}");
}
/// #220 weld-gone proof for mc: the R-bootstrap pipeline runs the same
@@ -4227,8 +4223,7 @@ fn mc_campaign_runs_an_arbitrary_blueprint() {
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"mc", &fixture, "--real", "GER40",
"--axis", "r_breakout_signal.channel_hi.length=20,40",
"--axis", "r_breakout_signal.channel_lo.length=10,20",
"--axis", "r_breakout_signal.channel_length=10,20",
"--stop-length", "3", "--stop-k", "2.0",
"--block-len", "5", "--resamples", "100", "--seed", "7",
"--from", FROM_MS, "--to", TO_MS,
@@ -4263,8 +4258,9 @@ fn mc_campaign_runs_an_arbitrary_blueprint() {
}
/// #220 weld-gone proof for generalize: one mean-reversion candidate graded
/// across two instruments — three axes with names the CLI has never heard of,
/// including an F64 axis. Gated on the GER40/USDJPY Sept-2024 archive.
/// across two instruments — two axes with names the CLI has never heard of
/// one ganged I64 window and an independent F64 band. Gated on the GER40/USDJPY
/// Sept-2024 archive.
#[test]
fn generalize_campaign_runs_an_arbitrary_blueprint() {
if !local_data_present() {
@@ -4276,8 +4272,7 @@ fn generalize_campaign_runs_an_arbitrary_blueprint() {
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"generalize", &fixture, "--real", "GER40,USDJPY",
"--axis", "r_meanrev_signal.mean_window.length=20",
"--axis", "r_meanrev_signal.var_window.length=20",
"--axis", "r_meanrev_signal.window=20",
"--axis", "r_meanrev_signal.band.factor=2.0",
"--stop-length", "3", "--stop-k", "2.0",
"--from", GER40_SEPT2024_FROM_MS, "--to", GER40_SEPT2024_TO_MS,