1use crate::pipeline::Pipeline;
24use base64::Engine as _;
25use cortiq_core::format::{CmfHeader, RoutingCalibration, SelectionDescriptor, SkillRecord};
26use cortiq_core::knowledge::{BACKBONE_CLASS_ID, METRIC_MSE_UNIT, parse_hex64};
27use cortiq_core::quant::{f16_to_f32, f32_to_f16};
28use cortiq_core::{CmfModel, PhiSpec, RouterPolicy, hash64};
29
30pub const NOVELTY_W_ENERGY: f32 = 0.5;
32pub const NOVELTY_W_MARGIN: f32 = 0.25;
33pub const NOVELTY_W_CONF: f32 = 0.25;
34pub const NOVELTY_MARGIN_K: f32 = 8.0;
35
36#[derive(Debug, Clone)]
37pub struct SkillRoute {
38 pub id: String,
39 pub error: f32,
41 pub raw_error: f32,
43 pub probability: f32,
46}
47
48#[derive(Debug, Clone)]
50pub struct Routing {
51 pub scores: Vec<SkillRoute>,
53 pub confidence: f32,
55 pub margin: f32,
57 pub novelty: f32,
59 pub is_novel: bool,
61 pub calibrated: bool,
62}
63
64impl Routing {
65 pub fn winner(&self) -> Option<&SkillRoute> {
66 self.scores.first()
67 }
68}
69
70pub fn decode_f16(b64: &str) -> Option<Vec<f32>> {
71 let bytes = base64::engine::general_purpose::STANDARD.decode(b64).ok()?;
72 Some(
73 bytes
74 .chunks_exact(2)
75 .map(|c| f16_to_f32(u16::from_le_bytes([c[0], c[1]])))
76 .collect(),
77 )
78}
79
80fn sigmoid(x: f32) -> f32 {
81 1.0 / (1.0 + (-x).exp())
82}
83
84fn softmax(v: &mut [f32]) {
85 if v.is_empty() {
86 return;
87 }
88 let mx = v.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
89 let mut s = 0.0f32;
90 for x in v.iter_mut() {
91 *x = (*x - mx).exp();
92 s += *x;
93 }
94 for x in v.iter_mut() {
95 *x /= s.max(1e-30);
96 }
97}
98
99pub fn recon_error(phi: &[f32], mean: &[f32], basis: &[f32], rank: usize) -> f32 {
101 let hidden = phi.len();
102 let r: Vec<f32> = phi.iter().zip(mean).map(|(p, m)| p - m).collect();
103 let rr: f32 = r.iter().map(|v| v * v).sum();
104 let mut proj = 0f32;
105 for k in 0..rank {
106 let row = &basis[k * hidden..(k + 1) * hidden];
107 let c: f32 = row.iter().zip(&r).map(|(b, v)| b * v).sum();
108 proj += c * c;
109 }
110 (rr - proj).max(0.0)
111}
112
113pub fn decide(
116 rows: &[ErrorRow],
117 calib: Option<&cortiq_core::format::RoutingCalibration>,
118 fallback_tau: f32,
119) -> Routing {
120 let mut idx: Vec<usize> = (0..rows.len()).collect();
121 idx.sort_by(|&a, &b| rows[a].1.total_cmp(&rows[b].1));
122 let mut scores: Vec<SkillRoute> = idx
123 .iter()
124 .map(|&i| SkillRoute {
125 id: rows[i].0.clone(),
126 error: rows[i].1 / rows[i].4.max(1e-12),
127 raw_error: rows[i].1,
128 probability: 0.0,
129 })
130 .collect();
131 if scores.is_empty() {
132 return Routing {
133 scores,
134 confidence: 0.0,
135 margin: 0.0,
136 novelty: 1.0,
137 is_novel: true,
138 calibrated: calib.is_some(),
139 };
140 }
141 let (Some(c), Some(&top)) = (calib, idx.first()) else {
142 let e_min = scores[0].error;
143 return Routing {
144 scores,
145 confidence: 0.0,
146 margin: 0.0,
147 novelty: f32::NAN,
148 is_novel: e_min > fallback_tau,
149 calibrated: false,
150 };
151 };
152 let mut logits: Vec<f32> = idx
154 .iter()
155 .map(|&i| -rows[i].1 / c.temperature.max(1e-3))
156 .collect();
157 softmax(&mut logits);
158 for (s, p) in scores.iter_mut().zip(&logits) {
159 s.probability = *p;
160 }
161 let confidence = logits[0];
162 let inv = |e: f32| 1.0 / (1.0 + e);
164 let margin = if idx.len() > 1 {
165 inv(rows[idx[0]].1) - inv(rows[idx[1]].1)
166 } else {
167 inv(rows[idx[0]].1)
168 };
169 let (em, es) = (
171 rows[top].2.unwrap_or(0.0),
172 rows[top].3.unwrap_or(1.0).max(1e-4),
173 );
174 let z = (rows[top].1 - em) / es;
175 let novelty = NOVELTY_W_ENERGY * sigmoid(z)
176 + NOVELTY_W_MARGIN / (1.0 + margin * NOVELTY_MARGIN_K)
177 + NOVELTY_W_CONF * (1.0 - confidence);
178 Routing {
179 scores,
180 confidence,
181 margin,
182 novelty,
183 is_novel: novelty > c.novelty_theta,
184 calibrated: true,
185 }
186}
187
188pub fn error_rows(
190 model: &CmfModel,
191 phi_of_layer: &mut dyn FnMut(usize) -> Vec<f32>,
192) -> Vec<ErrorRow> {
193 let hidden = model.arch().hidden_size;
194 let mut rows = Vec::new();
195 for skill in &model.header.skills {
196 if skill.is_v2() {
200 continue;
201 }
202 let Some(sel) = &skill.selection else {
203 continue;
204 };
205 let unit = match sel.metric.as_str() {
206 "mse" => false,
207 "mse_unit" => true,
208 m => {
209 tracing::warn!("skill '{}': unknown metric '{}'", skill.id, m);
210 continue;
211 }
212 };
213 let mut phi = phi_of_layer(sel.phi_layer);
214 if unit {
215 let n = phi.iter().map(|x| x * x).sum::<f32>().sqrt().max(1e-12);
216 for x in phi.iter_mut() {
217 *x /= n;
218 }
219 }
220 let (Some(mean), Some(basis)) = (decode_f16(&sel.mean), decode_f16(&sel.basis)) else {
221 tracing::error!("skill '{}': malformed selection payload", skill.id);
222 continue;
223 };
224 if mean.len() != hidden || basis.len() != sel.rank * hidden || phi.len() != hidden {
225 tracing::error!("skill '{}': selection dims mismatch", skill.id);
226 continue;
227 }
228 let e = recon_error(&phi, &mean, &basis, sel.rank);
229 let pp: f32 = phi.iter().map(|v| v * v).sum();
230 rows.push((skill.id.clone(), e, sel.err_mean, sel.err_std, pp));
231 }
232 rows
233}
234
235pub fn route_full(
237 model: &CmfModel,
238 pipeline: &mut Pipeline,
239 ids: &[u32],
240 fallback_tau: f32,
241) -> Routing {
242 let mut phi_cache: Vec<(usize, Vec<f32>)> = Vec::new();
243 let mut phi_of = |layer: usize| -> Vec<f32> {
244 if let Some((_, p)) = phi_cache.iter().find(|(l, _)| *l == layer) {
245 return p.clone();
246 }
247 let p = pipeline.probe_phi(ids, layer);
248 phi_cache.push((layer, p.clone()));
249 p
250 };
251 let rows = error_rows(model, &mut phi_of);
252 decide(&rows, model.header.routing.as_ref(), fallback_tau)
253}
254
255pub fn route(model: &CmfModel, pipeline: &mut Pipeline, ids: &[u32]) -> Vec<SkillRoute> {
257 route_full(model, pipeline, ids, 0.30).scores
258}
259
260pub fn holdout_phis(model: &CmfModel) -> Vec<(usize, Vec<f32>)> {
263 let hidden = model.arch().hidden_size;
264 let mut out = Vec::new();
265 for (si, skill) in model.header.skills.iter().enumerate() {
266 let Some(sel) = &skill.selection else {
267 continue;
268 };
269 let (Some(h), Some(n)) = (sel.holdout.as_ref(), sel.holdout_n) else {
270 continue;
271 };
272 let Some(v) = decode_f16(h) else { continue };
273 if v.len() != n * hidden {
274 continue;
275 }
276 for i in 0..n {
277 out.push((si, v[i * hidden..(i + 1) * hidden].to_vec()));
278 }
279 }
280 out
281}
282
283pub fn calibrate(
290 model: &CmfModel,
291 target_fpr: f32,
292) -> Option<cortiq_core::format::RoutingCalibration> {
293 let samples = holdout_phis(model);
294 if samples.is_empty() {
295 return None;
296 }
297 let skills = &model.header.skills;
300 let mut per_sample: Vec<(Vec<ErrorRow>, usize)> = Vec::new();
301 for (si, phi) in &samples {
302 let layer = skills[*si]
303 .selection
304 .as_ref()
305 .map(|s| s.phi_layer)
306 .unwrap_or(0);
307 let mut phi_of =
308 |l: usize| -> Vec<f32> { if l == layer { phi.clone() } else { Vec::new() } };
309 let rows = error_rows(model, &mut phi_of);
310 let Some(pos) = rows.iter().position(|r| r.0 == skills[*si].id) else {
311 continue;
312 };
313 per_sample.push((rows, pos));
314 }
315 if per_sample.is_empty() {
316 return None;
317 }
318 let mut best_t = 1.0f32;
320 let mut best_nll = f32::INFINITY;
321 let mut t = 1e-3f32;
322 while t <= 1e6 {
325 let mut nll = 0.0f32;
326 for (rows, pos) in &per_sample {
327 let mut logits: Vec<f32> = rows.iter().map(|r| -r.1 / t).collect();
328 softmax(&mut logits);
329 nll -= logits[*pos].max(1e-9).ln();
330 }
331 if nll < best_nll {
332 best_nll = nll;
333 best_t = t;
334 }
335 t *= 1.15;
336 }
337 let mut cal = cortiq_core::format::RoutingCalibration {
338 temperature: best_t,
339 novelty_theta: 0.5,
340 samples: per_sample.len(),
341 target_fpr,
342 };
343 let mut nov: Vec<f32> = per_sample
345 .iter()
346 .map(|(rows, _)| decide(rows, Some(&cal), 1.0).novelty)
347 .filter(|v| v.is_finite())
348 .collect();
349 nov.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
350 if !nov.is_empty() {
351 let q = (1.0 - target_fpr).clamp(0.0, 1.0);
352 let idx = (((nov.len() - 1) as f32) * q).round() as usize;
353 cal.novelty_theta = (nov[idx.min(nov.len() - 1)] + 1e-4).min(0.999);
354 }
355 Some(cal)
356}
357
358pub type ErrorRow = (String, f32, Option<f32>, Option<f32>, f32);
363
364#[derive(Debug, Clone, PartialEq, Eq)]
366pub enum RouteTarget {
367 Backbone,
368 Skill(String),
369}
370
371#[derive(Debug, Clone)]
374pub struct RouteDecision {
375 pub target: RouteTarget,
376 pub routing: Routing,
377 pub reason: String,
378}
379
380impl RouteDecision {
381 pub fn skill(&self) -> Option<&str> {
383 match &self.target {
384 RouteTarget::Skill(s) => Some(s),
385 RouteTarget::Backbone => None,
386 }
387 }
388
389 pub fn forced(target: RouteTarget, reason: impl Into<String>) -> Self {
393 Self {
394 target,
395 routing: Routing {
396 scores: Vec::new(),
397 confidence: 0.0,
398 margin: 0.0,
399 novelty: f32::NAN,
400 is_novel: false,
401 calibrated: false,
402 },
403 reason: reason.into(),
404 }
405 }
406
407 pub fn target_label(&self) -> &str {
409 self.skill().unwrap_or("backbone")
410 }
411
412 pub fn e_base(&self) -> Option<f32> {
414 self.routing
415 .scores
416 .iter()
417 .find(|s| s.id == BACKBONE_CLASS_ID)
418 .map(|s| s.error)
419 }
420
421 pub fn e_skill(&self) -> Option<f32> {
424 self.routing
425 .scores
426 .iter()
427 .find(|s| s.id != BACKBONE_CLASS_ID)
428 .map(|s| s.error)
429 }
430
431 pub fn nearest_skill(&self) -> Option<&str> {
433 self.routing
434 .scores
435 .iter()
436 .find(|s| s.id != BACKBONE_CLASS_ID)
437 .map(|s| s.id.as_str())
438 }
439
440 pub fn describe(&self) -> String {
443 let f = |v: Option<f32>| match v {
444 Some(x) if x.is_finite() => format!("{x:.4}"),
445 _ => "—".to_string(),
446 };
447 let nov = if self.routing.novelty.is_finite() {
448 format!("{:.3}", self.routing.novelty)
449 } else {
450 "—".to_string()
451 };
452 format!(
453 "route: {} | novelty {nov} | E_base {} | E_skill {} | {}",
454 self.target_label(),
455 f(self.e_base()),
456 f(self.e_skill()),
457 self.reason
458 )
459 }
460
461 pub fn summary_json(&self) -> serde_json::Value {
464 let num = |v: Option<f32>| -> serde_json::Value {
465 match v {
466 Some(x) if x.is_finite() => serde_json::json!(x),
467 _ => serde_json::Value::Null,
468 }
469 };
470 serde_json::json!({
471 "target": self.target_label(),
472 "novelty": num(Some(self.routing.novelty)),
473 "e_base": num(self.e_base()),
474 "e_skill": num(self.e_skill()),
475 "reason": self.reason,
476 })
477 }
478}
479
480#[derive(Debug, Clone, Copy, Default)]
482pub struct RouteOptions {
483 pub include_quarantine: bool,
489}
490
491pub fn is_routable(s: &SkillRecord, opts: RouteOptions) -> bool {
497 s.is_auto_routable()
498 || (opts.include_quarantine
499 && s.is_v2()
500 && s.selection.is_some()
501 && s.status.as_deref() != Some("retired"))
502}
503
504pub fn routable_skills<'a>(header: &'a CmfHeader, opts: RouteOptions) -> Vec<&'a SkillRecord> {
507 calibration_classes(header)
508 .into_iter()
509 .filter(|s| is_routable(s, opts))
510 .collect()
511}
512
513pub fn no_candidate_reason(header: &CmfHeader, opts: RouteOptions) -> Option<String> {
518 let Some(policy) = &header.router else {
519 return Some("the file declares no router policy (ROUTER_V2)".into());
520 };
521 if header.routing.is_none() {
522 return Some("the router is not calibrated (header.routing absent)".into());
523 }
524 let h = skills_hash(header);
525 if parse_hex64(&policy.skills_hash) != Some(h) {
526 return Some(format!(
527 "skills_hash mismatch (router {}, file {h:016x}): the calibration is stale",
528 policy.skills_hash
529 ));
530 }
531 if routable_skills(header, opts).is_empty() {
532 let classes: Vec<String> = calibration_classes(header)
533 .iter()
534 .map(|s| format!("{}={}", s.id, s.status.as_deref().unwrap_or("?")))
535 .collect();
536 return Some(format!(
537 "no routable skill class (classes {classes:?}; include_quarantine {})",
538 opts.include_quarantine
539 ));
540 }
541 None
542}
543
544pub const PROMPT_CONTRACT_CMF_IM_V1: &str = "cmf-im-v1";
549const IM_USER: &str = "<|im_start|>user\n";
550const IM_ASSISTANT: &str = "<|im_end|>\n<|im_start|>assistant\n";
551
552pub fn render_cmf_im_v1(user_text: &str) -> String {
555 format!("{IM_USER}{user_text}{IM_ASSISTANT}")
556}
557
558pub fn cmf_im_v1_single_user_turn(prompt: &str) -> Option<&str> {
562 let q = prompt.strip_prefix(IM_USER)?.strip_suffix(IM_ASSISTANT)?;
563 (!q.contains("<|im_start|>") && !q.contains("<|im_end|>")).then_some(q)
564}
565
566#[derive(Debug, Clone, Copy, PartialEq, Eq)]
568pub enum PromptFrame {
569 Raw,
571 CmfImV1,
574 Other,
576}
577
578impl PromptFrame {
579 fn label(self) -> &'static str {
580 match self {
581 Self::Raw => "a raw completion prompt",
582 Self::CmfImV1 => "cmf-im-v1",
583 Self::Other => "another chat template",
584 }
585 }
586}
587
588pub fn chat_frame(tok: &crate::tokenizer::Tokenizer) -> PromptFrame {
592 match &tok.chat_template {
593 Some(t) if t.contains("<|im_start|>") && t.contains("<|im_end|>") => PromptFrame::CmfImV1,
594 Some(_) => PromptFrame::Other,
595 None if tok.im_start_id.is_some() && tok.im_end_id.is_some() => PromptFrame::CmfImV1,
596 None => PromptFrame::Other,
597 }
598}
599
600pub fn enforce_prompt_contract(
612 header: &CmfHeader,
613 decision: RouteDecision,
614 frame: PromptFrame,
615 can_render: bool,
616) -> (RouteDecision, bool) {
617 let Some(id) = decision.skill() else {
618 return (decision, false);
619 };
620 let Some(contract) = header
621 .skills
622 .iter()
623 .find(|s| s.id == id)
624 .and_then(|s| s.prompt_contract.clone())
625 else {
626 return (decision, false);
627 };
628 let known = contract == PROMPT_CONTRACT_CMF_IM_V1;
629 if known && frame == PromptFrame::CmfImV1 {
630 return (decision, false);
631 }
632 if known && frame == PromptFrame::Raw && can_render {
633 let mut d = decision;
634 d.reason = format!(
635 "{}; prompt rendered as {PROMPT_CONTRACT_CMF_IM_V1} (the skill's contract)",
636 d.reason
637 );
638 return (d, true);
639 }
640 let reason = format!(
641 "prompt contract '{contract}' of skill '{id}' not satisfied ({}): the backbone runs \
642 [decision was: {}]",
643 frame.label(),
644 decision.reason
645 );
646 (
647 RouteDecision {
648 target: RouteTarget::Backbone,
649 routing: decision.routing,
650 reason,
651 },
652 false,
653 )
654}
655
656pub fn is_router_v2(model: &CmfModel) -> bool {
659 model.header.router.is_some()
660}
661
662pub fn route_request(model: &CmfModel, backbone: &mut Pipeline, user_text: &str) -> RouteDecision {
676 route_request_with(model, backbone, user_text, RouteOptions::default())
677}
678
679pub fn route_request_with(
681 model: &CmfModel,
682 backbone: &mut Pipeline,
683 user_text: &str,
684 opts: RouteOptions,
685) -> RouteDecision {
686 let q_ids = if model.header.router.is_some() {
692 backbone.tokenizer.encode_plain(user_text)
693 } else {
694 backbone.tokenizer.encode(user_text)
695 };
696 route_request_ids(model, backbone, &q_ids, opts)
697}
698
699pub fn route_request_keyed(
703 model: &std::sync::Arc<CmfModel>,
704 backbone: &mut Pipeline,
705 user_text: &str,
706 opts: RouteOptions,
707) -> RouteDecision {
708 match &model.header.router {
709 Some(p) if p.phi.pool == cortiq_core::knowledge::phi_pool::KEY_SPAN => {
710 let holders = crate::lookup::key_holders(model, user_text);
711 let no_key = RouteDecision::forced(RouteTarget::Backbone, "no table key in the message");
712 if holders.is_empty() {
713 return route_keyless(model, backbone, p, user_text, no_key);
714 }
715 let d = match crate::lookup::key_span_phi(model, backbone, &p.phi, user_text) {
716 Some(phi) => route_policy_candidates(&model.header, &phi, opts, &holders),
717 None => no_key,
718 };
719 if d.target == RouteTarget::Backbone {
721 return route_keyless(model, backbone, p, user_text, d);
722 }
723 d
724 }
725 _ => route_request_with(model, backbone, user_text, opts),
726 }
727}
728
729pub fn route_turns_keyed(
733 model: &std::sync::Arc<CmfModel>,
734 backbone: &mut Pipeline,
735 turns: &[&str],
736 opts: RouteOptions,
737) -> RouteDecision {
738 let last = turns.first().copied().unwrap_or("");
739 let Some(p) = model.header.router.as_ref().filter(|p| {
740 p.phi.pool == cortiq_core::knowledge::phi_pool::KEY_SPAN && p.phi.followup_alpha.is_some()
741 }) else {
742 return route_request_keyed(model, backbone, last, opts);
743 };
744 if turns.len() < 2 || crate::lookup::message_names_key(model, last) {
745 return route_request_keyed(model, backbone, last, opts);
746 }
747 let d = match crate::lookup::followup_phi(model, backbone, &p.phi, last, &turns[1..]) {
748 Some((phi, holders)) => {
749 if let (Some(f), Some(m)) = (p.followup.as_ref(), p.phi.followup_margin) {
750 route_followup(&model.header, f, m, &phi, &holders, opts)
751 } else {
752 let mut header = model.header.clone();
753 if let (Some(r), Some(m)) = (header.router.as_mut(), p.phi.followup_margin) {
754 r.margin = m;
755 }
756 route_policy_candidates(&header, &phi, opts, &holders)
757 }
758 }
759 None => RouteDecision::forced(RouteTarget::Backbone, "no table key in the conversation"),
760 };
761 if d.target == RouteTarget::Backbone {
762 return route_keyless(model, backbone, p, last, d);
763 }
764 d
765}
766
767const KEYLESS_MAX_TOKENS: usize = 160;
770
771fn route_keyless(
775 model: &std::sync::Arc<CmfModel>,
776 backbone: &mut Pipeline,
777 p: &RouterPolicy,
778 user_text: &str,
779 otherwise: RouteDecision,
780) -> RouteDecision {
781 let Some(k) = p.keyless.as_ref() else {
782 return otherwise;
783 };
784 let q = backbone.tokenizer.encode_plain(user_text);
785 if q.is_empty() || q.len() > KEYLESS_MAX_TOKENS {
786 return otherwise;
787 }
788 let (ids, span) = phi_span_ids(&p.phi, &q);
789 let hidden = backbone.hidden_size;
790 let rows = backbone.probe_hidden_rows(&ids, p.phi.layer);
791 let phi = pool_key_span(&rows, hidden, span, (0, 0), 1.0);
792 let (Some(fact), Some(chat)) = (
793 descriptor_row(&k.record, &k.fact, &phi, hidden),
794 descriptor_row(BACKBONE_CLASS_ID, &k.chat, &phi, hidden),
795 ) else {
796 return otherwise;
797 };
798 let (e_fact, e_chat) = (fact.1 / fact.4.max(1e-12), chat.1 / chat.4.max(1e-12));
799 if !(e_fact + k.margin < e_chat) {
800 return otherwise;
801 }
802 RouteDecision {
803 target: RouteTarget::Skill(k.record.clone()),
804 routing: decide(&[chat, fact], None, 1.0),
805 reason: format!("asks for a fact outside the records: E {e_fact:.4} + {:.4} < E_chat {e_chat:.4}", k.margin),
806 }
807}
808
809fn route_followup(
812 header: &CmfHeader,
813 f: &cortiq_core::knowledge::FollowupTopologies,
814 margin: f32,
815 phi: &[f32],
816 holders: &[String],
817 opts: RouteOptions,
818) -> RouteDecision {
819 let hidden = header.arch.hidden_size;
820 let phi = unit_copy(phi);
821 let base = descriptor_row(BACKBONE_CLASS_ID, &f.base, &phi, hidden);
822 let skills: Vec<ErrorRow> = holders
823 .iter()
824 .filter(|id| header.skills.iter().any(|s| s.id == **id && is_routable(s, opts)))
825 .filter_map(|id| descriptor_row(id, f.skills.get(id)?, &phi, hidden))
826 .collect();
827 let mut rows: Vec<ErrorRow> = base.iter().cloned().collect();
828 rows.extend(skills.iter().cloned());
829 let routing = decide(&rows, None, 1.0);
830 let backbone = |routing: Routing, reason: String| RouteDecision { target: RouteTarget::Backbone, routing, reason };
831 let (Some(base), Some(best)) = (base, skills.iter().min_by(|a, b| a.1.total_cmp(&b.1))) else {
832 return backbone(routing, "no follow-up topology for the records holding the key".into());
833 };
834 let (e_base, e_skill) = (base.1 / base.4.max(1e-12), best.1 / best.4.max(1e-12));
835 if !(e_skill + margin < e_base) {
836 let reason = format!("follow-up margin not beaten: E_skill {e_skill:.4} + {margin:.4} ≥ E_base {e_base:.4}");
837 return backbone(routing, reason);
838 }
839 let reason = format!("follow-up on '{}': E {e_skill:.4} + {margin:.4} < E_base {e_base:.4}", best.0);
840 RouteDecision { target: RouteTarget::Skill(best.0.clone()), routing, reason }
841}
842
843pub fn route_request_ids(
845 model: &CmfModel,
846 backbone: &mut Pipeline,
847 q_ids: &[u32],
848 opts: RouteOptions,
849) -> RouteDecision {
850 match &model.header.router {
851 Some(policy) if policy.phi.pool == cortiq_core::knowledge::phi_pool::KEY_SPAN => {
852 RouteDecision::forced(
853 RouteTarget::Backbone,
854 "router.phi.pool key_span needs the message text (route_request_keyed)",
855 )
856 }
857 Some(policy) => {
858 let (ids, span) = phi_span_ids(&policy.phi, q_ids);
859 let phi = backbone.probe_phi_span(&ids, policy.phi.layer, span);
860 route_policy_with(&model.header, &phi, opts)
861 }
862 None => route_legacy(model, backbone, q_ids),
863 }
864}
865
866fn route_legacy(model: &CmfModel, backbone: &mut Pipeline, ids: &[u32]) -> RouteDecision {
870 let tau = std::env::var("CMF_OOD_TAU")
871 .ok()
872 .and_then(|v| v.parse().ok())
873 .unwrap_or(0.30);
874 let routing = route_full(model, backbone, ids, tau);
875 let Some(w) = routing.winner().cloned() else {
876 return RouteDecision {
877 target: RouteTarget::Backbone,
878 routing,
879 reason: "no routable skill in this container".into(),
880 };
881 };
882 if routing.is_novel {
883 let reason = if routing.calibrated {
884 format!(
885 "novel input (novelty {:.3} over θ): nearest '{}' not taken",
886 routing.novelty, w.id
887 )
888 } else {
889 format!(
890 "novel input (E_min {:.4} > τ {tau}, uncalibrated file): nearest '{}' not taken",
891 w.error, w.id
892 )
893 };
894 return RouteDecision {
895 target: RouteTarget::Backbone,
896 routing,
897 reason,
898 };
899 }
900 let reason = format!(
901 "legacy recon-argmin: '{}' (E {:.4}), in scope",
902 w.id, w.error
903 );
904 RouteDecision {
905 target: RouteTarget::Skill(w.id),
906 routing,
907 reason,
908 }
909}
910
911pub fn calibration_classes(header: &CmfHeader) -> Vec<&SkillRecord> {
916 let mut v: Vec<&SkillRecord> = header
917 .skills
918 .iter()
919 .filter(|s| s.is_v2() && s.selection.is_some() && s.status.as_deref() != Some("retired"))
920 .collect();
921 v.sort_by(|a, b| a.id.as_bytes().cmp(b.id.as_bytes()));
922 v
923}
924
925pub fn skills_hash(header: &CmfHeader) -> u64 {
934 fn push(buf: &mut Vec<u8>, id: &str, d: &SelectionDescriptor) {
935 buf.extend_from_slice(id.as_bytes());
936 buf.push(0);
937 buf.extend_from_slice(d.metric.as_bytes());
938 buf.push(0);
939 buf.extend_from_slice(&(d.phi_layer as u64).to_le_bytes());
940 buf.extend_from_slice(&(d.rank as u64).to_le_bytes());
941 buf.extend_from_slice(d.mean.as_bytes());
942 buf.push(0);
943 buf.extend_from_slice(d.basis.as_bytes());
944 buf.push(0);
945 for x in [d.err_mean, d.err_std] {
946 match x {
947 Some(v) => {
948 buf.push(1);
949 buf.extend_from_slice(&v.to_bits().to_le_bytes());
950 }
951 None => buf.push(0),
952 }
953 }
954 buf.push(0x1e);
955 }
956 let mut buf = b"cmf-skills-v2\0".to_vec();
957 match &header.router {
958 Some(r) => push(&mut buf, BACKBONE_CLASS_ID, &r.base),
959 None => buf.push(0),
960 }
961 for s in calibration_classes(header) {
962 push(&mut buf, &s.id, s.selection.as_ref().expect("filtered"));
963 }
964 hash64(&buf)
965}
966
967pub fn phi_span_ids(policy: &PhiSpec, q_ids: &[u32]) -> (Vec<u32>, std::ops::Range<usize>) {
972 let mut ids =
973 Vec::with_capacity(policy.prefix_ids.len() + q_ids.len() + policy.suffix_ids.len());
974 ids.extend_from_slice(&policy.prefix_ids);
975 let start = ids.len();
976 ids.extend_from_slice(q_ids);
977 let end = ids.len();
978 ids.extend_from_slice(&policy.suffix_ids);
979 (ids, start..end)
980}
981
982pub fn pool_span_unit(hiddens: &[f32], hidden: usize, span: std::ops::Range<usize>) -> Vec<f32> {
987 let rows = hiddens.len() / hidden.max(1);
988 let (a, b) = (span.start.min(rows), span.end.min(rows));
989 let mut phi = vec![0.0f32; hidden];
990 if b <= a {
991 return phi;
992 }
993 for r in a..b {
994 for (p, x) in phi.iter_mut().zip(&hiddens[r * hidden..(r + 1) * hidden]) {
995 *p += x;
996 }
997 }
998 let n = phi.iter().map(|x| x * x).sum::<f32>().sqrt();
999 if n > 0.0 {
1000 phi.iter_mut().for_each(|x| *x /= n);
1001 }
1002 phi
1003}
1004
1005pub fn pool_key_span(
1008 hiddens: &[f32],
1009 hidden: usize,
1010 span: std::ops::Range<usize>,
1011 window: (usize, usize),
1012 alpha: f32,
1013) -> Vec<f32> {
1014 let unit_mean = |rows: &mut dyn Iterator<Item = usize>| -> Vec<f32> {
1015 let mut p = vec![0f32; hidden];
1016 for r in rows {
1017 for (a, x) in p.iter_mut().zip(&hiddens[r * hidden..(r + 1) * hidden]) {
1018 *a += x;
1019 }
1020 }
1021 unit_copy(&p)
1022 };
1023 let (ka, kb) = (span.start + window.0, span.start + window.1);
1024 let key = unit_mean(&mut (ka..kb));
1025 let rest = unit_mean(&mut (span.start..ka).chain(kb..span.end));
1026 let mix: Vec<f32> = key.iter().zip(&rest).map(|(k, r)| k + alpha * r).collect();
1027 unit_copy(&mix)
1028}
1029
1030pub fn holdout_count(n: usize) -> usize {
1037 (n / 5).clamp(1, n.saturating_sub(2).max(1))
1038}
1039
1040pub fn encode_f16(v: &[f32]) -> String {
1044 let bytes: Vec<u8> = v
1045 .iter()
1046 .flat_map(|x| f32_to_f16(*x).to_le_bytes())
1047 .collect();
1048 base64::engine::general_purpose::STANDARD.encode(bytes)
1049}
1050
1051fn f16_round(v: &[f32]) -> Vec<f32> {
1052 v.iter().map(|x| f16_to_f32(f32_to_f16(*x))).collect()
1053}
1054
1055fn gauss_vec(seed: u64, n: usize) -> Vec<f32> {
1058 let mut s = seed
1059 .wrapping_mul(6_364_136_223_846_793_005)
1060 .wrapping_add(1_442_695_040_888_963_407);
1061 let mut next = || {
1062 s = s
1063 .wrapping_mul(6_364_136_223_846_793_005)
1064 .wrapping_add(1_442_695_040_888_963_407);
1065 (s >> 11) as f64 / (1u64 << 53) as f64
1066 };
1067 let mut out = Vec::with_capacity(n + 1);
1068 while out.len() < n {
1069 let (u1, u2) = (next().max(1e-12), next());
1070 let r = (-2.0 * u1.ln()).sqrt();
1071 let t = 2.0 * std::f64::consts::PI * u2;
1072 out.push((r * t.cos()) as f32);
1073 out.push((r * t.sin()) as f32);
1074 }
1075 out.truncate(n);
1076 out
1077}
1078
1079pub fn top_principal_rows(
1087 centered: &[Vec<f32>],
1088 h: usize,
1089 k: usize,
1090 iters: usize,
1091 seed: u64,
1092) -> Vec<f32> {
1093 let mut q = gauss_vec(seed.wrapping_add(777), k * h);
1094 let mut spare = seed.wrapping_add(1);
1095 let mut orth = |q: &mut [f32]| {
1096 for i in 0..k {
1097 loop {
1098 for j in 0..i {
1099 let dot: f32 = (0..h).map(|t| q[i * h + t] * q[j * h + t]).sum();
1100 for t in 0..h {
1101 q[i * h + t] -= dot * q[j * h + t];
1102 }
1103 }
1104 let nrm: f32 = (0..h)
1105 .map(|t| q[i * h + t] * q[i * h + t])
1106 .sum::<f32>()
1107 .sqrt();
1108 if nrm > 1e-12 {
1109 for t in 0..h {
1110 q[i * h + t] /= nrm;
1111 }
1112 break;
1113 }
1114 let fresh = gauss_vec(spare, h);
1115 spare = spare.wrapping_add(1);
1116 q[i * h..(i + 1) * h].copy_from_slice(&fresh);
1117 }
1118 }
1119 };
1120 orth(&mut q);
1121 let mut tmp = vec![0f32; k * h];
1122 let mut coef = vec![0f32; k];
1123 for _ in 0..iters {
1124 tmp.fill(0.0);
1125 for c in centered {
1126 for (i, ci) in coef.iter_mut().enumerate() {
1127 *ci = q[i * h..(i + 1) * h]
1128 .iter()
1129 .zip(c)
1130 .map(|(a, b)| a * b)
1131 .sum();
1132 }
1133 for (i, &ci) in coef.iter().enumerate() {
1134 if ci != 0.0 {
1135 for (t, x) in tmp[i * h..(i + 1) * h].iter_mut().zip(c) {
1136 *t += ci * x;
1137 }
1138 }
1139 }
1140 }
1141 q.copy_from_slice(&tmp);
1142 orth(&mut q);
1143 }
1144 q
1145}
1146
1147pub fn fit_descriptor(
1158 phis_raw: &[Vec<f32>],
1159 phi_layer: usize,
1160 rank: usize,
1161) -> Option<SelectionDescriptor> {
1162 let n = phis_raw.len();
1163 if n < 2 {
1164 return None;
1165 }
1166 let h = phis_raw[0].len();
1167 if h == 0 || phis_raw.iter().any(|p| p.len() != h) {
1168 return None;
1169 }
1170 let phis: Vec<Vec<f32>> = phis_raw.iter().map(|p| unit_copy(p)).collect();
1171 let n_hold = holdout_count(n);
1172 let (train, hold) = phis.split_at(n - n_hold);
1173 let nt = train.len();
1174 let mut mean = vec![0f32; h];
1175 for p in train {
1176 for (m, v) in mean.iter_mut().zip(p) {
1177 *m += v / nt as f32;
1178 }
1179 }
1180 let centered: Vec<Vec<f32>> = train
1181 .iter()
1182 .map(|p| p.iter().zip(&mean).map(|(v, m)| v - m).collect())
1183 .collect();
1184 let rank = rank.min(nt.saturating_sub(1)).max(1);
1185 let basis = top_principal_rows(¢ered, h, rank, 120, 99);
1186 let (mq, bq) = (f16_round(&mean), f16_round(&basis));
1187 let errs: Vec<f32> = train.iter().map(|p| recon_error(p, &mq, &bq, rank)).collect();
1188 let em = errs.iter().sum::<f32>() / nt as f32;
1189 let es = (errs.iter().map(|e| (e - em).powi(2)).sum::<f32>() / nt as f32)
1190 .sqrt()
1191 .max(1e-4);
1192 let hold_flat: Vec<f32> = hold.concat();
1193 Some(SelectionDescriptor {
1194 metric: METRIC_MSE_UNIT.into(),
1195 phi_layer,
1196 mean: encode_f16(&mean),
1197 basis: encode_f16(&basis),
1198 rank,
1199 err_mean: Some(em),
1200 err_std: Some(es),
1201 holdout: Some(encode_f16(&hold_flat)),
1202 holdout_n: Some(hold.len()),
1203 })
1204}
1205
1206pub fn cmf_im_v1_phi_spec(tok: &crate::tokenizer::Tokenizer, layer: usize) -> Result<PhiSpec, String> {
1214 for s in ["<|im_start|>", "<|im_end|>"] {
1215 if tok.encode(s).len() != 1 {
1216 return Err(format!(
1217 "the tokenizer has no added token {s}: the cmf-im-v1 frame cannot be rendered"
1218 ));
1219 }
1220 }
1221 Ok(PhiSpec {
1222 layer,
1223 pool: "span_mean".into(),
1224 norm: "unit".into(),
1225 key_alpha: None,
1226 followup_alpha: None,
1227 followup_margin: None,
1228 prefix_ids: tok.encode(IM_USER),
1229 suffix_ids: tok.encode(IM_ASSISTANT),
1230 })
1231}
1232
1233fn unit_copy(phi: &[f32]) -> Vec<f32> {
1234 let n = phi.iter().map(|x| x * x).sum::<f32>().sqrt();
1235 if n > 0.0 {
1236 phi.iter().map(|x| x / n).collect()
1237 } else {
1238 phi.to_vec()
1239 }
1240}
1241
1242pub fn descriptor_row(
1244 id: &str,
1245 sel: &SelectionDescriptor,
1246 phi_unit: &[f32],
1247 hidden: usize,
1248) -> Option<ErrorRow> {
1249 if sel.metric != METRIC_MSE_UNIT {
1250 return None;
1251 }
1252 let (mean, basis) = (decode_f16(&sel.mean)?, decode_f16(&sel.basis)?);
1253 if mean.len() != hidden || basis.len() != sel.rank * hidden || phi_unit.len() != hidden {
1254 return None;
1255 }
1256 let e = recon_error(phi_unit, &mean, &basis, sel.rank);
1257 let pp: f32 = phi_unit.iter().map(|v| v * v).sum();
1258 Some((id.to_string(), e, sel.err_mean, sel.err_std, pp))
1259}
1260
1261pub fn policy_rows(header: &CmfHeader, phi: &[f32]) -> (Option<ErrorRow>, Vec<ErrorRow>) {
1270 let Some(r) = &header.router else {
1271 return (None, Vec::new());
1272 };
1273 let hidden = header.arch.hidden_size;
1274 let phi = unit_copy(phi);
1275 let base = descriptor_row(BACKBONE_CLASS_ID, &r.base, &phi, hidden);
1276 let skills = calibration_classes(header)
1277 .into_iter()
1278 .filter_map(|s| {
1279 let sel = s.selection.as_ref()?;
1280 if sel.phi_layer != r.phi.layer {
1281 return None;
1282 }
1283 descriptor_row(&s.id, sel, &phi, hidden)
1284 })
1285 .collect();
1286 (base, skills)
1287}
1288
1289pub fn decide_backbone_gated(
1293 base: Option<&ErrorRow>,
1294 skills: &[ErrorRow],
1295 calibration: Option<&RoutingCalibration>,
1296 policy: Option<&RouterPolicy>,
1297 file_skills_hash: u64,
1298) -> RouteDecision {
1299 decide_backbone_gated_with(
1300 base,
1301 skills,
1302 &|_| true,
1303 calibration,
1304 policy,
1305 file_skills_hash,
1306 )
1307}
1308
1309#[allow(clippy::neg_cmp_op_on_partial_ord)]
1322pub fn decide_backbone_gated_with(
1323 base: Option<&ErrorRow>,
1324 skills: &[ErrorRow],
1325 routable: &dyn Fn(&str) -> bool,
1326 calibration: Option<&RoutingCalibration>,
1327 policy: Option<&RouterPolicy>,
1328 file_skills_hash: u64,
1329) -> RouteDecision {
1330 let mut rows: Vec<ErrorRow> = Vec::with_capacity(skills.len() + 1);
1331 rows.extend(base.cloned());
1332 rows.extend(skills.iter().cloned());
1333 let backbone = |routing: Routing, reason: String| RouteDecision {
1334 target: RouteTarget::Backbone,
1335 routing,
1336 reason,
1337 };
1338 let Some(policy) = policy else {
1339 return backbone(
1340 decide(&rows, None, 1.0),
1341 "no router policy (ROUTER_V2 absent): the backbone runs".into(),
1342 );
1343 };
1344 if parse_hex64(&policy.skills_hash) != Some(file_skills_hash) {
1345 return backbone(
1346 decide(&rows, None, 1.0),
1347 format!(
1348 "skills_hash mismatch (router {}, file {file_skills_hash:016x}): the calibration \
1349 is stale — recalibrate",
1350 policy.skills_hash
1351 ),
1352 );
1353 }
1354 let Some(cal) = calibration else {
1355 return backbone(decide(&rows, None, 1.0), "router not calibrated".into());
1356 };
1357 let Some(base) = base else {
1358 return backbone(
1359 decide(&rows, Some(cal), 1.0),
1360 "no backbone descriptor row".into(),
1361 );
1362 };
1363 if !(base.4 > 1e-12) {
1364 return backbone(
1365 decide(&rows, Some(cal), 1.0),
1366 "degenerate φ (empty span)".into(),
1367 );
1368 }
1369 let routing = decide(&rows, Some(cal), 1.0);
1370 if !skills.iter().any(|r| routable(&r.0)) {
1371 return backbone(
1372 routing,
1373 "no routable skill (v2, active, measured gate)".into(),
1374 );
1375 }
1376 let e_base = base.1 / base.4.max(1e-12);
1377 let Some(winner) = routing.winner().cloned() else {
1378 return backbone(routing, "no scored class".into());
1379 };
1380 if winner.id == BACKBONE_CLASS_ID {
1381 return backbone(
1382 routing,
1383 format!("the backbone is the nearest class (E_base {e_base:.4})"),
1384 );
1385 }
1386 if !routable(&winner.id) {
1387 return backbone(
1388 routing,
1389 format!(
1390 "nearest class '{}' is not routable (not active with a measured gate): the \
1391 backbone runs",
1392 winner.id
1393 ),
1394 );
1395 }
1396 if routing.is_novel {
1397 let reason = format!(
1398 "novel input (novelty {:.3} > θ {:.3})",
1399 routing.novelty, cal.novelty_theta
1400 );
1401 return backbone(routing, reason);
1402 }
1403 let e_skill = winner.error;
1404 if !(e_skill + policy.margin < e_base) {
1405 return backbone(
1406 routing,
1407 format!(
1408 "margin not beaten: E_skill {e_skill:.4} + margin {:.4} ≥ E_base {e_base:.4}",
1409 policy.margin
1410 ),
1411 );
1412 }
1413 let reason = format!(
1414 "skill '{}': E {e_skill:.4} + margin {:.4} < E_base {e_base:.4}, confidence {:.3}, \
1415 novelty {:.3} ≤ θ {:.3}",
1416 winner.id, policy.margin, routing.confidence, routing.novelty, cal.novelty_theta
1417 );
1418 RouteDecision {
1419 target: RouteTarget::Skill(winner.id),
1420 routing,
1421 reason,
1422 }
1423}
1424
1425pub fn route_policy(header: &CmfHeader, phi: &[f32]) -> RouteDecision {
1428 route_policy_with(header, phi, RouteOptions::default())
1429}
1430
1431pub fn route_policy_with(header: &CmfHeader, phi: &[f32], opts: RouteOptions) -> RouteDecision {
1434 let (base, skills) = policy_rows(header, phi);
1435 let routable = |id: &str| {
1436 header
1437 .skills
1438 .iter()
1439 .find(|s| s.id == id)
1440 .is_some_and(|s| is_routable(s, opts))
1441 };
1442 decide_backbone_gated_with(
1443 base.as_ref(),
1444 &skills,
1445 &routable,
1446 header.routing.as_ref(),
1447 header.router.as_ref(),
1448 skills_hash(header),
1449 )
1450}
1451
1452pub fn route_policy_candidates(
1455 header: &CmfHeader,
1456 phi: &[f32],
1457 opts: RouteOptions,
1458 candidates: &[String],
1459) -> RouteDecision {
1460 let (base, mut skills) = policy_rows(header, phi);
1461 skills.retain(|r| candidates.iter().any(|c| *c == r.0));
1462 let routable = |id: &str| {
1463 header
1464 .skills
1465 .iter()
1466 .find(|s| s.id == id)
1467 .is_some_and(|s| is_routable(s, opts))
1468 };
1469 let d = decide_backbone_gated_with(
1470 base.as_ref(),
1471 &skills,
1472 &routable,
1473 header.routing.as_ref(),
1474 header.router.as_ref(),
1475 skills_hash(header),
1476 );
1477 let margin = d
1479 .skill()
1480 .and_then(|id| header.skills.iter().find(|s| s.id == id))
1481 .and_then(|s| s.lookup.as_ref())
1482 .and_then(|l| l.route_margin);
1483 if let (Some(m), Some(eb), Some(es)) = (margin, d.e_base(), d.e_skill()) {
1484 if !(es + m < eb) {
1485 let reason = format!("record margin not beaten: E_skill {es:.4} + {m:.4} ≥ E_base {eb:.4}");
1486 return RouteDecision { target: RouteTarget::Backbone, routing: d.routing, reason };
1487 }
1488 }
1489 d
1490}
1491
1492pub fn clopper_pearson_upper(k: usize, n: usize, confidence: f64) -> f64 {
1496 if n == 0 || k >= n {
1497 return 1.0;
1498 }
1499 let q = 1.0 - (1.0 - confidence) / 2.0;
1500 let (a, b) = ((k + 1) as f64, (n - k) as f64);
1501 if k == 0 {
1502 return 1.0 - (1.0 - q).powf(1.0 / n as f64);
1504 }
1505 let (mut lo, mut hi) = (0.0f64, 1.0f64);
1507 for _ in 0..200 {
1508 let mid = 0.5 * (lo + hi);
1509 if reg_inc_beta(a, b, mid) < q {
1510 lo = mid;
1511 } else {
1512 hi = mid;
1513 }
1514 }
1515 0.5 * (lo + hi)
1516}
1517
1518fn ln_gamma(x: f64) -> f64 {
1519 const C: [f64; 9] = [
1521 0.999_999_999_999_809_9,
1522 676.520_368_121_885_1,
1523 -1_259.139_216_722_402_8,
1524 771.323_428_777_653_1,
1525 -176.615_029_162_140_6,
1526 12.507_343_278_686_905,
1527 -0.138_571_095_265_720_12,
1528 9.984_369_578_019_572e-6,
1529 1.505_632_735_149_311_6e-7,
1530 ];
1531 if x < 0.5 {
1532 return (std::f64::consts::PI / (std::f64::consts::PI * x).sin()).ln() - ln_gamma(1.0 - x);
1533 }
1534 let x = x - 1.0;
1535 let mut acc = C[0];
1536 for (i, c) in C.iter().enumerate().skip(1) {
1537 acc += c / (x + i as f64);
1538 }
1539 let t = x + 7.5;
1540 0.5 * (2.0 * std::f64::consts::PI).ln() + (x + 0.5) * t.ln() - t + acc.ln()
1541}
1542
1543fn reg_inc_beta(a: f64, b: f64, x: f64) -> f64 {
1545 if x <= 0.0 {
1546 return 0.0;
1547 }
1548 if x >= 1.0 {
1549 return 1.0;
1550 }
1551 let ln_front = ln_gamma(a + b) - ln_gamma(a) - ln_gamma(b) + a * x.ln() + b * (1.0 - x).ln();
1552 let cf = |a: f64, b: f64, x: f64| -> f64 {
1553 let tiny = 1e-300;
1554 let (qab, qap, qam) = (a + b, a + 1.0, a - 1.0);
1555 let mut c = 1.0;
1556 let mut d = 1.0 - qab * x / qap;
1557 if d.abs() < tiny {
1558 d = tiny;
1559 }
1560 d = 1.0 / d;
1561 let mut h = d;
1562 for m in 1..400 {
1563 let m = m as f64;
1564 let m2 = 2.0 * m;
1565 let aa = m * (b - m) * x / ((qam + m2) * (a + m2));
1566 d = 1.0 + aa * d;
1567 if d.abs() < tiny {
1568 d = tiny;
1569 }
1570 c = 1.0 + aa / c;
1571 if c.abs() < tiny {
1572 c = tiny;
1573 }
1574 d = 1.0 / d;
1575 h *= d * c;
1576 let aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
1577 d = 1.0 + aa * d;
1578 if d.abs() < tiny {
1579 d = tiny;
1580 }
1581 c = 1.0 + aa / c;
1582 if c.abs() < tiny {
1583 c = tiny;
1584 }
1585 d = 1.0 / d;
1586 let del = d * c;
1587 h *= del;
1588 if (del - 1.0).abs() < 1e-15 {
1589 break;
1590 }
1591 }
1592 h
1593 };
1594 if x < (a + 1.0) / (a + b + 2.0) {
1595 ln_front.exp() * cf(a, b, x) / a
1596 } else {
1597 1.0 - ln_front.exp() * cf(b, a, 1.0 - x) / b
1598 }
1599}
1600
1601pub fn calibrate_v2(
1614 header: &CmfHeader,
1615 target_fpr: f32,
1616) -> Result<(RoutingCalibration, serde_json::Value), String> {
1617 let policy = header
1618 .router
1619 .as_ref()
1620 .ok_or("calibrate_v2: the file declares no router policy (header.router)")?;
1621 let hidden = header.arch.hidden_size;
1622 let classes = calibration_classes(header);
1623 if classes.is_empty() {
1624 return Err("calibrate_v2: no v2 skill with a selection descriptor".into());
1625 }
1626 let mut descs: Vec<(&str, &SelectionDescriptor)> = vec![(BACKBONE_CLASS_ID, &policy.base)];
1628 for s in &classes {
1629 let sel = s.selection.as_ref().expect("filtered");
1630 if sel.metric != METRIC_MSE_UNIT || sel.phi_layer != policy.phi.layer {
1631 return Err(format!(
1632 "calibrate_v2: skill '{}' descriptor is {}@{}, the policy needs {METRIC_MSE_UNIT}@{}",
1633 s.id, sel.metric, sel.phi_layer, policy.phi.layer
1634 ));
1635 }
1636 descs.push((&s.id, sel));
1637 }
1638 let holdout = |d: &SelectionDescriptor| -> Vec<Vec<f32>> {
1639 let (Some(h), Some(n)) = (d.holdout.as_ref(), d.holdout_n) else {
1640 return Vec::new();
1641 };
1642 match decode_f16(h) {
1643 Some(v) if v.len() == n * hidden => (0..n)
1644 .map(|i| unit_copy(&v[i * hidden..(i + 1) * hidden]))
1645 .collect(),
1646 _ => Vec::new(),
1647 }
1648 };
1649 let mut samples: Vec<(usize, Vec<ErrorRow>)> = Vec::new();
1651 for (ci, (_, d)) in descs.iter().enumerate() {
1652 for phi in holdout(d) {
1653 let rows: Option<Vec<ErrorRow>> = descs
1654 .iter()
1655 .map(|(id, dd)| descriptor_row(id, dd, &phi, hidden))
1656 .collect();
1657 let rows = rows.ok_or("calibrate_v2: a descriptor is malformed (dims / base64)")?;
1658 samples.push((ci, rows));
1659 }
1660 }
1661 let n_general = samples.iter().filter(|(c, _)| *c == 0).count();
1662 let n_in = samples.len() - n_general;
1663 if n_general == 0 {
1664 return Err("calibrate_v2: router.base carries no holdout (general prompts)".into());
1665 }
1666 if n_in == 0 {
1667 return Err("calibrate_v2: no skill carries an in-scope holdout".into());
1668 }
1669
1670 let mut best_t = 1.0f32;
1672 let mut best_nll = f32::INFINITY;
1673 let mut t = 1e-3f32;
1674 while t <= 1e6 {
1675 let mut nll = 0.0f32;
1676 for (ci, rows) in &samples {
1677 let mut logits: Vec<f32> = rows.iter().map(|r| -r.1 / t).collect();
1678 softmax(&mut logits);
1679 nll -= logits[*ci].max(1e-9).ln();
1680 }
1681 if nll < best_nll {
1682 best_nll = nll;
1683 best_t = t;
1684 }
1685 t *= 1.15;
1686 }
1687 let mut cal = RoutingCalibration {
1688 temperature: best_t,
1689 novelty_theta: 0.5,
1690 samples: samples.len(),
1691 target_fpr,
1692 };
1693 let mut nov: Vec<f32> = samples
1695 .iter()
1696 .filter(|(c, _)| *c > 0)
1697 .map(|(_, rows)| decide(rows, Some(&cal), 1.0).novelty)
1698 .filter(|v| v.is_finite())
1699 .collect();
1700 nov.sort_by(|a, b| a.total_cmp(b));
1701 if !nov.is_empty() {
1702 let q = (1.0 - target_fpr).clamp(0.0, 1.0);
1703 let idx = (((nov.len() - 1) as f32) * q).round() as usize;
1704 cal.novelty_theta = (nov[idx.min(nov.len() - 1)] + 1e-4).min(0.999);
1705 }
1706
1707 let hash = skills_hash(header);
1709 let mut pol = policy.clone();
1710 pol.skills_hash = format!("{hash:016x}");
1711 let (mut fa, mut hit) = (0usize, 0usize);
1712 let mut per: Vec<(usize, usize)> = vec![(0, 0); descs.len()];
1713 for (ci, rows) in &samples {
1714 let d = decide_backbone_gated(Some(&rows[0]), &rows[1..], Some(&cal), Some(&pol), hash);
1715 if *ci == 0 {
1716 fa += usize::from(d.skill().is_some());
1717 } else {
1718 per[*ci].0 += 1;
1719 if d.skill() == Some(descs[*ci].0) {
1720 hit += 1;
1721 per[*ci].1 += 1;
1722 }
1723 }
1724 }
1725 let per_skill: serde_json::Map<String, serde_json::Value> = descs
1726 .iter()
1727 .enumerate()
1728 .skip(1)
1729 .map(|(i, (id, _))| {
1730 let (n, h) = per[i];
1731 (
1732 id.to_string(),
1733 serde_json::json!({"n": n, "recall": if n > 0 { h as f64 / n as f64 } else { f64::NAN }}),
1734 )
1735 })
1736 .collect();
1737 let measured = serde_json::json!({
1738 "set": "calibration",
1739 "n_in": n_in,
1740 "n_general": n_general,
1741 "in_scope_recall": hit as f64 / n_in as f64,
1742 "false_accept": fa as f64 / n_general as f64,
1743 "false_accept_upper95": clopper_pearson_upper(fa, n_general, 0.95),
1744 "temperature": cal.temperature,
1745 "novelty_theta": cal.novelty_theta,
1746 "target_fpr": target_fpr,
1747 "margin": policy.margin,
1748 "skills_hash": format!("{hash:016x}"),
1749 "per_skill": per_skill,
1750 });
1751 Ok((cal, measured))
1752}
1753
1754#[cfg(test)]
1755mod tests {
1756 use super::*;
1757 use cortiq_core::knowledge::skill_kind;
1758
1759 const HID: usize = 16;
1760
1761 #[test]
1762 fn pool_key_span_mixes_the_key_and_the_rest_as_unit_vectors() {
1763 let rows = [9.0, 9.0, 3.0, 0.0, 0.0, 5.0, 0.0, 5.0];
1765 let phi = pool_key_span(&rows, 2, 1..4, (0, 1), 0.5);
1766 let n = (1.0f32 + 0.25).sqrt();
1767 assert!((phi[0] - 1.0 / n).abs() < 1e-6 && (phi[1] - 0.5 / n).abs() < 1e-6, "{phi:?}");
1768 let phi = pool_key_span(&rows[..4], 2, 1..2, (0, 1), 0.5);
1770 assert!((phi[0] - 1.0).abs() < 1e-6 && phi[1].abs() < 1e-6, "{phi:?}");
1771 }
1772
1773 #[test]
1774 fn holdout_count_is_a_fifth_at_least_one_and_leaves_train_samples() {
1775 for (n, want) in [(1, 1), (2, 1), (3, 1), (5, 1), (6, 1), (10, 2), (50, 10), (4000, 800)] {
1776 assert_eq!(holdout_count(n), want, "n = {n}");
1777 }
1778 }
1779
1780 #[test]
1781 fn encode_f16_round_trips_through_decode() {
1782 let v: Vec<f32> = (0..HID).map(|i| (i as f32 - 7.5) * 0.125).collect();
1783 assert_eq!(decode_f16(&encode_f16(&v)).unwrap(), v);
1784 }
1785
1786 #[test]
1791 fn fit_descriptor_recovers_the_dominant_direction() {
1792 let h = 8;
1793 let base: Vec<f32> = (0..h).map(|i| 1.0 + 0.05 * i as f32).collect();
1794 let dir: Vec<f32> = (0..h).map(|i| if i % 2 == 0 { 1.0 } else { -1.0 }).collect();
1795 let noise = gauss_vec(5, 40 * h);
1796 let samples: Vec<Vec<f32>> = (0..40)
1797 .map(|s| {
1798 let a = (s as f32 / 39.0 - 0.5) * 2.0;
1799 (0..h)
1800 .map(|i| base[i] + a * dir[i] + 0.02 * noise[s * h + i])
1801 .collect()
1802 })
1803 .collect();
1804 let d = fit_descriptor(&samples, 3, 1).expect("fit");
1805 assert_eq!(d.metric, METRIC_MSE_UNIT);
1806 assert_eq!(d.phi_layer, 3);
1807 assert_eq!(d.rank, 1);
1808 assert_eq!(d.holdout_n, Some(8));
1809 let basis = decode_f16(&d.basis).unwrap();
1810 assert_eq!(basis.len(), h);
1811 let mean = decode_f16(&d.mean).unwrap();
1815 let unit = |v: &[f32]| unit_copy(v);
1816 let mut proj = vec![0f32; h];
1817 let (hi, lo) = (unit(&samples[39]), unit(&samples[0]));
1818 for i in 0..h {
1819 proj[i] = hi[i] - lo[i];
1820 }
1821 let proj = unit(&proj);
1822 let cos: f32 = basis.iter().zip(&proj).map(|(a, b)| a * b).sum();
1823 assert!(cos.abs() > 0.95, "cos {cos}");
1824 assert!(d.err_mean.unwrap().is_finite() && d.err_std.unwrap() >= 1e-4);
1825 assert_eq!(mean.len(), h);
1826 let hold = decode_f16(d.holdout.as_ref().unwrap()).unwrap();
1827 assert_eq!(hold.len(), 8 * h);
1828 let last = unit(&samples[39]);
1830 for (a, b) in hold[7 * h..].iter().zip(&last) {
1831 assert!((a - b).abs() < 2e-3, "{a} vs {b}");
1832 }
1833 let d2 = fit_descriptor(&samples, 3, 2).unwrap();
1835 let b2 = decode_f16(&d2.basis).unwrap();
1836 let dot: f32 = b2[..h].iter().zip(&b2[h..]).map(|(a, b)| a * b).sum();
1837 let n1: f32 = b2[..h].iter().map(|a| a * a).sum::<f32>().sqrt();
1838 assert!(dot.abs() < 1e-2 && (n1 - 1.0).abs() < 1e-2, "dot {dot} n1 {n1}");
1839 assert_eq!(fit_descriptor(&samples[..3], 0, 16).unwrap().rank, 1);
1841 assert!(fit_descriptor(&samples[..1], 0, 1).is_none());
1842 assert!(fit_descriptor(&[vec![1.0], vec![1.0, 2.0]], 0, 1).is_none());
1843 assert_eq!(fit_descriptor(&samples, 3, 2).unwrap().basis, d2.basis);
1845 }
1846
1847 fn f16_b64(v: &[f32]) -> String {
1848 let bytes: Vec<u8> = v
1849 .iter()
1850 .flat_map(|x| cortiq_core::quant::f32_to_f16(*x).to_le_bytes())
1851 .collect();
1852 base64::engine::general_purpose::STANDARD.encode(bytes)
1853 }
1854
1855 fn header() -> CmfHeader {
1856 serde_json::from_value(serde_json::json!({
1857 "version": 2,
1858 "quant_type": "F32",
1859 "arch": {
1860 "arch_name": "synthetic", "hidden_size": HID, "intermediate_size": 32,
1861 "num_layers": 8, "num_attention_heads": 2, "num_kv_heads": 1, "head_dim": 8,
1862 "vocab_size": 16, "layer_types": vec!["FullAttention"; 8],
1863 "rms_norm_eps": 1e-6, "max_position_embeddings": 64
1864 }
1865 }))
1866 .unwrap()
1867 }
1868
1869 struct Rng(u64);
1871 impl Rng {
1872 fn next(&mut self) -> f32 {
1873 self.0 ^= self.0 << 13;
1874 self.0 ^= self.0 >> 7;
1875 self.0 ^= self.0 << 17;
1876 ((self.0 >> 40) as f32 / (1u64 << 24) as f32) * 2.0 - 1.0
1877 }
1878 }
1879
1880 fn sample(rng: &mut Rng, axis: usize, sigma: f32) -> Vec<f32> {
1882 let mut v: Vec<f32> = (0..HID).map(|_| sigma * rng.next()).collect();
1883 v[axis] += 1.0;
1884 unit_copy(&v)
1885 }
1886
1887 fn descriptor(
1890 train: &[Vec<f32>],
1891 holdout: &[Vec<f32>],
1892 noise_axis: usize,
1893 ) -> SelectionDescriptor {
1894 let mut mean = vec![0.0f32; HID];
1895 for s in train {
1896 for (m, x) in mean.iter_mut().zip(s) {
1897 *m += x / train.len() as f32;
1898 }
1899 }
1900 let mut basis = vec![0.0f32; HID];
1901 basis[noise_axis] = 1.0;
1902 let q = |v: &[f32]| -> Vec<f32> {
1903 v.iter()
1904 .map(|x| f16_to_f32(cortiq_core::quant::f32_to_f16(*x)))
1905 .collect()
1906 };
1907 let (mq, bq) = (q(&mean), q(&basis));
1908 let errs: Vec<f32> = train.iter().map(|s| recon_error(s, &mq, &bq, 1)).collect();
1909 let em = errs.iter().sum::<f32>() / errs.len() as f32;
1910 let es = (errs.iter().map(|e| (e - em).powi(2)).sum::<f32>() / errs.len() as f32).sqrt();
1911 SelectionDescriptor {
1912 metric: "mse_unit".into(),
1913 phi_layer: 2,
1914 mean: f16_b64(&mean),
1915 basis: f16_b64(&basis),
1916 rank: 1,
1917 err_mean: Some(em),
1918 err_std: Some(es),
1919 holdout: Some(f16_b64(&holdout.concat())),
1920 holdout_n: Some(holdout.len()),
1921 }
1922 }
1923
1924 fn policy(base: SelectionDescriptor) -> RouterPolicy {
1925 RouterPolicy {
1926 version: 2,
1927 policy: "backbone_gated".into(),
1928 granularity: "request".into(),
1929 phi: PhiSpec {
1930 layer: 2,
1931 pool: "span_mean".into(),
1932 norm: "unit".into(),
1933 key_alpha: None,
1934 followup_alpha: None,
1935 followup_margin: None,
1936 prefix_ids: vec![1, 7, 8],
1937 suffix_ids: vec![2, 1, 9],
1938 },
1939 base,
1940 margin: 0.05,
1941 skills_hash: "0".into(),
1942 measured: None,
1943 followup: None,
1944 keyless: None,
1945 }
1946 }
1947
1948 fn skill(id: &str, sel: SelectionDescriptor, status: &str) -> SkillRecord {
1949 SkillRecord {
1950 id: id.into(),
1951 layers: vec![5],
1952 selection: Some(sel),
1953 kind: Some(skill_kind::FFN_REPLACE.into()),
1954 status: Some(status.into()),
1955 gate: Some(serde_json::json!({"status": "measured"})),
1956 ..Default::default()
1957 }
1958 }
1959
1960 fn row(id: &str, e: f32) -> ErrorRow {
1961 (id.into(), e, Some(0.05), Some(0.05), 1.0)
1962 }
1963
1964 fn truth_policy() -> RouterPolicy {
1965 let mut p = policy(SelectionDescriptor {
1966 metric: "mse_unit".into(),
1967 phi_layer: 2,
1968 mean: String::new(),
1969 basis: String::new(),
1970 rank: 0,
1971 err_mean: None,
1972 err_std: None,
1973 holdout: None,
1974 holdout_n: None,
1975 });
1976 p.skills_hash = "00000000000000ab".into();
1977 p
1978 }
1979
1980 #[test]
1981 fn backbone_gated_truth_table() {
1982 let pol = truth_policy();
1983 let cal = RoutingCalibration {
1984 temperature: 0.1,
1985 novelty_theta: 0.99,
1986 samples: 10,
1987 target_fpr: 0.05,
1988 };
1989 let (base, near) = (row(BACKBONE_CLASS_ID, 0.9), row("herbs", 0.05));
1990 let go = |b: Option<&ErrorRow>,
1991 s: &[ErrorRow],
1992 c: Option<&RoutingCalibration>,
1993 p: Option<&RouterPolicy>,
1994 h: u64| { decide_backbone_gated(b, s, c, p, h) };
1995 let std_skills = std::slice::from_ref(&near);
1996
1997 let d = go(Some(&base), std_skills, Some(&cal), Some(&pol), 0xab);
1999 assert_eq!(d.target, RouteTarget::Skill("herbs".into()), "{}", d.reason);
2000
2001 let cases: Vec<(&str, RouteDecision, &str)> = vec![
2002 (
2003 "no policy",
2004 go(Some(&base), std_skills, Some(&cal), None, 0xab),
2005 "no router policy",
2006 ),
2007 (
2008 "hash mismatch",
2009 go(Some(&base), std_skills, Some(&cal), Some(&pol), 0xac),
2010 "skills_hash mismatch",
2011 ),
2012 (
2013 "no calibration",
2014 go(Some(&base), std_skills, None, Some(&pol), 0xab),
2015 "not calibrated",
2016 ),
2017 (
2018 "no base row",
2019 go(None, std_skills, Some(&cal), Some(&pol), 0xab),
2020 "no backbone descriptor",
2021 ),
2022 (
2023 "no skills",
2024 go(Some(&base), &[], Some(&cal), Some(&pol), 0xab),
2025 "no routable skill",
2026 ),
2027 (
2028 "winner is the backbone",
2029 go(
2030 Some(&row(BACKBONE_CLASS_ID, 0.01)),
2031 std_skills,
2032 Some(&cal),
2033 Some(&pol),
2034 0xab,
2035 ),
2036 "backbone is the nearest",
2037 ),
2038 (
2039 "novel",
2040 go(
2041 Some(&base),
2042 std_skills,
2043 Some(&RoutingCalibration {
2044 novelty_theta: 0.0,
2045 ..cal.clone()
2046 }),
2047 Some(&pol),
2048 0xab,
2049 ),
2050 "novel input",
2051 ),
2052 (
2053 "margin not beaten",
2054 go(
2055 Some(&row(BACKBONE_CLASS_ID, 0.32)),
2056 &[row("herbs", 0.30)],
2057 Some(&cal),
2058 Some(&pol),
2059 0xab,
2060 ),
2061 "margin not beaten",
2062 ),
2063 (
2064 "degenerate φ",
2065 go(
2066 Some(&(BACKBONE_CLASS_ID.into(), 0.9, None, None, 0.0)),
2067 std_skills,
2068 Some(&cal),
2069 Some(&pol),
2070 0xab,
2071 ),
2072 "degenerate",
2073 ),
2074 ];
2075 for (name, d, needle) in cases {
2076 assert_eq!(
2077 d.target,
2078 RouteTarget::Backbone,
2079 "case '{name}' routed to a skill"
2080 );
2081 assert!(
2082 d.reason.contains(needle),
2083 "case '{name}': reason '{}'",
2084 d.reason
2085 );
2086 }
2087 }
2088
2089 fn sample_mix(rng: &mut Rng, axes: &[(usize, f32)], sigma: f32) -> Vec<f32> {
2091 let mut v: Vec<f32> = (0..HID).map(|_| sigma * rng.next()).collect();
2092 for &(a, w) in axes {
2093 v[a] += w;
2094 }
2095 unit_copy(&v)
2096 }
2097
2098 #[test]
2105 fn a_prompt_nearest_to_a_quarantined_class_runs_the_backbone() {
2106 let mut rng = Rng(0x5eed_1234_abcd_0001);
2107 let draw = |rng: &mut Rng, axes: &[(usize, f32)], n: usize| -> Vec<Vec<f32>> {
2108 (0..n).map(|_| sample_mix(rng, axes, 0.02)).collect()
2109 };
2110 let (gen_ax, herb_ax, mush_ax) = (vec![(0, 1.0)], vec![(1, 1.0)], vec![(1, 1.0), (4, 0.8)]);
2111 let (gt, gh) = (draw(&mut rng, &gen_ax, 200), draw(&mut rng, &gen_ax, 100));
2112 let (ht, hh) = (draw(&mut rng, &herb_ax, 200), draw(&mut rng, &herb_ax, 100));
2113 let (mt, mh) = (draw(&mut rng, &mush_ax, 200), draw(&mut rng, &mush_ax, 100));
2114 let mut h = header();
2115 h.router = Some(policy(descriptor(>, &gh, 3)));
2116 h.skills.push(skill("herbs", descriptor(&ht, &hh, 4), "active"));
2119 h.skills
2120 .push(skill("mushrooms", descriptor(&mt, &mh, 5), "quarantine"));
2121 let (mut cal, _) = calibrate_v2(&h, 0.05).unwrap();
2122 cal.novelty_theta = 0.999;
2124 h.routing = Some(cal.clone());
2125 let hash = skills_hash(&h);
2126 h.router.as_mut().unwrap().skills_hash = format!("{hash:016x}");
2127
2128 let (mut to_backbone, mut old_to_herbs) = (0usize, 0usize);
2129 let n = 100;
2130 for _ in 0..n {
2131 let q = sample_mix(&mut rng, &mush_ax, 0.02);
2132 let d = route_policy(&h, &q);
2133 assert_eq!(
2134 d.nearest_skill(),
2135 Some("mushrooms"),
2136 "the quarantined class is scored: {}",
2137 d.reason
2138 );
2139 if d.target == RouteTarget::Backbone {
2140 to_backbone += 1;
2141 assert!(d.reason.contains("not routable"), "{}", d.reason);
2142 }
2143 let (base, rows) = policy_rows(&h, &q);
2145 let herbs_only: Vec<ErrorRow> = rows.into_iter().filter(|r| r.0 == "herbs").collect();
2146 let old = decide_backbone_gated(base.as_ref(), &herbs_only, Some(&cal), h.router.as_ref(), hash);
2147 old_to_herbs += usize::from(old.skill() == Some("herbs"));
2148 let dq = route_policy_with(&h, &q, RouteOptions { include_quarantine: true });
2150 assert_eq!(dq.skill(), Some("mushrooms"), "{}", dq.reason);
2151 }
2152 assert_eq!(to_backbone, n, "a mushroom prompt ran a skill");
2153 assert!(
2154 old_to_herbs * 10 >= n * 9,
2155 "the pre-fix decision did not misroute ({old_to_herbs}/{n}) — the regression is untested"
2156 );
2157 for _ in 0..50 {
2159 let d = route_policy(&h, &sample_mix(&mut rng, &herb_ax, 0.02));
2160 assert_eq!(d.skill(), Some("herbs"), "{}", d.reason);
2161 }
2162 h.skills[1].status = Some("stale_regate".into());
2164 let q = sample_mix(&mut rng, &mush_ax, 0.02);
2165 assert_eq!(route_policy(&h, &q).target, RouteTarget::Backbone);
2166 assert_eq!(
2167 route_policy_with(&h, &q, RouteOptions { include_quarantine: true }).skill(),
2168 Some("mushrooms")
2169 );
2170 assert!(no_candidate_reason(&h, RouteOptions::default()).is_none());
2171 h.skills[0].status = Some("quarantine".into());
2172 assert!(
2173 no_candidate_reason(&h, RouteOptions::default())
2174 .is_some_and(|r| r.contains("no routable skill class"))
2175 );
2176 assert!(no_candidate_reason(&h, RouteOptions { include_quarantine: true }).is_none());
2177 }
2178
2179 #[test]
2180 fn prompt_contract_is_enforced_in_one_place() {
2181 let mut h = header();
2182 h.skills.push(SkillRecord {
2183 prompt_contract: Some(PROMPT_CONTRACT_CMF_IM_V1.into()),
2184 ..skill("herbs", truth_policy().base, "active")
2185 });
2186 h.skills.push(SkillRecord {
2187 prompt_contract: Some("other-v9".into()),
2188 ..skill("odd", truth_policy().base, "active")
2189 });
2190 h.skills.push(skill("free", truth_policy().base, "active"));
2191 let pick = |id: &str| RouteDecision::forced(RouteTarget::Skill(id.into()), "t");
2192 let (d, r) = enforce_prompt_contract(&h, pick("herbs"), PromptFrame::CmfImV1, false);
2194 assert_eq!((d.skill(), r), (Some("herbs"), false));
2195 let (d, r) = enforce_prompt_contract(&h, pick("herbs"), PromptFrame::Raw, true);
2196 assert_eq!((d.skill(), r), (Some("herbs"), true));
2197 for (id, frame, can) in [
2199 ("herbs", PromptFrame::Raw, false),
2200 ("herbs", PromptFrame::Other, true),
2201 ("odd", PromptFrame::CmfImV1, true),
2202 ] {
2203 let (d, r) = enforce_prompt_contract(&h, pick(id), frame, can);
2204 assert_eq!(d.target, RouteTarget::Backbone, "{id} {frame:?}");
2205 assert!(!r && d.reason.contains("not satisfied"), "{}", d.reason);
2206 }
2207 let (d, r) = enforce_prompt_contract(&h, pick("free"), PromptFrame::Raw, false);
2209 assert_eq!((d.skill(), r), (Some("free"), false));
2210 let bb = RouteDecision::forced(RouteTarget::Backbone, "t");
2211 assert_eq!(enforce_prompt_contract(&h, bb, PromptFrame::Other, false).0.skill(), None);
2212 let p = render_cmf_im_v1("Что лечит зверобой?");
2214 assert_eq!(p, "<|im_start|>user\nЧто лечит зверобой?<|im_end|>\n<|im_start|>assistant\n");
2215 assert_eq!(cmf_im_v1_single_user_turn(&p), Some("Что лечит зверобой?"));
2216 assert_eq!(cmf_im_v1_single_user_turn("Что лечит зверобой?"), None);
2217 let two = format!("{}{}", render_cmf_im_v1("a"), "b<|im_end|>\n<|im_start|>user\nc<|im_end|>\n<|im_start|>assistant\n");
2218 assert_eq!(cmf_im_v1_single_user_turn(&two), None, "history is not one turn");
2219 assert_eq!(
2220 cmf_im_v1_single_user_turn(
2221 "<|im_start|>system\ns<|im_end|>\n<|im_start|>user\nq<|im_end|>\n<|im_start|>assistant\n"
2222 ),
2223 None
2224 );
2225 }
2226
2227 #[test]
2228 fn skills_hash_tracks_descriptors_not_status() {
2229 let mut rng = Rng(7);
2230 let tr: Vec<Vec<f32>> = (0..20).map(|_| sample(&mut rng, 1, 0.1)).collect();
2231 let mut h = header();
2232 h.router = Some(policy(descriptor(&tr, &[], 3)));
2233 h.skills
2234 .push(skill("herbs", descriptor(&tr, &[], 4), "quarantine"));
2235 let h0 = skills_hash(&h);
2236 h.skills[0].status = Some("active".into());
2237 h.skills[0].gate = Some(serde_json::json!({"status": "measured", "x": 1}));
2238 h.skills[0].selection.as_mut().unwrap().holdout = Some("AAAA".into());
2239 assert_eq!(
2240 skills_hash(&h),
2241 h0,
2242 "status/gate/holdout do not invalidate the calibration"
2243 );
2244 h.skills.push(SkillRecord {
2246 id: "legacy".into(),
2247 selection: Some(descriptor(&tr, &[], 5)),
2248 ..Default::default()
2249 });
2250 assert_eq!(skills_hash(&h), h0);
2251 let mut h2 = h.clone();
2253 h2.skills[0].selection.as_mut().unwrap().err_mean = Some(0.123);
2254 assert_ne!(skills_hash(&h2), h0);
2255 let mut h3 = h.clone();
2256 h3.skills
2257 .push(skill("more", descriptor(&tr, &[], 6), "quarantine"));
2258 assert_ne!(skills_hash(&h3), h0);
2259 let mut h4 = h.clone();
2260 h4.skills[0].status = Some("retired".into());
2261 assert_ne!(skills_hash(&h4), h0);
2262 let mut h5 = h.clone();
2263 h5.router.as_mut().unwrap().base.rank = 0;
2264 assert_ne!(skills_hash(&h5), h0);
2265 }
2266
2267 #[test]
2268 fn phi_span_is_the_user_text_only() {
2269 let p = policy(descriptor(&[vec![1.0; HID]], &[], 0)).phi;
2270 let (ids, span) = phi_span_ids(&p, &[40, 41, 42, 43]);
2271 assert_eq!(ids, vec![1, 7, 8, 40, 41, 42, 43, 2, 1, 9]);
2272 assert_eq!(span, 3..7);
2273 assert_eq!(&ids[span.clone()], &[40, 41, 42, 43]);
2274 let hiddens: Vec<f32> = (0..ids.len())
2276 .flat_map(|r| {
2277 (0..HID).map(move |c| {
2278 if (3..7).contains(&r) {
2279 (c == 0) as u8 as f32
2280 } else {
2281 100.0
2282 }
2283 })
2284 })
2285 .collect();
2286 let phi = pool_span_unit(&hiddens, HID, span);
2287 assert!((phi[0] - 1.0).abs() < 1e-6 && phi[1..].iter().all(|x| *x == 0.0));
2288 assert!(
2289 pool_span_unit(&hiddens, HID, 5..5)
2290 .iter()
2291 .all(|x| *x == 0.0)
2292 );
2293 }
2294
2295 #[test]
2296 fn clopper_pearson_matches_reference_values() {
2297 assert!((clopper_pearson_upper(0, 10, 0.95) - 0.308_5).abs() < 1e-3);
2299 assert!((clopper_pearson_upper(5, 10, 0.95) - 0.812_9).abs() < 1e-3);
2300 assert!((clopper_pearson_upper(1, 100, 0.95) - 0.054_5).abs() < 1e-3);
2301 let u = clopper_pearson_upper(0, 500, 0.95);
2302 assert!((u - 0.007_351).abs() < 1e-5, "{u}");
2303 assert_eq!(clopper_pearson_upper(3, 3, 0.95), 1.0);
2304 }
2305
2306 #[test]
2310 fn calibrate_v2_separates_backbone_and_skill() {
2311 let mut rng = Rng(0x9e37_79b9_7f4a_7c15);
2312 let draw = |rng: &mut Rng, axis: usize, n: usize| -> Vec<Vec<f32>> {
2313 (0..n).map(|_| sample(rng, axis, 0.1)).collect()
2314 };
2315 let (gt, gh) = (draw(&mut rng, 0, 200), draw(&mut rng, 0, 120));
2316 let (st, sh) = (draw(&mut rng, 1, 200), draw(&mut rng, 1, 120));
2317 let mut h = header();
2318 h.router = Some(policy(descriptor(>, &gh, 3)));
2319 h.skills
2320 .push(skill("herbs", descriptor(&st, &sh, 4), "active"));
2321
2322 let (cal, measured) = calibrate_v2(&h, 0.01).unwrap();
2323 assert_eq!(measured["false_accept"], 0.0, "{measured}");
2324 assert!(
2325 measured["in_scope_recall"].as_f64().unwrap() >= 0.98,
2326 "{measured}"
2327 );
2328 assert!(
2329 measured["false_accept_upper95"].as_f64().unwrap() < 0.031,
2330 "{measured}"
2331 );
2332 assert_eq!(measured["n_general"], 120);
2333 assert_eq!(measured["n_in"], 120);
2334
2335 h.routing = Some(cal);
2337 let hash = skills_hash(&h);
2338 h.router.as_mut().unwrap().skills_hash = format!("{hash:016x}");
2339 let (mut fa, mut hit) = (0, 0);
2340 for _ in 0..200 {
2341 fa += usize::from(
2342 route_policy(&h, &sample(&mut rng, 0, 0.1))
2343 .skill()
2344 .is_some(),
2345 );
2346 hit +=
2347 usize::from(route_policy(&h, &sample(&mut rng, 1, 0.1)).skill() == Some("herbs"));
2348 }
2349 assert_eq!(fa, 0);
2350 assert!(hit >= 190, "recall {hit}/200");
2351 let d = route_policy(&h, &sample(&mut rng, 9, 0.1));
2353 assert_eq!(d.target, RouteTarget::Backbone, "{}", d.reason);
2354
2355 let mut q = h.clone();
2357 q.skills[0].status = Some("quarantine".into());
2358 assert_eq!(
2359 route_policy(&q, &sample(&mut rng, 1, 0.1)).target,
2360 RouteTarget::Backbone
2361 );
2362 let mut stale = h.clone();
2363 stale
2364 .skills
2365 .push(skill("new", descriptor(&st, &[], 5), "active"));
2366 let d = route_policy(&stale, &sample(&mut rng, 1, 0.1));
2367 assert_eq!(d.target, RouteTarget::Backbone);
2368 assert!(d.reason.contains("stale"), "{}", d.reason);
2369 }
2370}