feat(aura-runner): skipped-tap note; undeclared-tap refusal enumerates declared taps
Two fold-selector fieldtest findings on the --tap seam, RED-first. Skipped-tap note (#334): an explicit --tap plan replaces the record-all default entirely (C27: unbound is inert) but gave no runtime signal that unlisted declared taps went unbound — a forgotten tap surfaced only as a missing trace file. bind_tap_plan's unbound arm now emits the C14 benign note (aura: note: declared tap "<name>" unbound this run) per skipped tap, exit unaffected. No new carrier: the arm is only reachable when plan.default_named is None (an explicit plan) — under record-all it is structurally dead, so the default emits nothing. Emitted runner-side beside the existing eprintln registers; the runner->CLI print migration is #297. Undeclared-tap refusal (#333): --tap on a tap the blueprint does not declare was refused without naming the valid taps, forcing the author back into the blueprint JSON. TapPlanError::UnknownTap now carries the declared-tap roster and Display appends it, mirroring UnknownLabel's fold-roster idiom (C29: a refusal names the closed set it checks against). Decision minuted on #333 (refusal roster over a new introspect view). Rider (#335): the fold roster doc lines now state the summary-row timestamp — the accumulator folds (count/sum/mean/min/max) land one row at the last warm (contributing) value's ts, and last gains the same at-its-own-timestamp wording first already carried. The ratification record lands in the sibling ledger/glossary commit. closes #334 closes #333
This commit is contained in:
@@ -322,13 +322,17 @@ fn run_tap_selector_refuses_an_unknown_label_with_the_roster() {
|
||||
assert!(!cwd.join("runs").exists(), "a refused run must not write the store");
|
||||
}
|
||||
|
||||
/// #310: a selection naming a tap the blueprint does not declare is
|
||||
/// refused typed (UndeclaredTap), before any store write.
|
||||
/// #310/#333: a selection naming a tap the blueprint does not declare is
|
||||
/// refused typed (UndeclaredTap), before any store write, and the refusal
|
||||
/// enumerates the blueprint's declared taps (the same way the unknown-fold
|
||||
/// refusal enumerates the fold-registry roster) — on the two-tap fixture,
|
||||
/// both `fast_tap` and `slow_tap` must be named so the author does not have
|
||||
/// to reopen the blueprint JSON.
|
||||
#[test]
|
||||
fn run_tap_selector_refuses_an_undeclared_tap() {
|
||||
let cwd = temp_cwd("tap-selector-undeclared");
|
||||
let bp_path = cwd.join("tapped_r_sma.json");
|
||||
std::fs::write(&bp_path, tap_blueprint_json()).expect("write tapped blueprint");
|
||||
let bp_path = cwd.join("two_tap.json");
|
||||
std::fs::write(&bp_path, two_tap_blueprint_json()).expect("write two-tap blueprint");
|
||||
let out = Command::new(BIN)
|
||||
.args(["run", bp_path.to_str().expect("utf-8 path"), "--tap", "nope=mean"])
|
||||
.current_dir(&cwd)
|
||||
@@ -337,9 +341,61 @@ fn run_tap_selector_refuses_an_undeclared_tap() {
|
||||
assert!(!out.status.success(), "an undeclared tap must refuse");
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(stderr.contains("nope"), "refusal must name the offending tap: {stderr}");
|
||||
assert!(
|
||||
stderr.contains("fast_tap") && stderr.contains("slow_tap"),
|
||||
"refusal must enumerate the blueprint's declared taps: {stderr}"
|
||||
);
|
||||
assert!(!cwd.join("runs").exists(), "a refused run must not write the store");
|
||||
}
|
||||
|
||||
/// #334: an explicit `--tap` plan that leaves a declared tap unbound emits
|
||||
/// the C14 benign skipped-tap note on stderr, per unbound tap, naming it —
|
||||
/// exit code stays 0 (this is a note, not a refusal). `fast_tap` (subscribed)
|
||||
/// gets no such note; `slow_tap` (left unlisted) does.
|
||||
#[test]
|
||||
fn run_tap_selector_notes_unbound_declared_taps_on_stderr() {
|
||||
let cwd = temp_cwd("tap-selector-unbound-note");
|
||||
let bp_path = cwd.join("two_tap.json");
|
||||
std::fs::write(&bp_path, two_tap_blueprint_json()).expect("write two-tap blueprint");
|
||||
|
||||
let out = Command::new(BIN)
|
||||
.args(["run", bp_path.to_str().expect("utf-8 path"), "--tap", "fast_tap=mean"])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.expect("spawn aura run");
|
||||
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(
|
||||
stderr.contains("aura: note: declared tap \"slow_tap\" unbound this run"),
|
||||
"stderr must note the unbound declared tap: {stderr}"
|
||||
);
|
||||
assert!(
|
||||
!stderr.contains("\"fast_tap\" unbound"),
|
||||
"the subscribed tap must not be noted as unbound: {stderr}"
|
||||
);
|
||||
}
|
||||
|
||||
/// #334: the record-all default (no `--tap` flag) leaves no declared tap
|
||||
/// unbound — nothing is skipped, so the note must never appear.
|
||||
#[test]
|
||||
fn run_with_no_tap_flag_emits_no_unbound_note() {
|
||||
let cwd = temp_cwd("tap-selector-default-no-note");
|
||||
let bp_path = cwd.join("two_tap.json");
|
||||
std::fs::write(&bp_path, two_tap_blueprint_json()).expect("write two-tap blueprint");
|
||||
|
||||
let out = Command::new(BIN)
|
||||
.args(["run", bp_path.to_str().expect("utf-8 path")])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.expect("spawn aura run");
|
||||
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(
|
||||
!stderr.contains("unbound this run"),
|
||||
"the record-all default must never note a skipped tap: {stderr}"
|
||||
);
|
||||
}
|
||||
|
||||
/// #310: a malformed or duplicate `--tap` is a usage error (exit 2)
|
||||
/// before anything runs.
|
||||
#[test]
|
||||
|
||||
@@ -1728,9 +1728,14 @@ mod tests {
|
||||
Ok(_) => panic!("unknown tap must be refused"),
|
||||
};
|
||||
assert!(
|
||||
matches!(err, TapPlanError::UnknownTap { ref name } if name == "no_such_tap"),
|
||||
matches!(err, TapPlanError::UnknownTap { ref name, .. } if name == "no_such_tap"),
|
||||
"{err:?}"
|
||||
);
|
||||
let msg = err.to_string();
|
||||
assert!(
|
||||
msg.contains("fast_tap"),
|
||||
"unknown-tap refusal enumerates the declared taps (fixture declares only fast_tap): {msg}"
|
||||
);
|
||||
|
||||
// Unknown label — the refusal enumerates the roster.
|
||||
let mut flat2 = tapped_r_sma().compile_with_params(&[]).expect("tapped r_sma compiles");
|
||||
|
||||
@@ -161,17 +161,21 @@ impl FoldRegistry {
|
||||
}),
|
||||
});
|
||||
for (label, doc, fold) in [
|
||||
("count", "number of warm rows (any kind; i64 row)", FoldKind::Count),
|
||||
("sum", "sum of the series (f64 taps; f64 row)", FoldKind::Sum),
|
||||
("mean", "arithmetic mean of the series (f64 taps; f64 row)", FoldKind::Mean),
|
||||
("min", "minimum of the series (f64 taps; f64 row)", FoldKind::Min),
|
||||
("max", "maximum of the series (f64 taps; f64 row)", FoldKind::Max),
|
||||
("count", "number of warm rows (any kind; i64 row); one row at the last warm ts", FoldKind::Count),
|
||||
("sum", "sum of the series (f64 taps; f64 row); one row at the last warm ts", FoldKind::Sum),
|
||||
(
|
||||
"mean",
|
||||
"arithmetic mean of the series (f64 taps; f64 row); one row at the last warm ts",
|
||||
FoldKind::Mean,
|
||||
),
|
||||
("min", "minimum of the series (f64 taps; f64 row); one row at the last warm ts", FoldKind::Min),
|
||||
("max", "maximum of the series (f64 taps; f64 row); one row at the last warm ts", FoldKind::Max),
|
||||
(
|
||||
"first",
|
||||
"first warm value, at its own timestamp (any kind; kind-preserving row)",
|
||||
FoldKind::First,
|
||||
),
|
||||
("last", "last warm value (any kind; kind-preserving row)", FoldKind::Last),
|
||||
("last", "last warm value, at its own timestamp (any kind; kind-preserving row)", FoldKind::Last),
|
||||
] {
|
||||
r.register(FoldEntry {
|
||||
label,
|
||||
@@ -217,7 +221,7 @@ impl FoldRegistry {
|
||||
/// `aura: ` + exit-1 refusal register via `Display`.
|
||||
pub enum TapPlanError {
|
||||
/// The plan names a tap the blueprint does not declare.
|
||||
UnknownTap { name: String },
|
||||
UnknownTap { name: String, declared: Vec<String> },
|
||||
/// The plan names a label the registry does not carry.
|
||||
UnknownLabel { label: String, roster: Vec<&'static str> },
|
||||
/// The entry's bind rule rejects the tap's column kind.
|
||||
@@ -237,8 +241,12 @@ pub enum TapPlanError {
|
||||
impl fmt::Display for TapPlanError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TapPlanError::UnknownTap { name } => {
|
||||
write!(f, "the tap plan names '{name}', but the blueprint declares no such tap")
|
||||
TapPlanError::UnknownTap { name, declared } => {
|
||||
write!(
|
||||
f,
|
||||
"the tap plan names '{name}', but the blueprint declares no such tap — declared taps: {}",
|
||||
declared.join(", ")
|
||||
)
|
||||
}
|
||||
TapPlanError::UnknownLabel { label, roster } => {
|
||||
write!(f, "unknown fold '{label}' — available: {}", roster.join(", "))
|
||||
@@ -402,7 +410,10 @@ pub fn bind_tap_plan(
|
||||
// Unknown-tap guard: every plan name must be declared.
|
||||
for name in plan.by_name.keys() {
|
||||
if !declared_taps.iter().any(|t| &t.name == name) {
|
||||
return Err(TapPlanError::UnknownTap { name: name.clone() });
|
||||
return Err(TapPlanError::UnknownTap {
|
||||
name: name.clone(),
|
||||
declared: declared_taps.iter().map(|t| t.name.clone()).collect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +433,18 @@ pub fn bind_tap_plan(
|
||||
Some((label, params)) => {
|
||||
Resolved::Named { label: label.clone(), params: params.clone() }
|
||||
}
|
||||
None => continue, // unbound, inert (C27)
|
||||
// Unbound, inert (C27) — but only reachable when `plan` carries
|
||||
// no default (an EXPLICIT plan, e.g. from `--tap`): record-all
|
||||
// (`default_named` = `Some(("record", …))`) always resolves the
|
||||
// Some arm above, so this arm never fires under record-all and
|
||||
// the note is exactly the C14 benign skipped-tap class (#334).
|
||||
// Emitted here (aura-runner), beside the pre-existing
|
||||
// runner-side `eprintln!` registers in this module/`member.rs`/
|
||||
// `measure.rs` — the runner→CLI print migration is #297.
|
||||
None => {
|
||||
eprintln!("aura: note: declared tap \"{}\" unbound this run", tap.name);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
};
|
||||
if let Resolved::Named { label, params } = &sub {
|
||||
|
||||
Reference in New Issue
Block a user