The headless guard matched any test whose name contained "ui", "view" or "wizard", so static checks that never touch the toolkit were skipped too. That is how four real failures stayed invisible: they lived in modules the name filter silently removed. Selection is now the `ui` marker alone; modules that import the GUI stack already guard themselves with pytest.importorskip, which tests/test_import_invariants.py enforces. Headless goes from 152 to 156 passed — four tests that were being skipped by accident now actually run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
17 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 — ✅ уже исправлено ревьюером
Отбор шёл по имени теста (ui, view, wizard), из-за чего пропускались и статические проверки, не касающиеся GUI, — именно поэтому оба дефекта выше жили незамеченными. Теперь отбор только по маркеру ui; модули, импортирующие GUI, защищают себя pytest.importorskip на уровне модуля, и это требование закреплено tests/test_import_invariants.py.
Действий не требуется — пункт оставлен для контекста. После правки headless-прогон даёт 156 passed вместо 152: четыре теста, которые раньше молча пропускались, теперь реально выполняются.
Критерий приёмки: полный 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 и не продолжать разработку поверх него до вердикта.