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 that42eddb3was verified against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
10 KiB
Ревью, раунд 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, строки 320–346
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:103–112:
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:91–94 (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 (строки 136–145) — риск порванного файла закрыт. Но 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удерживается на всё время subprocessagy; глобальная записьgemini:antigravityперезаписывается без восстановления.
6. Условия приёмки
Блокирующие:
- Блокер A — либо реальный сбор квот, либо честная маркировка
estimated/baselineв источнике и в UI. - Блокер 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).