use crate::calibrate::gate_score;
use crate::gate::{Gate, GateHealthRegistry, aggregate};
use crate::provider::{Auth, ModelRequest, ModelResponse, ProviderError, ProviderRegistry};
use firstpass_core::verdict::reason;
use firstpass_core::{
Attempt, Features, FinalOutcome, GENESIS_HASH, GateResult, Mode, ModelRef, PolicyRef,
PriceTable, RequestInfo, ServedFrom, Trace, Verdict,
};
use jiff::Timestamp;
use std::collections::HashMap;
use std::time::Instant;
use tokio::task::JoinHandle;
use uuid::Uuid;
#[derive(Debug)]
pub enum EngineOutcome {
Served(ModelResponse),
Failed(String),
}
#[derive(Debug)]
pub struct EnforceCtx<'a> {
pub ladder: &'a [String],
pub gates: &'a [Box<dyn Gate>],
pub health: &'a GateHealthRegistry,
pub base_request: &'a ModelRequest,
pub providers: &'a ProviderRegistry,
pub auth: &'a Auth,
pub prices: &'a PriceTable,
pub budget_per_request_usd: Option<f64>,
pub max_rungs: u32,
pub speculation: u32,
pub serve_threshold: Option<f64>,
pub features: Features,
pub tenant_id: String,
pub session_id: String,
pub prompt_hash: String,
pub api: String,
pub policy_id: String,
}
pub async fn route_enforce(ctx: EnforceCtx<'_>) -> (EngineOutcome, Trace) {
let LadderRun {
attempts,
spent,
gate_cost_total,
best,
mut served_rung,
hard_error,
} = if ctx.speculation == 0 {
run_serial(&ctx).await
} else {
run_speculative(&ctx).await
};
let (outcome, served_from, served_tokens) = match (served_rung, &best) {
(Some(_), Some((_, resp))) => (
EngineOutcome::Served(resp.clone()),
ServedFrom::Attempt,
(resp.in_tokens, resp.out_tokens),
),
(None, Some((idx, resp))) => {
served_rung = Some(*idx);
(
EngineOutcome::Served(resp.clone()),
ServedFrom::BestAttempt,
(resp.in_tokens, resp.out_tokens),
)
}
(_, None) => {
let msg = hard_error.unwrap_or_else(|| "all rungs failed".to_owned());
(EngineOutcome::Failed(msg), ServedFrom::Error, (0, 0))
}
};
let top_model = ctx.ladder.last().map(String::as_str).unwrap_or_default();
let baseline = ctx
.prices
.cost_usd(top_model, served_tokens.0, served_tokens.1)
.unwrap_or(spent);
let total_latency_ms = attempts.iter().map(|a| a.latency_ms).sum();
let escalations = attempts.len().saturating_sub(1) as u32;
let mut trace = Trace {
trace_id: Uuid::now_v7(),
prev_hash: GENESIS_HASH.to_owned(),
tenant_id: ctx.tenant_id,
session_id: ctx.session_id,
ts: Timestamp::now(),
mode: Mode::Enforce,
policy: PolicyRef {
id: ctx.policy_id,
explore: false,
},
request: RequestInfo {
api: ctx.api,
prompt_hash: ctx.prompt_hash,
features: ctx.features,
},
attempts,
deferred: vec![],
final_: FinalOutcome {
served_rung,
served_from,
total_cost_usd: spent,
gate_cost_usd: gate_cost_total,
total_latency_ms,
escalations,
counterfactual_baseline_usd: baseline,
savings_usd: 0.0,
},
};
trace.recompute_savings();
(outcome, trace)
}
struct LadderRun {
attempts: Vec<Attempt>,
spent: f64,
gate_cost_total: f64,
best: Option<(u32, ModelResponse)>,
served_rung: Option<u32>,
hard_error: Option<String>,
}
fn should_serve(
serve_threshold: Option<f64>,
gate_results: &[GateResult],
verdict: Verdict,
) -> bool {
match serve_threshold {
None => verdict == Verdict::Pass,
Some(t) => gate_score(gate_results, verdict) >= t,
}
}
async fn run_serial(ctx: &EnforceCtx<'_>) -> LadderRun {
let mut attempts: Vec<Attempt> = Vec::new();
let mut spent = 0.0_f64;
let mut gate_cost_total = 0.0_f64;
let mut best: Option<(u32, ModelResponse)> = None;
let mut served_rung: Option<u32> = None;
let mut hard_error: Option<String> = None;
let rung_limit = (ctx.max_rungs as usize).min(ctx.ladder.len());
for (idx, model_str) in ctx.ladder.iter().take(rung_limit).enumerate() {
let idx = idx as u32;
let start = Instant::now();
let provider = match ModelRef::parse(model_str) {
Ok(m) => ctx.providers.get(&m.provider),
Err(_) => None,
};
let Some(provider) = provider else {
let ms = elapsed_ms(start);
attempts.push(abstain_attempt(
idx,
model_str,
"unknown",
reason::PROVIDER_ERROR,
ms,
));
continue;
};
let mut req = ctx.base_request.clone();
req.model = model_str.clone();
match provider.complete(&req, ctx.auth).await {
Err(err) if err.is_failover_eligible() => {
let ms = elapsed_ms(start);
attempts.push(abstain_attempt(
idx,
model_str,
provider.id(),
reason::PROVIDER_ERROR,
ms,
));
continue;
}
Err(err) => {
let ms = elapsed_ms(start);
let (r, msg) = hard_reason(&err);
attempts.push(abstain_attempt(idx, model_str, provider.id(), r, ms));
hard_error = Some(msg);
break;
}
Ok(resp) => {
let ms = elapsed_ms(start);
let model_cost = ctx
.prices
.cost_usd(model_str, resp.in_tokens, resp.out_tokens)
.unwrap_or(0.0);
spent += model_cost;
let mut gate_results: Vec<GateResult> = Vec::with_capacity(ctx.gates.len());
for g in ctx.gates {
if !ctx.health.enabled(g.id()) {
tracing::warn!(gate = %g.id(), "skipping auto-disabled gate");
continue;
}
let r = g.evaluate(&req, &resp).await;
ctx.health.record(g.id(), r.verdict == Verdict::Abstain);
gate_results.push(r);
}
let gc: f64 = gate_results.iter().map(|g| g.cost_usd).sum();
gate_cost_total += gc;
spent += gc;
let verdict = aggregate(&gate_results);
let serve = should_serve(ctx.serve_threshold, &gate_results, verdict);
attempts.push(Attempt {
rung: idx,
model: model_str.clone(),
provider: provider.id().to_owned(),
in_tokens: resp.in_tokens,
out_tokens: resp.out_tokens,
cost_usd: model_cost,
latency_ms: ms,
gates: gate_results,
verdict,
});
best = Some((idx, resp));
if serve {
served_rung = Some(idx);
break;
}
if let Some(cap) = ctx.budget_per_request_usd
&& spent >= cap
&& (idx as usize) + 1 < rung_limit
{
break;
}
}
}
}
LadderRun {
attempts,
spent,
gate_cost_total,
best,
served_rung,
hard_error,
}
}
async fn run_speculative(ctx: &EnforceCtx<'_>) -> LadderRun {
let mut attempts: Vec<Attempt> = Vec::new();
let mut spent = 0.0_f64;
let mut gate_cost_total = 0.0_f64;
let mut best: Option<(u32, ModelResponse)> = None;
let mut served_rung: Option<u32> = None;
let mut hard_error: Option<String> = None;
let rung_limit = (ctx.max_rungs as usize).min(ctx.ladder.len());
let speculation = ctx.speculation as usize;
let mut inflight: HashMap<usize, JoinHandle<Result<ModelResponse, ProviderError>>> =
HashMap::new();
let mut idx = 0usize;
let mut done = false;
while idx < rung_limit && !done {
let window_end = (idx + speculation).min(rung_limit - 1);
for j in idx..=window_end {
if inflight.contains_key(&j) {
continue;
}
if j > idx
&& let Some(cap) = ctx.budget_per_request_usd
&& spent >= cap
{
continue;
}
if let Some(handle) = spawn_rung(ctx, j) {
inflight.insert(j, handle);
}
}
let model_str = &ctx.ladder[idx];
let provider = match ModelRef::parse(model_str) {
Ok(m) => ctx.providers.get(&m.provider),
Err(_) => None,
};
let Some(provider) = provider else {
attempts.push(abstain_attempt(
idx as u32,
model_str,
"unknown",
reason::PROVIDER_ERROR,
0,
));
idx += 1;
continue;
};
let Some(handle) = inflight.remove(&idx) else {
attempts.push(abstain_attempt(
idx as u32,
model_str,
provider.id(),
reason::PROVIDER_ERROR,
0,
));
idx += 1;
continue;
};
let start = Instant::now();
let joined = handle.await;
let ms = elapsed_ms(start);
match joined {
Err(_) => {
attempts.push(abstain_attempt(
idx as u32,
model_str,
provider.id(),
reason::PROVIDER_ERROR,
ms,
));
idx += 1;
}
Ok(Err(err)) if err.is_failover_eligible() => {
attempts.push(abstain_attempt(
idx as u32,
model_str,
provider.id(),
reason::PROVIDER_ERROR,
ms,
));
idx += 1;
}
Ok(Err(err)) => {
let (r, msg) = hard_reason(&err);
attempts.push(abstain_attempt(idx as u32, model_str, provider.id(), r, ms));
hard_error = Some(msg);
done = true;
}
Ok(Ok(resp)) => {
let model_cost = ctx
.prices
.cost_usd(model_str, resp.in_tokens, resp.out_tokens)
.unwrap_or(0.0);
spent += model_cost;
let mut req = ctx.base_request.clone();
req.model = model_str.clone();
let mut gate_results: Vec<GateResult> = Vec::with_capacity(ctx.gates.len());
for g in ctx.gates {
if !ctx.health.enabled(g.id()) {
tracing::warn!(gate = %g.id(), "skipping auto-disabled gate");
continue;
}
let r = g.evaluate(&req, &resp).await;
ctx.health.record(g.id(), r.verdict == Verdict::Abstain);
gate_results.push(r);
}
let gc: f64 = gate_results.iter().map(|g| g.cost_usd).sum();
gate_cost_total += gc;
spent += gc;
let verdict = aggregate(&gate_results);
let serve = should_serve(ctx.serve_threshold, &gate_results, verdict);
attempts.push(Attempt {
rung: idx as u32,
model: model_str.clone(),
provider: provider.id().to_owned(),
in_tokens: resp.in_tokens,
out_tokens: resp.out_tokens,
cost_usd: model_cost,
latency_ms: ms,
gates: gate_results,
verdict,
});
best = Some((idx as u32, resp));
if serve {
served_rung = Some(idx as u32);
done = true;
} else if let Some(cap) = ctx.budget_per_request_usd
&& spent >= cap
&& idx + 1 < rung_limit
{
done = true;
}
idx += 1;
}
}
}
for (j, handle) in inflight.drain() {
if handle.is_finished() {
if let Ok(Ok(resp)) = handle.await {
spent += ctx
.prices
.cost_usd(&ctx.ladder[j], resp.in_tokens, resp.out_tokens)
.unwrap_or(0.0);
}
} else {
handle.abort();
}
}
LadderRun {
attempts,
spent,
gate_cost_total,
best,
served_rung,
hard_error,
}
}
fn spawn_rung(
ctx: &EnforceCtx<'_>,
j: usize,
) -> Option<JoinHandle<Result<ModelResponse, ProviderError>>> {
let model_str = ctx.ladder.get(j)?;
let provider = match ModelRef::parse(model_str) {
Ok(m) => ctx.providers.get(&m.provider)?,
Err(_) => return None,
};
let mut req = ctx.base_request.clone();
req.model = model_str.clone();
let auth = ctx.auth.clone();
Some(tokio::spawn(
async move { provider.complete(&req, &auth).await },
))
}
fn elapsed_ms(start: Instant) -> u64 {
u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX)
}
fn abstain_attempt(rung: u32, model: &str, provider: &str, reason: &str, ms: u64) -> Attempt {
Attempt {
rung,
model: model.to_owned(),
provider: provider.to_owned(),
in_tokens: 0,
out_tokens: 0,
cost_usd: 0.0,
latency_ms: ms,
gates: vec![GateResult::abstain(provider, reason, ms)],
verdict: Verdict::Abstain,
}
}
fn hard_reason(err: &ProviderError) -> (&'static str, String) {
match err {
ProviderError::Http { status, .. } => {
(reason::PROVIDER_ERROR, format!("upstream http {status}"))
}
ProviderError::Decode(_) => (reason::PROVIDER_ERROR, "upstream decode error".to_owned()),
ProviderError::Transport(_) => (
reason::PROVIDER_ERROR,
"upstream transport error".to_owned(),
),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gate::{JsonValidGate, NonEmptyGate};
use crate::provider::{MockProvider, Provider};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
const HAIKU: &str = "anthropic/claude-haiku-4-5";
const SONNET: &str = "anthropic/claude-sonnet-5";
const OPUS: &str = "anthropic/claude-opus-4-8";
const GPT: &str = "openai/gpt-5.5";
fn resp(model: &str, text: &str) -> ModelResponse {
ModelResponse {
model: model.to_owned(),
text: text.to_owned(),
in_tokens: 1000,
out_tokens: 500,
raw: Value::Null,
}
}
fn base_request() -> ModelRequest {
ModelRequest {
model: String::new(),
system: None,
messages: vec![crate::provider::ChatMessage {
role: "user".into(),
content: "hi".into(),
}],
max_tokens: 256,
tools: Value::Null,
}
}
fn registry(
outcomes: Vec<(&str, &str, Result<ModelResponse, ProviderError>)>,
) -> ProviderRegistry {
let mut by_provider: HashMap<
String,
HashMap<String, Result<ModelResponse, ProviderError>>,
> = HashMap::new();
for (provider, model, out) in outcomes {
by_provider
.entry(provider.to_owned())
.or_default()
.insert(model.to_owned(), out);
}
let mut map: HashMap<String, Arc<dyn Provider>> = HashMap::new();
for (pid, outs) in by_provider {
map.insert(pid.clone(), Arc::new(MockProvider::new(pid, outs)));
}
ProviderRegistry::from_map(map)
}
#[allow(clippy::too_many_arguments)]
fn ctx<'a>(
ladder: &'a [String],
gates: &'a [Box<dyn Gate>],
req: &'a ModelRequest,
providers: &'a ProviderRegistry,
auth: &'a Auth,
prices: &'a PriceTable,
budget: Option<f64>,
health: &'a GateHealthRegistry,
) -> EnforceCtx<'a> {
EnforceCtx {
ladder,
gates,
health,
base_request: req,
providers,
auth,
prices,
budget_per_request_usd: budget,
max_rungs: 3,
speculation: 0,
serve_threshold: None,
features: Features::new(firstpass_core::TaskKind::CodeEdit),
tenant_id: "acme".into(),
session_id: "sess-1".into(),
prompt_hash: "deadbeef".into(),
api: "anthropic.messages".into(),
policy_id: "static-ladder@v0".into(),
}
}
#[tokio::test]
async fn serve_first_pass_no_escalation_with_savings() {
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned(), OPUS.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let providers = registry(vec![("anthropic", HAIKU, Ok(resp(HAIKU, r#"{"ok":1}"#)))]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let (out, trace) = route_enforce(ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
))
.await;
match out {
EngineOutcome::Served(r) => assert_eq!(r.model, HAIKU),
EngineOutcome::Failed(e) => panic!("expected served, got {e}"),
}
assert_eq!(trace.attempts.len(), 1);
assert_eq!(trace.final_.escalations, 0);
assert_eq!(trace.final_.served_from, ServedFrom::Attempt);
assert_eq!(trace.final_.served_rung, Some(0));
assert!(
trace.final_.savings_usd > 0.0,
"top-rung baseline should exceed haiku cost"
);
}
#[tokio::test]
async fn escalate_on_gate_fail() {
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let providers = registry(vec![
("anthropic", HAIKU, Ok(resp(HAIKU, " "))),
("anthropic", SONNET, Ok(resp(SONNET, "real answer"))),
]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let (out, trace) = route_enforce(ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
))
.await;
assert!(matches!(out, EngineOutcome::Served(r) if r.model == SONNET));
assert_eq!(trace.attempts.len(), 2);
assert_eq!(trace.attempts[0].verdict, Verdict::Fail);
assert_eq!(trace.attempts[1].verdict, Verdict::Pass);
assert_eq!(trace.final_.escalations, 1);
assert_eq!(trace.final_.served_rung, Some(1));
}
#[tokio::test]
async fn cross_provider_failover_on_transport_error() {
let ladder = vec![HAIKU.to_owned(), GPT.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let providers = registry(vec![
(
"anthropic",
HAIKU,
Err(ProviderError::Transport("connection refused".into())),
),
("openai", GPT, Ok(resp(GPT, "answer from openai"))),
]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let (out, trace) = route_enforce(ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
))
.await;
assert!(matches!(out, EngineOutcome::Served(r) if r.model == GPT));
assert_eq!(trace.attempts[0].verdict, Verdict::Abstain);
assert_eq!(
trace.attempts[0].gates[0].reason.as_deref(),
Some(reason::PROVIDER_ERROR)
);
assert_eq!(trace.attempts[1].verdict, Verdict::Pass);
assert_eq!(trace.final_.served_rung, Some(1));
}
#[tokio::test]
async fn budget_cap_stops_escalation() {
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned(), OPUS.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let providers = registry(vec![
("anthropic", HAIKU, Ok(resp(HAIKU, ""))),
("anthropic", SONNET, Ok(resp(SONNET, ""))),
("anthropic", OPUS, Ok(resp(OPUS, ""))),
]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let (_out, trace) = route_enforce(ctx(
&ladder,
&gates,
&req,
&providers,
&auth,
&prices,
Some(0.0),
&health,
))
.await;
assert!(
trace.attempts.len() < 3,
"budget should cut escalation short, got {}",
trace.attempts.len()
);
}
#[tokio::test]
async fn all_fail_serves_best_attempt() {
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(JsonValidGate)]; let req = base_request();
let providers = registry(vec![
("anthropic", HAIKU, Ok(resp(HAIKU, "not json"))),
("anthropic", SONNET, Ok(resp(SONNET, "still not json"))),
]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let (out, trace) = route_enforce(ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
))
.await;
assert!(
matches!(out, EngineOutcome::Served(r) if r.model == SONNET),
"serves highest attempt"
);
assert_eq!(trace.final_.served_from, ServedFrom::BestAttempt);
assert_eq!(trace.final_.served_rung, Some(1));
}
#[tokio::test]
async fn hard_4xx_does_not_escalate() {
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let providers = registry(vec![
(
"anthropic",
HAIKU,
Err(ProviderError::Http {
status: 400,
body: "bad request".into(),
}),
),
("anthropic", SONNET, Ok(resp(SONNET, "would have worked"))),
]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let (out, trace) = route_enforce(ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
))
.await;
assert!(
matches!(out, EngineOutcome::Failed(_)),
"4xx is a hard error, not failover"
);
assert_eq!(
trace.attempts.len(),
1,
"must not escalate past a client error"
);
assert_eq!(trace.final_.served_from, ServedFrom::Error);
}
#[tokio::test]
async fn counterfactual_and_savings_math() {
let ladder = vec![HAIKU.to_owned(), OPUS.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let served = resp(HAIKU, "answer");
let (in_t, out_t) = (served.in_tokens, served.out_tokens);
let providers = registry(vec![("anthropic", HAIKU, Ok(served))]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let (_out, trace) = route_enforce(ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
))
.await;
let expected_baseline = prices.cost_usd(OPUS, in_t, out_t).unwrap();
assert!((trace.final_.counterfactual_baseline_usd - expected_baseline).abs() < 1e-12);
let expected_savings = expected_baseline - trace.final_.total_cost_usd;
assert!((trace.final_.savings_usd - expected_savings).abs() < 1e-12);
assert!(trace.final_.savings_usd > 0.0);
}
#[tokio::test]
async fn produced_trace_is_chain_verifiable() {
let ladder = vec![HAIKU.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let providers = registry(vec![("anthropic", HAIKU, Ok(resp(HAIKU, "ok")))]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let (_out, trace) = route_enforce(ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
))
.await;
assert!(firstpass_core::verify_chain(std::slice::from_ref(&trace), GENESIS_HASH).is_ok());
let json = serde_json::to_string(&trace).unwrap();
let _back: Trace = serde_json::from_str(&json).unwrap();
}
#[tokio::test]
async fn auto_disabled_gate_is_skipped_by_the_engine() {
let ladder = vec![HAIKU.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let providers = registry(vec![("anthropic", HAIKU, Ok(resp(HAIKU, "")))]); let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new().with_budget("non-empty", 4, 0.5);
for _ in 0..4 {
health.record("non-empty", true);
}
assert!(
!health.enabled("non-empty"),
"precondition: gate is auto-disabled"
);
let (out, trace) = route_enforce(ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
))
.await;
assert!(matches!(out, EngineOutcome::Served(_)));
assert_eq!(trace.final_.served_rung, Some(0));
assert!(
trace.attempts[0].gates.is_empty(),
"disabled gate must be skipped, not run"
);
}
fn counted_registry(
outcomes: Vec<(&str, Result<ModelResponse, ProviderError>)>,
) -> (
ProviderRegistry,
std::sync::Arc<std::sync::Mutex<Vec<String>>>,
) {
let mut outs = HashMap::new();
for (model, out) in outcomes {
outs.insert(model.to_owned(), out);
}
let mock = MockProvider::new("anthropic", outs);
let log = mock.call_log();
let mut map: HashMap<String, Arc<dyn Provider>> = HashMap::new();
map.insert("anthropic".to_owned(), Arc::new(mock));
(ProviderRegistry::from_map(map), log)
}
#[tokio::test]
async fn speculation_prefetches_next_rung_but_serves_identically() {
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let (providers, log) = counted_registry(vec![
(HAIKU, Ok(resp(HAIKU, "answer"))),
(SONNET, Ok(resp(SONNET, "other"))),
]);
let health = GateHealthRegistry::new();
let (serial_out, serial_trace) = route_enforce(ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
))
.await;
assert_eq!(
*log.lock().unwrap(),
vec![HAIKU.to_owned()],
"serial must not touch rung 1 when rung 0 passes"
);
let (providers, log) = counted_registry(vec![
(HAIKU, Ok(resp(HAIKU, "answer"))),
(SONNET, Ok(resp(SONNET, "other"))),
]);
let health = GateHealthRegistry::new();
let mut c = ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
);
c.speculation = 1;
let (spec_out, spec_trace) = route_enforce(c).await;
assert!(
log.lock().unwrap().contains(&SONNET.to_owned()),
"speculation must fire rung 1 ahead: {:?}",
*log.lock().unwrap()
);
let (a, b) = match (serial_out, spec_out) {
(EngineOutcome::Served(a), EngineOutcome::Served(b)) => (a, b),
_ => panic!("both variants must serve"),
};
assert_eq!(
(a.model, a.text, a.out_tokens),
(b.model, b.text, b.out_tokens)
);
assert_eq!(spec_trace.final_.served_rung, Some(0));
assert_eq!(spec_trace.attempts.len(), 1, "only rung 0 is gated");
assert!(
spec_trace.final_.total_cost_usd > serial_trace.final_.total_cost_usd,
"speculative waste must show in total cost: spec={} serial={}",
spec_trace.final_.total_cost_usd,
serial_trace.final_.total_cost_usd
);
}
#[tokio::test]
async fn speculation_preserves_escalation_result() {
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let (providers, _log) = counted_registry(vec![
(HAIKU, Ok(resp(HAIKU, ""))),
(SONNET, Ok(resp(SONNET, "real answer"))),
]);
let health = GateHealthRegistry::new();
let mut c = ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
);
c.speculation = 2; let (out, trace) = route_enforce(c).await;
match out {
EngineOutcome::Served(r) => assert_eq!(r.model, SONNET),
EngineOutcome::Failed(e) => panic!("expected served, got {e}"),
}
assert_eq!(trace.final_.served_rung, Some(1));
assert_eq!(trace.attempts.len(), 2);
assert_eq!(trace.final_.escalations, 1);
assert_eq!(trace.attempts[0].verdict, Verdict::Fail);
assert_eq!(trace.attempts[1].verdict, Verdict::Pass);
}
#[tokio::test]
async fn latency_ab_speculative_beats_serial_p95() {
const DELAY: u64 = 40;
const N: usize = 30;
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let (auth, prices) = (Auth::default(), PriceTable::defaults());
fn pctl(sorted: &[u64], p: f64) -> u64 {
let i = (((sorted.len() - 1) as f64) * p).round() as usize;
sorted[i]
}
let mut serial = Vec::with_capacity(N);
let mut spec = Vec::with_capacity(N);
for run in 0..(2 * N) {
let speculation = u32::from(run >= N); let mut outs: HashMap<String, Result<ModelResponse, ProviderError>> = HashMap::new();
outs.insert(HAIKU.to_owned(), Ok(resp(HAIKU, ""))); outs.insert(SONNET.to_owned(), Ok(resp(SONNET, "ok")));
let mut map: HashMap<String, Arc<dyn Provider>> = HashMap::new();
map.insert(
"anthropic".to_owned(),
Arc::new(MockProvider::new("anthropic", outs).with_delay(DELAY)),
);
let providers = ProviderRegistry::from_map(map);
let health = GateHealthRegistry::new();
let mut c = ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
);
c.speculation = speculation;
let start = std::time::Instant::now();
let _ = route_enforce(c).await;
let ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX);
if speculation == 0 {
serial.push(ms);
} else {
spec.push(ms);
}
}
serial.sort_unstable();
spec.sort_unstable();
println!(
"latency A/B (per-rung {DELAY}ms, escalate every request):\n serial p50={} p95={} p99={}\n spec(k=1) p50={} p95={} p99={}",
pctl(&serial, 0.5),
pctl(&serial, 0.95),
pctl(&serial, 0.99),
pctl(&spec, 0.5),
pctl(&spec, 0.95),
pctl(&spec, 0.99),
);
assert!(
pctl(&spec, 0.95) * 4 < pctl(&serial, 0.95) * 3,
"spec p95 {}ms should beat serial p95 {}ms by >25%",
pctl(&spec, 0.95),
pctl(&serial, 0.95)
);
}
#[tokio::test]
async fn speculation_never_fires_past_max_rungs() {
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned(), OPUS.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let (providers, log) = counted_registry(vec![
(HAIKU, Ok(resp(HAIKU, ""))),
(SONNET, Ok(resp(SONNET, ""))),
(OPUS, Ok(resp(OPUS, ""))),
]);
let health = GateHealthRegistry::new();
let mut c = ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
);
c.max_rungs = 2;
c.speculation = 5; let (out, trace) = route_enforce(c).await;
assert!(
!log.lock().unwrap().contains(&OPUS.to_owned()),
"must not fire beyond max_rungs: {:?}",
*log.lock().unwrap()
);
assert_eq!(trace.attempts.len(), 2);
assert_eq!(trace.final_.served_from, ServedFrom::BestAttempt);
assert_eq!(trace.final_.served_rung, Some(1));
match out {
EngineOutcome::Served(r) => assert_eq!(r.model, SONNET),
EngineOutcome::Failed(e) => panic!("expected best-attempt served, got {e}"),
}
}
#[tokio::test]
async fn speculation_cuts_wall_clock_vs_serial() {
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(NonEmptyGate)];
let req = base_request();
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let build = || {
let mut outs = HashMap::new();
outs.insert(HAIKU.to_owned(), Ok(resp(HAIKU, ""))); outs.insert(SONNET.to_owned(), Ok(resp(SONNET, "real answer"))); let mock = MockProvider::new("anthropic", outs).with_delay(80);
let mut map: HashMap<String, Arc<dyn Provider>> = HashMap::new();
map.insert("anthropic".to_owned(), Arc::new(mock));
ProviderRegistry::from_map(map)
};
let providers = build();
let health = GateHealthRegistry::new();
let t = std::time::Instant::now();
let _ = route_enforce(ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
))
.await;
let serial = t.elapsed();
let providers = build();
let health = GateHealthRegistry::new();
let mut c = ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
);
c.speculation = 1;
let t = std::time::Instant::now();
let _ = route_enforce(c).await;
let spec = t.elapsed();
assert!(
spec < serial * 3 / 4,
"speculation must overlap rung latencies: serial={serial:?} spec={spec:?}"
);
}
#[derive(Debug)]
struct ScoreGate;
#[async_trait::async_trait]
impl Gate for ScoreGate {
fn id(&self) -> &str {
"score"
}
async fn evaluate(&self, _req: &ModelRequest, resp: &ModelResponse) -> GateResult {
let score = resp.text.trim().parse::<f64>().unwrap_or(0.0);
GateResult {
gate_id: self.id().to_owned(),
verdict: Verdict::Pass,
score: Some(firstpass_core::Score::clamped(score)),
cost_usd: 0.0,
ms: 0,
reason: None,
evidence_ref: None,
}
}
}
#[tokio::test]
async fn serve_threshold_escalates_past_low_scoring_rung() {
let ladder = vec![HAIKU.to_owned(), SONNET.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(ScoreGate)];
let req = base_request();
let providers = registry(vec![
("anthropic", HAIKU, Ok(resp(HAIKU, "0.5"))),
("anthropic", SONNET, Ok(resp(SONNET, "0.9"))),
]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let mut c = ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
);
c.serve_threshold = Some(0.8);
let (out, trace) = route_enforce(c).await;
assert!(matches!(out, EngineOutcome::Served(r) if r.model == SONNET));
assert_eq!(trace.attempts.len(), 2);
assert_eq!(trace.attempts[0].verdict, Verdict::Pass);
assert_eq!(trace.attempts[1].verdict, Verdict::Pass);
assert_eq!(trace.final_.escalations, 1);
assert_eq!(trace.final_.served_rung, Some(1));
assert_eq!(trace.final_.served_from, ServedFrom::Attempt);
}
#[tokio::test]
async fn serve_threshold_does_not_serve_a_pass_below_threshold() {
let ladder = vec![HAIKU.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(ScoreGate)];
let req = base_request();
let providers = registry(vec![("anthropic", HAIKU, Ok(resp(HAIKU, "0.3")))]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let mut c = ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
);
c.serve_threshold = Some(0.8);
let (out, trace) = route_enforce(c).await;
assert!(matches!(out, EngineOutcome::Served(r) if r.model == HAIKU));
assert_eq!(
trace.attempts[0].verdict,
Verdict::Pass,
"gate verdict was Pass"
);
assert_eq!(
trace.final_.served_from,
ServedFrom::BestAttempt,
"score below threshold must fall back, not serve as a normal pass"
);
}
#[tokio::test]
async fn serve_threshold_none_serves_on_verdict_regardless_of_score() {
let ladder = vec![HAIKU.to_owned()];
let gates: Vec<Box<dyn Gate>> = vec![Box::new(ScoreGate)];
let req = base_request();
let providers = registry(vec![("anthropic", HAIKU, Ok(resp(HAIKU, "0.1")))]);
let (auth, prices) = (Auth::default(), PriceTable::defaults());
let health = GateHealthRegistry::new();
let c = ctx(
&ladder, &gates, &req, &providers, &auth, &prices, None, &health,
);
assert_eq!(c.serve_threshold, None);
let (out, trace) = route_enforce(c).await;
assert!(matches!(out, EngineOutcome::Served(r) if r.model == HAIKU));
assert_eq!(trace.final_.served_from, ServedFrom::Attempt);
}
}