Generalize generalize/walkforward/mc to arbitrary blueprint axes; dissolve RGrid #220

Closed
opened 2026-07-07 23:01:44 +02:00 by Brummel · 3 comments
Owner

Motivation

In a topology-as-data world (C16/C24, the engine ships no project signals and
the World runs opaque blueprint data), the orchestration verbs must not know
any one strategy's parameter names. Three of the four dissolved verbs —
generalize, walkforward, mc — still do: they are structurally welded to the
retired r-sma demo (blueprint name sma_signal), hardcoding its two SMA
node-names fast/slow as their only signal axes and embedding its blueprint
literally. This is the last hardwired-harness residue #159 set out to remove;
#210's milestone-close already scoped these r-sma-specific verb front-ends to
that retirement. sweep is the proof it can be generic — it already takes
arbitrary --axis name=csv.

The weld

fast/slow are not a "candidate vocabulary": they are the node labels of one
topology. The sibling demos expose disjoint axis namespaces —
r_breakoutchannel_hi.length/channel_lo.length,
r_meanrevmean_window.length/var_window.length/band.factor. No axis
name is shared. The bind-key "fast.length" is literally <node-name>.length
of crates/aura-cli/examples/r_sma_open.json (two SMA primitives named
fast/slow, wired fast − slow via Sub into Bias).

The literals are re-asserted at ~13 sites across the three verbs (four layers
each), never derived from blueprint introspection:

  • clap command structs — GeneralizeCmd/WalkforwardCmd/McCmd
    (crates/aura-cli/src/main.rs:3081/3199/3241);
  • the *_args_from extractors (main.rs:3372/3411/3440) and the RGrid struct
    (main.rs:1100);
  • the translate_* doc-axis inserts and RiskRegime::Vol
    (crates/aura-cli/src/verb_sugar.rs:164/168, 409/413, 568/572; risk at
    188/431/590);
  • the probe_params pre-register bind check (verb_sugar.rs:325/480/643);
  • plus the blueprint itself, hardcoded via
    include_str!("../examples/r_sma_open.json") at all three dispatch sites
    (main.rs:3652/3974/4093).

The core is already generic

The fix is CLI-sugar-local; no engine change is needed for the axes:

  • CampaignDoc per-strategy axes: BTreeMap<String, Axis>
    (crates/aura-research/src/lib.rs:466) is keyed by arbitrary bind-key strings.
  • The member runner binds each arbitrary axis by suffix-match against the
    referenced blueprint's own param_space() (campaign_run.rs:bind_axes ~154);
    no fast/slow below the sugar.
  • translate_sweep (verb_sugar.rs:73) already loops arbitrary --axis name=csv through axis_from_values with no hardcoded name — the exact shape
    the other three should adopt.

Proposed change

Replace the hardcoded --fast/--slow argv front-ends of
generalize/walkforward/mc with the generic --axis name=csv mechanism the
sweep path already runs, and replace the
include_str!("../examples/r_sma_open.json") embed with a user-supplied
blueprint-file argument. RGrid then dissolves entirely (it is not reshaped
into a flat struct — that would just rename the sma-cross vocabulary).

Scope boundary

The stop is the one part that is not pure sugar. RiskRegime has a single
Vol { length, k } variant (aura-research/src/lib.rs:487); an arbitrary stop
rule needs an additive enum variant + its StopRule mapping — a core change.
Keep --stop-length/--stop-k as the single Vol regime for now; the additive
seam is a separate follow-up (the enum comment already flags a future Fixed).

Fold in #214 (bundle the translator args into a struct): it is ergonomics-only
and is obviated by this generalization, so it should be merged into this work
rather than done independently.

Cross-references

Reframed final cut of #159 (the original "dead-code sweep" is in fact this
generalization). Related: #210 (verb-dissolution, which scoped these front-ends),
#214 (arg-bundling, subsumed here).

