use crate::types::{ExecutorRoute, ExecutorTier, RoutingConfig};
pub fn normalize_task_class(task_class: &str) -> String {
task_class.trim().to_ascii_lowercase()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuleMatch {
TaskClass(usize),
Pattern(usize),
FallThrough,
}
impl RuleMatch {
pub fn describe(&self) -> String {
match self {
RuleMatch::TaskClass(i) => format!("taskClassRules[{i}]"),
RuleMatch::Pattern(i) => format!("patternRules[{i}]"),
RuleMatch::FallThrough => "fall-through".to_string(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RouteDecision {
pub tier: ExecutorTier,
pub rule: RuleMatch,
}
pub fn resolve(routing: &RoutingConfig, task_class: Option<&str>) -> RouteDecision {
let Some(normalized) = task_class.map(normalize_task_class) else {
return RouteDecision {
tier: ExecutorTier::Frontier,
rule: RuleMatch::FallThrough,
};
};
for (i, rule) in routing.task_class_rules.iter().enumerate() {
if normalize_task_class(&rule.task_class) == normalized {
return RouteDecision {
tier: rule.tier,
rule: RuleMatch::TaskClass(i),
};
}
}
for (i, rule) in routing.pattern_rules.iter().enumerate() {
if pattern_matches(&normalize_task_class(&rule.pattern), &normalized) {
return RouteDecision {
tier: rule.tier,
rule: RuleMatch::Pattern(i),
};
}
}
RouteDecision {
tier: ExecutorTier::Frontier,
rule: RuleMatch::FallThrough,
}
}
pub fn table_tier(routing: &RoutingConfig, task_class: Option<&str>) -> ExecutorTier {
resolve(routing, task_class).tier
}
fn pattern_matches(pattern: &str, text: &str) -> bool {
let (p, t) = (pattern.as_bytes(), text.as_bytes());
let (mut pi, mut ti) = (0usize, 0usize);
let (mut star, mut star_ti) = (None, 0usize);
while ti < t.len() {
if pi < p.len() && p[pi] == t[ti] {
pi += 1;
ti += 1;
} else if pi < p.len() && p[pi] == b'*' {
star = Some(pi);
star_ti = ti;
pi += 1;
} else if let Some(sp) = star {
pi = sp + 1;
star_ti += 1;
ti = star_ti;
} else {
return false;
}
}
while pi < p.len() && p[pi] == b'*' {
pi += 1;
}
pi == p.len()
}
pub fn seed_executor_route(
cfg: &crate::types::MissionConfig,
mission_goal: &str,
) -> Option<ExecutorRoute> {
let task_class = crate::ticket::parse_task_class_from_goal(mission_goal)?;
let rule = if cfg.routing.is_empty() {
None
} else {
match resolve(&cfg.routing, Some(&task_class)).rule {
RuleMatch::FallThrough => None,
matched => Some(matched.describe()),
}
};
Some(ExecutorRoute {
tier: cfg.executor_tier(),
rule,
})
}
pub fn validate_table(routing: &RoutingConfig) -> std::result::Result<(), String> {
let mut seen: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
for (i, rule) in routing.task_class_rules.iter().enumerate() {
let normalized = normalize_task_class(&rule.task_class);
if normalized.is_empty() {
return Err(format!(
"routing.taskClassRules[{i}].taskClass must be non-empty: routing matches on \
task-class text, so a blank rule could never match honestly"
));
}
if let Some(first) = seen.insert(normalized, i) {
return Err(format!(
"routing.taskClassRules[{i}].taskClass {:?} duplicates rule {first} after \
normalization (trim + case-insensitive); first-match-wins makes the later rule \
dead config — remove or reword one",
rule.task_class
));
}
}
let mut seen_patterns: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
for (i, rule) in routing.pattern_rules.iter().enumerate() {
let normalized = normalize_task_class(&rule.pattern);
if normalized.is_empty() {
return Err(format!(
"routing.patternRules[{i}].pattern must be non-empty: routing matches on \
task-class text, so a blank pattern could never match honestly (spell \"*\" \
to match every class)"
));
}
if let Some(first) = seen_patterns.insert(normalized, i) {
return Err(format!(
"routing.patternRules[{i}].pattern {:?} duplicates rule {first} after \
normalization (trim + case-insensitive); first-match-wins makes the later rule \
dead config — remove or reword one",
rule.pattern
));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{PatternRoute, TaskClassRoute};
fn table(rules: &[(&str, ExecutorTier)]) -> RoutingConfig {
RoutingConfig {
task_class_rules: rules
.iter()
.map(|(task_class, tier)| TaskClassRoute {
task_class: task_class.to_string(),
tier: *tier,
})
.collect(),
pattern_rules: vec![],
}
}
fn pattern_table(rules: &[(&str, ExecutorTier)]) -> RoutingConfig {
RoutingConfig {
task_class_rules: vec![],
pattern_rules: rules
.iter()
.map(|(pattern, tier)| PatternRoute {
pattern: pattern.to_string(),
tier: *tier,
})
.collect(),
}
}
#[test]
fn routing_abstraction_first_matching_rule_wins() {
let routing = table(&[
("execution-class", ExecutorTier::Local),
("docs-class", ExecutorTier::Frontier),
]);
assert_eq!(
table_tier(&routing, Some("execution-class")),
ExecutorTier::Local
);
assert_eq!(
table_tier(&routing, Some("docs-class")),
ExecutorTier::Frontier
);
let routing = table(&[
("execution-class", ExecutorTier::Frontier),
("execution-class", ExecutorTier::Local),
]);
assert_eq!(
table_tier(&routing, Some("execution-class")),
ExecutorTier::Frontier,
"first match wins — the shadowed later rule never decides"
);
}
#[test]
fn routing_abstraction_matching_uses_the_floors_normalization() {
let routing = table(&[("Execution-Class", ExecutorTier::Local)]);
assert_eq!(
table_tier(&routing, Some(" EXECUTION-CLASS ")),
ExecutorTier::Local
);
}
#[test]
fn routing_abstraction_no_matching_rule_stays_frontier() {
let routing = table(&[("execution-class", ExecutorTier::Local)]);
assert_eq!(
table_tier(&routing, Some("planning-class")),
ExecutorTier::Frontier
);
assert_eq!(table_tier(&routing, None), ExecutorTier::Frontier);
assert_eq!(table_tier(&routing, Some("")), ExecutorTier::Frontier);
}
#[test]
fn routing_abstraction_resolution_is_deterministic() {
let routing = table(&[
("alpha-class", ExecutorTier::Local),
("beta-class", ExecutorTier::Frontier),
("gamma-class", ExecutorTier::Local),
]);
for _ in 0..256 {
assert_eq!(
table_tier(&routing, Some("alpha-class")),
ExecutorTier::Local
);
assert_eq!(
table_tier(&routing, Some("beta-class")),
ExecutorTier::Frontier
);
assert_eq!(
table_tier(&routing, Some("gamma-class")),
ExecutorTier::Local
);
assert_eq!(
table_tier(&routing, Some("unlisted-class")),
ExecutorTier::Frontier
);
}
}
#[test]
fn routing_abstraction_validate_table_fails_closed_naming_the_rule() {
let routing = table(&[
("docs-class", ExecutorTier::Frontier),
(" ", ExecutorTier::Local),
]);
let err = validate_table(&routing).expect_err("a blank class must be refused");
assert!(err.contains("routing.taskClassRules[1].taskClass"), "{err}");
assert!(err.contains("non-empty"), "{err}");
let routing = table(&[
("execution-class", ExecutorTier::Local),
(" Execution-Class ", ExecutorTier::Frontier),
]);
let err = validate_table(&routing).expect_err("a duplicate class must be refused");
assert!(err.contains("routing.taskClassRules[1].taskClass"), "{err}");
assert!(err.contains("duplicates rule 0"), "{err}");
let routing = table(&[
("execution-class", ExecutorTier::Local),
("docs-class", ExecutorTier::Frontier),
]);
assert!(validate_table(&routing).is_ok(), "a clean table must pass");
assert!(validate_table(&RoutingConfig::default()).is_ok());
}
#[test]
fn routing_rules_config_exact_rule_beats_pattern_and_order_decides() {
let mut routing = pattern_table(&[("execution-*", ExecutorTier::Frontier)]);
routing.task_class_rules.push(TaskClassRoute {
task_class: "execution-class".to_string(),
tier: ExecutorTier::Local,
});
assert_eq!(
resolve(&routing, Some("execution-class")),
RouteDecision {
tier: ExecutorTier::Local,
rule: RuleMatch::TaskClass(0),
}
);
assert_eq!(
resolve(&routing, Some("execution-heavy-class")),
RouteDecision {
tier: ExecutorTier::Frontier,
rule: RuleMatch::Pattern(0),
}
);
let routing = pattern_table(&[
("docs-*", ExecutorTier::Local),
("*", ExecutorTier::Frontier),
]);
assert_eq!(
resolve(&routing, Some("docs-class")).rule,
RuleMatch::Pattern(0),
"first matching pattern wins"
);
assert_eq!(
resolve(&routing, Some("anything-else")).rule,
RuleMatch::Pattern(1),
"the catch-all claims what the earlier pattern did not"
);
}
#[test]
fn routing_rules_config_pattern_glob_semantics() {
let routing = pattern_table(&[("execution-*-class", ExecutorTier::Local)]);
for matching in [
"execution-heavy-class",
"execution--class",
"EXECUTION-A-B-CLASS ",
] {
assert_eq!(
resolve(&routing, Some(matching)).tier,
ExecutorTier::Local,
"{matching:?} must match execution-*-class after normalization"
);
}
for non_matching in [
"execution-class",
"execution-class-extra",
"xexecution-heavy-class",
"docs-class",
] {
assert_eq!(
resolve(&routing, Some(non_matching)),
RouteDecision {
tier: ExecutorTier::Frontier,
rule: RuleMatch::FallThrough,
},
"{non_matching:?} must NOT match execution-*-class"
);
}
assert!(pattern_matches("*", ""));
assert!(pattern_matches("**", "anything"));
assert!(!pattern_matches("", "x"));
assert!(pattern_matches("", ""));
}
#[test]
fn routing_rules_config_resolution_is_deterministic() {
let mut routing = pattern_table(&[
("docs-*", ExecutorTier::Frontier),
("execution-*", ExecutorTier::Local),
]);
routing.task_class_rules.push(TaskClassRoute {
task_class: "docs-class".to_string(),
tier: ExecutorTier::Local,
});
for _ in 0..256 {
assert_eq!(
resolve(&routing, Some("docs-class")),
RouteDecision {
tier: ExecutorTier::Local,
rule: RuleMatch::TaskClass(0),
}
);
assert_eq!(
resolve(&routing, Some("docs-other")),
RouteDecision {
tier: ExecutorTier::Frontier,
rule: RuleMatch::Pattern(0),
}
);
assert_eq!(
resolve(&routing, Some("execution-class")),
RouteDecision {
tier: ExecutorTier::Local,
rule: RuleMatch::Pattern(1),
}
);
assert_eq!(
resolve(&routing, Some("unlisted")),
RouteDecision {
tier: ExecutorTier::Frontier,
rule: RuleMatch::FallThrough,
}
);
}
}
#[test]
fn routing_rules_config_validate_table_fails_closed_on_pattern_rules() {
let routing = pattern_table(&[
("docs-*", ExecutorTier::Frontier),
(" ", ExecutorTier::Local),
]);
let err = validate_table(&routing).expect_err("a blank pattern must be refused");
assert!(err.contains("routing.patternRules[1].pattern"), "{err}");
assert!(err.contains("non-empty"), "{err}");
let routing = pattern_table(&[
("docs-*", ExecutorTier::Local),
(" DOCS-* ", ExecutorTier::Frontier),
]);
let err = validate_table(&routing).expect_err("a duplicate pattern must be refused");
assert!(err.contains("routing.patternRules[1].pattern"), "{err}");
assert!(err.contains("duplicates rule 0"), "{err}");
let mut routing = pattern_table(&[("docs-*", ExecutorTier::Frontier)]);
routing.task_class_rules.push(TaskClassRoute {
task_class: "execution-class".to_string(),
tier: ExecutorTier::Local,
});
assert!(validate_table(&routing).is_ok(), "a clean table must pass");
}
#[test]
fn routing_rules_config_seed_executor_route_records_rule_and_effective_tier() {
let ticket = crate::ticket::Ticket::parse(
"bump-dep",
"---\ntitle: Bump a dependency\ntask-class: execution-class\n---\n\n## Goal\nBump it.\n",
)
.expect("parse ticket");
let goal = ticket.mission_goal();
let mut table = pattern_table(&[]);
table.task_class_rules.push(TaskClassRoute {
task_class: "execution-class".to_string(),
tier: ExecutorTier::Local,
});
let mut cfg = crate::types::MissionConfig {
routing: table,
..crate::types::MissionConfig::default()
};
cfg.worker.backend = Some("local".to_string());
let route = seed_executor_route(&cfg, &goal).expect("a task class routes");
assert_eq!(route.tier, ExecutorTier::Local);
assert_eq!(route.rule.as_deref(), Some("taskClassRules[0]"));
let cfg = crate::types::MissionConfig {
routing: pattern_table(&[("docs-*", ExecutorTier::Local)]),
..crate::types::MissionConfig::default()
};
let route = seed_executor_route(&cfg, &goal).expect("a task class routes");
assert_eq!(route.tier, ExecutorTier::Frontier);
assert_eq!(route.rule, None, "the fall-through names no rule");
let cfg = crate::types::MissionConfig::default();
let route = seed_executor_route(&cfg, &goal).expect("a task class routes");
assert_eq!(route.tier, ExecutorTier::Frontier);
assert_eq!(route.rule, None);
assert!(seed_executor_route(&cfg, "ship the demo feature").is_none());
}
#[test]
fn routing_abstraction_routing_logic_carries_no_model_id_literals() {
let source = include_str!("routing.rs");
let logic = source
.split("#[cfg(test)]")
.next()
.expect("the test module marker exists");
for needle in [
"\"opus\"",
"\"sonnet\"",
"\"haiku\"",
"\"fable\"",
"gpt-",
"glm-",
"kimi-code",
"claude-",
"fireworks",
"qwen",
"mistral",
"deepseek",
] {
assert!(
!logic.contains(needle),
"model-id literal {needle:?} must never appear in the routing \
table code paths — route capability classes (ExecutorTier), \
not models"
);
}
}
}