use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::fleet_exact::FrozenRoute;
use crate::fleet_reasoning::ReasoningCapability;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum CredentialReadiness {
Configured,
KeylessLocal,
Missing { detail: String },
}
impl CredentialReadiness {
#[must_use]
pub const fn is_ready(&self) -> bool {
matches!(self, Self::Configured | Self::KeylessLocal)
}
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Configured => "configured",
Self::KeylessLocal => "keyless_local",
Self::Missing { .. } => "missing",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EndpointIdentity {
pub host: String,
pub local: bool,
}
impl EndpointIdentity {
#[must_use]
pub fn from_base_url(base_url: &str) -> Self {
let without_scheme = base_url
.trim()
.split_once("://")
.map_or(base_url.trim(), |(_, rest)| rest);
let authority = without_scheme
.split(['/', '?', '#'])
.next()
.unwrap_or_default();
let host = authority
.rsplit_once('@')
.map_or(authority, |(_, host)| host)
.to_ascii_lowercase();
let bare = host.split(':').next().unwrap_or(&host);
let local = bare == "localhost"
|| bare == "127.0.0.1"
|| bare == "::1"
|| bare.starts_with("192.168.")
|| bare.starts_with("10.")
|| bare.ends_with(".local");
Self { host, local }
}
#[must_use]
pub fn label(&self) -> String {
if self.local {
format!("{} (local)", self.host)
} else {
self.host.clone()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PreflightedRoute {
pub member_id: String,
pub provider_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_config_id: Option<String>,
pub provider_kind: String,
pub declared_model: String,
pub wire_model: String,
pub endpoint: EndpointIdentity,
pub credential: CredentialReadiness,
pub capability: ReasoningCapability,
}
impl PreflightedRoute {
#[must_use]
pub fn provider_config_id(&self) -> &str {
self.provider_config_id
.as_deref()
.unwrap_or(&self.provider_id)
}
#[must_use]
pub fn frozen(&self) -> FrozenRoute {
FrozenRoute {
provider: self.provider_id.clone(),
model: self.wire_model.clone(),
}
}
#[must_use]
pub fn model_canonicalized(&self) -> bool {
self.declared_model != self.wire_model
}
pub fn require_ready(&self) -> Result<(), PreflightError> {
match &self.credential {
CredentialReadiness::Configured | CredentialReadiness::KeylessLocal => Ok(()),
CredentialReadiness::Missing { detail } => Err(PreflightError::CredentialMissing {
member: self.member_id.clone(),
provider: self.provider_id.clone(),
detail: detail.clone(),
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct RoutePreflight {
workers: Vec<PreflightedRoute>,
router: Option<PreflightedRoute>,
}
impl RoutePreflight {
#[must_use]
pub fn new(workers: Vec<PreflightedRoute>, router: Option<PreflightedRoute>) -> Self {
Self { workers, router }
}
#[must_use]
pub fn workers(&self) -> &[PreflightedRoute] {
&self.workers
}
#[must_use]
pub fn router(&self) -> Option<&PreflightedRoute> {
self.router.as_ref()
}
#[must_use]
pub fn worker(&self, member_id: &str) -> Option<&PreflightedRoute> {
let key = member_id.trim().to_ascii_lowercase();
self.workers.iter().find(|route| route.member_id == key)
}
pub fn require_all_ready(&self) -> Result<(), PreflightError> {
for route in &self.workers {
route.require_ready()?;
}
Ok(())
}
#[must_use]
pub fn crosses_providers(&self, member_id: &str) -> bool {
match (self.worker(member_id), self.router()) {
(Some(worker), Some(router)) => worker.provider_id != router.provider_id,
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum PreflightError {
#[error(
"fleet member `{member}` is pinned to provider `{provider}`, which does not resolve to a \
configured provider: {detail}"
)]
ProviderUnresolved {
member: String,
provider: String,
detail: String,
},
#[error(
"fleet member `{member}` is pinned to model `{model}` on provider `{provider}`, which is \
not a valid route: {detail}"
)]
ModelUnresolved {
member: String,
provider: String,
model: String,
detail: String,
},
#[error(
"fleet member `{member}` cannot run: provider `{provider}` has no credential configured \
on this machine ({detail}). This is decided locally — no provider was contacted. Keyless \
local providers do not need one."
)]
CredentialMissing {
member: String,
provider: String,
detail: String,
},
#[error(
"cannot determine what reasoning control provider `{provider}` actually expresses for \
model `{model}`: {detail}. An exact fleet fails closed here rather than claiming a \
capability it did not verify."
)]
CapabilityUnknown {
provider: String,
model: String,
detail: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
fn route(member: &str, provider: &str, wire: &str) -> PreflightedRoute {
PreflightedRoute {
member_id: member.to_string(),
provider_id: provider.to_string(),
provider_config_id: None,
provider_kind: provider.to_string(),
declared_model: wire.to_string(),
wire_model: wire.to_string(),
endpoint: EndpointIdentity::from_base_url("https://api.z.ai/api/paas/v4"),
credential: CredentialReadiness::Configured,
capability: ReasoningCapability::tiered(),
}
}
#[test]
fn an_endpoint_identity_keeps_the_host_and_drops_credentials() {
let identity =
EndpointIdentity::from_base_url("https://user:sk-secret@api.z.ai/api/paas/v4");
assert_eq!(identity.host, "api.z.ai");
assert!(!identity.local);
assert!(!identity.label().contains("sk-secret"));
assert!(!identity.label().contains('/'));
}
#[test]
fn loopback_and_private_endpoints_are_marked_local() {
for url in [
"http://127.0.0.1:8000/v1",
"http://localhost:11434",
"http://192.168.1.20:8000/v1",
"http://box.local/v1",
] {
let identity = EndpointIdentity::from_base_url(url);
assert!(identity.local, "{url} must be local");
assert!(identity.label().ends_with("(local)"));
}
assert!(!EndpointIdentity::from_base_url("https://api.openai.com/v1").local);
}
#[test]
fn the_frozen_route_uses_the_canonical_wire_model() {
let mut preflighted = route("implementer", "zai", "glm-5");
preflighted.wire_model = "glm-5-20260101".to_string();
assert_eq!(preflighted.frozen().model, "glm-5-20260101");
assert_eq!(preflighted.frozen().provider, "zai");
assert!(preflighted.model_canonicalized());
assert_eq!(preflighted.declared_model, "glm-5");
}
#[test]
fn keyless_local_providers_are_ready() {
let mut local = route("worker", "vllm", "qwen3");
local.credential = CredentialReadiness::KeylessLocal;
local.endpoint = EndpointIdentity::from_base_url("http://127.0.0.1:8000/v1");
assert!(local.credential.is_ready());
local.require_ready().expect("keyless local is valid");
assert_eq!(local.credential.as_str(), "keyless_local");
}
#[test]
fn a_missing_credential_fails_the_workflow_locally() {
let mut route = route("implementer", "zai", "glm-5");
route.credential = CredentialReadiness::Missing {
detail: "no ZAI_API_KEY".to_string(),
};
let preflight = RoutePreflight::new(vec![route], None);
let err = preflight
.require_all_ready()
.expect_err("a member with no credential must not start");
assert!(matches!(err, PreflightError::CredentialMissing { .. }));
let message = err.to_string();
assert!(
message.contains("decided locally"),
"the error must say no provider was contacted: {message}"
);
}
#[test]
fn cross_provider_inference_is_detectable_from_the_preflight() {
let preflight = RoutePreflight::new(
vec![route("implementer", "zai", "glm-5")],
Some(route("router", "openai", "gpt-5.6-luna")),
);
assert!(preflight.crosses_providers("implementer"));
let same = RoutePreflight::new(
vec![route("implementer", "zai", "glm-5")],
Some(route("router", "zai", "glm-5-turbo")),
);
assert!(!same.crosses_providers("implementer"));
let none = RoutePreflight::new(vec![route("implementer", "zai", "glm-5")], None);
assert!(!none.crosses_providers("implementer"));
}
#[test]
fn a_preflight_serializes_without_secrets_or_paths() {
let preflight = RoutePreflight::new(
vec![route("implementer", "zai", "glm-5")],
Some(route("router", "openai", "gpt-5.6-luna")),
);
let json = serde_json::to_string(&preflight).expect("serialize");
let lowered = json.to_ascii_lowercase();
for forbidden in [
"api_key", "secret", "bearer", "base_url", "/users/", "https://",
] {
assert!(!lowered.contains(forbidden), "{forbidden} in {json}");
}
let back: RoutePreflight = serde_json::from_str(&json).expect("round-trip");
assert_eq!(back, preflight);
}
}