diff --git a/src/openhuman/inference/provider/ops/http_error.rs b/src/openhuman/inference/provider/ops/http_error.rs index e09d15d91..341d73c68 100644 --- a/src/openhuman/inference/provider/ops/http_error.rs +++ b/src/openhuman/inference/provider/ops/http_error.rs @@ -137,7 +137,16 @@ pub fn is_provider_config_rejection_http( provider: &str, body: &str, ) -> bool { - if !matches!(status.as_u16(), 400 | 404 | 422) { + // 403 is included for the Ollama Cloud subscription gate: + // `{"error":"this model requires a subscription, upgrade for access: …"}`. + // That is deterministic user-state (paid-tier model, free account) — the + // same class as the 400/404/422 config-rejection shapes above. See + // TAURI-RUST-4XK. The general `is_backend_auth_failure` polarity guard + // still fires first (backend 401/403 → SessionExpired), so this branch + // is only reachable for non-backend providers. The phrase-level polarity + // guard below (`provider != openhuman_backend::PROVIDER_LABEL`) provides + // a second layer of defence for the non-OpenAI-compat shapes. + if !matches!(status.as_u16(), 400 | 403 | 404 | 422) { return false; } if !crate::openhuman::inference::provider::is_provider_config_rejection_message(body) { diff --git a/src/openhuman/inference/provider/ops_tests.rs b/src/openhuman/inference/provider/ops_tests.rs index 0e3d2b116..bedd39445 100644 --- a/src/openhuman/inference/provider/ops_tests.rs +++ b/src/openhuman/inference/provider/ops_tests.rs @@ -533,6 +533,44 @@ mod provider_config_rejection_suppression { )); } + /// TAURI-RUST-4XK — Ollama Cloud returns HTTP 403 with body + /// `{"error":"this model requires a subscription, upgrade for access: …"}`. + /// Before this fix, `is_provider_config_rejection_http` rejected 403 + /// before reaching the phrase matcher, so the subscription-gate body + /// fell through to Sentry. Adding 403 to the allowed status set closes + /// that gap; the existing phrase in `config_rejection.rs` already + /// handles the body content. + #[test] + fn ollama_cloud_403_subscription_gate_is_suppressed() { + // Verbatim wire body from TAURI-RUST-4XK Sentry issue 5338. + let body = r#"ollama API error (403 Forbidden): {"error":"this model requires a subscription, upgrade for access: https://ollama.com/upgrade (ref: bc48f3c8-fba1-40b6-93a9-786a167d16f9)"}"#; + assert!( + is_provider_config_rejection_http( + reqwest::StatusCode::FORBIDDEN, + "ollama", + body, + ), + "TAURI-RUST-4XK: ollama 403 subscription-gate must be classified as provider config-rejection" + ); + } + + #[test] + fn openhuman_backend_403_subscription_phrase_is_not_suppressed() { + // Polarity guard: if our own backend somehow returned a 403 with + // the subscription phrase, that would be an unexpected regression + // and must still reach Sentry. The phrase does not appear in any + // expected backend body, so this is purely defensive. + let body = r#"{"error":"this model requires a subscription, upgrade for access: https://ollama.com/upgrade (ref: test)"}"#; + assert!( + !is_provider_config_rejection_http( + reqwest::StatusCode::FORBIDDEN, + openhuman_backend::PROVIDER_LABEL, + body, + ), + "backend 403 subscription phrase must NOT be suppressed (polarity guard)" + ); + } + #[test] fn log_helper_runs_without_panicking() { // Covers the demotion log path taken by `api_error` when a