fix: stop unfunded default Routstr before provider discovery

This commit is contained in:
archipelago
2026-10-08 12:55:59 -04:00
parent 0ff95251f5
commit b1df79cad0
3 changed files with 43 additions and 15 deletions
@@ -377,6 +377,9 @@ impl Backend for RoutstrBackend {
tools: &[ToolDef],
history: &[ChatMessage],
) -> 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;
self.send_with_providers(&providers, system, tools, history)
.await
+32 -15
View File
@@ -1800,25 +1800,42 @@ mod tests {
);
}
/// D-05: a fresh node's `AssistantBudget` defaults to a zero
/// allowance, and `select_backend` must never select Routstr in that
/// case — the operator sees Claude alone (or Claude's own error)
/// rather than a paid backend chosen and then declined at the payment
/// step.
/// Routstr is the default provider, but a fresh node cannot discover,
/// infer or pay until the operator explicitly sets an allowance.
#[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 budget = AssistantBudget::load(handler.data_dir()).await;
assert_eq!(
budget.allowance_sats, 0,
"a fresh node must default to a zero allowance"
);
assert_eq!(budget.allowance_sats, 0);
let (backend, id) = backends::select_backend(&handler).await;
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;
assert_ne!(
id,
backends::BackendId::Routstr,
"a zero allowance must never select Routstr"
);
assert_ne!(id, backends::BackendId::Routstr);
}
/// D-05: `payment_policy()`'s ceiling is computed ONLY from the
+8
View File
@@ -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.
The first dashboard production build passed; it will be rebuilt for this final
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.