fix: stop unfunded default Routstr before provider discovery
This commit is contained in:
@@ -377,6 +377,9 @@ impl Backend for RoutstrBackend {
|
|||||||
tools: &[ToolDef],
|
tools: &[ToolDef],
|
||||||
history: &[ChatMessage],
|
history: &[ChatMessage],
|
||||||
) -> Result<BackendTurn> {
|
) -> Result<BackendTurn> {
|
||||||
|
if self.policy.budget_sats == 0 {
|
||||||
|
anyhow::bail!("Set a Routstr spending allowance before using AI.");
|
||||||
|
}
|
||||||
let providers = discover_providers(self.tor_proxy.as_deref()).await;
|
let providers = discover_providers(self.tor_proxy.as_deref()).await;
|
||||||
self.send_with_providers(&providers, system, tools, history)
|
self.send_with_providers(&providers, system, tools, history)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1800,25 +1800,42 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// D-05: a fresh node's `AssistantBudget` defaults to a zero
|
/// Routstr is the default provider, but a fresh node cannot discover,
|
||||||
/// allowance, and `select_backend` must never select Routstr in that
|
/// infer or pay until the operator explicitly sets an allowance.
|
||||||
/// case — the operator sees Claude alone (or Claude's own error)
|
|
||||||
/// rather than a paid backend chosen and then declined at the payment
|
|
||||||
/// step.
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn zero_allowance_never_selects_routstr() {
|
async fn default_routstr_with_zero_allowance_stops_before_network_or_payment() {
|
||||||
let (handler, _tmp) = test_rpc_handler().await;
|
let (handler, _tmp) = test_rpc_handler().await;
|
||||||
let budget = AssistantBudget::load(handler.data_dir()).await;
|
let budget = AssistantBudget::load(handler.data_dir()).await;
|
||||||
assert_eq!(
|
assert_eq!(budget.allowance_sats, 0);
|
||||||
budget.allowance_sats, 0,
|
let (backend, id) = backends::select_backend(&handler).await;
|
||||||
"a fresh node must default to a zero allowance"
|
assert_eq!(id, backends::BackendId::Routstr);
|
||||||
);
|
let result = tokio::time::timeout(
|
||||||
|
std::time::Duration::from_secs(1),
|
||||||
|
backend.send("synthetic", &[], &[]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("zero allowance must stop before provider discovery");
|
||||||
|
let error = result.err().expect("zero allowance cannot run inference");
|
||||||
|
assert!(error.to_string().contains("spending allowance"));
|
||||||
|
let after = AssistantBudget::load(handler.data_dir()).await;
|
||||||
|
assert_eq!(after.allowance_sats, 0);
|
||||||
|
assert_eq!(after.spent_sats, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keep the saved legacy automatic selection behavior: zero allowance
|
||||||
|
/// must not enable the paid fallback.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn automatic_selection_with_zero_allowance_never_selects_routstr() {
|
||||||
|
let (handler, _tmp) = test_rpc_handler().await;
|
||||||
|
crate::settings::model_provider::ModelProvider {
|
||||||
|
provider: crate::settings::model_provider::Provider::Auto,
|
||||||
|
openai_model: String::new(),
|
||||||
|
}
|
||||||
|
.save(handler.data_dir())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let (_backend, id) = backends::select_backend(&handler).await;
|
let (_backend, id) = backends::select_backend(&handler).await;
|
||||||
assert_ne!(
|
assert_ne!(id, backends::BackendId::Routstr);
|
||||||
id,
|
|
||||||
backends::BackendId::Routstr,
|
|
||||||
"a zero allowance must never select Routstr"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// D-05: `payment_policy()`'s ceiling is computed ONLY from the
|
/// D-05: `payment_policy()`'s ceiling is computed ONLY from the
|
||||||
|
|||||||
@@ -397,3 +397,11 @@ The funding modal's Scan action now opens the existing wallet scanner and return
|
|||||||
to funding on close; six focused connection-modal tests pass after that wiring.
|
to funding on close; six focused connection-modal tests pass after that wiring.
|
||||||
The first dashboard production build passed; it will be rebuilt for this final
|
The first dashboard production build passed; it will be rebuilt for this final
|
||||||
scanner wiring before deployment. AIUI and isolated backend builds are ongoing.
|
scanner wiring before deployment. AIUI and isolated backend builds are ongoing.
|
||||||
|
|
||||||
|
The first full isolated backend run passed 1,717 tests with one outdated default
|
||||||
|
selection assertion failing (four explicit ignores). The assertion expected an
|
||||||
|
unconfigured node to choose Claude. Updated coverage distinguishes the new Routstr
|
||||||
|
default from a saved legacy Auto choice, and the Routstr adapter now rejects zero
|
||||||
|
allowance before even discovering providers. A rerun is required; no passing full
|
||||||
|
suite or deployment is claimed yet. Final dashboard and AIUI production builds
|
||||||
|
have both passed.
|
||||||
|
|||||||
Reference in New Issue
Block a user