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
TapChannel gains Net; tap_channel routes the full closed vocabulary
(producibility moved to the caller); the trace path drains the sixth
channel and persists the cost-adjusted net curve. A cost-less document
requesting net_r_equity keeps a skip notice, reworded to name the
remedy (add a cost block) — not a refusal. Two deliberate pin moves:
the absence pin re-pins to routed, the skip-notice e2e re-pins to the
reworded form. Post-review: positive write-path e2e added (a costed
GER40 campaign lands net_r_equity.json with the curve differing from
r_equity by the accrued costs).
Verified: routing pin + positive e2e green (real archive), full
workspace suite green, clippy -D warnings clean.
refs #234
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
The defining loop of a genuine external project, proven end-to-end and
green on arrival: a fresh aura-new project authors an OPEN blueprint
over its own node (knob_lab::Scale, factor unbound); --list-axes shows
the axis, a real-data GER40 sweep over it yields two members whose
manifests carry the project-node binding, the family persists and
reproduces 2/2 bit-identically, and one campaign cell over the same
blueprint runs to exit 0 linking a sweep family. No implementation was
needed — the loop already worked; this pin is the previously-missing
evidence (the deepest exercised path before was a single param-less
aura run). The axis-discovery leg is un-gated so the test stays
meaningful on a data-less host.
closes#235
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
DataSection gains an additive bindings block (role -> column; serde
default + skip-if-empty, so binding-less documents keep their content
ids — pinned). Validation is two-tier along the existing line: the
intrinsic tier checks binding VALUES against the column vocabulary
(DocFault::UnknownBindingColumn, alias-annotated display register); the
resolver tier checks binding KEYS against the loaded strategies'
input_roles() (RefFault::BindingRoleUnknown — a key must name a role of
at least one campaign strategy). A cross-surface pin keeps the doc-tier
vocabulary and the CLI binding module from drifting.
CliMemberRunner threads the campaign overrides into resolve_binding on
BOTH halves (column opening and wrap), keeping the C1 drift alarm
comparing like-with-like; the verb sugar passes no bindings (name
defaults rule).
Proof: an archive-gated campaign e2e rebinds price -> open and yields
different realized metrics from the close-bound run; refusal prose
exact-pinned at both tiers.
Verified: full workspace suite green, clippy -D warnings clean;
independent quality review, all findings repaired.
refs #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
--trace <fam> now translates through the verb sugar into a persist_taps
campaign-presentation block (was persist_taps: vec![] — the #168 no-op)
and persist_campaign_traces writes EVERY member of a selection-free
swept cell, one subdirectory per member under the cell dir, keyed by
the member's manifest params label in reproduce's exact format
(filesystem-sanitized, ordinal fallback); nominee cells keep their flat
layout. Members re-run at the cell's own stop regime and resolved pip,
streaming one member at a time, with the C1 drift alarm preserved per
member. The walkforward sibling lifts identically; run/mc --trace stay
refused by design (no campaign context / no family-name concept — their
help was already honest). Clap help lines and the chart NotFound hint
restored; the old #168 refusal pins re-pinned to the delivered
behaviour.
Post-review repairs: member_trace_key + cell_member_fanout extracted
pure and unit-pinned (6 tests), persisted_cells counts cells again,
cell-invariant space/geo hoisted out of the member loop, verb_sugar
tail comment corrected.
Verified: headline e2e green (GER40 archive), cli_run 124/0, full
workspace suite green, clippy -D warnings clean; independent spec
review compliant, quality review findings all repaired.
closes#224
refs #168
Pins the #224 delivery headline: the #168 refusal lifts and at least
one non-empty tap-series file lands under runs/traces/. Layout-agnostic
on purpose — the per-member key convention is the GREEN slice's call.
refs #224
Editing project src/ without cargo build silently runs the previous
dylib with plausible stale numbers (role-2 authoring-loop footgun).
Pins: source mtime newer than the dylib -> stderr warning naming both
timestamps, run proceeds unchanged (warn, not refuse — a stale run is
still deterministic and manifest-stamped).
refs #237
Five new rostered node types (count-pin 23 -> 28, both the in-crate
shape test and the cross-boundary CLI vocabulary e2e): Const is unary
with an f64 'value' param — the clock input drives it, its value is
ignored, since a zero-input node never evaluates in the total-push
engine — mirroring EqConst's constant-as-param pattern; Div is binary
IEEE-754 (x/0 -> signed inf, 0/0 -> NaN, unit-tested, no error
channel); Abs unary mirroring Sqrt; Max/Min binary pairwise, distinct
from the windowed RollingMax/RollingMin.
Acceptance proof: the committed executable spec composes an RSI-class
gain/loss-split-and-ratio signal purely from blueprint data through
std_vocabulary and runs it to hand-computed RS values — the r_meanrev
constant-folding workaround is no longer forced.
Verified: headline test green, aura-std 163/0, full workspace suite
green (independent mini-verify), clippy -D warnings clean.
closes#236
aura graph <op-script.json> exits 2 mislabeling the valid JSON array as
'blueprint document is not valid JSON'; register/--params already
shape-discriminate via composite_from_any. Pins render parity: op-script
and its built envelope render byte-identically at exit 0.
refs #226
wrap_r hardwired SimBroker::builder(SYNTHETIC_PIP_SIZE); the resolved
geometry-sidecar pip reached only the manifest broker label, so every
real-data run computed total_pips/max_drawdown at 0.0001 regardless of
instrument (GER40 pip=1: inflated 10^4) while the label claimed the
real pip.
wrap_r now takes pip_size. Real-pip call sites pass the resolved pip:
run_signal_r (resolve_run_data reordered before the wrap),
run_blueprint_member, and the campaign trace re-run in
persist_campaign_traces (instrument_geometry lookup mirroring the
run_member refusal — required so the C1 drift alarm compares re-run
and nominee at the same pip). Probe-only call sites (param-space and
axis probes; metrics discarded) keep SYNTHETIC_PIP_SIZE explicitly.
The two golden stitched_total_pips pins in cli_run.rs rescaled by
exactly the 10^4 bug factor while n_trades/expectancy_r stayed
byte-identical — confirming R metrics were never pip-contaminated.
Alternative rejected: dropping pip-denominated metrics from real-data
reports — the manifest already stamps the resolved pip, so running the
graph at it restores label truthfulness without shrinking the report.
Verified: full workspace suite green (0 failed incl. the real-data e2e
suite), clippy -D warnings clean.
closes#232
Two failing e2e pins (skip-on-no-data, GER40 archive present here):
- reproduce_real_sweep_family_re_derives_bit_identically: a --real sweep
family must reproduce N/N bit-identically; currently 0/N DIVERGED because
reproduce_family re-runs members over DataSource::Synthetic.
- reproduce_real_walkforward_family_does_not_panic: a --real WalkForward
family must yield a verdict or a clean refusal; currently panics at the
empty-OOS-window expect (main.rs:1123) for the same reason.
refs #229
The two open examples now expose their author-intended single knobs — the
closed builders always bound these pairs to one value by hand; the open forms
falsely offered them as independent axes:
- r_breakout_open: channel_hi.length + channel_lo.length -> channel_length
(the Donchian channel is structurally ONE parameter)
- r_meanrev_open: mean_window.length + var_window.length -> window; the band
factor stays an independent axis
Regenerated via the emitters (never hand-edited); the closed examples are
byte-unchanged. The carve builders gang in the open branch only.
Test migration: the two axis-namespace pins, five --real e2e invocations
(single gang axis, the 10,20 diagonal for the campaign pair — the mismatched
20,40/10,20 grid was exactly the configuration space the gang retires), and
the param_stability row counts (4 -> 3). The r-sma walkforward golden anchor
is untouched (it never swept a ganged pair).
Docs: authoring-guide gains the seventh op + the third param state
(open/bound/ganged) + the gang wrap note; README op list + Axis concept;
glossary gang entry; ledger C24 records the gang verb and the pre-ship
Tier-2 dormancy (no format-version bump while no out-of-repo reader exists).
Verified: full workspace suite 1104/0 (--real e2e included, local data
present), clippy -D warnings clean, cargo doc clean; live acceptance:
introspect --params prints channel_length:I64 alone / window:I64 +
band.factor:F64.
closes#61
Builder: NodeHandle::param -> ParamRef, GraphBuilder::gang, build() resolves
the gang table (collect-then-reject, BuildError::{UnknownParam,AmbiguousParam,
BadGang}) and terminates in Composite::with_gangs — the same predicate the
op-script and serde boundaries use. C24 lockstep extended: an op-script gang
replay serializes byte-identically to the GraphBuilder twin (FlatGraph edges +
sources + canonical JSON).
Render: model_to_json emits a per-scope gangs fragment (name, kind, [node,pos,
"param"] members) for ganged scopes only — the gang-free model stays
byte-identical (model_golden untouched). graph-viewer.js annotates a ganged
open param with its public knob name; guarded by a viewer_gang_param.{mjs,rs}
pair RED-first per the established viewer_bound_param convention (a welcome
extra beyond the plan's file list).
Ratified deviation: the model's kind string uses the serde form ("I64") — the
plan's implementation snippet (kind_str -> "i64") contradicted its own test;
resolved toward the test + the blueprint_serde form. Two quality nits held as
principled plan-holds (inline resolution block; the unreachable unwrap_or
placeholder check_gangs refuses first).
Verified: aura-engine lib 264/0 (model_golden, lockstep, builder, carrier all
green), viewer_gang_param 1/0, workspace clippy -D warnings clean. Slice
resumed across a transient API failure via workflow resume (cached prefix).
refs #61
Serde: CompositeData carries an additive-optional gangs section (omitted when
empty — every existing v1 document round-trips byte-identically, NO
format-version bump per the #61 decision; BLUEPRINT_FORMAT_VERSION doc now
records the pre-ship Tier-2 dormancy). project() emits gangs canonically
(members by (node,pos), gangs by first member) so declaration order never
reaches the content id; reconstruct() re-mints through Composite::with_gangs
and a corrupted section refuses as the new typed LoadError::Gang.
strip_debug_symbols blanks gang + member names, keeps (node,pos,kind) — the
identity id stays gang-name-blind and gang-structure-sensitive.
Construction: Op::Gang { as_name, into } with the feed-style two-phase eager
arm (member resolution against the post-bind schemas, kind uniformity,
single-gang membership; OpError::{GangKindMismatch,AlreadyGanged,GangArity});
finish() routes the assembled composite through with_gangs, and gang-name
collisions surface via the existing injectivity gate on the projected space.
CLI: OpDoc::Gang ({"op":"gang","as":…,"into":[…]}), kind_label/From/
format_op_error arms, and format_load_error phrases gang faults as prose
(gang_fault_prose) instead of leaking Debug — a ratified improvement over the
plan bytes.
Held quality nit (principled plan-hold): gang()'s inline collect-then-reject
param resolution is the module's established convention (bind/try_bind); a
shared cross-crate helper is out of this task's scope.
Verified: workspace build clean, aura-engine lib 258/0, aura-cli
graph_construct 48/0 (incl. the new gang-op e2e), clippy -D warnings clean.
refs #61