test(cli),docs: risk-axis introspection pins + guide/glossary coverage (#216 slice 2)
closes #216 The remaining introspection e2e pins: the bare-{} envelope probe (the issue's exact reproduction) lists the optional risk slot; a bound (non-empty) risk list closes it (no open slots); an EXPLICIT empty 'risk': [] still counts as open (pins the non-emptiness branch, which an is_none()-based probe would get wrong); the --block std::risk assert is sharpened to pin the optional rendering and the block doc line (the old bare contains("risk") matched std::risk trivially). Docs: the authoring guide's bare-{} transcript gains the risk line, the §3 worked example gains a two-regime risk section (heading updated), a prose paragraph explains the structural risk axis (stop defines R; absent = the same default vol regime the verbs bind), and both validate transcripts show the new matrix counts. The glossary gains the 'risk regime' entry (canonical term; avoid: stop regime, risk section). Verified: cargo test --workspace 62 result groups / 0 failures; clippy -D warnings clean; cargo doc 0 warnings; the guide's transcript strings grep-match the shipped hint/summary bytes (1/2/1).
This commit is contained in:
@@ -615,7 +615,14 @@ fn campaign_introspect_block_risk_describes_the_regime_shape() {
|
||||
out.contains("list of stop regimes { vol: { length, k } }; absent = one default regime"),
|
||||
"the block description must name the regime shape: {out}"
|
||||
);
|
||||
assert!(out.contains("risk"), "stdout/stderr: {out}");
|
||||
assert!(
|
||||
out.contains("optional, list of stop regimes { vol: { length, k } }; absent = one default regime"),
|
||||
"the risk slot must render as optional: {out}"
|
||||
);
|
||||
assert!(
|
||||
out.contains("std::risk — campaign section: the structural risk axis"),
|
||||
"stdout/stderr: {out}"
|
||||
);
|
||||
}
|
||||
|
||||
/// #216: a complete-but-risk-less campaign document (the exact shape a
|
||||
@@ -639,6 +646,63 @@ fn campaign_introspect_unwired_lists_the_risk_slot_when_absent() {
|
||||
);
|
||||
}
|
||||
|
||||
/// #216: the bare-envelope probe (`{}` — the issue's exact reproduction)
|
||||
/// names the optional risk axis alongside the required envelope slots.
|
||||
#[test]
|
||||
fn campaign_introspect_unwired_lists_the_optional_risk_slot_on_bare_envelope() {
|
||||
let dir = temp_cwd("campaign-introspect-unwired-risk-bare");
|
||||
write_doc(&dir, "bare.json", "{}");
|
||||
let (out, code) = run_code_in(&dir, &["campaign", "introspect", "--unwired", "bare.json"]);
|
||||
assert_eq!(code, Some(0));
|
||||
assert!(
|
||||
out.contains("open slot: risk (optional, list of stop regimes { vol: { length, k } }; absent = one default regime)"),
|
||||
"stdout/stderr: {out}"
|
||||
);
|
||||
}
|
||||
|
||||
/// #216: a bound (non-empty) risk list closes the slot — a complete document
|
||||
/// with risk reports no open slots at all.
|
||||
#[test]
|
||||
fn campaign_introspect_unwired_omits_a_bound_risk_slot() {
|
||||
let dir = temp_cwd("campaign-introspect-unwired-risk-bound");
|
||||
let with_risk = CAMPAIGN_DOC.replacen(
|
||||
"\"seed\": 1,",
|
||||
"\"seed\": 1,\n \"risk\": [ { \"vol\": { \"length\": 3, \"k\": 2.0 } } ],",
|
||||
1,
|
||||
);
|
||||
assert_ne!(with_risk, CAMPAIGN_DOC, "replacen must actually match the fixture's seed field");
|
||||
write_doc(&dir, "bound.campaign.json", &with_risk);
|
||||
let (out, code) =
|
||||
run_code_in(&dir, &["campaign", "introspect", "--unwired", "bound.campaign.json"]);
|
||||
assert_eq!(code, Some(0));
|
||||
assert!(!out.contains("open slot: risk"), "bound risk must not list: {out}");
|
||||
assert!(out.contains("no open slots"), "stdout/stderr: {out}");
|
||||
}
|
||||
|
||||
/// #216: an EXPLICIT `"risk": []` (key present, array empty) is still an open
|
||||
/// slot — same as the key being absent entirely. The probe distinguishes
|
||||
/// "bound" from "unbound" by non-emptiness, not by key presence
|
||||
/// (`v.get("risk").and_then(|r| r.as_array()).is_some_and(|a| !a.is_empty())`);
|
||||
/// a probe that instead branched on `is_none()` would wrongly treat an
|
||||
/// explicit empty list as closed, and no other test in this file exercises
|
||||
/// that branch (the absent-key and non-empty-array cases are covered above).
|
||||
#[test]
|
||||
fn campaign_introspect_unwired_lists_the_risk_slot_when_explicitly_empty() {
|
||||
let dir = temp_cwd("campaign-introspect-unwired-risk-explicit-empty");
|
||||
let explicit_empty = CAMPAIGN_DOC.replacen("\"seed\": 1,", "\"seed\": 1,\n \"risk\": [],", 1);
|
||||
assert_ne!(explicit_empty, CAMPAIGN_DOC, "replacen must actually match the fixture's seed field");
|
||||
write_doc(&dir, "empty-risk.campaign.json", &explicit_empty);
|
||||
let (out, code) =
|
||||
run_code_in(&dir, &["campaign", "introspect", "--unwired", "empty-risk.campaign.json"]);
|
||||
assert_eq!(code, Some(0), "stdout/stderr: {out}");
|
||||
assert!(
|
||||
out.contains(
|
||||
"open slot: risk (optional, list of stop regimes { vol: { length, k } }; absent = one default regime)"
|
||||
),
|
||||
"an explicit empty risk array must still count as an open slot: {out}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn campaign_introspect_unwired_reports_the_spec_example_slots() {
|
||||
let dir = temp_cwd("campaign-introspect-unwired");
|
||||
|
||||
Reference in New Issue
Block a user