# Задание: 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 (релизная инфраструктура) — тестовые регрессии уже закрыты.

## Критерии приёмки
Пункты 53–72 черновика принимаются, с поправками:
- **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` и не вести разработку поверх него до вердикта.