## Motivation In a topology-as-data world (C16/C24, the engine ships no project signals and the World runs opaque blueprint *data*), the orchestration verbs must not know any one strategy's parameter names. Three of the four dissolved verbs — `generalize`, `walkforward`, `mc` — still do: they are structurally welded to the retired `r-sma` demo (blueprint name `sma_signal`), hardcoding its two SMA node-names `fast`/`slow` as their only signal axes and embedding its blueprint literally. This is the last hardwired-harness residue #159 set out to remove; #210's milestone-close already scoped these r-sma-specific verb front-ends to that retirement. `sweep` is the proof it can be generic — it already takes arbitrary `--axis name=csv`. ## The weld `fast`/`slow` are not a "candidate vocabulary": they are the node labels of one topology. The sibling demos expose disjoint axis namespaces — `r_breakout` → `channel_hi.length`/`channel_lo.length`, `r_meanrev` → `mean_window.length`/`var_window.length`/`band.factor`. No axis name is shared. The bind-key `"fast.length"` is literally `<node-name>.length` of `crates/aura-cli/examples/r_sma_open.json` (two `SMA` primitives named `fast`/`slow`, wired `fast − slow` via `Sub` into `Bias`). The literals are re-asserted at ~13 sites across the three verbs (four layers each), never derived from blueprint introspection: - clap command structs — `GeneralizeCmd`/`WalkforwardCmd`/`McCmd` (`crates/aura-cli/src/main.rs:3081/3199/3241`); - the `*_args_from` extractors (`main.rs:3372/3411/3440`) and the `RGrid` struct (`main.rs:1100`); - the `translate_*` doc-axis inserts and `RiskRegime::Vol` (`crates/aura-cli/src/verb_sugar.rs:164/168, 409/413, 568/572`; risk at `188/431/590`); - the `probe_params` pre-register bind check (`verb_sugar.rs:325/480/643`); - plus the blueprint itself, hardcoded via `include_str!("../examples/r_sma_open.json")` at all three dispatch sites (`main.rs:3652/3974/4093`). ## The core is already generic The fix is CLI-sugar-local; no engine change is needed for the axes: - `CampaignDoc` per-strategy `axes: BTreeMap<String, Axis>` (`crates/aura-research/src/lib.rs:466`) is keyed by arbitrary bind-key strings. - The member runner binds each arbitrary axis by suffix-match against the referenced blueprint's own `param_space()` (`campaign_run.rs:bind_axes` ~154); no `fast`/`slow` below the sugar. - `translate_sweep` (`verb_sugar.rs:73`) already loops arbitrary `--axis name=csv` through `axis_from_values` with no hardcoded name — the exact shape the other three should adopt. ## Proposed change Replace the hardcoded `--fast`/`--slow` argv front-ends of `generalize`/`walkforward`/`mc` with the generic `--axis name=csv` mechanism the `sweep` path already runs, and replace the `include_str!("../examples/r_sma_open.json")` embed with a user-supplied blueprint-file argument. `RGrid` then **dissolves** entirely (it is not reshaped into a flat struct — that would just rename the sma-cross vocabulary). ## Scope boundary The **stop** is the one part that is not pure sugar. `RiskRegime` has a single `Vol { length, k }` variant (`aura-research/src/lib.rs:487`); an arbitrary stop rule needs an additive enum variant + its `StopRule` mapping — a core change. Keep `--stop-length`/`--stop-k` as the single Vol regime for now; the additive seam is a separate follow-up (the enum comment already flags a future `Fixed`). Fold in #214 (bundle the translator args into a struct): it is ergonomics-only and is obviated by this generalization, so it should be merged into this work rather than done independently. ## Cross-references Reframed final cut of #159 (the original "dead-code sweep" is in fact this generalization). Related: #210 (verb-dissolution, which scoped these front-ends), #214 (arg-bundling, subsumed here).
Brummel added the feature label 2026-07-07 23:01:44 +02:00
Author
Owner

Design reconciliation (specify)

