hermes-hub/agents/inbox/2026-08-20-review-round5-unreviewed-features.md
Hermes Team 46a185395c docs(review): add independent audit, five review rounds, and remediation task
Full audit of the project (93 findings) plus the review record for rounds 1-5:
P0 release blockers, 65482e8, 42dfe2c, the v0.1.1 candidate verdict, and the
review of the previously unreviewed OAuth/wizard/quota work. Includes the
remediation task with acceptance criteria that 42eddb3 was verified against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:22:57 +07:00

10 KiB
Raw Blame History

Ревью, раунд 5: 0d9005f…0c511cd (OAuth, мастер, тарифы, квоты, Claude/Grok, Plan A)

База: 8314d46проверено: 0c511cd (5 коммитов, +6970 / 665) Состояние: тесты 91 passed / 7 skipped / 3 deselected, релизный гейт зелёный.

Вердикт: два блокирующих дефекта — фабрикация данных о квотах и поддельный код авторизации. Оба относятся к классу «пользователь видит правдоподобное, но выдуманное».


1. Блокер A: сборщик квот выдаёт захардкоженные числа под видом данных провайдера

src/antigravity_provider/router/quota_collector.py (561 строка) не делает ни одного сетевого вызова:

urlopen вызовов: 0

При этом каждый сборщик возвращает фиксированные константы и помечает их источником провайдерского API:

# _collect_antigravity_quota, строки 320346
QuotaBucket(id="antigravity.gemini.5h",     used_percent=9.0,  remaining_percent=91.0, status="healthy")
QuotaBucket(id="antigravity.gemini.weekly", used_percent=1.0,  remaining_percent=99.0, status="healthy")
...
return QuotaSnapshot(..., source="antigravity_api")

Всего 17 литеральных used_percent по пяти провайдерам, с источниками: antigravity_api, codex_usage_api, opencode_api, claude_oauth_usage_api, xai_task_usage_api.

Эти значения доходят до пользователя: ui/views/accounts_view.py:168

snap = quota_snap or p.quota_snapshot or AccountQuotaService.get().get_snapshot(p.provider, p.profile_id)

и рисуются в quota_box карточки аккаунта. То есть на экране «Аккаунты» показывается «Gemini 5h — использовано 9%, осталось 91%» независимо от реального состояния квоты. Ровно та задача, ради которой существует продукт — понимать, где кончилась квота, — решается вымышленными числами.

Тесты фикцию не закрепляют (test_accounts_tariffs_quotas.py проверяет арифметику used/remaining на собственных значениях), поэтому дефект не виден по зелёному прогону.

Требуется одно из двух:

  • реализовать реальный сбор (запрос к провайдеру) — тогда источники честные;
  • либо пометить снапшоты как source="baseline"/"estimated" и явно показать в UI, что это оценка, а не данные провайдера. Значение "baseline" в коде уже есть (строка 525) — значит, различие осознавалось.

2. Блокер B: тихий фолбэк на поддельный код авторизации

codex_oauth.py:103112:

try:
    resp = _post_json(CODEX_OAUTH_USER_CODE_URL, {"client_id": CODEX_OAUTH_CLIENT_ID})
    self.user_code = resp.get("user_code")
    self.device_auth_id = resp.get("device_auth_id")
except Exception as e:
    logger.warning("Could not reach OpenAI deviceauth endpoint directly: %s. Using local session.", e)
    self.user_code = f"CDX-{secrets.token_hex(3).upper()}"
    self.device_auth_id = secrets.token_urlsafe(16)

То же в grok_oauth.py:9194 (GRK-XXXXXX + локальный device_code).

По RFC 8628 user_code и device_code выдаёт сервер авторизации. Сгенерированные локально, они серверу неизвестны. Последствие: при любой недоступности эндпоинта (сетевой сбой, смена API, гео-блокировка) мастер показывает пользователю правдоподобный код вида CDX-A1B2C3, предлагает ввести его на странице провайдера, затем поллит device_auth_id, которого не существует, — и через 15 минут выдаёт таймаут. Отличить подделку от настоящего кода пользователь не может; в логах остаётся только warning.

