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
+10 -2
View File
@@ -430,12 +430,20 @@ fn validate_campaign_file(file: &PathBuf, env: &Env) -> Result<(), String> {
.iter()
.map(|s| s.axes.values().map(|a| a.values.len()).product::<usize>())
.sum();
let regimes = doc.risk.len().max(1);
let regime_marker = if doc.risk.is_empty() { " (default)" } else { "" };
let cells =
doc.strategies.len() * doc.data.instruments.len() * doc.data.windows.len() * regimes;
println!(
"campaign document valid (intrinsic): {} strategy(ies), {} axes ({} points), {} window(s)",
"campaign document valid (intrinsic): {} strategy(ies), {} axes ({} points), {} instrument(s), {} window(s), {} regime(s){}{} cell(s)",
doc.strategies.len(),
axes,
points,
doc.data.windows.len()
doc.data.instruments.len(),
doc.data.windows.len(),
regimes,
regime_marker,
cells
);
if env.provenance().is_some() {
let resolve = |t: &str| env.resolve(t);