Commit Graph

138 Commits

Author SHA1 Message Date
claude 59f547e313 test(cli): RED -- mc synthetic member panic is contained like its siblings
Orchestrator probe after 51096a3: blueprint_mc_family is the one
remaining family builder running members bare — a closed poison
blueprint (SMA length bound 0) through aura mc --seeds 2 exits 101
with a raw panic. RED e2e pins the shared contract: no 101, an
aura: warning: fault line, no panic text or crates/ path, exit 3.
Single-seed escape confirms the bare seam (not the parallelism) is
the cause. refs #278
2026-07-24 00:47:05 +02:00
claude bd6ddf9efe test(cli): RED -- synthetic member panics are contained like the real path
Milestone-fieldtest bug (source-blind run, 2026-07-24): a member fault
on the synthetic walkforward/sweep path escapes as an uncaught panic
(exit 101, no class marker, internal source path leaked) while the
real path contains the identical fault via the #272 boundary as
aura: warning: + exit 3. Cause: the family builders in
aura-runner/src/family.rs run members bare, without the catch_unwind
containment that lives only in aura-campaign/src/exec.rs. Two RED
e2es pin the derived contract (decision log on #278): no exit 101,
a warning-marked fault line, no panic text or crates/ path on stderr,
exit 3 per the C14 partition. refs #278
2026-07-24 00:08:56 +02:00
claude 1102d776df feat(runner, cli): runner-side marker literals + the zero-trade walk-forward notice
Iteration stderr-markers-2, tasks 3-4 of the stderr-class-markers plan.

aura-runner's four continuing-run diagnostics join the class grammar as
hand-written literals (the #295 boundary keeps the macro owner in the
presentation crate): the stale-dylib warning gains the `aura: ` namespace,
and the three tap/no-nominee notices become `aura: note: ` — the latter
two extracted into pure note-builder fns with unit pins. The two e2e
pins on the cost-model tap string moved with the retag
(research_docs.rs). A new project_nodes e2e exercises the real stale-
dylib load path: fresh build emits nothing, a source newer than the
dylib emits exactly one class-marked warning and still succeeds.

The #313 notice ships on both walk-forward paths: `aura: note: all N
walk-forward windows recorded zero trades` before the summary JSON,
exit 0 untouched (a null result is a valid research result, #198).
Wording and condition live in one shared diag helper taking the
per-window trade counts (orchestrator tidy of the reviewer's
duplicated-predicate residue); the call sites keep only the
path-specific field extraction. Three e2es pin it: the equal-length
degenerate on the synthetic path (exactly one note, no warnings, exit
0, summary unchanged), a traded negative twin, and the real/sugar-path
degenerate gated on the GER40 archive.

refs #278, refs #313
2026-07-23 23:35:23 +02:00
claude 750ee93180 feat(research, registry, cli, runner): C29 register seam -- description gate, store-boundary doc gate, authored root docs
Iteration 3 of the self-description plan, tasks 1-3 (checkpoint commit
mid-iteration; the register e2e, the op-script doc slot, and the full
gates follow):

- aura-research: DocFault::BadDescription { subject, fault }; a present
  document description passes doc_gate in validate_process /
  validate_campaign (absent is never a fault and stays byte-identical);
  prose arm in doc_fault_prose; tests incl. content-id participation.
- aura-registry: the store boundary gates admission -- the public
  put_blueprint parses the canonical bytes and walks root + named
  nested composite docs (None refuses like ""), refusing via
  UndescribedComposite { name, fault } / MalformedBlueprint; the raw
  write survives as private put_blueprint_unchecked (store-mechanics
  tests + the never-retroactive witness: doc-less pre-C29 entries stay
  readable). Registry-internal seeders triaged junk-vs-real.
- authored root docs: the 4 example blueprints + their 4 open-fixture
  twins, the scaffold starter template, the scaled_signal and hl_signal
  inline fixtures, and the three programmatic member.rs builders
  (breakout, meanrev, hl_channel) via GraphBuilder::doc -- mechanical
  propagation of the authored lines.

Recovery note: the implement run's task 3 discarded tasks 1+2's edits
in three files via a file-level checkout (the issue-#23 guard shape);
the loop's boundary snapshot caught it and the content was restored
from recovery snapshot 11a6ae2, then re-verified (research 81 green,
registry 51 green, cli 58/59).

Known-open at this checkpoint: the op-script register test is red --
op-built composites have no doc surface (#125 scope cut); the op
vocabulary grows the anticipated doc slot in the next task of this
iteration (decision logged on the issue).

refs #316
2026-07-23 19:47:46 +02:00
claude 6b994cead3 test: red for show resolving a unique content-id prefix (refs #302)
aura {process|campaign} show is exact-match-only while every surface
prints 8-hex prefixes and reproduce has resolved prefixes since #298;
the fieldtest's prefix paste (df_4) hits the same flat not-found prose
as a genuinely-unknown id. The E2E test pins the wanted headline: a
unique 8-hex prefix of a registered id prints byte-identically what the
full-id show prints (#164 byte discipline), both verbs. The ambiguity
refusal is deliberately left to a unit test at the resolution seam —
hash-derived ids make a shared-prefix E2E fixture impractical.
2026-07-21 15:03:56 +02:00
claude 757e3ac1bd feat: the document-first surface closes — show read-back, typed stops, bare plateau
The #300 keystone cycle: the residual run-quintet flags were already
document vocabulary on the --real arm (#210/#220); this closes the loop
and ratifies the surface.

- `aura process|campaign show <content-id>` prints a registered
  document's canonical bytes (print!, no framing — #164), so
  generate -> retrieve -> hand-extend -> re-register needs no direct
  store filesystem access. Refusals reuse the store-hint prose, exit 1.
- walkforward/mc --stop-length/--stop-k become clap-typed i64/f64
  (generalize's form); stop_knob_or and its parse_csv_list helper are
  deleted (multi-value/invalid input is now clap's exit-2 rejection —
  the two dissolved-verb refusal tests retarget, the type-dead unit
  test is deleted).
- --select accepts bare `plateau` as the documented default
  plateau:mean (#227 carry); document schema untouched — the two
  spellings generate content-id-identical campaigns (test-pinned).
- family.rs's quadruplicated demo stop literal collapses onto one
  DEFAULT_STOP const (maintainer-lens drift finding).
- Ledger: C25's "which projection next" line resolved (document-first
  delivered, per-verb identity re-ratified — F8; host/MCP stay
  demand-driven); C18 records the read-back.

Design triage, fork decisions F1-F8, and the auto-signed spec live on
the reference issue. Verification: full workspace suite 1482 passed /
0 failed with the real GER40 archive exercised (the byte-identity
round-trips ran, not skipped); clippy -D warnings clean.

closes #300
2026-07-21 12:50:10 +02:00
claude 0651e16146 feat: the IC becomes the second metric vocabulary; rosters single-sourced
The A1 cut of #147 item 2, part 2 of 2 (the consumers), completing what
part 1 (the MetricVocabulary substrate) set up:

- aura-campaign's RANKABLE_METRICS hand-copy becomes a re-export of the
  backtest vocabulary's roster; the #190 guard test extends to pin the
  roster nesting (rankable == vocabulary roster, rankable within
  per-member, per-member within the research vocabulary) and the legacy
  exposure_sign_flips alias, so any drift fails a test loudly. The
  research and per-member rosters themselves stay hand-copies by ratified
  isolation (#190 accepted residual) — now oracle-pinned instead of trusted.
- aura-cli gains IcMetrics + IcKey implementing MetricVocabulary: the
  second PRODUCTION implementor. The standalone `aura measure ic`
  reduction now computes its permutation p via the shared null_draw +
  one_sided_p_laplace building blocks (independent draws instead of
  chained shuffles — statistically equivalent, deterministic per seed;
  emitted p values may differ in the last digits from #290's).
- Acceptance proven at the registry boundary: an engineered
  SweepFamily<IcMetrics> deflates through optimize_deflated to a LOW
  overfit probability and a noise family to a HIGH one (the permutation
  null, dispatched by the vocabulary — the deflation machinery is no
  longer baked-in R-only); same seed reproduces the identical
  FamilySelection; cross-vocabulary names are refused with the family's
  own roster in the prose (the C10 guard). A new cli_run E2E exercises
  the legacy alias through the real `aura runs family … rank` boundary.

Deviation from the plan, forced by the compiler: IcMetrics additionally
derives Debug (unwrap_err on the deflation Result needs it; mirrors
RunMetrics).

Verification: full workspace suite green (no FAILED, no compile errors),
clippy -D warnings clean, C18 goldens (cli_run, measure_ic, c28_layering)
green unchanged. A2 — measurement runs as sweep-family citizens — stays
deliberately deferred until a concrete campaign demand exists.

closes #147
2026-07-20 19:55:24 +02:00
claude b39fd63396 refactor: split the aura-std roster into C28 layer crates
Phase 4 of the Stratification milestone. aura-std held four C28 ladder
layers in one roster; this cuts them into layer-aligned, aura-core-only
node crates so the import direction is enforced by the crate graph:

- aura-std        — engine nodes only (arithmetic/logic/rolling + sinks)
- aura-market     — session, resample
- aura-strategy   — bias, stops, sizer, cost-model machinery
- aura-backtest   — sim_broker, position_management
- aura-vocabulary — the relocated closed std_vocabulary roster

Node modules move verbatim (byte-identical renames); consumers are
rewired by import path only. A new structural test
(aura-vocabulary/tests/c28_layering.rs) asserts each node crate's
[dependencies] stay within its C28-permitted inner set, catching the
acyclic-but-outward violation the compiler misses.

Behaviour byte-identical: full workspace suite green (1448 tests), no
golden edited, clippy -D warnings clean. C28 Status block updated.

closes #288
2026-07-19 20:28:20 +02:00
claude 9e30805fcc feat(cli,ledger): supply CLI run sources by role key; ledger note
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.
2026-07-15 19:52:39 +02:00
claude ea4e79d73f feat(research,campaign,cli): std::grid — the enumerate-only leading stage
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).
2026-07-14 13:07:40 +02:00
claude bdafbdef59 fix(cli): unwrap the walkforward UnknownKnob Debug frame into bare prose
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
2026-07-13 23:28:25 +02:00
claude 3d422341eb test: red executable-spec for #269 — walkforward unknown-axis rejection as bare prose
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
2026-07-13 23:17:59 +02:00
claude ce12e7f976 test: red executable-spec for #247 — sweep-boundary bind errors render as prose
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
2026-07-13 22:35:03 +02:00
claude 5a1696891a test: red executable-spec for #266 — aura --version carries the engine commit
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
2026-07-13 22:11:03 +02:00
claude a7d465d2b5 test: red executable-spec for #264 cut 2 — aura data list enumerates symbols
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
2026-07-13 21:52:32 +02:00
claude 28984ba73c test: red executable-spec for #264 cut 1 — aura data coverage reports month gaps
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
2026-07-13 21:28:58 +02:00
claude 1ccce9ad32 fix(test): anchor all test sandboxes on reclaimable pid-free names
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
2026-07-13 20:15:31 +02:00
claude a895891ab1 perf(ingest,cli): derive archive bounds from the monthly file index — probe_window drain retired
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
2026-07-13 16:47:57 +02:00
claude c31e946bb7 test(cli): synthetic per-test M1 archive — no-window generalize tests go hostless
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
2026-07-13 16:44:28 +02:00
claude 25b8354959 test(cli): per-test project dirs via a [nodes] pointer — project_lock retired
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
2026-07-13 16:43:16 +02:00
claude 038e1702b3 test(cli): bound three whale E2E windows instead of streaming full archives
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
2026-07-13 16:42:18 +02:00
claude 1ebb94c1b8 feat(engine,cli): run manifests stamp untouched bound defaults (closes #249)
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.
2026-07-13 14:20:23 +02:00
claude 4e6d5d4688 test(cli): RED — run manifest stamps untouched bound defaults (refs #249)
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.
2026-07-13 13:41:21 +02:00
claude 487431d97d test(cli): migrate the _open references — closed twins for sweeps, fixtures for open semantics (closes #248)
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.
2026-07-13 13:36:31 +02:00
claude 9d4e4c7897 audit: bound-override cycle close — drift-clean after one comment fix
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
2026-07-13 04:40:16 +02:00
claude e902a0f31d feat(cli,registry): bound-param axis overrides reach the campaign/--real trunk
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
2026-07-13 03:37:54 +02:00
claude 3f4c9478bf feat(cli): sweep axes override bound params on the blueprint verb paths
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
2026-07-13 02:03:14 +02:00
claude 394ac5e0ae test(cli): RED — empty --real window refusal misnames the symbol (#242)
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
2026-07-12 18:07:42 +02:00
Brummel 2532e8fcc3 test(cli): extract the shared demo-project fixture helpers into tests/common (#223)
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
2026-07-11 18:18:27 +02:00
Brummel 18f8e72946 feat(cli): chart resolves a --trace family by its chosen campaign name (#238)
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
2026-07-11 18:04:09 +02:00
Brummel c70b4cc2d2 test(cli): RED — chart cannot resolve a --trace family by its chosen name (#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
2026-07-11 17:20:14 +02:00
Brummel a081c14532 test(cli): RED — mc --real refuses a short window instead of fitting the roller (#239)
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
2026-07-11 16:24:33 +02:00
Brummel abb6fbaa87 test(cli): RED — chart cannot open a sweep --trace family by its printed handle
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
2026-07-11 14:27:28 +02:00
Brummel d3ad9f4e73 feat(cli): generalize no-window fallback resolves the intersection of all symbols' windows
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
2026-07-11 14:02:41 +02:00
Brummel bb8c01895b test(cli): RED — no-window generalize resolves symbols[0], not the intersection
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
2026-07-11 13:14:03 +02:00
Brummel 7748b53a10 feat(cli,docs): the r_channel OHLC example + ledger amendment C26 (#231 tasks 6-7)
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
2026-07-11 05:10:05 +02:00
Brummel 1d14ebc1cd feat(cli,engine): multi-column opening on every real-data path (#231 task 4)
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
2026-07-11 03:37:14 +02:00
Brummel 00e15b9371 feat(cli): sweep/walkforward --trace writes every member's tap series (#168 refusal lifted)
--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
2026-07-10 22:16:09 +02:00
Brummel fe4ea0a08d test(cli): RED — sweep --real --trace refuses instead of writing tap series
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
2026-07-10 21:13:52 +02:00
Brummel ed1e8a0d10 fix(cli): thread the resolved pip into wrap_r so real-data runs hit the graph broker
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
2026-07-10 18:28:42 +02:00
Brummel 0408fc41a2 test(cli): RED — reproduce over real-data families diverges/panics (#229)
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
2026-07-10 14:47:23 +02:00
Brummel fc9cf23b87 feat(cli,docs): ship the ganged open examples + document the gang construct (#61 tasks 7-8)
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
2026-07-10 14:07:22 +02:00
Brummel 692760d3b0 test(cli): RED — aura graph with a bad blueprint arg must be a usage fault (#28)
The #28 cycle-close audit found the just-shipped `aura graph <file>` silently
renders the embedded sample at exit 0 when the positional names an unreadable
blueprint (typo, nonexistent path, wrong extension): is_blueprint_file returns
None both for "no positional" (the legit sample default) and for a bad arg, and
dispatch_graph's None arm conflates them — re-introducing the exact #28 "I asked
for my graph, got the sample" surprise in the error path. This pins the fix: a
named-but-unreadable arg exits 2 like its siblings, while bare `aura graph` still
renders the sample.

refs #28
2026-07-10 03:46:30 +02:00
Brummel 7cc986486f test(cli): RED — aura graph must render a consumer blueprint, not the sample (#28)
aura graph is the last verb still sample-bound: its no-subcommand arm always
renders the embedded r_sma_open.json, ignoring any blueprint path, while
run/sweep/mc/walkforward/generalize consume a consumer blueprint file via
is_blueprint_file. This pins the target: aura graph <blueprint.json> renders
that file's own graph (its inlined model reflects the file's nodes, e.g.
r-breakout's channel_hi), not the embedded sample.

refs #28
2026-07-10 03:14:44 +02:00
Brummel 94cdbf90cc feat(campaign,cli): permit --select plateau in the walk_forward stage (#215)
The campaign walk_forward stage picked its per-window in-sample winner by
optimize_deflated (argmax) unconditionally, dropping the stage's `select`
field, and both the preflight and the `walkforward` verb's `--real` campaign
sub-branch refused plateau:* with a forward-pointer here (the #210 decision-4
deferral; the synthetic non-`--real` sub-branch has accepted plateau since
#173). Honour plateau selection when no gate precedes the wf stage.

- Preflight: the plateau-in-wf refusal is narrowed to gate-preceded stages.
  With no preceding gate the survivors are the full sweep grid, so the
  parameter lattice (grid.axis_lens) is exact and optimize_plateau is sound; a
  gate filters survivors below the grid, breaking the lattice, so plateau after
  any gate stays refused. The refusal is static — document validity must be
  data-independent (a gate's filtering depends on the data) — and keeps the
  existing "a gated survivor subset has no parameter lattice" message, which now
  describes exactly the only case it fires.
- Executor: run_walk_forward_stage threads the per-cell grid.axis_lens and picks
  each window's in-sample winner via select_sweep_winner — the same dispatch the
  sweep stage uses. (Argmax, deflate=true) is byte-identical to the old
  optimize_deflated call, so the argmax grade and content-id pins are unchanged;
  the plateau arms read axis_lens and ignore deflate.
- CLI: the walkforward verb threads --select through translate_walkforward /
  run_walkforward_sugar (a walkforward-local param, not the shared four-verb
  SugarInvocation), mapping the parsed Selection to the research SelectRule via
  select_rule_of; the refusal and its select_is_plateau helper are deleted.
  Scope is walkforward-only — the mc verb's wf-stage select is untouched.

The plateau:worst grade anchor (stitched_total_pips = -9683776.67) is distinct
from the argmax anchor (-10398606.67), proving the plateau path genuinely
changes winner selection rather than falling back to argmax. A gate-free plateau
process validates clean while a gate-preceded one is refused (a new
campaign-validate e2e). Argmax defaults stay byte-identical.

closes #215
2026-07-10 03:05:35 +02:00
Brummel abbfbdc208 fix(cli): refuse --trace on sweep/walkforward instead of silently dropping it (#168)
The clap help promised "persists each member's/OOS window's taps" and the chart
NotFound hint recommended `aura sweep --trace`, but every blueprint-mode arm
discarded the flag (run_blueprint_sweep's `let _ = persist`, the --real sugar's
persist_taps: vec![]) — the surface advertised trace-recording the code dropped.
Refuse --trace up front on both verbs (exit 2, "--trace is not yet available on
<verb>; see #224"), mirroring the existing aura run / aura mc refusals; drop the
false promise from the two help lines and the chart NotFound hint. Delivering
per-member trace-writing is deferred to #224.

Collateral: four synthetic-sweep tests that used --trace as a family-name handle
move to --name (same handle, still accepted); the walkforward name/trace test now
pins the up-front #224 refusal.

Workspace suite green (62 groups / 0 failed), clippy -D warnings clean.

refs #168
2026-07-09 23:47:32 +02:00
Brummel 53bbcedeec test(cli): RED — sweep/walkforward must refuse --trace with a #224 pointer (#168)
The clap help promises "persists each member's taps" and the chart hint
recommends `aura sweep --trace`, but every blueprint-mode arm discards the flag
(run_blueprint_sweep's `let _ = persist`, the --real sugar's persist_taps: vec![])
— the surface advertises trace-recording the code drops. Pin the decided honest
behaviour: sweep/walkforward refuse --trace up front (exit 2, naming the flag and
pointing forward to the deferred trace feature #224), mirroring the aura run / mc
refusals.

refs #168
2026-07-09 23:04:12 +02:00
Brummel f6d80486cd fix(cli): gate dissolved verbs on project presence, matching campaign run (#218)
A dissolved verb (sweep/generalize/walkforward/mc --real) run outside a project
silently created a content-addressed runs/ store in the cwd and ran to
completion — Env::runs_root falls back to a relative ./runs with no project, and
the four dispatchers skipped the provenance gate that `campaign run` applies
first (campaign_run.rs:322-333). It is now refused up front: one gate in the
shared validate_and_register_axes chokepoint, after axis validation and before
the first store touch (put_blueprint), via a new AxisRegisterError::NoProject
mapped to exit 1 carrying "<verb> needs a project …".

Placement is load-bearing: axis validation (an UnknownAxis usage error, exit 2)
stays ahead of the project gate, so a malformed --axis still exits 2 regardless
of project — a malformed command is malformed in any environment and already
writes no store. Four per-verb pins assert the refusal (exit 1, "needs a
project", no store); the six exit-2 usage-refusal tests stay green.

Workspace suite green (62 groups / 0 failed), clippy -D warnings clean.

closes #218
2026-07-09 22:40:48 +02:00
Brummel 2162bd6d97 test(cli): migrate dissolved-verb e2e tests into the project fixture (#218 prep)
The four dissolved verbs (sweep/generalize/walkforward/mc --real) are about to
gain a project gate that refuses outside a project (#218). 25 cli_run.rs tests
today run a well-formed verb to completion in a bare cwd and pin its grade /
store effect — i.e. they pin the pre-gate behaviour. Relocate them into the
built project fixture (fresh_project() over the demo-project cdylib, serialized,
store reset per test) so they still exercise the verb path once the gate lands.
Grades are byte-identical in-project (the fixture Aura.toml overrides no
instrument or data path — DEFAULT_DATA_PATH is absolute), verified against the
exact pins; local run 113 passed / 0 failed / 0 skipped with archive data.

Two new tests pin that the in-project run stamps manifest.project provenance
(the sweep chokepoint and generalize's run_blueprint_member path), a path no
cli_run.rs test previously exercised. The built_project/project_lock helpers are
duplicated into this binary to keep the change inside cli_run.rs; a shared
tests/common extraction is a separate hygiene item.

refs #218
2026-07-09 21:17:45 +02:00
Brummel 210230596e feat(cli): default the stop regime on walkforward/mc/generalize
GREEN side of the #217 executable-spec: --stop-length/--stop-k are now
optional on the three dissolved verbs; each missing flag independently
defaults to the single-sourced regime constants (length 3, k 2.0) at
the argv-boundary extractors — the same regime the campaign member
runner resolves for an unbound regime. The generated campaign document
still binds the regime explicitly, so the default spelling is
content-identical to an explicit --stop-length 3 --stop-k 2.0 (pinned
by registry dedup in the headline test). The multi-value stop refusal
survives byte-identically; sweep is untouched.

Sanctioned contract migrations (decision on the issue, 2026-07-09):
walkforward_dissolved_refuses_missing_knobs now pins the surviving
multi-value refusal; mc_args_from_refuses_missing_knobs pins the
default path. Usage strings name the defaults.

Held quality nit (deferred to cycle close, minimal-slice constraint):
the None=>default / Some=>parse stop block recurs 4x across the
walkforward/mc extractors.

closes #217
2026-07-09 16:38:05 +02:00