use crate::llm::capabilities::{CapabilitiesFile, ProviderRule, ToolFormatJustification};
use crate::llm_config::provider_is_self_hosted;
const TOOL_TASKS: [&str; 3] = ["agent", "code", "verify"];
const NATIVE_UNRELIABLE_TOOL_FAMILIES: &[(&str, &str)] = &[];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CapabilityFootgun {
pub provider: String,
pub model_match: String,
pub message: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CapabilityAuditReport {
pub footguns: Vec<CapabilityFootgun>,
}
impl CapabilityAuditReport {
pub fn is_clean(&self) -> bool {
self.footguns.is_empty()
}
pub fn render(&self) -> String {
self.footguns
.iter()
.map(|footgun| {
format!(
"provider.{} model_match=\"{}\": {}",
footgun.provider, footgun.model_match, footgun.message
)
})
.collect::<Vec<_>>()
.join("\n")
}
}
pub fn audit_capabilities(file: &CapabilitiesFile) -> CapabilityAuditReport {
audit_capabilities_with_families(file, NATIVE_UNRELIABLE_TOOL_FAMILIES)
}
fn audit_capabilities_with_families(
file: &CapabilitiesFile,
native_unreliable_families: &[(&str, &str)],
) -> CapabilityAuditReport {
let mut report = CapabilityAuditReport::default();
for (provider, rules) in &file.provider {
for rule in rules {
let reasoning_required_for_tools = rule.reasoning_required_for_tools.unwrap_or(false);
if reasoning_required_for_tools {
if let Some(overrides) = &rule.auto_reasoning_overrides {
let offending: Vec<&str> = TOOL_TASKS
.iter()
.copied()
.filter(|task| {
overrides
.get(*task)
.map(|level| level.eq_ignore_ascii_case("off"))
.unwrap_or(false)
})
.collect();
if !offending.is_empty() {
report.footguns.push(CapabilityFootgun {
provider: provider.clone(),
model_match: rule.match_label(),
message: format!(
"declares reasoning_required_for_tools = true but also pins \
auto_reasoning_overrides {{ {} = \"off\" }}; this route calls \
tools inside its reasoning channel, so forcing reasoning off \
for a tool task is the billed-noncommittal failure (0 \
tool_calls). Remove the \"off\" override(s) for tool tasks.",
offending.join("/")
),
});
}
}
}
if provider == "openrouter" && reasoning_required_for_tools {
let pinned = rule
.openrouter_provider_order
.as_ref()
.map(|order| !order.is_empty())
.unwrap_or(false);
if !pinned {
report.footguns.push(CapabilityFootgun {
provider: provider.clone(),
model_match: rule.match_label(),
message: "is an OpenRouter route with \
reasoning_required_for_tools = true (a Harmony-style tool route on \
the OpenRouter sub-provider lottery) but declares no \
openrouter_provider_order pin. Some OpenRouter upstreams \
mis-serialize the tool call even with reasoning ON. Pin a closed \
allowlist of known-clean upstreams, e.g. \
openrouter_provider_order = [\"Cerebras\", \"Groq\"]."
.to_string(),
});
}
}
if rule
.preferred_tool_format
.as_deref()
.map(|format| format.eq_ignore_ascii_case("native"))
.unwrap_or(false)
&& !rule.native_tools.unwrap_or(false)
{
report.footguns.push(CapabilityFootgun {
provider: provider.clone(),
model_match: rule.match_label(),
message: "declares preferred_tool_format = \"native\" without \
native_tools = true. Native tool format is only coherent \
for rows that enable native tool calls; either set \
native_tools = true or choose a text-channel tool format."
.to_string(),
});
}
if rule
.allowed_tool_choice_modes
.as_ref()
.map(|modes| !modes.is_empty())
.unwrap_or(false)
&& !rule.native_tools.unwrap_or(false)
{
report.footguns.push(CapabilityFootgun {
provider: provider.clone(),
model_match: rule.match_label(),
message: "declares allowed_tool_choice_modes while native_tools is \
not true. Tool-choice modes are native request-shape \
capabilities; enable native_tools or remove the native \
tool-choice declaration."
.to_string(),
});
}
let pins_native = rule
.preferred_tool_format
.as_deref()
.map(|format| format.eq_ignore_ascii_case("native"))
.unwrap_or(false);
if pins_native {
for (family, evidence) in native_unreliable_families {
if rule
.match_patterns()
.any(|pattern| pattern.to_ascii_lowercase().contains(family))
{
report.footguns.push(CapabilityFootgun {
provider: provider.clone(),
model_match: rule.match_label(),
message: format!(
"pins preferred_tool_format = \"native\" for the \
native-unreliable `{family}` family. {evidence} Steer this \
route to a text channel (preferred_tool_format = \"text\" or \
\"json\") and set tool_mode_parity = \"native_unreliable\" so \
the family verdict is consistent across hosts."
),
});
}
}
}
if provider_is_self_hosted(provider) && decides_tool_format(rule) {
match &rule.tool_format_justification {
None => {
report.footguns.push(CapabilityFootgun {
provider: provider.clone(),
model_match: rule.match_label(),
message: "is a self-hosted native/text decision without \
tool_format_justification. Record a measurement of THIS \
runtime (`tool_format_justification = { measured = \"...\" }`) \
or a structural sibling link (`{ mirrors = { provider, \
model_match } }`). A comment that cites another row is not a \
receipt."
.to_string(),
});
}
Some(ToolFormatJustification::Measured(receipt))
if receipt.trim().is_empty() =>
{
report.footguns.push(CapabilityFootgun {
provider: provider.clone(),
model_match: rule.match_label(),
message: "declares tool_format_justification.measured but the \
receipt is empty. Write the measurement of THIS runtime, or \
use mirrors to name the cited row."
.to_string(),
});
}
Some(ToolFormatJustification::Assumed(rationale))
if rationale.trim().is_empty() =>
{
report.footguns.push(CapabilityFootgun {
provider: provider.clone(),
model_match: rule.match_label(),
message: "declares tool_format_justification.assumed but the \
rationale is empty. State what the pin rests on and how to \
roll it back, so the next reader knows this row was never \
probed."
.to_string(),
});
}
Some(ToolFormatJustification::Mirrors(target)) => {
match find_rule(file, &target.provider, &target.model_match) {
None => {
report.footguns.push(CapabilityFootgun {
provider: provider.clone(),
model_match: rule.match_label(),
message: format!(
"mirrors provider.{} model_match=\"{}\", but that row \
does not exist. Point mirrors at a real row, or \
replace it with a measurement of THIS runtime.",
target.provider, target.model_match
),
});
}
Some(cited) => {
let ours = resolved_tool_decision(rule);
let theirs = resolved_tool_decision(cited);
if ours != theirs {
report.footguns.push(CapabilityFootgun {
provider: provider.clone(),
model_match: rule.match_label(),
message: format!(
"mirrors provider.{} model_match=\"{}\" \
(native_tools={}, preferred_tool_format={}), but \
this row resolved to native_tools={}, \
preferred_tool_format={}. The cited row changed \
or this runtime diverged; re-measure THIS \
runtime or update the dependant so the link \
stays honest.",
target.provider,
target.model_match,
theirs.0,
theirs.1,
ours.0,
ours.1
),
});
}
}
}
}
Some(ToolFormatJustification::Measured(_))
| Some(ToolFormatJustification::Assumed(_)) => {}
}
}
}
}
report
}
fn decides_tool_format(rule: &ProviderRule) -> bool {
rule.native_tools.is_some() || rule.preferred_tool_format.is_some()
}
fn resolved_tool_decision(rule: &ProviderRule) -> (bool, String) {
let native = rule.native_tools.unwrap_or(false);
let format = rule.preferred_tool_format.clone().unwrap_or_else(|| {
if native {
"native".to_string()
} else {
"json".to_string()
}
});
(native, format)
}
fn find_rule<'a>(
file: &'a CapabilitiesFile,
provider: &str,
model_match: &str,
) -> Option<&'a ProviderRule> {
file.provider.get(provider)?.iter().find(|rule| {
rule.match_label() == model_match
|| rule.match_patterns().any(|pattern| pattern == model_match)
})
}
pub fn audit_builtin() -> CapabilityAuditReport {
audit_capabilities(crate::llm::capabilities::builtin_file())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::llm::capabilities::parse_capabilities_toml;
fn audit_toml(src: &str) -> CapabilityAuditReport {
audit_capabilities(&parse_capabilities_toml(src).expect("parses"))
}
fn row(provider: &str, native: bool, justification: &str) -> String {
format!(
"[[provider.{provider}]]\nmodel_match = \"q*\"\nnative_tools = {native}\n\
tool_format_justification = {justification}\n\n"
)
}
fn mirrors(provider: &str) -> String {
format!("{{ mirrors = {{ provider = \"{provider}\", model_match = \"q*\" }} }}")
}
const TEST_FAMILIES: &[(&str, &str)] =
&[("flaky-fam", "Synthetic family used to exercise the gate.")];
fn audit_toml_with_families(src: &str) -> CapabilityAuditReport {
audit_capabilities_with_families(
&parse_capabilities_toml(src).expect("parses"),
TEST_FAMILIES,
)
}
#[test]
fn shipped_matrix_has_no_footguns() {
let report = audit_builtin();
assert!(
report.is_clean(),
"shipped capability matrix has footguns:\n{}",
report.render()
);
}
#[test]
fn shipped_self_hosted_qwen36_rows_are_independently_justified_and_not_unanimous() {
let file = crate::llm::capabilities::builtin_file();
let mut native_votes = Vec::new();
let mut measured = Vec::new();
for provider in ["ollama", "llamacpp", "local", "mlx"] {
let rule = file
.provider
.get(provider)
.into_iter()
.flatten()
.find(|rule| {
rule.match_patterns()
.any(|pattern| pattern.contains("qwen3.6"))
})
.unwrap_or_else(|| panic!("{provider} is missing a qwen3.6 capability row"));
match &rule.tool_format_justification {
Some(ToolFormatJustification::Measured(receipt)) => {
assert!(
!receipt.trim().is_empty(),
"{provider} qwen3.6 measured receipt is empty"
);
measured.push(provider);
}
Some(ToolFormatJustification::Assumed(rationale)) => {
assert!(
!rationale.trim().is_empty(),
"{provider} qwen3.6 assumed rationale is empty"
);
}
Some(ToolFormatJustification::Mirrors(_)) => {
panic!("{provider} qwen3.6 must carry its own receipt, not a sibling link");
}
None => panic!("{provider} qwen3.6 is missing tool_format_justification"),
}
native_votes.push((
provider,
rule.native_tools
.unwrap_or_else(|| panic!("{provider} qwen3.6 must set native_tools")),
));
}
assert!(
native_votes.iter().any(|(_, native)| *native)
&& native_votes.iter().any(|(_, native)| !*native),
"the four self-hosted qwen3.6 rows must not all agree; runtimes differ: {native_votes:?}"
);
assert!(
!measured.is_empty(),
"no self-hosted qwen3.6 row carries a measured receipt; the split is \
then four assumptions, not four findings"
);
}
#[test]
fn flags_reasoning_off_for_tools_contradiction() {
let report = audit_toml(
r#"
[[provider.someprov]]
model_match = "harmony-*"
reasoning_required_for_tools = true
auto_reasoning_overrides = { agent = "off" }
"#,
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
assert_eq!(report.footguns[0].provider, "someprov");
assert!(report.footguns[0].message.contains("billed-noncommittal"));
}
#[test]
fn flags_lottery_route_without_pin() {
let report = audit_toml(
r#"
[[provider.openrouter]]
model_match = "vendor/harmony-*"
reasoning_required_for_tools = true
reasoning_effort_levels = ["low", "medium", "high"]
"#,
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
assert!(report.footguns[0]
.message
.contains("openrouter_provider_order"));
}
#[test]
fn pinned_lottery_route_is_clean() {
let report = audit_toml(
r#"
[[provider.openrouter]]
model_match = "vendor/harmony-*"
reasoning_required_for_tools = true
openrouter_provider_order = ["Cerebras", "Groq"]
"#,
);
assert!(report.is_clean(), "{}", report.render());
}
#[test]
fn empty_pin_is_treated_as_no_pin() {
let report = audit_toml(
r#"
[[provider.openrouter]]
model_match = "vendor/harmony-*"
reasoning_required_for_tools = true
openrouter_provider_order = []
"#,
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
}
#[test]
fn non_openrouter_required_route_does_not_need_a_pin() {
let report = audit_toml(
r#"
[[provider.groq]]
model_match = "*gpt-oss-*"
reasoning_required_for_tools = true
reasoning_effort_levels = ["low", "medium", "high"]
"#,
);
assert!(report.is_clean(), "{}", report.render());
}
#[test]
fn qwen_style_off_override_without_required_flag_is_clean() {
let report = audit_toml(
r#"
[[provider.ollama]]
model_match = "qwen3.6*"
auto_reasoning_overrides = { agent = "off" }
"#,
);
assert!(report.is_clean(), "{}", report.render());
}
#[test]
fn ordinary_models_are_clean() {
let report = audit_toml(
r#"
[[provider.openrouter]]
model_match = "anthropic/claude-*"
native_tools = true
[[provider.openai]]
model_match = "gpt-*"
native_tools = true
"#,
);
assert!(report.is_clean(), "{}", report.render());
}
#[test]
fn flags_native_tool_format_without_native_tools() {
let report = audit_toml(
r#"
[[provider.someprov]]
model_match = "some-model"
native_tools = false
preferred_tool_format = "native"
"#,
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
assert!(report.footguns[0]
.message
.contains("preferred_tool_format = \"native\""));
}
#[test]
fn flags_native_unreliable_family_pinning_native() {
let report = audit_toml_with_families(
r#"
[[provider.someprov]]
model_match = "*flaky-fam*"
native_tools = true
preferred_tool_format = "native"
"#,
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
assert!(report.footguns[0]
.message
.contains("native-unreliable `flaky-fam` family"));
}
#[test]
fn native_unreliable_family_on_text_channel_is_clean() {
let report = audit_toml_with_families(
r#"
[[provider.someprov]]
model_match = "*flaky-fam*"
native_tools = true
preferred_tool_format = "text"
tool_mode_parity = "native_unreliable"
"#,
);
assert!(report.is_clean(), "{}", report.render());
}
#[test]
fn glm_native_pin_is_no_longer_a_family_footgun() {
let report = audit_toml(
r#"
[[provider.zai]]
model_match = "glm-5*"
native_tools = true
preferred_tool_format = "native"
"#,
);
assert!(
report.is_clean(),
"GLM native pin should not trip the family gate: {}",
report.render()
);
}
#[test]
fn native_pin_for_non_family_model_is_clean() {
let report = audit_toml(
r#"
[[provider.someprov]]
model_match = "some-reliable-native-model-*"
native_tools = true
preferred_tool_format = "native"
"#,
);
assert!(report.is_clean(), "{}", report.render());
}
#[test]
fn flags_tool_choice_modes_without_native_tools() {
let report = audit_toml(
r#"
[[provider.someprov]]
model_match = "some-model"
native_tools = false
preferred_tool_format = "text"
allowed_tool_choice_modes = ["auto", "none"]
"#,
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
assert!(report.footguns[0]
.message
.contains("allowed_tool_choice_modes"));
}
#[test]
fn flags_self_hosted_tool_format_decision_without_justification() {
let report = audit_toml(
r#"
[[provider.mlx]]
model_match = "*qwen3.6*"
native_tools = true
preferred_tool_format = "native"
"#,
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
assert!(
report.footguns[0]
.message
.contains("tool_format_justification"),
"{}",
report.render()
);
}
#[test]
fn measured_self_hosted_tool_format_decision_is_clean() {
let report = audit_toml(
r#"
[[provider.llamacpp]]
model_match = "*qwen3.6*"
native_tools = true
preferred_tool_format = "native"
tool_format_justification = { measured = "2026-08-19 CUDA receipt" }
"#,
);
assert!(report.is_clean(), "{}", report.render());
}
#[test]
fn flags_empty_measured_receipt() {
let report = audit_toml(
r#"
[[provider.local]]
model_match = "*qwen3.6*"
native_tools = true
tool_format_justification = { measured = " " }
"#,
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
assert!(
report.footguns[0].message.contains("receipt is empty"),
"{}",
report.render()
);
}
#[test]
fn assumed_self_hosted_tool_format_decision_is_clean() {
let report = audit_toml(&row("mlx", true, r#"{ assumed = "no probe yet" }"#));
assert!(report.is_clean(), "{}", report.render());
}
#[test]
fn flags_empty_assumed_rationale() {
let report = audit_toml(
r#"
[[provider.local]]
model_match = "*qwen3*"
native_tools = true
tool_format_justification = { assumed = " " }
"#,
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
assert!(
report.footguns[0].message.contains("rationale is empty"),
"{}",
report.render()
);
}
#[test]
fn flags_mirror_whose_cited_row_diverged() {
let report = audit_toml(
&(row("llamacpp", false, r#"{ measured = "sweep" }"#)
+ &row("mlx", true, &mirrors("llamacpp"))),
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
assert_eq!(report.footguns[0].provider, "mlx");
assert!(
report.footguns[0].message.contains("cited row changed"),
"{}",
report.render()
);
}
#[test]
fn matching_mirror_is_clean() {
let report = audit_toml(
&(row("llamacpp", true, r#"{ measured = "receipt" }"#)
+ &row("mlx", true, &mirrors("llamacpp"))),
);
assert!(report.is_clean(), "{}", report.render());
}
#[test]
fn flags_mirror_to_missing_row() {
let report = audit_toml(
r#"
[[provider.mlx]]
model_match = "*qwen3.6*"
native_tools = true
preferred_tool_format = "native"
tool_format_justification = { mirrors = { provider = "llamacpp", model_match = "*qwen3.6*" } }
"#,
);
assert_eq!(report.footguns.len(), 1, "{}", report.render());
assert!(
report.footguns[0].message.contains("does not exist"),
"{}",
report.render()
);
}
#[test]
fn hosted_tool_format_decision_does_not_need_justification() {
let report = audit_toml(
r#"
[[provider.openrouter]]
model_match = "qwen/qwen3.6*"
native_tools = true
preferred_tool_format = "native"
"#,
);
assert!(report.is_clean(), "{}", report.render());
}
#[test]
fn self_hosted_row_without_a_tool_format_decision_is_clean() {
let report = audit_toml(
r#"
[[provider.ollama]]
model_match = "llava*"
vision_supported = true
"#,
);
assert!(report.is_clean(), "{}", report.render());
}
}