Independent post-cycle review (opus, fresh context) on the branch diff
returned one Minor and one Nit; both adjudicated against the source and
fixed:
- Minor: the concepts paragraph and the sweep/walkforward/mc long helps
claimed the document desugaring unconditionally, but only the --real
branches route through verb_sugar — the synthetic branches execute
in-process and register no documents. The claims now carry the --real
qualifier (and the paragraph names the in-process synthetic path).
generalize keeps the unconditional wording: its only mode is --real
(usage requires it), so the claim is true as stated.
- Nit: the vocabulary listing's `None` fallback would have silently printed
a bare, C29-incomplete row if a rostered type id ever failed to resolve.
Replaced with a loud expect — an invariant breach should not degrade into
exactly the surface this cycle exists to remove.
refs #315
The shipped wording lifted #319's loose "quintet is pure translation"
phrasing into "the research verbs (run, sweep, ...) are sugar — each writes
registered process/campaign documents". Source check (dispatch_run): only
the four verb_sugar bridges (sweep, walkforward, mc, generalize) generate
and register documents; `run` is a direct single backtest, exactly why its
own long help already avoided the desugaring claim. The paragraph now names
the four document-bridged verbs and places run as their single-backtest
sibling — consistent with `run --help`.
Found by re-auditing the shipped prose claims against the source after the
owner asked how I know no error was made (skills-less-cycle experiment,
refs #315): prose claims in help text are exactly the class the green test
suite cannot semantically verify.
The binary is the only always-present teacher (C29); the 2026-07-22 external
field test showed the help teaching nothing but the verb inventory. This
cycle makes every closed vocabulary the binary ships listable with a one-line
meaning, and opens the help with the concepts the field agent had to discover
forensically.
#315 — help + introspection:
- `aura --help` opens with the two-layer concepts paragraph (research verbs
as sugar over registered process/campaign documents; the directly
authorable data plane; bias-as-target-position + protective-stop execution
model in R; how traces come to exist).
- Each document-bridged sugar verb's long help names the process shape it
desugars to (sweep -> std::sweep; walkforward -> [std::grid,
std::walk_forward]; mc -> + std::monte_carlo; generalize -> [std::sweep
(selection), std::generalize]). `run` is not verb_sugar-bridged, so its
help points at the canonical document-first form instead of claiming a
desugaring it does not perform.
- `graph introspect --vocabulary` lists each node type with its schema doc
(bare names taught nothing); `--node <T>`'s head line carries the meaning.
- `graph introspect --folds` (new): the tap-fold vocabulary with bind rule,
output kind, and meaning — it existed only as source comments. The rows
live in aura-std (`fold_vocabulary`), unit-pinned against
`fold_binds_at`/`fold_output_kind` so the prose cannot drift from the
executable rules, and doc_gate-checked (C29 entry seam). The discovery
surface is graph introspect because folds bind at graph-declared taps
(C27); the document layer still refuses a folds slot pending #310.
- `process introspect --metrics` lines carry each metric's meaning (the doc
column existed since #316 but was never printed).
- `campaign introspect --block std::presentation` lists the four persisted
taps' meanings under the persist_taps slot.
- The scaffolded project CLAUDE.md teaches the execution semantics (bias as
held target, protective stop, R) and the document data plane.
#323 — C29 authoring surfaces:
- `introspect --unwired` enumerates the optional `description` envelope slot
for both document kinds (absent-only, like risk/cost); the campaign lib
fixture gains a description, and the risk/cost-bound e2e binds description
too so "no open slots" keeps meaning complete.
- `graph build --help` carries the op-list reference: all nine op kinds with
fields and a worked element each (OP_REFERENCE lives beside OpDoc so a new
variant is one screen from its help line).
- The authoring guide's S0 worked literal is byte-pinned to the scaffold's
LIB_RS template (rendered with the guide's my_lab namespace); the scaffold
e2es compile that template for real, so the pin is a transitive compile
guard. The template gains the guide's C29 comment, the guide gains the
template's rename note — one worked example, two surfaces, zero drift.
Deliberately out of scope (ratified #319, 2026-07-24): no doc-strings for
the quintet's 48 flag fields — the sugar surface retires; only the pointer
lines above bridge toward the document layer.
Verification: full workspace suite green, clippy -D warnings clean,
aura-bench exit 0 with all fingerprints OK (help_ms +1.6% — the longer help
text, in tolerance), cargo doc warning count unchanged (7 pre-existing).
9 new e2e pins in tests/help_self_description.rs; guide + CLAUDE.md pins in
scaffold unit tests; fold-vocabulary rules + doc_gate unit-pinned in
aura-std.
closes#315closes#323
Iteration stderr-markers-2, tasks 3-4 of the stderr-class-markers plan.
aura-runner's four continuing-run diagnostics join the class grammar as
hand-written literals (the #295 boundary keeps the macro owner in the
presentation crate): the stale-dylib warning gains the `aura: ` namespace,
and the three tap/no-nominee notices become `aura: note: ` — the latter
two extracted into pure note-builder fns with unit pins. The two e2e
pins on the cost-model tap string moved with the retag
(research_docs.rs). A new project_nodes e2e exercises the real stale-
dylib load path: fresh build emits nothing, a source newer than the
dylib emits exactly one class-marked warning and still succeeds.
The #313 notice ships on both walk-forward paths: `aura: note: all N
walk-forward windows recorded zero trades` before the summary JSON,
exit 0 untouched (a null result is a valid research result, #198).
Wording and condition live in one shared diag helper taking the
per-window trade counts (orchestrator tidy of the reviewer's
duplicated-predicate residue); the call sites keep only the
path-specific field extraction. Three e2es pin it: the equal-length
degenerate on the synthetic path (exactly one note, no warnings, exit
0, summary unchanged), a traded negative twin, and the real/sugar-path
degenerate gated on the GER40 archive.
refs #278, refs #313
Iteration stderr-markers-1, tasks 1-2 of the stderr-class-markers plan
(spec signed via grounding-check PASS, decisions logged on #278).
The two stderr diagnostic classes become machine-separable (refs #278):
a new crate-internal diag module owns the grammar as note!/warning!
macros (`aura: note: <text>` benign continuing-run diagnostic,
`aura: warning: <text>` recorded fault the run survives); the four
undifferentiated sites are retagged — gate-emptied cell -> note,
failed cell / walkforward cell / mc cell -> warning — and the four
scaffold literals migrate onto the shared macro (output bytes
unchanged). The always-on record summary line gains the bare `aura: `
namespace it was missing; bare-prefix stays the vocabulary for error
lines accompanying a non-zero exit (C14 partition) and plain info
lines. Exit codes unchanged throughout.
Verification: RED->GREEN warning pin on the per-cell-fault e2e; new
gate-emptied e2e (impossible n_trades threshold) pins the note marker,
exit 0, and the summary-line prefix; scaffold e2es and the full
workspace suite green; clippy -D warnings clean on aura-cli. The C28
shell-module roster test gained the diag.rs entry.
Known residue for the next slices: aura-runner literals (stale-dylib
warning, three tap/no-nominee notes) and the #313 zero-trade notice
follow in tasks 3-5. The verb_sugar warning call sites remain e2e-
uncovered (a hostless walkforward fault fixture is structurally
unreachable — single-instrument validation refuses before the cell
loop); their grammar is pinned via the shared macro and the campaign
warning e2e.
refs #278
Completes the #283 registry cycle. run_measurement drops its
duplicated Recorder-bind + post-run try_iter drain for the shared
bind_tap_plan/BoundTaps pair — the mirror of run_signal_r is now the
same code, so the two declared-tap entry points cannot drift; the
measure CLI arm passes TapPlan::record_all() (semantics unchanged,
constant memory). Measure-side library tests pin a fold plan's
one-row trace (streamed mean bit-equal to the slice mean over the
synthetic prices) and the tap-free write-nothing guarantee through
the shared pair.
Run-side integration tests pin the #283 acceptance surface: fold and
live plans agree exactly with the recorded series of the same
blueprint (C1, three runs, three drain policies); a live-only plan
persists nothing; plan refusals (unknown tap, roster-enumerating
unknown label) fire typed before any store I/O; two identical
record-all runs produce byte-identical tap files. The previously
untested `aura: writing tap traces failed` register gains end-to-end
coverage on both arms: store root occupied by a file (pre-run
refusal) and a read-only run dir (deferred initialize-open failure —
the run completes, the failure surfaces terminally, no index.json is
written, the store's treat-as-absent crash shape).
Two forced deviations from the plan's scripted literals, both
verified equivalent: an explicit match replaces expect_err (BoundTaps
is deliberately not Debug — it carries a TraceStreamer), and the test
helper takes &Path, not &PathBuf (clippy::ptr_arg under -D warnings).
Closes#77 as specced: zero per-cycle heap on the tap path
((Timestamp, Cell) payload, committed earlier this branch) and the
Recorder→Probe rename retired (no Probe symbol anywhere; decision
recorded on #77, 2026-07-21).
Verification: cargo test --workspace — 1517 passed, 0 failed;
cargo clippy --workspace --all-targets -- -D warnings clean;
tap_recording 5/5 (byte-compat anchors + both register arms).
refs #283 refs #77
The #283 registry model's assembly seam: tap_plan.rs carries
TapSubscription (Named{label, params} — fully data-expressible — |
Live(closure), the single non-data variant), TapPlan (name →
subscription + default; the plan travels with the FoldRegistry it
resolves against, the Env-at-resolve pattern, so layered hosts inject
vocabulary as data), and the registry itself: eight core entries
(record, count, sum, mean, min, max, first, last), each with a doc
line (help + roster-enumerating refusals) and a scalar-typed param
schema (all v1 entries param-less; the seam ships now because it sits
in every entry's build signature). Typed TapPlanError refusals fire
pre-bootstrap, before any store I/O; the terminal Store variant keeps
the established "writing tap traces failed" register bytes.
bind_tap_plan/BoundTaps is the shared wiring pair both declared-tap
entry points call; run_signal_r is re-wired onto it here (Recorder
bind + post-run try_iter drain replaced — no O(cycles) retention, no
per-cycle heap), with all six callers threaded (both CLI run-arm and
the four example-identity test calls pass TapPlan::record_all()).
run_measurement follows in the next commit.
Registry growth stays a new Rust entry (C25); a layer-registered
entry is pinned by the injection test, param validation by a
schema-carrying test entry (missing/mismatched/unknown all refused
with named messages).
Verification: cargo build --workspace clean; aura-runner 85+ tests
green incl. both example-identity anchors; tap_recording byte-compat
anchors green (recorded traces byte-identical, tap-free writes
nothing, duplicate-tap refusal unchanged); clippy -D warnings clean.
refs #283 refs #77
The #300 keystone cycle: the residual run-quintet flags were already
document vocabulary on the --real arm (#210/#220); this closes the loop
and ratifies the surface.
- `aura process|campaign show <content-id>` prints a registered
document's canonical bytes (print!, no framing — #164), so
generate -> retrieve -> hand-extend -> re-register needs no direct
store filesystem access. Refusals reuse the store-hint prose, exit 1.
- walkforward/mc --stop-length/--stop-k become clap-typed i64/f64
(generalize's form); stop_knob_or and its parse_csv_list helper are
deleted (multi-value/invalid input is now clap's exit-2 rejection —
the two dissolved-verb refusal tests retarget, the type-dead unit
test is deleted).
- --select accepts bare `plateau` as the documented default
plateau:mean (#227 carry); document schema untouched — the two
spellings generate content-id-identical campaigns (test-pinned).
- family.rs's quadruplicated demo stop literal collapses onto one
DEFAULT_STOP const (maintainer-lens drift finding).
- Ledger: C25's "which projection next" line resolved (document-first
delivered, per-verb identity re-ratified — F8; host/MCP stay
demand-driven); C18 records the read-back.
Design triage, fork decisions F1-F8, and the auto-signed spec live on
the reference issue. Verification: full workspace suite 1482 passed /
0 failed with the real GER40 archive exercised (the byte-identity
round-trips ran, not skipped); clippy -D warnings clean.
closes#300
Cycle-close audit (architect: drift_found; design core clean). What
holds, architect-confirmed with the guard run and the diff read in
full: the shell boundary is real and enforced (full-workspace
c28_layering incl. completeness + shell-content checks, acceptance
grep clean — no member-run symbol left under crates/aura-cli/src);
aura-campaign keeps zero production dependency on the runner; the IC
move is verbatim against the anchor; the new library-only E2E pins C1
(two independent runners, byte-identical RunReport).
Drift items, all resolved as fixes in this commit:
- design ledger: the C28 status sentence "No crate exists yet for
measurement" contradicted this cycle's own phase-3 done line — now
records aura-measurement as seeded (#295), execution still unbuilt.
- the #147 registry-dispatch acceptance tests (IC deflation null,
seeded determinism, cross-vocabulary refusal) were pure library
properties stranded in the shell's test module — relocated to
aura-measurement/tests/registry_dispatch.rs (engine/registry as
dev-only, layering-exempt edges), per the cycle's own tests-move-
with-their-module rule.
- aura-runner's direct production edge on the external data-server
tree was structurally invisible (the direction table checks aura-*
keys only) and unrecorded — the C28 assembly prose now names it, and
a new c28_layering test pins the external tree's entry points to
exactly {aura-ingest, aura-runner, aura-cli}.
- the C28 shell prose named three buckets that did not cover two real
shell residents — it now names the op-script construction front-end
(translation) and the `aura new` scaffolder (authoring-tooling,
shell-resident like rendering until a second consumer wants it).
Deliberately deferred, tracker homes exist (not drift): #297
(process::exit sites in aura-runner's single-run verb paths), #294
(IC duplicate-timestamp semantics), #288-era rustdoc unresolved-link
warnings in aura-std/aura-backtest (pre-existing byte-identically at
the anchor).
No regression scripts are configured (the bench is report-only); the
architect review is the gate. Verification: cargo test --workspace
green (1474 passed, 0 failed) incl. the relocated registry-dispatch
tests and the new data-server guard; clippy --workspace --all-targets
-D warnings clean.
refs #295
The shell no longer defines what an aura backtest is. Tasks 1-9 of the
shell-boundary cycle — structural extraction, behaviour byte-identical:
- aura-runner (new; the C28 assembly position): input binding (the C26
module, moved whole), the C1-load-bearing param<->config translators,
harness assembly (wrap_r / run_signal_r / run_blueprint_member and the
probe/reopen cluster), axis + risk-regime conventions (bind_axes et
al.), the campaign family builders + MC guards, reproduce
(process::exit -> RunnerError, shell remaps to identical bytes), the
measurement-run orchestration, project loading (Env / cdylib load /
charter / staleness, moved whole), the coverage gap-walk (deduplicated
onto interior_gap_months), and DefaultMemberRunner — the public
implementation of aura_campaign::MemberRunner (ex CliMemberRunner).
The MemberRunner trait stays in aura-campaign; the column still
imports no harness/data-binding machinery.
- aura-measurement (new; seeds C28 rung 3): the IcMetrics/IcKey
vocabulary + information_coefficient, verbatim incl. serde derives
(#294 duplicate-timestamp semantics move as-is, unresolved).
- aura-backtest: the pure per-run scaffold (point_from_params,
wf_ms_sizes / fit_wf_ms_sizes, intersect_shared_window).
- shell residue: main.rs / campaign_run.rs keep argv/dispatch,
argv->document translation, and presentation only;
walkforward_summary_json_from_reports now calls the public
aura_engine::param_stability instead of its inline twin.
- worked example (crates/aura-runner/examples/world_member_run.rs): a
World program runs a member backtest through the library alone.
Held quality findings, adjudicated: the plan's literal pub-visibility
list for binding.rs kept (cosmetic); ~20 refusal sites inside
aura-runner (family/member/measure/translate) still process::exit —
behaviour-identical today, conversion to RunnerError is filed forward
(refs #297); rustfmt line-width drift on re-pathed call sites left for
a repo-wide fmt decision.
Verification: cargo test --workspace green (1471 passed, 0 failed);
cargo clippy --workspace --all-targets -D warnings clean. Byte-identity
is pinned by the untouched shell E2E suites (cli_run, measure_ic,
run_measurement, research_docs, run_refuses_unrunnable_blueprint,
tap_recording — zero assertion edits).
Remaining in this cycle: full-workspace c28_layering + shell-content
check, ledger amendments (C28 assembly position, C25/C14 control-surface
consequence, C26 realization note).
refs #295
The A1 cut of #147 item 2, part 2 of 2 (the consumers), completing what
part 1 (the MetricVocabulary substrate) set up:
- aura-campaign's RANKABLE_METRICS hand-copy becomes a re-export of the
backtest vocabulary's roster; the #190 guard test extends to pin the
roster nesting (rankable == vocabulary roster, rankable within
per-member, per-member within the research vocabulary) and the legacy
exposure_sign_flips alias, so any drift fails a test loudly. The
research and per-member rosters themselves stay hand-copies by ratified
isolation (#190 accepted residual) — now oracle-pinned instead of trusted.
- aura-cli gains IcMetrics + IcKey implementing MetricVocabulary: the
second PRODUCTION implementor. The standalone `aura measure ic`
reduction now computes its permutation p via the shared null_draw +
one_sided_p_laplace building blocks (independent draws instead of
chained shuffles — statistically equivalent, deterministic per seed;
emitted p values may differ in the last digits from #290's).
- Acceptance proven at the registry boundary: an engineered
SweepFamily<IcMetrics> deflates through optimize_deflated to a LOW
overfit probability and a noise family to a HIGH one (the permutation
null, dispatched by the vocabulary — the deflation machinery is no
longer baked-in R-only); same seed reproduces the identical
FamilySelection; cross-vocabulary names are refused with the family's
own roster in the prose (the C10 guard). A new cli_run E2E exercises
the legacy alias through the real `aura runs family … rank` boundary.
Deviation from the plan, forced by the compiler: IcMetrics additionally
derives Debug (unwrap_err on the deflation Result needs it; mirrors
RunMetrics).
Verification: full workspace suite green (no FAILED, no compile errors),
clippy -D warnings clean, C18 goldens (cli_run, measure_ic, c28_layering)
green unchanged. A2 — measurement runs as sweep-family citizens — stays
deliberately deferred until a concrete campaign demand exists.
closes#147
Give a measurement run a standalone post-run quality score: the Information
Coefficient (IC), corr(signal_t, forward_return_{t+h}), with a permutation
null model. Before this a measurement run persisted only tap names and series
(MeasurementReport) — inspectable but not rankable or deflatable. IC is the
first metric measurement supplies: the tap-to-scalar reduction the C28
process-column / metric-interface seam anticipates ("a named metric vocabulary
supplied by measurement and backtest instead of baked in R-only").
Surface: `aura measure ic <run> --signal <tap> --price <tap> [--horizon]
[--permutations] [--seed]`. It reads the run's two recorded tap traces, builds
the forward-return series offline over the recorded price spine, aligns
signal -> return by exact timestamp, computes the IC, and prints it with a
one-sided permutation-null overfit probability (Laplace-smoothed, mirroring the
R deflation arm's formula in optimize_deflated).
Placement follows the C28 ladder: the generic pieces — pearson_corr and a
Fisher-Yates permute over SplitMix64 — land in aura-analysis (the domain-free
statistics foundation, beside resample_block); the IC semantics (forward-return
horizon, the offline join) and the CLI verb land in aura-cli (the shell). The
run path (run_measurement), MeasurementReport, the trace store, and the registry
metric vocabulary are untouched — existing `aura run` output stays byte-identical
(the C18 golden run_prints_json_and_exits_zero stays green).
Causality (C2): the forward-return read close_{t+h} is over a completed run's
recorded trace, not an in-graph node. A look-ahead signal is structurally
impossible in a causal run, so the engineered-vs-noise acceptance property is
unit-tested over hand-built offline series (perfect signal -> IC ~= 1, p < 0.05;
a varying but exactly-uncorrelated signal -> IC = 0, not significant;
constant/degenerate -> the (0.0, 1.0) floor), while the E2E validates the CLI
wiring, report well-formedness, determinism, and the error paths over a real run.
This lands the second, structurally distinct null-model computation (permutation,
beside the moving-block bootstrap behind R). That makes the deferred registry
deflation-vocabulary abstraction (#147 item 2) demand-driven rather than
speculative — the next cycle.
Verified: cargo test --workspace green (incl. the new aura-analysis unit tests,
the aura-cli ic_* unit tests, and the measure_ic E2E); cargo clippy --workspace
--all-targets -D warnings clean; the C18 byte-identity golden unchanged.
closes#290
refs #147
C28 phase 2 (Stratification); realizes item 1 of the deferred #147. The
engine's production surface no longer names a backtest-metric type:
- RunReport becomes generic over its metric payload M; sweep/mc/walkforward/
blueprint thread the parameter (SweepPoint<M>, SweepFamily<M>, WindowRun<M>,
WalkForwardResult<M>). RunManifest stays concrete and engine-owned (its
selection: Option<FamilySelection> embeds the foundation-grade analysis type).
- summarize and the MC assembly (McDraw/McFamily/McAggregate/RBootstrap/
r_bootstrap/monte_carlo) move to aura-backtest - McAggregate::from_draws reads
RunMetrics fields by name, so generifying it is the phase-6 metric-vocabulary
abstraction (#147 item 2), still deferred; wholesale relocation is the honest
cut. The concrete instantiation lives in aura-backtest as
`type RunReport = aura_engine::RunReport<RunMetrics>` + sibling aliases.
- the statistics kernel (MetricStats/quantile/resample_block/SplitMix64) moves
to the aura-analysis foundation; the engine re-imports it (inner->foundation,
legal) and re-exports it so existing consumers stay source-compatible.
Dependency inversion in one commit: aura-engine drops aura-backtest from
[dependencies] (back to dev-deps for its SimBroker/RunMetrics test fixtures);
aura-backtest gains aura-engine. Cycle-free for lib targets - the cycle closes
only through the engine's dev-dep edge, the pattern aura-vocabulary already
uses. aura-backtest reaches the kernel transitively through the engine
re-export, so no aura-backtest -> aura-analysis edge exists (the C28 ladder
permits backtest -> {core, engine} only). run_indexed / SplitMix64::next_f64
widened pub(crate) -> pub for cross-crate use.
Consumers (registry/campaign/cli/composites/ingest/bench) rewired by import
path only, no call-site logic changed. The c28_layering structural test extends
to the full ladder: aura-analysis (no aura-* deps), aura-engine ⊆ {core,
analysis}, aura-backtest ⊆ {core, engine}.
Behaviour-preserving: 1448/0 tests, clippy -D warnings clean, serde shapes
byte-identical (C18 - RunReport<M> keeps field order manifest,metrics; the
CLI pre-serialized-splice contract unchanged), moved code traceable via git
rename detection. Cycle-introduced broken intra-doc links fixed.
closes#292
Phase 4 of the Stratification milestone. aura-std held four C28 ladder
layers in one roster; this cuts them into layer-aligned, aura-core-only
node crates so the import direction is enforced by the crate graph:
- aura-std — engine nodes only (arithmetic/logic/rolling + sinks)
- aura-market — session, resample
- aura-strategy — bias, stops, sizer, cost-model machinery
- aura-backtest — sim_broker, position_management
- aura-vocabulary — the relocated closed std_vocabulary roster
Node modules move verbatim (byte-identical renames); consumers are
rewired by import path only. A new structural test
(aura-vocabulary/tests/c28_layering.rs) asserts each node crate's
[dependencies] stay within its C28-permitted inner set, catching the
acyclic-but-outward violation the compiler misses.
Behaviour byte-identical: full workspace suite green (1448 tests), no
golden edited, clippy -D warnings clean. C28 Status block updated.
closes#288
Phase 3 of the Stratification milestone (contract C28). `aura run` now
shape-dispatches on the loaded blueprint: a `bias` output takes the
existing strategy path (wrap_r + R evaluation), byte-identical; a
blueprint that declares taps but exposes no `bias` runs bare via the new
`run_measurement` (bootstrap, run, drain the declared taps) with no
SimBroker, no risk executor, and no per-cycle equity/exposure/r
recorders; a blueprint exposing neither is refused. This gives the
measurement layer its own run verb and removes the O(cycles) recording
retention (the measured ~2 GB peak RSS over 12y UK100 M1) the mandatory
R scaffold imposed on a measurement-shaped run.
A new `MeasurementReport { manifest, taps }` (aura-engine::report, beside
RunReport) is the stdout/record shape — no R metrics, since a bare run
has no broker. The manifest's `broker` carries an interim "measurement"
sentinel; the honest `Option<String>` model is deferred to the #147
metric-genericity block, keeping this phase #147-free and the
strategy-path C18 goldens byte-identical.
`run_signal_r` is untouched (the tap machinery is duplicated into
`run_measurement`, not extracted). The measurement arm's closed-blueprint
guard uses `signal.param_space()` rather than `blueprint_axis_probe`,
which welds wrap_r and would panic on a no-`bias` signal.
refs #286
Two user-authorable blueprint shapes crashed `aura run` with an internal
.expect instead of the CLI's clean aura: refusal register:
- a tap wire pointing out of range: run_signal_r's compile now handles
CompileError (TapWireOutOfRange) as an aura: refusal + exit 1, naming that
the blueprint does not compile to a runnable harness.
- an exposed output not named bias: a pre-check in dispatch_run (the single
loaded-blueprint call site, before the free-knob axis probe that welds the
hardcoded bias port and would itself panic) refuses with a message telling
the user aura run expects a strategy with a bias output and listing what the
blueprint exposes. The sweep path (run_blueprint_member) is untouched.
Surfaced by the measurement-milestone fieldtest as the sharp edge of raw-index
tap authoring (#284 tracks the name-addressed tap op that removes the miscount
class at the source).
closes#282
The payoff: a hand-authored blueprint's declared taps become recorded output
from one `aura run`. run_signal_r binds each tap (hoisted into flat.taps after
wrap_r nests the signal) to a fresh Recorder before bootstrap, drains the
series after the run, and persists each as a ColumnarTrace via the existing
env.trace_store() — the same trace surface the campaign path feeds, so the
existing chart tooling reads them. Duplicate tap names are refused (the
caller-owned DuplicateBind dedup) before anything persists. A tap-free run
writes no trace store — byte-identical to today. Sweep/reduce leaves taps
unbound (run_blueprint_member untouched). Covered end-to-end: a persisted tap
series, the tap-free no-write guard, and the duplicate-name refusal.
refs #282
closes#275
The CLI half of by-name source binding, plus the ledger record.
Every production run site that carries a ResolvedBinding — `run_signal_r`,
`run_blueprint_member` (sweep / reproduction), and the campaign re-run/trace path —
now keys its opened sources by role name via `key_supply(binding, sources)` and
drives the harness through `Harness::run_bound` instead of the positional
`run(sources)`. `key_supply` pairs each opened column with its declared role from
`binding.entries()` — the one place open-order and role-order meet, made explicit so
`bind_sources` verifies the wiring↔supply role match by name rather than by a
maintained canonical-order convention.
Because the current single-binding CLI derives both the `SourceSpec` roles (via
`wrap_r`) and the supply roles (via `key_supply`) from the same `binding.entries()`,
the bind cannot fail on this path — so the call site asserts the invariant with
`.expect`, matching the adjacent `close_handle.expect("ResolvedBinding guarantees a
close entry")` idiom. The named `SourceBindError` refusal path lives in
`bind_sources` for future decoupled-supply callers (independently-built multi-feed /
recorded sources, #124), where a mismatch becomes reachable.
Ledger: a C4 realization note (supply resolved by name into declaration order, so
supply order is no longer load-bearing; the tie-break guarantee is unchanged) and a
scoped C23 refinement (a `SourceSpec.role` is load-bearing for source binding; every
other flat-graph name stays a non-load-bearing raw-index symbol).
The fieldtest-corpus `SourceSpec` sweep (planned Task 5) was reverted: the fieldtest
packages already fail to build against the current engine API for reasons unrelated
to `SourceSpec` (bootstrap arity, removed `InputSpec`, drifted
`Recorder`/`SimBroker`/`RMetrics`, renamed `Scalar` helpers) — pre-existing bit-rot
from earlier cycles. A partial `SourceSpec` migration neither revives nor further
breaks them, so the scope decision to touch them (premised on their being
otherwise-buildable) was withdrawn; reviving the corpus is separate from #275.
Verification: `cargo test --workspace` green (0 failed; the real-data OHLC channel
e2e exercises the migrated `run_bound` path byte-identically); `cargo build
--workspace --all-targets` clean; `cargo clippy --workspace --all-targets -D
warnings` clean.
closes#272
A member fault (no-data, bind, run, or a caught panic) is now a recorded
per-cell outcome instead of aborting the whole campaign and discarding every
already-computed cell. The incident that motivated this (a 22-instrument
campaign lost ~36 healthy cells ~6.7 min in because Copper had an archive gap)
now completes: the healthy cells persist, the gap cell is recorded as failed,
and the run exits 3.
Direction (owner decision 2026-07-14): run to completion and report
compromised results; no coverage preflight, no window synthesis.
Containment granularity:
- The CELL for a sweep-stage member fault (a grid hole structurally
compromises winner selection, so the whole cell fails).
- The FOLD for a walk_forward member fault (independent time windows): the
surviving folds pool into the family, failed folds are recorded as
StageRealization.window_faults, and the summary names the ratio.
- aura-registry: additive CellFault / CellFaultKind (closed:
no_data|bind|run|panic|window) / WindowFault / CellCoverage, plus
fault/coverage fields on CellRealization and window_faults on
StageRealization — all serde-default-skipped, so pre-#272 campaign_runs
lines parse and round-trip byte-identical.
- aura-campaign: run_cell returns a fault-annotated CellRealization instead of
Err (execute's accumulate-then-append-once tail is unchanged and now
persists every healthy cell + the one run record); a `contain` split keeps
ExecFault::Registry and doc-shape preflight faults global while Member/Window
become per-cell/per-fold. Member panics are caught with
catch_unwind(AssertUnwindSafe) at all three member-run sites (sweep IS/OOS)
and recorded as MemberFault::Panic — a member panic no longer aborts the
process. The wf stage partitions Registry faults (global) from Member/Window
(per-fold) and filters faulted-fold placeholders (the
"faulted-member-placeholder" broker sentinel) out of the persisted family.
- aura-cli: exec_fault_prose gains the Panic arm; CliMemberRunner::window_coverage
derives effective bounds + interior gap months from the #264 archive
primitives; present_campaign prints per-cell failure notes + a completion
summary and threads the failed-cell count; a run with >=1 failed cell exits 3
("completed with failed cells") uniformly across `aura campaign run` and the
dissolved sweep/walkforward/mc/generalize verbs (exit_on_campaign_result).
Usage stays 2, refused-before-running stays 1, clean stays 0.
Tests: the global-abort pins flip to containment (execute + the two wf fault
tests → fold-containment + all-folds-fail-the-cell); new panic-containment
tests on both the sweep path (PanicRunner) and the wf path (this commit adds
the wf mirror the loop left uncovered); a new gapped-archive e2e (one covered
cell + one gap cell → exit 3); the ~14 CLI exit-1 pins move to the exit-3
register; a pre-#272-line byte-identical round-trip guard.
Suite: cargo test --workspace green (1309 tests, 0 failed); clippy clean.
Decision log: #272 comments (fork rationale, the fold Registry/Member split,
the placeholder sentinel, uniform exit-3).
Follow-up (minor, not blocking): the plan under-scoped Task 1 to aura-registry
though the additive fields also touch aura-campaign's exec.rs literals — the
loop absorbed it mechanically; a future plan for a cross-crate additive-field
change should scope every crate's construction sites in the first task.
The second risk-regime variant vol_tf{period_minutes, length, k}
computes the vol estimator over completed time buckets: an H1 signal
gets an H1-matched stop in the research matrix instead of a hand-scaled
minute stop (the issue's k-inflation workaround retires). Additive
externally-tagged serde (stored Vol documents keep their content ids);
the executor arm wires the VolTfStop primitive via the builder+bind
chain; validate checks period_minutes/length/k positivity per regime.
The member-manifest round-trip holds for both variants (C1): vol_tf
members stamp stop_period_minutes/stop_length/stop_k and the reproduce
path re-derives the VolTf stop whenever stop_period_minutes is present
— never a silent default-Vol fallback. The Regimes slot label and the
unit notes name the new variant (period_minutes IS the stop's
timescale); glossary, authoring guide, and design ledger record the
second variant. Default regime and sugar paths byte-untouched.
Coverage: node units (rollover-only emission, bucket-delta math, the
constant-|delta| correspondence with the per-cycle regime), executor
fold, serde/validate units, the manifest round-trip unit, and a
hostless two-regime e2e (distinct ordinals; vol_tf members stamp their
timescale, vol members do not).
closes#262
Each cost component's knob (cost_per_trade / slip_vol_mult /
carry_per_cycle) now accepts one number for every cell — today's form,
byte-identical on the wire so stored documents keep their content ids
(CostValue's custom serde, the Axis precedent) — or an instrument-keyed
map resolved per cell at member construction. A mixed-scale matrix
(GER40 ~2e4, EURUSD ~1.1) keeps consistent cost units in ONE document:
the N-copies workaround retires and generalize-under-constant-costs
becomes expressible again.
Validation is strict both ways at the intrinsic tier: a map missing a
campaign instrument, or naming one the campaign does not list, refuses
at validate with prose naming the component and instruments (no silent
default — a partial map would reproduce exactly the silent unit
inconsistency the issue reports). The instrument threads through
cost_knob/cost_nodes_for/run_blueprint_member from the cell (run side
and persist re-run side receive the same value — the C1 drift-alarm
lockstep); the manifest stamps the resolved per-cell value under the
unchanged cost[k].<knob> key, and the sugar/reproduce paths pass an
inert instrument (their specs are scalar by construction). Fork
decisions and the discarded first attempt: issue #260 comments.
New coverage: serde round-trips (map form; scalar stays bare), validate
cross-check units, both prose directions pinned, and two hostless e2es
over the synthetic SYMA+SYMB archive — per-cell resolution visible in
the member manifests, and the validate refusal.
closes#260
Cycle-close audit over the /boss batch 84e1075..bdafbde (#258#259#264#266#247#265#269). Architect verdict: substance clean — C1
byte-identity (conduit serde-skipped, pinned floats verbatim, uncosted
bit-identical), C10 one-home-for-cost (all four conduit consumers read
the already-netted series), C18 (conduit off the wire, --version and
manifest single-sourced) all hold. Two medium debt items, both fixed
here rather than carried:
- render_bind_error's catch-all Debug-framed the remaining BindError
variants, making #269's no-identifier-on-stderr claim argv-gated
rather than structural. The match is now exhaustive: DuplicateBinding,
EmptyAxis, EmptyRange render as axis-usage prose; a Compile fault — a
blueprint defect, not a usage error — renders as a prose frame with
the compile detail explicitly labelled internal (per-variant
CompileError prose belongs to the graph-build surface, not this
boundary). Unit tests pin the arms.
- The #265 knob-unit notes covered constant and vol_slippage but not
carry.carry_per_cycle, which carries the identical price-unit-vs-R
trap; the note set and its pins now cover all three cost components.
No regression scripts are configured (the architect is the sole gate);
full workspace suite green, clippy clean. Spec and plan working files
discarded per the audit lifecycle.
refs #265 refs #269
render_bind_error's catch-all Debug-framed UnknownKnob although its
payload is already a fully-prosed message; the variant now renders the
payload alone. The two tests that pinned the frame verbatim repoint to
the prose fragments. Exit 2 unchanged. Completes the #247 defect
family: no Rust identifier reaches a user's stderr on either verb path.
closes#269
The blueprint-sweep terminal rendered BindError via {:?}, leaking
KindMismatch { .. } and MissingKnob(..) Rust Debug structs where the
sibling diagnostic on the same verb ships prose. Both now render in
that register — the axis name, the expected-vs-supplied kinds resp.
the unbound knob, and the --list-axes pointer; exit stays 2.
Scoped out (same defect family, different verb path): the walkforward
path's UnknownKnob wrapper still prints its Debug-ish frame around an
already-prose payload and is pinned verbatim by two existing tests;
filed as a follow-up rather than widened into this slice.
closes#247
aura --version now prints 'aura 0.1.0 (<commit>)', the commit sourced
from the same AURA_COMMIT build provenance the run manifest stamps
(option_env! at build time, 'unknown' fallback, dirty suffix included)
— a pre-run freshness probe: a stale binary is now distinguishable from
an engine bug at the CLI surface, and research docs pinning
'Engine: aura @ <sha>' can verify the binary they invoke.
The version string is memoized as a OnceLock-leaked &'static str:
clap 4.6's builder Str accepts only &'static str (no From<String>), so
the owned-String alternative a quality pass suggested does not build —
verified, held with evidence in the loop report.
closes#266
aura data list prints the archive's known symbols sorted, one per line
(DataServer::symbols() over the project-resolved archive root — the
discovery step before aura data coverage or scoping a campaign's
instrument matrix). An empty or absent archive prints a 'no symbols'
prose line and exits 0, per the verb corpus's empty-result register.
Pure report fn with unit tests; the headline e2e runs hostless over
the synthetic SYMA+SYMB archive.
Together with the coverage verb this closes the inventory gap: an
agent operating through the CLI alone can now discover instruments
and their per-month gaps before authoring a campaign.
closes#264
aura data coverage <SYMBOL> prints the archive's monthly file-index
coverage: a span line (first..last present month) and either an explicit
'no gaps' line or one 'missing: YYYY-MM..YYYY-MM' line per interior
contiguous hole. The Copper failure mode — a campaign aborting mid-run
because a first/last-bounds check passed while the window start had no
data — is now visible before any run (the real archive reports
'Copper missing: 2017-04..2019-09'). An unknown symbol (no archive
files) refuses on stderr with exit 1; informational absence stays
prose + exit 0 per the verb corpus convention.
Mechanics: list_m1_months goes pub in aura-ingest (archive_extent
deliberately walks past gaps and cannot report them); a new two-level
'aura data' clap namespace follows the Nodes precedent; the archive
root resolves through the normal project path (paths.data override,
data-server default otherwise). Pure report fn with unit tests for the
refusal, the gapless span, and the collapsed-range shape; the headline
e2e runs hostless over a new gapped synthetic-archive fixture.
refs #264 (cut 2, aura data list, follows separately)
The OOS bootstrap conduit RMetrics.trade_rs becomes net_trade_rs and
carries the COST-NETTED per-trade R (r − cost_in_r, trade order). Every
conduit consumer is thereby net-when-costed: the monte-carlo pooled-OOS
and per-survivor bootstraps, the walk-forward oos_r pooling, and the
deflation null-max — which previously compared a net observed statistic
against a gross-resampled null whenever a costed campaign selected on
net_expectancy_r. An uncosted run is bit-identical (empty cost stream
⇒ cost 0.0 per trade), so every existing golden pin stays green.
Design: one conduit, no knob — an explicit net: knob would let a costed
campaign silently produce a gross headline again (the exact misreading
of the issue's evidence). Per-member RMetrics scalar fields stay gross;
net_expectancy_r keeps its meaning. Wire shape unchanged (serde(skip)).
The net series is materialized as a separate expression in summarize_r;
the pinned-float expressions (net_sum, the SQN pair, the lockstep
r_metrics_from_rs copies) keep their tokens verbatim. Fork decisions
and rationales: issue #259 comments.
New coverage, both hostless over the synthetic SYMA archive: a costed
campaign twin shifts the pooled-OOS bootstrap (sweep→gate→wf→mc) and
every per-survivor bootstrap (sweep→mc) below its gross sibling, with
the trade population unchanged; a unit test pins the conduit as the
cost-netted series with the empty-stream degeneracy. Existing conduit
tests renamed with the field.
Verified: full workspace suite green (71 result blocks), clippy clean,
zero bare trade_rs tokens remain, ledger conduit prose updated in place.
closes#259
Every hand-rolled test-sandbox helper keyed its directory on
std::process::id() under env::temp_dir(), so the pre-create wipe never
matched a prior run's path: one directory stranded per helper site per
test-binary invocation, >50k dirs / 15.5 GiB on the 16 GiB tmpfs by
2026-07-13.
The remedy extends commit 84e1075's knob-lab pattern to every site:
fixed, tag-keyed, PID-FREE names under the build-tree tmp anchor, each
site keeping its pre-create remove_dir_all — which now genuinely
reclaims the previous run. Integration/bench targets use
env!("CARGO_TARGET_TMPDIR"); src-internal unit-test helpers (cargo sets
that var only for integration/bench targets) derive the same anchor
from env!("CARGO_MANIFEST_DIR") + ../../target/tmp. Residue is bounded
to one directory per site, on disk instead of the tmpfs, per checkout.
One deliberate exception: project_new.rs's tmp() stays under
env::temp_dir() because two of its tests (new_outside_a_work_tree_*)
need a base genuinely detached from any git work tree, and target/
lives inside the checkout's work tree. Its name carries a per-checkout
hash discriminator instead, so concurrently running checkouts (which
share /tmp) cannot race on the fixed name while each run still
reclaims its predecessor.
tempfile-RAII was considered and rejected (issue #258 decision log):
it cannot cover the process-lifetime OnceLock scaffolds (statics never
drop), adds a dependency, and still strands kill-leftovers on the
tmpfs.
Verified: full workspace suite green (incl. the new RED regression
test temp_cwd_sandbox_does_not_leak_under_env_temp_dir), clippy clean,
sandboxes observed under target/tmp with stable names across runs.
Stale pre-fix PID-keyed dirs in /tmp are not swept by this change and
age out only by manual cleanup.
closes#258
probe_window drained a source's entire close column just to learn its
first and last bar timestamp; both live callers (open_real_source and
campaign_window_ms on the campaign trunk) now resolve through
aura-ingest's new archive_extent(): list the symbol's SYM_YYYY_MM.m1
files, load only the boundary months via the existing loader seam, and
walk across gap months forward/backward — O(2 file loads) typical
instead of O(archive). Empty-window refusal semantics are unchanged
(pinned). Two characterization tests captured the resolved windows
against the OLD implementation over the synthetic per-test archive and
stay green unchanged after the swap.
Measured: the probe itself drops 366ms -> 13ms (~28x) on the full
2014-2026 GER40 archive; end-to-end command wall-clock moves only
~2-3% because the sim loop dominates — the fix removes an
O(archive-size) term, not the dominant cost.
closes#252
blueprint_walkforward_family's pre-flight executed the full first-IS-
window sweep via blueprint_sweep_over and discarded the result, purely
to surface axis errors once before the parallel window fan-out (#177).
validate_axis_grid now drives the sweep terminal's own resolve/arity/
kind checks with a constant placeholder report — same single-sourced
rejection wording, no member run, no roller derivation needed.
Scope correction against the issue text: this path serves only the
synthetic (non---real) walkforward. The --real trunk routes through
verb_sugar into aura-campaign's [Sweep, WalkForward] process, whose
leading full-window sweep is a separate, larger duplication — re-filed
with corrected attribution in the issue's closing comment.
closes#253
RunManifest gains defaults: Vec<(String, Scalar)> — the wrap-prefixed
bound_param_space() of the signal, read after axis reopening, so a
bound param an axis overrode has already left the space and flows
through params instead (disjoint by construction; verified end to end:
a sweep member's overridden fast.length sits in params while
slow.length/bias.scale sit in defaults). params keeps its "what varied"
semantics and stays the reproduce input. One-directional serde
widening (#[serde(default)]) per the selection/instrument/topology_hash
idiom — old records deserialize with an empty defaults; unlike the
Option fields it always serializes, mirroring params. ~20
struct-literal sites across five crates gained the field
(compile-mandated breadth, no behaviour change at those sites).
The C14 ledger records the underlying decision (2026-07-13): generated
outcome records spend redundancy on direct readability (single writer,
cannot drift); authored intent artifacts admit none (every redundancy
is a drift site) — so the fix lands in the manifest, never the
blueprint.
Verification: RED test run_manifest_stamps_untouched_bound_defaults
green; cargo build --workspace; cargo test --workspace green; clippy
-D warnings on the touched crates; binary-level sweep exclusivity
check.
The four _open blueprints leave the user-facing gallery
(crates/aura-cli/examples/) and become test fixtures; the closed twins
are now the only example corpus (bound params are overridable defaults
since #246, so the closed files serve both run and sweep).
Compile-time embeds follow the move: the emit_* generators and the
shipped_*_serialize byte-pins target tests/fixtures/ (the open fixtures
stay builder-generated and byte-pinned — regenerating after the move
left the tree clean), load_open_r_sma and the axis-probe read the
fixture, and the walk-forward refit test plus the `aura graph` default
sample switch to the closed r_sma.json. graph_construct's open-fixture
tests swap example() -> fixture(), their names dropping the now-false
"shipped example" claim; a new e2e test pins the relocation property
(all four fixtures load and introspect, the gallery holds exactly the
four closed examples).
Intermediate state: cli_run.rs / research_docs.rs / project_load.rs
still reference the old example paths and fail at runtime until the
follow-up migration commit (MIGRATE sites move to the closed twins,
NEEDS-OPEN sites to the fixtures).
Verification: cargo build --workspace; cargo test -p aura-cli --bin
aura; cargo test -p aura-cli --test graph_construct; the three ignored
emit generators re-run byte-stable.
Task 5 of the bound-override cycle. The campaign executor and its gates
accept an axis naming a bound param everywhere the open surface was
already accepted: CliMemberRunner::run_member re-opens the override set
on the cell's probe space and raw reload (raw_bound_overrides_of — the
campaign document's axis names live in strategy coordinates, matched
against bound_param_space directly), the P3 preflight in
validate_before_register threads the same set so the executable-shape
check passes, and aura-registry's validate_campaign_refs treats a bound
name as a valid axis (open space wins the arm order; the kind check
still faults a mismatched axis over a bound path).
The CLI-side helper family is named by coordinate system now:
wrapped_bound_overrides_of (wrap-prefixed, sweep/wf/reproduce) beside
raw_bound_overrides_of (campaign). A fresh post-block quality review
found no correctness defect; its two stale-comment findings (renamed
helpers cited under their old names) are fixed in this commit.
refs #246
Task 3-4 of the bound-override cycle. The family boundary derives the
override set from the invocation's axes (override_paths: an axis in the
open space passes through, one naming a bound param re-opens it in
strategy coordinates, one matching neither is refused with a message
pointing at --list-axes) and threads it through the axis probe and every
per-member reload (blueprint_axis_probe_reopened / reopen_all), so probe
and members stay slot-identical. The 'fully bound; nothing to sweep'
refusal retires; identity stays the authored document (the topology_hash
probe reload is never re-opened).
--list-axes now prints the bound surface after the open knobs — one
'<bp>.<node>.<param>:<KIND> default=<value>' line per bound param,
unconditionally (a bound param is an equally re-openable axis regardless
of how many knobs are open beside it).
Walk-forward (blueprint_sweep_over, blueprint_walkforward_family,
run_oos_blueprint) and reproduce (reproduce_family_in plus the
campaign-side trace re-run) derive the same set silently from recorded
manifest param names (bound_overrides_of), so a family that swept an
overridden bound param re-derives bit-identically.
refs #246
probe_window's two empty-result exits now route through a distinct
no_data_in_window refusal (requested bounds + archive path); its callers
have already proven the symbol present via has_symbol, so the old
"no local data for symbol" reuse misattributed a window fact to symbol
absence. The genuinely-unknown-symbol message and exit codes stay.
closes#242
Consumer-POV fieldtest evidence for the two-tier project model: the
data-only quickstart, the role-2 attach loop, real-data usage inside a
data-only project, and the refusal surfaces all match the docs.
Inline fixes for two of the friction findings:
- docs/project-layout.md: the day-in-the-life run example now shows real
Unix-ms window bounds instead of bare years (--from/--to take ms).
- `aura new` --help one-liner no longer calls the project a crate.
Remaining findings tracked on the Gitea queue (empty first-manifest
provenance on a fresh scaffold; misleading no-data message for an
uncovered --real window; the tier hint missing on the graph-build path;
the raw cargo-metadata leak for a missing [nodes] dir).
refs #241
- CLAUDE.md invariant 9 amended end to end: a project is a directory
anchored by a static Aura.toml, data-only by default; native node
logic lives in attached node crates (the nodes/ antecedent removed —
user decision 2026-07-12, re-opened by the role model's diagnosis).
- docs/project-layout.md reworked to the two tiers (data-only default
tree + sibling node crate, workspace note under the crate half); the
day-in-the-life walkthrough now attaches the project's node crate
once and wires the namespace the shown command actually produces.
- docs/authoring-guide.md: data-only quickstart (closed run target,
open sweep target) + `aura nodes new` as the native entry; section
cross-reference updated.
- docs/glossary.md: new "data-only project" and "node crate" entries;
Aura.toml and manifest entries carry the tier split.
- docs/design/INDEX.md: realization note (wiring-only tier, #241) in
the project-environment section.
- rustdoc: `[nodes]` intra-doc-link escapes in project.rs/main.rs.
Full gates green: workspace suite (all binaries), clippy -D warnings,
doc build without new warnings.
closes#241
- New clap family `aura nodes new <name> [--engine-path] [--namespace]`:
scaffolds a node crate as a SIBLING of the project root (Cargo.toml
with the aura-core path-dep, the parameterized Scale starter node,
roster macro, crate-scoped .gitignore + CLAUDE.md, own git repo) and
appends the `[nodes] crates = ["../<name>"]` pointer to the project's
Aura.toml. Refuses outside a project, on any existing [nodes] section
(incl. an empty crates array), and on an existing destination; joins
the load-phase bypass beside `aura new` (a scaffolder must work in an
unbuilt tree).
- The old single-tier scaffold() is gone; the crate templates live on
in scaffold_node_crate; dead-code bridges removed with their callers.
- tests/project_nodes.rs: attach + refusals + namespace override + the
full role-2 loop (scaffold, cargo build, vocabulary resolves
<ns>::Scale end to end).
- tests/project_sweep_campaign.rs fixture reworked to the two-tier
shape (aura new + aura nodes new --namespace knob_lab); the two
campaign acceptance tests are byte-unchanged and green.
refs #241
- NewCmd drops --engine-path/--namespace (they belong to the node-crate
scaffold); dispatch_new emits Aura.toml + .gitignore + two std-only
starter blueprints + CLAUDE.md, git init, no crate, no build step.
- Two starter blueprints because run requires a closed blueprint while
sweep requires open knobs: signal.json (closed, run target) and
signal_open.json (fast.length free) — the scaffolded CLAUDE.md
quickstart lines are both verified live.
- The crate templates survive untouched for the attach verb; old
scaffold()/scaffold_spec() carry a dead-code bridge until it lands.
- project_new.rs realigned: data-only file set, no-build run headline
(stdout report, C1 byte-identity, commit-only provenance, negative
runs/ assert), advertised-sweep headline (#218 gate passes, store
lands project-local).
refs #241
The NotFound arm of emit_chart now queries the recorded campaign runs:
records with a persisted trace_name whose stored campaign document
carries name == ARG. A unique match re-dispatches the existing charting
path onto the resolved handle; several matches refuse listing the
candidate handles in append order (refuse-don't-guess — re-running a
named invocation legitimately yields {campaign8}-0, -1, ...); zero
matches keep the not-found message verbatim. The exact trace-store
handle always wins; the name never becomes a store key (C18 untouched).
Guide §3 and the glossary tap entry document the name alternative.
closes#238
fit_wf_ms_sizes (pure, unit-tested) passes the fixed 90/30/30-day roller
through byte-identically whenever it fits the resolved campaign window,
and scales it down preserving the 3:1 IS:OOS ratio (step = OOS, one roll
at the minimum) when the window is shorter — so mc/walkforward --real
over a ~30-day window run to completion instead of dead-ending at the
executor with a remedy unreachable from the verb surface. Both dispatch
sites consume the fit; the executor's own check stays for authored
process documents. Verb help documents the fit.
closes#239
TraceStore::name_kind and read_family resolved taps exactly one
directory below the family handle, so the per-member fan-out layout
sweep/walkforward --trace writes since #224
(<name>/<cell>/<member>/index.json) classified as NotFound and
`aura chart <printed handle>` exited 1. Both sides now resolve depth-1
(campaign nominee layout) and depth-2, with depth-2 members keyed
<cell>/<member> (deterministic sort, C1); the on-disk layout is
unchanged. emit_chart's not-found message no longer suggests re-running
with the unknown handle as --trace (the data-creating command cannot
take an output handle as input); the message prefix stays pinned.
Milestone-fieldtest finding B1 (fixtures: 09da04f); verified against
the fieldtest's own on-disk family and the campaign depth-1 sibling.
The no---from/--to fallback in dispatch_generalize probes EACH listed
symbol's full archive window (same partial bounds threaded through) and
resolves the one shared campaign window as their intersection — latest
start, earliest end — instead of symbols[0]'s window alone, so the
cross-instrument floor is measured over a genuinely common period.
Disjoint archives refuse at runtime (exit 1) naming each symbol's span.
The intersect-or-refuse arithmetic lives in the pure
intersect_shared_window helper (unit-tested: single symbol, overlapping,
disjoint); the explicit --from+--to path stays byte-identical. The
superseded shape test is renamed to
generalize_without_explicit_window_resolves_a_shared_window_and_completes
with its doc comment reworded to the intersection contract.
closes#213
cost_nodes_for (beside stop_rule_for_regime) is the one CostSpec ->
bound-builder binding — every component fully bound, so the wrapped
param space stays cost-invariant. run_blueprint_member joins the cost
rows into summarize_r (member metrics genuinely net) and stamps the
cost components into the manifest params; CliMemberRunner threads the
doc's cost. The re-run sides re-derive the model from the manifest:
reproduce via cost_specs_from_params (the #233 stop pattern — a costed
family reproduces bit-identically) and persist_campaign_traces binds
the same model so the C1 drift alarm compares like with like.
Hand-computed pin: a constant cost model yields
net_expectancy_r == expectancy_r - cost_per_trade/|entry-stop| on a
synthetic member.
Verified: full workspace suite green, clippy -D warnings clean; in-loop
spec + quality gates passed per task.
refs #234
wrap_r accepts an optional CostLeg (cost node builders + the tx_cost/
tx_net channels); non-empty rebuilds the #221-deleted wiring — the
cost_graph fed by the executor's four geometry outputs, the vol-proxy
RollingMax/RollingMin/Sub (back from cfg(test) to production) feeding
each vol_slippage component, the interned cost[k].* names, a gated
4-col cost recorder in reduce mode (the summarize_r join input), and
the LinComb(4) net_r_equity curve in !reduce. All callers pass None —
behaviour-preserving; the entire suite green unchanged is the gate.
Co-temporality of cost rows with R rows unit-pinned.
Verified: full workspace suite green unchanged, clippy -D warnings
clean; in-loop spec compliant + quality approved (one held Minor: the
CostLeg doc's forward reference to cost_nodes_for, which task 4 lands).
refs #234
The acceptance proof: hl_channel — a causal Donchian channel (Delay(1)
excludes the current bar, C2) consuming high/low/close roles, built from
rostered vocabulary, shipped as the r_channel/r_channel_open example
pair (builder + regenerator + serialize pins, the r_* pattern). Proven
at three layers: a non-gated loaded-vs-carve equivalence over inline
sources (non-zero-bias hardened per review), the unconditional synthetic
multi-column refusal, and archive-gated CLI e2e — aura run on GER40
end-to-end plus a sweep whose members run and reproduce 2/2
bit-identically.
Ledger: new C26 entry (the binding vocabulary — closed column set +
price alias, name-driven default + campaign data.bindings override,
canonical order = the C4 tie-break, Blockly-litmus argument, the #71
extension point for recorded non-price sources) and the C20/C24
scaffolding-clause annotations: the single-price data weld is retired,
wrap_r's remaining R-scaffolding retirement stays #159.
Verified: all proof tests green, full workspace suite green, clippy -D
warnings clean; independent quality review approved.
closes#231
The six Close-only open sites route through the resolved binding's
column set via open_columns (probe_window, open_real_source,
run_sources/windowed_sources, the campaign member open, the trace
re-run open) — a strategy declaring high/low/close roles gets exactly
those columns, merged in the canonical C4 order. Synthetic data refuses
any binding beyond {close} with the honest --real remedy (the walk
generates a close series only); the sweep/mc family builders ride their
established Err contract, run/walkforward/reproduce refuse exit-1.
Proof at two layers: an engine e2e composes an OHLC blueprint from data
and runs it over four inline VecSources to hand-computed rows (the
merge-order pin), and an archive-gated CLI e2e sweeps a high/low
blueprint over GER40 opening the High and Low columns.
Verified: engine OHLC e2e green, full workspace suite green, clippy -D
warnings clean.
refs #231
Behaviour-preserving threading: wrap_r's hard-wired
source_role("price") weld is gone — root roles now come from a
ResolvedBinding, one per entry in canonical column order, each fed into
the signal's same-named port; the guaranteed close entry always feeds
broker + executor (whose 'price' is a node-schema port name, not a
role). Every existing blueprint resolves to the identical single-price
plan, so the entire suite is the gate: green unchanged.
Threaded through all callers: run_signal_r, run_blueprint_member (+ its
production and test callers), blueprint_axis_probe (lenient probe
binding), reproduce_family_in, the sweep/oos/walkforward/mc family
builders, CliMemberRunner::run_member, persist_campaign_traces.
Vocabulary role names use their static form at the port seam; only an
override-renamed role leaks its name once per graph build (post-review
polish capping the per-member leak).
Verified: full workspace suite green unchanged, clippy -D warnings
clean.
refs #231
crates/aura-cli/src/binding.rs: the closed column vocabulary (open/high/
low/close/spread/volume, price as the close alias) with ResolvedBinding
— one ordered plan both halves of the single-price weld will share.
resolve_binding applies override-wins-over-name-default, refuses unknown
roles / bad override values / the price+close double-alias / a rebound
reserved close role, synthesizes the broker-only close entry, and sorts
into canonical M1Field order (the C4 merge tie-break contract). 12
exact-prose unit tests. Module-wide dead_code allow is scaffolding until
the wrap seam consumes it next.
aura-ingest: open_columns / open_columns_window open any field set in
the given order (None if any field lacks an overlapping archive file);
open_ohlc now delegates with the fixed OHLC order — behaviour-identical,
still guarded by the open_ohlc_seam bit-identity test.
Post-review polish: RESERVED_CLOSE_ROLE constant couples the synthesized
close's two sites; dead_code note reworded condition-based.
Verified: binding 12/12, ingest absence test green, full workspace suite
green, clippy -D warnings clean; independent quality review approved.
refs #231