c3d62f2ce39da139733ba9704a01acf763cd0369
89 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c3d62f2ce3 |
feat(cli): graph register / --params accept the op-script form (fieldtest 0107 F5)
Both verbs now route through the same composite_from_any shape
discrimination file-mode --content-id shipped with (op-script array ->
one-build tail; envelope object -> loader), so the three on-ramp verbs
agree on accepted shapes and an op-script registers under the same
shape-invariant content id as its built envelope — no more raw serde
refusal ('invalid type: map, expected u32') hiding the graph build
bridge.
RED-first (tdd-author handoff): two same-seam behavioural pins
(register stores the envelope id for an op-script; --params matches
the envelope's axis lines) observed failing on the serde refusal, then
the two call sites rerouted.
Gates: workspace 990/0, clippy -D warnings clean.
closes #202
|
||
|
|
2968fbef40 |
feat(registry,cli): referential tier requires full open-param coverage; bind refusal speaks the raw namespace
validate == runnable (fieldtest 0107 F7): validate_campaign_refs now
checks the reverse direction — every open param of the resolved
strategy must be bound by some campaign axis — emitting the new
RefFault::ParamNotCovered { strategy, param } with the RAW
param_space() path; ref_fault_prose phrases it ('open param "X" is
bound by no campaign axis'). The executor's every-open-knob-required
rule is thereby mirrored at validate time, and campaign run's
referential gate catches an uncovered knob before any member binds.
The e2e fixture that legitimately under-covered (fast only) gains the
slow.length axis.
Second half, RED-first: the member-bind refusal no longer leaks the
wrapped bind-time path — it names the raw campaign-axis namespace with
the wrapped path as a parenthetical ('open param "threshold" ...
(wrapped path: sma.threshold)').
RED evidence: E0599 on the pinned ParamNotCovered test (tdd-author
handoff), then the flipped bind_axes prose pin observed failing on the
wrapped form.
Gates: workspace 988/0, clippy -D warnings clean.
closes #203
|
||
|
|
367f8678f1 |
feat(cli): #196 blueprint on-ramp — graph register, introspect --params, blueprint-file --content-id (0107 task 10)
Closes the F5 authoring gap: a campaign's strategy ref + axes are now authorable from the public surface without running a sweep first. 'aura graph register <blueprint.json>' puts the canonical form into the content-addressed store (id == topology hash) and prints the stored path via the pub blueprint_path accessor; 'aura graph introspect --params <FILE|64-hex-ID>' lists the RAW composite param_space — the exact namespace campaign axes are validated against (the wrapped --list-axes namespace stays the sweep-verb view); --content-id gains an optional FILE that shape-discriminates a blueprint envelope from an op-list document (stdin behaviour byte-identical without FILE). is_content_id promoted pub(crate) and shared with graph_construct so the two FILE-or-id surfaces cannot drift on the id shape. RED evidence was produced retroactively by the loop (stash-probe: the 5 new tests fail for the expected reasons without the src changes) — ordering deviation noted, outcome unaffected. Gates: graph_ 46/0, workspace 987/0, clippy -D warnings clean. closes #196 refs #198 |
||
|
|
aeb0366aed |
feat(cli): aura campaign run — the executor verb over the MemberRunner driver (0107 tasks 8-9)
campaign_run.rs: target resolution (file is register-then-run sugar; bare 64-hex is the canonical id — #198 decision 1), project gate before any store write, zero-fault referential gate, process fetch + v1 preflight, then aura_campaign::execute with the CLI MemberRunner over the shipped loaded-blueprint convention: per-member blueprint reload, reduce-mode wrap, unique suffix-join of raw campaign axis names onto the wrapped param_space, windowed real data via the shipped ms->ns source seam (absent archive/zero-bar windows -> NoData). Emission: family_table / selection_report lines gated on the doc's emit list (names debug_asserted against emit_vocabulary), the campaign_run record line always, persist_taps deferred LOUDLY on stderr before execution (F7 lesson), zero-survivor cells noted on stderr with exit 0. exec_fault_prose/member_fault_prose keep the Debug-free house seam. Seam tests: 8 campaign_run_* e2e tests incl. outside-project, bogus target, unknown id, v1-boundary (mc process registers fine, run refuses), persist_taps ordering, and the gated real-data sweep->gate->walkforward e2e — which ran its full assert path on this host (local GER40 2024-09 archive) rather than the data-less skip. Also: exec.rs campaign-id guard tightened to lowercase hex, matching is_content_id and the store's self-keyed form (review nit). Gates: workspace 975/0, clippy -D warnings clean. refs #198 |
||
|
|
67aff34923 |
feat(research): std::walk_forward vocabulary corrected to machinery-true fields (0107 task 1)
folds/in_sample_bars/out_of_sample_bars mapped to nothing WindowRoller::new accepts; the block now carries in_sample_ms/ out_of_sample_ms/step_ms/mode (WfMode: rolling|anchored) — the roller's three lengths in the campaign window's own epoch-ms unit plus both shipped RollModes (#198 decision 2). New intrinsic fault ZeroWalkForwardLength (path-addressed) refuses zero lengths at the doc tier, earlier than the roller's NonPositiveLength. Golden canonical pin, PROCESS_FIXTURE, introspection tables, and the aura-cli prose/fixture twins moved together; fieldtest corpus untouched (historical record — stored walk_forward docs get new content ids by design). Gates: aura-research 14/14, aura-cli green, workspace build clean. refs #198 |
||
|
|
ebe85683c9 |
audit: cycle 0106 tidy — ledger/glossary lift, unwired-hint fix, spec/plan retired
Architect drift review (holds: C14 headless authoring, C16/C17 no-DSL with aura-research a true leaf, content-id move behaviour-preserving per the untouched id goldens). Resolution per item: - fix (docs): C18 gains the cycle-0106 realization note (processes/ + campaigns/ stores, document canonical form, the content-id primitive's library home, the referential tier, no-executor status); the #109 analysis-meta-level open thread records the artifact half shipped v1 and what stays open (executor question, verb dissolution 'once it carries', the remaining #188 amendment package). Glossary: new campaign document + process document entries; content id generalized to all three artifact classes; experiment marked superseded by the campaign document (#188 re-cut of the builder-API thread). - fix (code): the --unwired guide advertised an identity_id process ref that validate_campaign refuses — the process.ref open-slot hint now says content-id-only (strategy refs keep both); lib + seam pins updated. - filed forward: #190 (metric-vocabulary single-sourcing, the 0105 roster drift class), #191 (identity-ref store scan is O(store), performance only). - carry-on: regression gates green by hand — cargo test --workspace 916/0, clippy -D warnings clean, cargo doc --no-deps 0 warnings; no baseline to ratify (project has no metric baseline scripts). Cycle 0106 spec and plan retired per convention (git rm); durable record is the ledger notes above plus #188/#189. refs #189 |
||
|
|
a9e047eca7 |
feat(cli): 0106 tasks 11-12 — aura campaign verb family + workspace gates
The campaign half of research_docs.rs: validate (intrinsic always; referential tier when a project env is discovered, with the explicit skip line outside one), introspect (--vocabulary/--block/--unwired/ --content-id, same exactly-one-of usage guard), register via the registry's campaign_path accessor, and RefFault prose (Display-free, unit-pinned). Five binary seam tests (skip line, empty-axis prose + exit 1, section vocabulary, spec-example open slots, content-addressed registration) reuse the file's temp_cwd helper — the plan's tempfile::tempdir() snippet deviated from the crate's zero-extra-dep test convention and was adapted, disclosed in the loop's report. The referential CLI branch inside a real loadable project stays fieldtest territory by the plan's own scope note (project::load needs a built dylib); its logic is pinned by the aura-registry tests. Gates: cargo test --workspace 916/0 (885 pre-cycle + 31 new), clippy -D warnings clean, cargo doc --no-deps 0 warnings. The worked spec example runs end-to-end headless: author both documents as text, validate with per-tier report lines, introspect vocabularies and open slots, obtain stable content ids, register into the store — no Rust compile, no run executed. closes #189 |
||
|
|
db09e5de52 |
feat(cli): 0106 task 10 — aura process verb family (validate/introspect/register)
New research_docs.rs module: role-addressed process verbs with house-style fault prose (Display-free enums phrased at the seam), the introspect exactly-one-of usage guard (exit 2), and content-addressed registration. Seven binary seam tests pin intrinsic-ok/refusal prose, vocabulary/block/ unwired/content-id introspection, exit codes, and the stored file's location under the runs root. Quality-gate finding folded in (and back into the plan): register no longer re-derives the store path from runs_root — aura-registry now exposes process_path/campaign_path, the same single mapping its put/get pair routes through, so the printed path cannot drift from the store layout. Verification: research_docs seam tests 7/0, registry 49/0, clippy clean. refs #189 |
||
|
|
57f401f2ab |
refactor(std): std_vocabulary_roster! macro — one source for the vocabulary roster
The three hand-kept copies of the 22-key zero-arg roster in aura-std/vocabulary.rs (std_vocabulary match arms, std_vocabulary_types list, the test's inline array) collapse into one private declarative macro invoked once with the "TypeId" => Type pairs — the resolver and the enumerable list now agree by construction (the #160 failure mode: a node resolvable but silently absent from graph introspect --vocabulary, or vice versa). Byte-preserving: same fn signatures, same 22 ids, same order; lib.rs re-export and every consumer untouched; the vocabulary stays a closed compiled-in set (invariant 9 / C24 — a compile-time expansion, no registry). Adding a zero-arg node is now one roster line plus the conscious count-pin bumps. Tests: the two unit tests reshaped (round-trip iterates the generated list with PrimitiveBuilder::label() as the independent oracle; the shape test keeps the count pin as deliberate friction). The E2E phase added one cross-boundary pin beyond the plan — kept: graph_introspect_vocabulary_lists_exactly_the_closed_roster_count checks the same count through Env::type_ids() and the CLI print loop across the real process boundary, which no roster-internal test can see. Accepted residual (documented at the roster site, recorded on #160): a new zero-arg node never rostered at all stays unguarded — no enumeration of zero-arg builders exists — and fails safe in both directions (clean UnknownNodeType on load; merely absent from --vocabulary). Verification: cargo build clean; cargo test --workspace 885 passed / 0 failed (884 baseline + 1 new e2e); clippy --all-targets -D warnings clean; cargo doc --no-deps 0 warnings. closes #160, refs #180 |
||
|
|
45fb06dba3 |
feat(engine+cli): topology-identity hash — --identity-id beside --content-id
Engine (aura-engine/blueprint_serde): blueprint_to_json factored into build_doc/serialize_doc (byte-preserving — canonical golden unchanged) and blueprint_identity_json added: the canonical document with every non-load-bearing debug symbol (C23) blanked (composite name, instance names, bound-param names, role names, output re-export names) while everything load-bearing survives (type ids, node order, edges, role targets/order, output pairs/order, bound pos/kind/value — openness stays identity-bearing). Re-exported from lib.rs. Six property tests: renamed twins (equal identity, unequal canonical), open-vs-bound, bound-value, edge-swap, nested-composite interior names, role/output renames — the last two are additive beyond the plan and cover the recursion and role/output arms the planned four did not. CLI (aura-cli): graph introspect gains --identity-id, a sibling of --content-id through the same shared content_id SHA-256 primitive; composite_from_str factored out of build_from_str (fault strings byte-identical). The exactly-one introspect dispatch is DELIBERATELY relaxed: the two id flags form one group and may combine — one build, both ids, one per line, content id first. In-crate cross-path twin test (Rust sma_signal vs op-script twin: distinct content ids, one identity id) beside the existing cross-surface pins; four e2e tests incl. the previously uncovered count!=1 usage exit-2 path. topology_hash, the blueprint store, reproduce, and every --content-id byte stay untouched (spec acceptance 3; all existing pins green). Verification: cargo build clean; cargo test --workspace 884 passed / 0 failed (873 baseline + 11 new); clippy -D warnings clean; doc build 0 warnings. The implement-loop's Task-4 spec-compliance block was a plan-byte count mismatch only (plan under-counted pre-existing blueprint_serde tests 6-vs-7 and did not anticipate the two sibling-accepted extra tests); gates re-run by hand, all green. closes #171, refs #180 |
||
|
|
560f594902 |
feat(cli): aura new — CLI wiring, load-bypass guard, authoring-loop e2e (0103 tasks 2-4)
Command::New + NewCmd + dispatch_new (usage faults exit 2, runtime refusals exit 1, the C14 partition); the main() guard exempts aura new from the eager project load so scaffolding works inside unbuilt trees. E2e (tests/project_new.rs, 6 tests): scaffold -> cargo build -> aura run twice byte-identical with manifest.project.namespace stamped; introspect lists the project type beside std; refusals for existing destination (1), invalid names (2), missing engine path with --engine-path hint (1); aura new inside an unbuilt project tree succeeds (the bypass proof); namespace override reaches the built vocabulary (an additive e2e beyond the plan, kept). Verification: cargo build --workspace clean; cargo test --workspace 873 passed / 0 failed; clippy -D warnings clean. refs #180 |
||
|
|
4928e289f7 |
feat(project): the project-as-crate load boundary (cycle 0102)
A research project is now a loadable external cdylib crate. Inside a directory whose ancestry holds an Aura.toml, aura discovers the project root cargo-style, locates the compiled dylib via cargo metadata (debug default, --release opt-in), loads it load-and-hold, and refuses mismatches before trusting anything: the AURA_PROJECT descriptor (aura-core::project, #[repr(C)]) carries a C-ABI stamp prefix (rustc + aura-core version, baked per consuming build by the new aura-core build.rs) validated before any Rust-ABI field is read. The vocabulary charter gates the merged resolution: project type ids are ::-namespaced (std stays bare), duplicates refuse, and the enumerable type-id list must agree with the resolver, so introspection can never silently omit a project type. All blueprint verbs resolve through the merged project + std vocabulary via a per-invocation Env threaded through the dispatch chains; registry, trace-store, and data paths anchor at the project runs root (Aura.toml [paths], paths-only by design — instrument geometry stays the recorded sidecar, C15). RunManifest gains the Tier-1 project provenance field (namespace + dylib sha256 + best-effort commit), stamped beside topology_hash on the blueprint-run paths; pre-0102 registry lines load unchanged. Default node names strip the namespace, so :: never reaches the param-path address space. Proven by the demo-project fixture (built by the e2e via cargo, path-dep on this workspace): run twice bit-identical, provenance recorded, introspection lists demo::* beside std, registry anchors at the discovered root from a subdirectory; the badcharter fixture proves the charter refusal through the real libloading path; a never-built project refuses with a cargo-build hint. Outside a project every path collapses to the previous literals — goldens and manifest pins byte-identical. Verification: cargo build --workspace clean; cargo test --workspace 862 passed / 0 failed (incl. 7 project_load e2e); clippy -D warnings clean (one precedent-matching allow(too_many_arguments) on run_oos_blueprint, whose arity the Env threading raised to 8); doc build unchanged. Docs/ledger aligned: Aura.toml field lists are paths-only in project-layout.md, glossary, C16/C17; new C13 realization note records the per-invocation-reload reading and the load-and-hold one-shot scope boundary. New deps, per-case review (aura-cli leaf binary only, never the frozen artifact): libloading, toml. refs #180 |
||
|
|
9402afe958 |
polish(cli): normalize usage-message casing to the clap house style
Every hand-rolled usage line now reads 'Usage: aura <verb> ...' — capital prefix plus the program name, the shape clap itself and the built-in run branch already spoke. 10 literal edits: 6 lowercase 'usage:' flips (graph introspect, run blueprint x2, runs family, mc blueprint, mc built-in), 4 bare closures gain the prefix (sweep/walkforward, blueprint + built-in branches); the mc built-in line names the program in both |-separated alternatives. Refusal diagnostics, the conforming run_args_from literal, clap output, and the fieldtests corpus stay byte-identical; exit codes keep the usage=2 / runtime=1 partition. Lockstep pin: the single casing-sensitive assertion (mc --real) flips to capital 'Usage'. Three additive regression tests pin the house style itself (bare-closure prefix, both mc alternatives, mc blueprint branch) — the pre-existing pins checked only verb tokens / a single Usage occurrence; deviation from the spec's no-new-test-bytes wording recorded on the issue. Verification: cargo test --workspace rc=0 (cli_run 133 passed), clippy -D warnings clean, grep gates exact (zero lowercase 'usage:' literals; 11 'Usage: aura' source lines), smoke 'aura sweep --strategy bogus' prints the house-style line and exits 2. closes #179 |
||
|
|
4de6d5cbad |
rename: retire the stage1-* family for the r-family (r-sma / r-breakout / r-meanrev)
The stage1 ordinal has been dead vocabulary since the C10 reframe dropped the two-stage research model: a 1 structurally implies a 2 that no longer exists. The family is renamed by its live discriminator - the R yardstick - with members named by their signal, uniform with their signal-named siblings (sma, macd, momentum): - selectors: stage1-r -> r-sma, stage1-breakout -> r-breakout, stage1-meanrev -> r-meanrev (old tokens are usage errors, exit 2 - no silent alias) - identifiers: Strategy::RSma/RBreakout/RMeanRev, HarnessKind::RSma, r_sma_*/r_breakout_*/r_meanrev_*, wrap_r, run_signal_r, RGrid, R_SMA_* - persisted identity: the sma_signal composite (param prefix sma_signal.fast.length / .slow.length; fixtures regenerated; content-ids shift - no test pins a literal hash, the registry parses no record names) - e2e test files git-mv'd to r_sma_e2e / r_breakout_e2e / r_meanrev_e2e - dead Stage-1/Stage-2 prose reworded to post-C10 vocabulary across rustdoc, Cargo.tomls, ledger live lines, and the glossary (historical entries stay; fieldtests corpus untouched) - CLAUDE.md invariant 7 rewritten to record the ratified C10 reframe faithfully (the token-swap alone would have laundered the retired gated-currency/realistic-broker design into unmarked live prose); the unbacked account-mode clause dropped Verification: cargo build/test --workspace green (51 targets), clippy -D warnings clean, doc build clean, acceptance grep gate leaves exactly the one resampling-stage false positive (harness.rs), smoke: --harness r-sma runs, --harness stage1-r exits 2 with the new usage line. Decision log: forks and rationale recorded on the issue (reconciliation + implementation-phase comments). closes #174 |
||
|
|
88e9c9a539 |
fix(cli): symmetric sweep/mc family-verb polish (closed error, mc seed label, mandatory-knob docs)
Three polish items surfaced by the World/C21 milestone fieldtest:
- A sweep of a fully-bound (closed) blueprint is refused up front with a clear
"this blueprint is fully bound; nothing to sweep" message — the symmetric
inverse of blueprint_mc_family's closed-blueprint guard — instead of leaking a
misleading UnknownKnob for a knob that is not unknown but bound out.
blueprint_sweep_family's error contract widens to String; the sweep terminal's
BindError (a genuine unknown/kind-mismatch axis) still surfaces verbatim.
- mc reproduce lines now carry each member's seed=<N> label. Monte-Carlo members
hold no tuning params, so the member label was blank; the seed is each draw's
realization identity. Sweep / walk-forward labels (which echo params) unchanged.
- docs (design ledger + glossary): state that every open knob --list-axes prints
is mandatory on sweep / walkforward — a subset is refused with the missing knob
named (BindError::MissingKnob), there is no default; pin a knob you do not want
to vary with a single-value axis.
RED-first for the two behaviour items. One existing unknown-axis E2E is re-pointed
from the closed fixture (which now hits the new "nothing to sweep" pre-check) to
the open fixture; it still exercises UnknownKnob("nope") specifically — resolve
raises UnknownKnob in phase 1 before MissingKnob in phase 2 — so the property is
preserved, not weakened.
closes #178
|
||
|
|
e20dd9ae49 |
Merge main into the clap-adoption cycle — re-integrate the two parallel CLI fixes
main advanced with two `fix(cli)` commits ( |
||
|
|
fa42bf3878 |
feat(0099): CLI exit-code split (iteration 2) — usage=2, runtime=1
Apply the clean exit-code partition (#175 deviation #8) on top of the clap migration: runtime failures now exit 1, usage errors stay exit 2, with no same-class inconsistency. Partition (attribution principle): exit 2 = a command-line fault (a bad flag value, an inapplicable combination, or the content of an argv-named file); exit 1 = the command was well-formed but the environment / recorded state it needs is missing, or piped stdin data is bad. The 6 boundary cases the spec's "parse-time vs run-time" rule under-specified are resolved on #175 (malformed/open argv blueprint -> usage 2; stdin op-script content -> runtime 1; applicability refusals -> usage 2; unknown --axis / --metric -> usage 2). Flipped 2->1 (runtime): no local data, no recorded geometry, no recorded run/family, "run has no tap named", trace-name collision, family/trace persist-write failures (normalizing the lone generalize inconsistency -- all persist -> 1 together), missing/corrupt content-addressed store state (the reproduce path), chart-read failures, and graph stdin op-script content + stdin-read I/O. reproduce-diverged stays exit 1. Usage sites (clap parse, argv-applicability guards, the dual-grammar blueprint-file read/parse) stay exit 2. Tests: ~8 domain-refusal pins flipped Some(2)->Some(1) (message substrings unchanged), the four runtime tests renamed _exit_2 -> _exit_1, and a partition property test added. Orchestrator fix after the loop: 3 sibling no-data skip-guards (run_real_with_trace + the two generalize cross-instrument tests) that the plan's Task-1 list missed -- left at Some(2), they would fall through to assert Some(0) and FAIL on a data-less machine; flipped to Some(1) for consistency with the flipped code. Deferred (cosmetic, filed forward): full "Usage:" / "usage:" / bare error-message casing normalization -- no functional impact, high pin-churn. Verified green (orchestrator, this session): cargo build --workspace, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace all clean (0 failed across every test binary). This completes the #175 clap-adoption cycle (iteration 1 = clap migration at 366170a; iteration 2 = this exit-code split). refs #175 |
||
|
|
366170aeab |
feat(0098): CLI clap migration (iteration 1) — scoped help, --version, GNU flags
Replace the hand-rolled aura-cli argv parser with a clap derive parser. Delivers, all from one declarative source: - scoped `aura <sub> --help` (stdout, exit 0) — each subcommand's own Options section; retires the #131 uniform-global-blob help - `--version` / `-V` → `aura 0.1.0` (stdout, exit 0) - GNU `--flag=value`, the `--` end-of-options terminator, and long-option abbreviation (`--harn` → `--harness`) Architecture: one root Cli/Command enum, one `*Cmd` struct per subcommand. The four dual-grammar subcommands (run/sweep/walkforward/mc) map the loaded-blueprint branch as an optional `[blueprint]` positional + a post-parse `is_file()` dispatch on a single-sourced predicate; each built-in handler re-asserts a stray-positional guard (refuse-don't-guess) so clap's optional positional cannot silently swallow a typo (pinned by two new E2E tests). The execution layer (run_*/emit_*/runs_*/reproduce_*) is untouched — only arg-plumbing changes, via thin `*_from` adapters reusing the existing value helpers. Exit codes behaviour-preserved: clap parse errors exit 2 (matching the old usage-error=2); domain refusals stay exit 2; reproduce-diverged stays exit 1. The exit-code split (2=usage / 1=runtime) is iteration 2. clap is admitted under the C16 per-case review (research-side CLI, invariant 8 untouched — a dev-loop compile tax, not a frozen-artifact tax). Forced plan corrections (implementer; verified against the diff): - Step 6's deletion of the `parse_*_args` fns orphaned ~34 grammar unit tests that called them; those were deleted and that grammar coverage moved to the renegotiated cli_run.rs E2E pins (`parse_select` kept + retested). - run_malformed_cost_value_...: a bad cost value is now a clap value-parse error (no "Usage:" line); renegotiated to pin exit 2 + empty stdout + the flag name "--cost-per-trade"; the units note stays pinned via `run --help`. - `allow_hyphen_values` on the three cost flags so `--cost-per-trade -0.5` reaches the non-negativity guard (else clap rejects -0.5 as an unknown option). - Deleted the now-dead `RealWindowGrammar` (clippy -D warnings). Orchestrator additions after the loop: - Enabled long-option abbreviation (`infer_long_args` on the root — one attribute, it propagates to subcommands) + a test, closing spec acceptance criterion 4 which the plan's RED tests did not cover. Decided to deliver rather than amend the spec: the two ratified decisions conflict on this low-tier item — Fork B (GNU compliance, getopt_long abbreviates) vs F5 (LLM/automation-first, human-only niceties deprioritized) — and the cost tiebreaker (one attribute, not per-struct work) resolves to deliver. Carry-on debt (not this iteration): the error-message "Usage:"/"usage:"/bare casing is now mixed (clap's capitalized "Usage:" beside preserved lowercase aura messages); a deliberate quality-hold since the casings are pinned by renegotiated/preserved tests. Cosmetic; a candidate for the iteration-2 exit-code-split cleanup. Verified green (orchestrator, this session): cargo build --workspace, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace all clean (0 failed across every test binary, including the renegotiated cli_run pins and the new --version / scoped-help / GNU-flag / dual-grammar tests). refs #175 |
||
|
|
6a775f700f |
fix(cli): walkforward validates its --axis grid once, not once per window
`aura walkforward <bp> --axis <name>=<v>` emitted a rejected axis's diagnostic more than once (a racy 1-3x) when the grid did not resolve. `blueprint_walkforward_family` resolved the axes *inside* the per-window closure, and `walk_forward` fans that closure out across the windows in parallel — so several windows each `eprintln!` the same `BindError` and `exit(2)` before any one exit tears the process down. Axis resolution is window-independent, so it is hoisted to a single dispatch-boundary pre-flight (mirroring `aura sweep`, which validates its axes once before any member runs): the first in-sample window is resolved up front, surfacing the `BindError` exactly once; the per-window closure then resolves already-validated axes and cannot re-raise (`expect`). RED-first: cli_run.rs `aura_walkforward_emits_an_unknown_axis_rejection_once` drives the verb 20x and asserts the rejection is single every run (the emit count was racy, so a single invocation would be a flaky assertion). Surfaced by the World/C21 milestone fieldtest. closes #177 refs #170 |
||
|
|
dec07809df |
fix(cli): aura run refuses an open blueprint with a clean exit 2, not a panic
`aura run <blueprint.json>` bootstraps the loaded signal over the EMPTY param point (a closed-blueprint assumption) and `.expect()`s `compile_with_params`, which panics (exit 101, ParamArity) when the blueprint carries >=1 free knob. The sibling `aura mc` already refuses an open blueprint cleanly at the dispatch boundary; `run` now does the same. The `["run", ..]` `.json`-discriminator arm probes the wrapped param_space (`blueprint_axis_probe`, the single source of that wrap) and, when it is non-empty, emits a "run requires a closed blueprint … N free knob(s) — bind them or use `aura sweep --axis`" diagnostic and exits 2 — mirroring `blueprint_mc_family`'s closed-guard. No panic on a naive consumer's first move (author an open blueprint, run it). RED-first: cli_run.rs `aura_run_rejects_an_open_blueprint_without_panicking` pins exit 2 + the named requirement + no `panicked`. Surfaced by the World/C21 milestone fieldtest. closes #176 refs #170 |
||
|
|
ff20f74e8f |
feat(0097): IS-refit walk-forward over a loaded blueprint — aura walkforward <bp.json> --axis
Ship the World/C21 milestone's last piece (Arm A, #173): a true in-sample-refit walk-forward over a loaded blueprint. `aura walkforward <blueprint.json> --axis <name>=<csv> [--axis …] [--select argmax|plateau:mean|plateau:worst]` rolls 24/12/12 IS/OOS windows over the synthetic span; per IS window it re-optimizes the blueprint's params over the user's --axis grid (the #169 prefixed names), selects the winner by sqn_normalized, runs it out-of-sample, and aggregates the per-window OOS reports into a FamilyKind::WalkForward family — content-addressed (put_blueprint) and aura-reproduce bit-identical. Structure mirrors the hard-wired stage1-r walk-forward arm one substitution deep: the generic engine walk_forward driver + select_winner + the synthetic schedule are reused; only the per-window IS-sweep (blueprint_sweep_over) and OOS-run (run_oos_blueprint) source the loaded blueprint via cycle-0096's blueprint_axis_probe. reproduce_family_in gains a WalkForward arm reconstructing each OOS window's windowed slice from the member manifest's window bounds (winner params via the shared manifest->cells recovery, as the MonteCarlo/Sweep arms do). Panic-safety: blueprint_sweep_over returns Result<_, BindError>; a bad/unknown --axis is a clean in-closure exit 2 (mirroring the hard-wired arm's select_winner handling), never a panic — pinned by the rejects_an_unknown_axis E2E. A no-axis invocation (nothing to re-fit) is a usage error; a malformed blueprint is rejected at the dispatch boundary (exit 2, no panic). Reduce-mode members carry R-metrics (no raw pip curve) → empty stitched equity; oos_r is the meaningful summary (C10 R-first yardstick). Held quality finding adjudicated: run_oos_blueprint's plan-prescribed redundant `pip: f64` (== data.pip_size()) removed — derived internally to match its siblings run_oos_r / blueprint_sweep_over, which also drops the too_many_arguments band-aid #[allow] (8->7 args). Two plan transcription typos the loop caught and fixed verified correct: BindError renders via Debug ({e:?}), parse_select via map_err (Result, not Option). Verified myself: cargo build --workspace, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace all green; 2 new unit tests + 5 new cli_run E2Es (persist shape, reproduce bit-identical, no-axis, malformed-safety, unknown-axis) pass; no regression in the hard-wired walkforward / sweep / mc / reproduce suites. Engine/registry untouched (invariants 1/2/8/9). closes #173 |
||
|
|
bd2003e217 |
feat(0096): list a loaded blueprint's sweepable axes — aura sweep <bp.json> --list-axes
A data-level author can now discover a loaded blueprint's open sweepable knobs before sweeping: `aura sweep <blueprint.json> --list-axes` prints one `<name>:<kind>` line per open knob, in param_space() order, then exits 0. Before this the only way to learn the names was to guess a `--axis` and read the MissingKnob/UnknownKnob error — which names only the rejected knob, never the valid set. Prerequisite for #173 (IS-refit walk-forward must know which axes to re-fit per in-sample window). Design (derived, recorded on #169): the query lands on the SWEEP verb, not `graph introspect`, because the accepted axis names are produced by the sweep's own harness-wrapping (wrap_stage1r nests the loaded signal as a BlueprintNode::Composite named "stage1_signal", so collect_params prefixes them — stage1_signal.fast.length, not the raw fast.length). Only the sweep verb owns that wrapping. A new blueprint_axis_probe helper single-sources the wrapped probe and now subsumes the two previously-inline copies (blueprint_sweep_family + blueprint_mc_family), so listed == swept by construction (and stays so across #159's harness retirement) — no second source of truth. --list-axes is a standalone query (valueless flag handled before the per-flag value-consume; rejected with exit 2 if combined with any other flag). A closed blueprint prints nothing (exit 0). A malformed blueprint is rejected at the existing dispatch boundary (exit 2, UnknownNodeType), never a panic. Tests: unit blueprint_axis_probe (prefixed names + empty for closed) + unit parse grammar (standalone), and five cli_run E2Es — prints names, closed-empty, rejects-other-flags, malformed-safety (no panic), and a round-trip that DERIVES the names from --list-axes and feeds them back into a working sweep (pins the listed==swept invariant #169 exists to close). Verified myself: cargo build --workspace, cargo test --workspace, cargo clippy --workspace --all-targets -D warnings all green; no regression in the refactored sweep/mc probes. Engine and registry untouched (invariants 1/8/9). closes #169 |
||
|
|
14e27a978b |
feat(0095): monte-carlo over a loaded blueprint — aura mc <bp.json> --seeds N
The MC half of #170 (World/C21): `aura mc <closed blueprint.json> --seeds N` builds a FamilyKind::MonteCarlo family from a loaded CLOSED blueprint. Each seed draws a distinct synthetic price walk (synthetic_walk_sources, the mc_family pattern), so members differ in the DATA (not just a manifest label) — the sim is deterministic (C1), so MC variation must come from re-drawing the input. Draws run disjointly in parallel via the shared monte_carlo helper (invariant 1). The canonical blueprint is stored once keyed by the shared topology_hash (the 0094 hook), and reproduce_family_in gains a MonteCarlo realization branch that reconstructs each member's seed-driven walk from manifest.seed — so a persisted mc family 'aura reproduce's bit-identically (the store-hook proof). Closed-blueprint only: MC binds no axis, so a free knob has no binder; an open blueprint yields a named Err (rendered to stderr + exit 2 at the run_blueprint_mc boundary, mirroring blueprint_sweep_family — no hidden process exit in the pure builder), pre-empting the compile_with_params arity panic. Two quality-gate-driven improvements over the ratified plan, both applied and verified: (1) delegate the disjoint draws to the engine monte_carlo seam instead of a hand-rolled loop (my plan's 'closure-shape mismatch' rationale was wrong — the Fn(u64,&[Scalar]) closure ignores the unused base_point, exactly as mc_family does); (2) the closed-blueprint guard returns Result rather than exiting inside the builder, matching the sweep sibling's contract and making the rejection unit-testable. Engine + registry untouched (invariants 8/9). Verified: cargo build --workspace clean; cargo clippy --workspace --all-targets -- -D warnings clean; cargo test --workspace green (5 new tests: the parser, anti-degenerate family, MC reproduce round-trip, and two E2Es incl. the open rejection; no regressions). refs #170 |
||
|
|
cec7811c32 |
audit(0094): cycle close — content-addressed reproduction; ledger refreshed, coverage closed; rm ephemeral spec/plan
Architect drift review (4751d4b..HEAD): drift_found — ledger-currency only; the
code is clean.
What holds (architect, confirmed by diff + green tests):
- Inv 8 / C9: aura-engine byte-untouched; content_id + sha2 in aura-cli, the
store in aura-registry.
- Inv 9: Registry::{put,get}_blueprint is a dumb bytes-by-key store (opaque
String keyed by String hash; no sha2, no parse, no sha256==hash verify — the
caller owns the hash). No domain knowledge crept into the engine or registry.
- C1: the run_blueprint_member extraction is behaviour-preserving (keystone
blueprint_sweep_member_equals_single_run_and_shares_topology_hash unchanged +
green); serde_json/float_roundtrip serves determinism (disk==recomputed), not a
mask (1-ULP canary green).
- C11/C12: one blueprint stored per family, keyed by the shared topology_hash.
Resolutions:
- [ledger] Refreshed C18 + C24: added the cycle-0094 Realization (the
content-addressed store runs/blueprints/<hash>.json + aura reproduce), and
retired the now-false "content-addressed identity / full reproduction deferred"
claims (INDEX.md:1261, C24 deferred block). Only the structural-axis /
whole-harness content-id (and the debug-name-in-id question) genuinely remains,
filed forward (#171, #170).
- [debt->closed] Added the two untested refuse-don't-guess E2E arms
(aura_reproduce_rejects_a_non_generated_family — a hard-wired sweep member has
no topology_hash; aura_reproduce_rejects_a_missing_stored_blueprint — the store
was deleted), and a cross-surface unit pin
(topology_hash_is_the_content_id_of_the_canonical_form) so the two acc-1 paths
cannot silently drift.
- [scaling] MC/walk-forward-from-blueprint (#170) will need the same put_blueprint
hook or reproduce won't cover them — noted on #170.
No regression scripts in this project (Test is the gate): full workspace suite
green (51 suites), clippy -D warnings clean, binary verified end-to-end.
Ephemeral spec/plan git-rm'd per the lifecycle convention; durable rationale is in
the ledger. The World/C21 milestone stays open (#170 remains).
closes #158
|
||
|
|
717a0b70af |
feat(0094b): content-id surface + Tier-1 stability — aura graph introspect --content-id (iter 2)
Completes #158 acceptance (acc 1 + acc 3) on top of iteration 1's reproduction: - acc 1: `aura graph introspect --content-id` reads an op-script and prints the content id of its canonical blueprint — the SHA256 (hex) of the same blueprint_to_json bytes `graph build` emits. Extracted the single `content_id` primitive shared by this surface AND topology_hash, so the two command paths (this surface / hashing a `graph build` output) agree by construction. Verified live: both e2be81b2… for the SMA-cross op-script. Deterministic + distinguishes topologies; a malformed op-list fails cleanly (exit 2, no partial hash). - acc 3: a Tier-1 optional field the blueprint does not use is tolerated by the loader (#156) and absent from the canonical omit-defaults form, so the content id is unchanged (content_id_is_stable_across_a_tolerated_tier1_field); plus content-id stability across the serialize -> reload -> re-serialize store round-trip (#164), the property reproduction rests on. Finding (documented, filed forward): an op-script and the Rust `stage1_signal` builder produce DIFFERENT canonical forms — the composite debug-name ("graph" vs "stage1_signal"), a named vs unnamed Sub, and an unbound vs bound Bias.scale — so they are different topologies by the byte definition and get different content ids. Content-addressing keys on the canonical form, which currently includes the composite debug-name (a non-load-bearing symbol, invariant 11). Whether the content id should exclude the debug-name (a topology-canonical form distinct from the byte-canonical form, which would also change the shipped cycle-0092 topology_hash values) is a forward design question, not this cycle. Verified: full workspace suite green (51 suites); clippy -D warnings clean. refs #158 |
||
|
|
008692c043 |
feat(0094): content-addressed reproduction — aura reproduce <family-id> (iter 1)
Restore C18 reproducibility for generated (blueprint-sweep) runs (#158, acc 2): a sweep's topology is stored once, content-addressed by the topology_hash the manifest already carries, and `aura reproduce <family-id>` re-derives every persisted member bit-identically (C1) from the stored blueprint. - aura-registry: a dumb content-addressed bytes store — Registry::put_blueprint / get_blueprint / blueprints_dir over runs/blueprints/<hash>.json. No sha2 dep (caller owns the hash; reproduction's bit-identical compare is the integrity check); absent id -> Ok(None) (treat-as-empty, like load). - aura-cli: run_blueprint_member extracted from blueprint_sweep_family's member closure (behaviour-preserving; keystone stays green) so the sweep AND reproduce re-run the identical reduce-mode path — bit-identity by construction. The store write hooks the sweep persist seam (one blueprint per family, shared topo). reproduce_family_in loads each member's blueprint by hash, reconstructs the point from the recorded params (point_from_params, inverse of zip_params, over the WRAPPED signal's param_space so the prefixed knob names match), re-runs, and compares metrics. `["reproduce", id]` dispatch arm; refuse-don't-guess on an unknown id / missing stored blueprint (exit 2), DIVERGED -> exit 1. Dependency decision (per-case review, ratified): enabled serde_json `float_roundtrip`. Stored member metrics round-trip through families.jsonl as f64 JSON; the default parser can be 1 ULP off, which would make disk-loaded != recomputed and "bit-identical" (C1) physically impossible. The feature guarantees exact f64 round-trip. Blast-radius clean (full workspace suite green); it serves determinism, the invariant reproduction rests on. Canary: f64_blueprint_param_survives_store_round_trip_bit_identically. Two plan bugs caught + fixed by the implement loop and verified: reproduce's param_space must come from the wrapped signal (prefixed names, the #167 lockstep), and the family id is 0-indexed (live-0 / smacross-0, not -1). Verified: full workspace suite green (51 suites); clippy -D warnings clean; and the binary end-to-end — `aura sweep` (3 members, one shared stored blueprint) then `aura reproduce live-0` (3/3 bit-identical incl the open-at-end member), unknown id -> exit 2. acc 1 (graph introspect --content-id) + acc 3 (Tier-1 id stability) land in iteration 2 (plan 0094b). refs #158 |
||
|
|
481add0ca2 |
feat(0093): sweep-from-blueprint — aura sweep <blueprint.json> over named axes
Cycle 2 of the World/C21 milestone (#166): the World now orchestrates FAMILIES from blueprint-data. `aura sweep <blueprint.json> --axis <name>=<csv> …` loads an OPEN signal blueprint, grids the by-name axes against its param_space, builds each member via cycle-1's wrap_stage1r seam, and aggregates to a FamilyKind::Sweep family — byte-identical in shape to the hard-wired sweep. - `parse_sweep_blueprint_args` + `SweepBlueprintArgs`: repeatable `--axis name=csv` (lexed i64-then-f64; resolve_axes kind-checks), `--name`/`--trace`, the shared `--real/--from/--to` accumulator → DataChoice. - `blueprint_sweep_family` (pure) + `run_blueprint_sweep` (IO) mirror stage1_r_sweep_family with three deviations: by-name axes (verbatim param_space names, e.g. `stage1_signal.fast.length`), reload-per-member (Composite is !Clone — its build closures are Box<dyn Fn>, #164 — so a fresh signal is re-loaded from the doc per member), and a per-member shared topology_hash. Members share the hash (same signal topology, only params vary); member_key distinguishes them. - The `aura sweep <file.json>` `.json`-discriminator arm mirrors cycle 1; the hard-wired `--strategy` sweep is untouched (#159). Task-3 design gap resolved: a sweep needs an OPEN blueprint (open knobs to grid), distinct from cycle-1's fully-bound run fixture (a bound blueprint has an empty param_space). New `tests/fixtures/stage1_signal_open.json` = blueprint_to_json(stage1_signal(None,None)); a sweep must bind ALL open knobs, so the E2E grids fast×slow (2×2 = 4 members). Verified end-to-end by running the binary: `aura sweep stage1_signal_open.json --axis stage1_signal.fast.length=2,4 --axis stage1_signal.slow.length=8,16 --trace f` persists a discoverable 4-member Sweep family; an unknown axis fails clean. Member equivalence vs run_signal_stage1r + shared-hash/distinct-member_key unit-tested. Full workspace suite green; clippy --all-targets -D warnings clean. refs #166 |
||
|
|
2b1c24668d |
test(0092): run-from-blueprint E2E + bit-identical keystone + demo fixtures
Closes the cycle-1 test coverage for #165. The `aura run <blueprint.json>` CLI arm itself (the .json dispatch + parse_blueprint_run_args + BlueprintRunArgs) shipped in ff4a1b3; this adds the coverage that proves it and the in-repo demo blueprint: - crates/aura-cli/tests/fixtures/stage1_signal.json — the canonical SMA-cross signal (blueprint_to_json of stage1_signal(2,4)); emit_demo_signal_fixture (#[ignore]) regenerates it. - crates/aura-cli/tests/fixtures/unknown_node.json — one type set to "Nope" for the fail-clean guard. - loaded_signal_runs_bit_identical_to_rust_built (the keystone, C1): a signal serialized -> loaded via blueprint_from_json -> run is bit-identical to its Rust-built twin, incl. topology_hash (64-hex). - topology_hash_is_stable_and_distinguishes (#158 anchor property). - cli_run.rs E2E: `aura run <demo>` exits 0 with topology_hash in the manifest; an unknown-node blueprint fails clean with UnknownNodeType (the #160 closed-set guard at the data-plane face, invariant 9). Verified end-to-end by actually running the binary: `aura run stage1_signal.json` -> RunReport with a 64-hex topology_hash, sim-optimal+risk-executor broker, R metrics; `aura run unknown_node.json` -> UnknownNodeType("Nope"), exit 2. Full workspace suite green; clippy --all-targets -D warnings clean. refs #165 |
||
|
|
b8b83cf1c9 |
feat(0091): byte-canonical blueprint emit; record the equality-surface decision
#164 — tighten the C24 "canonical, versioned" blueprint contract on two corners the milestone fieldtest surfaced. Both forks were derivable; resolved with rationale (recorded on #164). Fork 1 — canonical artifact carries no trailing newline. `blueprint_to_json` returns the JSON value with no trailing newline (647 bytes for the SMA-cross fixture); `aura graph build` had framed it with a `println!` (648). The library serializer is the single canonical source — the form content-addressed topology identity (#158) hashes — so the CLI is a transport that must not mutate the bytes: `build_cmd` now `print!`s the canonical bytes verbatim, making the CLI and library emit paths byte-identical. RED-first: the new E2E `graph_build_emit_is_byte_canonical_no_trailing_newline` pinned the trailing newline (FAIL), green after the one-line change. The two cycle-0088 `.out.json` goldens are re-recorded at 647 bytes. The loader stays lenient (a trailing newline on input still parses, Tier-1 robustness; the milestone fieldtest's `mt_4` tolerance check is unaffected). Fork 2 — the canonical JSON is the blueprint equality/identity surface; no second in-memory PartialEq/Debug is added. `Composite`/`BlueprintNode` cannot derive them: `PrimitiveBuilder` (aura-core node.rs) holds a `build: Box<dyn Fn(&[Cell]) -> Box<dyn Node>>` closure, neither comparable nor printable. Equality could only be defined over the serialized data, of which `blueprint_to_json` is already the single source; a separate in-memory equality notion would be a redundant second source of truth and a drift hazard against the form #158 hashes. Recorded the decision (discharges acceptance box 2's "or JSON-string is the intended equality surface" arm) in the C24 ledger; no code added for it. Verified: cargo test --workspace green (51 suites, incl. the new RED->green E2E); cargo clippy --workspace --all-targets -D warnings clean. closes #164 |
||
|
|
3c4b667955 |
feat: construction add names a node via name, mirroring the builder (#157)
The op-script authoring form is meant to be the Rust builder lifted into data —
every op is a 1:1 image of a GraphBuilder method. The one un-builder-ish key was
`add`'s `"as"`: the builder names a node with `.named("fast")`, so the data echo
is `"name"`, not the SQL-ish aliasing word `as`. Renamed the `add` wire key
`as -> name`.
`expose` keeps `as` — there it is a genuine alias (`expose bias.bias as bias`,
mirroring the builder's positional name arg). `source`/`input` keep their terse
verbs: those are plain shortenings of `source_role`/`input_role`, not a foreign
concept the way `as`-for-naming was.
CLI-side only: a one-attribute serde rename on the `OpDoc` Add DTO
(`#[serde(rename = "name")]`), since the engine `Op` is serde-free (the engine
field stays `as_name`, untouched). Updated the op-list test documents to the
`name` key (add ops only; expose's `as` left intact) and added an explicit pin
(`add_names_node_via_name_key`). The cycle-0088 fieldtest corpus
(fieldtests/cycle-0088-construction-op-script/*.json) is left on the old `as`
form as historical cycle-0088 evidence (it is not run by the suite).
Verified: full workspace suite + clippy green; `aura graph build` on a `name`-keyed
doc names the node and emits it in the #155 blueprint.
|
||
|
|
f1ab81d1b8 |
feat(0089): construction diagnostics read by-identifier (closes #162)
The cycle-0088 fieldtest found three presentation defects on the construction
op-script surface, all where the surface dropped out of its by-identifier register.
Presentation only — every fault still fires identically (same exit code, same
op/finalize attribution); only the rendered cause changes.
- Item 1 (finalize by-identifier). The holistic finalize fault leaked the raw
index Debug form (`UnconnectedPort { node: 2, slot: 1 }`). `GraphSession::finish()`
now translates the three reachable index-carrying CompileErrors into new
by-identifier OpError variants (UnconnectedPort{node,slot} / RoleKindMismatch{role}
/ UnboundRootRole{role}) using the session's `ids`/`schemas` and the composite's
`input_roles()` — the holistic gate calls (validate_wiring,
check_param_namespace_injective, check_root_roles_bound) stay byte-for-byte
unchanged (C24: no second validator; only the `.map_err` closures changed).
`aura graph build` now prints `finalize: slot sub.rhs is unconnected`.
- Item 2 (bind mismatch prose). `format_op_error`'s BadParam arm matches the
BindOpError variants instead of `{:?}`: `op 1 (add): param fast.length expects I64
but got F64`, matching the connect mismatch register.
- Item 3 (discoverable bind form). `introspect --node` shows the typed-Scalar bind
form: `param length:I64 (bind {"I64": <v>})`.
Item 1 spans both crates atomically (adding OpError variants forces the exhaustive
format_op_error match; the finish() translation flips behavioural test pins in both
crates). Five test touches: the two in-file unit tests, the two E2E twins plan-recon
found that the spec's testing strategy under-enumerated
(construction_e2e.rs, tests/graph_construct.rs), and a new unbound-root-role test;
the implementer added RoleKindMismatch / Ambiguous/UnknownParam coverage too. The
CLI finalize test was tightened (orchestrator) to pin `sub.rhs` and the absence of a
raw `node:` index.
Spec boss-signed on a grounding-check PASS (docs/specs+plans/0089). Verified: full
workspace suite + clippy green; the three messages reproduce exactly against the
built binary on the fieldtest fixtures.
|
||
|
|
25e452aaf7 |
feat(0088): §C construction op-script CLI — aura graph build/introspect
Iteration-2 §C of the construction service (#157), the CLI shell over the iteration-1 engine core ( |
||
|
|
d925a747cb |
feat(0086): cost-flag CLI ergonomics — units note, named diagnostics, non-R-harness guard
#153 (friction surfaced by the milestone-#148 close fieldtest). Three cost-flag fixes on `aura run`, all on the pure parse_run_args grammar: - Units discoverable: a COST_FLAGS_NOTE appended to the run usage + --help (price units, charged in R as cost/|entry-stop|, per-ENGINE-cycle carry, >= 0, stage1-r only). - A negative rate names the cause + flag ("<flag> must be non-negative, got <v>") via a factored parse_nonneg_rate helper, not a bare usage dump. - A cost flag on a non-R harness (sma/macd) is refused (exit 2, "cost flags require an R-evaluator harness (stage1-r)") instead of silently ignored — the named spec_gap, decided reject/fail-loud (refuse-don't-guess, recorded on #153), pinned by a new C10 ledger note. Internal RunArgs/RunData/HarnessKind gain #[derive(Debug)] (behaviour-neutral) so the pure parse_run_args unit tests can use expect_err. Verified independently: cargo test -p aura-cli green, the eight stage1-r cost goldens byte-unchanged; clippy -p aura-cli --all-targets -D warnings clean. Only the grammar/usage strings and the previously-unguarded sma-cost path changed; the cost-model graph and R math are untouched. closes #153 |
||
|
|
f5e00a9c72 |
feat(0085): per-cycle-held accrual — CarryCost + ChargeMode (approach B)
Cycle 5 of milestone #148 (cost-model graph in R): the per-cycle-held accrual mechanism — C10's "per-cycle-held factors accrue over the hold". A carry/funding cost is charged for every cycle a position is held, so the net_r_equity equity curve bleeds continuously over the hold (approach B, "ja, mach B") rather than stepping at close. Mechanism (logged on #148): ChargeMode { AtClose, PerHeldCycle } is a property of the cost factor, read by the one shared CostRunner (no second runner type). The AtClose arm is the pre-cycle-5 eval tokens VERBATIM (IEEE-754 byte-identity). The PerHeldCycle arm accrues `per` into `acc` each held cycle, dumps the accrued total into `cum` at close (resetting acc), and marks the open position via a growing open_cost_in_r. The bleed lives in open_cost_in_r, which the net_r_equity tap already subtracts — so summarize_r and the CLI net_eq wiring are UNCHANGED, and the cycle-0083/0084 net_expectancy_r goldens (-614.3134020253314 flat, -615.0304388396047 composed) + the C18 no-cost golden stay byte-identical. CarryCost is a ConstantCost twin differing only in charge_mode() -> PerHeldCycle (a labelled stress parameter, the flat base of the accrual family). Wired through the existing cost_graph/CostSum aggregation; a per-trade ConstantCost and a per-held-cycle CarryCost compose in one run (the costs sum per-field — the composed golden is exactly the sum of the two single-cost charges). New run-path --carry-per-cycle flag (sweep/walkforward/mc pass None, as the existing cost flags do). Tests: 2 RED-first unit B-proofs (open_cost grows over the hold, dumps the total at close, acc resets between trades — the discriminator a scalar net_expectancy_r cannot see); the CarryCost twin's 5 unit tests; 2 captured net_expectancy_r goldens (-768.2095026600887 carry, -1383.7939051991182 composed); a net_r_equity-bleeds-over- the-hold integration test (the terminal open hold's r_equity-net_r_equity gap strictly grows); plus negative-rate-refused-exit-2, monotone-in-rate, and zero-rate-exactly-free guards. Verified: cargo test --workspace (0 failures), cargo build --workspace, cargo clippy --workspace --all-targets -- -D warnings clean. summarize_r / aura-analysis untouched. Deferred to later #148 cycles (logged there): notional-based carry, the calendar-aware overnight swap, sweep-path cost. refs #148 |
||
|
|
face1879a6 |
audit(0084): cycle close — drift-clean (code); C10 cycle-0084 note + doc fixes
Cycle 4 of milestone #148 (cost-graph composite-builder). Architect drift review over fc52b4f..d5c44dd: drift_found, every item dispositioned. What holds (architect, evidence-of-review): C16/C9 — cost_graph lives in aura-composites, the factors in aura-std, aura-engine stays domain-free; the composite is ordinary downstream nodes. C11 behaviour-preservation is structural, not just asserted — the composite inlines to the same flat fan-in as the deleted CLI block (identical node set/order, cost[k].field slot map, geometry fan, net_eq terms/signs, cost_rec cols), so the cycle-0083 net_expectancy_r goldens stay byte-identical. C23 — role/port names non-load-bearing; 4 fixtures pin the role-set + output triple. Resolved this commit: - [high] ledger C10: the cycle-0083 note's "Still deferred (decision E)" is removed (E shipped this cycle) and a cycle-0084 realization note added. - [medium] cost_graph .leak() provenance (aura-composites/src/lib.rs): the "risk_executor precedent" was imprecise — that .leak() lives in risk_executor's TEST (one-shot, leak-safe), not its production builder. Doc corrected: the leak is fine for one-shot run-path construction but must be interned (the COL_PORTS production pattern) before cost reaches the sweep path. - [low] cli_run.rs golden docstring cited the deleted `slot * COST_WIDTH + f` CLI wiring; updated to the cost_graph composite's per-node cost[k].<port> wiring. Deferred (tracked, not pending) -> #152: - [medium] the cost[k].<port> index-namespacing restated across CostSum / cost_graph / the CLI (a build-validated lockstep — a divergent name fails loudly at g.build(), NOT the silent positional kind 0083 collapsed), AND the per-build .leak() that becomes an allocation regression on the per-member sweep path. Both are harmless on the current run-only cost path; the proper fix is one sweep-safe single-source cost-port-name contract (interned), best built with the deferred sweep-cost cycle. Regression gate: cargo test --workspace (0 failures), clippy --workspace --all-targets -- -D warnings clean, cargo doc clean — the architect is the primary gate (no dedicated regression script). Ephemera (spec 0084 + plan 0084) git rm'd. Milestone "Cost-model graph (in R)" remains OPEN. refs #148 |
||
|
|
6b53c239dd |
feat(0083): CostNode trait + shared cost-record contract
Lift the duplicated cost-node skeleton — shared verbatim by ConstantCost and VolSlippageCost and locked by convention against CostSum — into one abstraction: - A new aura-std/src/cost.rs owns the cost-record contract (COST_WIDTH, COST_FIELD_NAMES, GEOMETRY_WIDTH), the CostNode factor trait (one hook: cost_numerator, in price units), the generic CostRunner<F> adapter that holds the shared state (cum, out) and the co-temporality skeleton (geometry gating, numerator/latched R-normalization, closed/open charge, 3-field emit), and a cost_node_builder schema assembler. - ConstantCost and VolSlippageCost become thin CostNode factors; their new() returns CostRunner<Self>, so every existing call site and unit test binds transparently and stays green verbatim. - CostSum and main.rs drop their local triple consts for the shared COST_FIELD_NAMES — the cycle-2 audit-flagged by-convention 3-field lockstep is now a structural single-source contract (the four copies of the triple collapse to one). main.rs also reads COST_WIDTH in place of the literal slot stride. Behaviour-preserving: the builders emit unchanged schemas (same port names), so the wiring, the net_r_equity seam, and summarize_r are untouched; the numerator/latched token form is preserved verbatim (IEEE-754 byte-identity). The existing suite is the regression net — all green: the per-node unit tests pass verbatim (0.5/1.5, 0.375/1.375), the composition E2E exact, the C18 no-cost golden byte-identical. Two new CLI characterization goldens pin the exact net_expectancy_r of the flat and composed cost paths (the prior tests only asserted net < gross, which a numerator drift would pass silently). New cost.rs runner/contract tests + the HalfSpreadCost author doctest cover the new surface. Deviation from the plan: CostNode::name() was dropped (the plan over-specified it) — it is dead surface, nothing consumes it (CostRunner forwards label(); cost_node_builder takes the name as an explicit arg). Removed from the trait, both impls, the doctest, and the test stubs; build + suite + clippy green. Verified: cargo build --workspace --all-targets clean; cargo test --workspace 0 failures; cargo clippy --workspace --all-targets -- -D warnings clean; doctest green. refs #148 |
||
|
|
7f3756a395 |
feat(0082): cost-graph composition — VolSlippageCost + CostSum net-R aggregate
Cycle 2 of the "Cost-model graph (in R)" milestone (#148): the milestone's real architectural claim — the cost graph composes. Two cost nodes now sum into one net-R curve while summarize_r and the net_r_equity tap stay structurally unchanged. - VolSlippageCost (aura-std): a second, STATE-DEPENDENT cost node; per-trade charge = slip_vol_mult * volatility / |entry-stop|, in R. The vol is an independent short-horizon realized range (SLIP_VOL_LENGTH=5, distinct from the stop's EWMA-3) — scaling slippage by the stop's own vol would collapse cost-in-R to a constant (indistinguishable from ConstantCost). - CostSum (aura-std): the cost-graph OUTPUT node — sums N cost nodes' 3-field cost-in-R records per-field into one aggregate. summarize_r and net_r_equity read the aggregate (one home for cost; n=1 is the identity, so the cost path is uniform). A future CostNode trait (deferred) will unify the cost-triple the two producer nodes currently restate by-convention. - Run path: --cost-per-trade and --slip-vol-mult combine, their costs summing into the net-R curve; a hoisted vol proxy (RollingMax-RollingMin) keeps the single feed. Run-path-scoped; sweep/walkforward/mc pass None. Co-temporality contract (the load-bearing design decision; corrected from the signed spec after the implement-loop correctly BLOCKED Task 3 on it). summarize_r positional-joins cost[i] <-> record[i], so the cost stream must be co-temporal 1:1 with the PM record. A cost node is therefore gated ONLY by the PM geometry (closed/open/entry/stop); a not-yet-warm state input (the vol proxy warms later than PM) contributes 0 cost that cycle rather than withholding and desyncing the stream. This makes co-temporality structural + warmup-independent, preserves the C18 golden, and generalizes to any future cost factor. The rejected alternative (a key-join in summarize_r) would have moved the golden and pushed cost-graph logic into the post-run fold. Recorded on #148. Tests: VolSlippageCost + CostSum unit sets (incl. the co-temporal-zero-during- warmup case); the CLI composition run (both flags -> net_both < net_flat, net_r_equity persisted); node-level EXACT additive composition (net_both == net_flat + net_vol - gross over the real nodes), the aggregate net_r_equity == post-run net total, and CostSum(1) identity. Full workspace suite green; clippy -D warnings clean; the no-cost C18 golden byte-identical (the regression floor). Deferred (later cycles of this milestone): the general CostNode trait + cost-graph composite-builder (now justified by two concrete nodes), the conviction-weighting R-aggregation axis, and cost on the reduce-mode sweep path. Spec + plan amended to the corrected contract; both are cycle ephemera (git rm at cycle close). refs #148 |
||
|
|
3fe4684190 |
feat(0081): cost-model graph cycle 1 — ConstantCost node + net-R seam
Lands the first cycle of the "Cost-model graph (in R)" milestone: cost is
now a composable in-graph node, not a post-run scalar (C10).
- ConstantCost (aura-std): a flat round-trip cost per trade, emitted in R
as a 3-field record {cost_in_r, cum_cost_in_r, open_cost_in_r} isomorphic
to PositionManagement's R-triple. R-pure: cost_in_r = cost_per_trade /
|entry-stop|; notional cancels, no equity held.
- summarize_r folds the cost stream into net_expectancy_r, replacing its
scalar round_trip_cost (one home for cost, no double-count). Positional
co-temporal join; an empty stream is the gross-R baseline.
- net_r_equity tap (stage1_r_graph): LinComb(4) over [cum_realized_r,
unrealized_r, -cum_cost_in_r, -open_cost_in_r] -> Recorder, a sibling of
r_equity; --cost-per-trade on the run path wires the node + tap + fold.
- A no-cost run is byte-identical: no net_r_equity tap, net == gross, the
C18 golden unchanged.
Tests: exact-subsumption (the node-stream net == mean(r_i - cost_i) over
ALL trades incl. the window-end open one), the in-graph net_r_equity tap +
net < gross E2E, the no-cost golden held. Full suite green; clippy clean.
Implement-time decisions ratified (recorded on #148): the cost stream is
recorded 3-wide (the node's full output; summarize_r reads col 0/2, col 1
feeds the in-graph net curve) — resolving a 2-wide/3-wide inconsistency in
the plan's Step 3; Trade drops its `latched` field (cost now arrives in R),
so r_col ENTRY_PRICE/STOP_PRICE become #[allow(dead_code)] layout contract.
Scoped to the run path (non-reduce); sweep/walkforward/mc pass None — cost
on the reduce-mode sweep path and OOS-pooling cost are a deferred follow-up.
Known pre-existing concern (not this cycle): aura-std lib.rs module doc
still names "realistic broker profiles", retired by the C10 rework.
refs #148
|
||
|
|
c750b9fbc3 |
test: RED — family-emitting CLI commands must survive an early-closing stdout reader
Captures the pre-existing broken-pipe panic surfaced by the milestone fieldtest ([bug] finding): every multi-line subcommand (sweep / mc / walkforward / runs family / generalize) panics "failed printing to stdout: Broken pipe (os error 32)" and exits 101 when the stdout reader closes early (| head, | less, a closed UI pane), instead of terminating cleanly. Root cause: Rust's runtime resets SIGPIPE to SIG_IGN at startup, so a write to a closed pipe returns EPIPE and println! panics rather than the process dying quietly. crates/aura-cli/tests/cli_broken_pipe.rs spawns `aura sweep`, closes the stdout read end mid-stream, and asserts no panic in stderr + exit != 101. Verified RED (reproduces 6/6). The GREEN fix follows in the next commit. refs #146 |
||
|
|
0a06afed29 |
feat(0078): cross-instrument generalization as a validation step
Last piece of the Inferential-validation milestone (#146): a new `aura generalize` subcommand grades how consistently a single candidate holds across a set of instruments — the across-instrument axis of the same anti-false-discovery concern as trials-deflation (#144, within one family) and plateau-over-peak (#145, within one instrument's surface). Aggregator/validator, not a third selector (user-ratified): it runs a brought candidate (a single-cell stage1-r grid — --fast/--slow/--stop- length/--stop-k pinned to single values) across an instrument list (--real SYM1,SYM2,...), collects per-instrument R-metrics, and reduces them to a worst-case floor (min over instruments) + a sign-agreement count + the per-instrument breakdown. R-only (C10 — R is the only account/instrument-agnostic unit); the worst-case min is the cross- instrument sibling of PlateauMode::Worst and satisfies the non- domination constraint by construction (a strong instrument cannot lift a min). Rejected: a cross-instrument t-stat mean/std*sqrt(M), which reintroduces the pooled mean the milestone warns against. - RunManifest gains a first-class `instrument` lineage field (serde- widened, C14/C18 — legacy lines load as None, existing manifest bytes unchanged); the compat read-mirror threads it in lockstep. - FamilyKind::CrossInstrument (C12 comparison axis); the M per-instrument runs persist as a CrossInstrument family, each member self-identifying via its stamped instrument. The aggregate is recomputable from the members (McAggregate precedent), printed live. - The generalization reduction lives registry-side beside optimize_ deflated/optimize_plateau (C9), reusing resolve_metric/metric_value/ is_r_metric; check_r_metric is the data-free pre-check the CLI runs before evaluating any instrument. - Additive: existing sweep/walkforward/mc/standalone paths stay byte- identical (C23 — instrument omitted when None via skip_serializing_if). Verified by the orchestrator: cargo test --workspace green (0 failed — the new generalization_*, check_r_metric, parse_generalize_*, and the gated cross-instrument E2E all pass; the E2E ran the real GER40/USDJPY path on this host, exit 0); cargo clippy --workspace --all-targets -D warnings clean. closes #146 |
||
|
|
cb32658fd1 |
feat(0077): prefer a robust parameter plateau over the in-sample peak
Add an opt-in plateau selection objective for walk-forward, recorded on the same RunManifest.selection carrier #144 introduced. Default argmax is byte-identical (C23); plateau is reached only via a new --select flag. What landed: - FamilySelection / SelectionMode reshape (report.rs): the selection RULE (mode) and its ANNOTATION are orthogonal — deflation fields become Option (present iff Argmax), plus PlateauMean/PlateauWorst variants and neighbourhood_score/n_neighbours (present iff Plateau*). Legacy lines still load: the deflation scalars deserialize from bare values via serde default (C14/C18). compat.rs embeds FamilySelection by value — reshape flows through untouched. - GridSpace::axis_lens() + SweepBinder::sweep_with_lattice (engine): the grid radixes in param_space()/odometer order. sweep() delegates to sweep_with_lattice with the lattice dropped, so every existing .sweep() caller is byte-unchanged. - optimize_plateau + PlateauMode + closed_neighbourhood (aura-registry, C9): each member scores as the mean/worst of its closed mixed-radix grid neighbourhood's metric_value; the winner is the smoothed argmax by the metric's direction (earliest-odometer tie, as optimize). Pure, no RNG (C1); in-sample only (C2). A higher_is_better helper now sources the per-metric direction once, shared by metric_cmp and optimize_plateau. - CLI --select <argmax|plateau:mean|plateau:worst> (default argmax; unknown token exits 2). walkforward_family dispatches via a select_winner helper; sweep_over/stage1_r_sweep_over carry the lattice as Option<Vec<usize>>. runs-family display gains a plateau(<mode>)=<score> over <n> cells line. RandomSpace-refuse: walkforward has no --random producer, so the plateau-without-lattice guard is structural — select_winner returns the refuse on a None lattice (the caller prints the message and exits 2), unit-tested at the helper, not via a --random E2E (decided + recorded on #145 comment 1974, with the lattice-seam fork). Tests: plateau-picks-plateau-not-spike, mean-vs-worst, lower-is-better direction-flip, closed-neighbourhood golden, single-member degeneracy, C1 determinism; CLI select-parse, select_winner refuse; E2E argmax-byte-identical (C23), plateau:mean and plateau:worst provenance. Full workspace suite green; clippy --all-targets -D warnings clean. closes #145 |
||
|
|
a2959050a4 |
feat(0076): deflate the sweep winner's metric for the number of trials
Adds optimize_deflated beside optimize in aura-registry: the in-sample walk-forward selection now records, on each OOS winner's manifest, how much the winner's metric is inflated by the size of the search it won — a deflated score and (R arm) an overfit probability — without changing which member wins (additive, C23; a regression test pins optimize_deflated's winner == optimize's). - R arm (sqn_normalized/expectancy_r/...): a centred moving-block reality-check. Each member's per-trade R series is mean-subtracted to impose the no-edge null, resampled via the r_bootstrap kernel (extracted to a shared resample_block, byte-identical), and the best-of-K null-max gives overfit_probability = (count(null >= raw)+1)/(n+1) and deflated_score = raw - p95(null). Deterministic given the seed (C1). - total_pips arm: a closed-form expected-max-of-K dispersion floor (inv_norm_cdf + expected_max_of_normals), no probability. The record (FamilySelection on RunManifest) rides the proven serde(default, skip_serializing_if) widening + a compat.rs mirror, so legacy runs.jsonl/families.jsonl lines load unchanged and unstamped sweep/mc/standalone manifests stay byte-identical (C14/C18). The SelectionMode enum reserves a Plateau slot for #145. CLI: walk-forward stamps both arms; runs family <id> rank surfaces a human-readable deflated/overfit line. Quality review (re-dispatched after the implement-loop's quality gate exhausted) caught a real defect: on the R arm with positive resamples but no member carrying trade_rs (a family loaded from disk — trade_rs is serde-skipped), the null was all-NEG_INFINITY, turning deflated_score into +inf. Fixed by collapsing "no usable null" (zero resamples OR no trade_rs) into one degenerate floor (deflated == raw, overfit == 1.0); pinned by optimize_deflated_no_trade_rs_floors_instead_of_infinity. Verified: cargo test --workspace and cargo clippy --workspace --all-targets -D warnings both green. Frictionless Stage-1 R; n_eff advisory and reload-time recompute deferred (spec 0076 Out of scope). closes #144 |
||
|
|
2e77ab1dda |
feat(0075): Monte-Carlo R-bootstrap for OOS validation (iter 2)
Add `aura mc --strategy stage1-r [--real <SYM>]`: run the stage1-r
walk-forward, pool its OOS per-trade R series, and report a moving-block
bootstrap E[R] distribution / CI (one `mc_r_bootstrap` line with an `e_r`
quantile block + P(E[R]<=0)). Completes spec 0075's Monte-Carlo half on top
of iter 1's walk-forward R-reporting.
Engine: `r_bootstrap(rs, n_resamples, block_len, seed) -> RBootstrap` — a
deterministic moving-block bootstrap (non-circular, final block truncated;
block_len=1 = i.i.d. trade-shuffle; clamped to [1,n]; empty -> all-zero),
reusing the existing MetricStats/quantile and SplitMix64.
CLI: parse_mc_args/McArgs split — bare/`--name`/`--trace` route to the
unchanged synthetic seed-resweep `run_mc` (byte-for-byte preserved); the R
path accepts ONLY `--strategy stage1-r`, so a bare `--real` stays a usage
error (mc_rejects_real_flag_with_usage_exit_2 preserved, doc-comment
refreshed). run_mc_r_bootstrap reuses walkforward_family + RMetrics.trade_rs.
Corrective fixes over the plan snippets, both verified: the mc usage string
carries a `usage:` token (the new `["mc", rest @ ..]` arm now handles the
`--real` rejection, and the preserved golden asserts `stderr.contains
("usage")`); `#[allow(clippy::large_enum_variant)]` on McArgs keeps the
unboxed Stage1RGrid field layout.
Scope: single `--real <SYM>` per invocation; cross-symbol pooling is a
deferred follow-on (needs a multi-symbol grammar). Verified: cargo build
clean, full `cargo test --workspace` green (609 passed), clippy -D warnings
clean; bare `aura mc`, SMA walkforward, stage1-r single-run, and C18
round-trip goldens all preserved.
refs #139
|
||
|
|
f286bb85f7 |
feat(0075): walk-forward strategy-selectable + R-reporting (iter 1)
Make `aura walkforward --strategy stage1-r [--real <SYM>]` roll IS->OOS windows, sweep the stage1-r grid in-sample, pick the winner by an R metric (sqn_normalized), run it out-of-sample, and report per-window + pooled OOS R-metrics. The bare SMA walkforward path is byte-identical (its dispatch arm is verbatim today's body; the pooled `oos_r` block is emitted only when a window carries an `r` block). Engine: - RMetrics gains an in-memory `trade_rs: Vec<f64>` (realised R per closed trade), excluded from serde (`#[serde(skip)]`) and from a hand-written PartialEq, so the C18 wire shape and every existing equality assertion / round-trip stay unchanged. summarize_r now retains the per-trade R vector it used to drop. - New `r_metrics_from_rs(&[f64])` reduces a flat (pooled across-window) R series to RMetrics. Its R-distribution arithmetic is copied verbatim from summarize_r (the byte-pinned floats must not be algebraically refactored); the two copies are guarded in lockstep by a cross-reducer equality test. net_expectancy_r = expectancy_r (exact at the Stage-1 cost=0 invariant); conviction_terciles_r = [0,0,0] (per-trade conviction is not pooled). CLI: - walkforward gains --strategy + the four stage1-r grid flags (reusing parse_csv_list / Stage1RGrid); walkforward_family is strategy-dispatched. - Windowed stage1-r helpers: stage1_r_sweep_over (reduce-mode folded IS sweep, O(trades)/member) and run_oos_r (non-reduce OOS run, for the stitched pip-equity curve), plus stage1_r_space. Frictionless Stage-1 R (costs are Stage-2). Verified: cargo build clean, full `cargo test --workspace` green (SMA walkforward, synthetic mc, stage1_r_single_run_output_golden, and the C18 round-trips all preserved), clippy -D warnings clean. Monte-Carlo R-bootstrap is iter 2. refs #139 |
||
|
|
7e4b91ab5f |
feat(0074): source real-path pip from the geometry sidecar; drop the authored floor
The data-server now ships per-symbol geometry sidecars, so aura no longer hard-codes instrument geometry. A consumption grep showed production reads exactly one `InstrumentSpec` field — `pip_size` — at the real-data path; the other eight (incl. `instrument_id`) were constructed but never read. - aura-cli resolves the real-path pip from the recorded sidecar (`instrument_geometry` -> `InstrumentGeometry.pip_size`), refusing (exit 2) on absent geometry. The authored table no longer gates: any symbol with a sidecar + bars now runs (new test: USDJPY, never vetted, resolves its JPY pip 0.01 from the sidecar). - Removed the redundant authored floor: `InstrumentSpec`, `instrument_spec`, `VETTED_SYMBOLS`, and the table-only tests. The `InstrumentGeometry` seam (the surviving pip source) and its nameability guard are kept. - `instrument_id` (the "find out"): no production consumer. The position-event table's `instrument_id` is a decoupled caller parameter; the engine never imports an instrument spec. Dropped with the struct. - The example `pip_size_of` is re-sourced to the sidecar; the pure-structural blueprint tests use a literal pip to stay archive-independent. Speculative deploy-grade fields and the #124 override/floor tiers are deferred to the Stage-2 broker that will consume them, read from the sidecar geometry then. Ledger (C8/C15/#22/#106) updated; #124 collapses to "recorded sidecar geometry -> refuse" in the meantime. Verified: cargo test --workspace (all green), clippy -D warnings clean, cargo doc clean, with the local archive present so the sidecar paths run. refs #124 |
||
|
|
216cc8c417 |
feat(0072): stage1 mean-reversion candidate — EWMA Bollinger fade (#137)
Third price-only Stage-1 R strategy: aura sweep --strategy stage1-meanrev fades deviation from a rolling mean (price above mean+k*sigma -> short, below -> long, latched +-1), feeding the unchanged bias -> RiskExecutor (vol-stop = R) -> flat-1R seam. sigma = Sqrt(Ema((price-Ema(price))^2)) reuses the vol_stop deviation- squared-then-smoothed shape, so the band is built from existing aura-std nodes (Ema/Sub/Mul/Sqrt/LinComb/Add/Gt/Latch) -> ZERO new nodes. No Delay: the current bar legitimately belongs to its own band (causal, C2). Swept knobs: --window (one Ema length ganged across mean+variance) and --band-k; downstream vol-stop R knobs unchanged. An in-file test reads the SHIPPED graph's exposure tap to pin the fade polarity (short above the band, long below) — a latch-set/reset copy-paste from breakout would invert the signal yet leave the fold-vs-raw and window-grid CLI tests green. All 588 workspace tests pass (existing R/sma/momentum/breakout goldens byte-identical); clippy clean. refs #137 |
||
|
|
d845c509d3 |
feat(0071): stage1 breakout candidate — Donchian channel signal (#137)
A second Stage-1 R strategy candidate for the edge-research milestone, a structurally
different trend mechanic than the refuted SMA-momentum: a Donchian channel breakout.
Swaps only the signal leg of the stage1-r graph —
close -> Delay(1) -> {RollingMax,RollingMin}(N) -> {Gt,Gt} -> {Latch,Latch} ->
Sub = bias in {-1,0,+1} — keeping the vol-stop R definition unchanged, so it screens
under the identical R yardstick (clean A/B vs momentum).
- aura-std: new RollingMax / RollingMin nodes (sliding-window extremum via a monotonic
deque, O(1) amortized; mirror the Sma/Delay ring-buffer shape, warm-up skip-emit,
C7/C8). The +/-1 direction latch composes from two existing Latch nodes + Sub (no new
latch type).
- aura-cli: stage1_breakout_graph + a manual-grid stage1_breakout_sweep_family
(fully-bound graph per point, sidestepping parameter-ganging since one channel length
drives both rolling nodes); --strategy stage1-breakout + --channel grid flag; usage
strings updated.
- Causality (C2): one Delay(1) on close feeds both rolling nodes, so each channel covers
close[t-N..t-1] — the current bar is excluded. Pinned by a contrastive e2e test (a
strictly rising series up-breaks every warmed bar; a window including the current bar
never could) plus a +/-1-latch-hold test.
All existing goldens byte-identical (stage1-r/sma/momentum untouched); folded-no-trace
== raw-trace metrics; workspace tests + clippy green.
refs #137
|
||
|
|
eb8653488f |
feat(0070): wire folding sink reductions into the stage1-r sweep (closes #138)
Completes the M3 fix for BLOCKER #138. The no-trace stage1-r sweep now folds its reductions online: `stage1_r_graph(reduce=true)` wires `SeriesReducer` (equity, exposure -> one summary row each) and `GatedRecorder` (gated on `closed_this_cycle` -> only the trade rows + the final row), and the folded consumer rebuilds `RunMetrics` from those compact outputs. Per-member memory drops from O(cycles) (~2 GiB over ~5.5M one-minute bars) to O(trades), so full-history multi-member sweeps no longer exhaust RAM+swap. The `r_equity` tap is omitted when reducing (it is consumed only by the `--trace` persistence path). `reduce = trace.is_none()`: the `--trace` path and the single `run_stage1_r` keep the raw recorders + `summarize`/`summarize_r` over full rows — the byte-for-byte reference. A new CLI-seam test (`sweep_strategy_stage1_r_folded_no_trace_metrics_equal_raw_trace_metrics`) proves the folded no-trace sweep reports byte-identical per-member metrics to the raw `--trace` path. Verified: cargo test --workspace green (42 suites); cargo clippy --workspace --all-targets -- -D warnings clean; stage1-r goldens byte-identical. closes #138 |
||
|
|
e94b79eb98 |
test(stage1-r-sweep): RED — grid all four knobs from CSV flags
Executable-spec for the strategy-research enabling change: expose the
stage1-r sweep's hardcoded timescales as CLI grids so a slow
time-series-momentum edge can be screened across timeframes.
- sweep_strategy_stage1_r_grids_all_four_knobs_from_csv_flags (RED):
`aura sweep --strategy stage1-r --fast 240 --slow 960
--stop-length 240 --stop-k 2.0` must yield a 1-member family whose
member params are fast=240, slow=960, stop_length=240, stop_k=2.0.
Fails today: the four flags are rejected (exit 2), gridding absent.
- sweep_strategy_stage1_r_no_flags_keeps_the_default_grid (guard,
green today): a no-flags sweep still yields the {2,3}×{6,12} 4-member
family with the stop pinned at 3 / 2.0, so existing goldens stay
byte-green once the feature lands.
GREEN side follows via implement mini-mode.
refs #137
|
||
|
|
89d967a940 |
feat(0067): SQN100 metric + stage1-r sweep --trace
Two additive follow-ups to the cycle-0065/0066 R-sweep surface. Part A (#130) — n-normalized SQN (SQN100): RMetrics gains sqn_normalized = (mean_R/stdev_R)·√(min(n,100)), a turnover-robust ranking objective comparable across sweep members with different trade counts. The raw sqn is kept byte-identical (computed sqrt(n)*mean/sd, NOT via a factored shared ratio, so the existing metric does not drift even by 1 ULP). The field carries #[serde(default)] for C18 back-compat (a pre-0067 r: block deserialises with sqn_normalized = 0.0). The registry learns Metric::SqnNormalized (opt-in; the default ranker and raw sqn are unchanged), so `runs family rank sqn_normalized` works. The 0066 single-run golden is re-baselined (the r: block gains the field; for n=3 ≤ 100 the cap is inactive so sqn_normalized == sqn). Part B (#135) — wire --trace for the stage1-r sweep: stage1_r_sweep_family now honours --trace, persisting each member's equity/exposure/r_equity under runs/traces/<n>/<member_key>/ via the existing persist_traces_r (mirroring momentum_sweep_family); the interim run_sweep refusal guard is dropped. --trace is now symmetric across all three sweep strategies and a swept member is chartable (chart <n>/<member> --tap r_equity). Tests: SQN100 cap / below-cap / empty + serde back-compat (engine); rank-by sqn_normalized + unknown-metric message (registry); an E2E guard that both SQN values fold from recorded records and agree below the cap (the cross-crate column seam); inverted stage1-r --trace persistence + a CLI rank-by- sqn_normalized integration. Full workspace suite green (527 passed, 0 failed); clippy --all-targets -D warnings clean. Derived fork decisions logged on #130 and #135 (cap = fixed const 100; opt-in metric, default ranker unchanged; serde(default); Part B mirrors momentum_sweep_family + reuses persist_traces_r). closes #130 closes #135 |