feat(cli): annotator-stage seam flips + doc tidy + gated e2e with the mc suffix (0108 task 5)
The retired v1-boundary test is replaced by v2 pins: mc-before-wf refuses with the executor shape prose (while process validate accepts the same doc — the tier boundary observable across two commands, added by the task-2 e2e phase and kept), single-instrument generalize refuses data-free on the built fixture project, and the two addressing-mode tests' stale rationale comments now name the member-data seam they actually hit. PROCESS_BLOCKS doc strings drop 'in v1' (introspection text only — no content id moves). The gated real-data e2e extends its process with the monte_carlo suffix and asserts the pooled_oos bootstrap in the final campaign_run line — ran its full assert path on this host. Task-5 duplicates the plan had double-drafted (task 2's e2e phase pre-landed three of its tests) were correctly NOT re-added. Gates: workspace 1025/0 (fully green again), clippy -D warnings clean. refs #200
This commit is contained in:
@@ -644,8 +644,9 @@ fn seed_blueprint(dir: &Path, name: &str) -> String {
|
||||
}
|
||||
|
||||
/// Register `doc` as a process document in the project store; returns its id.
|
||||
/// Asserts register exits 0 — an intrinsically valid document (an mc-bearing
|
||||
/// one included) must always register; only `campaign run` draws the v1 line.
|
||||
/// Asserts register exits 0 — an intrinsically valid document (annotator
|
||||
/// stages in any order included) must always register; only `campaign run`'s
|
||||
/// preflight draws the executable-shape line.
|
||||
fn register_process_doc(dir: &Path, file: &str, doc: &str) -> String {
|
||||
write_doc(dir, file, doc);
|
||||
let (out, code) = run_code_in(dir, &["process", "register", file]);
|
||||
@@ -690,8 +691,10 @@ fn campaign_doc_json(
|
||||
)
|
||||
}
|
||||
|
||||
/// An mc-bearing process: intrinsically VALID (register accepts it) but past
|
||||
/// the executable v1 boundary (`campaign run` refuses it at preflight).
|
||||
/// An mc-bearing process: intrinsically valid AND (since the v2 executor) an
|
||||
/// executable pipeline shape — `sweep -> monte_carlo` annotates each sweep
|
||||
/// survivor. The two addressing-mode tests below run it over the [1, 2] 1970
|
||||
/// window, so they refuse deterministically at the member-data seam.
|
||||
const MC_PROCESS_DOC: &str = r#"{
|
||||
"format_version": 1,
|
||||
"kind": "process",
|
||||
@@ -702,7 +705,38 @@ const MC_PROCESS_DOC: &str = r#"{
|
||||
]
|
||||
}"#;
|
||||
|
||||
/// The minimal executable v1 pipeline (one sweep stage).
|
||||
/// Intrinsically valid but NOT an executable v2 shape: the annotator sits
|
||||
/// before the population stage (`walk_forward` after `monte_carlo`). The
|
||||
/// intrinsic tier deliberately permits this order (the shipped
|
||||
/// `validate_process_permits_walk_forward_after_an_annotator` pin in
|
||||
/// aura-research), so register accepts it; only `campaign run`'s preflight
|
||||
/// refuses it.
|
||||
const MC_BEFORE_WF_PROCESS_DOC: &str = r#"{
|
||||
"format_version": 1,
|
||||
"kind": "process",
|
||||
"name": "mc-before-wf",
|
||||
"pipeline": [
|
||||
{ "block": "std::sweep", "metric": "sqn_normalized", "select": "argmax" },
|
||||
{ "block": "std::monte_carlo", "resamples": 100, "block_len": 5 },
|
||||
{ "block": "std::walk_forward", "in_sample_ms": 1209600000, "out_of_sample_ms": 604800000,
|
||||
"step_ms": 604800000, "mode": "rolling", "metric": "sqn_normalized", "select": "argmax" }
|
||||
]
|
||||
}"#;
|
||||
|
||||
/// An executable generalize-bearing shape (`sweep -> generalize`). The
|
||||
/// refusal under test is the campaign-side static guard (std::generalize
|
||||
/// needs >= 2 instruments), not the pipeline shape.
|
||||
const GENERALIZE_PROCESS_DOC: &str = r#"{
|
||||
"format_version": 1,
|
||||
"kind": "process",
|
||||
"name": "gen-screen",
|
||||
"pipeline": [
|
||||
{ "block": "std::sweep", "metric": "sqn_normalized", "select": "argmax" },
|
||||
{ "block": "std::generalize", "metric": "net_expectancy_r" }
|
||||
]
|
||||
}"#;
|
||||
|
||||
/// The minimal executable pipeline (one sweep stage).
|
||||
const SWEEP_ONLY_PROCESS_DOC: &str = r#"{
|
||||
"format_version": 1,
|
||||
"kind": "process",
|
||||
@@ -710,10 +744,14 @@ const SWEEP_ONLY_PROCESS_DOC: &str = r#"{
|
||||
"pipeline": [ { "block": "std::sweep", "metric": "sqn_normalized", "select": "argmax" } ]
|
||||
}"#;
|
||||
|
||||
/// The full v1 shape for the gated e2e: sweep -> gate -> walk_forward. The
|
||||
/// gate (`n_trades ge 0`) passes every member, so walk-forward always has
|
||||
/// survivors. Roller: 14d IS / 7d OOS / 7d step in epoch-ms, tiling the
|
||||
/// ~30-day GER40 Sept-2024 campaign window.
|
||||
/// The full annotated shape for the gated e2e: sweep -> gate -> walk_forward
|
||||
/// -> monte_carlo. The gate (`n_trades ge 0`) passes every member, so
|
||||
/// walk-forward always has survivors; the mc annotator bootstraps the pooled
|
||||
/// per-window OOS trade-R series (200 resamples, block 5). Roller: 14d IS /
|
||||
/// 7d OOS / 7d step in epoch-ms, tiling the ~30-day GER40 Sept-2024 campaign
|
||||
/// window. NO generalize stage: this campaign has ONE instrument (a second
|
||||
/// local archive is not guaranteed), and the single-instrument refusal is
|
||||
/// pinned data-free in `campaign_run_refuses_single_instrument_generalize`.
|
||||
const WF_PROCESS_DOC: &str = r#"{
|
||||
"format_version": 1,
|
||||
"kind": "process",
|
||||
@@ -722,7 +760,8 @@ const WF_PROCESS_DOC: &str = r#"{
|
||||
{ "block": "std::sweep", "metric": "sqn_normalized", "select": "argmax" },
|
||||
{ "block": "std::gate", "all": [ { "metric": "n_trades", "cmp": "ge", "value": 0.0 } ] },
|
||||
{ "block": "std::walk_forward", "in_sample_ms": 1209600000, "out_of_sample_ms": 604800000,
|
||||
"step_ms": 604800000, "mode": "rolling", "metric": "net_expectancy_r", "select": "argmax" }
|
||||
"step_ms": 604800000, "mode": "rolling", "metric": "net_expectancy_r", "select": "argmax" },
|
||||
{ "block": "std::monte_carlo", "resamples": 200, "block_len": 5 }
|
||||
]
|
||||
}"#;
|
||||
|
||||
@@ -769,29 +808,130 @@ fn campaign_run_unknown_id_refuses() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The v1 boundary: `process register` ACCEPTS an mc-bearing document (it is
|
||||
/// intrinsically valid — asserted inside `register_process_doc`); only
|
||||
/// `campaign run` refuses it, at preflight, before any member runs (so no
|
||||
/// data is needed), with path-addressed Debug-free prose.
|
||||
/// The v2 boundary, order side: `[sweep, monte_carlo, walk_forward]` is
|
||||
/// intrinsically valid — `process register` ACCEPTS it (asserted inside
|
||||
/// `register_process_doc`; the intrinsic-tier ground is the shipped
|
||||
/// `validate_process_permits_walk_forward_after_an_annotator` pin) — but the
|
||||
/// executable pipeline shape fixes the suffix order (`std::sweep [std::gate]*
|
||||
/// [std::walk_forward]? [std::monte_carlo]? [std::generalize]?`), so
|
||||
/// `campaign run` refuses it at the data-free preflight, before any member
|
||||
/// runs, with Debug-free PipelineShape prose.
|
||||
#[test]
|
||||
fn campaign_run_v1_boundary_refuses_mc_process() {
|
||||
fn campaign_run_refuses_mc_before_walk_forward() {
|
||||
let _fixture = project_lock();
|
||||
let dir = built_project();
|
||||
let runs_dir = dir.join("runs");
|
||||
std::fs::remove_dir_all(&runs_dir).ok();
|
||||
let _cleanup = ScratchGuard(vec![
|
||||
ScratchPath::Dir(runs_dir.clone()),
|
||||
ScratchPath::File(dir.join("mc.process.json")),
|
||||
ScratchPath::File(dir.join("mc.campaign.json")),
|
||||
ScratchPath::File(dir.join("mcwf.process.json")),
|
||||
ScratchPath::File(dir.join("mcwf.campaign.json")),
|
||||
]);
|
||||
let bp_id = seed_blueprint(dir, "campaign-run-mc-seed");
|
||||
let proc_id = register_process_doc(dir, "mc.process.json", MC_PROCESS_DOC);
|
||||
write_doc(dir, "mc.campaign.json", &campaign_doc_json(&bp_id, &proc_id, (1, 2), "", ""));
|
||||
let (out, code) = run_code_in(dir, &["campaign", "run", "mc.campaign.json"]);
|
||||
let bp_id = seed_blueprint(dir, "campaign-run-mcwf-seed");
|
||||
let proc_id = register_process_doc(dir, "mcwf.process.json", MC_BEFORE_WF_PROCESS_DOC);
|
||||
write_doc(dir, "mcwf.campaign.json", &campaign_doc_json(&bp_id, &proc_id, (1, 2), "", ""));
|
||||
let (out, code) = run_code_in(dir, &["campaign", "run", "mcwf.campaign.json"]);
|
||||
assert_eq!(code, Some(1), "stdout/stderr: {out}");
|
||||
assert!(out.contains("not executable in v1"), "stdout/stderr: {out}");
|
||||
assert!(out.contains("std::monte_carlo"), "the prose names the block: {out}");
|
||||
assert!(!out.contains("UnsupportedStage"), "Debug leak: {out}");
|
||||
assert!(out.contains("process pipeline is not executable:"), "stdout/stderr: {out}");
|
||||
assert!(
|
||||
out.contains("std::walk_forward cannot follow std::monte_carlo"),
|
||||
"the detail names the ordering violation: {out}"
|
||||
);
|
||||
assert!(!out.contains("not executable in v1"), "retired v1 prose resurfaced: {out}");
|
||||
assert!(!out.contains("PipelineShape"), "Debug leak: {out}");
|
||||
}
|
||||
|
||||
/// The v2 boundary, campaign side: a generalize-bearing process is an
|
||||
/// executable shape, but `std::generalize` needs >= 2 instruments in the
|
||||
/// campaign — a STATIC preflight fact (the referential gate has run, no data
|
||||
/// is read), so the single-GER40 fixture campaign refuses with exit 1 before
|
||||
/// any member runs. This is the ONE home for the single-instrument refusal;
|
||||
/// the gated real-data e2e does not duplicate it (its campaign is also
|
||||
/// single-instrument, and this refusal is data-free by construction).
|
||||
#[test]
|
||||
fn campaign_run_refuses_single_instrument_generalize() {
|
||||
let _fixture = project_lock();
|
||||
let dir = built_project();
|
||||
let runs_dir = dir.join("runs");
|
||||
std::fs::remove_dir_all(&runs_dir).ok();
|
||||
let _cleanup = ScratchGuard(vec![
|
||||
ScratchPath::Dir(runs_dir.clone()),
|
||||
ScratchPath::File(dir.join("gen1.process.json")),
|
||||
ScratchPath::File(dir.join("gen1.campaign.json")),
|
||||
]);
|
||||
let bp_id = seed_blueprint(dir, "campaign-run-gen1-seed");
|
||||
let proc_id = register_process_doc(dir, "gen1.process.json", GENERALIZE_PROCESS_DOC);
|
||||
write_doc(dir, "gen1.campaign.json", &campaign_doc_json(&bp_id, &proc_id, (1, 2), "", ""));
|
||||
let (out, code) = run_code_in(dir, &["campaign", "run", "gen1.campaign.json"]);
|
||||
assert_eq!(code, Some(1), "stdout/stderr: {out}");
|
||||
assert!(
|
||||
out.contains("std::generalize needs at least 2"),
|
||||
"the refusal names the block and the minimum: {out}"
|
||||
);
|
||||
assert!(out.contains("1 instrument"), "the refusal names the available count: {out}");
|
||||
assert!(!out.contains("GeneralizeNeedsInstruments"), "Debug leak: {out}");
|
||||
}
|
||||
|
||||
/// An executable generalize-bearing shape whose metric is a pip metric, not
|
||||
/// an R metric (`total_pips` is a known, registered scalar — `process
|
||||
/// register` accepts it — but not R-denominated).
|
||||
const GENERALIZE_NON_R_PROCESS_DOC: &str = r#"{
|
||||
"format_version": 1,
|
||||
"kind": "process",
|
||||
"name": "gen-pip-screen",
|
||||
"pipeline": [
|
||||
{ "block": "std::sweep", "metric": "sqn_normalized", "select": "argmax" },
|
||||
{ "block": "std::generalize", "metric": "total_pips" }
|
||||
]
|
||||
}"#;
|
||||
|
||||
/// The v2 boundary, metric side: `std::generalize`'s metric guard
|
||||
/// (`check_r_metric`) is a STATIC preflight fact independent of the
|
||||
/// instrument-arity guard above it in the same match arm — with >= 2
|
||||
/// campaign instruments (so arity passes) and a pip metric, `campaign run`
|
||||
/// must still refuse data-free, naming the metric, and never fall through to
|
||||
/// the instrument-count prose or a Debug-formatted fault. A regression that
|
||||
/// let the arity check short-circuit past the metric check (or vice versa)
|
||||
/// would silently accept a pip-denominated `std::generalize`.
|
||||
#[test]
|
||||
fn campaign_run_refuses_generalize_non_r_metric() {
|
||||
let _fixture = project_lock();
|
||||
let dir = built_project();
|
||||
let runs_dir = dir.join("runs");
|
||||
std::fs::remove_dir_all(&runs_dir).ok();
|
||||
let _cleanup = ScratchGuard(vec![
|
||||
ScratchPath::Dir(runs_dir.clone()),
|
||||
ScratchPath::File(dir.join("genpip.process.json")),
|
||||
ScratchPath::File(dir.join("genpip.campaign.json")),
|
||||
]);
|
||||
let bp_id = seed_blueprint(dir, "campaign-run-genpip-seed");
|
||||
let proc_id = register_process_doc(dir, "genpip.process.json", GENERALIZE_NON_R_PROCESS_DOC);
|
||||
// Two instruments (arity passes), so the metric guard is the one that
|
||||
// actually fires. The [1, 2] 1970 window never matters: this refusal is
|
||||
// data-free, before any member runs.
|
||||
let campaign_doc = format!(
|
||||
r#"{{
|
||||
"format_version": 1,
|
||||
"kind": "campaign",
|
||||
"name": "run-seam-genpip",
|
||||
"data": {{ "instruments": ["GER40", "EURUSD"], "windows": [ {{ "from_ms": 1, "to_ms": 2 }} ] }},
|
||||
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
|
||||
"axes": {{ "fast.length": {{ "kind": "I64", "values": [2, 4] }},
|
||||
"slow.length": {{ "kind": "I64", "values": [8, 16] }} }} }} ],
|
||||
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
|
||||
"seed": 7,
|
||||
"presentation": {{ "persist_taps": [], "emit": [] }}
|
||||
}}"#
|
||||
);
|
||||
write_doc(dir, "genpip.campaign.json", &campaign_doc);
|
||||
let (out, code) = run_code_in(dir, &["campaign", "run", "genpip.campaign.json"]);
|
||||
assert_eq!(code, Some(1), "stdout/stderr: {out}");
|
||||
assert!(
|
||||
out.contains("std::generalize metric \"total_pips\" is not an R metric"),
|
||||
"the refusal names the offending metric: {out}"
|
||||
);
|
||||
assert!(!out.contains("2 instrument"), "the arity guard must not fire when arity is fine: {out}");
|
||||
assert!(!out.contains("GeneralizeNonRMetric"), "Debug leak: {out}");
|
||||
}
|
||||
|
||||
/// A v2 process doc with `std::walk_forward` following the annotator
|
||||
@@ -896,51 +1036,6 @@ fn campaign_run_refuses_zero_resamples_monte_carlo_before_any_member_runs() {
|
||||
assert!(!out.contains("ZeroBootstrapParam"), "Debug leak: {out}");
|
||||
}
|
||||
|
||||
/// A v2 process doc that ends in `std::generalize`.
|
||||
const GENERALIZE_PROCESS_DOC: &str = r#"{
|
||||
"format_version": 1,
|
||||
"kind": "process",
|
||||
"name": "generalize-screen",
|
||||
"pipeline": [
|
||||
{ "block": "std::sweep", "metric": "sqn_normalized", "select": "argmax" },
|
||||
{ "block": "std::generalize", "metric": "net_expectancy_r" }
|
||||
]
|
||||
}"#;
|
||||
|
||||
/// Property (0108 task 2): `std::generalize`'s campaign-level instrument-
|
||||
/// arity guard (>= 2 instruments) is a STATIC executor-preflight refusal,
|
||||
/// wired end to end from the campaign document's `data.instruments` through
|
||||
/// to CLI stdout, firing before any member runs (data-free) and naming the
|
||||
/// actual instrument count. `campaign_doc_json`'s fixture campaign carries
|
||||
/// exactly one instrument (`GER40`), so this exercises the campaign
|
||||
/// parameter `preflight` newly reads (previously unused in v1) rather than
|
||||
/// anything reachable from the process document alone. A regression that
|
||||
/// left the campaign parameter unread would silently accept a single-
|
||||
/// instrument `std::generalize` and only fail later, opaquely, inside the
|
||||
/// generalization math.
|
||||
#[test]
|
||||
fn campaign_run_refuses_single_instrument_generalize() {
|
||||
let _fixture = project_lock();
|
||||
let dir = built_project();
|
||||
let runs_dir = dir.join("runs");
|
||||
std::fs::remove_dir_all(&runs_dir).ok();
|
||||
let _cleanup = ScratchGuard(vec![
|
||||
ScratchPath::Dir(runs_dir.clone()),
|
||||
ScratchPath::File(dir.join("gen.process.json")),
|
||||
ScratchPath::File(dir.join("gen.campaign.json")),
|
||||
]);
|
||||
let bp_id = seed_blueprint(dir, "campaign-run-gen-seed");
|
||||
let proc_id = register_process_doc(dir, "gen.process.json", GENERALIZE_PROCESS_DOC);
|
||||
write_doc(dir, "gen.campaign.json", &campaign_doc_json(&bp_id, &proc_id, (1, 2), "", ""));
|
||||
let (out, code) = run_code_in(dir, &["campaign", "run", "gen.campaign.json"]);
|
||||
assert_eq!(code, Some(1), "stdout/stderr: {out}");
|
||||
assert!(
|
||||
out.contains("campaign has 1 instrument(s); std::generalize needs at least 2"),
|
||||
"stdout/stderr: {out}"
|
||||
);
|
||||
assert!(!out.contains("GeneralizeNeedsInstruments"), "Debug leak: {out}");
|
||||
}
|
||||
|
||||
/// Non-empty persist_taps defers LOUDLY, and the note's ORDER is pinned: it
|
||||
/// prints before member execution, so it is asserted here on a run
|
||||
/// that refuses at the member-data seam. The [1, 2] epoch-ms window (1970)
|
||||
@@ -978,12 +1073,13 @@ fn campaign_run_persist_taps_deferred_loudly() {
|
||||
}
|
||||
|
||||
/// Gated real-data e2e (the `cli_run.rs` skip idiom): a full
|
||||
/// sweep -> gate -> walk_forward campaign over GER40 Sept-2024 where the
|
||||
/// local archive is present — exit 0, emit-gated family/selection lines, a
|
||||
/// final line parseable as JSON with top-level `campaign_run` linking a sweep
|
||||
/// family id and a walk-forward family id, and the `campaign_runs.jsonl`
|
||||
/// sibling store written. Skips with a note elsewhere so
|
||||
/// `cargo test --workspace` stays green on a data-less machine.
|
||||
/// sweep -> gate -> walk_forward -> monte_carlo campaign over GER40
|
||||
/// Sept-2024 where the local archive is present — exit 0, emit-gated
|
||||
/// family/selection lines, a final line parseable as JSON with top-level
|
||||
/// `campaign_run` linking a sweep family id, a walk-forward family id, and a
|
||||
/// pooled-OOS bootstrap on the terminal mc stage, and the
|
||||
/// `campaign_runs.jsonl` sibling store written. Skips with a note elsewhere
|
||||
/// so `cargo test --workspace` stays green on a data-less machine.
|
||||
#[test]
|
||||
fn campaign_run_real_e2e_sweep_gate_walkforward() {
|
||||
let _fixture = project_lock();
|
||||
@@ -1030,7 +1126,11 @@ fn campaign_run_real_e2e_sweep_gate_walkforward() {
|
||||
let cells = v["campaign_run"]["cells"].as_array().expect("cells array");
|
||||
assert_eq!(cells.len(), 1, "one (strategy, instrument, window) cell: {record_line}");
|
||||
let stages = cells[0]["stages"].as_array().expect("stages array");
|
||||
assert_eq!(stages.len(), 3, "sweep + gate + walk_forward realized: {record_line}");
|
||||
assert_eq!(
|
||||
stages.len(),
|
||||
4,
|
||||
"sweep + gate + walk_forward + monte_carlo realized: {record_line}"
|
||||
);
|
||||
assert!(
|
||||
stages[0]["family_id"].as_str().is_some(),
|
||||
"sweep stage links a family: {record_line}"
|
||||
@@ -1044,6 +1144,21 @@ fn campaign_run_real_e2e_sweep_gate_walkforward() {
|
||||
stages[2]["family_id"].as_str().is_some(),
|
||||
"walk-forward stage links a family: {record_line}"
|
||||
);
|
||||
assert_eq!(
|
||||
stages[3]["block"].as_str(),
|
||||
Some("std::monte_carlo"),
|
||||
"the annotator stage is realized last: {record_line}"
|
||||
);
|
||||
let pooled = &stages[3]["bootstrap"]["pooled_oos"];
|
||||
assert!(
|
||||
pooled.is_object(),
|
||||
"an mc stage after walk_forward records ONE pooled-OOS bootstrap: {record_line}"
|
||||
);
|
||||
assert_eq!(
|
||||
pooled["n_resamples"].as_u64(),
|
||||
Some(200),
|
||||
"the bootstrap carries the doc's resamples verbatim: {record_line}"
|
||||
);
|
||||
// Emit-gated lines: per-member family_table lines (4 sweep members plus
|
||||
// the walk-forward OOS members) and at least one selection_report line.
|
||||
assert!(
|
||||
@@ -1061,6 +1176,76 @@ fn campaign_run_real_e2e_sweep_gate_walkforward() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Gated real-data e2e, the OTHER mc bootstrap input shape (#200 d1): with no
|
||||
/// walk_forward in the pipeline (`sweep -> monte_carlo`), the terminal
|
||||
/// annotator's dual-input dispatch takes the `PerSurvivor` arm, not
|
||||
/// `PooledOos` — one bootstrap per surviving sweep member, keyed by the
|
||||
/// member's ordinal into the sweep family, rather than one pooled bootstrap
|
||||
/// over walk-forward OOS windows. `campaign_run_real_e2e_sweep_gate_walkforward`
|
||||
/// above only exercises `PooledOos`; this is the only e2e coverage of
|
||||
/// `PerSurvivor` with real data (the addressing-mode tests use it only as a
|
||||
/// data-free refusal fixture).
|
||||
#[test]
|
||||
fn campaign_run_real_e2e_sweep_monte_carlo_per_survivor() {
|
||||
let _fixture = project_lock();
|
||||
let dir = built_project();
|
||||
let runs_dir = dir.join("runs");
|
||||
std::fs::remove_dir_all(&runs_dir).ok();
|
||||
let _cleanup = ScratchGuard(vec![
|
||||
ScratchPath::Dir(runs_dir.clone()),
|
||||
ScratchPath::File(dir.join("mc.process.json")),
|
||||
ScratchPath::File(dir.join("mc.campaign.json")),
|
||||
]);
|
||||
let bp_id = seed_blueprint(dir, "campaign-run-mc-persurvivor-seed");
|
||||
let proc_id = register_process_doc(dir, "mc.process.json", MC_PROCESS_DOC);
|
||||
// Same GER40 Sept-2024 window as the gated wf e2e above.
|
||||
write_doc(
|
||||
dir,
|
||||
"mc.campaign.json",
|
||||
&campaign_doc_json(&bp_id, &proc_id, (1725148800000, 1727740799999), "", ""),
|
||||
);
|
||||
let (out, code) = run_code_in(dir, &["campaign", "run", "mc.campaign.json"]);
|
||||
|
||||
// Skip on a data-less machine: the member-data refusal, never a panic.
|
||||
if code == Some(1)
|
||||
&& (out.contains("no recorded geometry") || out.contains("no data for instrument"))
|
||||
{
|
||||
eprintln!("skip: no local GER40 data for the campaign e2e");
|
||||
return;
|
||||
}
|
||||
|
||||
assert_eq!(code, Some(0), "stdout/stderr: {out}");
|
||||
let record_line = out
|
||||
.lines()
|
||||
.find(|l| l.starts_with("{\"campaign_run\":"))
|
||||
.expect("the always-on final campaign_run line");
|
||||
let v: serde_json::Value =
|
||||
serde_json::from_str(record_line).expect("campaign_run line parses as JSON");
|
||||
let cells = v["campaign_run"]["cells"].as_array().expect("cells array");
|
||||
let stages = cells[0]["stages"].as_array().expect("stages array");
|
||||
assert_eq!(stages.len(), 2, "sweep + monte_carlo realized: {record_line}");
|
||||
assert_eq!(
|
||||
stages[1]["block"].as_str(),
|
||||
Some("std::monte_carlo"),
|
||||
"the annotator stage is realized last: {record_line}"
|
||||
);
|
||||
let per_survivor = stages[1]["bootstrap"]["per_survivor"]
|
||||
.as_array()
|
||||
.expect("no walk_forward precedes: the PerSurvivor arm, not pooled_oos");
|
||||
assert_eq!(
|
||||
per_survivor.len(),
|
||||
4,
|
||||
"one bootstrap per surviving sweep member (4 axis combinations, no gate): {record_line}"
|
||||
);
|
||||
let (ordinal, bootstrap) = (per_survivor[0][0].as_u64(), &per_survivor[0][1]);
|
||||
assert_eq!(ordinal, Some(0), "each entry is keyed by ordinal into the sweep family: {record_line}");
|
||||
assert_eq!(
|
||||
bootstrap["n_resamples"].as_u64(),
|
||||
Some(100),
|
||||
"the bootstrap carries the doc's resamples verbatim: {record_line}"
|
||||
);
|
||||
}
|
||||
|
||||
/// `run_campaign` resolves a target two ways — a readable file (register-then-
|
||||
/// run sugar) or a bare content id (direct store address) — then funnels BOTH
|
||||
/// through one shared post-resolution path: "fetch the stored canonical bytes
|
||||
@@ -1069,8 +1254,11 @@ fn campaign_run_real_e2e_sweep_gate_walkforward() {
|
||||
/// that property observably: registering a document and then running it once
|
||||
/// by FILE and once by its own resulting content ID must refuse with the
|
||||
/// byte-identical prose — proof the two addressing modes are not two
|
||||
/// independently drifting code paths. No real data needed: both runs refuse
|
||||
/// at the (data-free) v1-pipeline-shape preflight.
|
||||
/// independently drifting code paths. No real data needed: with the [1, 2]
|
||||
/// 1970 window, both runs pass the (now mc-permitting) preflight and refuse
|
||||
/// at the member-data seam — no recorded geometry on a data-less host, no
|
||||
/// overlapping archive window on a data-ful one — exit 1 and byte-identical
|
||||
/// prose either way (the message depends only on env/instrument/window).
|
||||
#[test]
|
||||
fn campaign_run_by_content_id_matches_file_sugar_refusal() {
|
||||
let _fixture = project_lock();
|
||||
@@ -1106,6 +1294,10 @@ fn campaign_run_by_content_id_matches_file_sugar_refusal() {
|
||||
|
||||
let file_line = file_out.lines().find(|l| l.starts_with("aura: ")).expect("file refusal line");
|
||||
let id_line = id_out.lines().find(|l| l.starts_with("aura: ")).expect("id refusal line");
|
||||
assert!(
|
||||
file_line.contains("no recorded geometry") || file_line.contains("no data for instrument"),
|
||||
"the refusal is the member-data seam (mc processes execute now): {file_line}"
|
||||
);
|
||||
assert_eq!(file_line, id_line, "file- and id-addressed runs must refuse identically");
|
||||
}
|
||||
|
||||
@@ -1115,7 +1307,9 @@ fn campaign_run_by_content_id_matches_file_sugar_refusal() {
|
||||
/// observably: running the SAME registered campaign by bare id and by its
|
||||
/// `content:`-prefixed form must refuse identically (proof the prefix is
|
||||
/// stripped, not read as a second, unresolvable address). Both refuse at the
|
||||
/// data-free v1 preflight (MC process), so no real data is needed.
|
||||
/// member-data seam (the mc process is executable in v2; the [1, 2] 1970
|
||||
/// window guarantees a deterministic data refusal), so no real data is
|
||||
/// needed.
|
||||
#[test]
|
||||
fn campaign_run_tolerates_content_prefix_on_target() {
|
||||
let _fixture = project_lock();
|
||||
@@ -1153,6 +1347,10 @@ fn campaign_run_tolerates_content_prefix_on_target() {
|
||||
"a content:-prefixed target must resolve as the bare id, not refuse as an unknown target: {pfx_line}"
|
||||
);
|
||||
let bare_line = bare_out.lines().find(|l| l.starts_with("aura: ")).expect("bare refusal line");
|
||||
assert!(
|
||||
bare_line.contains("no recorded geometry") || bare_line.contains("no data for instrument"),
|
||||
"the refusal is the member-data seam (mc processes execute now): {bare_line}"
|
||||
);
|
||||
assert_eq!(bare_line, pfx_line, "bare-id and content:-prefixed runs must resolve identically");
|
||||
}
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ pub const PROCESS_BLOCKS: &[BlockSchema] = &[
|
||||
},
|
||||
BlockSchema {
|
||||
id: "std::monte_carlo",
|
||||
doc: "R-bootstrap over realised R (terminal annotator in v1)",
|
||||
doc: "R-bootstrap over realised R (terminal annotator)",
|
||||
slots: &[
|
||||
SlotInfo { name: "resamples", kind: SlotKind::U32, required: true },
|
||||
SlotInfo { name: "block_len", kind: SlotKind::U32, required: true },
|
||||
@@ -196,7 +196,7 @@ pub const PROCESS_BLOCKS: &[BlockSchema] = &[
|
||||
},
|
||||
BlockSchema {
|
||||
id: "std::generalize",
|
||||
doc: "cross-instrument worst-case floor (terminal annotator in v1)",
|
||||
doc: "cross-instrument worst-case floor (terminal annotator)",
|
||||
slots: &[SlotInfo { name: "metric", kind: SlotKind::MetricName, required: true }],
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user