use crate::error::DispatchError;
use crate::ir::{ComponentNode, Guardrail, LlmCall, OutcomeKind, RealizationKind, TriggerKind};
use std::collections::HashSet;
pub(super) const PER_STEP_TIER_CAPABILITY: &str = "llm:per_step_tier";
pub(super) const DRIVER_TOOLS: [&str; 2] = ["Task", "Read"];
pub(super) const DATA_ACCESS_CAPABILITIES: [&str; 4] = [
"sql_execution:read_only",
"genbi_build",
"semantic_introspection",
"schema_introspection",
];
pub(super) const READ_ONLY_GUARDRAIL_NAME: &str = "read_only_execution";
pub(super) const ARTIFACT_WRITE_GUARDRAIL_NAME: &str = "artifact_write";
pub(super) const RENDER_CONTRACT_CAPABILITY: &str = "render_contract";
pub(super) const DEFAULT_ARTIFACT_SCOPE: &str = ".";
pub(super) const DESTRUCTIVE_BASH_DENY_PATTERNS: [&str; 3] =
["Bash(rm:*)", "Bash(sudo:*)", "Bash(dd:*)"];
pub(super) fn unsupported(field: &str, value: &str) -> DispatchError {
DispatchError(format!(
"{field} '{value}' is not supported by the claude-code file target (wall-hit)"
))
}
pub(super) fn realization_supported(kind: RealizationKind) -> bool {
matches!(
kind,
RealizationKind::Skill | RealizationKind::Tool | RealizationKind::GatedTool
)
}
pub(super) fn trigger_supported(kind: TriggerKind) -> bool {
matches!(kind, TriggerKind::OneShot | TriggerKind::Scheduled)
}
pub(super) fn outcome_supported(kind: OutcomeKind) -> bool {
matches!(
kind,
OutcomeKind::None | OutcomeKind::Assertion | OutcomeKind::Mutation
)
}
pub(super) fn tier_collapse_comment(llm_calls: &[LlmCall], model: &str) -> Option<String> {
let distinct: HashSet<&str> = llm_calls.iter().map(|c| c.tier.as_str()).collect();
if distinct.len() <= 1 {
return None;
}
let steps = llm_calls
.iter()
.map(|c| format!("{}={}", c.name, c.tier))
.collect::<Vec<_>>()
.join(", ");
Some(format!(
"<!-- warble: per-step tiers [{steps}] collapsed to driver model '{model}' -->"
))
}
pub(super) fn has_data_access_capability(caps: &[String]) -> bool {
caps.iter()
.any(|c| DATA_ACCESS_CAPABILITIES.contains(&c.as_str()))
}
pub(super) fn is_read_only(guardrails: &[Guardrail]) -> bool {
guardrails
.iter()
.any(|g| g.name == READ_ONLY_GUARDRAIL_NAME)
}
pub(super) fn find_guardrail<'a>(guardrails: &'a [Guardrail], name: &str) -> Option<&'a Guardrail> {
guardrails.iter().find(|g| g.name == name)
}
pub(super) fn is_assertion(node: &ComponentNode) -> bool {
node.effect.outcome.kind == OutcomeKind::Assertion
}
pub(super) fn is_mutation(node: &ComponentNode) -> bool {
node.effect.outcome.kind == OutcomeKind::Mutation
}