feat(cli): bind the resolved regime stop and stamp it into the manifest (#210 T4)
The runtime-binding slice — the regime's stop now actually reaches the graph and
the record:
- `StopArm { Open, Bound(StopRule) }` replaces `wrap_r`'s `stop_open: bool` so a
bound stop carries its params; all call sites threaded (the r-sma sweep path
stays Open, every bound site passes the resolved rule).
- `run_blueprint_member` takes a `stop: StopRule` and stamps `stop_length` /
`stop_k` into the member manifest (the ratified C18 extension — the stop is no
longer invisible on the campaign/blueprint path); its callers thread the
default, and `CliMemberRunner` maps `cell.regime` (None -> baked default,
Some(Vol) -> that vol stop).
- The characterization pin flips from asserting the stop's ABSENCE to requiring
the stamped default; a real two-regime e2e pins that each regime stamps its
own k.
Known deferred gap (concern, documented descope): `persist_campaign_traces`
re-runs the nominee to persist its traces and — since `CellRealization` carries
no regime — binds that re-run to the DEFAULT stop, so a non-default-regime
nominee's persisted trace would reflect the wrong stop. Only the persist path
(non-empty `persist_taps`) is affected; the core run/stamp/generalize path is
correct. Filed as a follow-up. Full workspace suite green.
refs #210
This commit is contained in:
@@ -268,6 +268,15 @@ impl MemberRunner for CliMemberRunner<'_> {
|
|||||||
// id, project provenance stamped inside the helper.
|
// id, project provenance stamped inside the helper.
|
||||||
let signal = blueprint_from_json(&cell.blueprint_json, &|t| self.env.resolve(t))
|
let signal = blueprint_from_json(&cell.blueprint_json, &|t| self.env.resolve(t))
|
||||||
.expect("stored blueprint passed the referential gate; reload is infallible");
|
.expect("stored blueprint passed the referential gate; reload is infallible");
|
||||||
|
let stop = match cell.regime {
|
||||||
|
None => aura_composites::StopRule::Vol {
|
||||||
|
length: crate::R_SMA_STOP_LENGTH,
|
||||||
|
k: crate::R_SMA_STOP_K,
|
||||||
|
},
|
||||||
|
Some(aura_research::RiskRegime::Vol { length, k }) => {
|
||||||
|
aura_composites::StopRule::Vol { length, k }
|
||||||
|
}
|
||||||
|
};
|
||||||
let mut report = crate::run_blueprint_member(
|
let mut report = crate::run_blueprint_member(
|
||||||
signal,
|
signal,
|
||||||
&point,
|
&point,
|
||||||
@@ -278,6 +287,7 @@ impl MemberRunner for CliMemberRunner<'_> {
|
|||||||
geo.pip_size,
|
geo.pip_size,
|
||||||
&cell.strategy_id,
|
&cell.strategy_id,
|
||||||
self.env,
|
self.env,
|
||||||
|
stop,
|
||||||
);
|
);
|
||||||
report.manifest.instrument = Some(cell.instrument.clone());
|
report.manifest.instrument = Some(cell.instrument.clone());
|
||||||
Ok(report)
|
Ok(report)
|
||||||
@@ -679,7 +689,19 @@ fn persist_campaign_traces(
|
|||||||
let (tx_ex, rx_ex) = mpsc::channel();
|
let (tx_ex, rx_ex) = mpsc::channel();
|
||||||
let (tx_r, rx_r) = mpsc::channel();
|
let (tx_r, rx_r) = mpsc::channel();
|
||||||
let (tx_req, rx_req) = mpsc::channel();
|
let (tx_req, rx_req) = mpsc::channel();
|
||||||
let mut h = crate::wrap_r(signal, tx_eq, tx_ex, tx_r, tx_req, false, false, None)
|
let mut h = crate::wrap_r(
|
||||||
|
signal,
|
||||||
|
tx_eq,
|
||||||
|
tx_ex,
|
||||||
|
tx_r,
|
||||||
|
tx_req,
|
||||||
|
crate::StopArm::Bound(aura_composites::StopRule::Vol {
|
||||||
|
length: crate::R_SMA_STOP_LENGTH,
|
||||||
|
k: crate::R_SMA_STOP_K,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
)
|
||||||
.bootstrap_with_cells(&point)
|
.bootstrap_with_cells(&point)
|
||||||
.expect("the nominee's point re-bootstraps (it already ran this realization)");
|
.expect("the nominee's point re-bootstraps (it already ran this realization)");
|
||||||
let sources: Vec<Box<dyn aura_engine::Source>> = vec![Box::new(source)];
|
let sources: Vec<Box<dyn aura_engine::Source>> = vec![Box::new(source)];
|
||||||
|
|||||||
+40
-19
@@ -2285,7 +2285,7 @@ fn reproduce_family_in(
|
|||||||
let (tx_r, _) = mpsc::channel();
|
let (tx_r, _) = mpsc::channel();
|
||||||
let (tx_req, _) = mpsc::channel();
|
let (tx_req, _) = mpsc::channel();
|
||||||
let space =
|
let space =
|
||||||
wrap_r(reload(), tx_eq, tx_ex, tx_r, tx_req, false, true, None).param_space();
|
wrap_r(reload(), tx_eq, tx_ex, tx_r, tx_req, StopArm::Bound(StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }), true, None).param_space();
|
||||||
let point = point_from_params(&space, &stored.manifest.params);
|
let point = point_from_params(&space, &stored.manifest.params);
|
||||||
// A MonteCarlo member carries no tuning params (the params-join is empty), so its
|
// A MonteCarlo member carries no tuning params (the params-join is empty), so its
|
||||||
// reproduce line would print a BLANK member label; the seed IS its realization
|
// reproduce line would print a BLANK member label; the seed IS its realization
|
||||||
@@ -2333,6 +2333,7 @@ fn reproduce_family_in(
|
|||||||
pip,
|
pip,
|
||||||
&hash,
|
&hash,
|
||||||
env,
|
env,
|
||||||
|
StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K },
|
||||||
);
|
);
|
||||||
outcomes.push((label, rerun.metrics == stored.metrics));
|
outcomes.push((label, rerun.metrics == stored.metrics));
|
||||||
}
|
}
|
||||||
@@ -2488,11 +2489,11 @@ fn run_macd(trace: Option<&str>, env: &project::Env) -> RunReport {
|
|||||||
/// The r-sma vol-stop EWMA length (cycles). Single source for the `StopRule::Vol`
|
/// The r-sma vol-stop EWMA length (cycles). Single source for the `StopRule::Vol`
|
||||||
/// the blueprint embeds and the `stop` param the manifest records — kept honest by one
|
/// the blueprint embeds and the `stop` param the manifest records — kept honest by one
|
||||||
/// constant instead of a hand-synced literal across the function boundary.
|
/// constant instead of a hand-synced literal across the function boundary.
|
||||||
const R_SMA_STOP_LENGTH: i64 = 3;
|
pub(crate) const R_SMA_STOP_LENGTH: i64 = 3;
|
||||||
|
|
||||||
/// The r-sma vol-stop multiplier (1R = `k`·σ). Single source for the embedded
|
/// The r-sma vol-stop multiplier (1R = `k`·σ). Single source for the embedded
|
||||||
/// `StopRule::Vol` and its manifest record, like [`R_SMA_STOP_LENGTH`].
|
/// `StopRule::Vol` and its manifest record, like [`R_SMA_STOP_LENGTH`].
|
||||||
const R_SMA_STOP_K: f64 = 2.0;
|
pub(crate) const R_SMA_STOP_K: f64 = 2.0;
|
||||||
|
|
||||||
/// The r-sma demo signal SMA lengths (fast/slow), bound at single-run build time.
|
/// The r-sma demo signal SMA lengths (fast/slow), bound at single-run build time.
|
||||||
/// Single source for the graph that runs (`r_sma_graph`), the signal that is hashed
|
/// Single source for the graph that runs (`r_sma_graph`), the signal that is hashed
|
||||||
@@ -2636,12 +2637,24 @@ fn r_sma_graph(
|
|||||||
tx_ex,
|
tx_ex,
|
||||||
tx_r,
|
tx_r,
|
||||||
tx_req,
|
tx_req,
|
||||||
stop_open,
|
if stop_open {
|
||||||
|
StopArm::Open
|
||||||
|
} else {
|
||||||
|
StopArm::Bound(StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K })
|
||||||
|
},
|
||||||
reduce,
|
reduce,
|
||||||
cost,
|
cost,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The vol-stop arm `wrap_r` embeds: an OPEN stop whose two knobs land in
|
||||||
|
/// `param_space` as sweep axes (the r-sma verb's gridded-stop path), or a BOUND
|
||||||
|
/// stop with fixed params (a single run, a blueprint/campaign member).
|
||||||
|
enum StopArm {
|
||||||
|
Open,
|
||||||
|
Bound(StopRule),
|
||||||
|
}
|
||||||
|
|
||||||
/// Wrap a `signal` composite (a `price`→`bias` leg) in the R run
|
/// Wrap a `signal` composite (a `price`→`bias` leg) in the R run
|
||||||
/// scaffolding: pip broker, the per-tap recorders, the vol-stop RiskExecutor, the
|
/// scaffolding: pip broker, the per-tap recorders, the vol-stop RiskExecutor, the
|
||||||
/// r_equity / cost legs. The signal is nested and its `price`/`bias` boundary is
|
/// r_equity / cost legs. The signal is nested and its `price`/`bias` boundary is
|
||||||
@@ -2655,7 +2668,7 @@ fn wrap_r(
|
|||||||
tx_ex: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
|
tx_ex: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
|
||||||
tx_r: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
|
tx_r: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
|
||||||
tx_req: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
|
tx_req: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
|
||||||
stop_open: bool,
|
stop: StopArm,
|
||||||
reduce: bool,
|
reduce: bool,
|
||||||
cost: Option<(
|
cost: Option<(
|
||||||
CostConfig,
|
CostConfig,
|
||||||
@@ -2668,8 +2681,9 @@ fn wrap_r(
|
|||||||
let sig = g.add(BlueprintNode::Composite(signal));
|
let sig = g.add(BlueprintNode::Composite(signal));
|
||||||
// pip branch (verbatim from sample_blueprint_with_sinks).
|
// pip branch (verbatim from sample_blueprint_with_sinks).
|
||||||
let broker = g.add(SimBroker::builder(SYNTHETIC_PIP_SIZE));
|
let broker = g.add(SimBroker::builder(SYNTHETIC_PIP_SIZE));
|
||||||
// R branch: bias + price → RiskExecutor(vol_stop) → dense R-record. The stop knobs
|
// R branch: bias + price → RiskExecutor(vol_stop) → dense R-record. The stop is either
|
||||||
// are bound to the pinned constants (default) or left open as sweep axes (`stop_open`).
|
// left OPEN as sweep axes (`StopArm::Open`) or BOUND to a fixed `StopRule` — the pinned
|
||||||
|
// default constants, or an arbitrary per-regime rule the caller resolved (`StopArm::Bound`).
|
||||||
// In `reduce` mode the per-cycle taps fold online: SeriesReducer folds the eq/ex
|
// In `reduce` mode the per-cycle taps fold online: SeriesReducer folds the eq/ex
|
||||||
// f64 series to one summary row, GatedRecorder retains only the gated R rows — the
|
// f64 series to one summary row, GatedRecorder retains only the gated R rows — the
|
||||||
// O(cycles)→O(trades) memory win. The raw `Recorder`s (and the r_equity tap) are the
|
// O(cycles)→O(trades) memory win. The raw `Recorder`s (and the r_equity tap) are the
|
||||||
@@ -2688,10 +2702,9 @@ fn wrap_r(
|
|||||||
} else {
|
} else {
|
||||||
g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex))
|
g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex))
|
||||||
};
|
};
|
||||||
let exec = g.add(if stop_open {
|
let exec = g.add(match stop {
|
||||||
risk_executor_vol_open(1.0)
|
StopArm::Open => risk_executor_vol_open(1.0),
|
||||||
} else {
|
StopArm::Bound(rule) => risk_executor(rule, 1.0),
|
||||||
risk_executor(StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }, 1.0)
|
|
||||||
});
|
});
|
||||||
let rrec = if reduce {
|
let rrec = if reduce {
|
||||||
g.add(GatedRecorder::builder(PM_RECORD_KINDS.to_vec(), gate_col, Firing::Any, tx_r))
|
g.add(GatedRecorder::builder(PM_RECORD_KINDS.to_vec(), gate_col, Firing::Any, tx_r))
|
||||||
@@ -2846,7 +2859,7 @@ fn run_signal_r(
|
|||||||
// The req tap (r_equity recorder) is wired but not persisted on this path; keep the
|
// The req tap (r_equity recorder) is wired but not persisted on this path; keep the
|
||||||
// receiver alive so the sink's sends do not fail, but do not drain it.
|
// receiver alive so the sink's sends do not fail, but do not drain it.
|
||||||
let (tx_req, _rx_req) = mpsc::channel();
|
let (tx_req, _rx_req) = mpsc::channel();
|
||||||
let wrapped = wrap_r(signal, tx_eq, tx_ex, tx_r, tx_req, false, false, None);
|
let wrapped = wrap_r(signal, tx_eq, tx_ex, tx_r, tx_req, StopArm::Bound(StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }), false, None);
|
||||||
let flat = wrapped
|
let flat = wrapped
|
||||||
.compile_with_params(params)
|
.compile_with_params(params)
|
||||||
.expect("signal binds + wraps to a valid harness");
|
.expect("signal binds + wraps to a valid harness");
|
||||||
@@ -2884,16 +2897,24 @@ fn run_blueprint_member(
|
|||||||
pip: f64,
|
pip: f64,
|
||||||
topo: &str,
|
topo: &str,
|
||||||
env: &project::Env,
|
env: &project::Env,
|
||||||
|
stop: StopRule,
|
||||||
) -> RunReport {
|
) -> RunReport {
|
||||||
let (tx_eq, rx_eq) = mpsc::channel();
|
let (tx_eq, rx_eq) = mpsc::channel();
|
||||||
let (tx_ex, rx_ex) = mpsc::channel();
|
let (tx_ex, rx_ex) = mpsc::channel();
|
||||||
let (tx_r, rx_r) = mpsc::channel();
|
let (tx_r, rx_r) = mpsc::channel();
|
||||||
let (tx_req, _rx_req) = mpsc::channel();
|
let (tx_req, _rx_req) = mpsc::channel();
|
||||||
let mut h = wrap_r(signal, tx_eq, tx_ex, tx_r, tx_req, false, true, None)
|
let mut h = wrap_r(signal, tx_eq, tx_ex, tx_r, tx_req, StopArm::Bound(stop), true, None)
|
||||||
.bootstrap_with_cells(point)
|
.bootstrap_with_cells(point)
|
||||||
.expect("member bootstraps (point kind-checked against param_space)");
|
.expect("member bootstraps (point kind-checked against param_space)");
|
||||||
h.run(sources);
|
h.run(sources);
|
||||||
let named = zip_params(space, point); // by-name params for the manifest record
|
let mut named = zip_params(space, point); // by-name params for the manifest record
|
||||||
|
// `if let` (not an irrefutable `let`): `StopRule` also has a `Fixed` variant,
|
||||||
|
// which stamps no vol knobs. The campaign/single-run paths only pass `Vol`,
|
||||||
|
// so the `Fixed` arm is inert here.
|
||||||
|
if let StopRule::Vol { length, k } = stop {
|
||||||
|
named.push(("stop_length".to_string(), Scalar::i64(length)));
|
||||||
|
named.push(("stop_k".to_string(), Scalar::f64(k)));
|
||||||
|
}
|
||||||
let mut manifest = sim_optimal_manifest(named, window, seed, pip);
|
let mut manifest = sim_optimal_manifest(named, window, seed, pip);
|
||||||
manifest.broker = r_sma_broker_label(pip);
|
manifest.broker = r_sma_broker_label(pip);
|
||||||
manifest.topology_hash = Some(topo.to_string());
|
manifest.topology_hash = Some(topo.to_string());
|
||||||
@@ -2929,7 +2950,7 @@ fn blueprint_axis_probe(doc: &str, env: &project::Env) -> Composite {
|
|||||||
let (tx_ex, _) = mpsc::channel();
|
let (tx_ex, _) = mpsc::channel();
|
||||||
let (tx_r, _) = mpsc::channel();
|
let (tx_r, _) = mpsc::channel();
|
||||||
let (tx_req, _) = mpsc::channel();
|
let (tx_req, _) = mpsc::channel();
|
||||||
wrap_r(signal, tx_eq, tx_ex, tx_r, tx_req, false, true, None)
|
wrap_r(signal, tx_eq, tx_ex, tx_r, tx_req, StopArm::Bound(StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }), true, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `aura sweep <blueprint.json> --list-axes`: one `<name>:<kind>` line per open
|
/// `aura sweep <blueprint.json> --list-axes`: one `<name>:<kind>` line per open
|
||||||
@@ -3002,7 +3023,7 @@ fn blueprint_sweep_family(
|
|||||||
.sweep(|point| {
|
.sweep(|point| {
|
||||||
// fresh per-member graph (Composite is !Clone, reload per member) run through
|
// fresh per-member graph (Composite is !Clone, reload per member) run through
|
||||||
// the shared reduce-mode member path — the same fn reproduction re-runs.
|
// the shared reduce-mode member path — the same fn reproduction re-runs.
|
||||||
run_blueprint_member(reload(doc), point, &space, data.run_sources(env), window, 0, pip, &topo, env)
|
run_blueprint_member(reload(doc), point, &space, data.run_sources(env), window, 0, pip, &topo, env, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K })
|
||||||
})
|
})
|
||||||
// render the sweep terminal's BindError to a message (the fn's String error contract),
|
// render the sweep terminal's BindError to a message (the fn's String error contract),
|
||||||
// so `UnknownKnob("nope")` still surfaces verbatim at the IO wrapper.
|
// so `UnknownKnob("nope")` still surfaces verbatim at the IO wrapper.
|
||||||
@@ -3035,7 +3056,7 @@ fn blueprint_sweep_over(
|
|||||||
binder.sweep_with_lattice(|point| {
|
binder.sweep_with_lattice(|point| {
|
||||||
let sources = data.windowed_sources(from, to, env);
|
let sources = data.windowed_sources(from, to, env);
|
||||||
let window = window_of(&sources).expect("non-empty in-sample window");
|
let window = window_of(&sources).expect("non-empty in-sample window");
|
||||||
run_blueprint_member(reload(doc), point, &space, sources, window, 0, pip, &topo, env)
|
run_blueprint_member(reload(doc), point, &space, sources, window, 0, pip, &topo, env, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3053,7 +3074,7 @@ fn run_oos_blueprint(
|
|||||||
let pip = data.pip_size();
|
let pip = data.pip_size();
|
||||||
let sources = data.windowed_sources(from, to, env);
|
let sources = data.windowed_sources(from, to, env);
|
||||||
let window = window_of(&sources).expect("non-empty out-of-sample window");
|
let window = window_of(&sources).expect("non-empty out-of-sample window");
|
||||||
let report = run_blueprint_member(reload, params, space, sources, window, 0, pip, topo, env);
|
let report = run_blueprint_member(reload, params, space, sources, window, 0, pip, topo, env, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K });
|
||||||
(Vec::new(), report)
|
(Vec::new(), report)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3160,7 +3181,7 @@ fn blueprint_mc_family(
|
|||||||
let family = monte_carlo(&base_point, &seeds, |seed, _base| {
|
let family = monte_carlo(&base_point, &seeds, |seed, _base| {
|
||||||
let sources = synthetic_walk_sources(seed);
|
let sources = synthetic_walk_sources(seed);
|
||||||
let window = window_of(&sources).expect("non-empty synthetic walk");
|
let window = window_of(&sources).expect("non-empty synthetic walk");
|
||||||
run_blueprint_member(reload(doc), &[], &space, sources, window, seed, pip, &topo, env)
|
run_blueprint_member(reload(doc), &[], &space, sources, window, seed, pip, &topo, env, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K })
|
||||||
});
|
});
|
||||||
// Silent-vacuous MC guard (refuse-don't-guess, C10): with >= 2 seeds, if every draw's
|
// Silent-vacuous MC guard (refuse-don't-guess, C10): with >= 2 seeds, if every draw's
|
||||||
// metrics are bit-identical to the first, no seed reached a distinguishable realization —
|
// metrics are bit-identical to the first, no seed reached a distinguishable realization —
|
||||||
|
|||||||
@@ -1404,18 +1404,15 @@ const GER40_SEPT2024_FROM_MS: &str = "1725148800000";
|
|||||||
const GER40_SEPT2024_TO_MS: &str = "1727740799999";
|
const GER40_SEPT2024_TO_MS: &str = "1727740799999";
|
||||||
|
|
||||||
/// Characterization pin (grounding-check ratification for the risk-regime-axis
|
/// Characterization pin (grounding-check ratification for the risk-regime-axis
|
||||||
/// spec): the dissolved real-data sweep member manifest carries ONLY the swept
|
/// spec): the dissolved real-data sweep member manifest stamps its resolved stop
|
||||||
/// signal axes as `params` — NO `stop_length` / `stop_k`. The R-path stop is a
|
/// (`stop_length`/`stop_k`) — the default regime here. The R-path stop knobs are
|
||||||
/// `wrap_r` constant baked OUTSIDE the signal `param_space`, so it never reaches
|
/// bound via `run_blueprint_member`'s `stop` param and stamped into the manifest
|
||||||
/// a campaign member manifest today; the sibling
|
/// alongside the swept signal axes; the sibling
|
||||||
/// `sweep_real_blueprint_member_lines_pin_the_dissolved_contract` locates each
|
/// `sweep_real_blueprint_member_lines_pin_the_dissolved_contract` locates each
|
||||||
/// binding with `find` and so never pins this ABSENCE. This pins the stop-less
|
/// binding with `find` and so never pins the exact resolved VALUES this test
|
||||||
/// baseline the risk-regime cycle deliberately extends: once the resolved regime
|
/// checks. Gated on the local GER40 archive; skips cleanly when absent.
|
||||||
/// is stamped into the manifest, this assertion flips to require the stop key —
|
|
||||||
/// the ratified additive contract extension, exactly like the instrument stamp.
|
|
||||||
/// Gated on the local GER40 archive; skips cleanly when absent.
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sweep_real_blueprint_member_manifest_carries_no_stop_param_today() {
|
fn sweep_real_blueprint_member_manifest_stamps_the_default_stop() {
|
||||||
if !local_data_present() {
|
if !local_data_present() {
|
||||||
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
|
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
|
||||||
return;
|
return;
|
||||||
@@ -1445,14 +1442,11 @@ fn sweep_real_blueprint_member_manifest_carries_no_stop_param_today() {
|
|||||||
let params = v["report"]["manifest"]["params"]
|
let params = v["report"]["manifest"]["params"]
|
||||||
.as_array()
|
.as_array()
|
||||||
.expect("manifest.params is an array");
|
.expect("manifest.params is an array");
|
||||||
let names: Vec<&str> = params.iter().filter_map(|p| p[0].as_str()).collect();
|
let get = |name: &str| params.iter().find(|p| p[0].as_str() == Some(name));
|
||||||
// The swept signal axis IS stamped ...
|
assert_eq!(get("stop_length").and_then(|p| p[1]["I64"].as_i64()), Some(3),
|
||||||
assert!(!names.is_empty(), "the swept signal axis is stamped: {line}");
|
"the resolved default stop length is stamped: {line}");
|
||||||
// ... and no stop param is (the baseline this cycle extends).
|
assert_eq!(get("stop_k").and_then(|p| p[1]["F64"].as_f64()), Some(2.0),
|
||||||
assert!(
|
"the resolved default stop k is stamped: {line}");
|
||||||
!names.iter().any(|n| n.contains("stop")),
|
|
||||||
"no stop param is stamped in the campaign member manifest today: {line}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1526,6 +1520,11 @@ fn sweep_real_blueprint_member_lines_pin_the_dissolved_contract() {
|
|||||||
Some("GER40"),
|
Some("GER40"),
|
||||||
"the dissolved sweep stamps the instrument: {line}"
|
"the dissolved sweep stamps the instrument: {line}"
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
params.iter().any(|p| p[0].as_str() == Some("stop_length"))
|
||||||
|
&& params.iter().any(|p| p[0].as_str() == Some("stop_k")),
|
||||||
|
"the dissolved sweep stamps the resolved stop: {line}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// exactly one new Sweep family persisted, with the grid's four members.
|
// exactly one new Sweep family persisted, with the grid's four members.
|
||||||
|
|||||||
@@ -1745,6 +1745,158 @@ fn campaign_run_real_e2e_sweep_gate_walkforward() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Property (#210 T3, Task 4 Step 9 — the regime->stop map): a campaign
|
||||||
|
/// document's non-default risk regime (`risk: [{ "vol": { length, k } }]`)
|
||||||
|
/// reaches the real `CliMemberRunner::run_member` path and IS the stop actually
|
||||||
|
/// stamped into each emitted member's manifest — `stop_length`/`stop_k` equal
|
||||||
|
/// the regime's own values (5, 3.5), not `R_SMA_STOP_LENGTH`/`R_SMA_STOP_K`
|
||||||
|
/// (3, 2.0). Every other campaign-run test in this file is regime-less (`risk`
|
||||||
|
/// absent), so they all only ever exercise the `None` arm of the campaign
|
||||||
|
/// member runner's regime match and would keep passing even if a regression
|
||||||
|
/// dropped `cell.regime` on the floor and always stamped the baked default;
|
||||||
|
/// this is the one test that actually drives the `Some(RiskRegime::Vol { .. })`
|
||||||
|
/// arm end to end. Gated on the local GER40 archive like its sibling e2e tests
|
||||||
|
/// above; skips on a data-less host.
|
||||||
|
#[test]
|
||||||
|
fn campaign_run_real_e2e_non_default_regime_stamps_its_own_stop() {
|
||||||
|
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("regime.process.json")),
|
||||||
|
ScratchPath::File(dir.join("regime.campaign.json")),
|
||||||
|
]);
|
||||||
|
let bp_id = seed_blueprint(dir, "campaign-run-regime-seed");
|
||||||
|
let proc_id = register_process_doc(dir, "regime.process.json", SWEEP_ONLY_PROCESS_DOC);
|
||||||
|
let base = campaign_doc_json(
|
||||||
|
&bp_id,
|
||||||
|
&proc_id,
|
||||||
|
(1725148800000, 1727740799999),
|
||||||
|
"",
|
||||||
|
"\"family_table\"",
|
||||||
|
);
|
||||||
|
let with_regime = base.replacen(
|
||||||
|
"\"seed\": 7,",
|
||||||
|
"\"seed\": 7,\n \"risk\": [ { \"vol\": { \"length\": 5, \"k\": 3.5 } } ],",
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
assert_ne!(with_regime, base, "replacen must actually match the fixture's seed field");
|
||||||
|
write_doc(dir, "regime.campaign.json", &with_regime);
|
||||||
|
let (out, code) = run_code_in(dir, &["campaign", "run", "regime.campaign.json"]);
|
||||||
|
|
||||||
|
if code == Some(1)
|
||||||
|
&& (out.contains("no recorded geometry") || out.contains("no data for instrument"))
|
||||||
|
{
|
||||||
|
eprintln!("skip: no local GER40 data for the campaign regime e2e");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert_eq!(code, Some(0), "stdout/stderr: {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}");
|
||||||
|
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");
|
||||||
|
let get = |name: &str| params.iter().find(|p| p[0].as_str() == Some(name));
|
||||||
|
assert_eq!(
|
||||||
|
get("stop_length").and_then(|p| p[1]["I64"].as_i64()),
|
||||||
|
Some(5),
|
||||||
|
"the campaign's own regime length is stamped, not the R_SMA default: {line}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get("stop_k").and_then(|p| p[1]["F64"].as_f64()),
|
||||||
|
Some(3.5),
|
||||||
|
"the campaign's own regime k is stamped, not the R_SMA default: {line}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Property (#210 T3/T4 — per-cell regime resolution, not a shared last-write):
|
||||||
|
/// a campaign with TWO distinct non-default risk regimes stamps EACH regime's
|
||||||
|
/// OWN stop into ITS OWN cell's members, never the other regime's values and
|
||||||
|
/// never both collapsing onto one. The single-regime sibling test above cannot
|
||||||
|
/// catch a bug where the executor's per-cell loop resolves the WRONG regime for
|
||||||
|
/// a cell (e.g. an off-by-one on `regime_ordinal`, or a closure that captures
|
||||||
|
/// the last-seen `regime` instead of the current one) — with only one non-
|
||||||
|
/// default entry, such a bug is invisible (there is nothing else it could pick).
|
||||||
|
/// Distinguishes the two cells' member lines by the `-r{regime_ordinal}-`
|
||||||
|
/// segment `run_cell` bakes into the family name (exec.rs), which is the only
|
||||||
|
/// externally observable handle on which cell a member line belongs to. Gated
|
||||||
|
/// on the local GER40 archive like its siblings; skips on a data-less host.
|
||||||
|
#[test]
|
||||||
|
fn campaign_run_real_e2e_two_regimes_each_stamp_their_own_stop() {
|
||||||
|
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("tworegimes.process.json")),
|
||||||
|
ScratchPath::File(dir.join("tworegimes.campaign.json")),
|
||||||
|
]);
|
||||||
|
let bp_id = seed_blueprint(dir, "campaign-run-two-regimes-seed");
|
||||||
|
let proc_id = register_process_doc(dir, "tworegimes.process.json", SWEEP_ONLY_PROCESS_DOC);
|
||||||
|
let base = campaign_doc_json(
|
||||||
|
&bp_id,
|
||||||
|
&proc_id,
|
||||||
|
(1725148800000, 1727740799999),
|
||||||
|
"",
|
||||||
|
"\"family_table\"",
|
||||||
|
);
|
||||||
|
let with_regimes = base.replacen(
|
||||||
|
"\"seed\": 7,",
|
||||||
|
"\"seed\": 7,\n \"risk\": [ { \"vol\": { \"length\": 5, \"k\": 3.5 } }, \
|
||||||
|
{ \"vol\": { \"length\": 8, \"k\": 1.2 } } ],",
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
assert_ne!(with_regimes, base, "replacen must actually match the fixture's seed field");
|
||||||
|
write_doc(dir, "tworegimes.campaign.json", &with_regimes);
|
||||||
|
let (out, code) = run_code_in(dir, &["campaign", "run", "tworegimes.campaign.json"]);
|
||||||
|
|
||||||
|
if code == Some(1)
|
||||||
|
&& (out.contains("no recorded geometry") || out.contains("no data for instrument"))
|
||||||
|
{
|
||||||
|
eprintln!("skip: no local GER40 data for the campaign two-regime e2e");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert_eq!(code, Some(0), "stdout/stderr: {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 expect_for = |regime_tag: &str, want_length: i64, want_k: f64| {
|
||||||
|
let matches: Vec<&&str> = member_lines
|
||||||
|
.iter()
|
||||||
|
.filter(|line| line.contains(regime_tag))
|
||||||
|
.collect();
|
||||||
|
assert!(!matches.is_empty(), "at least one member line tagged {regime_tag}: {out}");
|
||||||
|
for line in matches {
|
||||||
|
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");
|
||||||
|
let get = |name: &str| params.iter().find(|p| p[0].as_str() == Some(name));
|
||||||
|
assert_eq!(
|
||||||
|
get("stop_length").and_then(|p| p[1]["I64"].as_i64()),
|
||||||
|
Some(want_length),
|
||||||
|
"{regime_tag} stamps its own stop_length, not the other regime's: {line}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get("stop_k").and_then(|p| p[1]["F64"].as_f64()),
|
||||||
|
Some(want_k),
|
||||||
|
"{regime_tag} stamps its own stop_k, not the other regime's: {line}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect_for("-r0-", 5, 3.5);
|
||||||
|
expect_for("-r1-", 8, 1.2);
|
||||||
|
}
|
||||||
|
|
||||||
/// Property: an UNPRODUCIBLE tap requested alongside a producible one degrades
|
/// Property: an UNPRODUCIBLE tap requested alongside a producible one degrades
|
||||||
/// gracefully, never a crash or a silently-dropped whole request. `net_r_equity`
|
/// gracefully, never a crash or a silently-dropped whole request. `net_r_equity`
|
||||||
/// is in the closed tap vocabulary (`validate_campaign` accepts it) but has no
|
/// is in the closed tap vocabulary (`validate_campaign` accepts it) but has no
|
||||||
|
|||||||
Reference in New Issue
Block a user