fix(planner): the other four server-side calls were still on the metered key
The test that was supposed to prevent this grepped for the literal `runtime.complete(` and passed while the phase planner (`mission_plan.rs`), both swarm calls, and a second enhance path in `claws.rs` still billed the pay-as-you-go account. They spell the receiver `state.runtime` or wrap the call across lines, so the receiver name was never the thing to match. The test now matches the METHOD, and covers all five files. `complete_or` gains the rule that makes it safe to apply everywhere: a `name:model` spec is an operator's explicit provider choice — the swarm worker model is configured exactly that way — and is passed straight to `Runtime::resolve_provider` untouched. Only a bare name is ambiguous, and a bare name is precisely what resolves to the default provider. Hijacking a chosen Kimi or GLM model onto Anthropic would be the same silent-substitution bug pointed the other way. `validator_preflight` and the evaluator judge keep calling the runtime directly, on purpose: both exist to exercise the CONFIGURED spec. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
deed591da6
commit
ee5a939ce6
@@ -68,14 +68,27 @@ pub async fn complete_or(
|
||||
// web search away from two features while every test still passed.
|
||||
web_search: bool,
|
||||
) -> Result<String, String> {
|
||||
let Some(provider) = provider() else {
|
||||
// A `name:model` spec is an operator's explicit provider choice — the swarm
|
||||
// worker model is literally configured that way (`kimi:kimi-k2.6`), and
|
||||
// `Runtime::resolve_provider` honours it. Forcing that onto Anthropic would
|
||||
// silently run someone's chosen model on the wrong provider, which is the
|
||||
// same class of bug as this module exists to fix, only pointed the other
|
||||
// way. Only a BARE name is ambiguous, and a bare name is what resolves to
|
||||
// the default provider — the metered key.
|
||||
if !is_bare_model_name(model) || provider().is_none() {
|
||||
return runtime
|
||||
.complete(system, user, model, max_tokens, web_search)
|
||||
.await;
|
||||
};
|
||||
}
|
||||
let provider = provider().expect("checked just above");
|
||||
complete_with(&provider, system, user, model, max_tokens, web_search).await
|
||||
}
|
||||
|
||||
/// Whether a model string names a model without naming a provider.
|
||||
pub fn is_bare_model_name(model: &str) -> bool {
|
||||
!model.contains(':')
|
||||
}
|
||||
|
||||
/// How long to wait before each retry. Four attempts, ~30s of patience total.
|
||||
///
|
||||
/// The subscription has no credit wall, but it does have a rate limit, and a
|
||||
@@ -205,19 +218,27 @@ mod tests {
|
||||
/// spec an operator configured (today `glm:glm-4.7`), and forcing it onto
|
||||
/// Anthropic would make it prove the wrong thing — it exists to answer "is
|
||||
/// the configured validator reachable".
|
||||
/// The first version of this test grepped for the literal
|
||||
/// `runtime.complete(` and passed while FOUR more call sites — the phase
|
||||
/// planner, both swarm calls, and a second enhance path — still billed the
|
||||
/// metered key. They were spelled `state.runtime` or wrapped across lines,
|
||||
/// so the receiver name was never the thing to look for. Match the METHOD.
|
||||
#[test]
|
||||
fn no_server_side_call_silently_uses_the_metered_key() {
|
||||
let sources = [
|
||||
("routes/mission_roster.rs", include_str!("routes/mission_roster.rs")),
|
||||
("routes/mission_plan.rs", include_str!("routes/mission_plan.rs")),
|
||||
("routes/planner.rs", include_str!("routes/planner.rs")),
|
||||
("routes/claws.rs", include_str!("routes/claws.rs")),
|
||||
("swarm.rs", include_str!("swarm.rs")),
|
||||
];
|
||||
for (name, src) in sources {
|
||||
assert!(
|
||||
!src.contains("runtime.complete("),
|
||||
!src.contains(".complete("),
|
||||
"{name} calls Runtime::complete directly — a bare model name there \
|
||||
resolves to the DEFAULT provider, which is the metered API key. \
|
||||
Use `subscription::complete_or`."
|
||||
Use `subscription::complete_or`, which passes a `name:model` \
|
||||
spec through untouched."
|
||||
);
|
||||
}
|
||||
// And the exception stays an exception, on purpose.
|
||||
@@ -254,6 +275,20 @@ mod tests {
|
||||
assert!(!is_transient(&LlmError::Wire("bad json".into())));
|
||||
}
|
||||
|
||||
/// An operator's explicit provider choice is never hijacked.
|
||||
///
|
||||
/// The swarm worker model is a configured `name:model` spec. Routing that
|
||||
/// onto the subscription would run someone's chosen Kimi or GLM model on
|
||||
/// Anthropic and report success — the same silent-substitution bug as the
|
||||
/// metered key, aimed the other way.
|
||||
#[test]
|
||||
fn a_provider_qualified_spec_is_left_alone() {
|
||||
assert!(is_bare_model_name("claude-opus-4-8"));
|
||||
assert!(is_bare_model_name("claude-haiku-4-5-20251001"));
|
||||
assert!(!is_bare_model_name("kimi:kimi-k2.6"));
|
||||
assert!(!is_bare_model_name("glm:glm-4.7"));
|
||||
}
|
||||
|
||||
/// A metered key in the OAuth slot must be REFUSED, not used.
|
||||
///
|
||||
/// Accepting it would authenticate, work, and bill the pay-as-you-go account
|
||||
|
||||
Reference in New Issue
Block a user