hermes-hub/agents/inbox/2026-08-21-plan-a-refactoring-with-verified-baseline.md
Hermes Team 559a56d80b docs(task): Plan A refactoring task with verified baseline statuses
Every Round 4 finding re-checked against HEAD 63c0385 instead of being carried
forward as still-open. Four are already fixed (customtkinter collection guard,
_CM_LOCK scope, gemini:antigravity restore, session affinity TTL), two are
partial, one is obsolete (web stack moved to legacy/), and five remain open.

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

172 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Задание: Plan A рефакторинг — с проверенной baseline
## Дата поступления
2026-08-21
## Статус проверки
Черновик задания сверен с фактическим кодом. **Проверочный HEAD: `63c0385`**, `origin/main` = `63c0385`, divergence 0, рабочее дерево чистое (кроме неотслеживаемого `COCKPIT_TOOLS_ARCHITECTURE_COMPARISON.md`).
Ниже сохранена нумерация черновика; у каждого пункта проставлен **проверенный статус**. Пункты со статусом ✅ выполнять не нужно — они уже закрыты, и повторная работа по ним будет потерей времени.
> Правило черновика «сначала проверить текущий HEAD, потом чинить» — правильное. Эта версия применяет его к самому черновику.
---
## ВАЖНО: преамбула черновика устарела
Черновик утверждает как текущее состояние:
> release gate на фактическом HEAD НЕ проходил
Это было верно для `3aae1a8` (раунд 4). На `63c0385`:
```
pytest: 149 passed, 20 skipped, 3 deselected
release gate: [RELEASE GATE: PASSED] Ready for Candidate v0.1.1
ruff check .: All checks passed
```
SHA из черновика (`8314d46`, `3aae1a8`, `0d9005f`, `249a888`) — историческая точка раунда 4, не текущее состояние. С тех пор в `main` вошли `2b8b709`, `0c511cd`, `42eddb3`, `8db5c46`, `51e5b67`, `46a1853`, `63c0385`.
---
## PHASE 0. Baseline — статусы проверены
### P0-1. customtkinter regression — ✅ ЗАКРЫТО
`tests/test_codex_opencode_wizard.py` больше не импортирует `customtkinter` на уровне модуля. Проверено: в окружении без UI-зависимостей сбор проходит, UI-тесты уходят в SKIP (`98 passed, 7 skipped`), а не в ERROR. Действий не требуется.
### P0-2. Автоматическая защита от повторения — ✅ ЗАКРЫТО
Реализовано в `8db5c46`:
- `tests/test_import_invariants.py` — падает, если тестовый модуль импортирует `customtkinter` без `pytest.importorskip`; отдельно обходит все модули пакета и ловит сломанные внутренние ссылки;
- CI-job `headless` в `.github/workflows/ci.yml` — сносит `customtkinter` и гоняет прогон, то есть инвариант держится автоматикой.
Не делать второй механизм. Если нужен ещё маркер/политика — расширять существующий.
### P0-3. Public update feed — ⛔ ОТКРЫТО (проверено сейчас)
```
manifest: HTTP 200, version=0.1.1, схема валидна
sha256: 7a57137a93ad6fba5961e5c24b5d871aed5944b037f974b6137c028a111ae756
package_url: .../releases/download/v0.1.1/hermes-hub-0.1.1.zip -> HTTP 404
```
Релизов в `ochenstarik-ui/hermes-hub-releases` нет. Пункт актуален полностью.
### P0-4. Release gate и package_url — ⚠️ ЧАСТИЧНО СДЕЛАНО
Гейт **уже** делает живую проверку и печатает:
> `[PENDING GITHUB RELEASE] Manifest is live (v0.1.1), package_url is ready for release asset upload (HTTP 404 at GitHub Releases)`
То есть вводящего в заблуждение «feed полностью рабочий» больше нет. Остаётся валидная часть требования: **трёхуровневый статус** `MANIFEST_LIVE` / `PACKAGE_LIVE` / `PACKAGE_HASH_VERIFIED` вместо одного текстового сообщения, и решение — должен ли gate падать при `PACKAGE_LIVE=false` (сейчас PASSED).
### P0-5. Атомарность публикации — ⛔ ОТКРЫТО, требование верное
Порядок `build → checksum → upload → verify → publish manifest → verify feed` — правильный. Сейчас нарушен: манифест рекламирует артефакт, которого нет.
### P0-6, P0-7, P0-8. Воспроизводимость артефактов и checksums — ⛔ ОТКРЫТО
`dist/` в `.gitignore`, в git 0 файлов, `dist/checksums.txt` содержит сумму устаревшей сборки (`7e2531…`). Требования верны без правок.
### P0-9. НЕ выпускать v0.1.1 — ✅ согласовано
Тег снят с `origin` и локально (указывал на `65482e8` — коммит без исправления P0-1 и с обфусцированным секретом). Не восстанавливать без отдельного решения.
### P0-10 / P0-11. OAuth/Wizard commits — ⚠️ ФОРМУЛИРОВКА НЕТОЧНА
Черновик утверждает:
> старый review сделал беглую проверку token leakage
Фактически раунд 5 был полным ревью именно этих коммитов и дал два блокирующих дефекта:
- фабрикация данных о квотах (17 захардкоженных значений под ярлыками `*_api`);
- поддельные device codes при недоступности провайдера.
Оба закрыты в `42eddb3` и покрыты 7 регрессионными тестами (`tests/test_data_truthfulness_and_oauth_security.py`), проверено: все 7 падают на `0c511cd` и проходят на HEAD. Беглой была только проверка на утечку токенов.
**Скорректированный объём:** не полное повторное ревью, а точечная проверка сценариев из списка P0-11, которые ревью не покрывало — listener lifecycle, duplicate callback, cancel/timeout/retry, state validation, session cleanup. И обязательно сохранить подтверждённый рабочий сценарий: Antigravity аккаунт добавляется, callback отрабатывает, AutoAgent работает.
### P0-12. OAuth и Scheduler/EventBus — ⛔ АКТУАЛЬНО
Требование «OAuth — отдельная state machine, не общий refresh scheduler» и «OAuth success → targeted update, не global rebuild» верное, к текущему коду претензий пока нет — это ограничение на будущий рефакторинг.
---
## P0-13. Остаточные замечания раунда 4 — статусы проверены
| # | Замечание | Статус на `63c0385` | Комментарий |
|---|---|---|---|
| 1 | Комментарии `router_profiles.yaml` стираются | ⛔ **OPEN** | Проверено: 5 строк → 0 после одного назначения роли |
| 2 | `model_timeout_seconds`, `monitoring_interval_seconds`, `auto_monitoring` не читаются | ⛔ **OPEN** | 0 читателей вне UI и `settings_service` |
| 3 | `test_installer.py` пишет в Start Menu / HKCU | ⛔ **OPEN** | 2 реальных запуска установщика; смягчено маркером `installer` и `addopts` |
| 4 | `_CM_LOCK` удерживается на всё время subprocess | ✅ **FIXED** в `0c511cd` | Lock снимается до `agy_generate` — см. предупреждение ниже |
| 5 | `gemini:antigravity` без восстановления | ✅ **FIXED** в `0c511cd` | `prev_cred` читается до подмены и возвращается в `finally` |
| 6 | SessionAffinity без TTL | ✅ **FIXED** | `ttl_seconds=1800`, `max_entries=1000`, `prune_expired()` |
| 7 | `router_state.json` без межпроцессной блокировки | ⚠️ **PARTIALLY FIXED** | Атомарная запись через `tempfile` + `os.replace` есть; межпроцессной блокировки нет (0 совпадений `msvcrt`/`fcntl`) |
| 8 | roadmap-модули не подключены | ⛔ **OPEN** | `capability_matrix`, `lifecycle_supervisor`, `skill_registry` — по 0 импортёров |
| 9 | web stack мёртв | 🔄 **OBSOLETE** | В `42eddb3` перенесён в `legacy/` — см. P0-21 |
> **Поправка ревьюера:** в отчёте раунда 5 пункты 4 и 5 были помечены как открытые. Это ошибка — они были исправлены в `0c511cd`. Приоритет у проверки кода, а не у прошлых записей.
### Новое предупреждение по пункту 4
Lock теперь снимается **до** запуска subprocess. Это устранило сериализацию, но создало гонку: два параллельных вызова разных Antigravity-профилей успевают перезаписать глобальную запись `gemini:antigravity`, пока чужой subprocess ещё работает. Нужен либо per-credential mutex на время жизни subprocess, либо отказ от глобальной записи в пользу изоляции только через `USERPROFILE`. **Обязателен concurrency regression test** (это же требует P0-15).
---
## P0-14 … P0-21 — уточнения
### P0-14. Session Affinity TTL — ⚠️ ЧАСТИЧНО
TTL и LRU есть, но `ttl_seconds` задаётся **дефолтом в конструкторе**, а `session_affinity_ttl_seconds` из `router_profiles.yaml` не читается. Требование черновика («TTL должен реально использовать `session_affinity_ttl_seconds`») актуально и точное.
### P0-15. `_CM_LOCK` — переформулировать
Lock больше не держится вокруг subprocess. Актуальная задача — не «сузить lock», а **закрыть гонку**, описанную выше, и покрыть её тестом.
### P0-16. Global `gemini:antigravity` — ✅ закрыто
Схема `captured → changed → operation → restored in finally` реализована. Проверить только устойчивость к timeout/cancel.
### P0-17. `router_state.json` — ⚠️ частично
Атомарность есть, межпроцессной блокировки нет. Актуальная часть требования — interprocess locking, если файл действительно делят GUI и процесс Hermes.
### P0-18, P0-19, P0-20 — ⛔ актуальны без правок
По P0-19 напоминание: сейчас три настройки показываются пользователю и сохраняются в `hub_settings.json`, но рантаймом игнорируются — это тот самый некорректный contract. Вариант Б (disable в UI) допустим и дешевле.
### P0-21. Web stack — ⚠️ ФАКТ ИЗМЕНИЛСЯ
Черновик просит «не удалять web stack автоматически как dead code». **Он уже перенесён** в `legacy/gui_server.py` и `legacy/gui_cockpit.html` в `42eddb3` — из `src/` удалён, но не потерян.
Решение принимать явно:
- **A.** вернуть в `src/` как основу backend API contract для Tauri (тогда покрыть тестами — сейчас `legacy/` исключён из проверок, и `build_team_hierarchy`, который он вызывает, только что чинился от `NameError`);
- **B.** оставить в `legacy/` как справочный материал.
В обоих случаях: `fastapi` и `uvicorn` сейчас остаются **обязательными** зависимостями в `pyproject.toml`, хотя код в `legacy/` не поставляется. Привести в соответствие с решением.
---
## P0-22, P0-23, P0-24 — принять без изменений
Это самые ценные пункты черновика: они закрывают процедурный сбой, повторившийся трижды — отчёт готовился по одному коммиту, а `main` уходил вперёд. Финальный отчёт обязан содержать `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, и release gate должен быть выполнен именно на `FINAL_HEAD`, без функциональных коммитов после него.
---
## Дополнительно: что уже сделано ревьюером (не переделывать)
Коммиты `8db5c46`, `51e5b67`, `46a1853`, `63c0385` уже в `main`:
1. **`deepseek_adapter` починен.** Модуль никогда не импортировался: ссылался на несуществующие `ProviderAdapter` и `ProfileConfig`, реализовывал 1 из 4 абстрактных методов. Переписан под `BaseProviderAdapter`, зарегистрирован как `"deepseek"`.
2. **Инварианты импорта**`tests/test_import_invariants.py`.
3. **CI-job `headless`**.
4. **CI больше не красный.** `ruff check .` падал на каждом пуше (1221 находка), обрывая job до pytest и release gate — в CI они не выполнялись ни разу. Добавлен `[tool.ruff]` с набором правил, ловящих ошибки (`E9, F63, F7, F82, F811`); стилевые правила выключены до отдельного прохода.
5. **Семь реальных дефектов**, найденных этим набором и исправленных:
- `HubModal` использовался без импорта — модалка назначения роли падала с `NameError` (то есть обработчик, добавленный для закрытия давнего замечания, не мог открыться);
- три `after(0, lambda: ...(e))` со ссылкой на имя из `except`, которое Python отвязывает при выходе из блока — падал сам путь сообщения об ошибке;
- `build_team_hierarchy` падал с `NameError: is_main` при каждом вызове;
- `Any` и `Tuple` в аннотациях без импорта.
## Известный риск для PHASE 3+
`antigravity_provider` — namespace-пакет (нет `__init__.py`), и его `__path__` охватывает **два** каталога: `src/` репозитория и установленный плагин в `%LOCALAPPDATA%\hermes\plugins\antigravity-provider\src`. Импорт может подтянуть модуль из развёрнутой копии другой версии. При работе с HubSnapshot/EventBus это способно давать неповторимые расхождения — учитывать при отладке.
---
## Порядок работы
Порядок фаз из черновика (PHASE 0 → 19) принимается. Уточнение к PHASE 1: «закрыть текущие P0 release/test regressions» сводится к P0-3, P0-5, P0-6, P0-7, P0-8 (релизная инфраструктура) — тестовые регрессии уже закрыты.
## Критерии приёмки
Пункты 5372 черновика принимаются, с поправками:
- **53, 54, 55** — уже выполнены, проверить как регрессию, а не реализовывать заново;
- **56, 57** — реализовать трёхуровневый статус и определить поведение гейта при `PACKAGE_LIVE=false`;
- **64** — переформулировать: не «lock не удерживается вокруг subprocess» (уже так), а «гонка при параллельной подмене `gemini:antigravity` закрыта и покрыта тестом»;
- **70** — web stack уже в `legacy/`; критерий — «принято и обосновано явное решение A или B», а не «не удалён».
## Релиз
`v0.1.1` не выпускать. После завершения — передать ревьюеру точный `FINAL_COMMIT_SHA` и не вести разработку поверх него до вердикта.