hermes-hub/agents/inbox/2026-08-21-A-antigravity-state-layer.md
Hermes Team 8143150ebc docs(task): add findings that only surface with UI dependencies installed
Running the suite with customtkinter present exposes four failures the headless
run never reaches: hermes_hub_app mutates sys.path so tests load the stale
installed plugin copy, and two OAuth lifecycle tests fail outright. conftest's
name-based skip hid both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 23:36:36 +07:00

16 KiB
Raw Blame History

Задание 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\pluginsntigravity-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 падения.

Критерии приёмки

  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 и не продолжать разработку поверх него до вердикта.