refactor: extract the member-run recipe into library crates (#295 part 1)

The shell no longer defines what an aura backtest is. Tasks 1-9 of the
shell-boundary cycle — structural extraction, behaviour byte-identical:

- aura-runner (new; the C28 assembly position): input binding (the C26
  module, moved whole), the C1-load-bearing param<->config translators,
  harness assembly (wrap_r / run_signal_r / run_blueprint_member and the
  probe/reopen cluster), axis + risk-regime conventions (bind_axes et
  al.), the campaign family builders + MC guards, reproduce
  (process::exit -> RunnerError, shell remaps to identical bytes), the
  measurement-run orchestration, project loading (Env / cdylib load /
  charter / staleness, moved whole), the coverage gap-walk (deduplicated
  onto interior_gap_months), and DefaultMemberRunner — the public
  implementation of aura_campaign::MemberRunner (ex CliMemberRunner).
  The MemberRunner trait stays in aura-campaign; the column still
  imports no harness/data-binding machinery.
- aura-measurement (new; seeds C28 rung 3): the IcMetrics/IcKey
  vocabulary + information_coefficient, verbatim incl. serde derives
  (#294 duplicate-timestamp semantics move as-is, unresolved).
- aura-backtest: the pure per-run scaffold (point_from_params,
  wf_ms_sizes / fit_wf_ms_sizes, intersect_shared_window).
- shell residue: main.rs / campaign_run.rs keep argv/dispatch,
  argv->document translation, and presentation only;
  walkforward_summary_json_from_reports now calls the public
  aura_engine::param_stability instead of its inline twin.
- worked example (crates/aura-runner/examples/world_member_run.rs): a
  World program runs a member backtest through the library alone.

Held quality findings, adjudicated: the plan's literal pub-visibility
list for binding.rs kept (cosmetic); ~20 refusal sites inside
aura-runner (family/member/measure/translate) still process::exit —
behaviour-identical today, conversion to RunnerError is filed forward
(refs #297); rustfmt line-width drift on re-pathed call sites left for
a repo-wide fmt decision.

Verification: cargo test --workspace green (1471 passed, 0 failed);
cargo clippy --workspace --all-targets -D warnings clean. Byte-identity
is pinned by the untouched shell E2E suites (cli_run, measure_ic,
run_measurement, research_docs, run_refuses_unrunnable_blueprint,
tap_recording — zero assertion edits).

Remaining in this cycle: full-workspace c28_layering + shell-content
check, ledger amendments (C28 assembly position, C25/C14 control-surface
consequence, C26 realization note).

refs #295
This commit is contained in:
2026-07-21 05:20:27 +02:00
parent 2cf4574e33
commit 5c2ac982bc
29 changed files with 5048 additions and 4370 deletions
+62
View File
@@ -0,0 +1,62 @@
//! Capture the git HEAD sha at compile time and expose it as `AURA_COMMIT`, so
//! `member::sim_optimal_manifest`'s `RunManifest.commit` is self-identifying
//! (C8/C18 audit trail: this run = this commit) even when aura-runner is
//! consumed by a downstream World program with no `aura` binary involved.
//! `cargo:rustc-env` is per-compilation-unit (aura-cli's own `build.rs` does
//! not propagate its env to this crate's `option_env!("AURA_COMMIT")`), so
//! aura-runner needs this identical capture rather than inheriting the
//! shell's. When there is no git (e.g. a packaged source tree), `AURA_COMMIT`
//! is left unset and `member.rs`'s `option_env!(...)` fallback to `"unknown"`
//! holds.
//!
//! Zero runtime/build dependency by commitment (C14/C18): we shell out to the
//! `git` already in PATH rather than pulling in a libgit crate. Verbatim twin
//! of `aura-cli/build.rs` (#295) — same repo-root depth (`crates/aura-runner`
//! sits beside `crates/aura-cli`), same capture logic, so the two crates'
//! `AURA_COMMIT` cannot disagree within one build.
use std::path::Path;
use std::process::Command;
fn main() {
// build.rs runs with CWD = the crate dir (crates/aura-runner); the repo
// root, which owns `.git`, is two levels up.
let repo_root = Path::new("../..");
let git_dir = repo_root.join(".git");
if !git_dir.exists() {
// No git: emit nothing; the `"unknown"` fallback in member.rs holds.
return;
}
// Re-run when HEAD moves or its working tree changes so AURA_COMMIT never
// goes stale across commits. `.git/logs/HEAD` grows on every HEAD move
// (commit, checkout, reset), covering branch switches without resolving the
// current branch's ref file by hand.
println!("cargo:rerun-if-changed=../../.git/HEAD");
println!("cargo:rerun-if-changed=../../.git/logs/HEAD");
let head = run_git(repo_root, &["rev-parse", "HEAD"]);
let Some(head) = head else { return };
// Append `-dirty` when the working tree has uncommitted changes, so a run
// built off an unclean tree is not silently attributed to the clean HEAD.
let dirty = run_git(repo_root, &["status", "--porcelain"])
.map(|s| !s.is_empty())
.unwrap_or(false);
let commit = if dirty { format!("{head}-dirty") } else { head };
println!("cargo:rustc-env=AURA_COMMIT={commit}");
}
/// Run `git <args>` in `dir`, returning trimmed stdout on success, `None` on any
/// failure (git missing, non-zero exit, non-utf8). Never panics: a build must
/// not fail because git is unavailable.
fn run_git(dir: &Path, args: &[&str]) -> Option<String> {
let out = Command::new("git").current_dir(dir).args(args).output().ok()?;
if !out.status.success() {
return None;
}
let s = String::from_utf8(out.stdout).ok()?;
Some(s.trim().to_string())
}