use std::sync::Arc;
use async_trait::async_trait;
use codewhale_workflow::{
CapturedReasoningRouter, CredentialReadiness, EffectiveReasoning, EndpointIdentity,
FleetDocument, FleetRouterRef, FleetSearchRoot, FleetSnapshot, FleetSnapshotMember,
FleetTaskReceipt, NamedFleetError, PermissionCeiling, PreflightError, PreflightedRoute,
ProviderReasoningControl, QualifiedFleetId, ReasoningCapability, ReasoningRouterProfile,
ReasoningTier, ResolvedReasoning, RoutePreflight, RouterAvailability, RouterCallInput,
RouterCallPlan, RouterIdentity, RoutingDisclosure, ShellCeiling, bounded_routing_payload,
captured_legacy_inline_router, parse_router_decision, resolve_exact_member_reasoning,
router_call_plan, router_system_prompt, router_user_message,
};
use crate::config::{ApiProvider, Config};
use crate::fleet::profile::AgentProfile;
use crate::fleet::roster::{FleetRoster, ProfileOrigin};
use crate::llm_client::LlmClient;
use crate::tui::app::ReasoningEffort;
fn personal_fleet_root() -> anyhow::Result<std::path::PathBuf> {
codewhale_config::codewhale_home()
}
pub(crate) fn personal_fleet_definitions_dir() -> anyhow::Result<std::path::PathBuf> {
Ok(personal_fleet_root()?.join("fleets"))
}
#[must_use]
pub(crate) fn fleet_search_roots(workspace: &std::path::Path) -> Vec<FleetSearchRoot> {
let mut roots = Vec::new();
if let Ok(home) = personal_fleet_root() {
roots.push(FleetSearchRoot::new("codewhale_home", home));
}
roots.push(FleetSearchRoot::new("workspace", workspace.to_path_buf()));
roots
}
pub(crate) fn load_fleet_document(
name: &str,
workspace: &std::path::Path,
) -> Result<(FleetDocument, QualifiedFleetId), NamedFleetError> {
FleetDocument::load_by_name(name, &fleet_search_roots(workspace))
}
pub(crate) const NETWORK_TOOL_DENYLIST: &[&str] = &[
"web_*",
"web.*",
"web.run",
"web_run",
"web_search",
"web.fetch",
"web_fetch",
"fetch_url",
"wait_for_dev_server",
"browse",
"browser",
"github",
"finance",
"rlm_open",
"rlm_eval",
"mcp*",
"start_mcp_server",
"list_mcp_resources",
"list_mcp_resource_templates",
"read_mcp_resource",
];
pub(crate) const NETWORK_DENIAL_SENTINEL: &str = "fetch_url";
pub(crate) const MUTATING_TOOL_DENYLIST: &[&str] = &[
"write_file",
"edit_file",
"apply_patch",
"fim_edit",
"revert_turn",
"rlm_eval",
];
pub(crate) const RAW_SHELL_DENYLIST: &[&str] = &[
"Bash",
"exec_shell",
"exec_shell_wait",
"exec_wait",
"exec_shell_interact",
"exec_interact",
"exec_shell_cancel",
"task_shell_start",
"task_shell_wait",
"terminal/*",
];
#[allow(dead_code)]
pub(crate) const RAW_SHELL_SENTINEL: &str = "exec_shell";
pub(crate) const VERIFICATION_SURFACE_DENYLIST: &[&str] = &["Run", "run_tests", "run_verifiers"];
pub(crate) const SHELL_AUTHORITY_SENTINEL: &str = "run_tests";
pub(crate) const NON_SHELL_EXECUTION_DENYLIST: &[&str] = &[
"task_gate_run",
"task_create",
"task_cancel",
"pr_attempt_record",
"pr_attempt_preflight",
"automation_run",
"automation_create",
"automation_update",
"automation_pause",
"automation_resume",
"automation_delete",
"start_mcp_server",
];
#[must_use]
pub(crate) fn is_posture_denial(rule: &str) -> bool {
[
NETWORK_TOOL_DENYLIST,
MUTATING_TOOL_DENYLIST,
RAW_SHELL_DENYLIST,
VERIFICATION_SURFACE_DENYLIST,
NON_SHELL_EXECUTION_DENYLIST,
]
.iter()
.flat_map(|list| list.iter())
.any(|entry| entry.eq_ignore_ascii_case(rule.trim()))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ChildAuthority {
pub(crate) ceiling: PermissionCeiling,
pub(crate) allowed_tools: Option<Vec<String>>,
pub(crate) disallowed_tools: Vec<String>,
pub(crate) write_authority: &'static str,
pub(crate) max_depth: u32,
pub(crate) posture_role: &'static str,
}
impl ChildAuthority {
#[must_use]
pub(crate) fn clamp(member: PermissionCeiling, session: PermissionCeiling) -> Self {
let ceiling = member.clamp_to(session);
let allowed_tools = (!ceiling.tools).then(Vec::new);
let mut disallowed_tools = Vec::new();
if !ceiling.network_tool {
disallowed_tools.extend(NETWORK_TOOL_DENYLIST.iter().map(|name| (*name).to_string()));
}
if !(ceiling.write && ceiling.shell == ShellCeiling::Full) {
disallowed_tools.extend(RAW_SHELL_DENYLIST.iter().map(|name| (*name).to_string()));
}
if ceiling.shell != ShellCeiling::Full {
disallowed_tools.extend(
VERIFICATION_SURFACE_DENYLIST
.iter()
.map(|name| (*name).to_string()),
);
}
if !ceiling.write {
disallowed_tools.extend(
MUTATING_TOOL_DENYLIST
.iter()
.map(|name| (*name).to_string()),
);
disallowed_tools.extend(
NON_SHELL_EXECUTION_DENYLIST
.iter()
.map(|name| (*name).to_string()),
);
}
Self {
ceiling,
allowed_tools,
disallowed_tools,
write_authority: if ceiling.write {
"workspace_write"
} else {
"read_only"
},
max_depth: ceiling.delegation_depth,
posture_role: posture_role_for(ceiling),
}
}
#[must_use]
pub(crate) fn fingerprint(&self) -> String {
let allowed = match &self.allowed_tools {
None => "inherit".to_string(),
Some(list) if list.is_empty() => "none".to_string(),
Some(list) => {
let mut list = list.clone();
list.sort();
list.join(",")
}
};
let mut denied = self.disallowed_tools.clone();
denied.sort();
denied.dedup();
format!(
"v1;posture={};write={};depth={};tools={};network={};shell={};allow={};deny={}",
self.posture_role,
self.write_authority,
self.max_depth,
self.ceiling.tools,
self.ceiling.network_tool,
self.ceiling.shell.as_str(),
allowed,
denied.join(","),
)
}
#[must_use]
pub(crate) fn clamp_for_role(
role: &str,
member: PermissionCeiling,
session: PermissionCeiling,
) -> Self {
let mut authority = Self::clamp(member, session);
authority.posture_role = posture_role_for_member(role, authority.ceiling);
let bounded_inspection_role = matches!(
role.trim().to_ascii_lowercase().as_str(),
"scout"
| "explore"
| "explorer"
| "reviewer"
| "review"
| "planner"
| "plan"
| "planning"
| "awaiter"
);
if bounded_inspection_role && authority.ceiling.shell != ShellCeiling::None {
authority
.disallowed_tools
.retain(|name| !name.eq_ignore_ascii_case("Bash"));
}
authority
}
}
#[must_use]
pub(crate) fn session_permission_ceiling(
runtime: &crate::tools::subagent::SubAgentRuntime,
) -> PermissionCeiling {
PermissionCeiling {
write: runtime.worker_profile.permissions.write,
network_tool: runtime.worker_profile.permissions.network
&& runtime.agent_tool_surface_options.web_search_enabled,
shell: session_shell_ceiling(runtime.worker_profile.shell, runtime.allow_shell),
delegation_depth: runtime.worker_profile.max_spawn_depth,
tools: true,
}
}
fn session_shell_ceiling(
shell: crate::worker_profile::ShellPolicy,
allow_shell: bool,
) -> ShellCeiling {
match shell {
crate::worker_profile::ShellPolicy::None => ShellCeiling::None,
crate::worker_profile::ShellPolicy::ReadOnly => ShellCeiling::ReadOnly,
crate::worker_profile::ShellPolicy::Full if allow_shell => ShellCeiling::Full,
crate::worker_profile::ShellPolicy::Full => ShellCeiling::None,
}
}
#[must_use]
pub(crate) fn posture_role_for_member(role: &str, ceiling: PermissionCeiling) -> &'static str {
canonical_role_within_ceiling(role, ceiling).unwrap_or_else(|| posture_role_for(ceiling))
}
fn canonical_role_within_ceiling(role: &str, ceiling: PermissionCeiling) -> Option<&'static str> {
use crate::tools::subagent::FleetRole;
if !ceiling.tools {
return None;
}
let canonical = FleetRole::from_str(role)?;
if matches!(canonical, FleetRole::Custom) {
return None;
}
let posture = crate::worker_profile::WorkerRuntimeProfile::for_role(canonical.clone());
let role_shell = match posture.shell {
crate::worker_profile::ShellPolicy::None => ShellCeiling::None,
crate::worker_profile::ShellPolicy::ReadOnly => ShellCeiling::ReadOnly,
crate::worker_profile::ShellPolicy::Full => ShellCeiling::Full,
};
if posture.permissions.write && !ceiling.write {
return None;
}
if role_shell == ShellCeiling::Full && role_shell > ceiling.shell {
return None;
}
Some(canonical.as_str())
}
#[must_use]
pub(crate) fn posture_role_for(ceiling: PermissionCeiling) -> &'static str {
if !ceiling.tools {
return "scout";
}
if ceiling.write {
return "builder";
}
match ceiling.shell {
ShellCeiling::None | ShellCeiling::ReadOnly => "scout",
ShellCeiling::Full => "verifier",
}
}
#[must_use]
#[cfg(test)]
pub(crate) fn write_authority_for(ceiling: PermissionCeiling) -> &'static str {
if ceiling.write {
"workspace_write"
} else {
"read_only"
}
}
#[must_use]
pub(crate) fn reasoning_capability_for_route(
provider: ApiProvider,
base_url: &str,
wire_model: &str,
) -> ReasoningCapability {
let body_for = |effort: ReasoningEffort| -> String {
let mut body = serde_json::json!({});
let value = effort.api_value_for_route(provider, base_url, wire_model);
crate::client::apply_reasoning_effort(&mut body, value, provider);
if let Some(object) = body.as_object_mut() {
object.remove("reasoning_split");
}
body.to_string()
};
let off = body_for(ReasoningEffort::Off);
let above_off: Vec<String> = [
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
ReasoningEffort::Max,
]
.into_iter()
.map(body_for)
.collect();
let empty = "{}";
let all_empty = off == empty && above_off.iter().all(|body| body == empty);
let mut distinct = above_off.clone();
distinct.sort();
distinct.dedup();
let control = if all_empty {
ProviderReasoningControl::None
} else if distinct.len() == 1 && distinct[0] == off && off.contains("adaptive") {
ProviderReasoningControl::NativeAdaptive
} else if distinct.len() > 1 {
ProviderReasoningControl::Tiers
} else {
ProviderReasoningControl::EnabledDisabled
};
let wire_tiers = [
ReasoningEffort::Off,
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
ReasoningEffort::Max,
]
.map(|effort| {
tier_of(effort.normalize_for_route(provider, base_url, wire_model))
.unwrap_or(ReasoningTier::Off)
});
ReasoningCapability {
control,
min_tier: None,
max_tier: None,
wire_tiers: None,
}
.with_wire_tiers(wire_tiers)
}
fn tier_of(effort: ReasoningEffort) -> Option<ReasoningTier> {
match effort {
ReasoningEffort::Off => Some(ReasoningTier::Off),
ReasoningEffort::Minimal => Some(ReasoningTier::Low),
ReasoningEffort::Low => Some(ReasoningTier::Low),
ReasoningEffort::Medium => Some(ReasoningTier::Medium),
ReasoningEffort::High => Some(ReasoningTier::High),
ReasoningEffort::XHigh => Some(ReasoningTier::Max),
ReasoningEffort::Ultra => Some(ReasoningTier::Max),
ReasoningEffort::Max => Some(ReasoningTier::Max),
ReasoningEffort::Auto => None,
}
}
#[must_use]
pub(crate) fn route_reasoning_setting(
provider: ApiProvider,
base_url: &str,
wire_model: &str,
tier: ReasoningTier,
) -> String {
effort_of(tier)
.as_setting_for_route(provider, base_url, wire_model)
.to_string()
}
fn effort_of(tier: ReasoningTier) -> ReasoningEffort {
match tier {
ReasoningTier::Off => ReasoningEffort::Off,
ReasoningTier::Low => ReasoningEffort::Low,
ReasoningTier::Medium => ReasoningEffort::Medium,
ReasoningTier::High => ReasoningEffort::High,
ReasoningTier::Max => ReasoningEffort::Max,
}
}
pub(crate) fn preflight_route(
member_id: &str,
provider: &str,
model: &str,
config: &Config,
) -> Result<PreflightedRoute, PreflightError> {
let identity = config
.resolve_provider_identity(provider.trim())
.map_err(|detail| PreflightError::ProviderUnresolved {
member: member_id.to_string(),
provider: provider.to_string(),
detail,
})?;
let wire_model = crate::config::requested_model_for_provider(identity.provider, model.trim())
.ok_or_else(|| PreflightError::ModelUnresolved {
member: member_id.to_string(),
provider: identity.key.clone(),
model: model.to_string(),
detail: "not a known model for this provider".to_string(),
})?;
crate::config::validate_route(identity.provider, &wire_model).map_err(|detail| {
PreflightError::ModelUnresolved {
member: member_id.to_string(),
provider: identity.key.clone(),
model: wire_model.clone(),
detail,
}
})?;
let mut scoped = config.clone();
scoped.scope_to_provider_identity(&identity);
let base_url = scoped.deepseek_base_url();
let credential =
if crate::config::provider_route_is_keyless_self_hosted(identity.provider, &base_url) {
CredentialReadiness::KeylessLocal
} else if crate::config::has_api_key_for(&scoped, identity.provider) {
CredentialReadiness::Configured
} else {
codewhale_telemetry::session_counters()
.bump_error(codewhale_telemetry::ErrorCounter::AuthPreflightFailed);
CredentialReadiness::Missing {
detail: format!("no credential configured for `{}`", identity.key),
}
};
Ok(PreflightedRoute {
member_id: member_id.to_string(),
provider_id: identity.key.clone(),
provider_config_id: identity
.migrated_legacy_ollama_cloud_route
.then(|| provider.trim().to_string()),
provider_kind: if identity.provider == ApiProvider::OllamaCloud {
identity.provider.as_str().to_string()
} else {
format!("{:?}", identity.provider).to_ascii_lowercase()
},
declared_model: model.trim().to_string(),
wire_model: wire_model.clone(),
endpoint: EndpointIdentity::from_base_url(&base_url),
credential,
capability: reasoning_capability_for_route(identity.provider, &base_url, &wire_model),
})
}
fn validate_route_client(route: &PreflightedRoute, config: &Config) -> Result<(), String> {
let mut scoped = config.clone();
let identity = config.resolve_provider_identity(route.provider_config_id())?;
scoped.scope_to_provider_identity(&identity);
crate::client::DeepSeekClient::new(&scoped)
.map(|_| ())
.map_err(|error| {
format!(
"member `{}` is pinned to provider `{}` (model `{}`), whose client could not be \
built on this machine: {error}",
route.member_id, route.provider_id, route.wire_model
)
})
}
#[async_trait]
pub(crate) trait FleetRouterCaller: Send + Sync + std::fmt::Debug {
async fn decide(&self, input: &RouterCallInput) -> Result<String, String>;
fn identity(&self) -> RouterIdentity;
}
#[derive(Clone)]
pub(crate) struct LiveFleetRouter {
client: crate::client::DeepSeekClient,
captured: CapturedReasoningRouter,
route: PreflightedRoute,
provider: ApiProvider,
base_url: String,
call: RouterCallPlan,
}
impl std::fmt::Debug for LiveFleetRouter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LiveFleetRouter")
.field("router", &self.captured.qualified())
.field("provider", &self.route.provider_id)
.field("model", &self.route.wire_model)
.field("call_reasoning", &self.call.tier)
.field("client", &"<redacted>")
.finish()
}
}
impl LiveFleetRouter {
pub(crate) fn bind(
captured: &CapturedReasoningRouter,
config: &Config,
) -> Result<Self, RouterBindError> {
let route = preflight_route(
&captured.id,
&captured.route.provider,
&captured.route.model,
config,
)
.map_err(|error| RouterBindError {
reason: error.to_string(),
})?;
route.require_ready().map_err(|error| RouterBindError {
reason: error.to_string(),
})?;
let identity = config
.resolve_provider_identity(route.provider_config_id())
.map_err(|detail| RouterBindError {
reason: format!(
"reasoning router provider `{}` did not resolve: {detail}",
route.provider_id
),
})?;
let mut scoped = config.clone();
scoped.scope_to_provider_identity(&identity);
let base_url = scoped.deepseek_base_url();
let client =
crate::client::DeepSeekClient::new(&scoped).map_err(|error| RouterBindError {
reason: format!(
"reasoning router provider `{}` client could not be built: {error}",
route.provider_id
),
})?;
let call = router_call_plan(captured.requested_call_reasoning, &route.capability);
Ok(Self {
client,
captured: captured.clone(),
route,
provider: identity.provider,
base_url,
call,
})
}
#[must_use]
pub(crate) fn route(&self) -> &PreflightedRoute {
&self.route
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RouterBindError {
pub(crate) reason: String,
}
#[async_trait]
impl FleetRouterCaller for LiveFleetRouter {
fn identity(&self) -> RouterIdentity {
RouterIdentity::from_captured(
&self.captured,
Some(&self.route),
Some(self.call.disclosure.clone()),
)
}
async fn decide(&self, input: &RouterCallInput) -> Result<String, String> {
use crate::models::{ContentBlock, Message, MessageRequest, SystemPrompt};
let request = MessageRequest {
model: self.route.wire_model.clone(),
messages: vec![Message {
role: "user".to_string(),
content: vec![ContentBlock::Text {
text: router_user_message(input),
cache_control: None,
}],
}],
max_tokens: crate::route_budget::effective_max_output_tokens_for_route(
self.provider,
&self.route.wire_model,
None,
),
system: Some(SystemPrompt::Text(router_system_prompt(input))),
tools: None,
tool_choice: None,
metadata: None,
thinking: None,
reasoning_effort: Some(route_reasoning_setting(
self.provider,
&self.base_url,
&self.route.wire_model,
self.call.tier,
)),
stream: Some(false),
temperature: None,
top_p: None,
};
let response = self
.client
.create_message(request)
.await
.map_err(|error| error.to_string())?;
if crate::models::is_incomplete_stop_reason(response.stop_reason.as_deref()) {
return Err(format!(
"reasoning router response incomplete: provider stop reason `{}`",
crate::models::stop_reason_detail(response.stop_reason.as_deref())
));
}
let text = response
.content
.into_iter()
.filter_map(|block| match block {
ContentBlock::Text { text, .. } => Some(text),
_ => None,
})
.collect::<Vec<_>>()
.join("");
if text.trim().is_empty() {
return Err("reasoning router returned an empty response".to_string());
}
Ok(text)
}
}
#[derive(Clone)]
pub(crate) struct ExactFleetWorkflow {
snapshot: Arc<FleetSnapshot>,
preflight: Arc<RoutePreflight>,
roster: Arc<FleetRoster>,
router: Option<Arc<dyn FleetRouterCaller>>,
router_unavailable: Option<String>,
}
impl std::fmt::Debug for ExactFleetWorkflow {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExactFleetWorkflow")
.field("fleet", &self.snapshot.fleet().qualified())
.field("members", &self.snapshot.members().len())
.field("router", &self.router.is_some())
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ExactMemberBinding {
pub(crate) member_id: String,
pub(crate) member_role: String,
pub(crate) route: PreflightedRoute,
pub(crate) requires_router: bool,
pub(crate) authority: ChildAuthority,
pub(crate) session: PermissionCeiling,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ExactMemberLaunch {
pub(crate) member_id: String,
pub(crate) member_role: String,
pub(crate) provider: String,
pub(crate) model: String,
pub(crate) thinking: String,
pub(crate) reasoning: ResolvedReasoning,
pub(crate) authority: ChildAuthority,
pub(crate) receipt: FleetTaskReceipt,
}
impl ExactFleetWorkflow {
pub(crate) fn capture(
document: &FleetDocument,
id: QualifiedFleetId,
captured_at: impl Into<String>,
config: Option<&Config>,
search_roots: &[FleetSearchRoot],
) -> Result<Self, String> {
let exact = document
.exact()
.ok_or_else(|| "fleet is not an exact fleet".to_string())?;
let captured_router = match exact.router_ref() {
None => None,
Some(FleetRouterRef::LegacyInline(_)) => captured_legacy_inline_router(exact),
Some(FleetRouterRef::Profile { name }) => {
let (profile, router_id) =
ReasoningRouterProfile::load_by_name(&name, search_roots).map_err(|error| {
format!(
"exact fleet `{}` references reasoning router `{name}`, which could \
not be loaded: {error}",
id.qualified()
)
})?;
Some(CapturedReasoningRouter::from_profile(
&profile,
router_id.origin,
))
}
};
let snapshot = FleetSnapshot::capture(id, document, captured_at, captured_router.clone())
.and_then(FleetSnapshot::into_verified)
.map_err(|error| error.to_string())?;
let (preflight, router) = Self::preflight_and_bind(&snapshot, captured_router, config)?;
let roster = Arc::new(FleetRoster::from_members(
snapshot
.members()
.iter()
.map(|member| {
let route = preflight.worker(&member.id);
exact_member_profile(member, route, document.source_path())
})
.collect(),
));
let router_unavailable = match (snapshot.router(), &router) {
(Some(_), None) => {
Some("the fleet's reasoning router could not be bound on this machine".to_string())
}
_ => None,
};
let workflow = Self {
snapshot: Arc::new(snapshot),
preflight: Arc::new(preflight),
roster,
router,
router_unavailable,
};
workflow.reject_unusable_auto_members()?;
Ok(workflow)
}
fn preflight_and_bind(
snapshot: &FleetSnapshot,
captured_router: Option<CapturedReasoningRouter>,
config: Option<&Config>,
) -> Result<(RoutePreflight, Option<Arc<dyn FleetRouterCaller>>), String> {
let Some(config) = config else {
return Err(format!(
"exact fleet `{}` cannot start: no session config is available to preflight its \
members' providers and models. An exact fleet fails closed here rather than \
dispatching a worker onto a route it never verified.",
snapshot.fleet().qualified()
));
};
let mut workers = Vec::with_capacity(snapshot.members().len());
for member in snapshot.members() {
let route = preflight_route(
&member.id,
&member.route.provider,
&member.route.model,
config,
)
.map_err(|error| {
format!(
"exact fleet `{}` cannot start: {error}",
snapshot.fleet().qualified()
)
})?;
route.require_ready().map_err(|error| {
format!(
"exact fleet `{}` cannot start: {error}",
snapshot.fleet().qualified()
)
})?;
workers.push(route);
}
for route in &workers {
validate_route_client(route, config).map_err(|error| {
format!(
"exact fleet `{}` cannot start: {error}",
snapshot.fleet().qualified()
)
})?;
}
let mut router: Option<Arc<dyn FleetRouterCaller>> = None;
let mut router_route = None;
if let Some(captured) = &captured_router {
match LiveFleetRouter::bind(captured, config) {
Ok(live) => {
router_route = Some(live.route().clone());
router = Some(Arc::new(live));
}
Err(error) => {
if snapshot.has_auto_member() {
return Err(format!(
"exact fleet `{}` cannot start: member(s) {} request reasoning \
`auto` but the fleet's reasoning router is unusable ({}). Fix the \
router profile or pin an explicit reasoning tier — exact fleets \
never fall back to legacy model routing or a local heuristic.",
snapshot.fleet().qualified(),
snapshot.auto_member_ids().join(", "),
error.reason,
));
}
}
}
}
Ok((RoutePreflight::new(workers, router_route), router))
}
fn reject_unusable_auto_members(&self) -> Result<(), String> {
if !self.snapshot.has_auto_member() || self.router.is_some() {
return Ok(());
}
let reason = self
.router_unavailable
.clone()
.unwrap_or_else(|| "this fleet references no reasoning router".to_string());
Err(format!(
"exact fleet `{}` cannot start: member(s) {} request reasoning `auto` but the fleet's \
reasoning router is unusable ({reason}). Attach a working reasoning router or pin an \
explicit reasoning tier — exact fleets never fall back to legacy model routing or a \
local heuristic.",
self.snapshot.fleet().qualified(),
self.snapshot.auto_member_ids().join(", "),
))
}
#[must_use]
pub(crate) fn snapshot(&self) -> &Arc<FleetSnapshot> {
&self.snapshot
}
#[must_use]
pub(crate) fn roster(&self) -> &Arc<FleetRoster> {
&self.roster
}
#[must_use]
#[cfg(test)]
pub(crate) fn member(&self, id_or_role: &str) -> Option<&FleetSnapshotMember> {
self.snapshot.member_by_id_or_role(id_or_role)
}
#[must_use]
pub(crate) fn member_names(&self) -> String {
self.snapshot
.members()
.iter()
.map(|member| {
if member.role == member.id {
member.id.clone()
} else {
format!("{} (role {})", member.id, member.role)
}
})
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn bind_member(
&self,
profile: Option<&str>,
role: Option<&str>,
session: PermissionCeiling,
) -> Result<ExactMemberBinding, String> {
let fleet = self.snapshot.fleet().qualified();
let profile = profile.map(str::trim).filter(|key| !key.is_empty());
let role = role.map(str::trim).filter(|key| !key.is_empty());
let member = match (profile, role) {
(None, None) => {
return Err(format!(
"fleet `{fleet}` is an exact fleet: every task must name a member via `role` \
or `profile`. Members: {}",
self.member_names()
));
}
(Some(profile), None) => self.lookup(profile)?,
(None, Some(role)) => self.lookup(role)?,
(Some(profile), Some(role)) => {
let by_profile = self.lookup(profile)?;
let by_role = self.lookup(role)?;
if by_profile.id != by_role.id {
return Err(format!(
"fleet `{fleet}`: task names profile `{profile}` (member `{}`) and role \
`{role}` (member `{}`), which are different members. A task must name \
one member; the two fields cannot disagree about who ran.",
by_profile.id, by_role.id
));
}
by_profile
}
};
let route = self.preflight.worker(&member.id).ok_or_else(|| {
format!(
"fleet `{fleet}`: member `{}` has no preflighted route",
member.id
)
})?;
Ok(ExactMemberBinding {
member_id: member.id.clone(),
member_role: member.role.clone(),
route: route.clone(),
requires_router: member.requested_reasoning.is_auto(),
authority: ChildAuthority::clamp_for_role(&member.role, member.permissions, session),
session,
})
}
fn lookup(&self, key: &str) -> Result<&FleetSnapshotMember, String> {
self.snapshot.member_by_id_or_role(key).ok_or_else(|| {
format!(
"unknown exact fleet member `{key}` in `{}`. Members: {}",
self.snapshot.fleet().qualified(),
self.member_names()
)
})
}
pub(crate) async fn route_admitted_task(
&self,
binding: &ExactMemberBinding,
task_summary: &str,
) -> Result<ExactMemberLaunch, String> {
self.snapshot
.verify_content_hash()
.map_err(|error| error.to_string())?;
let member = self.snapshot.member(&binding.member_id).ok_or_else(|| {
format!(
"fleet `{}`: member `{}` vanished between admission and launch",
self.snapshot.fleet().qualified(),
binding.member_id
)
})?;
let authority =
ChildAuthority::clamp_for_role(&member.role, member.permissions, binding.session);
if authority != binding.authority {
return Err(format!(
"fleet `{}`: member `{}` resolved a different permission envelope at launch than \
at admission, so the launch is refused. admitted={} launched={}",
self.snapshot.fleet().qualified(),
binding.member_id,
binding.authority.fingerprint(),
authority.fingerprint(),
));
}
let frozen = binding.route.frozen();
let capability = binding.route.capability;
let availability = self.router_availability();
let mut router_identity = None;
let mut routing_summary: Option<RoutingDisclosure> = None;
let decision = if binding.requires_router {
let router = self.router.as_ref().ok_or_else(|| {
format!(
"member `{}` requests reasoning `auto` but fleet `{}` has no usable reasoning \
router",
binding.member_id,
self.snapshot.fleet().qualified()
)
})?;
let cross_provider = self.preflight.crosses_providers(&binding.member_id);
let payload = bounded_routing_payload(task_summary).with_cross_provider(cross_provider);
routing_summary = Some(payload.disclosure().clone());
router_identity = Some(router.identity());
let input = RouterCallInput {
fleet: self.snapshot.fleet().qualified(),
member_id: binding.member_id.clone(),
frozen: frozen.clone(),
payload,
};
let raw = router.decide(&input).await.map_err(|error| {
format!(
"reasoning router call failed for member `{}`: {error}",
binding.member_id
)
})?;
Some(parse_router_decision(&raw).map_err(|error| {
format!(
"reasoning router returned an unusable decision for member `{}`: {error}",
binding.member_id
)
})?)
} else {
None
};
let reasoning = resolve_exact_member_reasoning(
&binding.member_id,
&frozen,
member.requested_reasoning,
&capability,
&availability,
decision.as_ref(),
router_identity.as_ref(),
)
.map_err(|error| error.to_string())?;
let thinking = match reasoning.effective() {
EffectiveReasoning::Tier(tier) => effort_of(tier).as_setting().to_string(),
EffectiveReasoning::NativeAdaptive => {
return Err(format!(
"member `{}` resolved to provider-native adaptive reasoning, which an exact \
fleet launch cannot place on a request. Pin an explicit reasoning tier.",
binding.member_id
));
}
};
let receipt = FleetTaskReceipt::new(
self.snapshot.fleet().qualified(),
self.snapshot.schema_kind(),
self.snapshot.schema_revision(),
self.snapshot.content_hash(),
binding.member_id.clone(),
binding.member_role.clone(),
&binding.route,
&reasoning,
routing_summary,
binding.authority.ceiling.network_tool,
)
.with_authority_fingerprint(authority.fingerprint())
.with_posture_role(binding.authority.posture_role);
Ok(ExactMemberLaunch {
member_id: binding.member_id.clone(),
member_role: binding.member_role.clone(),
provider: frozen.provider,
model: frozen.model,
thinking,
reasoning,
authority,
receipt,
})
}
fn router_availability(&self) -> RouterAvailability {
match (&self.router, &self.router_unavailable) {
(Some(_), _) => RouterAvailability::Ready,
(None, Some(reason)) => RouterAvailability::Unavailable {
reason: reason.clone(),
},
(None, None) => RouterAvailability::Absent,
}
}
}
fn exact_member_profile(
member: &FleetSnapshotMember,
route: Option<&PreflightedRoute>,
source: Option<&std::path::Path>,
) -> AgentProfile {
let posture_role = posture_role_for_member(&member.role, member.permissions);
let bounded_inspection_shell = matches!(posture_role, "scout" | "reviewer")
&& member.permissions.shell != ShellCeiling::None;
let wire_model = route.map_or_else(
|| member.route.model.clone(),
|route| route.wire_model.clone(),
);
let provider = route.map_or_else(
|| member.route.provider.clone(),
|route| route.provider_config_id().to_string(),
);
let profile = codewhale_config::FleetProfile {
slot: codewhale_config::FleetSlot::Custom(member.role.clone()),
role: codewhale_config::FleetRole {
name: posture_role.to_string(),
description: Some(format!("exact fleet member `{}`", member.id)),
instructions: None,
},
loadout: codewhale_config::FleetLoadout::Inherit,
model: Some(wire_model.clone()),
provider: Some(provider.clone()),
reasoning_effort: None,
permissions: codewhale_config::FleetProfilePermissions {
allow_shell: member.permissions.shell == ShellCeiling::Full || bounded_inspection_shell,
trust: false,
approval_required: true,
},
delegation: codewhale_config::FleetDelegationHints {
max_spawn_depth: Some(member.permissions.delegation_depth),
max_concurrency: None,
},
};
AgentProfile {
id: member.id.clone(),
display_name: Some(member.role.clone()),
description: Some(format!(
"Exact fleet member `{}` (role `{}`), pinned to {provider}/{wire_model}.",
member.id, member.role
)),
profile,
source: source
.map(std::path::Path::to_path_buf)
.unwrap_or_else(|| std::path::PathBuf::from("<exact fleet>")),
origin: ProfileOrigin::Config,
}
}
#[cfg(test)]
#[derive(Debug)]
pub(crate) struct StaticFleetRouter {
response: String,
identity: RouterIdentity,
pub(crate) seen: std::sync::Mutex<Vec<RouterCallInput>>,
}
#[cfg(test)]
impl StaticFleetRouter {
pub(crate) fn new(response: impl Into<String>) -> Arc<Self> {
Arc::new(Self {
response: response.into(),
identity: RouterIdentity {
id: "luna-low".to_string(),
origin: "workspace".to_string(),
service_kind: codewhale_workflow::REASONING_ROUTER_SERVICE_KIND.to_string(),
legacy_inline: false,
provider: "openai".to_string(),
model: "gpt-5.6-luna".to_string(),
endpoint: Some(EndpointIdentity::from_base_url("https://api.openai.com/v1")),
call: Some(
router_call_plan(
codewhale_workflow::RouterCallReasoning::Low,
&ReasoningCapability::tiered(),
)
.disclosure,
),
},
seen: std::sync::Mutex::new(Vec::new()),
})
}
pub(crate) fn call_count(&self) -> usize {
self.seen.lock().expect("router log").len()
}
}
#[cfg(test)]
#[async_trait]
impl FleetRouterCaller for StaticFleetRouter {
fn identity(&self) -> RouterIdentity {
self.identity.clone()
}
async fn decide(&self, input: &RouterCallInput) -> Result<String, String> {
self.seen.lock().expect("router log").push(input.clone());
Ok(self.response.clone())
}
}
#[cfg(test)]
impl ExactFleetWorkflow {
pub(crate) fn for_tests(
document: &FleetDocument,
id: QualifiedFleetId,
router: Option<Arc<StaticFleetRouter>>,
) -> Self {
Self::for_tests_with_capability(document, id, router, ReasoningCapability::tiered())
}
pub(crate) fn for_tests_with_capability(
document: &FleetDocument,
id: QualifiedFleetId,
router: Option<Arc<StaticFleetRouter>>,
capability: ReasoningCapability,
) -> Self {
let exact = document.exact().expect("exact fleet");
let captured = captured_legacy_inline_router(exact).or_else(|| {
exact.reasoning_router.as_ref().map(|name| {
CapturedReasoningRouter::from_profile(
&ReasoningRouterProfile::parse(&format!(
"name = \"{name}\"\nschema = \"reasoning_router\"\nprovider = \
\"openai\"\nmodel = \"gpt-5.6-luna\"\ncall_reasoning = \"low\"\n"
))
.expect("router profile"),
"workspace",
)
})
});
let snapshot =
FleetSnapshot::capture(id, document, "2026-07-26T00:00:00Z", captured.clone())
.expect("valid roster");
let workers = snapshot
.members()
.iter()
.map(|member| {
test_route(
&member.id,
&member.route.provider,
&member.route.model,
capability,
)
})
.collect::<Vec<_>>();
let router_route = captured.as_ref().map(|captured| {
test_route(
"router",
&captured.route.provider,
&captured.route.model,
capability,
)
});
let preflight = RoutePreflight::new(workers, router_route);
let roster = Arc::new(FleetRoster::from_members(
snapshot
.members()
.iter()
.map(|member| {
exact_member_profile(
member,
preflight.worker(&member.id),
document.source_path(),
)
})
.collect(),
));
Self {
snapshot: Arc::new(snapshot),
preflight: Arc::new(preflight),
roster,
router: router.map(|router| {
let router: Arc<dyn FleetRouterCaller> = router;
router
}),
router_unavailable: None,
}
}
pub(crate) fn for_tests_with_unavailable_router(
document: &FleetDocument,
id: QualifiedFleetId,
reason: &str,
) -> Result<Self, String> {
let mut workflow = Self::for_tests(document, id, None);
workflow.router_unavailable = Some(reason.to_string());
workflow.reject_unusable_auto_members()?;
Ok(workflow)
}
}
#[cfg(test)]
fn test_route(
member: &str,
provider: &str,
model: &str,
capability: ReasoningCapability,
) -> PreflightedRoute {
PreflightedRoute {
member_id: member.to_string(),
provider_id: provider.to_string(),
provider_config_id: None,
provider_kind: provider.to_string(),
declared_model: model.to_string(),
wire_model: model.to_string(),
endpoint: EndpointIdentity::from_base_url("https://api.example.test/v1"),
credential: CredentialReadiness::Configured,
capability,
}
}
#[cfg(test)]
mod shell_ceiling_tests {
use super::*;
fn ceiling(write: bool, shell: ShellCeiling) -> PermissionCeiling {
PermissionCeiling {
write,
network_tool: false,
shell,
delegation_depth: 0,
tools: true,
}
}
fn session() -> PermissionCeiling {
ceiling(true, ShellCeiling::Full)
}
fn denies_raw_shell(authority: &ChildAuthority) -> bool {
authority
.disallowed_tools
.iter()
.any(|rule| rule == RAW_SHELL_SENTINEL)
}
#[test]
fn a_shell_less_ceiling_installs_the_raw_shell_denial() {
for shell in [ShellCeiling::None, ShellCeiling::ReadOnly] {
let authority = ChildAuthority::clamp(ceiling(false, shell), session());
assert!(
denies_raw_shell(&authority),
"{shell:?} must deny raw shell"
);
}
}
#[test]
fn a_write_capable_member_clamped_to_no_shell_still_loses_raw_shell() {
let authority = ChildAuthority::clamp(
ceiling(true, ShellCeiling::Full),
ceiling(true, ShellCeiling::None),
);
assert_eq!(authority.ceiling.shell, ShellCeiling::None);
assert!(authority.ceiling.write, "the write half is unchanged");
assert!(denies_raw_shell(&authority));
}
#[test]
fn the_existing_verifier_and_full_ceilings_are_unchanged() {
let verifier = ChildAuthority::clamp(ceiling(false, ShellCeiling::Full), session());
assert!(denies_raw_shell(&verifier));
assert_eq!(verifier.posture_role, "verifier");
let full = ChildAuthority::clamp(ceiling(true, ShellCeiling::Full), session());
assert!(!denies_raw_shell(&full));
assert_eq!(full.posture_role, "builder");
}
#[test]
fn bounded_inspection_role_keeps_only_classifier_bounded_bash() {
for role in ["scout", "reviewer", "planner"] {
let authority = ChildAuthority::clamp_for_role(
role,
ceiling(false, ShellCeiling::ReadOnly),
session(),
);
assert!(
!authority
.disallowed_tools
.iter()
.any(|name| name.eq_ignore_ascii_case("Bash")),
"{role} keeps canonical Bash for per-input classification"
);
for denied in [
"exec_shell",
"task_shell_start",
"task_shell_wait",
"terminal/*",
"write_file",
"apply_patch",
] {
assert!(
authority.disallowed_tools.iter().any(|name| name == denied),
"{role} must still deny {denied}: {:?}",
authority.disallowed_tools
);
}
}
for role in ["consultant", "verifier"] {
let authority = ChildAuthority::clamp_for_role(
role,
ceiling(false, ShellCeiling::ReadOnly),
session(),
);
assert!(
authority
.disallowed_tools
.iter()
.any(|name| name.eq_ignore_ascii_case("Bash")),
"{role} must not gain the read-only inspection exception"
);
}
let parent_shell_off = ChildAuthority::clamp_for_role(
"scout",
ceiling(false, ShellCeiling::Full),
ceiling(true, ShellCeiling::None),
);
assert!(
parent_shell_off
.disallowed_tools
.iter()
.any(|name| name.eq_ignore_ascii_case("Bash")),
"a named Scout may not turn a parent shell-off ceiling into ReadOnly"
);
let planner_parent_shell_off = ChildAuthority::clamp_for_role(
"planner",
ceiling(false, ShellCeiling::ReadOnly),
ceiling(true, ShellCeiling::None),
);
assert!(
planner_parent_shell_off
.disallowed_tools
.iter()
.any(|name| name.eq_ignore_ascii_case("Bash")),
"a named planner may not turn a parent shell-off ceiling into ReadOnly"
);
assert_eq!(planner_parent_shell_off.posture_role, "planner");
assert_eq!(
session_shell_ceiling(crate::worker_profile::ShellPolicy::Full, false),
ShellCeiling::None
);
}
#[test]
fn the_shell_denial_is_visible_in_the_fingerprint() {
let no_shell = ChildAuthority::clamp(ceiling(false, ShellCeiling::None), session());
let full = ChildAuthority::clamp(ceiling(true, ShellCeiling::Full), session());
assert_ne!(no_shell.fingerprint(), full.fingerprint());
assert!(no_shell.fingerprint().contains("shell=none"));
}
#[test]
fn the_installed_shell_denials_are_posture_denials() {
let authority = ChildAuthority::clamp(ceiling(false, ShellCeiling::None), session());
for rule in &authority.disallowed_tools {
assert!(is_posture_denial(rule), "{rule} must be a posture denial");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use codewhale_workflow::{
EffectiveReasoningSource, ProviderEffectiveReasoning, RequestedReasoning,
};
const GLM_FLEET: &str = r#"
name = "glm-pair"
schema = "exact"
reasoning_router = "luna-low"
[[members]]
id = "implementer"
role = "builder"
provider = "zai"
model = "glm-5"
reasoning = "auto"
permissions = "read_write"
[[members]]
id = "auditor"
role = "reviewer"
provider = "zai"
model = "glm-5"
reasoning = "high"
permissions = "read_only"
"#;
fn id() -> QualifiedFleetId {
QualifiedFleetId {
name: "glm-pair".to_string(),
origin: "workspace".to_string(),
}
}
fn full_session() -> PermissionCeiling {
PermissionCeiling::preset("full").expect("preset")
}
fn workflow_with(router: Option<Arc<StaticFleetRouter>>, text: &str) -> ExactFleetWorkflow {
let document = FleetDocument::parse(text).expect("parse");
ExactFleetWorkflow::for_tests(&document, id(), router)
}
#[tokio::test]
async fn an_auto_member_takes_a_reasoning_only_router_decision_on_a_frozen_route() {
let router = StaticFleetRouter::new(r#"{"reasoning":"max"}"#);
let workflow = workflow_with(Some(router.clone()), GLM_FLEET);
let binding = workflow
.bind_member(None, Some("builder"), full_session())
.expect("role resolves");
assert_eq!(
router.call_count(),
0,
"binding a member must not cost a router call"
);
let launch = workflow
.route_admitted_task(&binding, "refactor three crates")
.await
.expect("auto resolves through the router");
assert_eq!(launch.provider, "zai");
assert_eq!(launch.model, "glm-5");
assert_eq!(launch.thinking, "max");
assert_eq!(launch.member_id, "implementer");
assert_eq!(launch.member_role, "builder");
assert_eq!(launch.reasoning.requested(), RequestedReasoning::Auto);
assert_eq!(
launch.reasoning.source(),
EffectiveReasoningSource::FleetRouter
);
let seen = router.seen.lock().expect("log");
assert_eq!(seen.len(), 1);
assert_eq!(seen[0].frozen.model, "glm-5");
assert_eq!(seen[0].member_id, "implementer");
assert_eq!(seen[0].payload.text(), "refactor three crates");
}
#[tokio::test]
async fn the_semantic_role_survives_while_the_id_addresses_the_roster() {
let workflow = workflow_with(
Some(StaticFleetRouter::new(r#"{"reasoning":"low"}"#)),
GLM_FLEET,
);
let binding = workflow
.bind_member(None, Some("reviewer"), full_session())
.expect("role lookup");
assert_eq!(binding.member_id, "auditor");
assert_eq!(binding.member_role, "reviewer");
let launch = workflow
.route_admitted_task(&binding, "read the diff")
.await
.expect("launch");
assert_eq!(launch.receipt.member_id, "auditor");
assert_eq!(
launch.receipt.member_role, "reviewer",
"the receipt records the semantic role, not the profile id"
);
let entry = workflow.roster().get("auditor").expect("roster entry");
assert_eq!(entry.display_name.as_deref(), Some("reviewer"));
}
#[test]
fn a_conflicting_task_role_and_profile_is_rejected() {
let workflow = workflow_with(None, GLM_FLEET);
let err = workflow
.bind_member(Some("implementer"), Some("reviewer"), full_session())
.expect_err("conflicting identity");
assert!(err.contains("different members"), "{err}");
assert!(err.contains("implementer"), "{err}");
assert!(err.contains("auditor"), "{err}");
let binding = workflow
.bind_member(Some("implementer"), Some("builder"), full_session())
.expect("agreeing identity");
assert_eq!(binding.member_id, "implementer");
}
#[tokio::test]
async fn an_explicit_tier_member_never_calls_the_router() {
let router = StaticFleetRouter::new(r#"{"reasoning":"off"}"#);
let workflow = workflow_with(Some(router.clone()), GLM_FLEET);
let binding = workflow
.bind_member(Some("auditor"), None, full_session())
.expect("bind");
assert!(!binding.requires_router);
let launch = workflow
.route_admitted_task(&binding, "read the diff")
.await
.expect("explicit tier");
assert_eq!(launch.thinking, "high");
assert_eq!(
launch.reasoning.source(),
EffectiveReasoningSource::MemberExplicit
);
assert_eq!(
router.call_count(),
0,
"an explicit tier must not spend a router call"
);
assert!(launch.receipt.router.is_none());
assert!(launch.receipt.routing_summary.is_none());
assert!(!launch.receipt.cross_provider_inference);
}
#[test]
fn a_task_that_is_never_admitted_costs_no_router_call() {
let router = StaticFleetRouter::new(r#"{"reasoning":"max"}"#);
let workflow = workflow_with(Some(router.clone()), GLM_FLEET);
assert!(
workflow
.bind_member(None, Some("wizard"), full_session())
.is_err()
);
assert!(
workflow
.bind_member(Some("implementer"), Some("reviewer"), full_session())
.is_err()
);
let _binding = workflow
.bind_member(None, Some("builder"), full_session())
.expect("valid binding");
assert_eq!(
router.call_count(),
0,
"no router call may happen before a task is admitted"
);
}
#[tokio::test]
async fn a_router_that_tries_to_move_the_route_fails_the_launch() {
let workflow = workflow_with(
Some(StaticFleetRouter::new(
r#"{"reasoning":"max","model":"glm-4"}"#,
)),
GLM_FLEET,
);
let binding = workflow
.bind_member(None, Some("builder"), full_session())
.expect("bind");
let err = workflow
.route_admitted_task(&binding, "anything")
.await
.expect_err("a route mutation must fail the launch");
assert!(err.contains("frozen"), "{err}");
}
#[tokio::test]
async fn a_duplicate_reasoning_key_fails_the_launch() {
let workflow = workflow_with(
Some(StaticFleetRouter::new(
r#"{"reasoning":"off","reasoning":"max"}"#,
)),
GLM_FLEET,
);
let binding = workflow
.bind_member(None, Some("builder"), full_session())
.expect("bind");
let err = workflow
.route_admitted_task(&binding, "anything")
.await
.expect_err("duplicate key");
assert!(err.contains("more than once"), "{err}");
}
#[test]
fn a_missing_router_fails_before_any_worker_is_dispatched() {
let router_less = GLM_FLEET.replace("reasoning_router = \"luna-low\"\n", "");
let document = FleetDocument::parse(&router_less).expect("parse");
let workflow = ExactFleetWorkflow::for_tests(&document, id(), None);
let err = workflow
.reject_unusable_auto_members()
.expect_err("auto without a router must not start");
assert!(err.contains("implementer"), "{err}");
assert!(err.contains("reasoning router"), "{err}");
assert!(
err.contains("never fall back"),
"the error must rule out legacy fallback: {err}"
);
}
#[test]
fn a_fleet_with_no_auto_member_starts_without_a_router() {
let text = r#"
name = "pinned"
schema = "exact"
[[members]]
id = "auditor"
provider = "zai"
model = "glm-5"
reasoning = "high"
permissions = "read_only"
"#;
let document = FleetDocument::parse(text).expect("parse");
let workflow = ExactFleetWorkflow::for_tests(
&document,
QualifiedFleetId {
name: "pinned".to_string(),
origin: "workspace".to_string(),
},
None,
);
workflow
.reject_unusable_auto_members()
.expect("no auto member means no router requirement");
assert_eq!(workflow.snapshot().members().len(), 1);
}
#[test]
fn a_locally_unusable_router_fails_before_any_worker_is_dispatched() {
let document = FleetDocument::parse(GLM_FLEET).expect("parse");
let err = ExactFleetWorkflow::for_tests_with_unavailable_router(
&document,
id(),
"no credential configured for `openai`",
)
.expect_err("an unusable router must not start an auto fleet");
assert!(err.contains("cannot start"), "{err}");
assert!(err.contains("implementer"), "{err}");
assert!(err.contains("no credential configured"), "{err}");
assert!(err.contains("never fall back"), "{err}");
}
#[test]
fn the_projected_roster_pins_each_members_exact_provider_and_model() {
let workflow = workflow_with(None, GLM_FLEET);
let member = workflow.roster().get("implementer").expect("roster member");
assert_eq!(member.profile.provider.as_deref(), Some("zai"));
assert_eq!(member.profile.model.as_deref(), Some("glm-5"));
assert_eq!(
member.profile.reasoning_effort, None,
"reasoning is decided per task, not baked into the projected profile"
);
}
#[test]
fn a_read_only_member_projects_a_read_oriented_posture() {
let workflow = workflow_with(None, GLM_FLEET);
let auditor = workflow.roster().get("auditor").expect("auditor");
assert_eq!(auditor.profile.role.name, "scout");
assert!(
auditor.profile.permissions.allow_shell,
"the projected Scout needs Bash registration for its typed read-only subset"
);
assert_eq!(
write_authority_for(workflow.member("auditor").expect("member").permissions),
"read_only"
);
let implementer = workflow.roster().get("implementer").expect("implementer");
assert_eq!(implementer.profile.role.name, "builder");
}
#[test]
fn a_members_posture_role_can_never_be_wider_than_its_ceiling() {
let read_only = PermissionCeiling::preset("read_only").expect("preset");
let read_write = PermissionCeiling::preset("read_write").expect("preset");
assert_eq!(posture_role_for_member("reviewer", read_only), "scout");
assert_eq!(posture_role_for_member("reviewer", read_write), "reviewer");
assert_eq!(posture_role_for_member("planner", read_only), "planner");
let analyst = PermissionCeiling::preset("analyst").expect("preset");
assert_eq!(
posture_role_for_member("planner", analyst),
"planner",
"a named planner under a no-shell ceiling stays planner; shell intersects"
);
assert_eq!(
posture_role_for_member("consultant", read_only),
"consultant"
);
assert_eq!(posture_role_for_member("oracle", read_only), "consultant");
assert_eq!(posture_role_for_member("verifier", read_only), "scout");
assert_eq!(
posture_role_for_member("verifier", read_write),
"verifier",
"a full-shell ceiling does permit it"
);
assert_eq!(posture_role_for_member("builder", read_only), "scout");
assert_eq!(posture_role_for_member("worker", read_only), "scout");
assert_eq!(posture_role_for_member("builder", read_write), "builder");
assert_eq!(posture_role_for_member("auditor", read_only), "scout");
assert_eq!(posture_role_for_member("auditor", read_write), "builder");
assert_eq!(posture_role_for_member("custom", read_write), "builder");
assert_eq!(
posture_role_for_member("builder", PermissionCeiling::ROUTER),
"scout"
);
}
#[test]
fn a_preserved_posture_role_resolves_to_its_runtime_agent_type() {
use crate::tools::subagent::FleetRole;
let workflow = workflow_with(None, GLM_FLEET);
for (id, expected) in [
("auditor", FleetRole::Scout),
("implementer", FleetRole::Builder),
] {
let member = workflow.roster().get(id).expect("roster entry");
assert_eq!(
crate::fleet::worker_runtime::roster_member_agent_type(member),
expected,
"{id} must resolve to the role its projected posture names"
);
}
}
#[test]
fn tools_false_yields_an_empty_tool_surface() {
let authority = ChildAuthority::clamp(PermissionCeiling::ROUTER, full_session());
assert!(!authority.ceiling.tools);
assert_eq!(
authority.allowed_tools.as_deref(),
Some(&[] as &[String]),
"tools = false must be an empty allowlist, not an absent one"
);
assert_eq!(authority.write_authority, "read_only");
assert_eq!(authority.max_depth, 0);
}
#[test]
fn network_disabled_denies_every_network_surface_even_with_tools_enabled() {
let member = PermissionCeiling::preset("read_write").expect("preset");
assert!(member.tools);
assert!(!member.network_tool);
let authority = ChildAuthority::clamp(member, full_session());
assert!(
authority.allowed_tools.is_none(),
"a tool-using member keeps full inheritance, narrowed by the deny list"
);
for expected in [
"web.run",
"web_run",
"web_search",
"fetch_url",
"wait_for_dev_server",
"github",
"mcp*",
] {
assert!(
authority
.disallowed_tools
.iter()
.any(|name| name == expected),
"{expected} must be denied: {:?}",
authority.disallowed_tools
);
}
assert!(
!authority.disallowed_tools.iter().any(|name| name == "Web"),
"the Web family name must survive so search/fetch stay reachable: {:?}",
authority.disallowed_tools
);
let networked = ChildAuthority::clamp(
PermissionCeiling::preset("full").expect("preset"),
full_session(),
);
assert!(networked.ceiling.network_tool);
assert!(networked.disallowed_tools.is_empty());
}
#[test]
fn network_disabled_denies_the_canonical_web_run_surface_and_its_aliases() {
let authority = ChildAuthority::clamp(
PermissionCeiling::preset("read_write").expect("preset"),
full_session(),
);
let denied = |name: &str| {
let lowered = name.to_ascii_lowercase();
authority.disallowed_tools.iter().any(|rule| {
let rule = rule.to_ascii_lowercase();
rule.strip_suffix('*')
.map_or(rule == lowered, |prefix| lowered.starts_with(prefix))
})
};
for name in [
"web.run",
"web_run",
"web_search",
"web.fetch",
"web_fetch",
"fetch_url",
"wait_for_dev_server",
"browse",
"browser",
] {
assert!(
denied(name),
"{name} must be denied: {:?}",
authority.disallowed_tools
);
}
assert!(
!denied("Web"),
"the Web family name must survive a network denial: {:?}",
authority.disallowed_tools
);
for name in ["read_file", "run_tests", "Git", "grep_files"] {
assert!(!denied(name), "{name} is not a network surface");
}
}
#[test]
fn network_disabled_denies_the_in_process_rlm_reach() {
let authority = ChildAuthority::clamp(
PermissionCeiling::preset("read_write").expect("preset"),
full_session(),
);
let denied = |name: &str| {
let lowered = name.to_ascii_lowercase();
authority.disallowed_tools.iter().any(|rule| {
let rule = rule.to_ascii_lowercase();
rule.strip_suffix('*')
.map_or(rule == lowered, |prefix| lowered.starts_with(prefix))
})
};
for reaching in ["rlm_open", "rlm_eval"] {
assert!(
denied(reaching),
"{reaching} reaches the network in-process and must be denied: {:?}",
authority.disallowed_tools
);
}
for kept in ["rlm", "rlm_session_objects", "rlm_configure", "rlm_close"] {
assert!(
!denied(kept),
"{kept} is bounded local metadata and must survive a network denial"
);
}
}
#[test]
fn the_network_denial_sentinel_is_installed_by_a_network_denial() {
assert!(
NETWORK_TOOL_DENYLIST.contains(&NETWORK_DENIAL_SENTINEL),
"{NETWORK_DENIAL_SENTINEL} must be an explicit entry, not a glob match"
);
let authority = ChildAuthority::clamp(
PermissionCeiling::preset("read_write").expect("preset"),
full_session(),
);
assert!(
authority
.disallowed_tools
.iter()
.any(|rule| rule == NETWORK_DENIAL_SENTINEL),
"a network denial must install the sentinel verbatim: {:?}",
authority.disallowed_tools
);
let networked = ChildAuthority::clamp(
PermissionCeiling::preset("full").expect("preset"),
full_session(),
);
assert!(
!networked
.disallowed_tools
.iter()
.any(|rule| rule == NETWORK_DENIAL_SENTINEL)
);
}
#[test]
fn every_network_denial_leaves_web_search_reachable_by_family_name() {
for preset in ["analyst", "read_only", "verifier", "read_write"] {
let authority = ChildAuthority::clamp(
PermissionCeiling::preset(preset).expect("preset"),
full_session(),
);
assert!(
!authority.ceiling.network_tool,
"{preset} is network-denied"
);
assert!(
!authority.disallowed_tools.iter().any(|rule| rule == "Web"),
"{preset} must keep the Web family name: {:?}",
authority.disallowed_tools
);
for sealed in [
"web_*",
"web.*",
"web.run",
"web_run",
"web_search",
"web.fetch",
"web_fetch",
"fetch_url",
"wait_for_dev_server",
"github",
"mcp*",
] {
assert!(
authority.disallowed_tools.iter().any(|rule| rule == sealed),
"{preset} must deny {sealed}: {:?}",
authority.disallowed_tools
);
}
}
}
#[test]
fn a_read_only_member_gets_a_truthful_non_mutating_tool_contract() {
let verifier = PermissionCeiling::preset("verifier").expect("preset");
assert!(!verifier.write);
assert_eq!(verifier.shell, ShellCeiling::Full);
let authority = ChildAuthority::clamp(verifier, full_session());
assert_eq!(authority.write_authority, "read_only");
let denied = |name: &str| {
authority.disallowed_tools.iter().any(|rule| {
rule == name || rule.strip_suffix('*').is_some_and(|p| name.starts_with(p))
})
};
for mutating in [
"write_file",
"edit_file",
"apply_patch",
"fim_edit",
"rlm_eval",
] {
assert!(
denied(mutating),
"{mutating} must be denied for a read-only member: {:?}",
authority.disallowed_tools
);
}
for raw_shell in [
"Bash",
"exec_shell",
"exec_shell_interact",
"task_shell_start",
"terminal/run",
] {
assert!(
denied(raw_shell),
"{raw_shell} is a general mutation primitive: {:?}",
authority.disallowed_tools
);
}
for kept in [
"Run",
"run_tests",
"run_verifiers",
"read_file",
"grep_files",
"rlm",
] {
assert!(!denied(kept), "{kept} must stay available to a verifier");
}
let builder = ChildAuthority::clamp(
PermissionCeiling::preset("read_write").expect("preset"),
full_session(),
);
assert!(builder.ceiling.write);
for kept in ["write_file", "apply_patch", "exec_shell"] {
assert!(
!builder.disallowed_tools.iter().any(|rule| rule == kept),
"{kept} must stay available to a write-capable member"
);
}
}
#[test]
fn a_write_denial_alone_keeps_local_rlm_loading() {
let member = PermissionCeiling {
write: false,
network_tool: true,
shell: ShellCeiling::ReadOnly,
delegation_depth: 0,
tools: true,
};
let authority = ChildAuthority::clamp(member, full_session());
assert!(!authority.ceiling.write);
assert!(authority.ceiling.network_tool);
let denied = |name: &str| authority.disallowed_tools.iter().any(|rule| rule == name);
assert!(denied("rlm_eval"), "eval runs code, so it mutates");
for kept in ["rlm", "rlm_open", "rlm_session_objects", "rlm_close"] {
assert!(
!denied(kept),
"{kept} loads and inspects; it does not mutate: {:?}",
authority.disallowed_tools
);
}
}
#[test]
fn the_parent_ceiling_wins_over_a_wider_saved_member() {
let session = PermissionCeiling {
write: false,
network_tool: false,
shell: ShellCeiling::ReadOnly,
delegation_depth: 0,
tools: true,
};
let member = PermissionCeiling::preset("full").expect("preset");
assert!(member.write && member.network_tool);
let authority = ChildAuthority::clamp(member, session);
assert!(!authority.ceiling.write, "a fleet may not grant write");
assert!(
!authority.ceiling.network_tool,
"a fleet may not grant a network tool"
);
assert_eq!(authority.ceiling.shell, ShellCeiling::ReadOnly);
assert_eq!(authority.ceiling.delegation_depth, 0);
assert_eq!(authority.write_authority, "read_only");
assert_eq!(authority.max_depth, 0);
assert_eq!(authority.posture_role, "scout");
assert!(!authority.disallowed_tools.is_empty());
}
#[test]
fn clamping_takes_the_narrower_side_of_every_field() {
let narrow_member = PermissionCeiling {
write: false,
network_tool: false,
shell: ShellCeiling::None,
delegation_depth: 0,
tools: true,
};
let authority = ChildAuthority::clamp(narrow_member, full_session());
assert!(!authority.ceiling.write);
assert_eq!(authority.ceiling.shell, ShellCeiling::None);
assert_eq!(authority.ceiling.delegation_depth, 0);
}
#[test]
fn glm_routes_report_an_enabled_disabled_provider_control() {
let capability = reasoning_capability_for_route(
ApiProvider::Zai,
crate::config::DEFAULT_ZAI_BASE_URL,
crate::config::ZAI_GLM_5_2_MODEL,
);
assert_eq!(
capability.control,
ProviderReasoningControl::EnabledDisabled,
"Z.AI's request shaping emits only thinking enabled/disabled"
);
assert!(!capability.supports_native_adaptive());
assert_eq!(
capability.provider_effective(ReasoningTier::High),
ProviderEffectiveReasoning::Enabled
);
assert_eq!(
capability.provider_effective(ReasoningTier::Off),
ProviderEffectiveReasoning::Disabled
);
}
#[test]
fn a_route_that_varies_its_wire_value_reports_distinct_tiers() {
let capability = reasoning_capability_for_route(
ApiProvider::Deepseek,
crate::config::DEFAULT_DEEPSEEK_BASE_URL,
"deepseek-v4-pro",
);
assert_eq!(capability.control, ProviderReasoningControl::Tiers);
}
#[test]
fn a_deepseek_route_reports_low_as_low_and_medium_as_high() {
let capability = reasoning_capability_for_route(
ApiProvider::Deepseek,
crate::config::DEFAULT_DEEPSEEK_BASE_URL,
"deepseek-v4-pro",
);
for (requested, expected) in [
(ReasoningTier::Low, ReasoningTier::Low),
(ReasoningTier::Medium, ReasoningTier::High),
(ReasoningTier::High, ReasoningTier::High),
(ReasoningTier::Max, ReasoningTier::Max),
(ReasoningTier::Off, ReasoningTier::Off),
] {
assert_eq!(
capability.wire_tier(requested),
expected,
"requested {requested:?} must be reported as what the wire carries"
);
let (effective, normalized) = capability.normalize(requested);
assert_eq!(effective, expected);
assert_eq!(normalized, requested != expected);
}
let resolved = codewhale_workflow::resolve_exact_member_reasoning(
"implementer",
&codewhale_workflow::FrozenRoute {
provider: "deepseek".to_string(),
model: "deepseek-v4-pro".to_string(),
},
RequestedReasoning::Low,
&capability,
&RouterAvailability::Absent,
None,
None,
)
.expect("resolve");
assert_eq!(resolved.requested(), RequestedReasoning::Low);
assert_eq!(
resolved.effective(),
codewhale_workflow::EffectiveReasoning::Tier(ReasoningTier::Low)
);
assert!(!resolved.capability_normalized());
}
#[test]
fn a_route_that_collapses_low_onto_high_says_so_instead_of_reporting_low() {
let capability = reasoning_capability_for_route(
ApiProvider::Siliconflow,
crate::config::DEFAULT_SILICONFLOW_BASE_URL,
"deepseek-ai/DeepSeek-V4-Pro",
);
for (requested, expected) in [
(ReasoningTier::Low, ReasoningTier::High),
(ReasoningTier::Medium, ReasoningTier::High),
(ReasoningTier::High, ReasoningTier::High),
(ReasoningTier::Max, ReasoningTier::Max),
(ReasoningTier::Off, ReasoningTier::Off),
] {
assert_eq!(
capability.wire_tier(requested),
expected,
"requested {requested:?} must be reported as what the wire carries"
);
let (effective, normalized) = capability.normalize(requested);
assert_eq!(effective, expected);
assert_eq!(normalized, requested != expected);
}
}
#[test]
fn preflight_freezes_provider_model_endpoint_and_local_readiness() {
let _env_lock = crate::test_support::lock_test_env();
let _key = crate::test_support::EnvVarGuard::set("ZAI_API_KEY", "zai-key");
let config = Config {
provider: Some("zai".to_string()),
..Default::default()
};
let route = preflight_route(
"implementer",
"zai",
crate::config::ZAI_GLM_5_2_MODEL,
&config,
)
.expect("preflight");
assert_eq!(route.member_id, "implementer");
assert_eq!(route.provider_kind, "zai");
assert_eq!(route.wire_model, crate::config::ZAI_GLM_5_2_MODEL);
assert!(!route.endpoint.host.is_empty());
assert!(!route.endpoint.host.contains('/'));
assert_eq!(route.credential, CredentialReadiness::Configured);
route.require_ready().expect("ready");
assert_eq!(route.frozen().model, route.wire_model);
}
#[test]
fn a_keyless_local_provider_preflights_as_ready() {
let _env_lock = crate::test_support::lock_test_env();
let config = Config {
provider: Some("ollama".to_string()),
..Default::default()
};
let Ok(route) = preflight_route("worker", "ollama", "qwen3", &config) else {
return;
};
assert_eq!(route.credential, CredentialReadiness::KeylessLocal);
assert!(route.credential.is_ready());
route.require_ready().expect("keyless local is valid");
assert!(route.endpoint.local, "a local runtime is marked local");
}
#[test]
fn ollama_cloud_and_custom_remote_preflight_require_route_scoped_credentials() {
let _env_lock = crate::test_support::lock_test_env();
let temp = tempfile::tempdir().expect("isolated credential home");
let _home = crate::test_support::EnvVarGuard::set("CODEWHALE_HOME", temp.path());
let _backend = crate::test_support::EnvVarGuard::set("CODEWHALE_SECRET_BACKEND", "file");
let _ollama_cloud_key = crate::test_support::EnvVarGuard::remove("OLLAMA_CLOUD_API_KEY");
let _ollama_key = crate::test_support::EnvVarGuard::remove("OLLAMA_API_KEY");
let _cli_source = crate::test_support::EnvVarGuard::remove("DEEPSEEK_API_KEY_SOURCE");
let _cli_key = crate::test_support::EnvVarGuard::remove("CODEWHALE_CLI_API_KEY");
codewhale_secrets::Secrets::auto_detect()
.set("ollama", "legacy-cloud-key")
.expect("seed released Ollama Cloud slot");
let cloud = Config {
provider: Some("deepseek".to_string()),
providers: Some(crate::config::ProvidersConfig {
ollama: crate::config::ProviderConfig {
base_url: Some(codewhale_config::provider::OLLAMA_CLOUD_BASE_URL.to_string()),
..Default::default()
},
..Default::default()
}),
..Default::default()
};
let cloud_route = preflight_route(
"cloud-worker",
"ollama",
crate::config::DEFAULT_OLLAMA_MODEL,
&cloud,
)
.expect("official Cloud route");
assert_eq!(cloud_route.provider_id, "ollama-cloud");
assert_eq!(cloud_route.provider_config_id.as_deref(), Some("ollama"));
assert_eq!(cloud_route.provider_kind, "ollama-cloud");
assert_eq!(cloud_route.credential, CredentialReadiness::Configured);
assert!(!cloud_route.endpoint.local);
cloud_route.require_ready().expect("Cloud env key is ready");
let custom_remote = Config {
provider: Some("ollama".to_string()),
providers: Some(crate::config::ProvidersConfig {
ollama: crate::config::ProviderConfig {
base_url: Some("https://ollama-gateway.example.test/v1".to_string()),
..Default::default()
},
..Default::default()
}),
..Default::default()
};
let custom_route = preflight_route(
"custom-worker",
"ollama",
crate::config::DEFAULT_OLLAMA_MODEL,
&custom_remote,
)
.expect("custom route still resolves structurally");
assert!(matches!(
custom_route.credential,
CredentialReadiness::Missing { .. }
));
assert!(!custom_route.endpoint.local);
assert!(custom_route.require_ready().is_err());
}
#[tokio::test]
async fn legacy_ollama_cloud_fleet_start_builds_clients_from_the_frozen_source_route() {
let _env_lock = crate::test_support::lock_test_env();
let temp = tempfile::tempdir().expect("isolated credential home");
let _home = crate::test_support::EnvVarGuard::set("CODEWHALE_HOME", temp.path());
let _backend = crate::test_support::EnvVarGuard::set("CODEWHALE_SECRET_BACKEND", "file");
let _cloud_env = crate::test_support::EnvVarGuard::remove("OLLAMA_CLOUD_API_KEY");
let _official_env = crate::test_support::EnvVarGuard::remove("OLLAMA_API_KEY");
codewhale_secrets::Secrets::auto_detect()
.set("ollama", "legacy-cloud-fleet-key")
.expect("seed released Ollama Cloud slot");
let config = Config {
provider: Some("deepseek".to_string()),
providers: Some(crate::config::ProvidersConfig {
ollama: crate::config::ProviderConfig {
base_url: Some(codewhale_config::provider::OLLAMA_CLOUD_BASE_URL.to_string()),
model: Some(crate::config::DEFAULT_OLLAMA_CLOUD_MODEL.to_string()),
..Default::default()
},
..Default::default()
}),
..Default::default()
};
let document = FleetDocument::parse(&format!(
r#"
name = "glm-pair"
schema = "exact"
[[members]]
id = "cloud-worker"
role = "builder"
provider = "ollama"
model = "{}"
reasoning = "medium"
permissions = "read_only"
"#,
crate::config::DEFAULT_OLLAMA_CLOUD_MODEL
))
.expect("legacy Cloud fleet parses");
let workflow = ExactFleetWorkflow::capture(
&document,
id(),
"2026-08-14T00:00:00Z",
Some(&config),
&[],
)
.expect("legacy Cloud fleet starts");
let route = workflow
.preflight
.worker("cloud-worker")
.expect("preflighted worker");
assert_eq!(route.provider_id, "ollama-cloud");
assert_eq!(route.provider_config_id.as_deref(), Some("ollama"));
assert_eq!(
workflow
.roster()
.get("cloud-worker")
.and_then(|profile| profile.profile.provider.as_deref()),
Some("ollama"),
"the child pin must rebuild the legacy table/slot even though receipts are canonical"
);
let binding = workflow
.bind_member(Some("cloud-worker"), None, full_session())
.expect("worker binds");
let launch = workflow
.route_admitted_task(&binding, "verify the frozen Cloud route")
.await
.expect("manual-tier launch needs no provider call");
assert_eq!(launch.provider, "ollama-cloud");
assert_eq!(launch.receipt.provider, "ollama-cloud");
let router_profile = ReasoningRouterProfile::parse(&format!(
r#"
name = "legacy-cloud-router"
schema = "reasoning_router"
provider = "ollama"
model = "{}"
call_reasoning = "low"
"#,
crate::config::DEFAULT_OLLAMA_CLOUD_MODEL
))
.expect("legacy Cloud router profile parses");
let captured =
CapturedReasoningRouter::from_profile(&router_profile, "workspace".to_string());
let live = LiveFleetRouter::bind(&captured, &config)
.expect("legacy Cloud Router binds its source table and secret");
assert_eq!(live.route.provider_id, "ollama-cloud");
assert_eq!(live.route.provider_config_id.as_deref(), Some("ollama"));
assert_eq!(live.client.api_provider(), ApiProvider::OllamaCloud);
assert_eq!(
live.client.base_url(),
codewhale_config::provider::OLLAMA_CLOUD_BASE_URL
);
}
#[test]
fn a_call_reasoning_value_is_shaped_by_the_configured_route_not_a_tier_label() {
for (tier, expected) in [
(ReasoningTier::Off, "off"),
(ReasoningTier::High, "high"),
(ReasoningTier::Max, "max"),
] {
assert_eq!(
route_reasoning_setting(
ApiProvider::Deepseek,
crate::config::DEFAULT_DEEPSEEK_BASE_URL,
"deepseek-v4-pro",
tier,
),
expected,
"{tier:?} on a deepseek route"
);
}
let codex = |tier| {
route_reasoning_setting(
ApiProvider::OpenaiCodex,
"https://chatgpt.com/backend-api/codex",
"gpt-5.6-codex",
tier,
)
};
assert_eq!(codex(ReasoningTier::Max), "xhigh");
assert_eq!(codex(ReasoningTier::Low), "low");
assert_ne!(
codex(ReasoningTier::Off),
"off",
"an always-thinking route cannot be asked for `off`; sending the label \
would take the provider default while the receipt claimed a tier"
);
}
#[test]
fn an_unresolvable_provider_fails_preflight() {
let config = Config::default();
let err = preflight_route("implementer", "not-a-provider", "whatever", &config)
.expect_err("unresolvable provider");
assert!(matches!(err, PreflightError::ProviderUnresolved { .. }));
}
#[tokio::test]
async fn a_launch_receipt_names_the_service_route_and_call_cost_without_content() {
let workflow = workflow_with(
Some(StaticFleetRouter::new(r#"{"reasoning":"max"}"#)),
GLM_FLEET,
);
let binding = workflow
.bind_member(None, Some("builder"), full_session())
.expect("bind");
let launch = workflow
.route_admitted_task(&binding, "refactor /Users/hunter/app with ZAI_API_KEY=zzz")
.await
.expect("launch");
let receipt = &launch.receipt;
assert_eq!(receipt.fleet, "workspace/glm-pair");
assert_eq!(receipt.schema_kind, "exact");
assert_eq!(receipt.member_id, "implementer");
assert_eq!(receipt.member_role, "builder");
assert_eq!(receipt.provider, "zai");
assert_eq!(receipt.model, "glm-5");
assert_eq!(receipt.requested_reasoning, "auto");
assert_eq!(receipt.effective_reasoning, "max");
assert_eq!(receipt.selection_source, "fleet_router");
assert!(!receipt.content_hash.is_empty());
let router = receipt.router.as_ref().expect("router identity");
assert_eq!(router.service_kind, "reasoning_router");
assert_eq!(router.qualified(), "workspace/luna-low");
assert_eq!(router.provider, "openai");
assert_eq!(router.model, "gpt-5.6-luna");
let call = router.call.as_ref().expect("call disclosure");
assert_eq!(call.requested, "low");
assert_eq!(call.effective, "low");
assert_eq!(call.provider_effective, "low");
assert!(receipt.cross_provider_inference);
assert!(
receipt.transport.contains("different provider"),
"{}",
receipt.transport
);
let disclosure = receipt.routing_summary.as_ref().expect("disclosure");
assert!(disclosure.transmitted_bytes > 0);
assert!(disclosure.content_hash.starts_with("sha256:"));
assert!(disclosure.redacted);
let json = serde_json::to_string(receipt).expect("serialize");
for forbidden in ["/Users/", "/home/", ".toml", "api_key", "zzz", "refactor"] {
assert!(!json.contains(forbidden), "{forbidden} in {json}");
}
let line = receipt.line();
for expected in [
"requested=auto",
"effective=max",
"source=fleet_router",
"reasoning_router:workspace/luna-low",
"router_call_requested=low",
] {
assert!(line.contains(expected), "{expected} missing from {line}");
}
assert!(!line.contains("refactor"), "{line}");
}
#[tokio::test]
async fn a_receipt_records_the_posture_without_renaming_the_members_role() {
const AUDIT_FLEET: &str = r#"
name = "glm-pair"
schema = "exact"
[[members]]
id = "auditor"
role = "auditor"
provider = "zai"
model = "glm-5"
reasoning = "high"
permissions = "read_only"
"#;
let workflow = workflow_with(None, AUDIT_FLEET);
let binding = workflow
.bind_member(None, Some("auditor"), full_session())
.expect("bind");
assert_eq!(binding.member_role, "auditor");
assert_eq!(binding.authority.posture_role, "scout");
let launch = workflow
.route_admitted_task(&binding, "review the queue")
.await
.expect("launch");
let receipt = &launch.receipt;
assert_eq!(receipt.member_role, "auditor");
assert_eq!(receipt.posture_role.as_deref(), Some("scout"));
let line = receipt.line();
assert!(line.contains("(role auditor)"), "{line}");
assert!(line.contains("posture=scout"), "{line}");
}
}