diff --git a/crates/aura-research/src/lib.rs b/crates/aura-research/src/lib.rs index 34cb060..f754de4 100644 --- a/crates/aura-research/src/lib.rs +++ b/crates/aura-research/src/lib.rs @@ -1381,6 +1381,38 @@ mod tests { assert!(matches!(parse_process("not json"), Err(DocError::NotJson(_)))); } + /// #301 (fieldtest cycle-300 df_3): the unknown-select-rule refusal must + /// teach the accepted vocabulary, not just refuse — an author who learned + /// bare `plateau` at the CLI pastes it into a document and needs the + /// roster (argmax | plateau:mean | plateau:worst) in the refusal prose to + /// self-serve the fix. The schema stays strict: bare "plateau" remains + /// refused in documents (#300 F7 — it is a CLI-only argv alias), and the + /// prose stays sparse — the roster only, no alias-bridging sentence. + #[test] + fn unknown_select_rule_refusal_enumerates_accepted_rules() { + let bare_plateau = PROCESS_FIXTURE.replacen("plateau:worst", "plateau", 1); + let err = + parse_process(&bare_plateau).expect_err("bare plateau stays refused in documents"); + let DocError::Malformed(msg) = err else { + panic!("expected DocError::Malformed, got {err:?}"); + }; + // The existing contract survives: the offending value stays named, quoted. + assert!( + msg.contains("unknown select rule \"plateau\""), + "offending value no longer named: {msg}" + ); + // The new behaviour: the refusal enumerates the accepted rules. + for accepted in ["argmax", "plateau:mean", "plateau:worst"] { + assert!( + msg.contains(accepted), + "accepted rule {accepted:?} not enumerated in the refusal: {msg}" + ); + } + // Precise and sparse: the roster alone rescues the paste case — no + // alias-hint sentence bridging plateau -> plateau:mean. + assert!(!msg.contains("alias"), "refusal grew an alias hint: {msg}"); + } + pub(crate) const CAMPAIGN_FIXTURE: &str = r#"{ "format_version": 1, "kind": "campaign",