$ aura campaign introspect --unwired a.json   # a.json = {}
open slot: data (required section: instruments + windows)
open slot: presentation (required section: persist_taps (equity | exposure | r_equity | net_r_equity) + emit)

$ aura campaign introspect --unwired b.json   # data filled, presentation left {}
open slot: description (optional, string — a one-line meaning (C29-gated when present))
open slot: risk (optional, list of stop regimes { vol: { length, k } } | { vol_tf: { period_minutes, length, k } }; absent = one default regime)
open slot: cost (optional, list of cost components { constant: { cost_per_trade } } | { vol_slippage: { slip_vol_mult } } | { carry: { carry_per_cycle } }; absent = zero costs (net == gross))
  ^ only OPTIONAL slots are listed. The two REQUIRED sub-slots of an empty
    presentation section (persist_taps, emit) are never named — contrast an empty
    data section, which drills down to instruments/windows (see above).

$ aura campaign validate b.json               # the slot the ladder never named
aura: campaign document: missing field `persist_taps`

$ aura campaign introspect --block std::presentation   # where the answer actually lives
std::presentation — campaign section: taps to persist + tables to emit (data-level only)
  persist_taps         required, list of: equity | exposure | r_equity | net_r_equity
      equity         cumulative pip equity per cycle
      exposure       signed position exposure per cycle
      r_equity       cumulative gross R per cycle
      net_r_equity   cumulative net R per cycle, after the cost model
  emit                 required, list of: family_table | selection_report
