From 7e4e9a88272bdd4ff51ebde7051166faefde4c4f Mon Sep 17 00:00:00 2001 From: Brummel Date: Sat, 4 Jul 2026 01:28:06 +0200 Subject: [PATCH] feat(cli): annotator-stage seam flips + doc tidy + gated e2e with the mc suffix (0108 task 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- crates/aura-cli/tests/research_docs.rs | 356 +++++++++++++++++++------ crates/aura-research/src/lib.rs | 4 +- 2 files changed, 279 insertions(+), 81 deletions(-) diff --git a/crates/aura-cli/tests/research_docs.rs b/crates/aura-cli/tests/research_docs.rs index 1871c6a..bb294fd 100644 --- a/crates/aura-cli/tests/research_docs.rs +++ b/crates/aura-cli/tests/research_docs.rs @@ -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"); } diff --git a/crates/aura-research/src/lib.rs b/crates/aura-research/src/lib.rs index 8a9e6a6..1c6788c 100644 --- a/crates/aura-research/src/lib.rs +++ b/crates/aura-research/src/lib.rs @@ -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 }], }, ];