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 that42eddb3was verified against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
51e5b67921
commit
46a185395c
7 changed files with 940 additions and 0 deletions
256
agents/inbox/2026-08-20-full-audit-and-improvement-backlog.md
Normal file
256
agents/inbox/2026-08-20-full-audit-and-improvement-backlog.md
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
# Полный аудит Hermes Hub и бэклог доработок
|
||||
|
||||
**Дата аудита:** 2026-08-20
|
||||
**Проверенная ревизия:** `origin/main` @ `5ccfd48` (github.com/ochenstarik-ui/hermes-hub, private)
|
||||
**Метод:** статический разбор всех исходников + прогон тестов на чистом checkout `origin/main` в отдельном worktree
|
||||
**Версия:** 0.1.0 (`pyproject.toml`, `compatibility.json`) против 1.3.0 (`about_view.py`, `gui_server.py`)
|
||||
**Объём:** ~19 500 строк (Python ~15k, C# 0.8k, HTML 0.85k, PS1/BAT 0.25k, docs/config ~2.5k)
|
||||
|
||||
> Первая редакция этого документа делалась по локальному снимку `fdf9ecc` (на 3 коммита старше). Все пункты ниже перепроверены по `origin/main`; раздел 2 фиксирует, что именно уже исправлено в `7609ad8`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Общая оценка
|
||||
|
||||
Ядро маршрутизации (`router_engine`, `health_tracker`, `session_affinity`, адаптеры) спроектировано грамотно: чистые слои, dataclass-модели, потокобезопасность, детерминированные тесты с моками. Коммит `7609ad8` («product stabilization») закрыл заметную часть проблем презентационного слоя — появился кэш сканирования, строгий resolver статусов, идентичность приложения в Windows, предпрогрев экранов.
|
||||
|
||||
Тем не менее **продукт остаётся неработоспособным как приложение**:
|
||||
|
||||
- нативное приложение не запускается на чистой машине — ни один установщик не ставит зависимости UI;
|
||||
- failover для основного провайдера (Antigravity) не срабатывает никогда из-за формата ошибок `agy_generate`;
|
||||
- три пользовательских сценария падают с `AttributeError`/`NameError`: удаление credentials, подключение аккаунта по API-ключу, перераспределение ролей;
|
||||
- настройки не сохраняются и ни на что не влияют;
|
||||
- «roadmap»-модули (+819 строк) не подключены ни к одному кодовому пути.
|
||||
|
||||
**Фактический прогон тестов на чистом checkout `origin/main`:**
|
||||
`1 failed, 28 passed, 2 skipped` + 1 ошибка сбора (`test_ui_refinement.py` не импортируется без `customtkinter`).
|
||||
Отчёт `agents/done/…stabilization…md` заявляет «35/35 PASSED» — это верно только на машине, где `dist/` существует локально и зависимости доустановлены руками.
|
||||
|
||||
**Зрелость:** ядро — Beta, оболочка — Alpha, упаковка — не готова к дистрибуции.
|
||||
|
||||
---
|
||||
|
||||
## 2. Что уже исправлено в `7609ad8` (не требует работы)
|
||||
|
||||
| Было в аудите | Статус в `origin/main` |
|
||||
|---|---|
|
||||
| `TeamView.update_data(app_state)` требует аргумент → главный экран не обновляется | **Исправлено** — сигнатура унифицирована (`app_state=None`) во всех view |
|
||||
| `scan_all()` вызывается 9–10 раз за один refresh | **Исправлено** — TTL-кэш 30 с (`unified_health.py:248`) |
|
||||
| Ложные `HEALTHY`/`QUOTA_EXHAUSTED` у неподключённых слотов | **Исправлено** — введён `STATUS_NOT_CONFIGURED`, устаревшие записи квот чистятся |
|
||||
| Иконка Python в панели задач | **Исправлено** — `SetCurrentProcessExplicitAppUserModelID("HermesHub.Desktop")` + multi-res `.ico` |
|
||||
| Лаг переключения вкладок | **Исправлено** — предпрогрев всех 8 экранов в `__init__`, переключение через `pack_forget()` |
|
||||
| Установщик не копирует `assets/branding` | **Исправлено** в новом `installer/HermesHubSetup.py` (в `.cs` и `install.ps1` — нет) |
|
||||
| Нет документации по разработке/безопасности/производительности | **Исправлено** — добавлены `docs/AUTH.md`, `DEVELOPMENT.md`, `PERFORMANCE.md`, `ROUTER.md`, `SECURITY.md` |
|
||||
| Нет адаптивности окна | **Исправлено** — debounce-обработчик resize |
|
||||
|
||||
---
|
||||
|
||||
## 3. P0 — Блокеры (перепроверено на `origin/main`, всё воспроизводится)
|
||||
|
||||
| # | Дефект | Файл | Последствие |
|
||||
|---|---|---|---|
|
||||
| 1 | `customtkinter` и `pillow` не устанавливаются ни `HermesHubSetup.py`, ни `HermesHubSetup.cs`, ни `install.ps1` | все три установщика | Запуск через `pythonw.exe` завершается молча: ни окна, ни ошибки. Проверено: в `%LOCALAPPDATA%\hermes\hermes-agent\venv` пакета нет |
|
||||
| 2 | `ProfileAuthManager.get_profile_dir` не существует — это модульная функция, а не метод класса | `profile_manager.py:79` vs `gui_server.py:216`, `hermes_hub_app.py:120`, `add_account_wizard.py:200` | `AttributeError` при удалении credentials и при сохранении API-ключа |
|
||||
| 3 | `json` не импортирован, но используется | `ui/add_account_wizard.py:200` | `NameError` → **Codex и OpenCode Go нельзя подключить через UI вообще** |
|
||||
| 4 | `AutoAssigner.auto_assign_all` не существует | `hermes_hub_app.py:411`, `team_view.py:188,243` | Кнопки «Перераспределить роли» и «⋮» падают |
|
||||
| 5 | `agy_generate` возвращает ошибки как успешный `chat.completion`: текст ошибки в `content`, ключа `error` нет | `agy_subprocess.py:584` + `router_engine.py` | `RouterEngine` проверяет `if "error" in response` → не срабатывает → `mark_success`, **failover для Antigravity не работает никогда**, пользователь получает текст ошибки вместо ответа модели |
|
||||
| 6 | Мастер ждёт `session.status == "error"`, сессия ставит `"failed"` | `add_account_wizard.py:243` vs `profile_oauth.py` | При ошибке OAuth мастер молча висит 120 с |
|
||||
| 7 | Кнопка «Назначить» шлёт action `assign_role`, обработчика нет | `accounts_view.py:244` vs `hermes_hub_app._handle_action` | Кнопка не делает ничего, без обратной связи |
|
||||
| 8 | Шаг 4 мастера не применяет выбранную роль — только пишет в журнал | `add_account_wizard.py:_finish` | Выбор пользователя игнорируется |
|
||||
| 9 | Шаг 3 показывает «✓ Аккаунт успешно проверен» без всякой проверки для API-ключей; список моделей захардкожен (`gpt-4.1`, `qwen-coder`, `deepseek-v2`) | `add_account_wizard.py` | Ложноположительная валидация, вымышленные модели |
|
||||
|
||||
---
|
||||
|
||||
## 4. P1 — Производительность и отзывчивость UI
|
||||
|
||||
| # | Проблема |
|
||||
|---|---|
|
||||
| 10 | `_restore_status()` вызывает `get_system_readiness()` **в UI-потоке** через `after(6000, …)`. При холодном/истёкшем кэше — блокировка окна на время сетевого скана 16 профилей |
|
||||
| 11 | При промахе кэша `scan_all()` опрашивает 16 профилей последовательно (таймаут 10 с на профиль) — нет параллелизма и общего бюджета времени |
|
||||
| 12 | 5 экранов (`accounts`, `health`, `providers`, `routing`, `logs`) полностью уничтожают и пересоздают виджеты через `winfo_children()` + `destroy()` — мерцание, потеря позиции скролла |
|
||||
| 13 | Нет периодического автообновления: данные устаревают до ручного нажатия «Обновить» (таймер только для resize) |
|
||||
| 14 | Тост затирает строку состояния и восстанавливается блокирующим вызовом |
|
||||
|
||||
---
|
||||
|
||||
## 5. P1 — Логика роутера и корректность состояний
|
||||
|
||||
Файлы `router_engine.py`, `health_tracker.py`, `agy_subprocess.py`, `profile_manager.py`, `router_config.py`, `session_affinity.py`, `hermes_plugin.py` в `7609ad8` **не менялись** — все находки в силе.
|
||||
|
||||
| # | Проблема | Файл |
|
||||
|---|---|---|
|
||||
| 15 | `_CM_LOCK` удерживается всё время subprocess `agy` (до 180+ с) → все Antigravity-профили сериализуются, параллельная работа команды невозможна | `antigravity_adapter.py` |
|
||||
| 16 | Профильные токены пишутся в **глобальную** запись `gemini:antigravity` Credential Manager и не восстанавливаются → «основной аккаунт Hermes» меняется как побочный эффект маршрутизации; гонки с самим `agy` и Hermes CLI | `antigravity_adapter.py` |
|
||||
| 17 | `overall_state` не сбрасывается при истечении cooldown (только в `mark_success`) → профиль остаётся «Квота исчерпана» бессрочно | `health_tracker.py:is_healthy` |
|
||||
| 18 | Роутер решает по состоянию семейства моделей, UI — по `overall_state`: роутер шлёт запросы в профиль, который UI показывает мёртвым | `health_tracker.py` / `unified_health.py` |
|
||||
| 19 | Session affinity без TTL и вытеснения — неограниченный рост памяти; `session_affinity_ttl_seconds` из шаблона конфига не реализован | `session_affinity.py` |
|
||||
| 20 | `router_state.json` пишется без межпроцессной блокировки — GUI и процесс Hermes затирают состояние друг друга | `health_tracker.py` |
|
||||
| 21 | `ProfileHealthRecord.active_leases` всегда 0, но выводится в CLI и API | `health_tracker.py`, `cli_commands.py` |
|
||||
| 22 | Категория `TRANSIENT` классифицируется, но обрабатывается как обычный отказ — без повтора и задержки | `router_engine.py` |
|
||||
| 23 | Классификация ошибок по подстрокам (`"429" in err_lower`) — ложные срабатывания на любом тексте с «429» | все адаптеры |
|
||||
| 24 | `_safe_env()` не используется: `custom_env = dict(os.environ)` передаёт в subprocess `agy` все ключи (`OPENAI_API_KEY` и пр.) | `agy_subprocess.py`, `antigravity_adapter.py` |
|
||||
| 25 | Нет стриминга ответов — Hermes получает только полный ответ | `router_engine.py` |
|
||||
| 26 | Нет учёта токенов, стоимости и latency между вызовами | — |
|
||||
| 27 | В шаблоне `router_profiles.example.yaml` блок `router:` полностью игнорируется загрузчиком (ожидаются ключи верхнего уровня); `max_failover_attempts`, `cooldown_base_seconds`, `cooldown_max_seconds`, `session_affinity_ttl_seconds` отсутствуют в схеме | `router_config.py:load_router_config` |
|
||||
| 28 | `save_router_config` пишет другую структуру, чем шаблон → первое сохранение («назначить оркестратором») уничтожает комментарии и структуру пользовательского YAML | `router_config.py` |
|
||||
| 29 | `max_concurrency=1` для Codex и OpenCode во встроенном дефолте, хотя это stateless REST (в шаблоне 2 и 3) — искусственное ограничение пропускной способности | `router_config.py` |
|
||||
| 30 | `antigravity_llm_execution` перехватывает **все** вызовы `llm_execution` независимо от провайдера и никогда не вызывает `next_call`; ошибка роутера логируется на уровне `debug` | `hermes_plugin.py` |
|
||||
| 31 | Каталог моделей захардкожен (`gemini-3.7-flash`, `qwen3.8-max`, `kimi-k2.7-code`…) и не сверяется с реальным | `router_config.py` |
|
||||
| 32 | `discover_models()` определяет каталог, вызывая `agy` с заведомо невалидной моделью и парся текст ошибки | `agy_subprocess.py` |
|
||||
| 33 | `AutoAssigner.check_duplicate_identity` сравнивает `auth_data["email"]`, но `profile_oauth` сохраняет только `{"token", "auth_method"}` → **детектор дубликатов не срабатывает никогда** | `auto_assigner.py` |
|
||||
|
||||
---
|
||||
|
||||
## 6. P1 — Безопасность
|
||||
|
||||
| # | Проблема |
|
||||
|---|---|
|
||||
| 34 | `CLIENT_ID`/`CLIENT_SECRET` Google зашиты в код и **склеены из фрагментов строк** (`"GOC" + "SPX" + …`, `oauth.py:26-28`) — приём обхода сканеров секретов, чужой OAuth-клиент. Требует явного решения: собственный клиент или документированное заимствование. Отчёты «0 секретов при сканировании» проходят именно из-за склейки |
|
||||
| 35 | `ProfileAuthManager.save_profile_auth` пишет токены plaintext без ограничения ACL. `docs/SECURITY_MODEL.md` заявляет изоляцию «правами текущего пользователя ОС», но код прав не выставляет (`chmod` есть только в `CredentialStore` и на Windows не работает) |
|
||||
| 36 | `access_token` передаётся в query-строке при верификации (`tokeninfo?access_token=…`) — попадает в логи |
|
||||
| 37 | `CORSMiddleware(allow_origins=["*"], allow_credentials=True)` на локальном API с эндпоинтами `delete`, `set-main`, `oauth/start` (`gui_server.py`) |
|
||||
| 38 | Лаунчер генерирует и исполняет Python-скрипт `%LOCALAPPDATA%\hermes\hermes_hub_entry.py` при каждом запуске — вектор подмены между записью и стартом |
|
||||
| 39 | Нет подписи установщика и лаунчера; `dist/checksums.txt` формируется вручную и не проверяется при установке |
|
||||
| 40 | В `hermes-hub.log` пишутся тексты ошибок провайдеров без санитизации (могут содержать фрагменты токенов) |
|
||||
|
||||
---
|
||||
|
||||
## 7. P1 — Тесты и качество
|
||||
|
||||
| # | Проблема |
|
||||
|---|---|
|
||||
| 41 | `pytest tests/` **падает на сборе**: `test_ui_refinement.py` → `hermes_hub_app` → `customtkinter` |
|
||||
| 42 | `test_setup_exe_exists` **падает на чистом checkout**: `dist/` в `.gitignore`, ещё 2 теста уходят в skip. Реальный результат — `1 failed, 28 passed, 2 skipped`, а не заявленные 35/35 |
|
||||
| 43 | Тесты мутируют реальные пользовательские данные: `do_set_main` пишет `router_active_profile.json`, `do_set_orchestrator` перезаписывает `router_profiles.yaml`, `get_profile_env_dir` создаёт каталоги профилей. Нет фикстуры с временным `HERMES_HOME` |
|
||||
| 44 | `test_installer.py` **запускает реальный установщик**: копирование в `%LOCALAPPDATA%`, ярлык, запись в реестр |
|
||||
| 45 | Тесты ходят в сеть (Google tokeninfo, OpenCode `/models`) — недетерминированность |
|
||||
| 46 | Нет тестов на: `classify_error` всех адаптеров, мастер подключения, YAML round-trip `load↔save`, CLI, логику установщиков |
|
||||
| 47 | Нет CI (`.github/workflows` отсутствует, репозиторий приватный) |
|
||||
| 48 | `ruff` в dev-зависимостях, но нет секции `[tool.ruff]` и запуска; нет `mypy` |
|
||||
|
||||
---
|
||||
|
||||
## 8. P1 — Упаковка, установка, дистрибуция
|
||||
|
||||
| # | Проблема |
|
||||
|---|---|
|
||||
| 49 | **Два параллельных установщика**: `installer/HermesHubSetup.cs` (711 строк, GUI-мастер, silent-режим, реестр, коды 0/10/11/12) и `installer/HermesHubSetup.py` (101 строка, копирует assets, но без реестра, ярлыка, silent-кодов). Неясно, какой канонический; `dist/HermesHubSetup.exe` собран из `.cs` |
|
||||
| 50 | Проверка совместимости фиктивна: `compatibility.json` не читается, `MIN_HERMES_VERSION` ни с чем не сравнивается, `IsHermesCompatible = true` всегда при наличии Python → **код возврата 11 недостижим**. То же в `install.ps1` (версия печатается, но не проверяется) |
|
||||
| 51 | `installer/HermesHubSetup.cs` и `scripts/install.ps1` не копируют `assets/branding` (исправлено только в `.py`-варианте) |
|
||||
| 52 | `ui/assets.py:39` — захардкоженный fallback `Path("E:/Agent projects/hermes-hub")` в исходниках |
|
||||
| 53 | `build_launcher.ps1` не передаёт `/win32icon:HermesHub.ico` → у самого `.exe` нет иконки (иконка ставится только в рантайме через `iconbitmap`) |
|
||||
| 54 | `install.ps1` копирует `__pycache__` |
|
||||
| 55 | `launcher/HermesHub.exe` — собранный бинарник в git; собранный установщик, наоборот, не отслеживается |
|
||||
| 56 | Нет single-instance guard — повторный клик по ярлыку открывает второе окно |
|
||||
| 57 | Нет логирования запуска: при `pythonw.exe` любая ошибка импорта = полная тишина |
|
||||
| 58 | Нет механизма обновления в самом приложении |
|
||||
| 59 | Версия расходится: `0.1.0` (pyproject, compatibility.json, installer) против `1.3.0` (About, FastAPI) |
|
||||
| 60 | `scripts/launch_hermes_hub.bat` запускает несуществующий веб-режим на `:8765`; CLI-аргументы `hub --port/--no-browser` парсятся и игнорируются |
|
||||
|
||||
---
|
||||
|
||||
## 9. P2 — Мёртвый и неподключённый код
|
||||
|
||||
| # | Объект | Строк | Статус |
|
||||
|---|---|---|---|
|
||||
| 61 | `supervisor/lifecycle_supervisor.py`, `supervisor/policies.py`, `capability/capability_matrix.py`, `skills/skill_registry.py`, `scheduler/task_safety.py`, `adapters/deepseek_adapter.py` | 819 | **Roadmap-модули из `7609ad8` не подключены ни к одному кодовому пути.** Импортируются только тестами (`task_safety` и `deepseek_adapter` — вообще ничем). `DeepSeekResponsesAdapter` не зарегистрирован в `_ADAPTERS` → недостижим через `get_adapter()` |
|
||||
| 62 | `gui_server.py` + `gui_cockpit.html` | 1177 | Параллельный веб-стек, не запускается ниоткуда; `fastapi`/`uvicorn` остаются обязательными зависимостями |
|
||||
| 63 | `runtime.generate_chat_completion`, `antigravity_client.py`, `transform.py`, `openai_compat.py`, `cloudcode.py` | ~590 | Прямой API-путь к Antigravity, не используется (весь трафик через `agy` CLI) |
|
||||
| 64 | `ui/views/dashboard_view.py` («Главная») | 141 | Не подключён к навигации |
|
||||
| 65 | `ui/splash.py` (`SplashScreen`) | 77 | Не используется |
|
||||
| 66 | `HealthTracker.get_status_summary()` | — | Не вызывается ниоткуда |
|
||||
| 67 | Резолвинг `hermes_home` | — | Продублирован в 5 файлах с тремя разными реализациями (`HERMES_HOME` / `LOCALAPPDATA` / `~/.hermes`) |
|
||||
| 68 | `do_set_main`/`do_test_profile`/`do_delete_credentials` | — | Дублируются между `gui_server.py` и `hermes_hub_app.py` |
|
||||
|
||||
---
|
||||
|
||||
## 10. P2 — Документация
|
||||
|
||||
| # | Проблема |
|
||||
|---|---|
|
||||
| 69 | `docs/ARCHITECTURE.md` и `CHANGELOG.md` описывают «Edge App Mode», «FastAPI Backend :8765», «health check gate (HTTP 200)» — этой архитектуры больше нет |
|
||||
| 70 | `agents/AGENTS.md` и отчёты в `agents/done/` указывают Source of Truth `E:\Agent projects\hermes-hub`; ссылки вида `file:///E:/Agent%20projects/...` мертвы |
|
||||
| 71 | `docs/SECURITY_MODEL.md` не описывает фактическую подмену глобальной записи `gemini:antigravity` при маршрутизации |
|
||||
| 72 | Нет описания схемы `router_profiles.yaml` (единственный источник истины — код) и справочника CLI |
|
||||
| 73 | Отчёты в `agents/done/` содержат утверждения, не подтверждаемые кодом: «35/35 PASSED», «интерактивные переключатели параметров», «9/9 экранов», «иконка вшита в лаунчер». Требуется выравнивание с фактами |
|
||||
| 74 | UI жёстко на русском, CLI на английском, строки не вынесены — i18n невозможен |
|
||||
|
||||
---
|
||||
|
||||
## 11. P2–P3 — Функциональные пробелы
|
||||
|
||||
| # | Пробел |
|
||||
|---|---|
|
||||
| 75 | Настройки не сохраняются: `CTkSwitch`/`CTkOptionMenu` не привязаны к `self.settings` (нет ни одного `command=`), `_save_settings()` пишет неизменённый словарь |
|
||||
| 76 | Ни один параметр не влияет на роутер: `failover_attempts`, `session_affinity`, `auto_failover`, `model_timeout_sec`, `monitoring_interval_min` нигде не читаются |
|
||||
| 77 | Пути в «Настройках» указывают на `hermes\router_profiles.yaml` и `hermes\auth.json`; реальные — `hermes\config\router_profiles.yaml` и `hermes\agy_profiles\<id>\auth.json` |
|
||||
| 78 | Нет ручного назначения профиля на роль (только автоматическое) |
|
||||
| 79 | Экран «Маршрутизация» read-only: нельзя изменить цепочку failover |
|
||||
| 80 | Нельзя добавить или удалить слот — 16 профилей жёстко зашиты в коде |
|
||||
| 81 | Кнопка «Обновить модели» не вызывает `adapter.discover_models()` — показываются `preferred_models` из конфига |
|
||||
| 82 | Фоновый мониторинг здоровья отсутствует, хотя переключатель для него есть |
|
||||
| 83 | Нет уведомлений (tray/toast) об исчерпании квоты и переключении на резерв |
|
||||
| 84 | Нет экспорта/импорта конфигурации и резервного копирования профилей |
|
||||
| 85 | Нет истории квот и использования |
|
||||
| 86 | Журнал живёт в памяти (200 записей) и обнуляется при перезапуске; `hermes-hub.log` пишется, но не читается обратно |
|
||||
| 87 | В журнале нет поиска и фильтра по уровню (только по категории) |
|
||||
| 88 | Тема жёстко тёмная, нет клавиатурной навигации и доступности |
|
||||
| 89 | `compatibility.json` заявляет `linux`/`darwin`, но Credential Manager, пути и лаунчер — только Windows |
|
||||
|
||||
---
|
||||
|
||||
## 12. Рекомендуемый план работ
|
||||
|
||||
### Этап 1 — Вернуть работоспособность (P0, ~2 дня)
|
||||
1. Устанавливать `customtkinter`/`pillow` в venv Hermes из установщика; проверять импорт после установки и падать с внятной ошибкой.
|
||||
2. Исправить `get_profile_dir` (сделать `@staticmethod` либо импортировать функцию), добавить `import json` в мастер.
|
||||
3. Реализовать `AutoAssigner.auto_assign_all()` либо убрать кнопки.
|
||||
4. Ввести маркер ошибки в `agy_generate` (`{"error": …}` или исключение) и восстановить failover для Antigravity; покрыть регрессионным тестом.
|
||||
5. Починить мастер: статус `"failed"`, реальная валидация API-ключа, реальные модели, применение выбранной роли, обработчик `assign_role`.
|
||||
6. Прогонять `pytest` в изолированном `HERMES_HOME`; убрать запуск установщика и сетевые вызовы из тестов; сделать `test_setup_exe_exists` skip-ом при отсутствии `dist/`.
|
||||
|
||||
### Этап 2 — Корректность состояний и отзывчивость (P1, ~3 дня)
|
||||
7. Все сетевые операции — только в фоне; `after(0, …)` получает готовые данные (включая `_restore_status`).
|
||||
8. Параллельный опрос профилей с общим бюджетом времени вместо последовательного.
|
||||
9. Обновление виджетов in-place вместо `destroy()`/пересоздания в 5 экранах.
|
||||
10. Сброс `overall_state` при истечении cooldown; единый источник истины по здоровью для роутера и UI.
|
||||
11. TTL и вытеснение в `SessionAffinityTracker`; файловая блокировка `router_state.json`.
|
||||
12. Сузить `_CM_LOCK` до операции подмены credential и восстанавливать прежнюю запись — либо перейти на изоляцию только через `USERPROFILE`.
|
||||
13. Связать `settings_view` с реальными параметрами роутера; выровнять пути.
|
||||
14. Починить `check_duplicate_identity` (сохранять `email` в `auth.json` при OAuth).
|
||||
|
||||
### Этап 3 — Безопасность и упаковка (P1, ~2 дня)
|
||||
15. Решение по OAuth-клиенту; прекратить склейку секрета из фрагментов.
|
||||
16. ACL/DPAPI для `auth.json`; санитизация логов; убрать токен из query-строки.
|
||||
17. Убрать `allow_origins=["*"]` либо удалить веб-стек целиком.
|
||||
18. Выбрать один установщик; реальная проверка версии из `compatibility.json` (сделать код 11 достижимым); исключить `__pycache__`; убрать `E:/…` fallback; встроить иконку через `/win32icon`.
|
||||
19. Логирование запуска в файл до инициализации UI; single-instance guard.
|
||||
|
||||
### Этап 4 — Долг и документация (P2, ~2 дня)
|
||||
20. Подключить roadmap-модули к реальным кодовым путям (как минимум зарегистрировать `DeepSeekResponsesAdapter` в `_ADAPTERS`) либо явно пометить их как экспериментальные.
|
||||
21. Удалить или вынести в `legacy/` веб-стек и прямой API-путь; убрать `fastapi`/`uvicorn` из обязательных зависимостей.
|
||||
22. Единый модуль `paths.py`; устранить дублирование `do_*`.
|
||||
23. Синхронизировать версию (0.1.0), обновить `ARCHITECTURE.md`/`CHANGELOG.md` под нативную архитектуру, исправить пути `E:\`.
|
||||
24. Выровнять отчёты в `agents/done/` с фактическим состоянием.
|
||||
25. Добавить CI (pytest + ruff) и конфигурацию ruff.
|
||||
|
||||
### Этап 5 — Развитие (P3)
|
||||
26. Ручное назначение ролей и редактор цепочек failover.
|
||||
27. Динамические слоты, реальный `discover_models`.
|
||||
28. Фоновый мониторинг + уведомления о квотах.
|
||||
29. Персистентный журнал с поиском; статистика использования.
|
||||
30. Стриминг ответов, учёт токенов и стоимости.
|
||||
31. Экспорт/импорт конфигурации, бэкап профилей.
|
||||
|
||||
---
|
||||
|
||||
## 13. Сводка
|
||||
|
||||
| Приоритет | Пунктов | Область |
|
||||
|---|---|---|
|
||||
| **Исправлено в `7609ad8`** | 8 | Кэш сканирования, resolver статусов, идентичность Windows, предпрогрев, assets, docs |
|
||||
| **P0** — блокеры | 9 | Запуск приложения, failover Antigravity, падения UI, мастер подключения |
|
||||
| **P1** — критично | 51 | Отзывчивость, корректность состояний, безопасность, тесты, упаковка |
|
||||
| **P2** — важно | 20 | Неподключённый код, архитектурный долг, документация |
|
||||
| **P3** — развитие | 9 | Новая функциональность |
|
||||
| **Итого к работе** | **89** | |
|
||||
150
agents/inbox/2026-08-20-review-p0-release-blockers.md
Normal file
150
agents/inbox/2026-08-20-review-p0-release-blockers.md
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# Ревью: `2a97e80` + `7926de9` (закрытие P0 и релизный конвейер)
|
||||
|
||||
**Ревьюер:** Claude (роль «Ревьюер»)
|
||||
**База:** `5ccfd48` → **проверено:** `7926de9`
|
||||
**Метод:** построчный разбор диффа + исполняемая проверка каждого утверждения в изолированном `HERMES_HOME`
|
||||
|
||||
**Вердикт: ⚠️ Принять нельзя — 4 блокирующих замечания.** Из 9 P0 фактически закрыто 8; один закрыт с регрессией. Дополнительно найдено 4 дефекта в новом коде.
|
||||
|
||||
---
|
||||
|
||||
## 1. Подтверждено закрытым (проверено исполнением)
|
||||
|
||||
| P0 | Проверка | Итог |
|
||||
|---|---|---|
|
||||
| №2 `get_profile_dir` | `hasattr(ProfileAuthManager,'get_profile_dir')` → `True`, поддержаны обе сигнатуры | ✅ |
|
||||
| №3 `json` в мастере | импорт присутствует, `_save_key` отрабатывает | ✅ |
|
||||
| №4 `auto_assign_all` | метод существует, выполняется | ✅ (см. B2) |
|
||||
| №6 статусы OAuth | `("error","failed","cancelled")` + `"timeout"` — `add_account_wizard.py:265` | ✅ |
|
||||
| №7 `assign_role` | обработчик + модалка `hermes_hub_app.py:408,419` | ✅ |
|
||||
| №8 роль из шага 4 | вызывается `assign_profile_to_role` | ✅ (см. B2) |
|
||||
| №9 валидация ключа | реальные `verify_codex_token` / `verify_opencode_token`, честная метка «НЕ ПРОВЕРЕН» | ✅ |
|
||||
| №1 зависимости UI | `HermesHubSetup.py:52` ставит пакеты в venv Hermes + проверка импорта. Пины `customtkinter>=6.0.0`, `pillow>=12.3.0` сверены с PyPI — версии существуют | ✅ |
|
||||
|
||||
**Сверх P0 — зачтено:** единая версия `0.1.1` (`version.py` → pyproject → compatibility.json → About); `paths.py` вместо пяти реализаций резолвинга и захардкоженного `E:/Agent projects`; сброс `overall_state` по истечении cooldown (проверено: `overall=healthy` при `family=quota-exhausted`, после истечения — оба healthy); типизированные исключения вместо угадывания по подстрокам на уровне роутера; `conftest.py` с изоляцией `HERMES_HOME` (проверено — работает); маркеры pytest и `addopts` с отключением `installer/live/network`; CI и `release_gate.py`.
|
||||
|
||||
**Тесты:** `41 passed, 1 failed, 3 deselected`. Единственный failed — `test_p0_1_installer_dependencies`, потому что в venv Hermes на этой машине `customtkinter` ещё не установлен (установщик не запускался). Регрессионный тест на failover `test_p0_5_antigravity_failover_on_quota` присутствует и проходит.
|
||||
|
||||
---
|
||||
|
||||
## 2. Блокирующие замечания
|
||||
|
||||
### B1. Регрессия: не-роутерный путь падает с `IndexError`
|
||||
|
||||
`agy_subprocess.py:584` теперь возвращает `{"error": {...}}` без `choices`. Для роутера это правильно, но `_error_completion` обслуживает **два** пути, а `hermes_plugin.py` не менялся.
|
||||
|
||||
Путь 2 (`hermes_plugin.antigravity_llm_execution`, когда роутер отключён `config.enabled=false` или выбросил исключение):
|
||||
```
|
||||
completion = agy_generate(request) # -> {"error": {...}}
|
||||
return openai_completion_object(completion) # -> choices = []
|
||||
```
|
||||
|
||||
Проверено исполнением:
|
||||
```
|
||||
после openai_completion_object: choices = []
|
||||
обращение Hermes к choices[0]: IndexError: list index out of range
|
||||
```
|
||||
|
||||
Было: пользователь видел текст ошибки как ответ ассистента. Стало: падение транспорта Hermes.
|
||||
|
||||
**Требуется:** обработать `{"error": …}` в `antigravity_llm_execution` — либо конвертировать в completion с текстом ошибки, либо пробрасывать типизированное исключение. Тест на путь с `enabled=false` обязателен.
|
||||
|
||||
---
|
||||
|
||||
### B2. `assign_profile_to_role` создаёт фиктивные роли и не меняет реальные
|
||||
|
||||
`auto_assigner.py:assign_profile_to_role` вызывает `config.get_role_policy(role_name)`, а тот для неизвестной роли возвращает **generic-политику с `preferred_chain = list(self.profiles.keys())`** — все 16 профилей. Результат сохраняется в конфиг.
|
||||
|
||||
При этом `auto_assign_all` раздаёт роли из списка `["orchestrator","coder","reviewer","researcher","tester","general"]`, а мастер — `coder/researcher/general/spare`. Реальные роли роутера: `orchestrator`, `coder-primary`, `coder-secondary`, `reviewer`, `research`, `fast`. Совпадают только `orchestrator` и `reviewer`.
|
||||
|
||||
Проверено в изолированном окружении:
|
||||
```
|
||||
роли ДО : [coder-primary, coder-secondary, fast, orchestrator, research, reviewer]
|
||||
роли ПОСЛЕ: [coder, coder-primary, coder-secondary, fast, orchestrator, research, reviewer]
|
||||
|
||||
длина цепочки роли coder : 16
|
||||
цепочка coder : [ag-w1, codex-orch, ..., ag-cold-1, ag-cold-2, ag-cold-3, opengo-1, ...]
|
||||
цепочка coder-primary : [codex-worker-1, ag-w1, opengo-3] <- НЕ ИЗМЕНИЛАСЬ
|
||||
```
|
||||
|
||||
Итог: назначение роли не влияет на маршрутизацию, а конфиг засоряется мусорными ролями с цепочками из 16 профилей, включая отключённые холодные резервы.
|
||||
|
||||
`test_p0_4` и `test_p0_7` этого не ловят, потому что проверяют механику («выполнилось без `AttributeError`», «цепочка изменилась и сохранилась»), а не семантику — что имя роли маршрутизируемое.
|
||||
|
||||
**Требуется:** маппинг человекочитаемых имён на реальные (`coder` → `coder-primary`, `researcher` → `research`, `tester`/`general` → `fast` или отдельная роль), отказ при неизвестной роли, и запрет на persist generic-политики со всеми профилями. В тесты — проверка, что `role_name in config.roles` **до** назначения.
|
||||
|
||||
---
|
||||
|
||||
### B3. Каждое назначение роли уничтожает пользовательский YAML
|
||||
|
||||
`save_router_config` пишет схему, отличную от шаблона. Раньше это срабатывало редко, теперь — при **каждом** подключении аккаунта через мастер и при каждом `auto_assign_all`.
|
||||
|
||||
Проверено на копии `config/router_profiles.example.yaml`:
|
||||
```
|
||||
шаблон: 5 строк комментариев, блок router: 1
|
||||
после одного назначения роли: 0 строк комментариев, блок router: 0
|
||||
```
|
||||
|
||||
Блок `router:` (`max_failover_attempts`, `cooldown_base_seconds`, `cooldown_max_seconds`, `session_affinity_ttl_seconds`) загрузчиком и так игнорируется — но пользователь, отредактировавший его, молча теряет правки.
|
||||
|
||||
**Требуется:** либо привести `load/save` к одной схеме и покрыть тестом round-trip (`load → save → load` без потерь), либо перейти на редактирование с сохранением структуры.
|
||||
|
||||
---
|
||||
|
||||
### B4. Rate limit классифицируется как исчерпание квоты — 30 минут вместо 60 секунд
|
||||
|
||||
`antigravity_adapter.py:invoke` — первая ветка ловит `("quota","resource_exhausted","429","limit","exhausted")`, ветка `"rate"` идёт **после** и для реального 429 недостижима: сообщение «429 Too Many Requests: rate limit exceeded» содержит и `429`, и `limit`.
|
||||
|
||||
Проверено исполнением:
|
||||
```
|
||||
вход : Antigravity error: agy error: 429 Too Many Requests: rate limit exceeded
|
||||
итог : QuotaExceededError -> category=quota-exhausted cooldown=1800s
|
||||
```
|
||||
|
||||
Профиль паркуется на 30 минут вместо 60 секунд. При цепочке из трёх профилей серия временных 429 выводит роль в «Failover Exhausted».
|
||||
|
||||
**Требуется:** проверять `rate`/`too many requests`/`429` **до** квотной ветки, либо различать по коду ответа, а не по подстроке.
|
||||
|
||||
---
|
||||
|
||||
## 3. Существенные замечания (не блокируют, но лучше в этом же заходе)
|
||||
|
||||
**S1. Потеряно время сброса квоты.** Типизированные исключения обрабатываются в начале `classify_error` и используют `exc.reset_in_sec or 1800`, но `reset_in_sec` при выбросе в `invoke` не заполняется. Regex-разбор («resets in 2h» → 7200 s) остался ниже и для типизированных исключений недостижим. Проверено: «individual quota reached, resets in 2h» → `cooldown=1800s`. Раньше было 7200. Прокидывайте распарсенную длительность в конструктор исключения.
|
||||
|
||||
**S2. Встроенный апдейтер нерабочий.** `update_manager.py:82` тянет манифест с `raw.githubusercontent.com/ochenstarik-ui/hermes-hub/main/dist/update_manifest.json`. Файл не отслеживается git (`dist/` в `.gitignore`), репозиторий приватный. Проверено: `HTTP 404`. Проверка обновлений всегда будет падать. Нужен публичный канал (GitHub Releases API) или отдельная ветка/репозиторий манифестов.
|
||||
|
||||
**S3. Апдейтер проверяет целостность, но не подлинность.** SHA-256 берётся из того же манифеста, что и `package_url`, — при подмене манифеста хеш подменяется вместе с пакетом. Плюс `HERMES_HUB_UPDATE_URL` переопределяется переменной окружения, а `package_url` скачивается без allowlist хостов. Для механизма, который распаковывает код в рабочий каталог и затем исполняется, нужна подпись пакета либо жёсткая привязка к домену релизов. (`extractall` от Zip Slip защищён — CPython нормализует пути, здесь претензий нет.)
|
||||
|
||||
**S4. `release_gate.check_security_zero_secrets` даёт ложное «0 секретов».** Проверка ищет отслеживаемые **файлы**-секреты и не видит `CLIENT_SECRET` в `oauth.py:28`, склеенный из фрагментов строк специально для обхода сканеров. Гейт будет зелёным при наличии секрета в коде.
|
||||
|
||||
**S5. `py_compile` по `dest.rglob("*.py")`** (`update_manager.py:194`) пройдёт по `.venv`, если апдейт применяется к каталогу разработки, — это тысячи файлов и вероятные ложные сбои с откатом. Ограничьте обход каталогом `src`.
|
||||
|
||||
**S6. Настройки по-прежнему не сохраняются.** Ни один `CTkSwitch`/`CTkOptionMenu` в `settings_view.py` (строки 78, 87, 96, 116, 124) не имеет `command=`/`variable=`, `_save_settings()` пишет неизменённый словарь дефолтов. Кнопка «💾 Сохранить» ничего не сохраняет. В отчёте это заявлено как «интерактивные переключатели» — расхождение.
|
||||
|
||||
**S7. `test_installer` всё ещё меняет систему.** Песочница через `LOCALAPPDATA`/`HERMES_HOME` в env закрывает файловое дерево, но `CreateStartMenuShortcut` пишет в `%APPDATA%\...\Start Menu`, а `RegisterInWindowsUninstall` — в `HKCU`. Ни то, ни другое не перенаправляется. Смягчено маркером `installer` и `addopts`, но при `-m installer` система снова будет изменена.
|
||||
|
||||
**S8. Отсутствие UI-зависимости роняет весь прогон.** `test_ui_refinement.py:11` импортирует `hermes_hub_app` на уровне модуля → `ModuleNotFoundError: customtkinter` прерывает сбор всей сессии («Interrupted: 1 error during collection»), а не пропускает один модуль. Нужен `pytest.importorskip("customtkinter")`.
|
||||
|
||||
**S9. Мастер не останавливает опрос при закрытии окна.** `_polling_active` сбрасывается только при переходе на шаг 3 (`add_account_wizard.py:279`). Закрытие модалки во время OAuth оставляет поток жить до 120 с, и он вызовет `self.after(0, …)` на уничтоженном виджете. Нужен `destroy()`/`WM_DELETE_WINDOW` со сбросом флага.
|
||||
|
||||
---
|
||||
|
||||
## 4. Осталось открытым из аудита (вне scope этого коммита)
|
||||
|
||||
Не претензия к коммиту — фиксирую, что эти пункты не затронуты:
|
||||
|
||||
- `_CM_LOCK` по-прежнему удерживается на всё время subprocess `agy` (`antigravity_adapter.py:invoke`) → профили Antigravity сериализуются.
|
||||
- Глобальная запись `gemini:antigravity` перезаписывается без восстановления → «основной аккаунт Hermes» меняется как побочный эффект маршрутизации.
|
||||
- Session affinity без TTL; `router_state.json` без межпроцессной блокировки.
|
||||
- `hermes_plugin` перехватывает все вызовы `llm_execution` и не вызывает `next_call`.
|
||||
- 5 экранов пересоздают виджеты через `destroy()`; нет периодического автообновления.
|
||||
- Roadmap-модули (`supervisor`, `capability`, `skills`, `scheduler`, `deepseek_adapter`) по-прежнему не подключены; `DeepSeekResponsesAdapter` не зарегистрирован в `_ADAPTERS`.
|
||||
- Веб-стек `gui_server.py` + `gui_cockpit.html` и прямой API-путь остаются мёртвыми; `fastapi`/`uvicorn` — в обязательных зависимостях.
|
||||
|
||||
---
|
||||
|
||||
## 5. Резюме для исполнителя
|
||||
|
||||
Порядок правок: **B1** (регрессия, ломает работающий сценарий) → **B4** (однострочная перестановка веток) → **B2** + **B3** (связаны: маппинг ролей и сохранение конфига) → S1, S8, S9 (дёшево) → S2–S5 (апдейтер, до объявления релиза).
|
||||
|
||||
Релизным гейтом это пропускать нельзя: `check_p0_release_gate` рапортует «9/9 P0 verified», хотя P0-5 закрыт с регрессией, а P0-4/P0-7 проходят на фиктивных ролях.
|
||||
104
agents/inbox/2026-08-20-review-round2-65482e8.md
Normal file
104
agents/inbox/2026-08-20-review-round2-65482e8.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# Ревью, раунд 2: `65482e8` (ответ на замечания B1–B4, S1–S9)
|
||||
|
||||
**База:** `7926de9` → **проверено:** `65482e8`
|
||||
**Метод:** повтор тех же исполняемых проверок, что выявили дефекты в раунде 1
|
||||
|
||||
**Вердикт: ✅ Блокеры закрыты, коммит принимается в `main`.**
|
||||
**⚠️ Релиз v0.1.1 объявлять рано** — канал обновлений не работает, а релизный гейт даёт ложно-зелёный результат по секретам.
|
||||
|
||||
---
|
||||
|
||||
## 1. Блокеры — все 4 закрыты (проверено повтором)
|
||||
|
||||
**B1 — не-роутерный путь.** Защита в двух местах: guard в `hermes_plugin.antigravity_llm_execution` (обе ветки) и в самом `openai_completion_object`. Повтор моей проверки:
|
||||
```
|
||||
choices: 1
|
||||
choices[0].message.content: Antigravity error: ... individual quota reached
|
||||
```
|
||||
Раньше — `IndexError`. Закрыто.
|
||||
|
||||
**B2 — фиктивные роли.** Введён `CANONICAL_ROLE_MAP`, неизвестные роли отклоняются:
|
||||
```
|
||||
coder -> ok=True назначен на роль 'coder-primary'
|
||||
researcher -> ok=True назначен на роль 'research'
|
||||
tester -> ok=True назначен на роль 'fast'
|
||||
general -> ok=True назначен на роль 'fast'
|
||||
spare -> ok=False Неизвестная роль маршрутизатора: 'spare'
|
||||
|
||||
роли ПОСЛЕ: без изменений (мусорные роли не создаются)
|
||||
цепочка coder-primary: ['ag-w1', 'codex-worker-1', 'opengo-3'] <- реально обновилась
|
||||
макс. длина цепочки: 4 (было 16)
|
||||
```
|
||||
Закрыто.
|
||||
|
||||
**B3 — разрушение YAML.** Схема `load`/`save` сведена. Блок `router:` сохраняется целиком со всеми значениями (`enabled`, `default_role`, `max_failover_attempts`, `cooldown_base_seconds`, `cooldown_max_seconds`, `session_affinity_ttl_seconds`); профилей 16/16, ролей 6/6, cooldown'ы не поплыли. Закрыто по существу — см. остаточное R3.
|
||||
|
||||
**B4 — 429 как квота.** Порядок веток исправлен:
|
||||
```
|
||||
429 Too Many Requests: rate limit exceeded -> RateLimitedError rate-limited retry_delay=60s
|
||||
individual quota reached, resets in 2h -> QuotaExceededError quota-exhausted cooldown=7200s
|
||||
```
|
||||
Закрыто (заодно S1 — длительность сброса снова парсится: 7200 вместо 1800).
|
||||
|
||||
---
|
||||
|
||||
## 2. Второстепенные — закрыто 5 из 8
|
||||
|
||||
| | Статус |
|
||||
|---|---|
|
||||
| S1 время сброса квоты | ✅ 7200 s для «resets in 2h» |
|
||||
| S5 `py_compile` по `.venv` | ✅ обход ограничен `target_src` |
|
||||
| S6 настройки не сохранялись | ✅ `_save_settings` читает состояние виджетов (`aff_sw.get()` и др.) |
|
||||
| S8 сбор pytest падал целиком | ✅ `pytest.importorskip("customtkinter")`; прогон: **44 passed, 2 skipped, 3 deselected** |
|
||||
| S9 опрос OAuth после закрытия | ✅ переопределён `destroy()` со сбросом `_polling_active` |
|
||||
| **S2 канал обновлений** | ❌ не исправлено |
|
||||
| **S3 подлинность пакета** | ❌ не адресовано |
|
||||
| **S4 слепая проверка секретов** | ❌ не исправлено |
|
||||
| S7 тест установщика меняет систему | ❌ не адресовано (риск низкий: `installer` deselected по умолчанию) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Новое, найденное в этом раунде
|
||||
|
||||
### N1. Мастер: «Только резерв (Spare)» молча не срабатывает, а журнал рапортует успех
|
||||
|
||||
`add_account_wizard.py:373` предлагает вариант `spare`, строка 394 маппит его в `"spare"`, строка 401 вызывает `assign_profile_to_role(..., "spare", ...)`. После правки B2 эта роль **отклоняется** (`ok=False`), но возвращаемое значение игнорируется, и следом безусловно пишется:
|
||||
|
||||
```python
|
||||
EventLogService.get().log("account", f"... Назначен на роль: {target_role}.", level="success")
|
||||
```
|
||||
|
||||
Пользователь выбирает «Только резерв», видит успех — назначения не происходит. До B2 создавалась мусорная роль, теперь не делается ничего; в обоих случаях функция не работала, но раньше отказ был хотя бы виден в конфиге.
|
||||
|
||||
**Нужно:** либо добавить `spare` в `CANONICAL_ROLE_MAP` с осмысленной семантикой (добавление в хвост цепочки нескольких ролей), либо проверять результат и показывать пользователю отказ. `auto_assign_all` возвращаемое значение проверяет (`if ok:`) — здесь надо так же.
|
||||
|
||||
### N2. Двойной префикс в тексте ошибки (косметика)
|
||||
|
||||
`_error_completion` уже добавляет `"Antigravity error: "`, guard в `hermes_plugin`/`runtime` добавляет его повторно → `Antigravity error: Antigravity error: individual quota reached`.
|
||||
|
||||
### N3. Релизный гейт зелёный на непроверенных вещах
|
||||
|
||||
- Пункт «3. Auto-Updater & Rollback → PASS»: ни один тест в `test_updater.py` не обращается к реальному манифесту — все работают на инъецированном словаре или локальных файлах. Реальный канал при этом отдаёт **HTTP 404** (проверено). Гейт подтверждает механику, но не работоспособность обновления.
|
||||
- Пункт «6. Zero Credentials & Secrets → PASS»: проверка ищет файлы (`auth.json`, `*.key`, `.env*`) и живые ключи по regex `sk-…`/`opencode-…`. `CLIENT_SECRET` в `oauth.py:29`, склеенный из фрагментов именно для обхода сканеров, не обнаруживается. Гейт будет зелёным при секрете в коде.
|
||||
|
||||
---
|
||||
|
||||
## 4. Остаточное (не блокирует merge, блокирует релиз)
|
||||
|
||||
**R1 (S2). Встроенный апдейтер нерабочий.** `update_manager.py:82` по-прежнему указывает на `raw.githubusercontent.com/ochenstarik-ui/hermes-hub/main/dist/update_manifest.json`. Проверено сейчас: `HTTP 404`, файл в git не отслеживается (`dist/` в `.gitignore`), репозиторий приватный. Пока не появится публичный канал (GitHub Releases API или отдельный репозиторий манифестов), «Проверить обновления» всегда будет падать.
|
||||
|
||||
**R2 (S3). Только целостность, без подлинности.** SHA-256 берётся из того же манифеста, что и `package_url`; `HERMES_HUB_UPDATE_URL` переопределяется переменной окружения; хост `package_url` не ограничен allowlist'ом. Для механизма, распаковывающего исполняемый код в рабочий каталог, нужна подпись пакета либо жёсткая привязка к домену релизов.
|
||||
|
||||
**R3 (из B3). Комментарии в YAML по-прежнему стираются:** 5 строк → 0 при первом сохранении. Данные и структура больше не теряются, но пользовательские пояснения — да. Решается либо переходом на `ruamel.yaml` с сохранением комментариев, либо явным предупреждением в документации, что файл перезаписывается приложением.
|
||||
|
||||
**R4.** Сохранённые настройки (`hub_settings.json`) по-прежнему никем не читаются: `failover_attempts`, `session_affinity`, `auto_failover`, `model_timeout_sec`, `monitoring_interval_min` не влияют на роутер. Переключатели теперь честно сохраняются — но эффекта не дают.
|
||||
|
||||
**Из аудита без изменений:** `_CM_LOCK` на всё время subprocess; глобальная запись `gemini:antigravity` без восстановления; session affinity без TTL; `router_state.json` без межпроцессной блокировки; roadmap-модули не подключены; веб-стек мёртв.
|
||||
|
||||
---
|
||||
|
||||
## 5. Рекомендация
|
||||
|
||||
1. **Merge `65482e8` в `main`** — блокеры закрыты, регрессий не внесено, тесты зелёные.
|
||||
2. **До объявления v0.1.1:** закрыть N1 (одна строка + проверка результата), поднять реальный канал обновлений (R1) либо скрыть кнопку «Проверить обновления», научить гейт видеть склеенные секреты (N3/S4) — иначе «RELEASE GATE: PASSED» вводит в заблуждение.
|
||||
3. N2, R3, R4 — в обычную очередь.
|
||||
100
agents/inbox/2026-08-20-review-round3-42dfe2c.md
Normal file
100
agents/inbox/2026-08-20-review-round3-42dfe2c.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# Ревью, раунд 3: `925e6c6` → `42dfe2c` (N1–N3, S2–S4, R1–R4)
|
||||
|
||||
**База:** `65482e8` → **проверено:** `42dfe2c` (4 коммита)
|
||||
**Тесты:** 50 passed, 2 skipped, 3 deselected. **Гейт:** PASSED (Candidate v0.1.1).
|
||||
|
||||
**Вердикт: ✅ Заявленное сделано и проверено. ⛔ Найден один новый блокер релиза — канонический установщик не содержит исправления P0-1.**
|
||||
|
||||
---
|
||||
|
||||
## 1. Закрыто (проверено исполнением)
|
||||
|
||||
**N1 — «Только резерв».** У `spare` появилась настоящая семантика, мастер проверяет результат:
|
||||
```
|
||||
ag-w1 в цепочках ДО : ['coder-primary']
|
||||
spare -> ok=True Профиль 'ag-w1' сохранен в пуле резерва (spare)
|
||||
ag-w1 в цепочках ПОСЛЕ: []
|
||||
мусорная роль -> ok=False Неизвестная роль маршрутизатора
|
||||
```
|
||||
Мелочь: при `ok=False` мастер делает `return` без закрытия окна и без сообщения в самом окне — предупреждение уходит только в журнал, кнопка выглядит «мёртвой». Лучше показать текст ошибки в модалке.
|
||||
|
||||
**N2 — двойной префикс.** `Antigravity error: individual quota reached` — один префикс.
|
||||
|
||||
**N3 / S4 — сканер секретов.** AST-обход с раскрытием конкатенаций и `''.join(...)`, allowlist для документированных публичных констант. Проверил синтетическим файлом:
|
||||
```
|
||||
CLIENT_SECRET = "".join(("GOC","SPX","-","someothersecret123")) -> Obfuscated secret assignment ✅
|
||||
API_KEY = "hardcoded-live-value-xyz" -> Hardcoded sensitive secret ✅
|
||||
реальный oauth.py -> чисто (константы в allowlist)
|
||||
```
|
||||
Сканер больше не даёт ложного «zero secrets».
|
||||
|
||||
**OAuth — деобфускация.** Склейка из фрагментов убрана, константы записаны прямо, решение задокументировано в `docs/OAUTH_CLIENT.md` с моделью угроз по RFC 8252/7636 и явным признанием, что используется стандартный клиент Google CloudCode Desktop. Это ровно то, что запрашивалось: «собственный клиент либо документированное заимствование».
|
||||
|
||||
**S3 — allowlist хостов.** HTTPS-only + список доменов GitHub, применяется и к манифесту (`:117`), и к `package_url` при скачивании (`:174`).
|
||||
|
||||
**R1 / S2 — канал обновлений.** Фид переехал на отдельный публичный репозиторий, 404 обрабатывается мягко («Канал обновлений пока не настроен»), гейт честно печатает `[NOT PUBLISHED YET]` вместо голого PASS. Формулировка сменилась на «Ready for Candidate».
|
||||
|
||||
**R4 — настройки влияют на роутер.** `settings_service.py` + чтение в `route_request`. Проверил поведением:
|
||||
```
|
||||
auto_failover=false -> router_error=True, попыток в trail: 1 (failover действительно отключён)
|
||||
```
|
||||
Работают `auto_failover`, `session_affinity`, `failover_attempts`, `auto_return_primary`.
|
||||
|
||||
---
|
||||
|
||||
## 2. ⛔ Новый блокер релиза: исправление P0-1 не попало в канонический установщик
|
||||
|
||||
`installer/README.md` (новый файл) объявляет:
|
||||
|
||||
> **Canonical Binary:** `HermesHubSetup.exe` (built from `HermesHubSetup.cs`)
|
||||
> `HermesHubSetup.py`: Internal development helper script… **Not distributed** as a primary installer artifact.
|
||||
> `install.ps1`: Deprecated legacy bootstrap script.
|
||||
|
||||
При этом проверка кода:
|
||||
|
||||
| | `HermesHubSetup.cs` (канонический) | `HermesHubSetup.py` (объявлен вспомогательным) |
|
||||
|---|---|---|
|
||||
| Установка `customtkinter`/`pillow` в venv Hermes | **нет ни одного упоминания** | есть (6 упоминаний, `pip install` + проверка импорта) |
|
||||
| Копирование `assets/branding` | **нет** | есть |
|
||||
|
||||
`build_installer.ps1` собирает `dist/HermesHubSetup.exe` именно из `.cs`.
|
||||
|
||||
Итог: архитектурное решение объявило каноническим тот установщик, где P0-1 **не исправлен**, а тот, где исправлен, — недистрибутируемым. По канонической цепочке установки приложение по-прежнему не запустится (`pythonw.exe` молча завершится на `import customtkinter`), и логотипов не будет.
|
||||
|
||||
Тест `test_p0_1_installer_dependencies` этого не ловит: он проверяет импортируемость `customtkinter` в текущем runtime, а не то, что канонический установщик его ставит.
|
||||
|
||||
**Нужно одно из двух:** перенести установку зависимостей и копирование assets в `HermesHubSetup.cs`, либо объявить каноническим `.py` и пересобрать конвейер под него. Плюс тест, проверяющий именно канонический артефакт.
|
||||
|
||||
---
|
||||
|
||||
## 3. Заявлено, но кодом не подтверждено
|
||||
|
||||
**S7.** В таблице отчёта: «unit tests isolated from Windows Registry/Start Menu». Фактически `tests/test_installer.py` **не менялся** с раунда 1 и по-прежнему делает `subprocess.run([SETUP_EXE, "/silent"])` (строки 53, 68). Песочница через `env` закрывает только файловое дерево; `CreateStartMenuShortcut` пишет в `%APPDATA%\…\Start Menu`, `RegisterInWindowsUninstall` — в `HKCU`. Это политика в `installer/README.md`, а не изоляция в коде. Риск низкий (маркер `installer` deselected по умолчанию), но формулировку в отчёте стоит поправить.
|
||||
|
||||
---
|
||||
|
||||
## 4. Мелкие замечания
|
||||
|
||||
**M1.** `is_allowed_update_host(url, allow_dev_local=True)` вызывается с `allow_dev_local=True` в обоих местах (`:117`, `:174`). Внутри: `if url.startswith("file://") or Path(url).exists(): return allow_dev_local or os.environ.get("HERMES_HUB_DEV_MODE") == "1"`. При `allow_dev_local=True` условие по переменной окружения не проверяется вообще — гейт dev-режима, который вы написали, обходится собственными вызовами. Локальный путь в `package_url` пройдёт allowlist в продакшене.
|
||||
|
||||
**M2.** Гейт: заголовок «2. P0 Release Blockers (**16/16**)», сообщение — «**12/12** P0 release blockers verified». Косметическое расхождение.
|
||||
|
||||
**M3.** `get_hub_settings()` читается с диска на **каждый** `route_request` — файловый I/O на запрос. Стоит кэшировать с инвалидацией по mtime.
|
||||
|
||||
**M4.** Репозиторий `ochenstarik-ui/hermes-hub-releases` пока не существует (проверено: `gh repo view` → not found, манифест 404). Ожидаемо для кандидата, но до релиза его нужно создать и опубликовать манифест.
|
||||
|
||||
---
|
||||
|
||||
## 5. Остаточное из прошлых раундов
|
||||
|
||||
- **R3.** Комментарии в `router_profiles.yaml` по-прежнему стираются при сохранении (5 → 0). Данные и структура целы.
|
||||
- **R4 частично.** `model_timeout_seconds`, `monitoring_interval_seconds`, `auto_monitoring` сохраняются, но нигде не читаются — работают 4 настройки из 7.
|
||||
- Без изменений: `_CM_LOCK` на всё время subprocess; глобальная запись `gemini:antigravity` без восстановления; session affinity без TTL; `router_state.json` без межпроцессной блокировки; roadmap-модули не подключены; веб-стек мёртв.
|
||||
|
||||
---
|
||||
|
||||
## 6. Рекомендация
|
||||
|
||||
1. **Merge — да.** Регрессий не внесено, тесты и гейт зелёные, качество ответа на замечания высокое.
|
||||
2. **Кандидатом v0.1.1 объявлять после закрытия блокера из раздела 2** — иначе установленное каноническим установщиком приложение не стартует.
|
||||
3. M1 — быстро и по делу (убрать `allow_dev_local=True` из продакшен-вызовов). M2, M3, S7-формулировка — в обычную очередь.
|
||||
120
agents/inbox/2026-08-20-review-round4-verdict.md
Normal file
120
agents/inbox/2026-08-20-review-round4-verdict.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# Ревью, раунд 4: вердикт по кандидату v0.1.1
|
||||
|
||||
**Отчёт исполнителя описывает `8314d46`. Фактический `origin/main` — `3aae1a8`, на 3 коммита впереди.**
|
||||
Проверка проводилась по фактическому HEAD.
|
||||
|
||||
**Вердикт: ⛔ тег `v0.1.1` не выпускать.** Заявленное в отчёте сделано и подтверждено, но на текущем HEAD релизный гейт **падает**, а публичный фид обновлений указывает на несуществующий пакет.
|
||||
|
||||
---
|
||||
|
||||
## 1. Заявленное в отчёте — подтверждаю
|
||||
|
||||
| Пункт | Проверка | Итог |
|
||||
|---|---|---|
|
||||
| Канонический `.cs`: `EnsurePythonDependencies` | `installer/HermesHubSetup.cs:89` — проверка импорта → `ensurepip` → `pip` → фолбэк на `uv` → повторная верификация | ✅ |
|
||||
| Канонический `.cs`: копирование assets | строки 263–269, три целевых каталога (install dir, плагин, hermes home) | ✅ |
|
||||
| Post-install smoke test | строка 303, `HERMES_HUB_IMPORT_OK`, проверяется ExitCode и наличие маркера | ✅ |
|
||||
| M1 — dev-bypass | `allow_dev_local` по умолчанию `False`, оба продакшен-вызова передают `False` явно (`:130`, `:187`) | ✅ |
|
||||
| M2 — счётчик гейта | `16/16` в заголовке и в сообщении согласованы | ✅ |
|
||||
| M3 — кэш настроек | mtime-кэш + `invalidate_settings_cache()` при сохранении | ✅ |
|
||||
| Публичный фид | `HTTP 200`, манифест валидный, v0.1.1 | ✅ |
|
||||
|
||||
Блокер раунда 3 (исправление P0-1 не в каноническом установщике) **закрыт**.
|
||||
|
||||
---
|
||||
|
||||
## 2. ⛔ Блокер 1: релизный гейт падает на текущем HEAD
|
||||
|
||||
Отчёт цитирует `[RELEASE GATE: PASSED]` — это результат для `8314d46`. На `3aae1a8` фактически:
|
||||
|
||||
```
|
||||
[RELEASE GATE: FAILED] One or more checks failed. Release blocked.
|
||||
```
|
||||
|
||||
Причина — новый `tests/test_codex_opencode_wizard.py` из коммита `3aae1a8` импортирует `customtkinter` на уровне модуля **без `pytest.importorskip`**, и сбор всей сессии прерывается:
|
||||
|
||||
```
|
||||
ERROR tests/test_codex_opencode_wizard.py
|
||||
!!! Interrupted: 1 error during collection !!!
|
||||
1 skipped, 3 deselected, 1 error
|
||||
```
|
||||
|
||||
Падают пункты гейта 2, 3 и 4. Это возврат ровно того дефекта, который закрывался как **S8** в раунде 2: отсутствие опциональной UI-зависимости должно давать *skip*, а не обрывать прогон. Остальные три тестовых модуля (`test_ui_refinement`, `test_p0_release_gate`, `test_oauth_lifecycle`) `importorskip` содержат — забыли только в новом.
|
||||
|
||||
Без этого файла суite зелёный: **57 passed, 4 skipped, 3 deselected**. Исходный код, который тестируется (`CodexOAuthSession`, `enable_clipboard_shortcuts`, `HubEntry`, `extract_jwt_identity`), на месте.
|
||||
|
||||
**Оговорка:** на машине, где `customtkinter` установлен, гейт пройдёт. Но инвариант «отсутствие UI-зависимости не ломает прогон» нарушен — а это ровно тот сценарий, ради которого существует P0-1.
|
||||
|
||||
**Правка:** одна строка `pytest.importorskip("customtkinter")` в новом файле.
|
||||
|
||||
---
|
||||
|
||||
## 3. ⛔ Блокер 2: фид обновлений указывает на несуществующий пакет
|
||||
|
||||
Манифест живой и отдаёт `HTTP 200`, но пакет из него — нет:
|
||||
|
||||
```
|
||||
package_url: https://github.com/ochenstarik-ui/hermes-hub-releases/releases/download/v0.1.1/hermes-hub-0.1.1.zip
|
||||
-> HTTP 404
|
||||
gh release list --repo ochenstarik-ui/hermes-hub-releases -> пусто
|
||||
```
|
||||
|
||||
Пункт гейта 7 проверяет только наличие полей `version` и `package_url` в манифесте, но не достижимость самого пакета — поэтому печатает `[LIVE STATUS] Public update manifest live`. Формально верно, по существу — обновление скачать нельзя.
|
||||
|
||||
Сейчас это не проявится (манифест объявляет 0.1.1, приложение 0.1.1 → «обновлений нет»), но как только выйдет 0.1.2, пользователь получит ошибку загрузки.
|
||||
|
||||
**Правка:** опубликовать релизный ассет с заявленным `sha256`, а в `check_production_update_feed` добавить HEAD-запрос по `package_url`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Артефакт релиза непроверяем из репозитория
|
||||
|
||||
Отчёт приводит SHA256 собранного установщика `1D41B9D6…`. Проверить его нельзя:
|
||||
|
||||
```
|
||||
файлов dist/ в git: 0 (dist/ в .gitignore)
|
||||
локальный dist/HermesHubSetup.exe: 7e2531… (сборка от 20.08 00:16, устаревшая)
|
||||
dist/checksums.txt: содержит старую сумму 7E2531…
|
||||
```
|
||||
|
||||
Собранный установщик существует только на машине исполнителя. Любой, кто клонирует репозиторий, установщика не получает, а `checksums.txt` вводит в заблуждение. Для релиза артефакт должен попасть в GitHub Release (там же, где ассет обновления), а `checksums.txt` — обновляться сборочным скриптом.
|
||||
|
||||
Заявленный clean-install тест в изолированном venv я подтвердить не могу — воспроизвести его без артефакта нечем. Код установщика при этом проверен и выглядит корректно (см. раздел 1).
|
||||
|
||||
---
|
||||
|
||||
## 5. Отчёт отстал от кода на 2097 строк
|
||||
|
||||
После `8314d46` в `main` влиты три коммита, которых отчёт не касается:
|
||||
|
||||
```
|
||||
0d9005f fix(oauth): eliminate ERR_CONNECTION_REFUSED, immediate listener binding, single session reuse
|
||||
249a888 feat(oauth): Cockpit Tools model, manual callback fallback, race protection
|
||||
3aae1a8 feat(wizard): Codex OAuth/API Key & OpenCode Go with clipboard UX
|
||||
```
|
||||
|
||||
`+2097 / −232` строк: новый `codex_oauth.py` (296), переработанный `profile_oauth.py` (+296), `add_account_wizard.py` (+880), `components.py` (+83), два новых тестовых модуля.
|
||||
|
||||
Это функциональная разработка, а не закрытие замечаний, и она не отревьюирована. Беглая проверка на утечки: в `codex_oauth.py` и `profile_oauth.py` токены и коды в логи не пишутся — логируются только `source` и HTTP-код. Полноценного ревью этих коммитов не проводилось.
|
||||
|
||||
**Вердикт по релизу не может покрывать неотрецензированный код.** Либо тег ставится на `8314d46` (после закрытия блокера 2), либо новые коммиты идут на отдельное ревью.
|
||||
|
||||
---
|
||||
|
||||
## 6. Условия выпуска v0.1.1
|
||||
|
||||
1. Добавить `pytest.importorskip("customtkinter")` в `tests/test_codex_opencode_wizard.py`; убедиться, что гейт зелёный в окружении **без** UI-зависимостей.
|
||||
2. Опубликовать релизный ассет `hermes-hub-0.1.1.zip` с суммой из манифеста; расширить проверку 7 HEAD-запросом по `package_url`.
|
||||
3. Положить `HermesHubSetup.exe` в GitHub Release и обновить `dist/checksums.txt` сборочным скриптом.
|
||||
4. Определиться с областью тега: `8314d46` либо отдельное ревью коммитов `0d9005f…3aae1a8`.
|
||||
|
||||
Пункты 1 и 2 — блокирующие. 3 и 4 — обязательны для честного релиза, но могут быть закрыты процедурно.
|
||||
|
||||
---
|
||||
|
||||
## 7. Остаточное из прошлых раундов (не блокирует)
|
||||
|
||||
- Комментарии в `router_profiles.yaml` стираются при сохранении.
|
||||
- `model_timeout_seconds`, `monitoring_interval_seconds`, `auto_monitoring` сохраняются, но не читаются (работают 4 настройки из 7).
|
||||
- `tests/test_installer.py` по-прежнему пишет в Start Menu и `HKCU` при запуске с `-m installer`.
|
||||
- `_CM_LOCK` на всё время subprocess; глобальная запись `gemini:antigravity` без восстановления; session affinity без TTL; `router_state.json` без межпроцессной блокировки; roadmap-модули не подключены; веб-стек мёртв.
|
||||
123
agents/inbox/2026-08-20-review-round5-unreviewed-features.md
Normal file
123
agents/inbox/2026-08-20-review-round5-unreviewed-features.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# Ревью, раунд 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:
|
||||
|
||||
```python
|
||||
# _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`
|
||||
|
||||
```python
|
||||
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`:
|
||||
|
||||
```python
|
||||
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` удерживается на всё время 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`).
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# Задание: Правдивость данных и готовность к релизу v0.1.1
|
||||
|
||||
## Дата поступления
|
||||
2026-08-21
|
||||
|
||||
## Контекст
|
||||
По итогам ревью раунда 5 (`0d9005f…0c511cd`, +6970 строк) выявлены два блокирующих дефекта одного класса: **пользователю показываются правдоподобные, но выдуманные данные**. Тесты и релизный гейт при этом зелёные — дефекты не детектируются существующими проверками.
|
||||
|
||||
Ветка `main` = `0c511cd`. Тег `v0.1.1` снят с `origin` и локально (указывал на `65482e8` без исправления P0-1).
|
||||
|
||||
**Просьба: на время выполнения этого задания заморозить `main` для новой функциональности.** За два предыдущих ревью в ветку влилось ~7300 строк, из-за чего вердикт устаревал к моменту выдачи.
|
||||
|
||||
---
|
||||
|
||||
## Область задачи (Scope)
|
||||
|
||||
### P0-1. Сборщик квот не должен выдавать выдуманные числа за данные провайдера
|
||||
|
||||
`src/antigravity_provider/router/quota_collector.py` не делает ни одного сетевого вызова (`urlopen`: 0), но возвращает 17 захардкоженных `used_percent` и помечает снапшоты источниками `antigravity_api`, `codex_usage_api`, `opencode_api`, `claude_oauth_usage_api`, `xai_task_usage_api`. Эти значения выводятся в карточке аккаунта (`ui/views/accounts_view.py:168`).
|
||||
|
||||
Выбрать один из путей:
|
||||
|
||||
**Путь А (предпочтительный) — реальный сбор.** Запрашивать потребление у провайдера там, где есть API; при успехе оставлять источник `*_api`, при неудаче — переключаться на путь Б.
|
||||
|
||||
**Путь Б — честная маркировка.** Если данные вычисляются локально:
|
||||
- источник снапшота — `estimated` или `baseline`, но **не** `*_api`;
|
||||
- в UI рядом с корзиной квоты — явный признак оценки (значок/подпись «оценка», не проверено у провайдера);
|
||||
- значения не должны выглядеть как измерение: либо диапазон, либо «нет данных» вместо `9%`.
|
||||
|
||||
Смешанный вариант (А с фолбэком на Б) допустим и желателен.
|
||||
|
||||
### P0-2. Убрать тихий фолбэк на поддельный код авторизации
|
||||
|
||||
`router/codex_oauth.py:103–112` и `router/grok_oauth.py:91–94` при недоступности эндпоинта провайдера генерируют локальные `user_code` (`CDX-…`, `GRK-…`) и `device_code`/`device_auth_id`. По RFC 8628 их выдаёт сервер авторизации; локально сгенерированные ему неизвестны, поллинг гарантированно завершается таймаутом через 15 минут, а пользователь всё это время видит правдоподобный код.
|
||||
|
||||
Требуется:
|
||||
- в обычном режиме при неудаче запроса кода — **немедленная ошибка** с текстом причины в мастере, без запуска поллинга;
|
||||
- фолбэк на локальную сессию — только под явным `HERMES_HUB_DEV_MODE=1` (по образцу `update_manager.is_allowed_update_host`), с видимой пометкой в UI, что сессия тестовая;
|
||||
- проверить остальные OAuth-модули (`claude_oauth.py`, `profile_oauth.py`) на такой же паттерн.
|
||||
|
||||
### P1-3. Распространить решение по OAuth-клиентам на новых провайдеров
|
||||
|
||||
`docs/OAUTH_CLIENT.md` описывает только Google. В коде появились ещё три заимствованных клиента:
|
||||
```
|
||||
codex_oauth.py:31 app_EMoamEEZ73f0CkXaXp7hrann (OpenAI)
|
||||
grok_oauth.py:30 b1a00492-073a-47ea-816f-4c329264a828 (xAI)
|
||||
claude_oauth.py клиент Claude
|
||||
```
|
||||
Добавить их в документ с той же структурой: тип клиента, модель угроз, обоснование по RFC 8252/7636, явное указание происхождения.
|
||||
|
||||
### P1-4. Определить судьбу `gui_server.py`
|
||||
|
||||
Файл вырос ещё на 112 строк, но `run_gui_server` не вызывается ниоткуда (проверено по `.py`, `.bat`, `.ps1`, `.cs`). Либо подключить, либо вынести в `legacy/`, либо удалить вместе с `gui_cockpit.html`. При удалении — убрать `fastapi`/`uvicorn` из обязательных зависимостей `pyproject.toml`.
|
||||
|
||||
### P1-5. Организационные пункты релиза (из раунда 4)
|
||||
|
||||
1. Опубликовать GitHub Release в `ochenstarik-ui/hermes-hub-releases`: ассет `hermes-hub-0.1.1.zip` с суммой, совпадающей с `sha256` в `update_manifest.json` (сейчас `package_url` → HTTP 404).
|
||||
2. Положить туда же `HermesHubSetup.exe`; `dist/checksums.txt` обновлять сборочным скриптом, а не вручную.
|
||||
3. Тег `v0.1.1` ставить только на коммит, прошедший ревью. Если локальный тег остался на машине исполнителя — удалить: `cd <repo>; git tag -d v0.1.1`.
|
||||
|
||||
### P2-6. Долг, накопленный за пять раундов (в обычную очередь)
|
||||
|
||||
- `capability_matrix`, `lifecycle_supervisor`, `skill_registry`, `deepseek_adapter` — не подключены ни к одному кодовому пути; подключить либо пометить экспериментальными.
|
||||
- Комментарии в `router_profiles.yaml` стираются при сохранении.
|
||||
- `model_timeout_seconds`, `monitoring_interval_seconds`, `auto_monitoring` сохраняются, но не читаются (работают 4 настройки из 7).
|
||||
- `tests/test_installer.py` при `-m installer` пишет в Start Menu и `HKCU`.
|
||||
- `_CM_LOCK` удерживается на всё время subprocess `agy`; глобальная запись `gemini:antigravity` перезаписывается без восстановления.
|
||||
- `health_tracker`: запись атомарна, но межпроцессной блокировки нет (last-writer-wins между GUI и Hermes).
|
||||
|
||||
---
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
Ревью будет проверять ровно это, исполнением:
|
||||
|
||||
1. `grep -c urlopen quota_collector.py` > 0 **либо** ни один снапшот с локально вычисленными значениями не имеет источник `*_api`. Проверяется вызовом сборщика и чтением поля `source`.
|
||||
2. В UI карточки аккаунта для оценочных данных присутствует визуальный признак оценки.
|
||||
3. При недоступности эндпоинта устройства (мокируется исключением в `_post_json`) и **без** `HERMES_HUB_DEV_MODE`: `start()` возвращает ошибку, `status != "pending"`, поток поллинга не стартует.
|
||||
4. С `HERMES_HUB_DEV_MODE=1` фолбэк работает и помечен в UI.
|
||||
5. `docs/OAUTH_CLIENT.md` содержит разделы по OpenAI, xAI и Claude.
|
||||
6. `run_gui_server` либо вызывается, либо файла нет в `src/`.
|
||||
7. `curl -I <package_url из манифеста>` → HTTP 200, sha256 совпадает.
|
||||
8. Регрессия не внесена: полный `pytest tests/` зелёный **в окружении без UI-зависимостей** (проверка инварианта `importorskip`), релизный гейт PASSED.
|
||||
9. Новые тесты на P0-1 и P0-2, падающие на текущем `0c511cd`.
|
||||
|
||||
## Что НЕ входит в задание
|
||||
Новая функциональность. Задание закрывает дефекты и готовит релиз; любые новые возможности — после тега.
|
||||
Loading…
Reference in a new issue