Spec: verb-axis-generalization. The issue body settles the headline design
(generic --axis name=csv + user-supplied blueprint; RGrid dissolves; the stop
stays the single Vol regime; #214 folded in). The forks below are finer-grained
choices the body leaves open; this records their resolution.

  • Fork: where the generalized grammar lives / mode discriminator → the
    campaign path becomes each verb's blueprint mode with --real: aura <verb> <blueprint.json> --real <SYM[,SYM]> --axis <name>=<csv> …. A blueprint
    without --real keeps the existing synthetic in-process family paths
    (run_blueprint_walkforward/run_blueprint_mc) unchanged.
    Basis: derived — --real is already the dissolution split's discriminator
    (the #210 "Reading A" comments at the walkforward/mc dispatch sites: only the
    real-archive execution routes through the campaign path), and the shipped
    sweep grammar discriminates exactly this way (real → campaign sugar,
    synthetic → family builder).
  • Fork: welded flags removed vs stubbed--fast/--slow/--strategy
    are deleted from the three command structs; a legacy invocation fails as a
    clap unknown-argument usage error (exit 2, same class as today's refusals).
    Basis: derived — the issue's dissolve-don't-rename rationale; a stub flag
    whose only behaviour is reproducing the same exit-2 is dead surface.
  • Fork: stop flags--stop-length/--stop-k stay explicit, single-value,
    required, mapping to RiskRegime::Vol exactly as today (sweep alone continues
    to bind no regime).
    Basis: derived — preserves the generated campaign-document bytes for
    equivalent invocations, so the committed exact-grade anchors survive as pure
    argv migrations; also keeps the issue's stop scope boundary intact.
  • Fork: generalize axis arity → each --axis on generalize carries exactly
    one value; a multi-value CSV is refused (the single-candidate semantics; the
    refusal migrates from the retired --fast 2,3 refusal).
    Basis: derived — generalize grades one candidate across instruments; a value
    grid would silently turn grading into sweeping.
  • Fork: summary reconstruction is in scope → the walkforward summary
    renderer's hardcoded axis-name list (fast.length/slow.length/…) becomes a
    parameter derived from the invocation's axes plus the stop columns.
    Basis: derived — the issue's motivation clause ("the orchestration verbs must
    not know any one strategy's parameter names") covers user-visible output; a
    welded summary renders wrong for every non-sma blueprint, which would defeat
    the generalization it ships with.
  • Fork: mc mode split--seeds (synthetic closed-blueprint seed family)
    and --real+--axis (campaign pipeline) stay disjoint modes of the one mc
    verb; mixing flags across modes stays a usage error.
    Basis: derived — they are two distinct shipped operations; the disjointness
    mirrors the cross-mode refusal lists already in the mc dispatch.
  • Fork: #214 invocation-struct shape → one shared invocation struct
    (axes, name, symbols, window, blueprint reference, plus an optional stop —
    sweep passes none, mirroring its empty risk vector) with per-verb extras
    (walk-forward window sizes; mc resamples/block-len/seed) as explicit
    parameters; acceptance: zero #[allow(clippy::too_many_arguments)] remains
    in the verb-sugar module.
    Basis: derived — #214's own resolution ("bundle once over the settled common
    shape, covering all four verbs"), now unblocked since all four verbs are
    dissolved.

Non-goals confirmed: the aura graph default blueprint embed (not a dissolved
verb), a RiskRegime::Fixed variant (future additive seam), --select plateau
on the campaign path (tracked separately as #215), and the synthetic family
paths, which stay byte-identical.

## Design reconciliation (specify) Spec: verb-axis-generalization. The issue body settles the headline design (generic `--axis name=csv` + user-supplied blueprint; RGrid dissolves; the stop stays the single Vol regime; #214 folded in). The forks below are finer-grained choices the body leaves open; this records their resolution. - **Fork: where the generalized grammar lives / mode discriminator** → the campaign path becomes each verb's blueprint mode with `--real`: `aura <verb> <blueprint.json> --real <SYM[,SYM]> --axis <name>=<csv> …`. A blueprint without `--real` keeps the existing synthetic in-process family paths (`run_blueprint_walkforward`/`run_blueprint_mc`) unchanged. Basis: derived — `--real` is already the dissolution split's discriminator (the #210 "Reading A" comments at the walkforward/mc dispatch sites: only the real-archive execution routes through the campaign path), and the shipped sweep grammar discriminates exactly this way (real → campaign sugar, synthetic → family builder). - **Fork: welded flags removed vs stubbed** → `--fast`/`--slow`/`--strategy` are deleted from the three command structs; a legacy invocation fails as a clap unknown-argument usage error (exit 2, same class as today's refusals). Basis: derived — the issue's dissolve-don't-rename rationale; a stub flag whose only behaviour is reproducing the same exit-2 is dead surface. - **Fork: stop flags** → `--stop-length`/`--stop-k` stay explicit, single-value, required, mapping to `RiskRegime::Vol` exactly as today (sweep alone continues to bind no regime). Basis: derived — preserves the generated campaign-document bytes for equivalent invocations, so the committed exact-grade anchors survive as pure argv migrations; also keeps the issue's stop scope boundary intact. - **Fork: generalize axis arity** → each `--axis` on generalize carries exactly one value; a multi-value CSV is refused (the single-candidate semantics; the refusal migrates from the retired `--fast 2,3` refusal). Basis: derived — generalize grades one candidate across instruments; a value grid would silently turn grading into sweeping. - **Fork: summary reconstruction is in scope** → the walkforward summary renderer's hardcoded axis-name list (`fast.length`/`slow.length`/…) becomes a parameter derived from the invocation's axes plus the stop columns. Basis: derived — the issue's motivation clause ("the orchestration verbs must not know any one strategy's parameter names") covers user-visible output; a welded summary renders wrong for every non-sma blueprint, which would defeat the generalization it ships with. - **Fork: mc mode split** → `--seeds` (synthetic closed-blueprint seed family) and `--real`+`--axis` (campaign pipeline) stay disjoint modes of the one mc verb; mixing flags across modes stays a usage error. Basis: derived — they are two distinct shipped operations; the disjointness mirrors the cross-mode refusal lists already in the mc dispatch. - **Fork: #214 invocation-struct shape** → one shared invocation struct (axes, name, symbols, window, blueprint reference, plus an optional stop — sweep passes none, mirroring its empty risk vector) with per-verb extras (walk-forward window sizes; mc resamples/block-len/seed) as explicit parameters; acceptance: zero `#[allow(clippy::too_many_arguments)]` remains in the verb-sugar module. Basis: derived — #214's own resolution ("bundle once over the settled common shape, covering all four verbs"), now unblocked since all four verbs are dissolved. Non-goals confirmed: the `aura graph` default blueprint embed (not a dissolved verb), a `RiskRegime::Fixed` variant (future additive seam), `--select plateau` on the campaign path (tracked separately as #215), and the synthetic family paths, which stay byte-identical.
Author
Owner

Design reconciliation addendum (specify)

Spec: verb-axis-generalization. One further fork surfaced during the spec's
grounding review and is resolved here.

  • Fork: axis-name form on the CLI surface (raw vs wrapped-typed) → the
    generalized verbs take the wrapped-typed form
    --axis <blueprint-name>.<node>.<param>=<csv> (e.g.
    sma_signal.fast.length=2,3), validated against the blueprint's wrapped
    param_space() and stripped to the raw document key
    (wrapped_to_raw_axis) before the sugar — so the generated campaign
    documents keep today's raw keys and grades.
    Basis: derived — this is sweep's shipped, test-pinned contract: the raw form
    is refused at the CLI before any data access
    (aura_sweep_real_blueprint_refuses_a_raw_form_axis_name_before_data_access),
    and the wrapped→raw strip is pinned as an inverse pair
    (raw_matches_wrapped_and_wrapped_to_raw_axis_are_inverses). Diverging from
    that contract for the three migrated verbs would fork the axis vocabulary
    across verbs — the exact inconsistency the generalization exists to remove.
## Design reconciliation addendum (specify) Spec: verb-axis-generalization. One further fork surfaced during the spec's grounding review and is resolved here. - **Fork: axis-name form on the CLI surface (raw vs wrapped-typed)** → the generalized verbs take the **wrapped-typed form** `--axis <blueprint-name>.<node>.<param>=<csv>` (e.g. `sma_signal.fast.length=2,3`), validated against the blueprint's wrapped `param_space()` and stripped to the raw document key (`wrapped_to_raw_axis`) before the sugar — so the generated campaign documents keep today's raw keys and grades. Basis: derived — this is sweep's shipped, test-pinned contract: the raw form is refused at the CLI before any data access (`aura_sweep_real_blueprint_refuses_a_raw_form_axis_name_before_data_access`), and the wrapped→raw strip is pinned as an inverse pair (`raw_matches_wrapped_and_wrapped_to_raw_axis_are_inverses`). Diverging from that contract for the three migrated verbs would fork the axis vocabulary across verbs — the exact inconsistency the generalization exists to remove.
Author
Owner

Spec auto-signed (grounding-check PASS)

Spec: verb-axis-generalization (git-ignored working file, docs/specs/). Signed
autonomously: the signature is an independent fresh-context grounding review
returning PASS — every load-bearing current-behaviour assumption is ratified by
a currently-green test (sweep's grafted dispatch sequence, the wrapped→raw axis
strip inverse pair, generic bind_axes suffix-matching, the three exact-grade
anchors as migration baseline). No human signed. One earlier grounding BLOCK
(raw vs wrapped axis form) was resolved by adopting sweep's shipped wrapped
contract — see the reconciliation addendum in this thread — and the corrected
spec re-passed. Status: spec signed, ready for implementation planning.

## Spec auto-signed (grounding-check PASS) Spec: verb-axis-generalization (git-ignored working file, docs/specs/). Signed autonomously: the signature is an independent fresh-context grounding review returning PASS — every load-bearing current-behaviour assumption is ratified by a currently-green test (sweep's grafted dispatch sequence, the wrapped→raw axis strip inverse pair, generic bind_axes suffix-matching, the three exact-grade anchors as migration baseline). No human signed. One earlier grounding BLOCK (raw vs wrapped axis form) was resolved by adopting sweep's shipped wrapped contract — see the reconciliation addendum in this thread — and the corrected spec re-passed. Status: spec signed, ready for implementation planning.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#220