From c32c01a63cf6981472bf5fb1c4f456196101ba9c Mon Sep 17 00:00:00 2001 From: Brummel Date: Sat, 11 Jul 2026 12:16:57 +0200 Subject: [PATCH] =?UTF-8?q?test(cli):=20archive-gated=20e2e=20=E2=80=94=20?= =?UTF-8?q?a=20costed=20campaign=20nets=20members=20and=20reproduces=20(#2?= =?UTF-8?q?34=20task=207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- crates/aura-cli/tests/research_docs.rs | 240 +++++++++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/crates/aura-cli/tests/research_docs.rs b/crates/aura-cli/tests/research_docs.rs index a9208d7..81b7420 100644 --- a/crates/aura-cli/tests/research_docs.rs +++ b/crates/aura-cli/tests/research_docs.rs @@ -2822,6 +2822,246 @@ fn campaign_run_real_e2e_skips_unproducible_tap_gracefully() { ); } +/// #234 (archive-gated): the net-R campaign path end to end. The SAME document +/// runs twice — without and with a `cost` block (the only textual difference). +/// Cost only ADDS: every member's gross field family is identical across the +/// two runs and net == gross without cost; with cost, every member's +/// net_expectancy_r sits strictly below its gross expectancy_r. The costed run +/// persists `net_r_equity` when requested (no skip notice), while the +/// cost-less run skips it with the remedy notice; and the costed family +/// reproduces bit-identically (components re-derived from the member +/// manifests). Skips on a data-less host like its siblings. +#[test] +fn campaign_run_real_e2e_cost_block_nets_members_and_persists_net_r_equity() { + 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("costnet.process.json")), + ScratchPath::File(dir.join("costless.campaign.json")), + ScratchPath::File(dir.join("costnet.campaign.json")), + ]); + let bp_id = seed_blueprint(dir, "campaign-run-costnet-seed"); + let proc_id = register_process_doc(dir, "costnet.process.json", SWEEP_ONLY_PROCESS_DOC); + let base = campaign_doc_json( + &bp_id, + &proc_id, + (1725148800000, 1727740799999), + "\"net_r_equity\", \"equity\"", + "\"family_table\"", + ); + // The same document, plus a cost block — the ONLY difference. 2.0 price + // units per close on GER40 (index ~18500, M1 vol-stop distances of a few + // points) yields a clearly visible per-trade cost-in-R. + let with_cost = base.replacen( + "\"seed\": 7,", + "\"seed\": 7,\n \"cost\": [ { \"constant\": { \"cost_per_trade\": 2.0 } }, { \"vol_slippage\": { \"slip_vol_mult\": 0.5 } } ],", + 1, + ); + assert_ne!(with_cost, base, "replacen must actually match the fixture's seed field"); + write_doc(dir, "costless.campaign.json", &base); + write_doc(dir, "costnet.campaign.json", &with_cost); + + // Per-member (axis-params key -> (expectancy_r, net_expectancy_r)), + // parsed from the emitted family_table member lines. Cost stamps are + // excluded from the key so the two runs' members align. + let members = |out: &str| -> Vec<(String, f64, f64)> { + out.lines() + // A member line starts with the family_id key itself; the always-on + // final `{"campaign_run":...}` summary line also embeds a nested + // "family_id" (per selection stage) and must NOT be swept in here. + .filter(|l| l.starts_with("{\"family_id\":")) + .map(|l| { + let v: serde_json::Value = serde_json::from_str(l).expect("member line parses"); + let key = v["report"]["manifest"]["params"] + .as_array() + .expect("params array") + .iter() + .filter(|p| !p[0].as_str().unwrap_or_default().starts_with("cost[")) + .map(|p| p.to_string()) + .collect::>() + .join(","); + let r = &v["report"]["metrics"]["r"]; + ( + key, + r["expectancy_r"].as_f64().expect("expectancy_r"), + r["net_expectancy_r"].as_f64().expect("net_expectancy_r"), + ) + }) + .collect() + }; + + // (a) the cost-less baseline: net == gross, and net_r_equity skips with + // the remedy notice while equity still persists. + let (out_gross, code_gross) = run_code_in(dir, &["campaign", "run", "costless.campaign.json"]); + if code_gross == Some(1) + && (out_gross.contains("no recorded geometry") || out_gross.contains("no data for instrument")) + { + eprintln!("skip: no local GER40 data for the cost e2e"); + return; + } + assert_eq!(code_gross, Some(0), "cost-less baseline run: {out_gross}"); + assert!( + out_gross.contains( + "aura: tap \"net_r_equity\" needs a cost model; add a cost block to the campaign document; skipped" + ), + "the cost-less run skips the net tap with the remedy: {out_gross}" + ); + let gross_members = members(&out_gross); + assert_eq!(gross_members.len(), 4, "2x2 axes -> four member lines: {out_gross}"); + for (key, e, ne) in &gross_members { + assert_eq!(e, ne, "cost-less member {key} must have net == gross"); + } + + // (b) the costed run: gross identical member-for-member, net strictly + // below gross, no skip notice, net_r_equity persisted on disk. + let (out_net, code_net) = run_code_in(dir, &["campaign", "run", "costnet.campaign.json"]); + assert_eq!(code_net, Some(0), "costed run: {out_net}"); + assert!( + !out_net.contains("skipped"), + "a costed run must not skip any requested tap: {out_net}" + ); + let net_members = members(&out_net); + assert_eq!(net_members.len(), 4, "the same four members: {out_net}"); + for (key, e, ne) in &net_members { + let (_, gross_e, _) = gross_members + .iter() + .find(|(gk, _, _)| gk == key) + .unwrap_or_else(|| panic!("costed member {key} has no cost-less twin")); + assert_eq!(e, gross_e, "cost only ADDS: member {key} gross must be identical"); + assert!(ne < e, "member {key}: net {ne} must sit strictly below gross {e}"); + } + + let record_line = out_net + .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 trace_name = v["campaign_run"]["trace_name"] + .as_str() + .expect("persist_taps non-empty => the record carries trace_name"); + let cell_key = format!("{}-GER40-w0", &bp_id[..8]); + let cell_dir = runs_dir.join("traces").join(trace_name).join(&cell_key); + assert!( + cell_dir.join("net_r_equity.json").is_file(), + "the net curve persists at {}", + cell_dir.display() + ); + assert!( + cell_dir.join("equity.json").is_file(), + "the sibling tap still persists: {}", + cell_dir.display() + ); + + // (c) the costed family reproduces bit-identically — the components are + // re-derived from the member manifests (#233 pattern, cost edition). + let family_id = out_net + .lines() + .find(|l| l.starts_with("{\"family_id\":")) + .map(|l| { + serde_json::from_str::(l).expect("member line parses")["family_id"] + .as_str() + .expect("family_id is a string") + .to_string() + }) + .expect("a costed member line carries the family id"); + let (rep_out, rep_code) = run_code_in(dir, &["reproduce", &family_id]); + assert_eq!(rep_code, Some(0), "reproduce: {rep_out}"); + assert!( + rep_out.contains("reproduced 4/4 members bit-identically"), + "the costed family re-derives: {rep_out}" + ); +} + +/// Property (#234): a `carry` cost component — the third and structurally +/// distinct cost leg (a per-held-cycle accrual charged while a position stays +/// open, via `CarryCost`'s own extra input, unlike `constant`'s flat per-close +/// charge or `vol_slippage`'s volatility-proxy-scaled charge) — nets a real +/// campaign member through the SAME production path as its `constant`/ +/// `vol_slippage` siblings (`campaign run` -> `CliMemberRunner` (net-mode) -> +/// `persist_campaign_traces`, non-reduce re-run binding a `CostLeg`). The +/// re-run must bind the identical carry model the nominee ran under, so it +/// must not false-fail the C1 drift alarm; the emitted member must stamp +/// `cost[0].carry_per_cycle`; and the accrual must actually move at least one +/// member's net off its gross (non-vacuity — a zero-effect charge would let +/// this pass even if the per-cycle accrual were never wired). Gated on the +/// local GER40 archive; skips on a data-less host. +#[test] +fn campaign_run_real_e2e_carry_cost_persists_without_drift_alarm() { + 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("carrycost-persist.process.json")), + ScratchPath::File(dir.join("carrycost-persist.campaign.json")), + ]); + let bp_id = seed_blueprint(dir, "campaign-persist-carrycost-seed"); + let proc_id = + register_process_doc(dir, "carrycost-persist.process.json", SWEEP_ONLY_PROCESS_DOC); + let base = campaign_doc_json( + &bp_id, + &proc_id, + (1725148800000, 1727740799999), + "\"r_equity\"", + "\"family_table\"", + ); + let with_cost = base.replacen( + "\"seed\": 7,", + "\"seed\": 7,\n \"cost\": [ { \"carry\": { \"carry_per_cycle\": 0.001 } } ],", + 1, + ); + assert_ne!(with_cost, base, "replacen must actually match the fixture's seed field"); + write_doc(dir, "carrycost-persist.campaign.json", &with_cost); + let (out, code) = run_code_in(dir, &["campaign", "run", "carrycost-persist.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 carry-cost-persist e2e"); + return; + } + + assert!( + !out.contains("C1 violation"), + "the carry persist-side re-run must bind the SAME cost model the nominee ran \ + under, not false-fail C1: {out}" + ); + assert_eq!(code, Some(0), "a deterministic carry-costed campaign persists cleanly: {out}"); + + let member_lines: Vec<&str> = + out.lines().filter(|l| l.starts_with("{\"family_id\":")).collect(); + assert!(!member_lines.is_empty(), "family_table member lines emitted: {out}"); + let mut any_net_moved = false; + for line in &member_lines { + let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON"); + let params = v["report"]["manifest"]["params"] + .as_array() + .expect("manifest.params is an array"); + assert!( + params.iter().any(|p| p[0].as_str() == Some("cost[0].carry_per_cycle")), + "the costed member stamps its carry component: {line}" + ); + if let Some(r) = v["report"]["metrics"]["r"].as_object() { + let gross = r["expectancy_r"].as_f64().expect("expectancy_r is a number"); + let net = r["net_expectancy_r"].as_f64().expect("net_expectancy_r is a number"); + if gross != net { + any_net_moved = true; + } + } + } + assert!( + any_net_moved, + "at least one member's carry accrual must move net off gross \ + (else the per-held-cycle charge never engaged): {out}" + ); +} + /// 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