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.
closes#256
Fork B (owner decision 2026-07-14): the dissolved walkforward/mc
translations' leading sweep executed the full grid over the whole campaign
window and persisted a Sweep family, yet only the enumerated parameter
points ever crossed the stage seam (the wf stage re-sweeps them per IS
window itself). The leading stage is now the fieldless vocabulary block
std::grid: it enumerates, executes nothing, persists nothing.
- aura-research: StageBlock::Grid ({"block":"std::grid"}), schema-strict
parse arm (empty slot list: every key but "block" is refused by the
generic unknown-slot check), PROCESS_BLOCKS entry, intrinsic-tier no-op
arm; the vocabulary test's non-empty-slots guard carries a pinned
std::grid-only exception (a nominal slot would misdescribe the
vocabulary to describe_block consumers).
- aura-campaign: the inter-stage seam is a typed two-armed StageFlow
(points-only vs executed members); gate / mc-per-survivor fence the
points-only arm with defensive PipelineShape faults; preflight admits
std::grid only as the first stage and only immediately before
std::walk_forward (every other neighbor consumes executed reports).
- aura-cli: translate_walkforward / translate_mc lead with
StageBlock::Grid; the two family-shape E2E pins flip to zero Sweep
families; translate_generalize keeps its executed sweep(argmax) — its
generalize stage consumes the argmax winner report as the cell nominee.
- docs: dated #256 amendment in the ledger's verb-dissolution narrative;
the authoring-guide vocabulary transcript gains the std::grid line.
Behaviour preservation: the exact-grade real-data pins
(walkforward_real_e2e_pins_the_exact_current_grade,
mc_r_bootstrap_real_e2e_pins_the_exact_current_grade) pass unmodified —
survivor points reach the wf stage in the same odometer order as before.
Measured (the #256 acceptance measurement; debug build, real GER40 2025,
2x2 grid, `aura walkforward --real`, 3 runs each):
before (a55e4cf): 6.27 / 6.18 / 6.18 s
after: 4.52 / 4.49 / 4.45 s (~ -27% wall clock)
Suite: cargo test --workspace green (0 failed); clippy clean.
Decision log: #256 comments (fork rationale incl. the StageFlow seam and
the slot-guard exception).
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
A Frankfurt-baked zero-arg Session preset (open 09:00 Europe/Berlin as
literals) joins the std vocabulary as SessionFrankfurt, declaring
period_minutes: I64 (default 15) as its one scalar knob — the
single-knob roster pattern the cost nodes established. A data-only
blueprint can now reach a session/time-of-day stream (bars_since_open),
unblocking time-of-day conditioners for the confirm/refute research
model. Roster count pins bump 28 -> 29 in both conscious-act sites.
Fork decision on the issue: the preset variant over a param-vocabulary
extension — a timezone param would need a string kind, breaching the
four-scalar discipline; the preset keeps the vocabulary closed and is
the additive extension the roster-exclusion comment anticipates. The
4-arg Session::builder stays untouched; in_session/session_open_ts
stay deferred (#154).
closes#261
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 rejection must reach stderr as the prose payload alone — no
UnknownKnob(..) Debug frame around it. Fails on today's frame; the
prose fragments already pass, so the sole pinned defect is the frame.
refs #269
The four silently-misreadable cost/risk knobs (cost_per_trade,
slip_vol_mult, vol.length, vol.k) must carry one-line unit/semantics
annotations in campaign introspect --unwired — blocking the price-vs-R
and length-as-timescale misreadings that produce no error today.
refs #265
Both blueprint-sweep bind faults (wrong-kind axis value; an open param
no axis covers) must print prose in the unknown-axis sibling's register
— axis name, kind/knob specifics, a --list-axes pointer — never the raw
Debug structs KindMismatch{..}/MissingKnob(..). Exit stays 2. Fails on
the Debug leakage present today, not on scaffolding.
refs #247
aura --version must print 'aura 0.1.0 (<commit>)', the commit sourced
from the same AURA_COMMIT build provenance the run manifest stamps —
a pre-run freshness probe distinguishing a stale binary from an engine
bug. Fails on the absent parenthesized commit, not on scaffolding.
refs #266
aura data list must print the archive's symbols sorted, one per line,
exit 0 — the discovery step before aura data coverage. Hostless RED over
the synthetic SYMA+SYMB archive; fails on the absent 'list' subcommand
(clap exit 2), not on scaffolding.
refs #264
aura data coverage <SYMBOL> must print the archive's first/last present
month and each interior missing-month range collapsed to one
'missing: YYYY-MM..YYYY-MM' line — the Copper failure mode (first/last
check passes while the window start has no data) made visible before a
campaign runs. Hostless RED over a new gapped synthetic-archive fixture
(fresh_project_with_gapped_data); fails on the absent 'data' subcommand
(clap exit 2), not on scaffolding.
refs #264
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
The helper keys its sandbox on std::process::id() under env::temp_dir(),
so the pre-create wipe never matches a prior run's path: one directory
strands per test-binary invocation on the 16 GiB tmpfs (>50k dirs by
2026-07-13). The test pins the property at one representative site: the
returned path embeds no PID and lives off env::temp_dir().
refs #258
built_scale_project() scaffolded ~66 MB (built node crate) into a
pid-suffixed tempdir, so its wipe-before-scaffold never hit a previous
run's copy: one leaked directory per test-binary run. 159 of those
(~10 GB) filled the host's 16 GB /tmp tmpfs to 100%, breaking every
std::env::temp_dir() consumer. A fixed name under CARGO_TARGET_TMPDIR
(real disk, reclaimed by cargo clean) makes the existing wipe reclaim
the previous run — steady state is exactly one directory. Trade-off:
two concurrent cargo-test invocations over the same checkout collide
on the fixed name, the documented pre-existing caveat class for this
suite (#223).
closes#257
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
Env::data_path() handed paths.data to the data layer verbatim, so a
relative value resolved against the invoking process's cwd — aura run
from a project subdirectory silently read a different archive location
than from the root, while runs/ stayed anchored. Relative values now
join onto the project root, matching runs_root() and the [nodes]
pointer resolution; absolute values and the no-project default pass
through unchanged. RED-first unit test pins the root-anchored
resolution.
closes#254
The two generalize E2E tests whose property is no-window span
resolution (shared-window fallback, intersection semantics) no longer
stream the 6.6 GB host archive: fresh_project_with_data() generates a
deterministic two-symbol M1 archive (SYMA 2024-01..08, SYMB 2024-03..06
strictly inside it, so the intersection differs from symbols[0]'s
window on both bounds) in the exact zip/48-byte-record format
data-server reads, plus geometry sidecars, under the per-test project's
paths.data. Both tests drop their local_data_present gates — they now
run on any host, data mount or not.
~35s and ~50s become 0.74s and 1.14s; the full cli_run binary lands at
~25s (211s at the branch base). Full workspace suite: 247s -> 45.7s.
refs #250
Every E2E test that mutated the shared demo-project fixture store now
mints its own tempdir project whose 2-line Aura.toml points at the
once-built fixture crate by absolute [nodes] pointer; the runs/ store
follows the project root, so no two tests contend and libtest's
default thread parallelism applies. project_load keeps driving the
fixture directly (the suite's only proof of the inline-crate routing
arm); its single store-mutating test is that binary's only one, so no
lock is needed there either. This also closes the documented
cross-process race window (#223): there is no shared mutable store
left to race on.
Measured on the data-full host: cli_run 211s -> 57.2s (140 tests),
research_docs 21.1s -> 2.1s (73 tests), project_load green, clippy
clean.
refs #250
The two dissolved-walkforward flag tests assert window-independent
properties (plateau acceptance, per-flag stop defaulting) and now use
the ~135-day window the reproduce e2e already uses. The mc roller-fit
test replaces its full-archive probe sweep with a fixed ~30-day window
inside that same span: the property needs a data-carrying window far
shorter than the 90+30-day roller, not a live-span derivation, and the
archive only grows forward. Measured on the data-full host: 19.6s,
19.6s and 14.5s+ of serialized real-data compute drop to 1.1-1.9s each.
refs #250
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.
Executable spec for the defaults field: aura run on the fully bound
examples/r_sma.json must report a manifest whose params stays [] (the
"what varied" record) and whose new defaults field carries the
untouched bound params as wrap-prefixed (name, Scalar) pairs in
bound_param_space() order — fast.length I64 2, slow.length I64 4,
bias.scale F64 0.5. Asserts on the stdout JSON (C14: stdout shape ==
on-disk shape), so it compiles against the current RunManifest and
fails on the absent key.
The references to the relocated _open blueprints follow the plan's
two-way split: tests that only need a sweepable blueprint sweep the
closed twins via bound-override axes (#246) with their axis strings
byte-unchanged (the wrap prefix is the blueprint's internal name,
identical across twins); the 12 tests that genuinely exercise
open-param semantics (run/mc closed-guard refusals, subset-axes
MissingKnob, --list-axes open/bound line mix, gang dissolution and
campaign paths) read the fixtures under tests/fixtures/. The
research_docs/project_load store seeds sweep the closed twin (campaign
axis references resolve via the #246 reopen path); the INDEX.md history
note records the relocation.
Two deviations from the plan, both verified against the tree: doc
comments referencing the bare filename (no examples/ prefix) escaped
the global path swap and were reworded to stay truthful; the seed
variable open_bp was renamed closed_bp (the plan kept it, but the
rename touches only the binding and its uses and removes a
misdescription).
Verification: cargo test -p aura-cli --test cli_run (140 passed, 0
failed, real data exercised); --test research_docs; --test
project_load; the four byte-pinned exact grades unchanged; grep-clean
for examples/r_*_open.json across crates+docs; full workspace suite
green.
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.
Architect drift review over 0ad8fc6..e4fb64d: the C12 amendment's words
match the shipped behaviour exactly (identity from the authored document,
run/mc closed-guards untouched, bound = overridable default; every
point-binding reload goes through the reopened probe). One medium item
fixed here: the unknown-axis test's rationale comment still described the
retired 'fully bound; nothing to sweep' refusal. Noted as low-priority
debt, unchanged: the four override-derivation helpers across the wrapped/
raw coordinate systems are candidates for consolidation when the next
family boundary appears.
refs #246
Task 6 of the bound-override cycle: SIGNAL_OPEN_JSON_STD and its write
retire — aura new scaffolds signal.json alone, and the project CLAUDE.md
quickstart advertises run AND sweep against that one file (the sweep line
overrides the bound fast.length; --list-axes shows open + bound axes).
The data-only e2e loop proves it end to end: scaffold, run the closed
starter, sweep it via an override axis, and assert signal.json is the
only starter blueprint. The authoring-guide's starter-pair sentence is
retargeted; the last signal_open reference outside the unrelated #196
fixture string is gone.
refs #246
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
RED pin: graph_build_hints_attach_for_namespaced_ids_in_a_data_only_project
(graph_construct.rs e2e) — the op-script path reports OpError::
UnknownNodeType without consulting the tier-aware hint the blueprint
load path carries.
refs #244
aura new and aura nodes new follow their git init with a best-effort
initial commit (inline committer identity, warning-never-fatal, skipped
whenever the init itself was skipped per #204), so a fresh project's
first run stamps a real commit instead of an empty provenance block.
closes#243
aura new git-inits but never commits, so the first run stamps empty
project provenance. RED pin:
new_outside_a_work_tree_leaves_a_resolvable_head.
refs #243
An inverted or uncovered --from/--to window on a symbol the archive DOES
hold refuses via the symbol-absence message ("no local data for symbol")
instead of naming the window. RED pin:
run_real_empty_window_refusal_names_the_window_not_the_symbol
(archive-gated). Cause: probe_window routes both empty-in-window cases
through no_real_data although has_symbol already proved presence.
refs #242
- 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
A project-namespaced (::) unknown type id now hints by tier: outside
any project the existing "no Aura.toml was found" text stays
byte-identical; inside a data-only project the hint names the missing
node crate and the attach verb (aura nodes new); a loaded node crate
gets no hint (the vocabulary error carries the message).
refs #241
- ProjectProvenance namespace/dylib_sha256 widen to Option (Tier-1
additive serde): a data-only run stamps commit-only provenance; old
string-field manifests keep parsing.
- The load boundary tier-selects: a [nodes] pointer list in Aura.toml
(one entry honored; MultiCrate / pointer-context refusals) loads the
pointed-at crate; a root Cargo.toml stays the pre-#241 native project,
unchanged; neither means data-only (std vocabulary, project-local
runs/, both-tier provenance).
- ProjectEnv splits into directory facts + Option<NativeEnv>; the crate
pipeline (metadata -> artifact -> staleness -> ABI -> charter)
relocates byte-identical into load_crate.
- e2e fixtures: dataonly-project, multicrate-project,
nested-nodes-project (a pointer crate resolves and stamps its
namespace end to end).
refs #241
The viewer threads the model's doc through normalizeModel into both
composite view states — appended to INFO.B (collapsed box) and INFO.C
(expanded cluster frame) — and shows the root composite's doc as a muted
header line (#rootdoc span in GRAPH_HEAD, empty when absent; the DOM
population sits in the browser-only block, the same boundary as the
breadcrumb). viewer_tooltip.mjs pins the doc'd cases beside the
byte-exact un-doc'd pins; sample-model.json gains the additive doc key;
e2e drives aura graph over a nested-doc blueprint into the emitted page.
closes#125
Composite gains doc: Option<String> (the prose twin of name, a C23 debug
symbol): with_doc/doc() on Composite, a doc knob on GraphBuilder, a
Tier-1 additive-optional CompositeData field (no format-version bump;
absent-field documents keep their exact bytes), identity-stripped like
the name. The graph model emits an optional trailing doc fragment in
both scopes; json_str is hardened for free text (\n/\t/\r named,
control chars as \u00XX — the doc is the first multi-line value through
it). e2e: a hand-authored doc'd blueprint renders with the doc embedded;
the doc moves the content id but never the identity id.
refs #125
The headless viewer suite covered only the DOT half (normalizeModel/
genDot); the tooltip half — INFO.B (collapsed), INFO.C (expanded
cluster), INFO.P (ports), and md()'s markdown-to-HTML step — was
asserted by no test. viewer_tooltip.mjs pins the current shapes;
graph-viewer.js only hoists md() above the browser guard and exports
it (location-only move, no behaviour change), so the pin can drive the
real conversion headlessly. Surfaced by the grounding-check on the #125
composite-doc spec: its viewer assumptions had no current-behaviour pin.
refs #125
built_project/project_lock/ScratchPath/ScratchGuard move from their three
per-binary copies (cli_run.rs, research_docs.rs, project_load.rs's
built_fixture near-copy) into tests/common/mod.rs; project_load's one
shared-store-mutating test now takes the lock it previously lacked. The
module doc states the boundary plainly: the Mutex serializes threads
within one process only — process-parallel runners still race on the
shared fixture store (documented, dormant under cargo test's sequential
binaries; see the issue thread for the observed process-parallel repro).
closes#223
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
--trace <NAME> lands the name in the generated campaign document, but no
lookup surface consumes it: the trace store knows only the content-derived
handle {campaign8}-{run}, so the user cannot address their own family by
the name they chose.
refs #238
The dissolved mc/walkforward dispatches stamp the fixed 90/30/30-day
roller into the generated process document regardless of the resolved
campaign window; a window under 120 days dead-ends at the executor with
no remedy reachable from the verb surface.
refs #239
Milestone-fieldtest finding B1: TraceStore::name_kind/read_family
resolve taps exactly one directory below the handle, so the depth-2
per-member layout sweep/walkforward --trace writes (#224,
<name>/<cell>/<member>/index.json) classifies as NotFound and
`aura chart <handle>` exits 1 — with a remedy string that suggests
re-running the very command that produced the data. The new autonomous
test fabricates the depth-2 family via TraceStore and pins: chart on
the printed family handle exits 0 and emits the HTML.
refs milestone fieldtest 09da04f
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
The new archive-gated e2e pins the #213 contract: a multi-symbol
generalize without --from/--to must resolve the shared campaign window
as the intersection (latest start, earliest end) of the listed symbols'
full archive windows. On the current tree it resolves symbols[0]'s full
window (AAPL.US listed first keeps AAPL's wider span on both bounds), so
the test fails RED. Expected windows are derived from the live archive
via single-symbol sweep probes, never hard-coded.
refs #213
The closing proof: a GER40 campaign with a constant+vol_slippage cost
block runs to exit 0; every member's net_expectancy_r < expectancy_r
(costs bite), the same doc without the cost block reproduces today's
exact gross numbers (golden parity), net_r_equity.json persists on
request, and the costed family reproduces bit-identically. One plan
literal corrected in place: the member-line filter exact-matches the
line shape so the campaign_run summary record (which nests a family_id)
no longer false-matches.
Verified: e2e green on the real archive, full workspace suite green,
clippy -D warnings clean.
closes#234