fix(readiness): роль на резерве считается работающей

Со скриншота владельца: заголовок «Ролей в строю: 0/6», а ниже шесть
предупреждений «роль работает через резервный аккаунт». Пять ролей
исправно отвечали, интерфейс сообщал, что не работает ни одна.

roles_ready считал только роли со здоровым ОСНОВНЫМ профилем. Роль,
обслуживаемая резервом, попадала в degraded_roles, но не в ready.
Ошибка в худшую сторону: отказ показывался там, где всё работает — а
переключение на резерв это ровно то, ради чего продукт и создан.

Теперь роль с живым резервом считается работающей и одновременно
помечается деградировавшей: оба состояния остаются различимыми.

Попутно склонение: «Есть 1 ролей без рабочего маршрута» заменено на
согласованное с числом — 1 роль, 2 роли, 5 ролей.

Проверено на живых данных: 6/6 в строю, состояние «деградация», а не
«критическое».

Тесты: 375 passed, ruff чисто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-24 10:19:33 +07:00
parent bca88a56a6
commit 03d73f8d0b
2 changed files with 62 additions and 2 deletions

View file

@ -170,6 +170,17 @@ class ProviderSummary:
last_refresh_at: str
def _plural_roles(n: int) -> str:
"""Согласовать число со словом «роль»: 1 роль, 2 роли, 5 ролей."""
tail = n % 100
if 11 <= tail <= 14:
word = "ролей"
else:
last = n % 10
word = "роль" if last == 1 else "роли" if 2 <= last <= 4 else "ролей"
return f"{n} {word}"
@dataclass
class SystemReadiness:
state: str # HEALTHY | LIMITED | DEGRADED | CRITICAL
@ -567,6 +578,12 @@ class UnifiedHealthService:
break
if has_working_fallback:
# Роль, обслуживаемая резервом, РАБОТАЕТ — она просто
# деградировала. Раньше она не попадала в roles_ready, и
# интерфейс писал «Ролей в строю: 0/6», пока пять ролей
# исправно отвечали. Это вводит в заблуждение в худшую
# сторону: пользователь видит отказ там, где всё работает.
roles_ready += 1
degraded_roles += 1
warnings.append(f"Роль '{rname}' работает через резервный аккаунт (Primary недоступен).")
else:
@ -577,11 +594,11 @@ class UnifiedHealthService:
if dead_roles > 0:
state = READINESS_CRITICAL
title_ru = "Критическое состояние"
summary_ru = f"Есть {dead_roles} ролей без рабочего маршрута!"
summary_ru = f"Без рабочего маршрута: {_plural_roles(dead_roles)}."
elif degraded_roles > 0:
state = READINESS_DEGRADED
title_ru = "Деградация маршрутов"
summary_ru = f"{degraded_roles} ролей работают через резерв."
summary_ru = f"Через резерв работают: {_plural_roles(degraded_roles)}."
elif connected_accounts < total_accounts:
state = READINESS_LIMITED
title_ru = "Ограниченная готовность"

View file

@ -0,0 +1,43 @@
"""Роль на резерве считается работающей.
Дефект со скриншота владельца: заголовок «Ролей в строю: 0/6», а ниже
шесть предупреждений вида «роль работает через резервный аккаунт». Пять
ролей исправно отвечали, а интерфейс сообщал, что не работает ни одна.
roles_ready считал только роли со здоровым ОСНОВНЫМ профилем. Роль,
обслуживаемая резервом, попадала в degraded, но не в ready. Это ошибка в
худшую сторону: пользователь видит отказ там, где всё работает.
"""
from __future__ import annotations
from antigravity_provider.router.unified_health import _plural_roles
def test_role_on_fallback_counts_as_ready():
"""Роль, у которой жив резерв, обязана попадать в «в строю»."""
import inspect
from antigravity_provider.router import unified_health
src = inspect.getsource(unified_health.UnifiedHealthService.get_system_readiness)
marker = "if has_working_fallback:"
assert marker in src
tail = src.split(marker, 1)[1].split("else:", 1)[0]
assert "roles_ready += 1" in tail, (
"роль на резерве снова не засчитывается как работающая"
)
assert "degraded_roles += 1" in tail, (
"признак деградации потерян — состояние должно оставаться отличимым"
)
def test_plural_roles_agrees_with_number():
"""«Есть 1 ролей» — так по-русски не пишут."""
assert _plural_roles(1) == "1 роль"
assert _plural_roles(2) == "2 роли"
assert _plural_roles(4) == "4 роли"
assert _plural_roles(5) == "5 ролей"
assert _plural_roles(11) == "11 ролей"
assert _plural_roles(21) == "21 роль"
assert _plural_roles(112) == "112 ролей"