feat: the select-rule refusal enumerates the accepted roster (closes #301)
An unknown select rule in a process document now refuses with the accepted vocabulary beside the quoted offending value: block std::sweep: unknown select rule "plateau" (accepted: argmax | plateau:mean | plateau:worst) mirroring the roster-naming metric-refusal shape. The roster is derived from slot_kind_label(SlotKind::SelectRule) — CLI describe output and the document refusal stay in sync by construction — and a companion test pins the "select rule: " prefix that derivation trims, so a label-wording drift breaks a test instead of silently degrading the prose. No alias-hint sentence: the enumeration alone rescues the bare-plateau paste case (fieldtest df_3), and the document schema stays strict per #300 F7.
This commit is contained in:
@@ -295,10 +295,17 @@ fn select_from(
|
||||
) -> Result<SelectRule, String> {
|
||||
let s = require_str(m, "select", block)?;
|
||||
// SelectRule's own derived Deserialize (which carries the #[serde(rename)]
|
||||
// wire strings) is the type oracle, mirroring kind_tag/scalar_from_bare:
|
||||
// no second hardcoded copy of the three select-rule wire strings.
|
||||
serde_json::from_value(serde_json::Value::String(s.clone()))
|
||||
.map_err(|_| format!("block {block}: unknown select rule \"{s}\""))
|
||||
// wire strings) is the type oracle for *parsing*, mirroring
|
||||
// kind_tag/scalar_from_bare. The refusal's roster text below is sourced
|
||||
// from slot_kind_label(SlotKind::SelectRule) rather than a second
|
||||
// hardcoded copy of the three wire strings: CLI describe output and the
|
||||
// document refusal stay in sync by construction (add a SelectRule
|
||||
// variant, update one string).
|
||||
serde_json::from_value(serde_json::Value::String(s.clone())).map_err(|_| {
|
||||
let roster =
|
||||
slot_kind_label(SlotKind::SelectRule).trim_start_matches("select rule: ");
|
||||
format!("block {block}: unknown select rule \"{s}\" (accepted: {roster})")
|
||||
})
|
||||
}
|
||||
|
||||
fn mode_from(
|
||||
@@ -1413,6 +1420,20 @@ mod tests {
|
||||
assert!(!msg.contains("alias"), "refusal grew an alias hint: {msg}");
|
||||
}
|
||||
|
||||
/// The refusal derives its roster from slot_kind_label by trimming the
|
||||
/// "select rule: " prefix. That trim is a positional contract: if the
|
||||
/// label's wording ever drifts, trim_start_matches degrades to a no-op
|
||||
/// and the refusal grows a redundant prefix without any test noticing.
|
||||
/// This pin makes that drift loud.
|
||||
#[test]
|
||||
fn select_rule_label_keeps_the_prefix_the_refusal_trims() {
|
||||
assert!(
|
||||
slot_kind_label(SlotKind::SelectRule).starts_with("select rule: "),
|
||||
"slot_kind_label(SelectRule) no longer starts with \"select rule: \" — \
|
||||
update select_from's roster derivation alongside the label"
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) const CAMPAIGN_FIXTURE: &str = r#"{
|
||||
"format_version": 1,
|
||||
"kind": "campaign",
|
||||
|
||||
Reference in New Issue
Block a user