# Полный аудит 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** | |
