119221c8bbcb2b9cc1de7c7a75050f04e89f98b3
261 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
119221c8bb |
spec: 0046 sweep named-binding (C12 #57)
Thread a derived named view of a sweep point so consumers stop re-zipping param_space() names onto the bare &[Scalar]. A free function zip_params over (ParamSpec names ⊗ positional point) in aura-core; GridSpace retains the ParamSpec list it already receives in new(); SweepFamily carries it and exposes named_params(i). The run-closure signature `F: Fn(&[Scalar]) -> RunReport + Sync` stays byte-for-byte (honours #52's #71-firewall constraint, so RandomSpace plugs in with zero reconciliation). The lossy f64 collapse moves into the manifest constructor; the view stays typed. Behaviour-preserving; SweepPoint and enumeration untouched. Design ratified in-context (brainstorm, approach C over A, free function over a NamedPoint type); a reconciliation comment on #57 records the resolved forks with provenance. Human sign-off after the auto-sign panel's grounding lens caught a blast-radius undercount in the first draft — corrected to all eight sim_optimal_manifest call sites plus the aura-registry SweepFamily struct-literal. refs #57 |
||
|
|
c2756732d4 |
refactor(aura-registry): split FamilyRunRecord family_id into {family, run} + derived id
Behaviour-preserving normalisation of the lineage record. The fused
`family_id: String` ("{name}-{counter}") is split into its two factors —
`family: String` + `run: usize` — and the user-facing handle becomes a derived
`family_id()` method ("{family}-{run}"), never stored or parsed.
Why: the fused field forced the counter logic to generate-and-check candidate
strings (to stay robust to '-' inside a name) rather than read it; with `run` a
plain int, `next_run` is a clean numeric max+1 over the field. The split is more
normalised (one fact per field) and the only property the fused token bought — a
single paste-able CLI handle — is recovered by deriving it. Storage and all
user-visible output stay byte-identical: append_family still returns
"{name}-{run}", Family.id is still "{name}-{run}", and the CLI prints the same
"family_id" lines (the json! key + the returned String, both unchanged).
Internal-only: FamilyRunRecord and group_families (now keyed on the (family,
run) tuple, first-seen order preserved) change; lib.rs re-exports, the
extractors, and all of aura-cli are untouched (everything downstream goes
through Family.id + append_family's returned String). The on-disk
families.jsonl key changes ({"family","run"} vs {"family_id"}) — a gitignored
runtime artifact with no committed fixture, and tests round-trip within a temp
dir, so no migration is owed. Doc reconciliation: the lineage/lib module headers
and the C18 ledger realization note now describe the split shape.
Verification: a workflow ran the refactor in an implementer agent, then three
adversarial lenses (behaviour-preservation, new-logic correctness,
completeness/doc-lag) tried to refute it — the first two found nothing, the
third flagged the four doc-lag sites now fixed here. Self-run gates:
cargo test --workspace green (registry 11, incl. a strengthened round-trip test
pinning the split fields + derived handle), cargo clippy --workspace
--all-targets -D warnings clean, cargo doc clean.
|
||
|
|
a168f07cbb |
audit: cycle 0045 — registry lineage (C18 ledger note; flat-store follow-up #73)
Architect drift review (eeba218..HEAD) + regression gate (the project's cargo test --workspace is the gate; no separate regression script). What holds (architect): C9 layering intact — aura-engine gains no registry dependency, lineage lives entirely in aura-registry. #71 firewall held — RunManifest/aura-core byte-untouched, no blob/path/payload field on FamilyRunRecord, the member window is producer-supplied via Source::bounds()/window_of, never a Vec scan. C2 clean — bounds() is cursor-independent (no look-ahead). Family store is a disjoint sibling of the flat store (pinned by a disjointness test). Regression gate green: 228 tests, clippy --workspace --all-targets -D warnings clean. Drift resolved: - [fix, this commit] C18 ledger gap: docs/design/INDEX.md C18 carried no realization note for the run registry. Added the cycle-0029 (flat registry) and cycle-0045 (lineage as related records / family store) realization notes, in the ledger's established per-cycle style. - [forward-queue] Flat-store CLI dead-end: after this cycle no CLI command writes the flat runs.jsonl (sweep/walkforward moved to the family store, aura run never persisted), so aura runs list / rank read an unfed store. This is a spec-accepted state (0045 kept runs list "for standalone runs only") and a genuine design fork (give aura run a persisting path vs. retire the flat-store CLI surface) with no clear default — filed as #73 rather than decided here. Recorded as a deferred follow-up in the C18 ledger note. - [carry-on] Low/cosmetic: family-wrapped CLI stdout alphabetizes nested manifest keys (serde_json::json!) vs. declaration order (to_json()); both valid JSON, the stored families.jsonl uses declaration order, round-trip unaffected. Noted as a sub-item on #73. refs #70 |
||
|
|
bcd1072ca8 |
feat(aura-cli): aura mc + family-aware runs + window_of migration (0045 iter 2)
Iteration 2 of spec 0045 — the CLI surface for orchestration families. window_of migration: every manifest-window scan (run_sample, sweep_family, walkforward_family, sweep_over, run_oos, run_macd) now reads the window from Source::bounds() via window_of(&sources) instead of prices.first()/.last(). Behaviour-preserving (the walk-forward roller boundaries are bar-aligned, so the producer-supplied window equals the old first/last) — pinned by the unchanged run_sample (1,7) and walk-forward determinism tests. aura mc: new built-in Monte-Carlo family on the CLI (monte_carlo over a built-in seed set + the sample harness re-seeded per draw), persisted via append_family and rendered as one member line per seed (carrying the family_id) plus an mc_aggregate line. McAggregate is not Serialize, so the aggregate line is built from its three MetricStats blocks. family-aware runs: aura sweep / walkforward / mc now persist to the family store via append_family with an optional --name (per-kind default), printing the assigned family_id per member. aura runs families lists family headers (id, kind, member count); aura runs family <id> [rank <metric>] lists/ranks one family's members as a unit. An unknown family id is an empty family (exit 0); an unknown metric is a usage error (exit 2). Two pre-existing cli_run.rs E2E tests were adapted to the new family contract (a required consequence of sweep/walkforward switching from the flat store to the family store — a plan gap caught at implement time): the sweep-output test now asserts the family-wrapper, and the runs list/rank flow became the family list / per-family-rank flow (which also exercises the per-name counter sweep-0/sweep-1 end-to-end). A new E2E test drives aura mc through the binary. Known cosmetic detail: the family-wrapped sweep/mc/walkforward stdout nests the report via serde_json::json! (to_value -> alphabetical keys, leading "broker"), while runs family / runs list print via RunReport::to_json() (declaration order, leading "commit"). Both valid JSON; key order is not load-bearing and the stored families.jsonl uses to_string (declaration order), so the round-trip is unaffected. Verification: cargo test --workspace green; cargo clippy --workspace --all-targets -D warnings clean. refs #70 |
||
|
|
1d9555c468 |
feat(aura-engine,aura-registry): producer-supplied window + registry lineage (0045 iter 1)
Iteration 1 of spec 0045 — the engine + registry core for storing orchestration families as linked records. aura-engine: add Source::bounds() — the inclusive (from, to) data extent a producer will stream, known without materialization (#71 firewall) — and a free window_of() that folds the union extent across a run's sources. This is the producer-supplied replacement for the prices.first()/.last() Vec scans at the CLI call sites (those migrate in iteration 2); a lazy producer now reports its window so stored lineage stays byte-identical whether a source is eager or streamed. aura-ingest: M1FieldSource stores its requested [from_ms, to_ms] window at open and reports it via bounds() (normalized to epoch-ns; None when open-ended — the archive-extent query is a deferred non-goal). aura-registry: new lineage module — FamilyKind / FamilyRunRecord (a RunReport stamped with family_id + kind + ordinal) / Family, plus Registry::append_family (assigns family_id = "{name}-{counter}" via a per-name generate-and-check counter, writes members to a sibling families.jsonl) and load_family_members; group_families re-derives the families (the round-trip), and the three *_member_reports extractors pull the per-kind member reports. The flat runs.jsonl store and its append/load/rank_by/optimize API are byte-for-byte unchanged (a test pins the two stores' disjointness). C9 preserved: aura-engine gains no registry dependency. New dep: serde derive on aura-registry (per-case policy, same basis as serde_json already is). Verification: cargo build --workspace; cargo test --workspace (228 green, incl. 3 new engine bounds/window_of, 1 data-gated ingest bounds run against real archive data, 5 new registry lineage round-trip/counter/disjointness tests); cargo clippy --workspace --all-targets -D warnings clean. The CLI surface (aura mc, family-aware aura runs, the window_of migration) is iteration 2. refs #70 |
||
|
|
d6daaef009 |
plan: 0045 registry lineage for orchestration families
Decompose spec 0045 into two iterations. Iteration 1 (engine + registry core): Source::bounds() + window_of, M1FieldSource::bounds(), and the aura-registry lineage module (FamilyKind / FamilyRunRecord / Family, group_families, next_family_id, the three *_member_reports extractors, Registry::append_family / load_family_members) with round-trip and per-name-counter tests. Iteration 2 (CLI surface): aura mc, family-aware aura runs (families / family [rank]), sweep/walkforward family persistence via append_family with --name, and the window_of migration of the manifest-window scans. refs #70 |
||
|
|
bf829f605b |
spec: 0045 registry lineage for orchestration families (boss-signed)
Persist sweep / Monte-Carlo / walk-forward runs as named, linked families
(C18 lineage / C21): a FamilyRunRecord stamps each member with a shared
family_id = "{name}-{counter}" in a sibling families.jsonl store, group_families
re-derives a family as a unit, and the CLI gains aura mc + family-aware aura runs.
Producer-supplied window via Source::bounds()/window_of (the #71 firewall: no Vec
scan, no input artifact). family_id form (name + counter, not a content-hash)
resolved by the user in-session; reconciliation comment on the issue records it.
Auto-signed under the boss spec-auto-sign gate: all objective gates green
(precondition, self-review, grounding-check PASS) and a unanimous five-lens
spec-skeptic panel (criterion, grounding, scope-fork, ambiguity, plan-readiness).
refs #70
|
||
|
|
eeba2182d7 |
audit: cycle 0044 — drift-clean (walk-forward family)
Architect drift review over 61a8436..HEAD (the 0044 walk-forward cycle: spec |
||
|
|
4764656062 |
feat(aura-engine): walk-forward family — WindowRoller + walk_forward (C12 axis 3)
The third C12 orchestration axis: walk-forward varies the data window. A
WindowRoller is a pure iterator of WindowBounds { is, oos } — bounds only, never
tick data (#71 firewall). walk_forward runs a caller closure per split disjointly
over the shared run_indexed core (C1), and stitches the OOS pip-equity segments
into one continuous curve (each segment offset by the running sum of prior
segments' finals; an empty segment contributes 0.0, mirroring summarize). C2
no-look-ahead is structural: the roller only ever emits oos.0 == is.1 + 1, pinned
by a zero-tick bounds test. The in-sample optimize (axis 2) is closure-supplied,
not called by the engine: aura-engine has no aura-registry dependency (C9), and
C12 forbids baking search policy into the primitive — the CLI bridges both crates.
Param-stability is on-demand, not stored (the R2 decision, recorded with
provenance on #69): WalkForwardResult stores only the raw per-window outcomes +
the stitched curve, and param_stability(&result) -> Vec<MetricStats> is a public
reduction over the retained per-window chosen params. A stored summary would be
recomputable from the windows (redundant) and would force one statistic canonical
when "stability" admits several; this mirrors SweepFamily (raw points + on-demand
optimize), not McFamily (stored aggregate). MetricStats::from_values is extracted
from McAggregate::from_draws (behaviour-preserving — the 7 mc tests stay green)
and gains serde so the CLI summary renders it and #70 lineage can persist it.
`aura walkforward` runs a built-in rolling walk-forward over a synthetic windowed
source: per window it sweeps the built-in grid in-sample, optimizes by total_pips,
runs the chosen params out-of-sample, persists each OOS RunReport (C18), and prints
a stitched summary line.
Two plan deviations, both compiler-forced and consistent with siblings: added
#[derive(Debug)] to WindowRoller (the RED tests' unwrap_err needs Ok: Debug; the
sibling types already derive Debug); removed a now-redundant test-module
SyntheticSpec import after Step 2 hoisted it to the top-level use.
Verification: cargo test --workspace green (aura-engine 152 incl. walkforward 9 +
mc 9, aura-cli 14); clippy --workspace --all-targets -D warnings exit 0; cargo doc
--workspace --no-deps clean.
refs #69
|
||
|
|
7d52eff41f |
plan: 0044 walk-forward family (C12 axis 3)
Bite-sized, placeholder-free plan for the walk-forward axis, in three tasks: (1) extract MetricStats::from_values from McAggregate::from_draws + serde derive (behaviour-preserving, the 7 mc tests guard); (2) the walkforward module — WindowRoller (bounds-only iterator), walk_forward over the shared run_indexed core, continuous OOS stitch (empty segment -> +0.0), on-demand param_stability, + crate exports + module-header doc-lag fix; (3) the aura walkforward CLI demo bridging engine+registry via optimize, with serde_json for the summary line. RED-first tests 1-11 (incl. 7b empty-segment stitch) per the spec. refs #69 |
||
|
|
a4712eb336 |
spec: 0044 walk-forward family (C12 axis 3)
Walk-forward orchestration: a WindowRoller rolls (in-sample, out-of-sample) splits over a time span, walk_forward runs a disjoint harness per split via the shared run_indexed core (C1), and stitches the OOS pip-equity segments into one continuous curve. The varying dimension is the data window (axis 3), realized eager-agnostically (#71): the public surface carries WindowBounds + a per-window closure, never a materialized stream Vec; C2 no-look-ahead is a pure bounds invariant (oos.0 > is.1), checkable with zero ticks. The in-sample optimize (axis 2) is closure-supplied, not called by the engine: aura-engine cannot depend on aura-registry (C9), and C12 forbids baking search policy into the primitive. The CLI bridges both crates in run_walkforward. Param-stability shape (the load-bearing fork) resolved with the user as R2: on-demand, not stored. WalkForwardResult stores only raw per-window outcomes + the stitched curve; param_stability(&result) -> Vec<MetricStats> is a public on-demand reduction over the retained per-window chosen params (sweep-precedent: summaries computed on demand, not cached, vs McFamily's stored aggregate). A stored summary would be recomputable from the raw windows (redundant) and would force one statistic canonical when "stability" admits several. Recorded with provenance in the #69 reconciliation comment. MetricStats::from_values is extracted from McAggregate::from_draws (behaviour-preserving; the 7 mc tests guard) so the MC aggregate and the helper share one reduction. Empty OOS segment contributes 0.0 to the stitch offset (mirrors summarize's unwrap_or(0.0)). Gates: Step-1.5 precondition clean (no fork silently picked), self-review clean, grounding-check PASS (twice — re-run after each edit). Auto-sign panel escalated on the scope-fork design lens (param-stability shape) to human sign-off; the user resolved it (R2), so this is a user-signed spec, not boss-signed. refs #69 |
||
|
|
61a8436c2c |
audit: cycle 0043 — drift-clean (monte-carlo family)
Architect drift review over b920362..HEAD (the 0043 monte-carlo cycle). Cycle is sound: C12 axis 4 faithfully realized (monte_carlo varies the seed, the 0042 seeded source genuinely perturbs each run, so distinct_seeds_produce_distinct_draws is a real property); C1 honoured (one run_indexed core, output sorted by job index = input order independent of completion, 1-vs-8-thread determinism pinned, parallelism across draws never within); the run_indexed extraction is a genuine behaviour-preserving refactor (C11/C23 — the three pre-existing sweep tests stay green, and both family executors are thin adapters over the single core; the points.len() -> n.max(1) clamp is equivalent since GridSpace rejects empty axes); the #71 eager-agnostic firewall is respected (API takes seeds + a per-draw closure, no materialized stream Vec crosses the boundary). One medium doc-lag found and fixed here: aura-engine/src/lib.rs module header listed Monte-Carlo (and, already stale, the grid sweep) under "Still to come"; both orchestration axes ship now, so the header records the grid axis (sweep / SweepFamily) and the seed axis (monte_carlo / McFamily) as delivered, with walk-forward, random param-sweep, registry lineage, and the C10 position-event / broker layer remaining future. Regression gate (test suite — no dedicated regression scripts in project facts): cargo test --workspace green (incl. 7 new mc::tests + the sweep guard tests); clippy --workspace --all-targets -D warnings clean; cargo doc -p aura-engine clean (the new intra-doc links resolve). closes #68 |
||
|
|
e2c550f2f9 |
feat(aura-engine): monte_carlo family — McFamily over a seed set (C12 axis 4)
`monte_carlo(base_point, seeds, run_one) -> McFamily`, the Monte-Carlo
orchestration family: a fixed base point run over a seed set, each seed a
disjoint C1 realization. C12 axis 4 — Monte-Carlo IS a sweep over seeds, so it
reuses the disjoint-parallel executor rather than forking a run loop.
The reuse is realized by extracting the index-parallel core out of
`sweep_with_threads` into a `pub(crate) run_indexed<T>` (generic over the
per-job result, over a 0..n job range); `sweep_with_threads` becomes a thin
adapter over it. Behaviour-preserving (C1/C11/C23): the three pre-existing sweep
tests stay green — they ARE the proof the refactor preserved behaviour. A small
incidental win: the adapter consumes `points` via zip, dropping the old
per-point clone.
mc.rs adds McDraw{seed,report} / McFamily{draws,aggregate} (draws in seed-INPUT
order, independent of thread completion) / McAggregate (mean + p5/p25/p50/p75/p95
of all three run metrics — the V1 decision: covers every metric, not a "chosen"
one) / MetricStats, a private type-7 linear-interpolation `quantile`, and
`McAggregate::from_draws` (a pure post-run reduction recomputable from the
retained raw draws). Eager-agnostic firewall (#71): the API takes seeds + a
per-draw closure `Fn(u64,&[Scalar])->RunReport`, never a materialized stream Vec;
seed->Source construction stays a closure-body concern. An empty seed set is a
debug-asserted precondition, preserving the no-Result `-> McFamily` signature.
7 RED-first mc::tests pin: one draw per seed in input order; determinism across
1 vs 8 threads (C1); draw == an independent seeded run; distinct seeds perturb
the family; aggregate == from_draws(draws); type-7 quantile/mean math on a known
fixture; quantile endpoints + singleton. Workspace green, clippy -D warnings
clean, cargo doc clean.
refs #68
|
||
|
|
5181a7132c |
plan: 0043 monte-carlo family — McFamily over a seed set
Three tasks: (1) extract the disjoint-parallel core out of sweep_with_threads into a shared `pub(crate) run_indexed<T>` (behaviour-preserving; the three pre-existing sweep tests are the green guard), (2) add the `mc.rs` module (McDraw/McFamily/McAggregate/MetricStats, type-7 `quantile`, `McAggregate::from_draws`, `monte_carlo`/`monte_carlo_with_threads` driving run_indexed) with 7 RED tests, (3) wire `mod mc;` + the five public exports into lib.rs. RED-first; module-declaration ordering keeps mc.rs's tests dark until the final wire-up task. refs #68 |
||
|
|
0bbc8190b8 |
spec: 0043 monte-carlo family — McFamily over a seed set (boss-signed)
C12 axis 4: Monte-Carlo as a sweep over seeds. `monte_carlo(base_point, seeds, closure) -> McFamily`, the analog of SweepFamily, reusing the disjoint-parallel executor (extracted into a shared `run_indexed` core) rather than forking a new run loop. Each realization is a disjoint C1 unit; parallelism is across draws. The aggregate (V1) covers all three run metrics with mean + p5/p25/p50/p75/p95, a pure post-run reduction over the retained raw draws. Eager-agnostic firewall (#71): the API takes seeds + a per-draw closure, never a materialized stream Vec; seed->Source construction stays a closure-body concern. Signed under the /boss spec auto-sign gate: objective gates green (precondition, self-review, grounding-check PASS) and a unanimous five-lens spec-skeptic panel (criterion, grounding, scope-fork, ambiguity, plan-readiness) returned SOUND. The aggregate fork was settled to V1 by the user (issue #68 reconciliation comment, verbatim "ja, wir machen v1", 2026-06-15). refs #68 |
||
|
|
b9203624d3 |
audit: cycle 0042 — drift-clean (seed-as-input)
Architect drift review over 682e459..HEAD (the seed-as-input cycle plus the prior unaudited `aura run --real` work). Cycle is sound: Fork B respected (seed forks at the data-generation edge into the source and the manifest; the engine loop sees only a Source — C12/C1/C3 intact), the contract names Source never Vec (precondition for #68/#52 delivered as specced), and the four seed-free callers thread 0 in lockstep. One medium doc-lag item found and fixed here: aura-engine/src/lib.rs module-doc listed the seed axis under "still to come"; seed-as-input shipped this cycle and RunManifest.seed is now live, so the headline doc now records SyntheticSpec as a delivered seeded Source producer (params + orchestration families remain future). One low carry-on note (not drift): run_sample_seeded exists only under #[cfg(test)] — intended this cycle (no --seed CLI flag per spec non-goals); the only non-test seed value remains 0. A scaling marker for #68, not debt. Regression gate (test suite — no dedicated regression scripts in project facts): cargo test --workspace green; clippy --workspace --all-targets -D warnings clean; cargo doc -p aura-engine clean (the new [`SyntheticSpec`] intra-doc link resolves). |
||
|
|
c9f0e438e1 |
feat(aura-engine): seeded source + live RunManifest.seed (C12 seed-as-input)
A seeded source whose stream is fully determined by a u64 seed, making RunManifest.seed a live captured input instead of the dead 0 it was. This is C12's reconciliation of stochastic runs with C1: a run is bit-identical for a fixed seed (the seed is a captured input, not hidden nondeterminism), and a different seed gives a different-but-reproducible run. Precondition for the Monte-Carlo family (#68) and random param-sweep (#52). Engine (aura-engine): - SplitMix64: a ~10-line private, dependency-free PRNG. Chosen over a rand-family crate for guaranteed bit-stability across toolchains/crate versions — the whole value of seed-as-input is reproducibility, and a crate whose algorithm is not version-stable would silently break a recorded seed on a dep bump. No new direct dep. - SyntheticSpec + source(seed) -> impl Source: the Fn(u64) -> impl Source contract (Fork B — seed at the data-generation edge, outside the engine graph). Returns a Source, never a Vec, so the MC family can re-seed N times without materializing N streams. First cut collects to a VecSource internally; the signature does not name Vec, so a lazy generator is a drop-in replacement. Re-exported from lib.rs beside VecSource. CLI (aura-cli): - sim_optimal_manifest gains a seed: u64 param recorded into the manifest (the single Fork B capture site). All four existing seed-free callers (run_sample, run_sample_real, the sweep grid-report, run_macd) pass 0 unchanged — their determinism tests stay green. - A test-only run_sample_seeded + SeededTrace exercise a live seed and expose the drained sink trace, so the three e2e tests assert bit-identity at the trace level (strictly stronger than the folded 3-field metrics). Tests (RED-first): 2 engine producer tests (same-seed identical / different-seed differs) + 3 cli e2e tests (bit-identical trace, different-seed metrics differ, seed recorded in manifest). Full workspace green; clippy -D warnings clean. Errata vs plan 0042 (two compile-forced deviations, neither alters the spec contract): - source returns `impl Source + use<>`: under edition 2024 a bare `impl Source` captures the &self lifetime, which blocks boxing as Box<dyn Source + 'static> for run() (E0597). The source owns its data, so use<> (captures nothing) is correct. - SyntheticSpec is imported inside `#[cfg(test)] mod tests` in main.rs: it is used only by the seeded test vehicle, so a top-level import is unused in the non-test build under clippy -D warnings. closes #66 |
||
|
|
f7230a5068 |
plan: 0042 seed-as-input — seeded source + live RunManifest.seed
Two tasks: (1) aura-engine SplitMix64 PRNG + pub SyntheticSpec::source(seed) -> impl Source beside VecSource, with producer RED tests; (2) aura-cli sim_optimal_manifest gains a seed param (four callers pass 0), plus a test-only run_sample_seeded + SeededTrace and three e2e RED tests pinning the #66 acceptance. RED-first throughout. refs #66 |
||
|
|
5604c420c5 |
spec: 0042 seed-as-input — seeded source + live RunManifest.seed (boss-signed)
A seeded source whose stream is fully determined by a u64 seed, wired so the seed reaches RunManifest.seed at the manifest-construction site (Fork B: seed at the data-generation edge, outside the engine graph). Contract is a producer Fn(u64) -> impl Source, never seed -> Vec, so the Monte-Carlo family can re-seed N times without materializing N streams. Deterministic dependency-free PRNG (SplitMix64) for C1 bit-stability. Precondition for #68 (Monte-Carlo) and #52 (random sweep). Auto-signed under /boss spec auto-sign: objective gates green (precondition, self-review, grounding-check PASS) and a unanimous five-lens spec-skeptic panel (criterion, grounding, scope-fork, ambiguity, plan-readiness all SOUND) after one editorial-repair round. refs #66 |
||
|
|
0a2e2022a5 |
docs(aura-cli): note the --real real-data path in the module header
The header described `aura run` as synthetic-only; --real now also streams real M1 bars through the #71 Source seam. Keep the entry-point doc honest. |
||
|
|
0ce843bada |
feat(aura-cli): aura run --real <SYMBOL> backtests the sample on real M1 bars
The first real-data backtest from the CLI: `aura run --real <SYMBOL> [--from <ms>] [--to <ms>]` runs the existing built-in sample signal-quality harness over real M1 *close* bars instead of synthetic prices, printing the same RunReport JSON as `aura run`. Bars are streamed lazily through aura_ingest::M1FieldSource (a Box<dyn Source>) — dogfooding the #71 Source seam, O(one chunk) resident, never eager. aura-ingest + data-server enter aura-cli's deps (data-server git line mirrored from aura-ingest). M1 only this cycle, deliberately. The TickSource is deferred: raw ticks are the wrong input for the bar-semantic SMA sample (an SMA over ticks is microstructure noise until a resampler node exists, C2), and tick's real driver is the realistic broker's bid/ask execution (C10) — it should arrive with that consumer, not speculatively here. Tick data is also local to only EURUSD/XAUUSD/GER40, whereas M1 covers the test symbol AAPL.US. Shape: run_sample_real builds sample_harness(), guards has_symbol / M1FieldSource::open (unknown symbol or empty window -> 'aura: no local data' + exit 2, never a panic), and reuses sim_optimal_manifest + the run_sample fold. parse_real_args is a pure, unit-tested grammar (mandatory symbol, then --from/--to in any order; bad/missing/duplicate flag -> Err). Data path is data_server::DEFAULT_DATA_PATH (no --data-path flag this cycle). Known simplification: the manifest window is derived by draining a *separate* single-pass probe source for first/last ts, so an unbounded window streams the archive twice (once to bound, once to run). Acceptable and O(1)-resident for now; a future Harness::run could surface the first/last cycle ts to drop the probe pass. Verified: cargo build/test/clippy --workspace all green; the gated run_sample_real_streams_real_close_bars_deterministically RAN (not skipped) over AAPL.US 2006-08; manual CLI smoke of the success + two error paths. |
||
|
|
682e459554 |
audit: cycle 0041 — drift-clean (Source ingestion seam)
Architect drift review over 8b330e3..HEAD (the Source-seam cycle #71 plus the interim #67 optimize work and refactors). No semantic or contract drift: the seam faithfully preserves C3 (ms→epoch-ns at the one ingestion boundary), C4 (tie-by-source-index, byte-for-byte), and C7 (field-per-source SoA); the full suite is byte-identical green and the gated streaming tests pass on real data. Regression gate: `cargo test --workspace` green (the project declares no dedicated regression script; the suite is the gate). Tidied stale prose the cycle left behind (no behaviour change): - docs/design/INDEX.md (C12): replaced the cycle-0011 "deliberate eager gap" status note with a cycle-0041 realization note — the producer `Source` seam + streaming `M1FieldSource` now deliver per-source O(one-chunk) streaming; precisely scopes what remains open (cross-*sim* Arc<[T]> window sharing, still unbuilt until the orchestration families #66/#68/#69 consume it). - aura-core/src/lib.rs: dropped the now-shipped "Source trait + data-server ingestion" from the module doc's "still to come" list (aura-engine's twin line was fixed in the cycle; aura-core's was missed). - aura-ingest/src/lib.rs: module header now documents both coexisting paths (eager load_m1_window/M1Columns vs lazy streaming M1FieldSource). - aura-registry/src/lib.rs: rank_by/optimize doc comments linked to the private `metric_cmp` (a public→private intra-doc-link warning from the #67 commit); demoted to plain code spans, so `cargo doc --workspace` is now warning-clean. |
||
|
|
8158e97dc6 |
feat(aura-ingest): streaming M1FieldSource — lazy data-server window through the seam
Completes the Source ingestion seam (plan Tasks 3-4): a real, lazily-streamed
data-server source proving the producer seam against the actual data source,
and closing the cycle-0011 deliberate eager-materialization gap.
- M1Field + a free decode(field, bar) — a pure projection of one M1 bar into
(epoch-ns ts, Scalar), reusing unix_ms_to_epoch_ns so ms->ns stays the one
C3 normalization point. Pure ⇒ hermetically unit-tested without an iterator.
- M1FieldSource: a streaming Source over a data-server M1 window. Holds one
Arc<[M1Parsed]> chunk (a zero-copy clone of the cache's chunk) + a cursor +
a pre-decoded head (so peek(&self) is cheap over a &mut next_chunk refill);
constructs each Scalar per-pull, refills when the chunk drains. Resident
footprint is O(one chunk), independent of window length.
- aura-engine moved [dev-dependencies] -> [dependencies] (M1FieldSource is a
library item implementing aura_engine::Source, not a test-only type).
- A gated streaming integration test (skips where local data is absent):
* end-to-end backtest through the seam to a RunReport, C1-deterministic
(two runs bit-identical);
* residency predicate — driving the source the way run() does (peek/next)
over the *unbounded* archive (~1.77M bars locally), peak resident_records
stays <= one data-server chunk (CHUNK_SIZE) while total >> CHUNK_SIZE:
O(one chunk), NOT O(window). A O(window) source would peak at `total`;
* two-field sharing (close + volume over one window, same ts axis).
The eager load_m1_window / close_stream path is kept (still valid for bounded
loads); the gap is closed by a streaming path now existing, not by deleting the
eager one. The residency test drives the full archive rather than the plan's
narrow 2006-08 window, which holds only ~21 bars locally — too few for the
> CHUNK_SIZE non-vacuity precondition; the unbounded archive is the stronger
"independent of window length" proof.
Verified: cargo build/test/clippy --workspace clean; the 3 gated tests ran
against real local data and passed; aura-ingest/aura-engine docs clean (two
pre-existing aura-registry doc-link warnings are from an earlier commit, not
this cycle).
closes #71
|
||
|
|
eec129ee51 |
feat(aura-engine): Source ingestion seam — run() takes Vec<Box<dyn Source>>
The engine's ingestion input was the only type encoding an eager-dataflow
assumption: `Harness::run(Vec<Vec<(Timestamp, Scalar)>>)` — one fully
materialized stream per source. At realistic research scale (20y, 3 tick
streams, ~7.9e9 ticks) that layout is ~190 GB resident, non-residable. Yet
the merge loop never needs the whole stream: it only ever peeks the head
timestamp of each live source and pops one record. So the eager Vec is
gratuitous.
This lands the first half of the Source seam (plan Tasks 1-2):
- A `Source` trait (`peek(&self) -> Option<Timestamp>`,
`next(&mut) -> Option<(Timestamp, Scalar)>`), object-safe so the merge
holds `Vec<Box<dyn Source>>`. The peek/next split is the producer contract
the k-way merge drives.
- `VecSource`, an adapter over the old `Vec<(Timestamp, Scalar)>` — the
cycle-0011 eager shape, named. It IS the old behaviour.
- `Harness::run` re-typed to `Vec<Box<dyn Source>>`; the merge loop's index
arithmetic becomes peek/next. C4 tie-by-source-index is preserved (the
strictly-`<` replace + source-order scan is byte-for-byte the old
semantics). The forward+eval body is unchanged.
- All 53 call sites threaded VecSource-wrapped in the same change, so the
workspace compiles atomically and run output is byte-identical (C1).
Behaviour-preserving is the load-bearing guarantee: the full suite is green
and unchanged (the two-run C1 determinism pairs — real_bars r1/r2, blueprint
sweep-equality, harness h/h2 — stay byte-identical); aura-engine unit tests
129 -> 132 (+3 VecSource unit tests). No residency change yet: VecSource holds
the same Vec as before. The lazy data-server-backed streaming source and its
end-to-end residency proof are the second half (plan Tasks 3-4).
refs #71
|
||
|
|
9337a8585d |
plan: 0041 source ingestion seam
Decomposes the boss-signed spec into four tasks: (1) the `Source` trait + `VecSource` adapter in aura-engine; (2) the atomic `Harness::run` re-type to `Vec<Box<dyn Source>>` with all 53 call sites threaded behaviour-preserving; (3) the streaming `M1FieldSource` over a data-server window; (4) a gated streaming e2e + the measured residency predicate. refs #71 |
||
|
|
ae240140c4 |
spec: 0041 source ingestion seam (boss-signed)
Re-types the engine's ingestion input from a materialized Vec<Vec<(Timestamp, Scalar)>> into a `Source` producer trait the k-way merge drives by peek/next, and proves it against a real, lazily-streamed data-server window in the same cycle — closing the cycle-0011 deliberate eager-materialization gap (C12 Arc<[T]> cross-sim sharing). A VecSource adapter keeps every existing run call site byte-identical (behaviour-preserving); an M1FieldSource streams data-server chunks with O(one-chunk) residency, independent of window length. Foundational cycle of the World-II milestone; #66/#68/#69 inherit the seam. Signed under the boss auto-sign gate: precondition clean, grounding PASS on the final bytes, unanimous five-lens spec-skeptic panel (two editorial ambiguity rounds on the residency predicate repaired to a measured per-pull CHUNK_SIZE ceiling). refs #71 |
||
|
|
2b56e090a9 |
feat(aura-registry): optimize — argmax over a SweepFamily by a named metric
Ships C12 orchestration axis 2 (optimize): pick the single best point of a sweep family by a named RunMetrics field. The GREEN half of the RED-first iteration whose executable-spec landed in the previous commit. optimize(&SweepFamily, metric) -> Result<SweepPoint, RegistryError> returns the whole winning SweepPoint — its params coordinate AND its RunReport — because the caller needs the params that won, not just the metric. "Best" is fixed per metric (total_pips higher-is-better; max_drawdown / exposure_sign_flips lower-is-better); ties resolve to the earliest enumeration-order point (the family is in odometer order, and only a strictly-better later point displaces the incumbent). Single source of truth for "best", as #67 required ("reuse rank_by; do not fork"): rank_by's per-metric direction is extracted into a private `metric_cmp` helper that resolves the boundary metric *name* to a closed `enum Metric` once and returns one comparator closure. Both rank_by (sort by it) and optimize (argmax via `reduce`, strictly-better-displaces) now call it, so the per-metric direction lives in exactly one match. rank_by is behaviour-preserving — its existing tests pass unchanged. The sweep executor is untouched. No caller-supplied `direction`: one definition of best per metric, consistent with the shipped rank_by invariant (a caller direction would admit incoherent asks like the worst max_drawdown). An unknown metric is UnknownMetric. A SweepFamily is non-empty by construction (EmptyAxis is rejected upstream), so the argmax always yields a winner. CLI surface: the "pick the best" view is already served by `aura runs rank <metric>`, whose first line is the optimum over the persisted registry; optimize() is the new *programmatic* axis-2 entry, for World programs that hold a live SweepFamily and need the winning params back in-process — which the registry/RunReport path cannot give. No redundant CLI command added. closes #67 |
||
|
|
bdc383eb8b |
test(aura-registry): RED executable-spec for optimize argmax — refs #67
The RED half of a RED-first iteration for the optimize/argmax orchestration axis (C12 axis 2). Pins the headline behaviour before the feature exists: optimize(&SweepFamily, metric) returns the single winning SweepPoint — its params coordinate AND its RunReport — chosen by the metric's fixed per-metric sense of best (reusing rank_by's direction semantics), with ties resolving to the earliest enumeration-order point. No caller-supplied direction: that would contradict the shipped rank_by invariant that "best" is fixed by each metric's meaning, and would admit incoherent asks (e.g. the worst max_drawdown). One definition of best per metric. RED for the right reason — optimize is absent (E0425), the sole error; cargo build --workspace stays green (test-only module). GREEN lands in the following commit, which closes #67. |
||
|
|
d8e0fb70c8 |
refactor(aura-cli): centralize the sim-optimal RunManifest construction
Three RunReport builders hand-rolled the same RunManifest with identical commit/seed/broker fields (only params/window varied), drifting independently. Extract sim_optimal_manifest(params, window) — one source for the broker label, so #22's per-asset pip work has a single edit point. |
||
|
|
6021ce6aeb |
docs(aura-engine): drop two stale aura-cli cross-references in graph_model
kind_str and collect_distinct_composites no longer exist in aura-cli (it consumes model_to_json directly), so the "mirrors aura-cli's ..." notes pointed at nothing. Keep the substantive rationale (Debug is PascalCase). |
||
|
|
941503ed3d |
refactor(aura-engine): fold resolve/resolve_axes into one generic resolve_into
resolve (single Scalar per slot) and resolve_axes (a Vec<Scalar> axis per slot) were the same two-phase named-binding resolution against param_space(), duplicated almost verbatim — the live instance of the "two raise-sites for one invariant" hazard (cf. #45/#53). A change to binding precedence or error ordering had to touch both in lockstep. Collapse the shared skeleton into resolve_into<T>, parameterized by two closures: claim_ok (phase-1 claim-time gate, axis-only EmptyAxis, before the duplicate check) and kind_ok (phase-2 per-slot kind gate). The error total order now lives in exactly one place. Behaviour-preserving: same error for same input, all 231 tests green (the resolve/bind precedence tests pin it). |
||
|
|
a8bd52eaff |
refactor(aura-core): own FieldSpec.name to drop the derive_signature heap leak
FieldSpec.name was &'static str, forcing derive_signature to Box::leak every re-exported composite OutField name. Because compile_with_params reruns derive_signature per sweep point, an N-point sweep over an M-output-field composite leaked N×M boundary names unbounded — a scaling wall the moment the World/sweep layer compiles families per point. Pull FieldSpec.name to String, mirroring the already-owned PortSpec.name and ParamSpec.name: a composite re-exports interior fields under runtime boundary names, so ownership is the correct model, not a workaround. FieldSpec loses Copy; the only fallout is one borrow at the render site. leak_name/Box::leak deleted. Behaviour-preserving: signature contents unchanged, all 231 tests green. |
||
|
|
21c1621bd0 |
docs,engine: drop coined "compilat", use FlatGraph / "flat graph"
"Compilat" (German "Kompilat") was a coined noun for the product of the
bootstrap compilation — neither English nor a natural fit. The runtime
artifact already has a code identifier for exactly this thing: the
`FlatGraph` struct (harness.rs). Replace the coinage with that identifier:
- prose mentions -> "flat graph" (mirrors the type, reads plainly)
- definitional anchors -> `FlatGraph` (C11, C23, the running-graph line)
- `render_compilat` -> `render_flat_graph` (historical render symbol;
keeps the `render_blueprint` / `render_flat_graph`
source-vs-product pairing)
- `intra-compilat` -> `intra-graph`
- `from_compilat` (test) -> `from_flat`
"compilation" / "re-compilation" / "bootstrap-as-compilation" (the process,
ordinary English) are deliberately left untouched. Behaviour-preserving:
only comments, design ledger, specs/plans, one test-local variable and its
assert messages change. Full workspace test suite green; clippy clean.
|
||
|
|
8b330e3eaa |
audit: cycle 0040 — drift-clean (wiring-totality check)
Close-audit for cycle 0040 (commit range b6174cf..227d004). Architect drift review against the design ledger + CLAUDE.md domain invariants: no contract drift, no debt. - C23 intact: check_ports_connected is index-based and pre-build, reads only signature().inputs.len() + the existing raw-index Edge/Target, and emits nothing into FlatGraph. Lowering pushes leaf-recipe signatures (not derive_signature's output); composites inline away; the run loop is untouched. - derive_signature/interior_slot_kind bounds-totality weakens no validation: the placeholder kind for a structurally-invalid composite is consumed only by the parent's speculative kind-check, while the recursion independently re-rejects the same out-of-range index via the guarded BadInteriorIndex/OutputPortOutOfRange loops. The change converts a panic into a deferred-but-still-reported error. - The C8 cycle-0040 realization note accurately records the new invariant, scopes it to interior slots, names open roles as providers, and does not re-claim the dropped swap-closure goal or overstate name load-bearingness. - No legal graph is newly rejected (open source:None roles excluded as providers; root-open stays governed by UnboundRootRole). Regression gate: no regression script declared in project facts -> architect is the sole gate (drift-clean). cargo test --workspace 231 passed / 0 failed; cargo clippy --workspace --all-targets -- -D warnings clean (independently re-run by the orchestrator). refs #65 |
||
|
|
227d004c9d |
feat(aura-engine): reject unwired or double-wired input ports
A graph that leaves an interior input slot unconnected, or wires one slot from more than one producer, is now a compile-time error instead of a silently-wrong run. Until now an unwired interior slot was accepted and bootstrapped to an empty column (harness.rs:137-148), so a forgotten connection ran a deterministic but wrong backtest; two producers into one slot — ill-formed, since a slot holds one column — was likewise uncompiled-against. A single name-free check, `check_ports_connected`, is added to `validate_wiring` after the existing index/kind checks and before the nested-composite recursion. It counts coverage of each (interior-node, slot) uniformly across interior edges and role targets, and rejects zero coverage (new `CompileError::UnconnectedPort`) or >1 (new `CompileError::DoubleWiredPort`). Because `validate_wiring` is called once from `compile_with_params` and recurses, both the raw `Composite::new` path and the `GraphBuilder::build()` path inherit it at every nesting level with no builder-side code (mirrors `check_param_namespace_injective`, the param-side sibling). A composite's own open input roles (source: None) are coverage providers — the wired-by-enclosing boundary, the root already guarded by UnboundRootRole — not consumers. Index-based and name-free: nothing reaches the compilat, so C23 holds. Supporting fix: the check computes `signature()` on every interior node before the recursion validates that node's interior, so `derive_signature` and `interior_slot_kind` are made bounds-total (a placeholder kind for an out-of-range OutField/target, never a panic; the real fault is still reported by validate_wiring's guarded OutputPortOutOfRange/BadInteriorIndex). This latent panic was exposed by the new check, not introduced by it. Test maintenance (blast radius enumerated empirically, not hand-traced): four existing tests relied incidentally on under-wiring being accepted — three negative tests get a covering root role so their intended deep fault still surfaces via the recursion, and one param-order nesting test is fully wired (param order unchanged). Six new tests: five raw-surface (unwired, double-wire via edge+role and edge+edge, nested, open-role boundary) and one builder-surface forgotten-leg (proving the GraphBuilder inherits the check). Narrowed scope of #65: the original "promote names to load-bearing wiring keys / close the exposure-price swap structurally" goal was dropped — #64 already moved authoring to handles + visible port names, so the residual same-kind swap is a valid-but-wrong name choice no structural check catches, and a structural fix would push domain semantics into the domain-free engine (C10/C7). This ships the name-free half that stands on its own: wiring completeness, which catches forgotten and doubled connections. Verified: cargo test --workspace 231 passed / 0 failed; cargo clippy --workspace --all-targets -- -D warnings clean. closes #65 |
||
|
|
277f8714d4 |
plan: 0040 add derive_signature bounds-guard step
Implement-time discovery: the new check (and the existing edge/role checks) compute signature() on every interior node before the recursion validates that node's interior. For a structurally-invalid composite (an out-of-range OutField or role target), derive_signature indexed unguarded and PANICKED — exposed by output_port_out_of_range_rejected the moment its covering root role forces c.signature(). Added Task 1 Step 2b: make derive_signature and interior_slot_kind bounds-total (a placeholder kind for an invalid index; the real fault is still reported by validate_wiring's guarded OutputPortOutOfRange / BadInteriorIndex). Fix verified empirically before re-dispatch. Not a design change to the check itself. refs #65 |
||
|
|
669541c6f6 |
plan: 0040 wiring-totality-check
Seven-task plan for the wiring-totality check (parent spec docs/specs/0040-wiring-totality-check.md, |
||
|
|
69b16f7d39 |
spec: 0040 correct blast radius to four (empirically probed)
The boss-signed 0040 spec's blast-radius section claimed "exactly three" flipping tests, named from a hand-trace. A throwaway probe (the check implemented, full workspace suite run, then discarded) found FOUR: the three named negative tests plus `param_space_mirrors_compiled_flat_node_param_order_under_nesting`, a compile-success param-order test that today compiles a deliberately under-wired nested graph (LinComb's two term inputs never wired) just to read its flat param order. The new check rejects it where it currently returns Ok. Correction is editorial, not a design change: the check itself is unchanged. The fourth test's fix differs from the three negative tests — it needs the contrived graph fully wired (fan fast_slow's output into both LinComb terms + a covering root role), not just a covering role — so the §Blast radius and §Acceptance criteria now enumerate all four with their distinct fixes. Verified empirically (119 passed, 4 failed; every other crate green), the strongest possible grounding for the count. refs #65 |
||
|
|
07b1ae1c63 |
spec: 0040 wiring-totality-check (boss-signed)
Reject graphs with an unwired or double-wired interior input port. A new
name-free structural check `check_ports_connected` in `validate_wiring`
(crates/aura-engine/src/blueprint.rs) requires every interior node's every
declared input slot to be covered by exactly one wiring act — one `Edge{to,slot}`
or one `Role` `Target{node,slot}`, counted uniformly. Zero coverage is the new
`CompileError::UnconnectedPort` (a forgotten connection that today bootstraps a
silent empty column, harness.rs:137-148); more than one is `DoubleWiredPort` (a
slot holds one column). Run once at the existing compile boundary and recursive
per nesting level, so both the raw `Composite::new` and the `GraphBuilder::build`
surfaces inherit it. Index-based, name-free — the compilat is untouched (C23).
Narrowed scope of #65: the original "promote names to load-bearing wiring keys /
close the exposure-price swap structurally" goal is dropped (see the reconciliation
comment on the issue) — #64 already moved authoring to handles + visible port
names, so the residual same-kind swap is a valid-but-wrong name choice no
structural check catches, and a structural fix would push domain semantics into
the domain-free engine (C10/C7). This cycle ships only the name-free half that
stands on its own.
Auto-signed under /boss: precondition gate clean (no fork silently picked),
self-review + parse-trace clean (no spec-validation parser declared -> documented
no-op), grounding-check PASS on the final bytes (one BLOCK on an incomplete
blast-radius survey, repaired forward by naming the three flipping negative tests),
and a unanimous five-lens spec-skeptic panel (criterion, grounding, scope-fork,
ambiguity, plan-readiness all SOUND).
refs #65
|
||
|
|
b6174cff80 |
refactor(aura-engine): rename NodeHandle::in_/out to input/output
The trailing underscore on in_() was a keyword-escape scar (`in` is reserved).
Rename the NodeHandle port/field accessors to input()/output(), which also
aligns the authoring surface with the schema's own nomenclature
(NodeSchema.inputs/output, "input port"/"output field"): node.input("lhs")
now reads in the same vocabulary the schema uses, instead of a second one.
Mechanical, behaviour-preserving: the two method definitions plus every call
site in builder.rs and the aura-cli sample blueprints. Full workspace green;
clippy --all-targets -D warnings clean.
|
||
|
|
50105c1957 |
refactor(aura-cli): author the sample blueprints via GraphBuilder
Adopt the name-based GraphBuilder (cycle 0039) in the CLI sample blueprints, which previously hand-wired raw positional Edge/Role/OutField via Composite::new. sma_cross, macd, signals, sample_blueprint_with_sinks, and macd_strategy_blueprint now read as typed-handle authoring: nodes are added for NodeHandles, ports/fields are wired by name (series/lhs/rhs/value, exposure/price, term[i], col[0], the macd histogram/signal/macd outputs), and build() lowers to the same index-wired Composite. This is what #64 was for — the builder was previously exercised only in its own tests; the showcase sample now dogfoods it. Behaviour-preserving: node order, instance names (.named), bound params (blend.weights[2]), and wiring are byte-identical, so param_space, the graph render, the single run, and the sweep are unchanged — guarded green by the existing aura-cli sample/sweep/param_space/render tests and the engine suite (full workspace 0 failed; clippy --all-targets -D warnings clean). sample_harness is deliberately left as a direct FlatGraph construction (baked Sma::new(2) nodes, the single-run path) — it builds the compilat directly, not a value-empty Composite, so it is not a GraphBuilder target. |
||
|
|
7865030d33 |
audit: cycle 0039 — drift-clean (GraphBuilder name-based wiring)
Close-audit for cycle 0039 (commit range 3a1dceb..a6a314b). Architect drift review against the design ledger + CLAUDE.md domain invariants: no contract drift, no debt. No regression scripts are configured (the architect is the gate); build + test + clippy verified green independently by the orchestrator (aura-engine 123 passed, +9 new; full workspace green; clippy --all-targets -D warnings exit 0). cycle 0039 (clean): - C23 / C11 preserved: the compilat is byte-unchanged this cycle. harness.rs (Edge / FlatGraph / Target / SourceSpec) is untouched; the new GraphBuilder resolves every port/field name in build() (resolve_slot / resolve_field) and lowers to the unchanged Composite::new with raw-index Edge / Role / OutField. No name-carrying field was added to any compilat type — names resolve at the authoring boundary and never reach FlatGraph. - C10 / C17 preserved: GraphBuilder is a genuine fluent Rust builder over the existing structs, not a DSL. Node references are typed Copy handles; only port/field names are strings, resolved against declared NodeSchema data. - C8 / C1 / C2 preserved: resolution is cold authoring-time; the run loop and the bootstrap kind-check remain the structural gate (name resolution is necessary, not sufficient). - #21 deferral is coherent: the exposure/price swap is made legible (named slots) but deliberately NOT structurally closed; issue #65 carries the structural follow-up (promoting port/field names to load-bearing keys), and no ledger contract claims the swap is closed. Awareness (not drift, spec-consistent): feed() indexes self.roles[role.0] directly, so a RoleHandle minted by a different builder panics rather than returning BuildError::BadHandle (which covers only NodeHandles, per the signed spec). RoleHandle out-of-range robustness is unspecified by spec 0039. |
||
|
|
a6a314bb98 |
feat(aura-engine): name-based blueprint wiring via GraphBuilder
Add an additive, fluent GraphBuilder authoring surface that wires a blueprint's topology by typed node handles and port/field names instead of raw positional indices. Node references are Copy NodeHandle values (a node reference cannot be mistyped); only port/field names are strings, resolved against each node's cached NodeSchema by exactly-one-match at a single fallible build() -> Result<Composite, BuildError> — the Binder posture, one level up. build() lowers to the unchanged Composite::new, so the compilat stays index-wired (C23): names resolve at the authoring boundary and never reach FlatGraph. A new From<Composite> for BlueprintNode lets add() accept nested composites. Verified: builder-authored sma_cross is structurally equal to the hand-wired fixture, and the full builder-authored harness lowers to a byte-identical FlatGraph (equal edges + sources). The five BuildError variants (Unknown/Ambiguous In/Out, BadHandle) are covered, and the SimBroker exposure/price legs are now addressable by name (the #21 legibility win). The structural close of the exposure/price swap (#21) is deliberately out of scope — this builder makes the swap legible, not impossible — tracked as #65. Two plan-test corrections applied during implementation (both in-scope, no behaviour change): the harness parity test uses compile_with_params with a topology-invariant param vector on both sides (the harness declares three params, so a no-param compile() would trip ParamArity), and the error asserts use .err()/Some(...) rather than unwrap_err() (Composite is not Debug). aura-engine 123 tests green (+9); full workspace green; clippy --all-targets -D warnings clean. Existing index-form Composite::new sites and tests untouched (coexistence). closes #64 |
||
|
|
031081bf62 |
plan: 0039 graphbuilder-name-based-wiring
Task-by-task plan for the typed-handle GraphBuilder: the From<Composite> lift, the builder module (types + accumulators + the fallible build() resolver), the lib.rs wiring, and the test suite (structural parity, harness FlatGraph parity, the five BuildError variants, #21 legibility, coexistence + clippy gate). refs #64 |
||
|
|
292c95756f |
spec: 0039 graphbuilder-name-based-wiring (boss-signed)
Add a typed-handle GraphBuilder authoring surface for blueprint topology: node references become Copy NodeHandle values, ports/fields resolve by name against the existing PortSpec.name/FieldSpec.name at a single fallible build() terminal, lowering to the unchanged index-wired Composite (C23 holds by construction — names never reach the compilat, mirroring param-name resolution). Spec auto-signed under /boss: all objective gates green (precondition, self-review, grounding-check PASS) and a unanimous five-lens spec-skeptic panel (criterion, grounding, scope-fork, ambiguity, plan-readiness all SOUND). The structural close of the SimBroker exposure/price swap (#21) is explicitly out of scope, tracked as #65. refs #64 |
||
|
|
3a1dceb83a |
audit: cycle 0038 — drift-clean (shared SMA-cross test fixtures)
Close-audit for cycle 0038 (commit range c939673..1f01bad). Architect drift review against the design ledger + CLAUDE.md domain invariants: no contract drift, no debt. No regression scripts are configured (the architect is the gate); build + test + clippy verified green independently by the orchestrator (aura-engine 114 passed, count unchanged; the two load-bearing consumers green; clippy --all-targets -D warnings exit 0). cycle 0038 (clean): - C16 preserved: the new shared fixtures live in a #[cfg(test)] mod test_fixtures, which compiles into no shipped artifact — the engine still ships no sample. - C1 / C11 / C12 strengthened, not weakened: the determinism witness (composite_sma_cross_runs_bit_identical_to_hand_wired) and the sweep-equivalence witness (sweep_equals_n_independent_runs) now consume one shared fixture instead of two hand-kept copies, so the harness topology can no longer silently diverge between the two test modules — the exact hazard #53 named. - Test-organisation convention held: a crate-root #[cfg(test)] module of pub(crate) free fns, sibling to the existing reexport_tests precedent, declared in lib.rs. - graph_model.rs's minimal golden copy (sma_cross / sample_root) was correctly left independent and out of scope, by design. - Stale-cross-ref sweep clean: docs/design/INDEX.md and docs/glossary.md name no fixture site that the move invalidated; the fixture-name hits in docs/ are historical specs/plans pinned to their own cycles, not live contracts. In-flight plan correction (recorded for history): the plan's literal import list over-imported sma_cross, which neither consumer calls directly (reached only transitively via composite_sma_cross_harness); the implementer trimmed it to satisfy the plan's own clippy -D warnings gate — within the task's import-cleanup scope. |
||
|
|
1f01badac7 |
refactor(aura-engine): share the SMA-cross harness test fixtures
Collapse the three verbatim-duplicated #[cfg(test)] fixtures — synthetic_prices, sma_cross, composite_sma_cross_harness — that lived in both blueprint.rs's and sweep.rs's test modules into one shared crate-root `#[cfg(test)] mod test_fixtures` (pub(crate) free fns). The two consumers now import from `crate::test_fixtures`; sweep.rs's test-mod `use` block sheds the imports that were only needed by the moved fns. Why: the topology had to move in lockstep across the two copies but no test pinned them equal, so an edit to one silently diverged the other (#53). One definition removes the hazard structurally — better than a pin-them-equal test, which loses its premise once there is a single definition. Test-only; no production code, public API, or runtime path changes. C16 untouched (a #[cfg(test)] module ships in no artifact). The CLI sample is no longer a third peer (cycle 0036 enriched it into a trend+momentum blend), so this is an engine-internal two-copy dedup, not the three-way the issue originally named. graph_model.rs keeps its own intentionally-minimal golden copy, out of scope. Plan glitch fixed in-flight: the plan's literal import list named sma_cross, but neither consumer calls it directly (only transitively via composite_sma_cross_harness), so importing it tripped the plan's own Step-7 clippy -D warnings gate. Trimmed to {composite_sma_cross_harness, synthetic_prices} — within the task's "drop now-unused imports" scope. Verified: cargo test --workspace green (aura-engine 114 passed, count unchanged — behaviour-preserving); the two load-bearing fixture consumers composite_sma_cross_runs_bit_identical_to_hand_wired (C1) and sweep_equals_n_independent_runs (C11/C12) both green; cargo clippy --workspace --all-targets -- -D warnings clean. closes #53 |
||
|
|
1718ba0f98 | plan: 0038 dedup-sma-cross-test-fixtures | ||
|
|
47179ce5a4 |
spec: 0038 dedup-sma-cross-test-fixtures (boss-signed)
Collapse the three verbatim-duplicated SMA-cross harness test fixtures (synthetic_prices, sma_cross, composite_sma_cross_harness) in aura-engine's blueprint.rs and sweep.rs #[cfg(test)] modules into one shared crate-root #[cfg(test)] mod test_fixtures. Behaviour-preserving, test-only. Auto-signed under /boss: all objective gates green (precondition, self-review, grounding-check PASS) and a unanimous five-lens spec-skeptic panel returned SOUND. refs #53 |
||
|
|
c939673aa7 |
audit: cycle 0037 — drift-clean (bound params in the graph model + viewer)
Close-audit for cycle 0037 (commit range d069484..47e0e60). Architect drift review
against the design ledger + CLAUDE.md domain invariants: no contract drift, no debt.
No regression scripts are configured (the architect is the gate); build + test +
clippy verified green independently by the orchestrator.
cycle 0037 (clean):
- C23 preserved: bind still resolves the slot name to a fixed position and re-splices
the value in the build closure; BoundParam/"bound" is recorded purely beside that
capture, a render/debug symbol dropped at lowering — bootstrap and the run loop
never read bound_params() (only the model serializer does). The compilat stays
index-wired. A genuine twin of the cycle-0035 instance-name thread.
- C14 preserved: the inline no-bind model_golden (sample_root) is byte-unchanged;
the "bound" field is conditional (mirrors "name"), so unbound nodes are
byte-identical, and only the sample fixture's blend node gained the field.
scalar_str is total over all four Scalar variants and deterministic (f64 via {:?}
keeps a decimal point so a bound f64 never reads as an i64).
- C12/C19 preserved: the tunable surface is untouched — bind still removes the slot
from schema.params; the eight-param sweep pins stay byte-identical with weights[2]
absent. The render annotation reads from a separate bound vec, not param_space().
Test coverage: the original-position unit test (trailing + reverse-chained), the
prim_record conditional-field unit test (i64 + f64), and a headless render guard
pinning slot-order faithfulness for both a trailing and a middle bind (the middle
case catches an append-only regression).
Resolution: carry-on. Next iteration: pick the next backlog item.
|