The UI redesign draft duplicated eight Plan A phases on the same files
(accounts_view, routing_view, hermes_hub_app, unified_health), which would have
put two agents into the same merge conflicts. Ownership is now split by file
path, with docs/UI_STATE_CONTRACT.md as the interface between them.
Four draft items were already implemented at 50fde5f and are marked as such
rather than reassigned: OAuth URL copy-before-open, keyed account card reuse,
the no-fake-metrics rule, and the importorskip guard.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
131 lines
12 KiB
Markdown
131 lines
12 KiB
Markdown
# Задание A (Antigravity): слой состояния и данных
|
||
|
||
## Дата поступления
|
||
2026-08-21
|
||
|
||
## База
|
||
Проверочный HEAD на момент выдачи: **`50fde5f`**. Перед началом выполнить `git fetch`, зафиксировать фактический `BASE_SHA` и не считать этот SHA актуальным автоматически.
|
||
|
||
## Ветка
|
||
`antigravity/state-layer`
|
||
|
||
---
|
||
|
||
## ГРАНИЦА РАБОТ — читать первым
|
||
|
||
Параллельно выполняется **Задание B (Codex)** — переработка интерфейса. Чтобы задания не конфликтовали, разделение проходит **по файлам**, а не по смыслу.
|
||
|
||
**Ваша зона (можно менять):**
|
||
```
|
||
src/antigravity_provider/router/state_store.py
|
||
src/antigravity_provider/router/unified_health.py
|
||
src/antigravity_provider/router/scheduler.py
|
||
src/antigravity_provider/router/event_bus.py
|
||
src/antigravity_provider/router/quota_collector.py
|
||
src/antigravity_provider/router/model_registry.py
|
||
src/antigravity_provider/router/account_identity.py
|
||
src/antigravity_provider/router/router_engine.py
|
||
src/antigravity_provider/router/router_config.py
|
||
src/antigravity_provider/router/session_affinity.py
|
||
src/antigravity_provider/router/health_tracker.py
|
||
src/antigravity_provider/router/profile_manager.py
|
||
src/antigravity_provider/router/auto_assigner.py
|
||
src/antigravity_provider/router/adapters/**
|
||
src/antigravity_provider/router/*_oauth.py
|
||
src/antigravity_provider/*.py
|
||
scripts/**, installer/**, config/**
|
||
tests/** (кроме tests/test_ui_*.py)
|
||
```
|
||
|
||
**Чужая зона (НЕ трогать):**
|
||
```
|
||
src/antigravity_provider/router/ui/** ← весь UI, включая views, components, theme, wizard
|
||
src/antigravity_provider/router/hermes_hub_app.py
|
||
tests/test_ui_*.py
|
||
```
|
||
|
||
Если для вашей задачи потребовалось изменить файл из чужой зоны — **это сигнал, что контракт спроектирован неверно**. Вместо правки UI расширьте ViewModel или событие.
|
||
|
||
---
|
||
|
||
## P0. Контракт ViewModel публикуется первым
|
||
|
||
До любых других изменений зафиксировать и запушить контракт, против которого Codex будет писать интерфейс: `docs/UI_STATE_CONTRACT.md`.
|
||
|
||
Описать точно, по фактическому коду:
|
||
|
||
- `HubSnapshot` — состав, гарантии консистентности, поле версии/seq;
|
||
- `ProfileViewModel` — все поля, какие опциональны, что означает каждое состояние `auth_state`/`health_state`;
|
||
- `QuotaSnapshot` и `QuotaBucket` — полная схема (`id`, `label`, `used_percent`, `remaining_percent`, `used`, `limit`, `reset_at`, `unit`, `scope`, `model_family`), какие поля реально заполняются **для каждого провайдера отдельно**, и признак `is_estimated`;
|
||
- `AgentViewModel`, `RolePipeline`, `PipelineNode`, `ProviderSummary`, `SystemReadiness`;
|
||
- перечень событий `event_bus` с полезной нагрузкой: что именно приходит при изменении квоты одного аккаунта, при добавлении аккаунта, при смене маршрута.
|
||
|
||
**Критично:** для каждого поля указать, реально ли backend его отдаёт, или это заглушка. Codex обязан отличать «данных нет» от «данные есть». Раздел «Backend gaps» обязателен.
|
||
|
||
Этот документ — интерфейс между двумя заданиями. После публикации менять его только с явной пометкой в отчёте.
|
||
|
||
---
|
||
|
||
## Область задачи
|
||
|
||
### 1. Единый источник состояния
|
||
Завершить `HubSnapshot` как единственный источник для UI. Ни один view не должен иметь возможности самостоятельно инициировать сканирование — не потому, что это запрещено правилом, а потому, что данные ему приходят готовыми.
|
||
|
||
Убрать вызовы `scan_all()` из кода, доступного UI: сервис отдаёт снапшот, обновляет его сам.
|
||
|
||
### 2. Централизованный планировщик обновлений
|
||
На базе существующего `HermesRefreshScheduler`:
|
||
- интервалы на провайдера;
|
||
- дедупликация одновременных запросов;
|
||
- защита от устаревшего ответа (поздний ответ не перезаписывает более свежее состояние — использовать `seq`);
|
||
- раздельные операции: обновить один аккаунт / одного провайдера / всё.
|
||
|
||
### 3. Событийная модель вместо полного пересбора
|
||
Изменение квоты одного аккаунта обязано порождать точечное событие с идентификатором аккаунта, а не сигнал «всё изменилось». То же для добавления/удаления аккаунта, смены авторизации, смены активного маршрута.
|
||
|
||
**Отдельно:** OAuth — самостоятельная state machine. Не подключать OAuth listener, таймауты авторизации и жизненный цикл PKCE к общему планировщику. После успешной авторизации: сохранить аккаунт → событие `ACCOUNT_ADDED` → точечное обновление, **без** глобального пересканирования.
|
||
|
||
### 4. Мульти-корзинные квоты
|
||
Довести `quota_collector` до реального сбора там, где провайдер это отдаёт. Требования:
|
||
- не сводить разные лимиты к одному проценту;
|
||
- корзины привязаны к семейству моделей, если у провайдера лимиты раздельные (Antigravity: Gemini и не-Gemini — проверить фактическим кодом, что именно доступно);
|
||
- каждый снапшот честно помечен `is_estimated` и источником; запрет на `*_api` для локально вычисленных значений остаётся в силе;
|
||
- если провайдер не отдаёт данных — отдавать отсутствие данных, а не число.
|
||
|
||
Роутер должен понимать, что переключение модели может сменить квотный пул.
|
||
|
||
### 5. Реестр моделей и маршрутизация
|
||
Довести `model_registry` и capability-роутинг: выбор модели с учётом возможностей, стоимости и остатка квоты. Учитывать `preferred_models` профиля и приоритет роли.
|
||
|
||
### 6. Долги, оставшиеся открытыми
|
||
- **Мёртвые модули.** `capability_matrix`, `lifecycle_supervisor`, `skill_registry` сейчас лишь реэкспортируются из `router/__init__.py` — ни один кодовый путь их не использует. Реэкспорт не равен интеграции. Либо подключить по-настоящему, либо удалить и записать причину.
|
||
- **`HKCU` в тестах.** `APPDATA`/`USERPROFILE` теперь в песочнице, но `HermesHubSetup.cs` пишет в реестр двумя вызовами `Registry.CurrentUser`, а реестр переменными окружения не перенаправляется. Вынести реальный запуск установщика в явный integration-режим либо параметризовать ключ реестра.
|
||
- **Комментарии `router_profiles.yaml`** стираются при сохранении (5 строк → 0). Оценить `ruamel.yaml`; если замена YAML-стека велика — зафиксировать отдельным долгом и не расширять scope.
|
||
- **Сериализация Antigravity.** `_AGY_INVOCATION_LOCK` вернул корректность ценой параллелизма: вызовы профилей со своим auth снова идут по одному. Оценить отказ от глобальной записи `gemini:antigravity` в пользу изоляции только через `USERPROFILE` — это снимет и лок, и гонку. Если не делать — записать как осознанный долг.
|
||
|
||
### 7. Релизная инфраструктура
|
||
- выложить ассет `hermes-hub-0.1.1.zip` с суммой из манифеста (сейчас `package_url` → HTTP 404) и `HermesHubSetup.exe` в GitHub Release;
|
||
- `dist/checksums.txt` — только из `scripts/build_dist.py`, вручную не править;
|
||
- порядок публикации: build → checksum → upload → verify → publish manifest → verify feed. Манифест не должен рекламировать несуществующий артефакт.
|
||
|
||
**Тег `v0.1.1` не создавать.**
|
||
|
||
---
|
||
|
||
## Критерии приёмки
|
||
|
||
1. `docs/UI_STATE_CONTRACT.md` опубликован до остальных изменений, содержит раздел «Backend gaps» и для каждого поля — признак реальности данных.
|
||
2. Ни один файл из чужой зоны не изменён (`git diff --name-only BASE_SHA..HEAD -- src/antigravity_provider/router/ui src/antigravity_provider/router/hermes_hub_app.py` пуст).
|
||
3. `scan_all()` недоступен из UI-слоя; сервис обновляет снапшот сам.
|
||
4. Изменение квоты одного аккаунта порождает событие с идентификатором аккаунта; тест это проверяет.
|
||
5. Поздний ответ не перезаписывает более свежее состояние; тест на гонку `seq`.
|
||
6. OAuth-сессия не управляется общим планировщиком; успешная авторизация даёт точечное обновление, тест это фиксирует.
|
||
7. Мульти-корзинные квоты: для каждого провайдера в контракте указано, какие корзины реально доступны; ни одна локально вычисленная величина не помечена источником провайдера.
|
||
8. Мёртвые модули либо интегрированы (есть вызывающий код вне `__init__.py`), либо удалены.
|
||
9. Тесты установщика не оставляют записей в `HKCU` при обычном прогоне.
|
||
10. `package_url` из манифеста отдаёт HTTP 200, sha256 совпадает с загруженным артефактом.
|
||
11. Полный `pytest` зелёный **в окружении без UI-зависимостей**; `ruff check .` чисто; release gate PASSED на финальном коммите.
|
||
12. Отчёт содержит `BASE_SHA`, `FINAL_SHA`, `origin/main`, `git status`, точный результат тестов (`X passed / Y skipped / Z failed`) и список того, что осталось.
|
||
|
||
## Порядок сдачи
|
||
Никаких функциональных коммитов после последнего запуска release gate. Ревьюеру передать точный `FINAL_COMMIT_SHA` и не продолжать разработку поверх него до вердикта.
|