Commit Graph

183 Commits

Author SHA1 Message Date
Brummel 962b249814 fix(cli): aura graph rejects a bad blueprint arg, not a silent sample (#28)
The #28 cycle-close audit found aura graph <file> silently rendered the embedded
sample at exit 0 when the positional named an unreadable blueprint (typo,
nonexistent path, wrong extension): is_blueprint_file returns None both for "no
positional" and for a bad arg, and dispatch_graph's None arm conflated them —
re-introducing the exact #28 "I asked for my graph, got the sample" surprise in
the error path, and diverging from every sibling verb (a bad blueprint arg is a
usage fault, exit 2, per C14). Split the None arm: no positional -> the sample
(unchanged); a positional that is not a readable .json -> a usage error naming
the arg, exit 2, mirroring dispatch_run.

Also close the paired doc drift the audit flagged: the README blueprint-verb
table and the authoring guide now document the aura graph <file> render surface,
absent while graph was the last sample-bound verb.

refs #28
2026-07-10 04:10:02 +02:00
Brummel 518c5e9017 feat(cli): render a consumer's own blueprint with aura graph <file> (#28)
aura graph was the last verb still sample-bound: its no-subcommand arm always
rendered the embedded r_sma_open.json, so a downstream researcher who authored
their own blueprint could introspect it as data but not RENDER it to see
mis-wiring before a run — the Construction-layer milestone payoff only half
realized. Give GraphCmd a first-positional blueprint (args_conflicts_with_subcommands
so it coexists with build/introspect/register), and branch the no-subcommand arm
on is_blueprint_file, mirroring dispatch_run: read the file, reload it via
blueprint_from_json against the project/std vocabulary, and render_html it; the
no-argument form still renders the embedded sample.

The library-packaging half (a downstream-linkable render crate) is dropped — the
ratified consumer surface is CLI-over-blueprint-data (decision on #28). The
nested-composite render is not a concern: the old graph.rs ascii render that
carried the unimplemented! was retired, render_html serializes via model_to_json
with no panic, and #26 is already closed.

closes #28
2026-07-10 03:39:31 +02:00
Brummel 94cdbf90cc feat(campaign,cli): permit --select plateau in the walk_forward stage (#215)
The campaign walk_forward stage picked its per-window in-sample winner by
optimize_deflated (argmax) unconditionally, dropping the stage's `select`
field, and both the preflight and the `walkforward` verb's `--real` campaign
sub-branch refused plateau:* with a forward-pointer here (the #210 decision-4
deferral; the synthetic non-`--real` sub-branch has accepted plateau since
#173). Honour plateau selection when no gate precedes the wf stage.

- Preflight: the plateau-in-wf refusal is narrowed to gate-preceded stages.
  With no preceding gate the survivors are the full sweep grid, so the
  parameter lattice (grid.axis_lens) is exact and optimize_plateau is sound; a
  gate filters survivors below the grid, breaking the lattice, so plateau after
  any gate stays refused. The refusal is static — document validity must be
  data-independent (a gate's filtering depends on the data) — and keeps the
  existing "a gated survivor subset has no parameter lattice" message, which now
  describes exactly the only case it fires.
- Executor: run_walk_forward_stage threads the per-cell grid.axis_lens and picks
  each window's in-sample winner via select_sweep_winner — the same dispatch the
  sweep stage uses. (Argmax, deflate=true) is byte-identical to the old
  optimize_deflated call, so the argmax grade and content-id pins are unchanged;
  the plateau arms read axis_lens and ignore deflate.
- CLI: the walkforward verb threads --select through translate_walkforward /
  run_walkforward_sugar (a walkforward-local param, not the shared four-verb
  SugarInvocation), mapping the parsed Selection to the research SelectRule via
  select_rule_of; the refusal and its select_is_plateau helper are deleted.
  Scope is walkforward-only — the mc verb's wf-stage select is untouched.

The plateau:worst grade anchor (stitched_total_pips = -9683776.67) is distinct
from the argmax anchor (-10398606.67), proving the plateau path genuinely
changes winner selection rather than falling back to argmax. A gate-free plateau
process validates clean while a gate-preceded one is refused (a new
campaign-validate e2e). Argmax defaults stay byte-identical.

closes #215
2026-07-10 03:05:35 +02:00
Brummel 67f98b6bc5 feat(cli): swap the scaffold's starter node for a param-bearing Scale (#183)
The `aura new` scaffold shipped one node, a param-less Identity, so a
downstream author had no public example of a build closure consuming a
bound param — the highest-value gap the project-environment fieldtest found
(F2): a newcomer could copy a param-less node but had to read engine source
to write their first *tunable* one, which the consumer contract forbids.
Swap that single starter node for a Scale node (mirroring the std gain
operator): its schema declares a `factor` ParamSpec and its build closure
reads the bound value (`|p| Box::new(Scale::new(p[0].f64()))`), and the
starter blueprint binds `factor` so `aura run` exercises the bound-param
path with zero hand-editing.

factor is bound to 1.0 — a neutral identity default the newcomer then tunes,
not the only C7-safe value (only |factor|>1 would push the terminal `bias`
output past the bounded [-1,+1] contract; the knob is genuinely read and
multiplied each cycle). The param-reading closure is the copyable template
content #183 delivers; a hypothetical `|_|` regression is caught by the
scaffold unit test's source guard, and tests/project_new.rs re-validates the
swap by building and running the scaffold end to end.

Template-side only (#181): the frozen 0102 demo-project fixture is untouched.

closes #183
2026-07-10 00:52:29 +02:00
Brummel 93a2e2bb60 test(cli): RED — scaffold's starter node must read a bound param (#183)
The `aura new` scaffold ships one node, a param-less Identity, so there is
no public example of a build closure consuming a bound param — the F2 gap
the project-environment fieldtest rated its highest-value authoring miss.
This pins the target: the rendered starter lib.rs declares a ParamSpec and
reads p[0] in its build closure, and the starter blueprint binds `factor`
on the project's own node, so `aura run` exercises the bound-param path
with zero hand-editing.

refs #183
2026-07-10 00:17:47 +02:00
Brummel f2526b1720 docs(cli,ledger): #168 cycle-close audit — reconcile the --trace surfaces (closes #168)
The #168 refusal left sibling --trace surfaces still lying and the design ledger
still describing CLI --trace as a live persist path (surfaced by the cycle-close
architect drift review). Reconcile them to record reality:

- the chart NotFound hint no longer points at `aura run --trace` (itself refused);
  it names the live trace-writer (a campaign document's persist_taps presentation).
- RunCmd/McCmd --trace help stop promising persistence (both flags already refuse).
- README drops --trace from the live naming flags.
- the ledger records the CLI --trace retirement: per-member --trace was never
  wired to the blueprint sweep (`let _ = persist`) and was silently dropped at
  #159/#220 — it never migrated as the old HISTORY note claimed; the single live
  TraceStore::write is the campaign persist_taps; restoration is deferred to #224.

Workspace suite green (62 groups / 0 failed), clippy -D warnings clean.

closes #168
2026-07-10 00:04:27 +02:00
Brummel abbfbdc208 fix(cli): refuse --trace on sweep/walkforward instead of silently dropping it (#168)
The clap help promised "persists each member's/OOS window's taps" and the chart
NotFound hint recommended `aura sweep --trace`, but every blueprint-mode arm
discarded the flag (run_blueprint_sweep's `let _ = persist`, the --real sugar's
persist_taps: vec![]) — the surface advertised trace-recording the code dropped.
Refuse --trace up front on both verbs (exit 2, "--trace is not yet available on
<verb>; see #224"), mirroring the existing aura run / aura mc refusals; drop the
false promise from the two help lines and the chart NotFound hint. Delivering
per-member trace-writing is deferred to #224.

Collateral: four synthetic-sweep tests that used --trace as a family-name handle
move to --name (same handle, still accepted); the walkforward name/trace test now
pins the up-front #224 refusal.

Workspace suite green (62 groups / 0 failed), clippy -D warnings clean.

refs #168
2026-07-09 23:47:32 +02:00
Brummel f6d80486cd fix(cli): gate dissolved verbs on project presence, matching campaign run (#218)
A dissolved verb (sweep/generalize/walkforward/mc --real) run outside a project
silently created a content-addressed runs/ store in the cwd and ran to
completion — Env::runs_root falls back to a relative ./runs with no project, and
the four dispatchers skipped the provenance gate that `campaign run` applies
first (campaign_run.rs:322-333). It is now refused up front: one gate in the
shared validate_and_register_axes chokepoint, after axis validation and before
the first store touch (put_blueprint), via a new AxisRegisterError::NoProject
mapped to exit 1 carrying "<verb> needs a project …".

Placement is load-bearing: axis validation (an UnknownAxis usage error, exit 2)
stays ahead of the project gate, so a malformed --axis still exits 2 regardless
of project — a malformed command is malformed in any environment and already
writes no store. Four per-verb pins assert the refusal (exit 1, "needs a
project", no store); the six exit-2 usage-refusal tests stay green.

Workspace suite green (62 groups / 0 failed), clippy -D warnings clean.

closes #218
2026-07-09 22:40:48 +02:00
Brummel a74c3d116e feat(research,cli): make the risk axis discoverable — probe, roster, validate counts (#216 slice 1)
The structural risk-regime axis worked end to end but was invisible on the
public surface (refs #216): the --unwired probe swept only required slots
(risk is #[serde(default)]), no vocabulary block described RiskRegime, and
the validate summary counted axes/windows but no regimes or cells.

- open_slots_campaign gains its one optional-slot probe: a risk-less (or
  empty/non-array) document lists
  'open slot: risk (optional, list of stop regimes { vol: { length, k } };
  absent = one default regime)'; a bound list closes the slot. Never a
  required flip — content-id parity of risk-less documents is load-bearing.
- CAMPAIGN_SECTIONS gains std::risk (SlotKind::Regimes, required: false) so
  --vocabulary lists it and --block std::risk describes the regime shape.
- The intrinsic validate summary now reports the realized matrix, mirroring
  the exec cross-product exactly (strategies x instruments x windows x
  regimes; empty risk = one default cell, marked '(default)'). Points stay
  a grid property, deliberately not a cell factor.

Sanctioned migrations (recorded on #216): the two unit pins encoding the old
invisibility — open_slots_report_partial_documents_by_path now pins exactly
one open slot on the complete risk-less fixture (doubling as the probe's
unit pin; fixture bytes unchanged), and the presentation-hint test migrates
from full-vector equality to locating its entry (its purpose is the hint
text, not the vector length).

Verified: cargo test -p aura-research (29/0), -p aura-cli --test
research_docs (60/0), clippy -D warnings clean.
2026-07-09 17:51:22 +02:00
Brummel 68317ec95d chore(cli,ledger): #217 cycle-close audit — coverage backfill + stop-knob helper
Cycle-close audit for the stop-default cycle (af8564d RED + 2102305
GREEN). Architect review: what holds — the argv extractors and the
campaign member runner default to the same single-sourced regime
constants; the multi-value refusal and the C10/C20 regime semantics
survive; manifests stamp the resolved stop (C18). Three drift items,
all resolved fix-path here:

- generalize's default path was shipped untested; added the
  generalize_args_from_defaults_missing_knobs_to_the_regime unit test
  mirroring mc's sibling.
- The None=>default / Some=>parse+single-value stop block recurred 4x
  across the walkforward/mc extractors; extracted into the generic
  stop_knob_or helper (refusal strings byte-identical; generalize's
  typed unwrap_or extractor deliberately not unified — no parsing
  there).
- Ledger stop-defaulting note now records the two default
  representations: sweep binds no regime (late-resolved by the member
  runner), the three verbs bind the default eagerly into the document
  — same R behaviour, deliberately different content ids.

Regression gate green, no baseline moved: full workspace suite 0
failures across all test-result groups; clippy -D warnings clean;
cargo doc 0 warnings. No spec/plan working files existed (tdd entry
path — the committed executable spec is the contract).

refs #217
2026-07-09 16:48:49 +02:00
Brummel 210230596e feat(cli): default the stop regime on walkforward/mc/generalize
GREEN side of the #217 executable-spec: --stop-length/--stop-k are now
optional on the three dissolved verbs; each missing flag independently
defaults to the single-sourced regime constants (length 3, k 2.0) at
the argv-boundary extractors — the same regime the campaign member
runner resolves for an unbound regime. The generated campaign document
still binds the regime explicitly, so the default spelling is
content-identical to an explicit --stop-length 3 --stop-k 2.0 (pinned
by registry dedup in the headline test). The multi-value stop refusal
survives byte-identically; sweep is untouched.

Sanctioned contract migrations (decision on the issue, 2026-07-09):
walkforward_dissolved_refuses_missing_knobs now pins the surviving
multi-value refusal; mc_args_from_refuses_missing_knobs pins the
default path. Usage strings name the defaults.

Held quality nit (deferred to cycle close, minimal-slice constraint):
the None=>default / Some=>parse stop block recurs 4x across the
walkforward/mc extractors.

closes #217
2026-07-09 16:38:05 +02:00
Brummel 4acea45519 chore(cli,docs): #220 cycle-close audit — doc reconcile + dispatch dedup
Cycle-close audit for the verb-axis-generalization cycle (a2294c7,
e7efe1f, b849bb7). Architect drift review: what holds — all four verbs
route through the one validate_and_register_axes seam into the campaign
executor; RGrid gone workspace-wide; zero too_many_arguments allows in
verb_sugar.rs; no r_sma_open embed at the verb dispatch sites (the
aura-graph default embed survives by design). Five drift items found,
all resolved fix-path in this commit:

- README (blueprint-verbs section + mc row), glossary (walk-forward
  entry), and ledger (verb-dissolution passage) reconciled to the
  shipped generic grammar; the ledger HISTORY note now separates the
  two supersessions correctly (#159 retired the built-in --strategy
  demo surface; #220 de-welded the verbs) instead of mis-attributing
  the weld removal to #159.
- Four stale characterization doc-comments in cli_run.rs reworded:
  the retired welded grammar is now named as the pinned grades'
  provenance, not the current spelling; one dangling reference to a
  deleted test repointed to the live anchor.
- The quadruplicated dispatch blocks extracted into two shared helpers
  beside validate_and_register_axes: exit_axis_register_error (the
  AxisRegisterError exit-map, byte-identical stderr + exit codes) and
  campaign_window_ms (from_choice -> full_window -> unix-ms clip);
  generalize's explicit unclipped (Some, Some) passthrough arm is
  untouched — only its fallback arm, which already ran the identical
  mechanics, calls the helper.

Regression gate green, no baseline moved (nothing to ratify):
cargo test --workspace exit 0, 62 test-result groups, 0 failures;
clippy --workspace --all-targets -D warnings clean; cargo doc
--workspace --no-deps 0 warnings. Spent cycle spec + plan working
files discarded per lifecycle.

refs #220
2026-07-09 15:35:47 +02:00
Brummel e7efe1f765 feat(cli): walkforward + mc go blueprint-generic (#220 slice 2)
Tasks 3-4 of the verb-axis-generalization plan:

- walkforward: the welded r-sma campaign branch moves out of the None
  arm into the blueprint mode, split on --real (campaign sugar) vs the
  unchanged synthetic family path; WalkforwardCmd drops
  --strategy/--fast/--slow; the full-window clip block, wf_ms_sizes and
  WINNER_SELECTION_METRIC move unchanged; the --select plateau #215
  refusal survives. walkforward_summary_json_from_reports is de-welded:
  the axis-name list is passed in from the invocation instead of the
  const AXES r-sma quadruple.
- mc: McCmd drops the welded flags and gains --axis; the blueprint arm
  splits on --real (campaign pipeline: requires --axis, refuses
  --seeds) vs the unchanged synthetic seed family (--seeds); block-len/
  resamples/seed defaults and the --name/--trace refusal stay
  byte-identical.
- translate_walkforward/run_walkforward_sugar and translate_mc/
  run_mc_sugar adopt SugarInvocation (+ WfWindows/McKnobs); ZERO
  #[allow(clippy::too_many_arguments)] remain in verb_sugar.rs (#214
  complete).
- Tests: wf/mc e2e groups migrate argv-only (grade anchors keep all
  pinned floats verbatim); collateral usage/retired-token tests assert
  clap's structural rejection of the deleted flags; the synthetic
  family control groups pass unmodified.

Necessary deviation from the plan's literal step text: re-added
#[allow(clippy::type_complexity)] on walkforward_args_from (clippy -D
warnings flags the 6-tuple; mirrors its siblings). Held quality nits
(validate/strip block now quadruplicated across the four dispatch fns;
inline exit-mapping recurrence) are deliberate plan-holds — dedup
follows as the rule-of-three cleanup at cycle end.

Verification: full workspace suite green in the loop's independent
verify; build + clippy -D warnings clean.

refs #220, refs #214
2026-07-09 14:48:59 +02:00
Brummel a2294c7c3a feat(cli): SugarInvocation + generalize goes blueprint-generic (#220 slice 1)
Tasks 1-2 of the verb-axis-generalization plan (spec auto-signed on a
grounding-check PASS, decision log on #220):

- verb_sugar gains the shared SugarInvocation<'_> carrier (#214) with
  VolStop and the factored doc_axes_from/risk_from/probe_params_from
  helpers; translate_sweep/run_sweep_sugar adopt it (no behaviour
  change — sweep argv and family output byte-identical), and
  translate_generalize/run_generalize_sugar follow, dropping their
  too_many_arguments allows (4 remain for wf/mc).
- generalize is de-welded: GeneralizeCmd takes a blueprint positional
  plus repeatable --axis <wrapped-name>=<value> (one value per axis);
  --strategy/--fast/--slow are deleted; dispatch_generalize runs the
  sweep sequence (read, wrapped-namespace validation, canonicalize +
  topology_hash registration, wrapped_to_raw_axis strip) with a new
  usage closure. RGrid is deleted workspace-wide.
- cli_run generalize tests migrate argv-only; the exact-grade anchor
  keeps all pinned floats verbatim; generalize_refuses_a_non_r_sma_
  strategy is deleted (its premise — a strategy gate — dissolves by
  design); the multi-value refusal now fires per --axis.

Verification: full workspace suite green in the loop's independent
verify; build + clippy clean. Held quality finding (kept as a
plan-hold): dispatch_generalize duplicates dispatch_sweep's
validation/strip block verbatim — dedup follows once wf/mc land the
same block (rule-of-three, end of this cycle).

refs #220, refs #214
2026-07-09 13:13:59 +02:00
Brummel b5ad53d233 refactor(cli): cut wrap_r's dead cost-graph branch (#221)
Behaviour-preserving removal of the unreachable single-run cost seam: no
caller ever passed Some(cost). Drops the cost parameter from wrap_r (the
compiler enumerated 8 sites, all passing None), the dead branch, the
vol_proxy arm, CostConfig, SLIP_VOL_LENGTH, and the 4 inert cost imports;
RollingMax/RollingMin/Sub move to #[cfg(test)] (last production caller
gone). The cost_graph composite stays alive in aura-composites; the
campaign/sweep reduce path (C10) is untouched. Decision + rationale on the
issue. Suite green unchanged (62 groups), clippy clean.

closes #221
2026-07-09 10:51:03 +02:00
Brummel a1551a6097 fix(campaign): thread the risk regime through trace persistence
The persist-side C1 drift alarm re-ran every nominee under the hardcoded
default vol-stop because CellRealization dropped the regime at the record
boundary — a non-default risk regime + persist taps false-failed C1 and
refused to persist a correct trace; two regimes over the same cell would
also have collided in one trace dir.

Fix at the record boundary: CellRealization carries regime + regime_ordinal
(serde-default + skip-serializing keeps pre-existing stored records
byte-stable), populated where the cell is realized; the regime->StopRule
binding is single-sourced in stop_rule_for_regime, shared by the member
runner and the persist re-run so the two sites cannot drift apart again;
campaign_cell_key appends -r{regime_ordinal} for non-default regimes
(default keys unchanged, no stored dir renamed).

closes #219, closes #212
2026-07-09 10:38:03 +02:00
Brummel 3198747b48 docs(cli): finish the #159 tidy — sweep dead-symbol comments in main.rs
Behaviour-free comment/rustdoc sweep of the last file the #159
demo-retirement audit flagged: repoint run_sweep/run_mc/mc_family/
walkforward_family/r_sma_sweep_over/run_oos_r/sample_blueprint_with_sinks
references to their live blueprint_* analogs or mark them retired.
De-links the r_sma_sweep_family intra-doc link at blueprint_sweep_family —
cargo doc --workspace --no-deps is now warning-clean. Verified: suite
1029/0, clippy clean, diff comment-only.

closes #222, refs #159
2026-07-09 09:53:28 +02:00
Brummel 07a73fb66e docs(cli,ledger): sweep #159 demo-retirement drift — stale comments + ledger notes
Cycle-close audit tidy for the #159 demo-retirement arc: behaviour-free
reword of prose/comments/docs left pointing at symbols the arc deleted.
Adversarially verified by the audit workflow; cargo build --workspace
--all-targets green.

- README: drop the removed `--harness` token; restrict the legacy built-in
  form claim to walkforward/mc/generalize (run/sweep no longer resolve one).
- glossary: the built-in `--strategy` sweep surface is retired (#159), not a
  live inline path.
- INDEX.md ledger: append [HISTORY] supersession markers (house style) to the
  dated realization notes advertising the retired `--harness` / built-in
  `--strategy` CLI forms (cost-flag #153, cycles 0065/0066/0067, name-res,
  #210 status); historical text preserved.
- aura-engine test-fixture docs: drop dead cross-crate refs to the deleted
  aura-cli fns sample_harness / build_sample and the deleted `macd` composite.
- cli_run.rs: reword the #159-cut-2 pin doc to the current clap generic-usage
  reality (no strategy_from / Strategy::RBreakout); r_sma_sweep_family ->
  blueprint_sweep_family.
- verb_sugar.rs: fix the pre-existing `E[R]` unresolved-doc-link warning.

Remaining tidy — the main.rs comment cluster plus its rustdoc link at :1546
(the second cargo-doc warning) — held for a follow-up issue. The dead
cost-graph branch is tracked in #221. refs #159
2026-07-08 11:12:22 +02:00
Brummel ee7001647c feat(cli): retire the PIP demo pair (sma/momentum) to the generic path (#159 cut 4)
The two legacy pip-scored demos — the SMA-cross (`sma`/`Strategy::SmaCross`) and
momentum (`momentum`/`Strategy::Momentum`) — and the whole built-in *synthetic*
run/sweep/walkforward/mc + `macd` surface they anchored are dropped, not
migrated: both terminated in a `SimBroker(pip_size)` construction-arg node
absent from the zero-arg std vocabulary and reported the legacy pip yardstick
C10 retired with the realistic broker. With the R-family already migrated to
data (cuts 1-3) the `Strategy` enum held only these two, so the retirement
collapses the entire built-in-strategy machinery: `Strategy`, `HarnessKind`,
`strategy_from`, `run_sweep`/`sweep_family`/`momentum_*`,
`run_sample*`/`sample_*`/`sma_cross`, the built-in
`walkforward_family`/`sweep_over`/`run_oos` and `run_mc`/`mc_family`, the `macd`
composite cluster, and `run_dispatch`/`run_args_from`/`RunCmd.--harness`.

The generic surfaces survive untouched: `aura run <bp.json>`,
`aura sweep <bp.json> --axis`, `aura walkforward|mc --strategy r-sma --real`,
`aura campaign`, `aura generalize`. Grammar consequence (derived forks logged on
#159): `run`/`sweep` now require an explicit blueprint (no synthetic default);
`sma`/`momentum` join the retired r-breakout/r-meanrev tokens in the generic
`Usage:` error, pinned by a new negative
(`sweep_and_walkforward_reject_retired_pip_tokens_as_unrecognized`); bare
`aura graph` renders the shipped `examples/r_sma_open.json` (loaded via
`blueprint_from_json`), and the render self-containment test with it; the
`sample-model.json` viewer corpus + its two `.mjs` tests stay frozen as static
test data.

`member_key`/`MAX_KEY`/`fnv1a64` were removed too. The plan wrongly listed
`member_key` as a survivor; after the built-in sweep/mc retire it has no
production caller (campaign_run derives its own content key), so it and its unit
tests are genuinely dead. The spec-compliance gate surfaced this plan-deviation;
it was ratified after verifying the tree stays green.

Verified: `cargo build --workspace`, `cargo test --workspace` (0 failed), and
`cargo clippy --workspace --all-targets -- -D warnings` all clean.

refs #159
2026-07-08 02:14:43 +02:00
Brummel 257ab0b9f2 feat(std,cli): add the Scale node, retire the r-meanrev demo to data (#159 cut 3)
Cut 3 of the hard-wired demo retirement. Unlike r-sma/r-breakout, r-meanrev could
not round-trip as data: its band computes band_k*sigma (a constant times a stream)
and the closed 22-node std_vocabulary had no way to express it (EqConst is i64->bool,
Bias is clamp(signal/scale,±1), Mul is binary, LinComb is excluded as a
construction-arg node; no Div/Const/constant-emitter). Standard operators are missing
by chance, not by design, so this adds the one r-meanrev needs.

Scale node (aura-std): out = input * factor — one f64 input, one f64 `factor` param,
stateless, warm-up-filtered (the EqConst/Bias shape). A zero-input `Const` emitter was
rejected: the per-cycle eval loop gates every node through an input-firing test
(harness.rs `fires()`), so a node with no input never fires without an engine-core
change; `Scale` fits the existing model and, by IEEE-754 commutativity, reproduces the
retiring LinComb's band byte-for-byte. Rostered (`"Scale" => Scale`); the two
vocabulary count-pins bump 22 -> 23.

r-meanrev migration: r_meanrev_signal(window, band_k) is carved out of the fused
r_meanrev_graph as a #[cfg(test)] price->bias Composite whose band is `Scale` in place
of `LinComb(1)`; production loads the shipped JSON (examples/r_meanrev{,_open}.json),
never a builder. Before the fused builder was deleted, an equivalence test proved the
carved Scale-band signal reproduces its grade byte-for-byte on the synthetic stream
(window=3, band_k=2). Durable survivors: the byte-identity of the examples to the
carve, the loaded-grades-identically-to-carve proof, the closed/open introspect
anchors (mean_window.length, var_window.length, band.factor — band_k is now a
first-class sweepable param), and — re-pointed onto the carve rather than dropped —
the fades-short-above/long-below behavioural test, which becomes the durable
carve-correctness gate the deleted equivalence test used to be.

Deletions + the dead-code cascade the retirement opened: r_meanrev_graph,
r_meanrev_sweep_family, Strategy::RMeanRev (+ all arms). r-meanrev was the last reader
of run_sweep's grid, so the whole vestigial built-in-sweep grid apparatus retires with
it: run_sweep's `grid` param, the sweep call-site grid build, and SweepCmd's
--fast/--slow/--stop-length/--stop-k/--window/--band-k (sma/momentum sweeps build their
own blueprints and ignored these; the fast/slow/stop-* had been vestigial since r-sma's
cut 1b). RGrid the struct SURVIVES — the dissolved `generalize` verb resolves its
candidate from its fast/slow/stop_length/stop_k via generalize_args_from — so only its
window/band_k fields go; its stale doc-comment (r-sma-sweep / persist_traces_r / CLI
flags, all now gone) is rewritten to its true generalize-only role. Also retired as
transitively dead: persist_traces_r and the metrics_object test helper (their only
callers were the deleted families/tests); Add/Gt/Latch/Mul/Sqrt imports are now
#[cfg(test)] (their last production caller was r_meanrev_graph).

Test surface: the two built-in --strategy r-meanrev sweep tests drop; a new negative
pins that --strategy r-meanrev on both sweep and walkforward now falls into the generic
usage error; the grid-flag stray-positional negative is re-pointed to a surviving flag.

Verification (own, not the workflow's report): full `cargo test --workspace` green
(0 failed across 62 result lines); `cargo clippy --workspace --all-targets -- -D
warnings` clean, no dead-code residue; the r-sma + r-breakout anchors and the
dissolved-verb real goldens (generalize still reads RGrid) stay green. Bundled as one
commit (the Scale node's count-pin and the r-meanrev anchors share graph_construct.rs,
so a clean two-commit split was not path-separable); the implement-loop ran all three
plan tasks in one pass and the tree was verified by hand.

refs #159
2026-07-07 22:02:50 +02:00
Brummel 5856cadadd refactor(cli): retire the r-breakout demo — topology now lives only as data (#159 cut 2)
Cut 2 of the hard-wired demo retirement, applying the r-sma template one level
down. The r-breakout signal leg was fused into r_breakout_graph (which also built
the whole pip/R harness inline), so this cut first carves the signal leg out as a
pure price->bias Composite, ships it as a proven blueprint example, then deletes
the fused builder and the built-in --strategy r-breakout surface. The r-breakout
topology now exists only as data (crates/aura-cli/examples/r_breakout{,_open}.json).

Carved: r_breakout_signal(channel) -> Composite, a verbatim lift of the fused
builder's signal leg (Delay/RollingMax/RollingMin/Gt/Latch/Sub) exposing the raw
exposure as "bias"; the generic wrap_r/run_signal_r path (unchanged) supplies the
pip/R harness. It is #[cfg(test)] — its only role is regenerating the examples and
pinning them to a faithful serialisation; production loads the shipped JSON, never
this builder (so no dead-code residue, no production topology constructor — #159's
invariant).

Proof: before the fused builder was deleted, an equivalence test proved the carved
signal, wrapped and run, reproduces the retiring r_breakout_graph grade byte-for-byte
on the synthetic stream at channel=3 with the shared default R-stop (metric
computation is identical on both paths). That gate is deleted with the builder it
references; the durable anchors that survive are the byte-identity of the shipped
examples to the carve, the loaded-example-grades-identically-to-the-carve proof, and
the closed-ness / open-axis introspect anchors (channel_hi.length, channel_lo.length
per-node — ganging is the separate #61).

Deleted: r_breakout_graph, r_breakout_sweep_family (aura sweep --strategy r-breakout),
Strategy::RBreakout + all four match arms, the r-breakout token from the --strategy
usage strings and docs, and the now-dead --channel flag + RGrid.channel field (its
sole reader was r_breakout_sweep_family). The data successors: aura run
examples/r_breakout.json, and aura sweep examples/r_breakout_open.json --axis
channel_hi.length --axis channel_lo.length.

Test surface: the three built-in-surface sweep tests drop; the walkforward
valid-but-unsupported-token test is re-pointed to momentum (the property is generic
and stays live until Cut 4); and a new negative pins that --strategy r-breakout on
both sweep and walkforward now falls into the generic usage error, not a
special-cased message — catching a half-finished retirement that left the token
parseable.

Verification (own, not the workflow's report): full `cargo test --workspace` green
(0 failed across every binary); `cargo clippy --workspace --all-targets -- -D warnings`
clean, no dead-code residue; the r-sma introspect + grade anchors and the
dissolved-verb real goldens stay green. The implement-loop ran both plan tasks in one
pass (task_range was not honoured); the tree was verified by hand — grep-confirmed the
retired surface is gone and the carve is #[cfg(test)]-scoped.

refs #159
2026-07-07 19:25:13 +02:00
Brummel f7c809e5ce refactor(cli): retire the r-sma demo builder — topology now lives only as data (#159 cut 1b)
Cut 1b of the hard-wired demo retirement — the removal half. With the proven
example shipped in cut 1, delete the r-sma builder and its whole transitive
closure so no production function constructs the r-sma topology any more; it
exists only as data (crates/aura-cli/examples/r_sma{,_open}.json).

Deleted: sma_signal (the builder), r_sma_graph, run_r_sma (aura run --harness
r-sma), r_sma_sweep_family (aura sweep --strategy r-sma), the synthetic inline
r-sma walkforward/mc machinery (r_sma_space/r_sma_sweep_over/run_oos_r/
run_mc_r_bootstrap/mc_r_bootstrap_report + the walkforward_family RSma arm), both
fixture emitters, the round-trip + cut-1 byte-fidelity tests, the R_SMA_FAST/
SLOW/BIAS_SCALE consts, and the Strategy::RSma / HarnessKind::RSma / McArgs::RealR
variants. The three dissolved-verb synth sites (generalize/walkforward/mc
--strategy r-sma --real) now embed the shipped open example via include_str!
instead of building it — byte-safe, guarded by the unchanged dissolved-verb
real-archive goldens. The two now-redundant fixtures move to the examples
(deleted, ~40 readers repointed). The --cost-* flags on `aura run` retire with
the harness (their sole consumer was run_r_sma; cost-model coverage survives
engine/std/composites-level).

The retired demo surfaces (--harness r-sma, sweep/walkforward/mc --strategy r-sma
inline) have working data successors: aura run examples/r_sma.json, aura sweep
examples/r_sma_open.json --axis …, and the --strategy r-sma --real campaign path.

Deviation from the plan, noted: StopArm collapsed to StopRule in wrap_r's
signature (its only other variant, Open, died with r_sma_graph) — cleaner than
the planned vestigial single-variant enum; behaviour-preserving, touching
campaign_run.rs and the r-breakout/r-meanrev call sites.

Follow-up (not this cut): wrap_r's cost-node-building branch is now unreachable
(nothing passes Some(CostConfig) after run_r_sma's deletion) yet clippy-clean —
a latent cleanup for when the inline cost surface fully retires.

The stage1-r param-suffix remapping (r_sma_friendly_name + the FAST/SLOW/
STOP_LENGTH/STOP_K_SUFFIX consts) went dead and was removed, closing #167 by
deletion.

Verification (own, not the workflow's report): full `cargo test --workspace`
green; `cargo clippy --workspace --all-targets -- -D warnings` clean, no
dead-code residue; the cut-1 grade anchor (shipped_r_sma_example_reproduces_the_
builtin_grade) and the dissolved-verb real goldens (generalize/walkforward/mc
_dissolves_through_the_campaign_path) stay byte-identical. A first workflow
attempt was discarded after a subagent's `git checkout -- main.rs` recovery
silently reverted earlier tasks' edits (filed on the plugin tracker as
Brummel/Skills#23); this landing is the hardened re-run, verified by hand.

closes #167
refs #159
2026-07-07 17:41:59 +02:00
Brummel d7fa4b4743 feat(cli): ship r-sma demo as a proven blueprint-data example (#159 cut 1)
Cut 1 of the hard-wired demo retirement — the copy+prove phase. The r-sma demo
strategy now ships as runnable blueprint data under crates/aura-cli/examples/
(r_sma.json closed 2/4, r_sma_open.json open), emitted from the current builder
so it is byte-for-byte the builder's serialization.

Two load-bearing proofs, captured while the builder is still alive:
- byte-fidelity: the shipped examples equal blueprint_to_json(&sma_signal(..)) —
  the "the copy is identical to the original" evidence the retirement method
  requires before any original is removed;
- grade anchor: `aura run examples/r_sma.json` reproduces the built-in
  `--harness r-sma` metrics byte-for-byte (expectancy_r/sqn/total_pips/n_trades).

Plus two property tests on the shipped copies (beyond the plan, kept as on-topic
proof strengthening): the closed example introspects to zero unbound params
(genuinely closed), and the open example lists exactly its two SMA-length axes
from the public gallery location.

Purely additive: the builder (sma_signal) and every inline r-sma surface are
retained. The recon showed deleting the builder has a large transitive closure
(the synthetic inline surface, the Strategy::RSma enum, ~40 integration tests);
that removal is cut 1b, a separate cycle, made safe by the byte-fidelity proof
frozen here.

Full workspace suite green; clippy -D warnings clean.

refs #159
2026-07-07 12:40:14 +02:00
Brummel 7f5f576f41 feat(cli): dissolve aura mc --strategy r-sma --real (#210)
The FOURTH and LAST verb dissolution. `aura mc --strategy r-sma --real`
now runs as thin sugar over the one campaign executor: a generated,
content-addressed [std::sweep(argmax), std::walk_forward, std::monte_carlo]
campaign document whose terminal monte_carlo stage produces the same
StageBootstrap::PooledOos(RBootstrap). The sugar reads it from
run.outcome.record.cells[].stages[].bootstrap and reprints the existing
mc_r_bootstrap_json — one line, byte-identical to the inline path.

Pure sugar, NO executor change (aura-campaign/aura-engine/aura-research
diff-free). Mirrors the shipped walkforward dissolution:
- verb_sugar.rs: GeneratedMc, translate_mc, register_generated_mc,
  run_mc_sugar (record-read + one grade line, no member lines).
- main.rs: mc_args_from + the dispatch_mc built-in-arm split gated on
  `--strategy r-sma && --real`.

The one new load-bearing decision is the seed mapping: translate_mc sets
campaign.seed = the mc --seed. The wf winners are argmax hence
deflation-seed-independent (proven by the shipped walkforward anchor), so
the pooled OOS series is unchanged while the terminal r_bootstrap at that
seed reproduces the inline bootstrap. A new e2e
(mc_dissolved_seed_changes_the_bootstrap_draw_not_the_pooled_series)
pins exactly this: two --seed values yield identical n_trades but a moved
E[R] mean.

Fork C retention (as walkforward): the arm splits, run_mc_r_bootstrap /
run_mc stay inline for the fenced synthetic/non-real paths (until #159).
The three synthetic-r-sma tests use no --real, so they never enter the
sugar branch and run the inline path unchanged. --name/--trace are
rejected on the sugar path, matching the inline mc r-path.

Verification: the committed byte-identity anchor
mc_r_bootstrap_real_e2e_pins_the_exact_current_grade runs the real GER40
archive and passes byte-for-byte through the new path; full workspace
suite green; clippy --all-targets -D warnings clean.

refs #210
2026-07-07 00:23:33 +02:00
Brummel a047af65fb feat(cli): dissolve aura walkforward --strategy r-sma --real (#210 T3-T4)
The dispatch split lands: `dispatch_walkforward`'s built-in arm now routes
`--strategy r-sma --real` through the one campaign path (`run_walkforward_sugar`
-> generated campaign doc -> executor -> summary reprint), reproducing the
`{"walkforward":{…}}` line byte-for-byte. Everything else — synthetic (any
strategy) and non-r-sma real — stays on the inline `run_walkforward`, fenced
until #159 (Reading A). The committed anchor
`walkforward_real_e2e_pins_the_exact_current_grade` passes: the dissolved path
reproduces windows=9, stitched_total_pips, oos_r, and the param_stability means
exactly. `--select plateau:*` is refused (exit 2 -> #215, Fork B); a multi-value
stop is refused (Fork A).

Two plan guesses were wrong and the anchor + real-data e2e caught both; the fixes
are documented inline:

- Window resolution: the plan mirrored generalize's `(--from,--to) -> (f,t)`
  shortcut, but the inline `wf_full_span` ALWAYS clips `--from`/`--to` to the
  archive's actual first/last bar (`probe_window`), even when both flags are
  explicit — a holiday/weekend edge shifts every IS/OOS window's calendar
  placement (stitched_total_pips diverged by ~5.16M, param_stability means intact,
  confirming only window boundaries moved). The dissolved path now calls
  `source.full_window(env)` unconditionally, matching the inline behaviour;
  verified byte-for-byte against the still-fenced inline path.
- Reducer axis match: the campaign path records the blueprint axes wrapped
  (`sma_signal.fast.length`) while the stop rides unwrapped via the risk regime, so
  the reducer's exact-name match panicked on real data. Fixed with the existing
  `campaign_run::raw_matches_wrapped` (#203) wrap-convention helper.

Tests: the dissolution-proof e2e `walkforward_dissolves_through_the_campaign_path`
(one process + one campaign + one campaign-run auto-registered, windows=9) plus
front-end refusal coverage (multi-value stop, `--select plateau`, missing knobs,
empty `--real`, unknown `--select`, `--name`/`--trace` exclusivity). Full workspace
suite green; `clippy --all-targets -D warnings` clean.

refs #210
2026-07-06 23:05:08 +02:00
Brummel a72cd6b115 feat(cli): walkforward campaign translator + summary reducer (#210 T1-T2)
The additive machinery for the walkforward dissolution, unwired this slice
(dispatch_walkforward untouched — `aura walkforward` behaviour byte-for-byte
unchanged). Two pieces, both mirroring the generalize seam:

- `translate_walkforward` + `register_generated_wf` + `GeneratedWalkforward`
  (verb_sugar.rs): argv -> a `[std::sweep(argmax), std::walk_forward]` process +
  a campaign carrying the multi-value fast/slow IS-refit grid over one instrument
  under a single Vol risk regime (the stop). The preceding sweep enumerates the
  survivor grid; the wf stage refits it per window.
- `walkforward_summary_json_from_reports` (main.rs): the pure-sugar reducer that
  reconstructs the verb's summary line from the campaign's recorded per-window OOS
  reports — `stitched_total_pips` = the per-window total_pips summed in roll order,
  `param_stability` over the four r-sma axes via the same `MetricStats::from_values`,
  pooled `oos_r` via `r_metrics_from_rs`. Byte-identical to the inline
  `walkforward_summary_json` (the committed anchor gates it once Task 3 wires it).

Both carry `#[allow(dead_code)]` until Task 3's dispatch split calls them.

The third `Generated*`/`register_generated_*` trio duplicates the sweep/generalize
shape by design (per-verb mirror); the bundle consolidation is deferred to #214.
Unit test note: `r_metrics_from_rs` clears `trade_rs` (it is the terminal pooled
reducer), so the reducer's characterization fixture rebuilds each window's
`RMetrics` with `trade_rs` restored, modelling what a real OOS window's report
carries (from `summarize_r`).

refs #210
2026-07-06 22:12:47 +02:00
Brummel 256ec7320c feat(cli): dissolve aura generalize into the campaign path (#210 T3)
`dispatch_generalize` synthesizes the bare `sma_signal` blueprint, resolves the
window (explicit --from/--to, else the first symbol's full window), and calls
the new `run_generalize_sugar`; the inline `run_generalize` is deleted. The
sugar builds the selection-bearing campaign doc, runs it through
`run_campaign_returning`, reprints the byte-identical `{"generalize":…}` +
`{"family_id":…}` lines, and persists the CrossInstrument family.

The exact-grade anchor stays green through the path shift — proving the two stop
mechanisms (former grid axis, now the risk-regime seam -> StopRule::Vol) yield
identical R. The family pin relaxes to "exactly one CrossInstrument family"
since the campaign executor now also persists a per-instrument Sweep family per
cell (the durable audit trail; #210 Q4). Two new e2e pin the window fallback and
the family member attribution order.

Deviations from the plan (necessary, verified): cross_instrument_members returns
Vec<aura_engine::RunReport> (aura_registry::RunReport is not pub); a shared
validate_before_register helper is factored out of run_sweep_sugar (DRY); the
run_generalize doc-comment and the now-unused `generalization` import removed.

refs #210
2026-07-06 17:42:36 +02:00
Brummel ed8b179e34 feat(cli): the generalize campaign translator (#210 T2)
`translate_generalize` builds the selection-bearing campaign document
(`[std::sweep(argmax), std::generalize]`, a single risk regime carrying the
stop, the candidate as single-value raw axes) + `GeneratedGeneralize` +
`register_generated_g`, mirroring the sweep translator. A determinism unit test
pins identical-invocation -> identical content id and the regime/pipeline shape.
The new items are unused until the dispatch-rewire task (#210 T3) wires the
runner; a bridge `#![allow(dead_code)]` (removed in T3) mirrors the sweep
sibling's landing.

refs #210
2026-07-06 17:04:56 +02:00
Brummel 9ddea840dd refactor(cli): factor the campaign run outcome out of the presenter (#210 T1)
`run_campaign_returning` runs the resolve->execute body and returns a
`CampaignRun` bundle (outcome + campaign/strategies/server); `present_campaign`
consumes it for the stdout tail; `run_campaign_by_id` becomes their composition.
Behaviour-preserving — the existing campaign + sweep-sugar e2e stay green
(research_docs 54/0 incl. the real-data e2e, sweep dissolution e2e green).
Exposes the outcome so the forthcoming generalize sugar can read it without the
presentation tail.

refs #210
2026-07-06 17:04:56 +02:00
Brummel c62f7e9878 feat(cli): bind the resolved regime stop and stamp it into the manifest (#210 T4)
The runtime-binding slice — the regime's stop now actually reaches the graph and
the record:

- `StopArm { Open, Bound(StopRule) }` replaces `wrap_r`'s `stop_open: bool` so a
  bound stop carries its params; all call sites threaded (the r-sma sweep path
  stays Open, every bound site passes the resolved rule).
- `run_blueprint_member` takes a `stop: StopRule` and stamps `stop_length` /
  `stop_k` into the member manifest (the ratified C18 extension — the stop is no
  longer invisible on the campaign/blueprint path); its callers thread the
  default, and `CliMemberRunner` maps `cell.regime` (None -> baked default,
  Some(Vol) -> that vol stop).
- The characterization pin flips from asserting the stop's ABSENCE to requiring
  the stamped default; a real two-regime e2e pins that each regime stamps its
  own k.

Known deferred gap (concern, documented descope): `persist_campaign_traces`
re-runs the nominee to persist its traces and — since `CellRealization` carries
no regime — binds that re-run to the DEFAULT stop, so a non-default-regime
nominee's persisted trace would reflect the wrong stop. Only the persist path
(non-empty `persist_taps`) is affected; the core run/stamp/generalize path is
correct. Filed as a follow-up. Full workspace suite green.

refs #210
2026-07-06 14:52:20 +02:00
Brummel 2f9f3571ae feat(research): RiskRegime vocabulary + CampaignDoc.risk field + validation (#210 T1-2)
The first slice of the risk-regime structural axis (spec/plan abab1f1):

- aura-research: a serializable `RiskRegime` closed vocabulary (externally
  tagged, sole variant `Vol { length, k }` — a content-addressable mirror of
  the shipped two-variant StopRule, so a future Fixed regime is additive), and
  an optional top-level `CampaignDoc.risk: Vec<RiskRegime>` that absent-
  serializes (the `description` precedent) — content-id parity for every
  risk-less document.
- validate_campaign refuses a non-positive regime (length < 1 or k <= 0, NaN
  included) via a new `DocFault::BadRegime`; the CLI renders it as prose at the
  research_docs seam (register/validate paths).
- The three CLI-seam E2E tests pin that a well-formed risk section passes
  `campaign validate` and a bad one is refused with prose (exit 1, no Debug
  leak) — the field is reachable at the seam a data author drives, not only in
  library units.

Adding the field is compile-breaking at every `CampaignDoc { .. }` literal
(serde default does not cover Rust construction), so this slice also threads
`risk: vec![]` into the sites across aura-cli and aura-campaign — the
field-addition's compile-gate completion the plan should have scoped here. The
BadRegime prose reads `risk[i]: ...` (the implementer's terser wording over the
plan's illustrative string; the spec pinned no exact text). Full workspace
suite green.

refs #210
2026-07-06 14:02:33 +02:00
Brummel c9d962f0ea fix(cli): sweep-entry ergonomics — honest refusals, no store litter (#210 c0110 fieldtest)
Three test-first fixes from the cycle-0110 fieldtest findings:

- Axis-name preflight on the dissolved sweep arm: every --axis name is
  checked against the wrapped probe namespace (the --list-axes names)
  BEFORE the #203 strip; an unknown or raw-form name is refused
  house-style echoing exactly what the user typed, with a --list-axes
  pointer — no more stripped-then-mangled fragments from the deep
  referential refusal. Data-free, fires before the archive is touched.

- Validate-before-register in the sweep sugar: intrinsic doc checks +
  executor preflight + the pure bind_axes coverage check (reused, not
  re-inlined; now pub(crate)) all run in-memory before
  register_generated — a refused invocation leaves no generated
  document in the content-addressed store and no campaign_runs.jsonl
  line (previously P2/P3 refusals littered runs/campaigns, one doc
  malformed).

- blueprint_slot_prose: the sweep/walkforward/mc loaded-blueprint slots
  share one dispatch-boundary presenter — an op-script array gets a
  targeted 'run aura graph build first' hint, every load error goes
  through blueprint_load_prose + the missing-project hint, and the raw
  {e:?} Debug leak (#184's pattern, deliberately left on these three
  slots back then) is gone. Two stale Debug-leak pins flipped, two new
  refusal tests.

Suite 1060/0, clippy -D warnings clean.

refs #210
2026-07-04 19:48:57 +02:00
Brummel 2c729965db audit: cycle 0110 tidy — verb-dissolution cycle 1 closed drift-clean
Architect review (scope e7c7bde..b7aaa0b) found three items; resolutions:

- fix: docs/authoring-guide.md + glossary aligned with the shipped
  vocabulary (std::sweep selection group optional/all-or-nothing,
  selection-free = terminal-only, worked example verified against the
  live binary).
- fix: the #203 wrapped/raw axis-name convention is now single-sourced
  in campaign_run.rs (wrapped_to_raw_axis + raw_matches_wrapped, a
  cross-documented inverse pair; dispatch_sweep and both bind sites
  call through it; inverse-property unit test). Ratify note: this
  unifies two previously different algorithms onto the documented
  first-segment semantics — the old loose ends_with suffix-match also
  accepted sub-segment shorthands (e.g. axis "length" against
  "sma_signal.fast.length") that no stored document or test used;
  such shorthands now refuse loudly (unbound/unknown axis) instead of
  matching, the refuse-don't-guess reading of #203.
- carry: run_blueprint_sweep/blueprint_sweep_family remain generically
  real-capable though the dispatch no longer routes real data to them;
  the residual capability is shared DataSource machinery, its removal
  falls out with the built-in/synthetic branch retirement (#159), and
  the synthetic-only status is documented at the fn.

What holds: C24 canonical form (flatten group leaves every stored
content id unchanged, golden pin untouched), C18 lineage (topology_hash
IS content_id_of, single store write resolves the strategy ref;
selection-free arm still persists the full family), C25 closed-
vocabulary discipline (all-or-nothing group, Blockly-clean), C3 (ms/ns
conversion through the ingest seam's own named fn).

Ledger: #109 open-thread status lifted to cycles 0107-0110 reality
(executor shipped, amendment package landed, verb dissolution running
as milestone #210 with sweep dissolved).

Regression gates: cargo test --workspace 1056/0; clippy -D warnings
clean; cargo doc clean. Cycle spec+plan removed at close per
convention.

refs #210
2026-07-04 19:16:06 +02:00
Brummel b7aaa0ba59 feat(cli): dissolve the real-data blueprint sweep into the campaign path (#210 T4)
aura sweep <bp.json> --real ... --axis ... now runs as thin sugar over
its generated campaign document through the one campaign executor: the
dispatch registers the canonical blueprint (topology_hash IS
content_id_of over the same canonical bytes, so the strategy ref
resolves against the existing single store write), strips the CLI's
wrapped axis names to the raw campaign namespace (#203 bind
convention), converts the probed archive window back through the
ingest seam's own epoch_ns_to_unix_ms (made pub; C3 — the seam owns
the ms/ns convention, never an inline division), and hands off to
verb_sugar::run_sweep_sugar. The inline real-data arm is gone; the
synthetic arm and --list-axes are untouched.

campaign_run gains RunPresentation {Full, MemberLinesOnly}:
run_campaign keeps Full (record line unchanged, project gate
unchanged); the sugar path runs MemberLinesOnly — the generated doc's
emit already limits stdout to family_table member lines, the mode
suppresses only the final record line. The record append is identical
in both modes; no project gate on the by-id path (the dissolved verb
works project-less, exactly as before).

The characterization pin survives the re-cut with exactly the one
sanctioned assertion flip (instrument-absence -> instrument==GER40)
and now also pins the generated artifacts: one process + one campaign
document auto-registered (dedup on a second identical invocation),
raw axis keys in the stored document, the stored window a ms sub-range
of the request, C1 at the member-report level with the registry's
run-suffix increment isolated.

Suite 1055/0, clippy clean, gated real-data paths executed locally.

refs #210
2026-07-04 19:01:27 +02:00
Brummel fd0b21d070 feat(cli): verb_sugar translator — argv to generated campaign documents (#210 T3)
The pure half of the sweep re-cut: translate_sweep maps a real-data
blueprint-sweep invocation (axes, name, symbol, window, canonical
blueprint bytes) to a selection-free single-stage process document plus
a campaign document carrying exactly the invocation's intent (seed 0 —
no stage consumes it — so identical invocations dedupe onto identical
content ids); register_generated puts both into the content-addressed
stores. Unit tests pin determinism, name flow, mixed-kind-axis refusal,
and that generated documents pass intrinsic validation + preflight.

Dead-code-allowed until the dispatch rewire wires the module (next
task removes the bridge attribute). Plan bytes reconciled with three
necessary deviations the spec gate surfaced: derive(Debug) for
unwrap_err, ScalarKind lives in aura_core, and 'gen' is reserved in
edition 2024.

refs #210
2026-07-04 18:01:51 +02:00
Brummel c37abb2a27 feat(research,campaign): optional selection group on the sweep stage (#210 T1-T2)
The std::sweep selection triple (metric/select/deflate) becomes ONE
optional group, Option<SweepSelection>, serde-flattened so the wire
form keeps the flat slots: every existing document parses to Some and
its canonical bytes — and content id — are unchanged (golden pin
byte-untouched). A half-populated group is refused by the schema-strict
parser (all-or-nothing); a bare {"block":"std::sweep"} is the
selection-free sweep.

Campaign side: preflight permits a selection-free sweep only as the
pipeline's terminal stage (new ExecFault::SelectionFreeSweepNotTerminal,
prose in aura-cli's exhaustive exec_fault_prose); the executor's
selection-free arm appends the family exactly as before but records no
StageSelection and produces no nominee (recording one would fabricate
intent — #210 fork decision 1).

Introspection follows: the sweep block's schema table marks the group
optional; CLI introspection pins updated. New tests: group-or-nothing
refusals, selection-free round-trip + distinct content id, terminal-only
preflight accept/refuse, selection-free execute realization.

refs #210
2026-07-04 17:43:53 +02:00
Brummel 61c8828fad feat(cli): house-style loader-error prose + missing-project hint on the run path
aura run leaked the raw LoadError Debug form (UnknownNodeType("..."))
while graph build already phrased the same fault house-style (#162
convention: engine error types are Display-free, the CLI presents them).
Route the run path through the shared blueprint_load_prose presenter (now
pub(crate), env-agnostic) so both surfaces phrase a LoadError identically
and neither leaks the Debug token (#184). Add unresolved_namespace_hint:
when the unresolved id looks project-namespaced (::) and no project was
loaded (env.provenance() is None), append a hint pointing at the missing
Aura.toml — the newcomer running the host from the wrong directory now
gets an orienting diagnosis instead of a bare type error (#185). The hint
is applied on both the run and graph-build paths. RED-first: the two
existing run-path pins were flipped to assert the prose + absence of the
Debug token before implementing; suite green (1043/0).

closes #184
closes #185
2026-07-04 15:19:58 +02:00
Brummel 5256b3d0ca refactor(registry): single-source the deflation-bootstrap constants (#199)
DEFLATION_N_RESAMPLES (1000) and DEFLATION_BLOCK_LEN (5) were duplicated
as independent copies in aura-cli and aura-campaign — a silent-drift risk
(an edit in one crate but not the other refuses nothing, it just produces
a wrong deflation result). Promote a single definition into aura-registry,
which owns optimize_deflated (the sole consumer of these counts); aura-cli
imports it and aura-campaign re-exports it to keep its public path stable.
DEFLATION_SEED stays cli-local (no campaign twin). Values unchanged;
behaviour-preserving, suite green unchanged (1043/0).

closes #199
2026-07-04 15:12:40 +02:00
Brummel 7604651579 feat(cli): aura.css becomes the style pin — tokens, component library, CLAUDE.md rule
Second half of the style pin. assets/aura.css now carries (a) the full
:root token palette (Catppuccin-Mocha-derived; --bg deliberately darker
than Mocha base; new --border3 #585b70 completes the border ladder at an
identical computed value), (b) the shell rules migrated color-literal to
var() with identical computed values (font stacks stay literal — the
--mono token's longer fallback chain would change the computed value),
and (c) an opt-in, class-scoped component library distilled from the
milestone demo page (.aura-doc register, .wrap, .badge, .stats, .cards,
.term, .callout, table.res, .pipe) — unused by the runtime pages, which
render pixel-identically. CHART_CSS migrates to the same tokens (it
lands in the same document after the shell <style>, so :root resolves).
CLAUDE.md gains the binding '## HTML surfaces' rule: every HTML surface
embeds this asset; extend, never fork.

Gates: suite 1043/0, clippy -D warnings clean, doc build 0 warnings;
regenerated chart page resolves all 17 used var() names against its
:root. Independent CSS audit passed (two cosmetic notes: --bg3/--orange
currently unused; the pre-existing html,body height:100% also reaches
future .aura-doc pages).

closes #209
2026-07-04 11:24:07 +02:00
Brummel 652ad34b3f refactor(cli): extract the shared shell stylesheet into assets/aura.css
Pure byte-preserving extraction: the CSS block inside SHELL_HEAD moves
verbatim into crates/aura-cli/assets/aura.css and is spliced back via
concat!(.., include_str!(..), ..). Proven against a pre-change baseline:
chart HTML (aura chart 42edebd2-0/597d719b-GER40-w0) and graph HTML
(bare aura graph) regenerate sha256-identical (bcc2cc0f.., a0e6a97f..),
each deterministic across two generations. Suite 1043/0.

First half of the style pin: the asset becomes the single source the
runtime pages embed; tokens + component library follow separately.

refs #209
2026-07-04 11:11:09 +02:00
Brummel 6f2cf443c4 fieldtest: milestone-research-artifacts corpus (GREEN) + Aura.toml data-key doc fix (F7)
MILESTONE VERDICT: GREEN — the whole span (0106-0109) holds end to
end as one continuous consumer scenario: authored from a bare {} via
introspection alone, every ref resolved from the public surface, three
validation tiers, run by content id over two real instruments through
the full v2 pipeline, realization recorded (selection/survivors/
bootstrap/generalizations/trace_name), traces on disk and charted,
read back via campaign runs, byte-identical re-runs (C1), refusals
precise. 0 bugs, 0 frictions, 0 spec gaps; 2 doc-gaps.

F7 fixed in this commit (the docs claimed an archive-root path without
naming the key; a guessed [paths] archive silently no-ops): project-
layout and the glossary Aura.toml entry now name [paths] data, and the
aura new template advertises the commented-out key. F8 (op-script
grammar not CLI-discoverable) filed as #208, sibling of the #197 docs
half — one docwriter pass once desired.

The milestone-close gate evidence is this corpus; the formal milestone
close and the push remain user-reserved.

refs #189
2026-07-04 04:45:36 +02:00
Brummel 01925e0619 feat(cli): envelope-probe discoverability + kind-key refusal prose (#193 residue)
The --unwired help line names the whole-envelope starting move (start
from a bare {}), closing the 0106-F1 discoverability gap the narrowed
issue kept open; the wrong-kind refusal names the exact key a blind
author must fix ('the "kind" key must be "process"') instead of
describing the mismatch abstractly. The third original item (block-ids
vs field names in --vocabulary) is sufficiently served by the
envelope probe per the issue's narrowing comment.

RED-first (tdd-author handoff): both pins observed failing on the old
help text / prose.

Gates: workspace 1043/0, clippy -D warnings clean.

closes #193
2026-07-04 04:27:52 +02:00
Brummel 179c2f8bf0 audit: cycle 0109 tidy (drift resolved by ledger/glossary lift + tap-channel cross-pin)
Architect verdict: drift_found — the code holds (C1 honored not
assumed: the nominee re-run's metrics-equality hard refusal; C22/C14
clean: existing TraceStore + unchanged viewer, serde-default widening
round-trips, name composition single-sourced in derive_trace_name).
Resolved here:

- C18: the 0107 paragraph's 'persist_taps is deferred' points forward;
  new cycle-0109 realization paragraph records the closed tap
  vocabulary + UnknownTap tier, the nominee-only non-reduce re-run
  with the C1 metrics guard, the campaign trace family layout, the
  trace_name claim contract, the loud-skip lines, and the noted
  chart-over-family-root debt.
- Glossary: tap entry names the closed vocabulary + escalation rule;
  campaign document's presentation clause references it; campaign run
  gains the trace_name pointer.
- Debt fixed inline (architect med): tap_channel gains the
  emit_vocabulary-twin debug_assert cross-pin so a fifth vocabulary
  tap fails loudly instead of silently skipping.

The consumed 0108 fieldtest spec is removed with the cycle's spec+plan
(all its dispositions shipped: F6 #205, F8 #207, F11 #206, F7/F9/F10
doc-tightens).

Regression: cargo test --workspace 1041/0; clippy -D warnings clean;
cargo doc 0 warnings.

refs #201
2026-07-04 04:22:41 +02:00
Brummel 46a85d3ea4 feat(cli): persist_campaign_traces — campaign presentation persists nominee traces (0109 tasks 3-4)
The deferral line dies; persist_taps is honored: after execute()
returns with a claimed trace_name, the CLI re-runs each cell's nominee
once in non-reduce mode (all four channels drained; windowed to the
nominee manifest's own ns bounds; bound via the shipped
point_from_params inverse against the wrapped probe space), asserts
METRICS equality against the recorded nominee (the C1 drift alarm —
hard refusal on divergence), and writes the requested-and-producible
taps through the existing TraceStore as
traces/{campaign8}-{run}/{strategy8}-{instrument}-w{n}/{tap}.json
(ensure_name_free once; window ordinal doc-derived by position match).
Loud lines: per-cell no-nominee skip, per-run net_r_equity
unproducible skip (no cost leg in the campaign runner), one summary.
The Task-1(b) seam test tightened to pin the deferral line's absence.

Gated e2e extended: persist_taps [equity, r_equity] over GER40 —
trace_name in the record, both tap files on disk, aura chart reads
the cell back — ran its FULL path on this host (local archive
present), not the skip branch.

Gates: workspace 1041/0, clippy -D warnings clean.

closes #201
2026-07-04 04:16:22 +02:00
Brummel b8ef77b1c7 feat(research,registry,campaign): tap vocabulary + trace_name record contract (0109 tasks 1-2)
Task 1: the tap namespace is a closed vocabulary — tap_vocabulary()
(the wrap convention's four sink names; escalation = new entry or an
authored blueprint sink, never an open node-path namespace) with
DocFault::UnknownTap in validate_campaign (index-addressed, prose
enumerating the vocabulary via the single source), SlotKind::TapKinds
on the persist_taps slot, open-slot hint naming the taps. The 0107
deferral seam test split into its two 0109 pins (unknown tap refuses
at validate; a valid tap passes validate and hits the member-data
seam) — the deferral eprintln itself survives until task 3.

Task 2: CampaignRunRecord.trace_name lands serde-default sparse; the
claim contract holds across the seam — execute stamps the sentinel iff
persist_taps is non-empty, append_campaign_run composes
Some("{campaign8}-{run}") onto the stored line (safe prefix take at
the registry seam), execute mirrors the same derivation onto the
returned copy; None stays None; the hand-seeded byte-identity dump pin
survives (absent -> skipped).

Gates: workspace 1037/0, clippy -D warnings clean.

refs #201
2026-07-04 03:41:01 +02:00
Brummel 0bb203a38f feat(cli): aura campaign runs — read-back for stored realizations (fieldtest 0108 F11)
The recorded bootstrap/generalization evidence is no longer write-only:
'aura campaign runs' lists every stored record (campaign id, run
counter, cell count, first cell's stage signature); 'aura campaign
runs <id> [run]' dumps the BARE stored line(s) — the store form,
byte-identical, never the {"campaign_run": ...} run-emit wrapper (the
F10 parsing trap). A lookup, not an action: empty/unmatched notes and
exits 0 (the runs-family convention); outside a project the campaign
family's project refusal.

RED-first (tdd-author handoff): hermetic seed of one hand-written
store line into the fixture project, list + byte-identical dump pinned,
observed failing on clap's unknown-subcommand exit 2.

Gates: workspace 1027/0, clippy -D warnings clean.

closes #206
2026-07-04 02:22:36 +02:00
Brummel 891c40d64b feat(cli): generalize applicability tag + honest R-expectancy refusal prose (fieldtest 0108 F8)
--metrics now tags the four generalizable names (expectancy_r,
net_expectancy_r, sqn, sqn_normalized) via check_r_metric as the
predicate — the registry's own R-expectancy rule, no fourth roster
site (#190). The GeneralizeNonRMetric refusal names the real rule
with the roster pointer instead of mislabeling R-denominated-but-
unranked metrics (max_r_drawdown is a drawdown in R, not a 'pip
metric').

RED-first (tdd-author handoff): the extended --metrics pins and the
flipped prose pins (unit + seam) observed failing on the old output.

Gates: workspace 1026/0, clippy -D warnings clean.

closes #207
2026-07-04 02:14:58 +02:00
Brummel 38ffe50b57 feat(cli): campaign validate runs the executor preflight as a third tier (fieldtest 0108 F6)
valid == runnable for every pure document/campaign property: inside a
project, once intrinsic + referential pass, validate fetches the
referenced process from the store and runs the data-free
aura_campaign::preflight (promoted to pub) — an executable shape gains
'campaign document valid (executable): pipeline shape and static
guards pass'; a shape/param/metric/instrument fault refuses under
'campaign is not executable:' with the exec_fault_prose detail, exit 1,
before any member runs. Outside a project the skip line is unchanged
(the third tier needs the store like the second).

Collateral the RED author predicted: the referential e2e's OK fixture
(PROCESS_DOC: deflate+plateau sweep, overfit_probability gate) was
intrinsically-valid-but-never-executable — that test now registers an
executable twin (argmax/no-deflate, per-member gate metric); the base
PROCESS_DOC fixture stays untouched for all other consumers.

RED-first (tdd-author handoff): the third-tier pin observed failing on
the two-line validate output.

Gates: workspace 1026/0, clippy -D warnings clean.

closes #205
2026-07-04 02:08:28 +02:00
Brummel ab778f2361 feat(campaign,registry,engine): record widenings + preflight v2 fault set (0108 tasks 1-2)
Task 1: RBootstrap and Generalization gain serde derives;
lineage.rs gains StageBootstrap (per_survivor | pooled_oos) and
CampaignGeneralization; StageRealization.bootstrap and
CampaignRunRecord.generalizations land serde-default sparse (a
pre-0108 campaign_runs.jsonl line still parses — pinned), threaded
None/empty through every literal site. No behaviour change.

Task 2: ExecFault::UnsupportedStage is gone; the preflight now admits
the v2 shape sweep [gate]* [wf]? [mc]? [generalize]? via a monotone
rank walk (precise per-pair PipelineShape prose) and statically
refuses: single-instrument generalize (GeneralizeNeedsInstruments),
non-R generalize metrics via check_r_metric (GeneralizeNonRMetric —
no fourth roster site), zero mc params (ZeroBootstrapParam). CLI
prose swapped accordingly. The loop's E2E phase added a tier-boundary
pin: process validate accepts [sweep, mc, walk_forward] while campaign
run refuses it data-free.

KNOWN RED (documented in the plan, flips in task 5):
crates/aura-cli/tests/research_docs.rs::campaign_run_v1_boundary_refuses_mc_process
still pins the retired 'not executable in v1' prose. Interim gates:
registry 57/0, campaign 33/0, cli --bin 111/0, workspace build clean.

refs #200
2026-07-04 00:38:25 +02:00
Brummel 4da47cb919 fix(cli): aura new skips git init inside an existing work tree (0107 fieldtest, orthogonal)
The cargo-new mirror completed: a scaffold landing inside a tracked
tree no longer nests a .git (the gitlink/submodule hazard that bit the
fieldtest corpus); outside a work tree the best-effort init is
unchanged. Oracle: git rev-parse --is-inside-work-tree in the target
dir; any failure reads as not-in-a-work-tree so the init stays
best-effort.

RED-first (tdd-author handoff): the inside-a-work-tree pin observed
failing on the nested .git; the outside guard pinned against
over-removal.

Gates: workspace 1003/0, clippy -D warnings clean.

closes #204
2026-07-03 23:19:34 +02:00