fix: reparent governance backend failures under GatewayUnavailableError (issue #7)
A fail-closed limiter or breaker backend means the scope cannot emit a single request, which is exactly scope-level unavailability. But the error sat directly under PolyGatewayError, so a caller writing only `except GatewayUnavailableError` dropped it into the catch-all branch: Redis blips once and a backlog of tasks burns its business failure budget into the dead letter queue, over a fault a restart would clear. Three gate paths leak to callers rather than being absorbed by _record_quietly (try_acquire, try_enter, progress_age_s); each is now pinned by a test, since none of them had one before. The two unknown-source sites move to SourceNotConfiguredError instead of following along. They report a misconfigured source name, not an outage, and letting them into the retryable family would be the mirror of the bug being fixed here: the task would retry forever and never surface.
This commit is contained in:
@@ -89,10 +89,27 @@ class TestGatewayUnavailable:
|
||||
class TestBackendFailure:
|
||||
def test_governance_backend_error_is_not_transient(self):
|
||||
"""限流/熔断后端故障必须报错不放行,且不落入可重试分类。"""
|
||||
exc = GovernanceBackendError("redis down")
|
||||
exc = GovernanceBackendError("redis down", scope="llm")
|
||||
assert isinstance(exc, PolyGatewayError)
|
||||
assert not isinstance(exc, TransientError)
|
||||
|
||||
def test_is_scope_level_unavailability(self):
|
||||
"""fail-closed 时整个 scope 一个请求都发不出去,调用方一条 except 应覆盖(issue #7)。"""
|
||||
exc = GovernanceBackendError("限流后端 try_acquire 失败: boom", scope="LLM")
|
||||
assert isinstance(exc, GatewayUnavailableError)
|
||||
assert exc.reason == "governance_backend_down"
|
||||
assert exc.scope == "llm" # 与既有 scope 级异常同款: 归一化小写
|
||||
assert exc.retry_after_s == GOVERNANCE_BACKEND_RETRY_AFTER_S
|
||||
|
||||
def test_diagnostic_message_survives_reparenting(self):
|
||||
"""父类把 message 覆写为模板串,而各构造点的诊断串是排障主线索(§3.5)。"""
|
||||
exc = GovernanceBackendError("熔断后端 try_enter 失败: boom", scope="llm")
|
||||
assert str(exc) == "熔断后端 try_enter 失败: boom"
|
||||
|
||||
def test_retry_after_overridable(self):
|
||||
exc = GovernanceBackendError("redis down", scope="llm", retry_after_s=30.0)
|
||||
assert exc.retry_after_s == 30.0
|
||||
|
||||
|
||||
class TestSourceNotConfigured:
|
||||
"""装配缺陷有意留在 scope 级家族之外(issue #7 §3.4,Q1 人类拍板)。"""
|
||||
|
||||
Reference in New Issue
Block a user