16 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 не создавать.
P0-bis. Найдено при прогоне с установленными UI-зависимостями
Обнаружено 2026-08-21 при проверке PHASE 1 Codex. Эти дефекты не видны в обычном прогоне: без customtkinter соответствующие тесты пропускаются, поэтому suite зелёный, а код не исполняется.
1. Подмена sys.path заставляет тесты грузить УСТАНОВЛЕННУЮ копию плагина
hermes_hub_app.py:41 при импорте делает sys.path.insert(0, ...) для %LOCALAPPDATA%/hermes/plugins/antigravity-provider/src. Как только любой тест импортирует этот модуль, путь установленной копии оказывается впереди репозитория, и последующие импорты antigravity_provider.* резолвятся в развёрнутую — то есть более старую — версию.
Проявление: tests/test_p0_release_gate.py::test_p0_5_b1_non_router_error_fallback и ::test_n2_error_formatter_deduplication падают с ImportError: format_antigravity_error только в полном прогоне, а по отдельности проходят. Проверено: функция есть в src/antigravity_provider/runtime.py и отсутствует в установленной копии.
Последствия шире тестов: в проде приложение может исполнять развёрнутый плагин вместо ожидаемого кода, а после обновления — смесь версий. Пакет antigravity_provider — namespace-типа (нет __init__.py), поэтому его __path__ спокойно объединяет оба каталога.
Нужно: определить единственный источник импорта. Либо приложение не трогает sys.path и полагается на установленный пакет, либо repo-режим явно вытесняет установленный. Смешанное состояние недопустимо. Добавить тест, фиксирующий, из какого каталога загружен antigravity_provider.
2. test_oauth_lifecycle.py — два теста падают при реальном исполнении
FAILED test_e_repeated_open_browser_invariance
FAILED test_f_copy_before_open_browser
assert wizard.oauth_url is not None
E assert None is not None
Падают и по отдельности, и в полном прогоне — от порядка не зависят. То есть заявленная в 249a888 «immediate URL readiness» этими тестами не подтверждена. Разобраться: дефект в продукте или в тесте (например, мок start_profile_oauth не покрывает путь _init_antigravity_oauth). Пока не выяснено — считать функцию непроверенной.
3. Заглушка в conftest.py слишком широкая
pytest_collection_modifyitems пропускает тест, если в его имени встречается ui, view или wizard. Под это попадают и статические проверки, которым GUI не нужен, — из-за чего оба дефекта выше жили незамеченными. Заменить на явную маркировку (@pytest.mark.gui) либо на проверку реального импорта GUI-модулей.
Критерий приёмки: полный pytest зелёный и в окружении без UI-зависимостей, и в окружении с установленными customtkinter/pillow/psutil. Сейчас второй вариант даёт 4 падения.
Критерии приёмки
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 и не продолжать разработку поверх него до вердикта.