Location
crates/librefang-llm-driver/src/lib.rs:71-205 (failover_reason())
crates/librefang-types/src/error.rs:38, 52, 68, 72
Problem
failover_reason() takes LlmError::Api { status, message: String }, lowercases, and substring-matches "rate limit", "credit", "balance", "context", "model not found" to decide retry vs failover. Provider rewords or i18n's the error → classification becomes Unknown overnight.
LibreFangError flattens reqwest / rusqlite / wasmtime errors to String:
Memory(String)
LlmDriver(String)
Network(String)
Serialization(String)
Only Io(#[from] std::io::Error) keeps source. e.source() chain is dead at the top level — retry logic can't match on the underlying type, only re-parse the string.
Impact
Failover policy is a substring lookup that any provider tweak breaks. Higher-level retry/circuit-break logic can't make typed decisions.
Fix
LlmError::Api gains code: Option<ProviderErrorCode> (rate_limit_exceeded / insufficient_quota / context_length_exceeded). Each driver populates it once when parsing the provider's JSON. failover_reason() reads code first, falls back to substring.
LibreFangError::{Memory, LlmDriver, Network} change to #[from] typed errors; preserve source chain.
Filed via automated multi-perspective audit (security / concurrency / architecture / error-handling / frontend / performance subagents). Curated against #3359–#3409 to avoid duplicates.
Location
crates/librefang-llm-driver/src/lib.rs:71-205(failover_reason())crates/librefang-types/src/error.rs:38, 52, 68, 72Problem
failover_reason()takesLlmError::Api { status, message: String }, lowercases, and substring-matches"rate limit","credit","balance","context","model not found"to decide retry vs failover. Provider rewords or i18n's the error → classification becomesUnknownovernight.LibreFangErrorflattens reqwest / rusqlite / wasmtime errors toString:Io(#[from] std::io::Error)keeps source.e.source()chain is dead at the top level — retry logic can'tmatchon the underlying type, only re-parse the string.Impact
Failover policy is a substring lookup that any provider tweak breaks. Higher-level retry/circuit-break logic can't make typed decisions.
Fix
LlmError::Apigainscode: Option<ProviderErrorCode>(rate_limit_exceeded / insufficient_quota / context_length_exceeded). Each driver populates it once when parsing the provider's JSON.failover_reason()reads code first, falls back to substring.LibreFangError::{Memory, LlmDriver, Network}change to#[from]typed errors; preserve source chain.Filed via automated multi-perspective audit (security / concurrency / architecture / error-handling / frontend / performance subagents). Curated against #3359–#3409 to avoid duplicates.