From 75bfb9326fdd3ada6f4bd50cbe4fbb067524f0a1 Mon Sep 17 00:00:00 2001 From: claude Date: Mon, 13 Jul 2026 23:39:38 +0200 Subject: [PATCH] =?UTF-8?q?audit:=20batch=20tidy=20=E2=80=94=20exhaustive?= =?UTF-8?q?=20bind-error=20prose=20+=20the=20carry=20unit=20note?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cycle-close audit over the /boss batch 84e1075..bdafbde (#258 #259 #264 #266 #247 #265 #269). Architect verdict: substance clean — C1 byte-identity (conduit serde-skipped, pinned floats verbatim, uncosted bit-identical), C10 one-home-for-cost (all four conduit consumers read the already-netted series), C18 (conduit off the wire, --version and manifest single-sourced) all hold. Two medium debt items, both fixed here rather than carried: - render_bind_error's catch-all Debug-framed the remaining BindError variants, making #269's no-identifier-on-stderr claim argv-gated rather than structural. The match is now exhaustive: DuplicateBinding, EmptyAxis, EmptyRange render as axis-usage prose; a Compile fault — a blueprint defect, not a usage error — renders as a prose frame with the compile detail explicitly labelled internal (per-variant CompileError prose belongs to the graph-build surface, not this boundary). Unit tests pin the arms. - The #265 knob-unit notes covered constant and vol_slippage but not carry.carry_per_cycle, which carries the identical price-unit-vs-R trap; the note set and its pins now cover all three cost components. No regression scripts are configured (the architect is the sole gate); full workspace suite green, clippy clean. Spec and plan working files discarded per the audit lifecycle. refs #265 refs #269 --- crates/aura-cli/src/main.rs | 49 +++++++++++++++++++++++++- crates/aura-cli/tests/research_docs.rs | 9 +++++ crates/aura-research/src/lib.rs | 3 ++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/crates/aura-cli/src/main.rs b/crates/aura-cli/src/main.rs index c9b3698..e589e77 100644 --- a/crates/aura-cli/src/main.rs +++ b/crates/aura-cli/src/main.rs @@ -1935,7 +1935,25 @@ fn render_bind_error(e: &BindError) -> String { see `aura sweep --list-axes`" ), BindError::UnknownKnob(msg) => msg.clone(), - other => format!("{other:?}"), + BindError::DuplicateBinding(name) => format!( + "axis {name}: bound twice — each param takes exactly one axis — \ + see `aura sweep --list-axes`" + ), + BindError::EmptyAxis(name) => format!( + "axis {name}: supplies no values — give at least one, e.g. `--axis {name}=2,4`" + ), + BindError::EmptyRange(name) => format!( + "axis {name}: the named range is empty — give it at least one value" + ), + // A blueprint defect, not an axis usage error: the point passed name + // resolution but its bootstrap failed. Prose frame with the compile + // detail explicitly labelled as internal — per-variant prose for + // CompileError belongs to the graph-build surface, not this boundary. + BindError::Compile(e) => format!( + "the resolved axis point failed to bootstrap — the blueprint is \ + defective at this point, re-validate it with `aura graph build` \ + (internal detail: {e:?})" + ), } } @@ -4250,6 +4268,35 @@ mod tests { assert_eq!(intersect_shared_window(&symbols, &windows), Ok((100, 200))); } + /// Audit follow-up to #247/#269: `render_bind_error` is exhaustive — the + /// axis-usage variants the old catch-all Debug-framed render as prose + /// naming the axis, with no Rust identifier on the user's stderr. + #[test] + fn render_bind_error_prose_covers_the_axis_usage_variants() { + let dup = render_bind_error(&aura_engine::BindError::DuplicateBinding("a.b".into())); + assert!(dup.contains("a.b") && dup.contains("bound twice"), "{dup}"); + assert!(!dup.contains("DuplicateBinding"), "Debug leak: {dup}"); + let empty = render_bind_error(&aura_engine::BindError::EmptyAxis("a.b".into())); + assert!(empty.contains("a.b") && empty.contains("no values"), "{empty}"); + assert!(!empty.contains("EmptyAxis"), "Debug leak: {empty}"); + let range = render_bind_error(&aura_engine::BindError::EmptyRange("a.b".into())); + assert!(range.contains("a.b") && !range.contains("EmptyRange"), "Debug leak: {range}"); + } + + /// Audit follow-up to #247/#269: a `Compile` fault at the sweep boundary — + /// a blueprint defect, not an axis usage error — renders as a prose frame + /// that names the situation and labels the embedded compile detail as + /// internal, instead of leaking the bare Debug struct as the whole message. + #[test] + fn render_bind_error_frames_a_compile_fault_as_prose_with_labeled_detail() { + let msg = render_bind_error(&aura_engine::BindError::Compile( + aura_engine::CompileError::BadInteriorIndex, + )); + assert!(msg.contains("failed to bootstrap"), "{msg}"); + assert!(msg.contains("internal detail"), "{msg}"); + assert!(!msg.starts_with("Compile"), "the frame must lead with prose: {msg}"); + } + /// An unknown symbol (no archive files at all — an empty month list) /// refuses rather than reporting a bogus empty-span coverage (#264): the /// caller eprintln's the message and exits 1. diff --git a/crates/aura-cli/tests/research_docs.rs b/crates/aura-cli/tests/research_docs.rs index ad5b1e7..e75cbdd 100644 --- a/crates/aura-cli/tests/research_docs.rs +++ b/crates/aura-cli/tests/research_docs.rs @@ -717,6 +717,15 @@ fn campaign_introspect_unwired_annotates_cost_and_risk_knob_units() { low.contains("scales the stop distance"), "vol.k must be annotated as SCALING the stop distance (the risk-unit lever): {out}" ); + + // carry.carry_per_cycle — the identical price-unit-vs-R trap, accruing per + // held cycle (audit follow-up to #265: the note set must cover ALL three + // cost components, not two of three). + assert!(low.contains("carry_per_cycle"), "the carry knob must still be named: {out}"); + assert!( + low.contains("price units per held cycle"), + "carry_per_cycle must be annotated as a PRICE-unit per-held-cycle accrual: {out}" + ); } #[test] diff --git a/crates/aura-research/src/lib.rs b/crates/aura-research/src/lib.rs index 9de5278..8987d46 100644 --- a/crates/aura-research/src/lib.rs +++ b/crates/aura-research/src/lib.rs @@ -1095,6 +1095,7 @@ pub fn open_slots_campaign(text: &str) -> Result, DocError> { vec![ "constant.cost_per_trade is in price units, not R (charged per close as cost/|entry-stop| in R).".to_string(), "vol_slippage.slip_vol_mult is a multiplier on the per-cycle vol estimate (charged in R likewise).".to_string(), + "carry.carry_per_cycle is in price units per held cycle, not R (accrues over the hold, emitted in R likewise).".to_string(), ], )); } @@ -1966,6 +1967,7 @@ mod tests { vec![ "constant.cost_per_trade is in price units, not R (charged per close as cost/|entry-stop| in R).".to_string(), "vol_slippage.slip_vol_mult is a multiplier on the per-cycle vol estimate (charged in R likewise).".to_string(), + "carry.carry_per_cycle is in price units per held cycle, not R (accrues over the hold, emitted in R likewise).".to_string(), ], ), ] @@ -2046,6 +2048,7 @@ mod tests { vec![ "constant.cost_per_trade is in price units, not R (charged per close as cost/|entry-stop| in R).".to_string(), "vol_slippage.slip_vol_mult is a multiplier on the per-cycle vol estimate (charged in R likewise).".to_string(), + "carry.carry_per_cycle is in price units per held cycle, not R (accrues over the hold, emitted in R likewise).".to_string(), ], )] );