feat(research,cli): make the risk axis discoverable — probe, roster, validate counts (#216 slice 1)

The structural risk-regime axis worked end to end but was invisible on the
public surface (refs #216): the --unwired probe swept only required slots
(risk is #[serde(default)]), no vocabulary block described RiskRegime, and
the validate summary counted axes/windows but no regimes or cells.

- open_slots_campaign gains its one optional-slot probe: a risk-less (or
  empty/non-array) document lists
  'open slot: risk (optional, list of stop regimes { vol: { length, k } };
  absent = one default regime)'; a bound list closes the slot. Never a
  required flip — content-id parity of risk-less documents is load-bearing.
- CAMPAIGN_SECTIONS gains std::risk (SlotKind::Regimes, required: false) so
  --vocabulary lists it and --block std::risk describes the regime shape.
- The intrinsic validate summary now reports the realized matrix, mirroring
  the exec cross-product exactly (strategies x instruments x windows x
  regimes; empty risk = one default cell, marked '(default)'). Points stay
  a grid property, deliberately not a cell factor.

Sanctioned migrations (recorded on #216): the two unit pins encoding the old
invisibility — open_slots_report_partial_documents_by_path now pins exactly
one open slot on the complete risk-less fixture (doubling as the probe's
unit pin; fixture bytes unchanged), and the presentation-hint test migrates
from full-vector equality to locating its entry (its purpose is the hint
text, not the vector length).

Verified: cargo test -p aura-research (29/0), -p aura-cli --test
research_docs (60/0), clippy -D warnings clean.
This commit is contained in:
2026-07-09 17:51:22 +02:00
parent 68317ec95d
commit a74c3d116e
3 changed files with 148 additions and 17 deletions
+84 -1
View File
@@ -547,15 +547,98 @@ fn campaign_validate_refuses_bad_risk_regime_prose_exit_1() {
assert!(!out.contains("BadRegime"), "Debug leak: {out}");
}
/// #216: the intrinsic validate summary reports the realized matrix — the
/// exec cross-product strategies × instruments × windows × regimes — so an
/// 8-cell document no longer reads as regime-less. Points (the axis grid) are
/// deliberately NOT a cell factor: 2 points here, yet 8 cells.
#[test]
fn campaign_validate_counts_regimes_and_cells() {
let dir = temp_cwd("campaign-validate-cells");
let doc = r#"{
"format_version": 1,
"kind": "campaign",
"name": "screen",
"data": { "instruments": ["GER40", "FRA40"],
"windows": [ { "from_ms": 1, "to_ms": 2 }, { "from_ms": 2, "to_ms": 3 } ] },
"risk": [ { "vol": { "length": 3, "k": 1.5 } }, { "vol": { "length": 3, "k": 3.0 } } ],
"strategies": [ { "ref": { "content_id": "9f3a" },
"axes": { "fast": { "kind": "I64", "values": [8, 16] } } } ],
"process": { "ref": { "content_id": "4e2d" } },
"seed": 1,
"presentation": { "persist_taps": [], "emit": ["family_table"] }
}"#;
write_doc(&dir, "cells.campaign.json", doc);
let (out, code) = run_code_in(&dir, &["campaign", "validate", "cells.campaign.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
assert!(
out.contains("campaign document valid (intrinsic): 1 strategy(ies), 1 axes (2 points), 2 instrument(s), 2 window(s), 2 regime(s) — 8 cell(s)"),
"stdout/stderr: {out}"
);
}
/// #216: a risk-less document counts its implicit default regime honestly —
/// one regime, marked "(default)" — instead of reading as regime-less.
#[test]
fn campaign_validate_counts_the_default_regime() {
let dir = temp_cwd("campaign-validate-default-regime");
write_doc(&dir, "c.campaign.json", CAMPAIGN_DOC);
let (out, code) = run_code_in(&dir, &["campaign", "validate", "c.campaign.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
assert!(
out.contains("campaign document valid (intrinsic): 1 strategy(ies), 1 axes (1 points), 1 instrument(s), 1 window(s), 1 regime(s) (default) — 1 cell(s)"),
"stdout/stderr: {out}"
);
}
#[test]
fn campaign_introspect_vocabulary_lists_sections() {
let (out, code) = run_code(&["campaign", "introspect", "--vocabulary"]);
assert_eq!(code, Some(0));
for id in ["std::data", "std::strategy", "std::process_ref", "std::presentation"] {
// #216: std::risk joins the roster the CLI's own --vocabulary listing
// enumerates — the regime axis is discoverable from the public surface,
// not only from the design ledger or a generated document.
for id in ["std::data", "std::strategy", "std::process_ref", "std::presentation", "std::risk"] {
assert!(out.contains(id), "vocabulary misses {id}: {out}");
}
}
/// #216: `--block std::risk` (the CLI's own introspection, not the
/// `aura-research` library directly) names the regime shape and its
/// absent-means-default semantics — the second half of "the risk-regime axis
/// is undiscoverable from the ... introspection surface" (mirrors the
/// existing `--block std::presentation` / tap-vocabulary pin).
#[test]
fn campaign_introspect_block_risk_describes_the_regime_shape() {
let (out, code) = run_code(&["campaign", "introspect", "--block", "std::risk"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
assert!(
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}");
}
/// #216: a complete-but-risk-less campaign document (the exact shape a
/// verb-generated document has before a `--stop-*` flag is added) has its one
/// remaining open slot — the optional risk axis — enumerated by the public
/// `campaign introspect --unwired` CLI surface. Before this iteration the
/// slot existed only in the `aura-research` library's own unit tests; a
/// consumer with just the built binary had no way to discover it (#216's
/// exact complaint: "never lists the risk slot").
#[test]
fn campaign_introspect_unwired_lists_the_risk_slot_when_absent() {
let dir = temp_cwd("campaign-introspect-unwired-risk");
write_doc(&dir, "c.campaign.json", CAMPAIGN_DOC);
let (out, code) = run_code_in(&dir, &["campaign", "introspect", "--unwired", "c.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)"
),
"stdout/stderr: {out}"
);
}
#[test]
fn campaign_introspect_unwired_reports_the_spec_example_slots() {
let dir = temp_cwd("campaign-introspect-unwired");