feat(cli): dissolved --trace requests only producible taps (#240)

persist_taps_from now filters the tap vocabulary through campaign_run's
own tap_channel classification (exposed pub(crate)) and drops the net
channel: every generated sugar campaign carries an empty cost section by
construction, so net_r_equity was unproducible there and the executor's
skip notice named a remedy (add a cost block to the campaign document)
unreachable from the sweep/walkforward verb. The authored-campaign path
keeps the notice verbatim — there the user wrote the document and the
remedy is reachable. The two full-vocabulary pins move to the producible
subset (deliberate pin move, documented in their doc comments).

closes #240
This commit is contained in:
2026-07-11 16:16:04 +02:00
parent 955c909769
commit 2be6cf8860
2 changed files with 46 additions and 19 deletions
+2 -2
View File
@@ -640,7 +640,7 @@ fn present_campaign(
/// (tx_net, #234) — produced only when the campaign document carries a cost
/// block (the caller's producibility check).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TapChannel {
pub(crate) enum TapChannel {
Equity,
Exposure,
REquity,
@@ -650,7 +650,7 @@ enum TapChannel {
/// Route one requested tap of the closed vocabulary to its drained channel;
/// `None` marks a name outside the vocabulary (unreachable — `validate_campaign`
/// refuses it — and mapped defensively rather than guessed at).
fn tap_channel(tap: &str) -> Option<TapChannel> {
pub(crate) fn tap_channel(tap: &str) -> Option<TapChannel> {
// The emit_vocabulary debug_assert's twin: every vocabulary tap must be
// routable here — a future fifth vocabulary entry fails loudly instead of
// silently skipping.
+44 -17
View File
@@ -46,12 +46,22 @@ pub(crate) struct SugarInvocation<'a> {
}
/// The generated campaign's `persist_taps` section for one invocation:
/// the full closed tap vocabulary when `--trace` was requested, else empty
/// (the #168 no-op — this is the one seam `translate_sweep`/
/// `translate_walkforward` share for the decision).
/// when `--trace` was requested, the tap vocabulary MINUS the net channel
/// (`net_r_equity`), else empty (the #168 no-op — this is the one seam
/// `translate_sweep`/`translate_walkforward` share for the decision). Every
/// generated sugar campaign carries an empty cost section (`cost: vec![]`)
/// by construction, so the net channel is never producible there; requesting
/// it anyway would make `campaign_run`'s executor print an unreachable "add a
/// cost block to the campaign document" remedy (#240). The exclusion reuses
/// `campaign_run`'s own tap/channel classification (`tap_channel`/
/// `TapChannel::Net`) rather than duplicating the net-tap name here.
fn persist_taps_from(trace: bool) -> Vec<String> {
if trace {
tap_vocabulary().iter().map(|t| t.to_string()).collect()
tap_vocabulary()
.iter()
.filter(|t| crate::campaign_run::tap_channel(t) != Some(crate::campaign_run::TapChannel::Net))
.map(|t| t.to_string())
.collect()
} else {
vec![]
}
@@ -742,19 +752,28 @@ mod tests {
assert!(err.contains("mixed value kinds"), "{err}");
}
/// Property (#224): `SugarInvocation.trace = true` requests the FULL closed
/// tap vocabulary on the generated campaign's `presentation.persist_taps` —
/// the honest inverse of the #168 no-op, where `persist_taps` stayed `vec![]`
/// regardless of `--trace`. `trace = false` (the default, pinned by the
/// sibling `translate_sweep_is_deterministic_and_names_flow`) keeps the
/// vocabulary empty.
/// Property (#224, pin moved to #240): `SugarInvocation.trace = true`
/// requests the tap vocabulary MINUS the net channel on the generated
/// campaign's `presentation.persist_taps` — the honest inverse of the
/// #168 no-op (where `persist_taps` stayed `vec![]` regardless of
/// `--trace`), narrowed to what the generated campaign's own empty cost
/// section can actually produce (#240: `net_r_equity` is unproducible
/// there and must be excluded). `trace = false` (the default, pinned by
/// the sibling `translate_sweep_is_deterministic_and_names_flow`) keeps
/// the vocabulary empty. Kept as its own translator-level pin alongside
/// the cross-translator `dissolved_trace_requests_only_producible_taps_
/// excluding_the_net_channel` property test (#240): that test pins the
/// shared `persist_taps_from` seam once for both translators together,
/// this one keeps `translate_sweep`'s own regression independent of the
/// walkforward sibling so a translator-specific wiring break is not
/// masked by the other translator still passing.
#[test]
fn translate_sweep_trace_requests_the_full_tap_vocabulary() {
fn translate_sweep_trace_requests_the_producible_tap_subset() {
let ax = axes();
let mut i = inv(&ax, "probe", &["GER40"], None, "{\"bp\":1}");
i.trace = true;
let g = translate_sweep(&i).unwrap();
let want: Vec<String> = aura_research::tap_vocabulary().iter().map(|t| t.to_string()).collect();
let want: Vec<String> = vec!["equity".to_string(), "exposure".to_string(), "r_equity".to_string()];
assert_eq!(g.campaign.presentation.persist_taps, want);
}
@@ -940,16 +959,24 @@ mod tests {
assert!(aura_campaign::preflight(&a.process, &a.campaign).is_ok());
}
/// Property (#224): mirrors `translate_sweep_trace_requests_the_full_tap_vocabulary`
/// for walkforward's translator — the walkforward sibling refusal lifts
/// identically, riding the same `persist_taps_from` seam.
/// Property (#224, pin moved to #240): mirrors
/// `translate_sweep_trace_requests_the_producible_tap_subset` for
/// walkforward's translator — the walkforward sibling refusal lifts
/// identically, riding the same `persist_taps_from` seam, and the same
/// net-channel exclusion applies (#240: the generated campaign's empty
/// cost section cannot produce `net_r_equity`). Kept as its own
/// translator-level pin alongside the cross-translator
/// `dissolved_trace_requests_only_producible_taps_excluding_the_net_
/// channel` property test for the same reason as the sweep sibling: a
/// walkforward-specific wiring break must not be masked by sweep alone
/// still passing.
#[test]
fn translate_walkforward_trace_requests_the_full_tap_vocabulary() {
fn translate_walkforward_trace_requests_the_producible_tap_subset() {
let ax = wf_axes();
let mut i = inv(&ax, "walkforward", &["GER40"], Some(VolStop { length: 14, k: 2.0 }), "{\"bp\":1}");
i.trace = true;
let g = translate_walkforward(&i, "sqn_normalized", WF_W, SelectRule::Argmax).unwrap();
let want: Vec<String> = aura_research::tap_vocabulary().iter().map(|t| t.to_string()).collect();
let want: Vec<String> = vec!["equity".to_string(), "exposure".to_string(), "r_equity".to_string()];
assert_eq!(g.campaign.presentation.persist_taps, want);
}