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>
12 KiB
Задание 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 не создавать.
Критерии приёмки
docs/UI_STATE_CONTRACT.mdопубликован до остальных изменений, содержит раздел «Backend gaps» и для каждого поля — признак реальности данных.- Ни один файл из чужой зоны не изменён (
git diff --name-only BASE_SHA..HEAD -- src/antigravity_provider/router/ui src/antigravity_provider/router/hermes_hub_app.pyпуст). scan_all()недоступен из UI-слоя; сервис обновляет снапшот сам.- Изменение квоты одного аккаунта порождает событие с идентификатором аккаунта; тест это проверяет.
- Поздний ответ не перезаписывает более свежее состояние; тест на гонку
seq. - OAuth-сессия не управляется общим планировщиком; успешная авторизация даёт точечное обновление, тест это фиксирует.
- Мульти-корзинные квоты: для каждого провайдера в контракте указано, какие корзины реально доступны; ни одна локально вычисленная величина не помечена источником провайдера.
- Мёртвые модули либо интегрированы (есть вызывающий код вне
__init__.py), либо удалены. - Тесты установщика не оставляют записей в
HKCUпри обычном прогоне. package_urlиз манифеста отдаёт HTTP 200, sha256 совпадает с загруженным артефактом.- Полный
pytestзелёный в окружении без UI-зависимостей;ruff check .чисто; release gate PASSED на финальном коммите. - Отчёт содержит
BASE_SHA,FINAL_SHA,origin/main,git status, точный результат тестов (X passed / Y skipped / Z failed) и список того, что осталось.
Порядок сдачи
Никаких функциональных коммитов после последнего запуска release gate. Ревьюеру передать точный FINAL_COMMIT_SHA и не продолжать разработку поверх него до вердикта.