fix(cli): reproduce re-derives real-data families from the member manifest (#229)
reproduce_family no longer hardcodes DataSource::Synthetic: it reads the family's instrument + window from the persisted manifest and rebuilds the same local-archive source the dissolved verbs run over; a missing archive refuses with the verbs' established named-data refusal (exit 1) instead of silently re-running over wrong data. Sweep families now reproduce N/N bit-identically; WalkForward families yield a verdict instead of the empty-OOS-window panic. The synthetic-family path is byte-unchanged. Accepted minimal duplication: reproduce_family performs its own family lookup to read the instrument before delegating to reproduce_family_in, whose signature and unit-test seam stay byte-identical (constraint: minimal fix). MonteCarlo reproduce still runs the synthetic seed walk regardless of a real manifest — pre-existing, semantically distinct (bit-identity over bootstrap resamples), filed forward. Verified: both #229 RED tests green over the GER40 archive, full workspace suite 1107/0, clippy -D warnings clean. closes #229
This commit is contained in:
+48
-16
@@ -24,7 +24,7 @@ use aura_engine::{
|
||||
};
|
||||
use aura_registry::{
|
||||
check_r_metric, group_families, mc_member_reports, optimize_deflated,
|
||||
optimize_plateau, rank_by, sweep_member_reports, walkforward_member_reports, FamilyKind,
|
||||
optimize_plateau, rank_by, sweep_member_reports, walkforward_member_reports, Family, FamilyKind,
|
||||
FamilyMember, Generalization, NameKind, PlateauMode, Registry, RunTraces,
|
||||
DEFLATION_BLOCK_LEN, DEFLATION_N_RESAMPLES,
|
||||
};
|
||||
@@ -1031,6 +1031,22 @@ fn point_from_params(space: &[ParamSpec], params: &[(String, Scalar)]) -> Vec<Ce
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Look up a persisted family by id, or exit 1 (unknown id / registry load failure) —
|
||||
/// the single place `reproduce_family` and `reproduce_family_in` resolve a family, so
|
||||
/// the two exit-1 error phrasings can't drift out of sync between the call sites.
|
||||
fn load_family_or_exit(reg: &Registry, id: &str) -> Family {
|
||||
let members = reg.load_family_members().unwrap_or_else(|e| {
|
||||
eprintln!("aura: {e}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
group_families(members).into_iter().find(|f| f.id == id).unwrap_or_else(|| {
|
||||
// reproduce is an action, not a lookup: an unknown id is a hard error (distinct
|
||||
// from `runs family <id>`'s treat-as-empty exit 0).
|
||||
eprintln!("aura: no such family '{id}'");
|
||||
std::process::exit(1);
|
||||
})
|
||||
}
|
||||
|
||||
/// Re-derive every member of a persisted sweep family from the content-addressed store
|
||||
/// and compare to the stored result, against an explicit registry (testable seam).
|
||||
fn reproduce_family_in(
|
||||
@@ -1039,18 +1055,8 @@ fn reproduce_family_in(
|
||||
data: &DataSource,
|
||||
env: &project::Env,
|
||||
) -> ReproduceReport {
|
||||
let members = reg.load_family_members().unwrap_or_else(|e| {
|
||||
eprintln!("aura: {e}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
let Some(family) = group_families(members).into_iter().find(|f| f.id == id) else {
|
||||
// reproduce is an action, not a lookup: an unknown id is a hard error (distinct
|
||||
// from `runs family <id>`'s treat-as-empty exit 0).
|
||||
eprintln!("aura: no such family '{id}'");
|
||||
std::process::exit(1);
|
||||
};
|
||||
let family = load_family_or_exit(reg, id);
|
||||
let pip = data.pip_size();
|
||||
let window = data.full_window(env);
|
||||
let mut outcomes = Vec::new();
|
||||
for member in &family.members {
|
||||
let stored = &member.report;
|
||||
@@ -1123,7 +1129,19 @@ fn reproduce_family_in(
|
||||
let w = window_of(&s).expect("non-empty OOS window");
|
||||
(s, w)
|
||||
}
|
||||
_ => (data.run_sources(env), window),
|
||||
// Sweep / plain-run members: a Real source reopens the exact per-member
|
||||
// window the manifest recorded, the same `windowed_sources` loader
|
||||
// WalkForward uses above (and `CliMemberRunner` uses at mint time, #229)
|
||||
// — never a fresh full-archive probe, which need not match the window the
|
||||
// family was minted over. Synthetic keeps the pre-#229 full-window path
|
||||
// (byte-identical: `data.full_window` is a pure, no-IO computation).
|
||||
_ => match data {
|
||||
DataSource::Real { .. } => {
|
||||
let (from, to) = stored.manifest.window;
|
||||
(data.windowed_sources(from, to, env), (from, to))
|
||||
}
|
||||
DataSource::Synthetic => (data.run_sources(env), data.full_window(env)),
|
||||
},
|
||||
};
|
||||
let rerun = run_blueprint_member(
|
||||
reload(),
|
||||
@@ -1144,10 +1162,24 @@ fn reproduce_family_in(
|
||||
|
||||
/// `aura reproduce <family-id>`: re-derive a persisted sweep family from the
|
||||
/// content-addressed blueprint store and verify each member reproduces bit-identically
|
||||
/// (C18 "re-derives full results on demand"). Synthetic data this cycle (deterministic);
|
||||
/// recorded-dataset reproduction rides the DataServer seam (#124).
|
||||
/// (C18 "re-derives full results on demand"). The data source is reconstructed from
|
||||
/// the family's own manifest (#229) — a hardcoded `DataSource::Synthetic` here would
|
||||
/// re-derive a real-data family over the wrong stream; see `reproduce_family_in`'s
|
||||
/// per-member window loader for how the reconstructed source is actually used.
|
||||
fn reproduce_family(id: &str, env: &project::Env) {
|
||||
let rep = reproduce_family_in(&env.registry(), id, &DataSource::Synthetic, env);
|
||||
let reg = env.registry();
|
||||
let family = load_family_or_exit(®, id);
|
||||
// Reconstruct the DataSource the family was minted over: `None` instrument
|
||||
// (every synthetic-family member, pre-#229 lines) stays the built-in synthetic
|
||||
// stream; a real instrument reopens the same local archive `--real` runs use,
|
||||
// via the same named-data refusal (exit 1) on a missing sidecar/archive.
|
||||
let data = match family.members.first().and_then(|m| m.report.manifest.instrument.clone()) {
|
||||
None => DataSource::Synthetic,
|
||||
Some(symbol) => {
|
||||
DataSource::from_choice(DataChoice::Real { symbol, from_ms: None, to_ms: None }, env)
|
||||
}
|
||||
};
|
||||
let rep = reproduce_family_in(®, id, &data, env);
|
||||
let total = rep.outcomes.len();
|
||||
let ok = rep.outcomes.iter().filter(|(_, b)| *b).count();
|
||||
for (label, identical) in &rep.outcomes {
|
||||
|
||||
Reference in New Issue
Block a user