Skip to main content

kranz_engine/
routing.rs

1//! The backend routing table (ticket `backend-routing-abstraction`, KRZ-331):
2//! deterministic resolution of a ticket's task class to an executor
3//! CAPABILITY CLASS ([`ExecutorTier`]) under the operator-configured table
4//! ([`RoutingConfig`]).
5//!
6//! This module is the floor of the routing abstraction: local endpoints,
7//! hosted frontier models, and hosted fine-tunes are peers behind one
8//! interface because the table routes to capability classes only — NEVER to
9//! model ids (docs/reviews/local-llm-and-triumvirate.md §1; the hardcoded-id
10//! trap is "dead on arrival on anyone else's machine"). Which concrete
11//! endpoint a `local` route resolves to is ordinary local-backend role
12//! config (`baseUrl` + `model` + `contextBudget`): a hosted fine-tune behind
13//! an OpenAI-compatible endpoint is exactly that config shape, not a new
14//! backend kind and not routing-table content. The grep-style test below
15//! pins the no-model-id rule on this file's logic.
16//!
17//! Determinism is the contract the rest of the engine relies on: the SAME
18//! (table, task class) inputs ALWAYS produce the same route — the first
19//! matching exact `taskClassRules` entry wins; only when none matches are the
20//! ordered `patternRules` consulted (first match wins); no match anywhere
21//! falls through to [`ExecutorTier::Frontier`] (the safe default, identical
22//! to the hardcoded floor's fallthrough). An EMPTY table is not resolved
23//! here at all: [`crate::config::route_task_class_executor`] keeps the
24//! legacy literal floor for it byte-for-byte, so configuring no table is a
25//! perfect regression of today's behavior. Anything LLM-judged is
26//! deliberately above this floor (the worker self-escalation layered on
27//! top), never inside it. The tracked, base-branch-owned rules FILE that
28//! populates the table lives in [`crate::routing_rules`] (ticket
29//! `routing-rules-config`); the seed-time route record derived here
30//! ([`seed_executor_route`]) rides `worker.spawned`.
31
32use crate::types::{ExecutorRoute, ExecutorTier, RoutingConfig};
33
34/// Normalize a task-class string for comparison: trimmed and ASCII-lowercased
35/// — the SAME normalization the hardcoded floor
36/// ([`crate::config::task_class_to_tier`]) applies, so a table rule matches
37/// exactly the strings the literal floor would have.
38pub fn normalize_task_class(task_class: &str) -> String {
39    task_class.trim().to_ascii_lowercase()
40}
41
42/// Which table entry decided a route — the provenance half of the decision,
43/// recorded per worker session so the effective route is never a hidden
44/// implementation detail (ticket `routing-rules-config`).
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum RuleMatch {
47    /// `taskClassRules[i]` matched the task class exactly (after
48    /// normalization).
49    TaskClass(usize),
50    /// `patternRules[i]` matched — consulted only when no exact rule did.
51    Pattern(usize),
52    /// No rule matched; the fall-through to [`ExecutorTier::Frontier`]
53    /// decided.
54    FallThrough,
55}
56
57impl RuleMatch {
58    /// The recorded form: the table key path + index, exactly the shape
59    /// [`validate_table`] errors name, so a session's route can be traced to
60    /// the same rule a validation failure would name.
61    pub fn describe(&self) -> String {
62        match self {
63            RuleMatch::TaskClass(i) => format!("taskClassRules[{i}]"),
64            RuleMatch::Pattern(i) => format!("patternRules[{i}]"),
65            RuleMatch::FallThrough => "fall-through".to_string(),
66        }
67    }
68}
69
70/// The full resolution of one task class against the table: the tier AND the
71/// rule that decided it. [`table_tier`] is the tier-only view for callers
72/// that predate provenance.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct RouteDecision {
75    pub tier: ExecutorTier,
76    pub rule: RuleMatch,
77}
78
79/// Resolve `task_class` against the routing table: the FIRST matching exact
80/// rule wins; with no exact match the FIRST matching pattern rule wins; no
81/// match anywhere (or no task class at all) falls through to
82/// [`ExecutorTier::Frontier`].
83///
84/// Pure and total: no I/O, no time, no randomness — repeated calls with the
85/// same inputs return the same decision, which is what makes the floor route
86/// auditable (`mission.created` records the routed config, and each
87/// `worker.spawned` can carry the recomputed decision). Callers handle the
88/// empty table separately (the legacy literal floor), so this function
89/// treats every rule present as intentional; [`validate_table`] has already
90/// failed closed on a malformed one.
91pub fn resolve(routing: &RoutingConfig, task_class: Option<&str>) -> RouteDecision {
92    let Some(normalized) = task_class.map(normalize_task_class) else {
93        return RouteDecision {
94            tier: ExecutorTier::Frontier,
95            rule: RuleMatch::FallThrough,
96        };
97    };
98    for (i, rule) in routing.task_class_rules.iter().enumerate() {
99        if normalize_task_class(&rule.task_class) == normalized {
100            return RouteDecision {
101                tier: rule.tier,
102                rule: RuleMatch::TaskClass(i),
103            };
104        }
105    }
106    for (i, rule) in routing.pattern_rules.iter().enumerate() {
107        if pattern_matches(&normalize_task_class(&rule.pattern), &normalized) {
108            return RouteDecision {
109                tier: rule.tier,
110                rule: RuleMatch::Pattern(i),
111            };
112        }
113    }
114    RouteDecision {
115        tier: ExecutorTier::Frontier,
116        rule: RuleMatch::FallThrough,
117    }
118}
119
120/// Resolve `task_class` against the routing table: the FIRST matching rule
121/// wins; no rule matching (or no task class at all) falls through to
122/// [`ExecutorTier::Frontier`]. Tier-only view of [`resolve`].
123pub fn table_tier(routing: &RoutingConfig, task_class: Option<&str>) -> ExecutorTier {
124    resolve(routing, task_class).tier
125}
126
127/// The pattern language for [`crate::types::PatternRoute`]: `*` matches any
128/// (possibly empty) run of characters, every other character is a literal
129/// byte. Both sides are already normalized (trim + lowercase) by the caller.
130/// Deliberately NOT regex: the rules file is an operator contract, and a
131/// two-wildcard-semantics glob keeps every rule auditable at a glance while
132/// the classic two-pointer backtracking scan stays total and deterministic.
133fn pattern_matches(pattern: &str, text: &str) -> bool {
134    let (p, t) = (pattern.as_bytes(), text.as_bytes());
135    let (mut pi, mut ti) = (0usize, 0usize);
136    // Last `*` position and the text index it had consumed up to — the
137    // backtrack point when a post-`*` literal fails to match.
138    let (mut star, mut star_ti) = (None, 0usize);
139    while ti < t.len() {
140        if pi < p.len() && p[pi] == t[ti] {
141            pi += 1;
142            ti += 1;
143        } else if pi < p.len() && p[pi] == b'*' {
144            star = Some(pi);
145            star_ti = ti;
146            pi += 1;
147        } else if let Some(sp) = star {
148            pi = sp + 1;
149            star_ti += 1;
150            ti = star_ti;
151        } else {
152            return false;
153        }
154    }
155    while pi < p.len() && p[pi] == b'*' {
156        pi += 1;
157    }
158    pi == p.len()
159}
160
161/// The seed-time route record (ticket `routing-rules-config`): the EFFECTIVE
162/// tier and the rule that decided it, folded once from `mission.created`
163/// (whose `goal` still carries the folded task class — `plan.approved`
164/// overwrites `state.mission.goal` with the plan's own goal, so the class
165/// exists ONLY on this event) onto [`crate::types::Mission`], then replayed
166/// onto every `worker.spawned`. Routing is provenance, not a hidden
167/// implementation detail — and because resolution is deterministic, the
168/// fold-time recomputation from the recorded (goal, config) equals the
169/// seed-time decision [`crate::config::route_task_class_executor`] made, so
170/// nothing new has to be persisted and a resumed engine records exactly what
171/// a fresh one would.
172///
173/// The recorded tier is the SEEDED effective tier (derived from the routed
174/// config exactly as [`crate::types::MissionState::executor_tier`] derives
175/// it): a later mid-mission `config.changed` backend flip moves the live
176/// tier, not this seed-time record — the flip is its own event.
177///
178/// `None` when the seed goal carried no task class at all: routing never
179/// decided anything for such a mission, and its `worker.spawned` payload
180/// stays byte-identical to the pre-provenance shape.
181pub fn seed_executor_route(
182    cfg: &crate::types::MissionConfig,
183    mission_goal: &str,
184) -> Option<ExecutorRoute> {
185    let task_class = crate::ticket::parse_task_class_from_goal(mission_goal)?;
186    let rule = if cfg.routing.is_empty() {
187        // The legacy literal floor decided; there is no table rule to name.
188        None
189    } else {
190        match resolve(&cfg.routing, Some(&task_class)).rule {
191            RuleMatch::FallThrough => None,
192            matched => Some(matched.describe()),
193        }
194    };
195    Some(ExecutorRoute {
196        tier: cfg.executor_tier(),
197        rule,
198    })
199}
200
201/// Fail-closed validation of the routing table, called by
202/// [`crate::config::validate`] so a malformed table never reaches a mission.
203/// Returns `Err` naming the offending rule (the config-file key path), never
204/// a silently-corrected table:
205///
206/// - a blank `taskClass` could never match honestly (routing matches on
207///   task-class text), and
208/// - a duplicate class after normalization is dead config under
209///   first-match-wins — almost always a mistake the operator meant to
210///   reorder or merge, so it is refused rather than quietly shadowed.
211///
212/// The same two checks apply to `patternRules` (ticket
213/// `routing-rules-config`): a blank pattern is meaningless (spell `*`), and
214/// a duplicate pattern is dead config under first-match-wins.
215pub fn validate_table(routing: &RoutingConfig) -> std::result::Result<(), String> {
216    let mut seen: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
217    for (i, rule) in routing.task_class_rules.iter().enumerate() {
218        let normalized = normalize_task_class(&rule.task_class);
219        if normalized.is_empty() {
220            return Err(format!(
221                "routing.taskClassRules[{i}].taskClass must be non-empty: routing matches on \
222                 task-class text, so a blank rule could never match honestly"
223            ));
224        }
225        if let Some(first) = seen.insert(normalized, i) {
226            return Err(format!(
227                "routing.taskClassRules[{i}].taskClass {:?} duplicates rule {first} after \
228                 normalization (trim + case-insensitive); first-match-wins makes the later rule \
229                 dead config — remove or reword one",
230                rule.task_class
231            ));
232        }
233    }
234    let mut seen_patterns: std::collections::HashMap<String, usize> =
235        std::collections::HashMap::new();
236    for (i, rule) in routing.pattern_rules.iter().enumerate() {
237        let normalized = normalize_task_class(&rule.pattern);
238        if normalized.is_empty() {
239            return Err(format!(
240                "routing.patternRules[{i}].pattern must be non-empty: routing matches on \
241                 task-class text, so a blank pattern could never match honestly (spell \"*\" \
242                 to match every class)"
243            ));
244        }
245        if let Some(first) = seen_patterns.insert(normalized, i) {
246            return Err(format!(
247                "routing.patternRules[{i}].pattern {:?} duplicates rule {first} after \
248                 normalization (trim + case-insensitive); first-match-wins makes the later rule \
249                 dead config — remove or reword one",
250                rule.pattern
251            ));
252        }
253    }
254    Ok(())
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use crate::types::{PatternRoute, TaskClassRoute};
261
262    fn table(rules: &[(&str, ExecutorTier)]) -> RoutingConfig {
263        RoutingConfig {
264            task_class_rules: rules
265                .iter()
266                .map(|(task_class, tier)| TaskClassRoute {
267                    task_class: task_class.to_string(),
268                    tier: *tier,
269                })
270                .collect(),
271            pattern_rules: vec![],
272        }
273    }
274
275    fn pattern_table(rules: &[(&str, ExecutorTier)]) -> RoutingConfig {
276        RoutingConfig {
277            task_class_rules: vec![],
278            pattern_rules: rules
279                .iter()
280                .map(|(pattern, tier)| PatternRoute {
281                    pattern: pattern.to_string(),
282                    tier: *tier,
283                })
284                .collect(),
285        }
286    }
287
288    #[test]
289    fn routing_abstraction_first_matching_rule_wins() {
290        let routing = table(&[
291            ("execution-class", ExecutorTier::Local),
292            ("docs-class", ExecutorTier::Frontier),
293        ]);
294        assert_eq!(
295            table_tier(&routing, Some("execution-class")),
296            ExecutorTier::Local
297        );
298        assert_eq!(
299            table_tier(&routing, Some("docs-class")),
300            ExecutorTier::Frontier
301        );
302
303        // A class two rules could have claimed is decided by ORDER: the
304        // earlier rule wins, so table order is the only precedence knob.
305        let routing = table(&[
306            ("execution-class", ExecutorTier::Frontier),
307            ("execution-class", ExecutorTier::Local),
308        ]);
309        assert_eq!(
310            table_tier(&routing, Some("execution-class")),
311            ExecutorTier::Frontier,
312            "first match wins — the shadowed later rule never decides"
313        );
314    }
315
316    #[test]
317    fn routing_abstraction_matching_uses_the_floors_normalization() {
318        let routing = table(&[("Execution-Class", ExecutorTier::Local)]);
319        // Same case- and whitespace-insensitivity as the hardcoded floor.
320        assert_eq!(
321            table_tier(&routing, Some("  EXECUTION-CLASS ")),
322            ExecutorTier::Local
323        );
324    }
325
326    #[test]
327    fn routing_abstraction_no_matching_rule_stays_frontier() {
328        let routing = table(&[("execution-class", ExecutorTier::Local)]);
329        assert_eq!(
330            table_tier(&routing, Some("planning-class")),
331            ExecutorTier::Frontier
332        );
333        assert_eq!(table_tier(&routing, None), ExecutorTier::Frontier);
334        assert_eq!(table_tier(&routing, Some("")), ExecutorTier::Frontier);
335    }
336
337    #[test]
338    fn routing_abstraction_resolution_is_deterministic() {
339        // The floor's contract: same (table, task class) → same route, every
340        // time. Iterate enough to catch accidental nondeterminism (hash
341        // iteration, time, randomness) — the implementation is an ordered
342        // scan, so any deviation here is a regression.
343        let routing = table(&[
344            ("alpha-class", ExecutorTier::Local),
345            ("beta-class", ExecutorTier::Frontier),
346            ("gamma-class", ExecutorTier::Local),
347        ]);
348        for _ in 0..256 {
349            assert_eq!(
350                table_tier(&routing, Some("alpha-class")),
351                ExecutorTier::Local
352            );
353            assert_eq!(
354                table_tier(&routing, Some("beta-class")),
355                ExecutorTier::Frontier
356            );
357            assert_eq!(
358                table_tier(&routing, Some("gamma-class")),
359                ExecutorTier::Local
360            );
361            assert_eq!(
362                table_tier(&routing, Some("unlisted-class")),
363                ExecutorTier::Frontier
364            );
365        }
366    }
367
368    #[test]
369    fn routing_abstraction_validate_table_fails_closed_naming_the_rule() {
370        // Blank class: refused, naming the offending rule index.
371        let routing = table(&[
372            ("docs-class", ExecutorTier::Frontier),
373            ("   ", ExecutorTier::Local),
374        ]);
375        let err = validate_table(&routing).expect_err("a blank class must be refused");
376        assert!(err.contains("routing.taskClassRules[1].taskClass"), "{err}");
377        assert!(err.contains("non-empty"), "{err}");
378
379        // Duplicate after normalization: refused, naming both rules — a
380        // shadowed rule is dead config under first-match-wins.
381        let routing = table(&[
382            ("execution-class", ExecutorTier::Local),
383            (" Execution-Class ", ExecutorTier::Frontier),
384        ]);
385        let err = validate_table(&routing).expect_err("a duplicate class must be refused");
386        assert!(err.contains("routing.taskClassRules[1].taskClass"), "{err}");
387        assert!(err.contains("duplicates rule 0"), "{err}");
388
389        // A clean table validates.
390        let routing = table(&[
391            ("execution-class", ExecutorTier::Local),
392            ("docs-class", ExecutorTier::Frontier),
393        ]);
394        assert!(validate_table(&routing).is_ok(), "a clean table must pass");
395        assert!(validate_table(&RoutingConfig::default()).is_ok());
396    }
397
398    #[test]
399    fn routing_rules_config_exact_rule_beats_pattern_and_order_decides() {
400        // An exact taskClassRules entry always beats a pattern that would
401        // also match — specific over general, documented precedence.
402        let mut routing = pattern_table(&[("execution-*", ExecutorTier::Frontier)]);
403        routing.task_class_rules.push(TaskClassRoute {
404            task_class: "execution-class".to_string(),
405            tier: ExecutorTier::Local,
406        });
407        assert_eq!(
408            resolve(&routing, Some("execution-class")),
409            RouteDecision {
410                tier: ExecutorTier::Local,
411                rule: RuleMatch::TaskClass(0),
412            }
413        );
414        // The same class WITHOUT the exact rule is the pattern's claim.
415        assert_eq!(
416            resolve(&routing, Some("execution-heavy-class")),
417            RouteDecision {
418                tier: ExecutorTier::Frontier,
419                rule: RuleMatch::Pattern(0),
420            }
421        );
422
423        // Within the pattern list, order is the only precedence knob.
424        let routing = pattern_table(&[
425            ("docs-*", ExecutorTier::Local),
426            ("*", ExecutorTier::Frontier),
427        ]);
428        assert_eq!(
429            resolve(&routing, Some("docs-class")).rule,
430            RuleMatch::Pattern(0),
431            "first matching pattern wins"
432        );
433        assert_eq!(
434            resolve(&routing, Some("anything-else")).rule,
435            RuleMatch::Pattern(1),
436            "the catch-all claims what the earlier pattern did not"
437        );
438    }
439
440    #[test]
441    fn routing_rules_config_pattern_glob_semantics() {
442        let routing = pattern_table(&[("execution-*-class", ExecutorTier::Local)]);
443        // `*` matches any run, empty included: the double dash of
444        // "execution--class" is "execution-" + "" + "-class".
445        for matching in [
446            "execution-heavy-class",
447            "execution--class",
448            "EXECUTION-A-B-CLASS ",
449        ] {
450            assert_eq!(
451                resolve(&routing, Some(matching)).tier,
452                ExecutorTier::Local,
453                "{matching:?} must match execution-*-class after normalization"
454            );
455        }
456        // Anchored at BOTH ends: "execution-class" has no second dash for the
457        // literal "-class" suffix, and prefixes/suffixes around the pattern
458        // never slide.
459        for non_matching in [
460            "execution-class",
461            "execution-class-extra",
462            "xexecution-heavy-class",
463            "docs-class",
464        ] {
465            assert_eq!(
466                resolve(&routing, Some(non_matching)),
467                RouteDecision {
468                    tier: ExecutorTier::Frontier,
469                    rule: RuleMatch::FallThrough,
470                },
471                "{non_matching:?} must NOT match execution-*-class"
472            );
473        }
474        // Bare `*` matches everything, including the empty class.
475        assert!(pattern_matches("*", ""));
476        assert!(pattern_matches("**", "anything"));
477        assert!(!pattern_matches("", "x"));
478        assert!(pattern_matches("", ""));
479    }
480
481    #[test]
482    fn routing_rules_config_resolution_is_deterministic() {
483        // Same (table, task class) → same decision, rule included, every
484        // time — the per-session provenance record recomputes this at spawn,
485        // so any nondeterminism would fork the audit trail.
486        let mut routing = pattern_table(&[
487            ("docs-*", ExecutorTier::Frontier),
488            ("execution-*", ExecutorTier::Local),
489        ]);
490        routing.task_class_rules.push(TaskClassRoute {
491            task_class: "docs-class".to_string(),
492            tier: ExecutorTier::Local,
493        });
494        for _ in 0..256 {
495            assert_eq!(
496                resolve(&routing, Some("docs-class")),
497                RouteDecision {
498                    tier: ExecutorTier::Local,
499                    rule: RuleMatch::TaskClass(0),
500                }
501            );
502            assert_eq!(
503                resolve(&routing, Some("docs-other")),
504                RouteDecision {
505                    tier: ExecutorTier::Frontier,
506                    rule: RuleMatch::Pattern(0),
507                }
508            );
509            assert_eq!(
510                resolve(&routing, Some("execution-class")),
511                RouteDecision {
512                    tier: ExecutorTier::Local,
513                    rule: RuleMatch::Pattern(1),
514                }
515            );
516            assert_eq!(
517                resolve(&routing, Some("unlisted")),
518                RouteDecision {
519                    tier: ExecutorTier::Frontier,
520                    rule: RuleMatch::FallThrough,
521                }
522            );
523        }
524    }
525
526    #[test]
527    fn routing_rules_config_validate_table_fails_closed_on_pattern_rules() {
528        // Blank pattern: refused, naming the rule index + field.
529        let routing = pattern_table(&[
530            ("docs-*", ExecutorTier::Frontier),
531            ("  ", ExecutorTier::Local),
532        ]);
533        let err = validate_table(&routing).expect_err("a blank pattern must be refused");
534        assert!(err.contains("routing.patternRules[1].pattern"), "{err}");
535        assert!(err.contains("non-empty"), "{err}");
536
537        // Duplicate after normalization: refused, naming both rules.
538        let routing = pattern_table(&[
539            ("docs-*", ExecutorTier::Local),
540            (" DOCS-* ", ExecutorTier::Frontier),
541        ]);
542        let err = validate_table(&routing).expect_err("a duplicate pattern must be refused");
543        assert!(err.contains("routing.patternRules[1].pattern"), "{err}");
544        assert!(err.contains("duplicates rule 0"), "{err}");
545
546        // A clean mixed table validates.
547        let mut routing = pattern_table(&[("docs-*", ExecutorTier::Frontier)]);
548        routing.task_class_rules.push(TaskClassRoute {
549            task_class: "execution-class".to_string(),
550            tier: ExecutorTier::Local,
551        });
552        assert!(validate_table(&routing).is_ok(), "a clean table must pass");
553    }
554
555    #[test]
556    fn routing_rules_config_seed_executor_route_records_rule_and_effective_tier() {
557        // A folded ticket goal (the exact channel create parses) + a seeded,
558        // frozen config → the record folded from mission.created.
559        let ticket = crate::ticket::Ticket::parse(
560            "bump-dep",
561            "---\ntitle: Bump a dependency\ntask-class: execution-class\n---\n\n## Goal\nBump it.\n",
562        )
563        .expect("parse ticket");
564        let goal = ticket.mission_goal();
565
566        // Table routed the class local and the endpoint exists: the seeded
567        // config was rewritten to the local backend, so the record names the
568        // rule AND the effective local tier.
569        let mut table = pattern_table(&[]);
570        table.task_class_rules.push(TaskClassRoute {
571            task_class: "execution-class".to_string(),
572            tier: ExecutorTier::Local,
573        });
574        let mut cfg = crate::types::MissionConfig {
575            routing: table,
576            ..crate::types::MissionConfig::default()
577        };
578        cfg.worker.backend = Some("local".to_string());
579        let route = seed_executor_route(&cfg, &goal).expect("a task class routes");
580        assert_eq!(route.tier, ExecutorTier::Local);
581        assert_eq!(route.rule.as_deref(), Some("taskClassRules[0]"));
582
583        // Fall-through: the table exists but claims nothing — tier effective
584        // from the (unrouted) config, no rule named.
585        let cfg = crate::types::MissionConfig {
586            routing: pattern_table(&[("docs-*", ExecutorTier::Local)]),
587            ..crate::types::MissionConfig::default()
588        };
589        let route = seed_executor_route(&cfg, &goal).expect("a task class routes");
590        assert_eq!(route.tier, ExecutorTier::Frontier);
591        assert_eq!(route.rule, None, "the fall-through names no rule");
592
593        // No table at all: the legacy literal floor decided; no rule to name,
594        // and a goal WITHOUT a folded task class records nothing at all (the
595        // pre-provenance byte shape).
596        let cfg = crate::types::MissionConfig::default();
597        let route = seed_executor_route(&cfg, &goal).expect("a task class routes");
598        assert_eq!(route.tier, ExecutorTier::Frontier);
599        assert_eq!(route.rule, None);
600        assert!(seed_executor_route(&cfg, "ship the demo feature").is_none());
601    }
602
603    /// The no-hardcoded-model-ids rule (KRZ-331, citing
604    /// docs/reviews/local-llm-and-triumvirate.md §1), pinned as a grep over
605    /// this module's NON-TEST source: the routing table resolves capability
606    /// classes ([`ExecutorTier`]) only, so no model-id literal may appear in
607    /// the resolution logic or its docs. Model ids legitimately live
608    /// ELSEWHERE in core and are the documented allowlist, all outside the
609    /// routing table code paths: the per-role default model names in
610    /// `config.rs` (`role_default_model`) and `types.rs`
611    /// (`MissionConfig::default`), the backend default model constants in
612    /// `cost.rs`, tier classification in `config::model_tier`, and test
613    /// fixtures everywhere (including this file's own test module, which the
614    /// split below excludes — the needles themselves live there).
615    #[test]
616    fn routing_abstraction_routing_logic_carries_no_model_id_literals() {
617        let source = include_str!("routing.rs");
618        let logic = source
619            .split("#[cfg(test)]")
620            .next()
621            .expect("the test module marker exists");
622        // Quoted alias forms (what a literal in code would look like) and
623        // distinctive id substrings (provider-qualified or versioned ids).
624        for needle in [
625            "\"opus\"",
626            "\"sonnet\"",
627            "\"haiku\"",
628            "\"fable\"",
629            "gpt-",
630            "glm-",
631            "kimi-code",
632            "claude-",
633            "fireworks",
634            "qwen",
635            "mistral",
636            "deepseek",
637        ] {
638            assert!(
639                !logic.contains(needle),
640                "model-id literal {needle:?} must never appear in the routing \
641                 table code paths — route capability classes (ExecutorTier), \
642                 not models"
643            );
644        }
645    }
646}