Cycle B of milestone "The World — parameter-space & sweep". A blueprint is now
value-empty: a leaf holds a param-generic recipe, not a built node, and a positional
Scalar vector is bound slot-by-slot at bootstrap — so one blueprint bootstraps into
many distinct instances under different vectors, with no cdylib rebuild (C12/C19).
This is the binding primitive a sweep (#32) drives.
Ratified design (brainstorm): value-empty reconstruct-through-new() over
mutate-in-place and over a default-bearing variant. The value lives only in the
injected vector (no baked default), keeping the blueprint a pure param-generic
recipe (C19); every injected value flows through the node's own constructor (the
single sizing/validation gate).
- aura-core: LeafFactory { name, params, build } — the recipe (params -> sized node
through `new`); Scalar::as_i64/as_f64 value accessors. Node trait unchanged.
- aura-std: each of the 7 nodes exposes factory() (SMA length:I64, Exposure
scale:F64, LinComb arity x weights[i]:F64; Sub/Add/SimBroker/Recorder paramless,
capturing their non-param construction args — pip_size, the Recorder channel).
- aura-engine: BlueprintNode::Leaf(LeafFactory), From<LeafFactory>; param_space()
reads factory.params() pre-build; compile_with_params/bootstrap_with_params build
each leaf from its kind-checked slice while lowering (build-then-wire), arity
checked up front via param_space().len(); CompileError::{ParamKindMismatch,
ParamArity}. compile/inline/edge-rewrite are structurally unchanged, so the
compilat stays bit-identical for a given point (the bit-identity and both
param_space mirror tests stay green). The vestigial pre-build Composite::schema /
BlueprintNode::schema (no live caller — interface resolution is structural on the
built flat nodes) are removed.
- aura-cli: the blueprint view labels leaves by bare type via LeafFactory::label()
(`[SMA]`) — the ascii-dag renderer cannot render wide cluster-sibling labels (see
spec); the compiled view still labels valued (`SMA(2)`). The sample supplies its
point as a vector; the mis-wiring swap moved to the compiled view.
The #34 dual-traversal drift hazard is subsumed: compile_with_params consumes the
vector in the same recipe walk param_space() reports, so the two share one
traversal.
Verified: cargo build/test --workspace green (127 tests, incl. bit-identity, both
mirror tests, and 4 new injection tests — different-vector-different-run, kind
mismatch, arity, determinism); clippy --workspace --all-targets -D warnings clean;
`aura graph` blueprint view renders cleanly.
Scope: one vector -> one instance. Deferred: sweep enumeration (#32), domain
validation (#32/C20), single-run authoring convenience (#35).
closes#31
Make a wired graph introspectable so a mis-wiring becomes visible. Two
selectable views, both authored from one built-in sample blueprint:
aura graph clustered blueprint view — composites drawn as named
ascii-dag cluster boxes (pre-inline, C9)
aura graph --compiled flat compilat view — composite boundaries dissolved
(C23); the graph the run loop actually runs
The payoff is intrinsic, param-carrying node labels: two SMAs read SMA(2) /
SMA(4), so a swapped fast/slow input reads back differently. This is realized
by a `label()` default method on the core Node trait — a non-load-bearing
render symbol the run loop never reads (wiring is by index), the symbol C23
already reserves citing #13. Recorded as a C8 refinement in the ledger.
Rejected the alternatives in brainstorm: a separate Describe trait (forces a
combined trait-object dance for a label the ledger calls non-load-bearing) and
extrinsic parallel labels (decoupled from the node, so an author can label the
slow SMA "fast" and mask the very bug the render exists to surface).
Mechanism:
- aura-core: Node::label() default method (additive, object-safe, single-line).
- aura-std: per-node overrides — SMA(n)/Exposure(s)/SimBroker(p) carry params;
Sub/Add/LinComb/Recorder are bare (their identity is not a mis-wiring axis).
- aura-engine: Composite gains an authored `name` (cluster title; dissolves at
inline) + read-only graph-as-data accessors on Blueprint/Composite. No
external dependency — the engine stays dependency-pure (C16); ascii-dag lives
only in aura-cli. The label-free index-wired compilat (C23) is unchanged.
- aura-cli: ascii-dag v0.9.1 + a graph.rs adapter (Vertical mode; labels
materialized into an owned Vec<String> that outlives the borrow-based Graph).
`aura run` is untouched (the sample duplication is dedup idea #14).
Tests: a concern-defining test (swapped sample renders != correct), structure
pins (cluster present in blueprint view, absent in compiled view), per-node
label disambiguation, and two frozen byte-goldens of the deterministic render.
The cycle-0012 bit-identical and run-sample non-regression tests stay green.
Verified by the orchestrator (not the agent report): cargo build --workspace
clean; cargo test --workspace all green; cargo clippy --workspace --all-targets
-D warnings clean; both views run live and render as expected.
closes#13
`aura run` keyed only on the first token being `run`, so `aura run
extra-garbage` printed the full report and exited 0 -- a typo'd
`aura run --sweep` silently ran instead of hinting. #16 ratified the
strict reading over the lenient one (the lenient reading buys no real
forward-compat: future args would be parsed explicitly anyway, while the
silent-swallow actively hides typos and clashes with the exit-2 hygiene
the unknown-subcommand path already enforces).
Add a match guard so only a bare `run` reaches the JSON happy path:
Some("run") if args.next().is_none() => println!(...)
A trailing token fails the guard and falls through to the existing
`_ =>` usage-error arm (stderr usage, exit 2) -- no literal duplication,
the error path is reused. Bare `aura run` is unchanged, pinned by the
positive-preservation assertion and the two sibling run-tests. Verified:
cargo test -p aura-cli green (6), clippy --all-targets -D warnings clean.
closes#16
`aura run extra-garbage` keys only on the first token being `run` and
ignores the rest, so it prints the full report and exits 0 -- a typo'd
`aura run --sweep` masquerades as a successful run instead of getting a
hint. The 0010 cycle_scope ("any other or missing subcommand -> usage +
exit 2") did not unambiguously cover `run <token>`; #16 ratifies the
strict reading.
This RED pins it: a trailing unexpected token takes the error path
(usage on stderr, empty stdout, exit 2), mirroring the no-args sibling;
bare `aura run` is held to its current happy path (JSON on stdout, exit
0) by a positive-preservation assertion so the strict guard cannot
regress it.
refs #16
RunManifest.commit read option_env!("AURA_COMMIT").unwrap_or("unknown"),
so a normal build's manifest identity field was a bare placeholder --
C8's audit trail (this run = this commit) had nothing to point back at
once runs are archived (C18 registry).
Add crates/aura-cli/build.rs: at compile time it shells out to the `git`
already in PATH -- `rev-parse HEAD`, plus a `-dirty` suffix when
`status --porcelain` is non-empty -- and emits
`cargo:rustc-env=AURA_COMMIT=<sha>`. The existing option_env! read is
untouched and now picks it up. Chose shelling to git over a libgit crate
to keep the zero-external-dependency commitment (C14/C18); the firewall
crate is aura-ingest, and a build-time process call adds no runtime dep.
No-git case (packaged source tree, no .git): build.rs returns early and
swallows git errors, leaving AURA_COMMIT unset so "unknown" still holds.
Staleness guard: build.rs emits rerun-if-changed on .git/HEAD and
.git/logs/HEAD (logs/HEAD grows on every HEAD move incl. branch switch),
so the captured sha is rebuilt when HEAD moves rather than going stale.
Determinism (C1) is intact: AURA_COMMIT is fixed at compile time, so two
runs of one build still produce byte-identical manifests.
Two tests that pinned the old "unknown" placeholder are relaxed to the
new structural contract (not scope creep -- they were the old contract):
the cli_run.rs JSON-shape prefix no longer pins the commit value, and
the main.rs unit test asserts commit is non-empty and stable across runs
instead of equal to "unknown". The headline contract (manifest.commit
contains the real HEAD sha) is pinned by run_manifest_commit_carries_
real_git_head (4552d0c). Verified: cargo test -p aura-cli green (5),
clippy --all-targets -D warnings clean.
closes#15
RunManifest.commit comes from option_env!("AURA_COMMIT") defaulting to
"unknown", so the C18 manifest's identity field -- which code produced
this run -- is a bare placeholder in a normal build. C8's audit-trail
invariant (this run = this commit) needs the manifest to point back at
its source once runs are archived.
This RED pins the desired contract structurally: a normal `aura run`'s
manifest.commit CONTAINS the current git HEAD sha (obtained in-test via
`git rev-parse HEAD`) and is NOT the bare "unknown" literal. Structural
(contains, not equals) on purpose: the working tree may be dirty when
the test runs (this test file itself was uncommitted at author time), so
a build that appends a "-dirty" suffix must still pass. GREEN is a
build.rs in aura-cli capturing HEAD at compile time, "unknown" retained
only for the no-git case.
refs #15
`aura --help` and `aura -h` are a newcomer's reflex first command but
fell through to the catch-all unknown-subcommand arm: exit 2, empty
stdout, error text on stderr. The conventional help affordance returning
the error path is a C22 first-contact friction (milestone fieldtest).
Add a `Some("--help") | Some("-h")` arm before the catch-all that prints
the usage to stdout and returns normally (exit 0). The `_ =>` error path
is untouched, so an unknown subcommand still prints `aura: usage: aura
run` to stderr and exits 2 -- pinned by the negative-preservation
assertion in the RED test (07ba20a). Verified: cli_run.rs all green
(help/no-args/run), clippy --all-targets -D warnings clean.
Minimal slice per the tdd handoff: the stdout help literal ("usage: aura
run") and the stderr error literal ("aura: usage: aura run") are left as
two distinct user-facing strings rather than factored into a shared
usage constant -- a single usage source is a deliberate non-goal here,
left for a future cycle if one wants it.
closes#20
`aura --help` and `aura -h` are a newcomer's reflex first command, but
today they hit the unknown-subcommand error path: exit 2, empty stdout,
one-line `aura: usage: aura run` on stderr. For a C22 "newcomer sees a
populated trace" milestone the conventional help affordance returning
the error path is a first-contact friction.
This compile-clean RED pins the desired success-path contract: `--help`
and `-h` print non-empty usage to stdout and exit 0, and the usage names
the `run` subcommand. A negative-preservation assertion keeps an unknown
subcommand (`frobnicate`) on the exit-2 error path so the fix cannot
regress bad-args handling. Asserts structural facts only (exit code,
non-empty stdout, mentions `run`), not exact help prose.
refs #20
The Walking-skeleton milestone's closing seam: a real `aura run` binary that
bootstraps a built-in sample harness, runs it deterministically, and prints the
cycle-0009 metrics+manifest report as canonical JSON to stdout — the headline
C14 "run a sim, emit structured metrics" move, end-to-end, from a binary for the
first time.
Two deliverables:
- aura-std::Recorder — a reusable recording sink node (the glossary *sink* role,
C8/C22): a pure consumer (output: vec![]) over kinds.len() input columns,
holding an mpsc::Sender<(Timestamp, Vec<Scalar>)> as its out-of-graph
destination. mpsc keeps the engine's purity invariant (C7): no Rc/RefCell.
Supports all four base scalar kinds; returns None until every column is warm.
Promotes the shape the #[cfg(test)] fixtures already proved into a shipped,
reusable block (the fixtures stay as historical snapshots; de-dup is a later
tidy).
- aura-cli `run` subcommand — synthetic_prices / sample_harness / run_sample /
main. The sample harness (synthetic source → SMA(2)/SMA(4) → Sub → Exposure →
SimBroker → two Recorder sinks) is authored in plain Rust over the raw
Harness::bootstrap(nodes, sources, edges) API (C17/C20) — the open
experiment-builder DSL thread is deliberately not committed this cycle. main
hand-parses one subcommand: `run` prints run_sample().to_json() (exit 0),
anything else prints a one-line usage to stderr (exit 2). aura-cli gains
aura-std + aura-core path deps; the workspace stays zero-(external-)dependency.
The built-in synthetic stream (7 ticks, rises then reverses) yields a non-trivial
demo trace: equity [0,0,0,0,-0.08,-0.17,-0.13] → total_pips -0.13, max_drawdown
0.17, exposure_sign_flips 1. The run_sample unit test pins the integer flip count
exactly and the two f64 metrics within 1e-9 (the real run's float dust is ~7e-15,
confirming the tolerance); determinism is pinned exactly (two runs → identical
JSON). tests/cli_run.rs drives the built binary for the observable exit/stdout
contract.
manifest.commit is filled from option_env!("AURA_COMMIT") (defaults to
"unknown"); real HEAD capture via build.rs is deferred. Non-goals untouched:
experiment-builder DSL, aura new, Aura.toml schema, the data-server source (#7).
One deviation from the plan's verbatim code: a scoped
#[allow(clippy::type_complexity)] on sample_harness (its (Harness, Receiver,
Receiver) return tuple trips the lint under -D warnings) — consistent with the
identical allow on the build_two_sink_harness fixture in aura-engine. Verified
myself: cargo test --workspace (61 green, 0 red), clippy --all-targets
-D warnings (clean), cargo doc -D warnings (clean), and both smoke runs.
closes#8
Cargo workspace aura-core -> aura-engine -> aura-cli (bin `aura`) plus nodes/ for hot-reloadable cdylib node crates. Crate bodies are intentionally API-free; types arrive from the first spec. Adds the skills profile (.claude/dev-cycle-profile.yml), the project CLAUDE.md with the eight domain invariants, and the design-ledger skeleton.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>