14e27a978bcfe88fbca7a69c4ffe89eaeafbd486
232 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
ff4a1b3d4a |
feat(0092): run-from-blueprint infrastructure — restructure + topology_hash + shared seam (Tasks 1-3)
Cycle-1 infrastructure for #165 (World/C21). Behaviour-preserving + workspace green; the CLI arm + tests (Tasks 4-5) follow. Task 1 — restructured stage1_r_graph into stage1_signal() (the serializable signal leg: SMA-cross -> Bias, a `price` input-role + a `bias` output) + wrap_stage1r(signal, ...) (broker/sinks/exec/cost around a nested signal), with stage1_r_graph() = wrap_stage1r(stage1_signal(...)). DEVIATION from the plan's "callers untouched" claim, verified behaviour-preserving: nesting the signal prefixes its param-space names (stage1_signal.fast.length), which broke the sweep's bare .axis("fast.length"); fixed exactly as the #137 stop-knob machinery does — FAST_LENGTH_SUFFIX/SLOW_LENGTH_SUFFIX suffix-resolution + stage1_r_friendly_name arms mapping the prefixed names back to the bare manifest names. All observable behaviour (manifest names, member keys, metrics, traces) byte-identical; the flat graph and every metric unchanged (full suite green, incl. sweep/walkforward/MC). Task 2 — RunManifest.topology_hash: Option<String> (Tier-1 optional, serde default/skip — old manifests byte-identical, #156), threaded None into all 24 literal sites across 7 crates. Also fixed the aura-registry RunManifestRead read-mirror to carry topology_hash (it was hardcoding None on load) — else a stored hash would silently drop on the registry round-trip once Task 3 stores a real one. Task 3 — the shared run_signal_stage1r seam (hash the signal, wrap, compile with params, bootstrap, run, manifest with params from param_space + topology_hash) + the sha256_hex topology_hash helper (sha2 in aura-cli, off the frozen engine, invariant 8). run_stage1_r now carries its topology_hash too (every run becomes self-identifying, #158). The seam is unwired until Task 4 (transient #[allow(dead_code)], retired there); RED-first seam tests added. Verified: cargo test --workspace green (51 suites, 0 failures); cargo clippy --workspace --all-targets -D warnings clean. refs #165 |
||
|
|
4a3ae05db2 |
test(graph_model): cover a producer backing >1 re-exported output field
#47 — close a coverage gap in the graph model serializer: a single interior producer node backing more than one re-exported output field was never exercised by a fixture (every existing one maps each output to a distinct producer or a single field). The issue was filed at cycle 0022 against `aura-cli/src/graph.rs`'s `render_definition` / `output_binding` helper, which rendered a textual `(n1, n2) := <producer>` tuple binding. That file and helper no longer exist — the textual definition renderer was replaced by the JSON model emission now in `aura-engine/src/graph_model.rs`, where the multi-OutField case is plain flat iteration over `c.output()` (one `[name,kind]` entry + one `#N` boundary binding per `OutField`, by `(node, field)`), not a special tuple-binding arm. So the original branch is gone; this guards the flat emission that replaced it. New test `multi_outfield_producer_emits_one_binding_per_field` (+ two fixtures): a two-field producer (`hi`:f64, `lo`:i64 — distinct kinds, so the per-field `field_kind` lookup is exercised) re-exported as both composite outputs, nested in a root so it renders through `composite_def_json`. Asserts both fields surface with their own kind (`"outputs":[["hi","f64"],["lo","i64"]]`) and both bindings reference the same producer at distinct ordinals (`0.o0`/`#0`, `0.o1`/`#1`). Test-only; no production code touched (the behaviour was already correct). Verified: cargo test -p aura-engine green (237 in the lib suite); clippy clean. closes #47 |
||
|
|
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 |
||
|
|
cdd2da6337 |
feat(0090): blueprint format forward-compat — Tier-1 tolerance pinned, two-tier discipline codified (closes #156)
The blueprint data format (C24) extends additively under one codified two-tier discipline: - Tier-1 (additive-optional): a new optional field/section is tolerated by an older reader (serde ignores unknown fields — no deny_unknown_fields), with no format_version bump; by C1 a new optional field defaults to prior behaviour, so an old blueprint reproduces the same graph. - Tier-2 (must-understand: a new node type, edge semantics, or structural-axis kind): bumps format_version, so an old reader refuses cleanly (LoadError::UnsupportedVersion / UnknownNodeType) rather than silently building a different graph. Most of #156 already shipped with #155 (cycle 0087): the envelope, omit-defaults canonical form, and both Tier-2 load-path refusals were present and green. This cycle pins the one previously-unproven property — Tier-1 forward-tolerance of an unknown optional field on the current loader — and codifies the discipline. Changes: - New characterization pin (in-crate, blueprint_serde.rs): unknown_optional_field_is_tolerated_byte_identically — an unknown optional key at doc and primitive level loads byte-identically and runs bit-identically. - Two public-seam E2E pins (blueprint_serde_e2e.rs, added by the E2E phase): unknown_envelope_field_tolerated_through_public_api and unknown_primitive_field_tolerated_through_public_api — split the two distinct tolerance surfaces (envelope vs primitive) across the World/cdylib crate boundary, where a deny_unknown_fields added to one would break forward-compat while leaving the in-crate test green. - LoadError::UnsupportedVersion doc reworded from the deferred "is #156" note to the settled two-tier discipline statement. - C24 ledger (docs/design/INDEX.md): Status codification sentence added; #156 dropped from Remaining (#158/#159 stay). Derived fork (recorded on #156): the per-section required-flag mechanism (PNG critical-vs-ancillary) is NOT built — the version envelope already gives the must-understand refusal at version granularity, no current Tier-2 section validates a finer scheme, and C1 holds either way. The pin is a characterization test (GREEN on first run) — it documents existing serde silent-ignore behaviour, no production code changed. Verified: cargo test -p aura-engine green (all targets, three new tests confirmed by name); cargo clippy --workspace --all-targets -- -D warnings clean. |
||
|
|
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.
|
||
|
|
16520429be |
fix(0088): reject dataflow cycles in construction build (closes #161)
The construction op-script service accepted a dataflow cycle and exited 0,
emitting a non-DAG blueprint — violating domain invariant 5 / C9 (the graph is a
DAG; the only feedback path is an explicit delay/state node). The cycle-0088
fieldtest surfaced it (fieldtests/cycle-0088-construction-op-script/FINDINGS.md).
The construction gate set had no acyclicity check: a cyclic interior-edge set
passed both the eager per-op gates and the holistic finish, so replay() returned
Ok. Fix: an eager reachability check at the connect op that closes the cycle —
GraphSession::closes_cycle(from, to) asks "does the `to` node already reach the
`from` node through the edges added so far?" and rejects with a new by-identifier
OpError::WouldCycle { from, to } ("connecting from -> to would close a cycle"),
threaded through the CLI's exhaustive format_op_error. The fault is attributed to
the offending connect op, consistent with the surface's eager per-op diagnostics.
Eager rather than a holistic topological check at finish: a cycle is closed by
one specific connect, and naming that connect is the sharpest by-identifier error.
RED-first: two tests pin the symptom (a 2-cycle a<->b and a self-loop), both red
before the fix and green after; the 19 existing construction tests, the full
workspace suite, and clippy stay green.
Scope is the cycle only. The validity-floor cases the fieldtest also noted (an
unfed declared source role, an output-less graph) are degenerate-but-well-formed,
not invariant violations, and are deferred (refs #161) pending the build->consume
edge (#28) that defines what a valid emitted blueprint must guarantee.
|
||
|
|
86841b0740 |
audit(0088): cycle close — drift fixes + C24 ledger refresh; close construction service
Architect drift review of cycle 0088 (#157, the introspectable fail-fast construction service: §A shared gate predicates, §B the GraphSession/Op/replay per-op surface + introspection, §C the aura graph build/introspect CLI). What holds (confirmed against the diff): no-second-validator (C24) — edge_kind_check is the single kind-check site called by both validate_wiring and GraphSession::connect, and validate_wiring / check_param_namespace_injective are reused verbatim in finish(); engine domain-free (invariant 9) — construction takes an injected Fn(&str)->Option<PrimitiveBuilder>, no registry, the closed match + std_vocabulary_types live in aura-std; topology-as-data / no DSL (C24/C17/inv 10) — the engine Op is serde-free, the OpDoc wire DTO is CLI-side, ops are static and non-Turing-complete; replay-equals-builder byte-identity (construction_e2e.rs) protects C9/C19/C23. Resolution: - fix (drift-med): the root-role-boundness gate was the one holistic check finish() re-implemented instead of sharing — extracted check_root_roles_bound (blueprint.rs) and called from BOTH compile_with_cells and finish(), so finish() now reuses ALL the holistic gates and the no-second-validator claim is literally true. Behaviour- preserving (same UnboundRootRole variant; unbound_root_role_is_rejected stays green). - fix (drift-med): removed the #![allow(dead_code)] on construction.rs — empirically unnecessary (the pub re-exported surface is reachable; clippy -D warnings green without it) — and its now-inaccurate comment (it claimed removal "when the consumer lands", but the consumer is a separate crate). Also tidied a stale graph_construct.rs module-doc line. - fix (ledger): C24 Status refreshed — #157 moved out of "Remaining" with a cycle-0088 realization note (the construction service: eager/holistic gate split, build-free introspection, emits the #155 blueprint); #159 reframed as paired with #157. - carry-on (debt-med, forward-noted): the std_vocabulary roster is now a third hand-kept copy (match + test list + std_vocabulary_types) and introspect --vocabulary escalates #160's gap to user-facing — noted on #160; still fails safe. - carry-on (scaling-low, forward-noted): the engine Op -> wire OpDoc direction is unguarded for future Op variants — noted on #156 (the format-extension track). No architect-sweep / regression script is declared in the project facts, so the suite is the gate: cargo test --workspace 51 suites green; clippy --all-targets -D warnings clean. Cycle drift-clean. Ephemeral cycle artifacts removed (lifecycle convention): docs/specs/0088-*, docs/plans/0088-*. closes #157 |
||
|
|
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 ( |
||
|
|
27ac4dc537 |
feat(0088): §B construction op-script surface — GraphSession/Op/replay + introspection
Iteration-1 §B of the construction service (#157), on the §A shared predicates
(
|
||
|
|
ea1ca32dbd |
feat(0088): §A shared gate predicates — edge_kind_check, resolution helpers, try_bind
Iteration-1 §A of the construction service (#157): the surface-agnostic shared predicates the eager op-script path and the holistic finalize gates both call — no second validator (C24). - edge_kind_check (blueprint.rs): the per-edge producer/consumer kind check, lifted verbatim out of validate_wiring's edge loop into a pub(crate) fn that validate_wiring now calls — behaviour-preserving (same Bootstrap(KindMismatch) variant; the existing compile-time gate test stays green). - resolve_input_slot / resolve_output_field (builder.rs): the exactly-one-match name→index resolution extracted as pub(crate) fns over (&NodeSchema, &str), so the runtime-String op-script names share GraphBuilder's resolution; GraphBuilder delegates and maps to the unchanged BuildError variants. - try_bind + BindOpError (aura-core node.rs): the fallible Result twin of bind (bind keeps its panic contract and pinned messages verbatim; try_bind is a separate method reporting the same three conditions as values). - check_param_namespace_injective + validate_wiring widened to pub(crate) for the finalize-stage reuse by the upcoming GraphSession::finish (§B). Partial iteration: §B (the GraphSession/Op/replay surface + introspection) and the §A→§B integration land next (plan Tasks 4-8). compile_with_params / check_ports_connected are behaviourally unchanged. Full suite green; clippy clean. Plan correction folded in: Task 3's test originally referenced aura_std::Sma — infeasible inside aura-core (aura-std depends on aura-core; the reverse edge is a dependency cycle). Adapted to a local PrimitiveBuilder probe with identical assertions. refs #157 |
||
|
|
d5602ec5ad |
feat(0087): blueprint serialization & loader — close the graph-as-data loop
C24 first cut (#155): a blueprint — the param-generic, named graph-as-data a Rust builder produces — is now a first-class serializable data value with a canonical, versioned format, and the engine has the missing load path (data -> blueprint -> FlatGraph), not only the Rust-builder construction path. What ships: - `blueprint_to_json` / `blueprint_from_json` in a new `aura-engine` blueprint_serde module: a faithful serde DTO projection (the build closures dropped; re-derived on load), top-level `format_version` envelope, canonical compact JSON (struct field order, defaults omitted via skip_serializing_if). - A resolver seam: the loader is generic over an injected `Fn(&str) -> Option<PrimitiveBuilder>`; the concrete closed `match` over the aura-std vocabulary lives in aura-std (`std_vocabulary`), never in the engine (the engine stays domain-free: lib deps aura-core + aura-analysis only, never the node-vocabulary crate). This is C24's compiled-in closed-set referenced by type identity, NOT a dynamic/marketplace node registry (domain invariant 9). - serde derives on the serialized plain types (Edge, Target, OutField, Role, BoundParam, ScalarKind); BoundParam additionally re-exported from aura-core's root (an additive fix the plan's file list missed). Load-bearing scope decisions (derived, logged on #155): - Sinks are excluded from the serialized blueprint — a recording sink captures an mpsc::Sender (runtime identity, not param/topology, C19 value-empty); sink addressing is the C18-registry / #101 concern. The round-trip test attaches identical sinks post-load to both twins. - Construction-arg builders (LinComb(arity), CostSum(n), SimBroker(pip), Session(..)) are out of the round-trippable set: their structural args are a C20 structural-axis concern the param-generic format does not yet encode; an absent type_id fails the load cleanly (UnknownNodeType), never a silent wrong graph. #155's vocabulary is the 22 zero-arg aura-std builders. Additively extensible later (#156). Orchestrator hardening on review: the serializer now emits bound params in ascending original-slot order (mirroring the loader), so the canonical form is bind-order-independent — a precondition for content-addressing (#158). Identity today (every #155 node has <=1 param); holds by construction for future multi-param nodes. Acceptance (all green): a serialized blueprint runs bit-identical (C1) to its Rust-built twin; serializer/loader are inverse (canonical idempotence, incl. a recursive nested composite); unknown-type and unsupported-version fail named, never panicking. cargo test --workspace 748 passed; clippy --all-targets -D warnings clean. closes #155 |
||
|
|
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 |
||
|
|
d5c44dd1ea |
feat(0084): cost-graph composite-builder
Cycle 4 of milestone #148 (Cost-model graph in R), decision E. New `cost_graph(Vec<PrimitiveBuilder>) -> Composite` in aura-composites: it fans the 4 PM-geometry inputs to N cost nodes, surfaces each node's extra inputs (discovered via schema introspection past GEOMETRY_WIDTH) as `cost[k].<port>` roles, sums them through CostSum, and exposes the 3-field aggregate. Replaces the CLI's manual slot-indexed cost-wiring + the hardcoded MAX_RUN_COST_NODES=2 cap with one principled composite of arbitrary arity. GEOMETRY_WIDTH re-exported from aura-std (first cross-crate consumer). Runtime port names .leak()'d (the risk_executor precedent), a bounded one-time cost at blueprint construction. Behaviour-preserving at the value level: the composite inlines at bootstrap (C11) to the same flat fan-in, so the two cycle-3 net_expectancy_r goldens (-614.3134020253314 flat, -615.0304388396047 composed), the C18 no-cost golden, and the full suite stay green verbatim. Honours C9 (a composite of cost nodes is ordinary downstream nodes), C16 (wiring lives in aura-composites, not aura-engine), C23 (label drift under cost_graph nesting is permitted; no with-cost graph label/shape golden exists). Four aura-composites unit tests pin the exposed contract: the two planned (n=2 heterogeneous role set; n=1 no-extra shape) plus two the implementer added that strengthen the headline — arbitrary-arity per-node namespacing (n=3, two VolSlippageCost -> distinct cost[1]/cost[2].volatility, the property that retires the 2-node cap) and geometry fan-once (n=2 extra-free -> only the 4 geometry roles). Accepted nit (held, non-gating): the CLI reconstructs the `cost[k].volatility` role name to feed the vol node's extra input — a string coupling to the composite's public role-naming convention. Kept: role-based composite wiring addresses roles by name (as the CLI already does for "closed"/"bias"/"price"); the convention is tested + build-validated; a decoupling accessor would widen this cycle's scope. Verified: cargo test --workspace (0 failures), clippy --workspace --all-targets -D warnings clean, cargo doc -p aura-composites clean. refs #148 |
||
|
|
fc52b4fced |
audit(0083): cycle close — drift-clean (code); C10 cycle-0083 note + lib.rs doc
Architect drift review over 6c7f5dd..HEAD. No regression scripts in this project → the architect is the sole gate; verdict: shipped code byte-clean. What holds (verified against the diff): - C9 — a CostRunner<F> is a plain downstream Node; the two cost nodes are thin factors wired through the same cost_node_builder. No runtime sub-object. - C18 / byte-identity — the numerator/latched token form is preserved verbatim; the two new CLI goldens pin exact net_expectancy_r and pass; the no-cost golden is untouched. - C10 co-temporality + C23 + invariant-4 — the gate-on-PM-geometry-only rule (cold factor input → 0 cost, row still emits) is now centralized once in the runner; name() dropped cleanly (label() remains the non-load-bearing symbol); the 3-field triple is a single source (COST_FIELD_NAMES) read by both producers + CostSum + the CLI — the cycle-0082 by-convention lockstep is structurally gone. Resolution: - [fix] docs/design/INDEX.md C10 — added the cycle-0083 realization note (each prior cost cycle got one; the deferred general CostNode trait is now shipped and the lockstep eliminated). Ledger deferred-work tracking re-synced. - [fix] crates/aura-std/src/lib.rs:5 — module doc now names the CostNode/CostRunner authoring surface. - [carry-on, low debt] vol_slippage_cost.rs still declares its extra-input port twice (the cost_node_builder arg and the trait extra_inputs()); a clean unifier needs design thought (builder() is static, no factor instance) and is not worth forcing at cycle close. Documented here; agreement rests on the helper + a test + the harness debug-assert. Cycle 0083 ephemera removed (docs/specs/0083 + docs/plans/0083). Verified: cargo build/test --workspace clean (0 failures), clippy -D warnings clean, cargo doc -p aura-std clean (CostNode/CostRunner intra-doc links resolve). 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 |
||
|
|
6c7f5dd147 |
audit(0082): cycle close — drift-clean; C10 cycle-0082 note + cost-flag validation symmetry
Cycle 0082 (cost-graph composition, cycle 2) closes drift-clean against C10. The
architect review confirms the cycle is fundamentally sound:
What holds: VolSlippageCost + CostSum are ordinary aura-std downstream nodes
(C9/C8), cost stays out of aura-engine; the vol proxy is a trailing realized range
(C2, no look-ahead); feed-forward, no equity feedback (C1); the co-temporality
contract (gate cost nodes on PM geometry only, 0-cost on a cold state input) is
consistent with C1/C2/C9 and the cycle-1 positional join, and charging 0 during
warm-up is honest (no estimate yet, not a fabricated guess). The no-cost C18 golden
is byte-identical.
Drift resolved at close:
- [medium -> fixed] docs/design/INDEX.md (C10): added the cycle-0082 realization
note. The load-bearing co-temporality contract (generalizes to all future cost
factors) lived only in code comments + spec 0082; the ledger is now its durable
home once the spec is removed.
- [low -> fixed] crates/aura-cli/src/main.rs: --cost-per-trade now rejects negatives
at parse, symmetric with --slip-vol-mult (it previously deferred to the node
assert's panic). Run-path tests + the no-cost golden stay green.
Carry-on (documented deferrals, not this cycle's debt to fix):
- The 3-field cost triple {cost_in_r, cum_cost_in_r, open_cost_in_r} is restated
by-convention across ConstantCost, VolSlippageCost, and CostSum (compiler-unlinked
lockstep) — to be unified by the general CostNode trait, now justified by two
concrete nodes (#148).
- SLIP_VOL_LENGTH=5 is sized to warm within the synthetic smoke fixture; documented
in the const doc (on real M1 data any reasonable window warms).
- A future per-cycle-held accrual (carry/funding) accrues over holding duration,
which will force a summarize_r fold change and stress the positional seam — flagged
for that deferred cycle.
Regression gate green and unchanged: full workspace suite 0-fail, clippy --workspace
--all-targets -D warnings clean. No baseline moved -> no ratify.
Spec + plan ephemera removed (git rm docs/specs/0082, docs/plans/0082).
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 |
||
|
|
e82725f2d7 |
audit(0081): cycle close — drift-clean (code); ledger C10 realization note + stale-broker doc sync (refs #148)
Cycle 0081 (cost-model graph, cycle 1) closes drift-clean against C10: the architect review confirmed ConstantCost + the net-R seam realize the contract at its floor (C9 cost node; net_r_equity a C8/C18 sink; R-pure; one home for cost, subsuming the retired scalar round_trip_cost; pure feed-forward C1), and the no-cost baseline is byte-identical (C18 golden held). Regression gate green and unchanged: full workspace suite 0-fail, clippy --workspace --all-targets -D warnings clean. No baseline moved -> no ratify. Ledger sync (docs/design/INDEX.md): added the C10 cycle-0081 realization note (ConstantCost node + net_r_equity seam shipped; scalar round_trip_cost retired; run-path-scoped; later-cycle deferrals named). Drift swept (cycle-adjacent + cheap one-liners): - crates/aura-analysis/src/lib.rs net_expectancy_r doc: the removed in-fold mechanic (mean(R - round_trip_cost/latched_dist)) -> the cost-stream fold. - crates/aura-std/src/lib.rs module doc: "realistic broker profiles" (retired by #116) -> the legacy SimBroker pip yardstick + cost-model nodes. Backlogged (pre-existing, broader, predates this cycle): the docs/project-layout.md backtest worked example is stale on #117 (exposure->bias) and #116 (realistic broker); a proper rewrite is filed as #151, not folded here. Architect-accepted: the #[allow(dead_code)] on r_col::ENTRY_PRICE/STOP_PRICE (their production reader legitimately removed when summarize_r stopped recovering latched_dist; still the layout contract + exercised by test fixtures). Spec + plan ephemera removed (git rm docs/specs/0081, docs/plans/0081). 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
|
||
|
|
04c581a441 |
refactor(0080): relocate FamilySelection/SelectionMode to aura-analysis (refs #136)
The last trading-domain types still defined in aura-engine's report module — the selection-provenance types FamilySelection and SelectionMode — move to aura-analysis, finishing the #136 goal of getting the trading types out of the engine. aura-analysis is the only non-cyclic home that removes them from the engine: RunManifest.selection: Option<FamilySelection> needs the type and the engine already depends on aura-analysis; aura-registry and aura-cli reach them unchanged via aura-engine's re-export (report.rs pub-re-exports them, lib.rs untouched). Their tests stay in report.rs (one round-trips through RunManifest, which stays in the engine) and reach the types via the re-export. Behaviour-preserving (C1): full suite 665/0 unchanged incl. the C18 goldens, clippy clean, only report.rs and aura-analysis/src/lib.rs change. Staying in the engine (trace-coupled, deferred to the World/C21 layer, not #136): RunManifest, RunReport, summarize. The aura-registry Metric/metric_cmp vocabulary stays in the registry by design — the registry is the trading selection layer, not the domain-free engine (the deflation statistics branch on metric identity, irreducibly domain). See the #136 thread for the rationale. |
||
|
|
a8f67c7c80 |
audit(0079): cycle close — drift-clean; ledger crate-topology sync + serde_json dev-dep tightening (refs #136)
Architect drift review (3e8fd73..HEAD): the extraction is faithful and behaviour-preserving — every moved symbol's definition left report.rs, 35 unit tests relocated with their subject, aura-engine/src/lib.rs byte-unchanged, the four downstream crates byte-unchanged. No code defect. Regression gate green: cargo build/test --workspace = 665 passed / 0 failed (incl. the C18 cli_run goldens and the registry legacy-line loads); clippy --workspace --all-targets -D warnings clean. Ledger sync (docs/design/INDEX.md) — pre-existing crate-topology drift the extraction made visible: - C10 notes (cycles 0063/0068) placed PositionAction / PositionEvent / derive_position_events "in aura-engine beside RunMetrics / summarize_r"; cycle 0079 moved all of them to aura-analysis — corrected, with a forward pointer to the new C16 note. - C10 cycle-0065 note claimed "RiskExecutor ships as a public aura-engine composite-builder" and "Composites live in aura-engine ... aura-engine -> aura-std a normal dependency"; both stale — composites live in aura-composites and aura-std is now an aura-engine [dev-dependencies] only. Corrected. - Added a C16 realization note recording the aura-analysis extraction as an architecture fact: the engine<->domain seam, the current five non-node workspace crates, and the deferred #136 generic-over-M tail. serde_json moved from [dependencies] to [dev-dependencies] in aura-analysis (architect debt item): the production types derive serde, but JSON rendering lives in aura-engine's RunReport::to_json, so serde_json is test-only here and must not enter the frozen artifact's library link (C16 frozen-artifact scrutiny). Build + the 35 crate tests stay green after the move. No baseline moved (the suite is the regression gate; it stayed green), so no ratify statement. No spec/plan ephemera to drop (compiler-driven / agent-driven cycle — no docs/specs or docs/plans files were created). |
||
|
|
94c88eb7e1 |
refactor(0079): extract the trading-analysis leaf into aura-analysis (refs #136)
aura-engine is documented (C16) and named as the reusable, domain-agnostic reactive SoA substrate, yet `report.rs` concentrated the trading-domain analysis layer inside it. This cycle relocates the pure-domain leaf into a new `aura-analysis` crate (deps: aura-core + serde only), erecting the engine/domain seam before the Stage-2 broker milestone deposits more currency/position-event code into the same module. Behaviour-preserving (C1): the full workspace suite stays green unchanged (665 passed), including the C18 on-disk byte-identity goldens (cli_run, the registry legacy-line loads). Moved to aura-analysis (bodies verbatim — no float expression re-associated): RunMetrics, RMetrics (+ its hand-written PartialEq), the private r_col column indices + SQN_CAP, summarize_r, r_metrics_from_rs, derive_position_events, PositionAction (+ From/TryFrom<i64>), PositionEvent, and the deflation stats helpers inv_norm_cdf / expected_max_of_normals. Their unit tests and private helpers (r_row / r_row_full / pm_row) relocate with them — tests live with their subject (the r_col helpers reach the private module directly, which a re-export cannot bridge). Stays in aura-engine for this cycle: the generic trace plumbing (ColumnarTrace, join_on_ts, JoinedRow, f64_field), RunManifest, RunReport, and summarize (it bridges recorded trace columns into RunMetrics, so it is trace-coupled). The mis-placed orchestration types FamilySelection / SelectionMode also stay (their relocation is entangled with the registry-vocabulary fork, deferred). report.rs pub-re-exports the moved symbols, so aura-engine's lib.rs re-export block and every `crate::report::X` / `aura_engine::X` consumer (aura-registry, aura-cli, aura-composites, aura-ingest) compile byte-unchanged; only the new crate, the workspace member list, aura-engine's Cargo.toml dep, and report.rs itself change. Deferred to later cycles (genuine design forks, route through specify when reached): making RunReport generic over a metric type M and re-bounding sweep/mc/walkforward; relocating the aura-registry Metric / metric_cmp / rank vocabulary; relocating FamilySelection / SelectionMode. Fork decisions logged on #136. |
||
|
|
3e8fd73f68 |
tidy: clear the milestone fieldtest findings (refusal reasons, --help, ledger + glossary)
Resolves the four non-bug findings from the inferential-validation milestone
fieldtest (the bug — the broken-pipe panic — is fixed in
|
||
|
|
1088320571 |
fix: terminate cleanly on a broken stdout pipe across all family-emitting commands
GREEN for the broken-pipe RED test (
|
||
|
|
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 |
||
|
|
d50a4828ad |
audit(0077): cycle close — drift addressed; legacy-load fixture + ledger sync; drop ephemera
Architect drift review over 9c6b9c7..cb32658 returned drift_found (3 items); all resolved here. What holds (architect): C23 default-argmax byte-identical, C9 separation (selector in aura-registry, walk_forward selection-agnostic), C1/C2 (plateau pure/deterministic/in-sample), one higher_is_better direction source. Resolutions: - [medium] Backfilled the pre-0077 FamilySelection legacy-load fixture (report.rs family_selection_loads_a_pre_0077_legacy_line). The feat commit and the struct doc-comment claimed "a legacy line still loads (C14/C18)" as landed, but no test pinned the field-level mapping: bare deflation scalars -> Some(...), absent plateau keys -> None. families.jsonl/runs.jsonl are append-only, so a winner stamped pre-0077 must read back; now pinned. - [low] Synced the ledger's inferential-validation open thread (docs/design/INDEX.md): #145 plateau-over-peak landed (cycle 0077), selection is now a pluggable objective (bare argmax -> trials-deflated -> plateau) on the manifest carrier; only #146 (cross-instrument generalization) remains of the milestone's three pieces. - [medium-high] Removed the cycle ephemera (git rm docs/specs/0077-*, docs/plans/0077-*) per the cycle-close convention. Regression: full workspace suite green and clippy --all-targets -D warnings clean (verified at feat-review time). No baseline scripts configured; the test + clippy gates are the regression gate. Cycle 0077 tidy (clean) — drift-clean, not a milestone close (the milestone fieldtest is a separate deliberate gate). |
||
|
|
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 |
||
|
|
ecc9541a56 |
0076.tidy: backfill spec-promised deflation tests + guard the dispersion-floor sign
Audit (architect, cycle 0076) found three tests the spec's testing strategy promised but the implement-loop did not land, plus an unguarded sign assumption: - optimize_deflated_centres_the_null_so_a_uniform_edge_is_not_called_overfit: the load-bearing protection for the mean-subtraction. A family where every member shares the same +R edge reads as NOT overfit only because the null is centred; an uncentred null would give ≈0.5. Removing the centring now flips a test (it previously passed every test — the architect's headline finding). - optimize_deflated_all_noise_family_has_high_overfit_probability: the high-p companion to the existing clear-edge low-p test. - optimize_deflated_provenance_reflects_only_the_passed_family: C2 at the registry boundary (n_trials == family size, raw == the family's own argmax), pinning that the record encodes only in-sample information. - A debug_assert in the dispersion-floor arm: it subtracts inflation, valid only for higher-is-better metrics; lower-is-better (max_drawdown/bias_sign_flips) would deflate wrong-signed. Unreached from the two shipped call sites, but optimize_deflated is public — guard the assumption rather than leave it implicit. cargo test -p aura-registry (33) + clippy green. refs #144 |
||
|
|
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 |
||
|
|
c192dfd344 |
audit(0075): cycle close — drift-clean; doc-link + trade_rs tripwire tidy; drop ephemera
Architect drift review (c99e7b8..2e77ab1) against the design ledger +
CLAUDE.md invariants: drift-clean, debt only — no contract or spec
violation. Verified intact: C18 wire shape (trade_rs is #[serde(skip)] +
excluded from the hand-written PartialEq; runs.jsonl/families.jsonl bytes
and digests unmoved; mc_r_bootstrap is a printed line, never a record),
C1 determinism (seeded r_bootstrap + disjoint walkforward windows, both
pinned by byte-identical-rerun e2e tests), C7 frictionless Stage-1
(round_trip_cost stays 0.0), C8/C12 streaming reduction (IS sweep folds
O(trades)/member, OOS run non-reduce). The verbatim summarize_r /
r_metrics_from_rs float duplication is guarded by the cross-reducer
equality test — sound.
Tidy applied this close:
- doc-link drift: the new E[R] notation in r_bootstrap / RBootstrap /
run_mc_r_bootstrap doc-comments was parsed by rustdoc as an intra-doc
link `[R]` (4 warnings). Wrapped in backticks; doc build now clean.
- coverage micro-gap [low, architect]: added a tripwire asserting a
populated trade_rs is absent from the serialized JSON (the serde(skip)
C18-preservation property had no direct test — only Vec::new() was
serialized before).
Carry-on (benign debt, not fixed): the mc usage literal carries a `usage:`
prefix the sibling sweep/walkforward usages omit — required because the
new `["mc", rest @ ..]` arm now handles the `--real` rejection and the
preserved golden asserts stderr.contains("usage"); churning the golden to
drop the prefix is not worth it.
Removed the cycle's ephemeral spec + plans (docs/specs/0075,
docs/plans/0075, docs/plans/0076) per the lifecycle rule.
Verified: cargo test --workspace 610 passed / 0 failed, clippy -D warnings
clean, cargo doc --no-deps clean.
closes #139
|
||
|
|
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 |
||
|
|
dda20abc28 |
audit(0073): cycle close — lift tier-2 geometry to ledger, finish VETTED_SYMBOLS, drop ephemera
Cycle-close audit: drift-clean on all load-bearing invariants (C7/C8/C15 hold; geometry stays non-scalar metadata, the Source seam still carries only (Timestamp, Scalar); scope boundary held — no resolver, quote_currency still &'static str, both #124's). Tidy: completed the VETTED_SYMBOLS consolidation the feat commit over-claimed — migrated the two remaining inline vetted-symbol arrays in the crate-internal unit tests (instrument_spec_pip_value_matches_contract_times_pip, instrument_spec_ids_are_distinct) to the const, so #140 now extends one list. Ledger: lifted this cycle's durable rationale into the C15 realization note (docs/design/INDEX.md) — the floor now carries tick_size/digits, and the recorded-metadata tier-2 source (instrument_geometry over symbol_meta -> neutral InstrumentGeometry, cross-checked against the authored floor) now exists at the ingestion edge; the tier-composing resolver remains #124. Drop the ephemeral cycle spec + plan (docs/specs, docs/plans) per the lifecycle convention; durable rationale now in the ledger + the #143 thread. refs #143 |
||
|
|
5b7cd5eb2d |
feat(0073): consume DataServer geometry — cross-check vetted floor + tick_size/digits
Make data-server's neutral InstrumentGeometry a first-class, broker-agnostic input at the aura-ingest edge: re-export InstrumentGeometry + a thin instrument_geometry(server, symbol) accessor over symbol_meta (the recorded dataset-metadata tier 2 of #124's hierarchy; the tier-composing resolver stays #124). Add the two deferred deploy-grade InstrumentSpec fields tick_size + digits (additive, struct stays Copy; C15), authored for the 5 vetted symbols from the verified sidecars. A new real-data cross-check asserts the authored floor agrees with provider geometry (pip_size, contract_size==lot_size, the C10 pip_value invariant, tick_size, digits, quote_currency), iterating the vetted set so #140's additions are covered for free; skip-if-archive-absent. Scope (derived, recorded on #143): no runtime resolver and no quote_currency type change — both remain #124's. 'quote_currency beyond FX' is satisfied at the geometry tier (InstrumentGeometry.quote: String), not by widening the &'static str floor. Beyond the plan (implementer additions, reviewed and accepted): hoisted the vetted-symbol list to one pub const VETTED_SYMBOLS, collapsing the three drift-prone copies plan-recon flagged (#140 now extends one constant); a tick_size == 10^(-digits) consistency guard (verified to hold across all 30 provider sidecars, so it will not false-trip on #140); a re-export-nameable seam test guarding the InstrumentGeometry re-export from silent removal. Verification: cargo test --workspace all green; cargo clippy --workspace --all-targets -D warnings clean. The cross-check ran against the live Pepperstone archive (not skipped) — all 5 vetted sidecars match the authored table. refs #143 |
||
|
|
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 |
||
|
|
f0949578a3 |
test(0072): mean-reversion signal-composition e2e (characterization pin)
Hand-wired EWMA Bollinger-band fade subgraph (price -> Ema mean + dev -> Mul square -> Ema var -> Sqrt sigma -> LinComb k-band -> Add/Sub bands -> Gt/Gt -> Latch/Latch -> Sub bias). Pins the fade direction (above mean+k*sigma -> short, below -> long), the latch hold, and flat-series -> no fade. Green on arrival (composes only existing aura-std nodes; no new node), so it is a characterization pin rather than a RED->GREEN unit. 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 |
||
|
|
cfe7bad0ff |
feat(0070): streaming sink reductions — finalize hook + folding sinks (#138)
M3 machinery for BLOCKER #138: sink reductions can now fold online instead of buffering every per-cycle row to an unbounded channel. - aura-core: additive `Node::finalize` end-of-stream hook (default no-op) + `SeriesFold` online accumulator (last / max_drawdown / sign_flips). - aura-engine: `Harness::run` calls `finalize` once per node in topo order after the source loop; `summarize` refactored to drive `SeriesFold` (byte-identical), the now-dead `sign0` removed. - aura-std: `GatedRecorder` (emits only gated rows + the final row on finalize) and `SeriesReducer` (folds one f64 series, emits one summary row on finalize), both emitting the `Recorder` channel type — no aura-engine dependency. - Integration tests: folded reductions are byte-for-byte equal to the raw-recorder path; the R-reduction's peak memory is O(trades), independent of cycle count. Ratified two off-plan fixes the implementer made in the tests (both plan defects, not code defects): the GatedRecorder correctly flushes the final non-gated row on finalize, so the retained count is K_TRADES + 1; and the heterogeneous PM record needs kind-correct zero scalars (a blanket f64 zero is rejected by the kind-typed AnyColumn::push). Wiring these reducers into the stage1-r sweep (the actual O(cycles)->O(trades) win) follows next. refs #138 |