From b24d4cdbabdf471dda3d696473db5200a4547843 Mon Sep 17 00:00:00 2001 From: infosave2007 Date: Sat, 8 Aug 2026 09:02:25 +0300 Subject: [PATCH] fix(routing): the task-rule badge only when the rule actually chose the head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit route_source reported "+task-rule" whenever a rule existed for the task — so a math prompt routed low by the ordinary tier chain still wore the badge next to the local model, reading as "the rule picked this" when it hadn't (the rule only defines math@high). matched now means the head of the chain came from the rule's tier-specific or any-tier list; verified live: the same low math request now reports route_source "router". --- src/routing.rs | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/routing.rs b/src/routing.rs index 2a32c72..61342f6 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -97,9 +97,16 @@ impl RoutingTable { } let d = self.default.clone(); push(&mut out, &d); - let matched = rule - .map(|r| !r.any.is_empty() || r.per.values().any(|v| !v.is_empty())) - .unwrap_or(false); + // "task-rule" must mean "the rule chose the head of this chain" — a + // rule that exists but lost to the tier chain (math has a high pick, + // the request came in low) must not claim the credit. + let matched = match (rule, out.first()) { + (Some(r), Some(head)) => { + r.per.get(tier).map(|v| v.contains(head)).unwrap_or(false) + || r.any.first() == Some(head) + } + _ => false, + }; (out, matched) } @@ -184,7 +191,9 @@ mod tests { assert_eq!(c[0], "cheap-coder"); // high: tier chain leads, task rule follows as fallback let (c, m) = t.candidates_for("code", "high"); - assert!(m); + // escalation put the global high chain first → the rule did not pick + // the head, so no task-rule credit + assert!(!m); // default rides along as the very last fallback assert_eq!(c, vec!["big", "cheap-coder", "cheap"]); } @@ -202,8 +211,10 @@ mod tests { let (c, m) = t.candidates_for("code", "high"); assert!(m); assert_eq!(c, vec!["best-coder", "big", "generic-coder", "cheap"]); - // at low: any-tier task models lead as before - let (c, _) = t.candidates_for("code", "low"); + // at low: any-tier task models lead as before (and the badge is + // honest — the head came from the rule) + let (c, m) = t.candidates_for("code", "low"); + assert!(m); assert_eq!(c, vec!["generic-coder", "cheap"]); // a tier with an explicit pick but no any-models let t2 = with_task_tier_models( @@ -215,6 +226,10 @@ mod tests { let (c, m) = t2.candidates_for("legal", "high"); assert!(m); assert_eq!(c, vec!["strong", "big", "cheap"]); + // the same rule at LOW did not shape the head → no task-rule credit + let (c, m) = t2.candidates_for("legal", "low"); + assert!(!m); + assert_eq!(c, vec!["cheap"]); } #[test]