Комментарий в коде говорит «for offline or simulated/mocked environment» — то есть режим предназначен для тестов, но срабатывает в продакшене по любому исключению.

Требуется: при неудаче запроса кода показывать ошибку немедленно, а фолбэк включать только под явным флагом (HERMES_HUB_DEV_MODE=1), по образцу уже сделанного в update_manager.is_allowed_update_host.


3. Существенные замечания

S1. Три новых заимствованных OAuth-клиента без документирования.

codex_oauth.py:31  CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"   (OpenAI Codex CLI)
grok_oauth.py:30   XAI_OAUTH_CLIENT_ID   = "b1a00492-073a-47ea-816f-4c329264a828"
claude_oauth.py    клиент Claude

Для Google было принято решение и оформлено docs/OAUTH_CLIENT.md с моделью угроз по RFC 8252. Три новых клиента появились без такого же разбора. Политику нужно распространить на них — иначе документ описывает одну треть реальной картины.

S2. gui_server.py вырос на 112 строк и по-прежнему ничем не запускается. Проверено: ни один .py, .bat, .ps1, .cs не вызывает run_gui_server. В мёртвый параллельный стек вкладываются усилия; fastapi/uvicorn остаются обязательными зависимостями.

S3. health_tracker: атомарность есть, межпроцессной блокировки нет. Запись через tempfile.mkstemp + os.replace (строки 136145) — риск порванного файла закрыт. Но GUI-процесс и процесс Hermes по-прежнему перетирают состояние друг друга целиком (last-writer-wins).


4. Закрыто в этих коммитах (подтверждаю)

Замечание прошлых раундов Статус
Session affinity без TTL и вытеснения ttl_seconds=1800, max_entries=1000, prune_expired()
_restore_status блокирует UI сетевым сканом читает готовый снапшот из HubStateStore, сети в UI-потоке нет
Новые модули оказываются мёртвым кодом не повторилось: quota_collector (9 импортёров), state_store (8), account_identity (5), scheduler (5), event_bus (4), model_registry (2) — всё подключено
Новые адаптеры не регистрируются claude и grok есть в _ADAPTERS
Наложение периодических задач HermesRefreshScheduler с overlap-skip и счётчиком tasks_skipped_overlap
Утечка секретов в логи не найдено: в add_account_wizard.py, codex_oauth.py, grok_oauth.py, claude_oauth.py токены и ключи не логируются; в мастере поле с show="*" и маскирование идентичности

Отдельно отмечу качество: капабилити-роутинг в router_engine со скорингом моделей и фолбэком в пределах аккаунта сделан аккуратно, state_store с дельта-обновлениями — правильное архитектурное решение против прежних полных пересканов.


5. Остаётся открытым с прошлых раундов

  • capability_matrix, lifecycle_supervisor, skill_registry, deepseek_adapter — по-прежнему не подключены ни к одному кодовому пути.
  • Комментарии в router_profiles.yaml стираются при сохранении.
  • model_timeout_seconds, monitoring_interval_seconds, auto_monitoring сохраняются, но не читаются.
  • tests/test_installer.py при запуске с -m installer пишет в Start Menu и HKCU.
  • _CM_LOCK удерживается на всё время subprocess agy; глобальная запись gemini:antigravity перезаписывается без восстановления.

6. Условия приёмки

Блокирующие:

  1. Блокер A — либо реальный сбор квот, либо честная маркировка estimated/baseline в источнике и в UI.
  2. Блокер B — фолбэк на локальный код только под HERMES_HUB_DEV_MODE=1, иначе немедленная ошибка.

До релиза: 3. Распространить решение по OAuth-клиентам на OpenAI, xAI и Claude в docs/OAUTH_CLIENT.md. 4. Определиться с gui_server.py: удалить, вынести в legacy/ или подключить.

После закрытия 1 и 2 препятствий к тегу со стороны кода не вижу — остаются организационные пункты из раунда 4 (опубликовать ассет релиза и HermesHubSetup.exe, обновить checksums.txt).