claude ede26bb231 fix(aura-cli, docs): fieldtest bugs — a refusal that misdirects, a handle denied, a stale signature
RED-first fixes for the three bugs the source-blind field test caught. Two
are the same failure the whole cycle keeps circling: prose that tells a
caller something the code does not do.

- The chart not-found refusal prescribed `aura exec --tap <NODE.FIELD>=<FOLD>`.
  The selector takes a declared tap NAME, so following the refusal verbatim
  earns a second refusal ("the tap plan names 'sub.value', but the blueprint
  declares no such tap"), and the flag is not needed at all — a plain run
  records every declared tap. This sentence predates the cycle and was carried
  over verbatim while the rest of the refusal was corrected; the field test
  ran it as written, which is exactly why it ran it.
- `aura measure ic <family-handle>` answered "no recorded run '…' under
  runs/traces" for a handle that IS recorded — the value the campaign run
  printed, that `chart` renders, whose directory exists. A family holds
  members rather than one series, so IC is measured over a member. The
  refusal now says that and lists the member keys, which also closes the
  friction that nothing on any text surface prints them: reaching a member
  previously meant the very `ls runs/traces/` this cycle set out to retire.
- C28's #297 note still gave the entry points' signature as the retired
  `(_, Vec<String>)` pair in the present tense, contradicting C27 one contract
  over. The close commit claimed lockstep; this makes it true.

Both code fixes have tests that fail without them. The field test also found
four friction items and two spec gaps, filed rather than fixed here.

refs #309
2026-07-26 23:43:23 +02:00

aura

aura is a "game engine for traders." It is a Rust framework — and a playground — for authoring trading strategies as composable dataflow nodes, backtesting them deterministically and massively in parallel, validating them (sweep / Monte-Carlo / walk-forward), and freezing a validated strategy into a standalone bot with a broker connection. Its differentiator is the World: a program that constructs and orchestrates whole families of backtests, not the single run.

This README is a discoverability map of the shipped aura CLI — what commands exist and what they do. It deliberately keeps per-flag detail light; the two authoritative sources for exact syntax are always:

  • aura <command> --help — the exhaustive flag grammar.
  • The design ledger (docs/design/INDEX.md) and glossary (docs/glossary.md) — the concepts and invariants behind the surface.

Build & run

aura is a Cargo workspace. Build everything and you get one binary named aura:

cargo build --workspace          # binary at target/debug/aura
cargo build --workspace --release  # optimized, at target/release/aura

Invoke it as aura <command> … (examples below use the plain name).

Concepts in one breath

  • Blueprint — a serialized signal graph as data: a param-generic price → bias node graph. A blueprint file (blueprint.json) is the unit a downstream consumer loads and executes or varies.
  • Open vs. closed — a blueprint with unbound (free) numeric knobs is open; one with every knob bound is closed. A plain exec wants closed; naming a knob as a campaign axis (or an exec --override) is what reopens it.
  • Family — the set of runs a campaign document's process pipeline produces over a blueprint (a sweep grid, a Monte-Carlo seed set, a walk-forward window sequence). Families are persisted in a content-addressed store and can be listed, ranked, and reproduced.
  • Axis — one named, sweepable knob of a blueprint (e.g. fast.length), bound with a comma-separated value list. A gang fuses several sibling knobs into one axis (one value drives all members).

Executing a document

aura exec <target> (#319) is the one executor verb: <target> is either a loaded blueprint file (a single synthetic run) or a campaign document (a .json file or its registered 64-hex content id — instruments × windows × strategy × param axes × process, see docs/authoring-guide.md §3). graph renders a blueprint's structure so a mis-wire is visible before a run.

Command Purpose
aura exec <bp.json> Run one backtest of a closed blueprint and print its report. An open (free-knob) blueprint is refused with a clean error — bind it, or vary it as a campaign axis.
aura exec <bp.json> --override <node.param>=<value> Reopen one bound param for this single execution only (the value is recorded raw in the manifest); repeatable.
aura exec <bp.json> --tap <name>=<fold> Subscribe a declared tap to a fold (record/count/sum/mean/min/max/first/last) for this run; repeatable.
aura graph <bp.json> Render the blueprint's structure as an interactive HTML DAG so a mis-wire is visible before a run. Omit the file to render the built-in sample; a named-but-unreadable file is a usage error.
aura graph introspect --params <bp.json|id> Discover a blueprint's open, sweepable knobs. Prints each as <name>:<kind>, bound params trailing default=<value>. Run this first to learn the axis names for a campaign document.
aura exec <campaign.json|id> [--parallel-instruments <n>] Execute a campaign document — a grid family, gates, walk-forward, Monte-Carlo, and cross-instrument generalization per its process document, one cell per (strategy, instrument, window).
aura runs families List every persisted family (id, kind, member count).
aura runs family <id> [rank <metric>] List one family's members, optionally ranked best-first by an R metric (e.g. sqn_normalized, expectancy_r).
aura reproduce <family-id> Re-derive every member of a persisted family from the content-addressed store and check it is bit-identical (the C18/C1 determinism guarantee).

Important contract — every open knob is mandatory on a campaign axis. A campaign document's strategies[].axes must name every open knob graph introspect --params lists: there is no default value — pin a knob you don't want to vary with a single-value axis ({"kind":"I64","values":[4]}). Omitting one is a clean error naming the missing knob, not a silent default.

Use aura exec --help for the exact flag grammar, docs/authoring-guide.md for the campaign document shape (data window, naming, presentation/emit).

Authoring & introspecting topology

A blueprint is authored declaratively from a JSON op-list: an ordered list of by-identifier construction ops replayed against the standard node vocabulary. Adding a new node type to that vocabulary is Rust, not JSON — see docs/authoring-guide.md, §0, "Authoring a new node in Rust".

Command Purpose
aura graph build Read an op-list document on stdin, build the blueprint, and print its canonical JSON to stdout (no trailing newline — it is the content-addressed artifact).
aura graph introspect --vocabulary List every node type available to op-lists.
aura graph introspect --node <T> Show one type's input ports, output fields, and param paths (with the bind-value form).
aura graph introspect --unwired Read a partial op-list on stdin and list its still-open interior slots.
aura graph introspect --content-id Read an op-list on stdin and print the SHA-256 content id of the blueprint it would build.
aura graph introspect --identity-id Read an op-list on stdin and print the SHA-256 identity id — the topology's debug-name-blind id, so two documents (or an op-script and a Rust-built blueprint) expressing the same topology print the same id. Combinable with --content-id (both ids, one per line, content id first).

Op-list shape

Each element is one op, tagged by "op". Node params are bound with the typed Scalar form — a tagged, capitalized-kind object: {"I64": 5}, {"F64": 1.5}, {"Bool": true}. A worked example (an SMA-crossover → bias signal):

[
  {"op":"source","role":"price","kind":"F64"},
  {"op":"add","type":"SMA","name":"fast","bind":{"length":{"I64":2}}},
  {"op":"add","type":"SMA","name":"slow","bind":{"length":{"I64":4}}},
  {"op":"add","type":"Sub"},
  {"op":"add","type":"Bias"},
  {"op":"feed","role":"price","into":["fast.series","slow.series"]},
  {"op":"connect","from":"fast.value","to":"sub.lhs"},
  {"op":"connect","from":"slow.value","to":"sub.rhs"},
  {"op":"connect","from":"sub.value","to":"bias.signal"},
  {"op":"expose","from":"bias.bias","as":"bias"}
]

Piping this document into aura graph build emits a blueprint.json you can then feed to exec above:

aura graph build < crossover.ops.json > crossover.bp.json
aura graph introspect --params crossover.bp.json

The op kinds are source, input, add, feed, connect, expose, tap, gang, doc, and use (tap declares a recorded measurement point on an interior wire; doc declares the composite's one-line meaning, required at register — C29; use splices a registered blueprint in as a nested composite, by content id or label; add additionally takes an args object for arg-bearing types (Session, LinComb, CostSum) — structural, non-scalar construction consumed before bind; see the authoring guide). See aura graph introspect --node <T> for a type's exact ports and the op-script grammar in the design ledger for the full semantics.

Where to go deeper

  • aura <command> --help — exhaustive, always-current flag grammar.
  • docs/design/INDEX.md — the design ledger: architecture, invariants, the World.
  • docs/glossary.md — canonical terminology (blueprint, family, axis, sweep / Monte-Carlo / walk-forward, R, content-id, …).
S
Description
No description provided
Readme 19 MiB
Languages
Rust 68.3%
JavaScript 24.7%
HTML 6%
Shell 0.9%
CSS 0.1%