docs(task): B3 — wire the contract v1.1 fields the UI never picked up

Codex built phases 2-6 against contract v1.0 while Antigravity shipped v1.1, so
eight fields now exist in the snapshot and render nowhere: plan_code,
plan_source, active_quota_status, active_quota_label, quota_status,
failover_reason, unavailable_reason and seq. PlanBadge was built in phase 1 and
still has nothing to display.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-21 08:50:22 +07:00
parent 2c612f22a0
commit 9ffc815ed5

View file

@ -0,0 +1,97 @@
# Задание B3 (Codex): подключить поля контракта v1.1
## Дата поступления
2026-08-21
## База
Проверочный HEAD: **`2c612f2`**, `origin/main` = `2c612f2`. Перед началом `git fetch` и обязательно подтянуть `main` — ваши фазы 26 уже влиты, поверх них есть правки.
## Ветка
`codex/contract-v11`. Ветка `codex/ui-redesign` влита в `main`.
---
## Что принято по фазам 26
Проверено исполнением, работа хорошая:
- граница соблюдена — вне вашей зоны только файл отчёта;
- **пересоздания виджетов не осталось нигде**: ни одного `winfo_children()` в `views/`, всё на стабильных ключах;
- **выдуманных метрик нет**, «Н/Д» используется в семи экранах;
- **views вообще не обращаются к backend** — ни `HubStateStore`, ни `scan_all`, ни сервисов; данные приходят только снапшотом;
- мастер подключения пережил слияние: трёхэлементная распаковка `start_profile_oauth` и `oauth_port` на месте;
- защита от краша при старте не только сохранена, но усилена — `update_data` выходит рано вместо отката к хранилищу.
Одна правка с моей стороны: инвариант `tests/test_view_startup_contract.py` требовал обращения к хранилищу и после вашей переделки начал скипаться для всех девяти views, то есть перестал защищать тот самый краш. Переписан под новый контракт — проверка `isinstance(snapshot, HubSnapshot)` теперь обязательна независимо от того, читает ли view хранилище.
---
## Задача
Вы работали по контракту **v1.0**, а Antigravity параллельно выпустил **v1.1** и добавил поля, которых на тот момент не было. Данные в снапшоте есть, но интерфейс их не показывает. Проверено на `2c612f2`:
| Поле | Где появилось | Используется в UI |
|---|---|---|
| `plan_code` | `ProfileViewModel` | **0 файлов** |
| `plan_source` | `ProfileViewModel` | **0 файлов** |
| `active_quota_status` | `AgentViewModel` | **0 файлов** |
| `active_quota_label` | `AgentViewModel` | **0 файлов** |
| `quota_status` | `PipelineNode` | **0 файлов** |
| `failover_reason` | `PipelineNode` | **0 файлов** |
| `unavailable_reason` | `QuotaBucket` | **0 файлов** |
| `seq` | `HubSnapshot` | **0 файлов** |
| `session_id` | `AgentViewModel` | 1 файл |
| `account_identity` | `PipelineNode` | 4 файла |
| `is_estimated` | `QuotaSnapshot` | 2 файла |
Перечитайте `docs/UI_STATE_CONTRACT.md` целиком — он переписан, появился раздел «Provider Truth Matrix».
### 1. Тариф — `PlanBadge` наконец можно показать
Компонент вы сделали в PHASE 1, данные появились в v1.1. Показывать бейдж, когда `plan_source` говорит, что тариф известен от провайдера (`provider_api`, `jwt_claim`, `provider_auth`), и **не показывать** при `inferred`/`unknown` — либо показывать с явной пометкой, что значение выведено, а не получено. Пользователь должен отличать «PRO по данным провайдера» от «похоже на PRO».
### 2. Квоты — состояние без данных
`QuotaBucket.unavailable_reason` объясняет, **почему** данных нет. Сейчас пользователь видит пустоту без причины. Показывать причину в подсказке или подписи корзины.
Важно: по контракту baseline-значения теперь `None`, а не числа. Убедитесь, что `QuotaBar` и `QuotaBucketWidget` корректно отрисовывают именно отсутствие значения, а не «0 %» — ноль означает «квота исчерпана», это принципиально разные состояния.
### 3. Команда — состояние агента
`active_quota_status` и `active_quota_label` показывают, в каком состоянии квота у аккаунта, на котором сейчас работает агент. Это прямо отвечает на вопрос «сколько осталось у того, кто выполняет задачу».
### 4. Маршрутизация — причина переключения
`PipelineNode.failover_reason` и `quota_status` — то, ради чего экран существует: видно не только «активен резерв 1», но и почему ушли с основного. Показывать причину у узла, с которого произошло переключение.
### 5. Свежесть данных
`HubSnapshot.seq` и политика `is_stale` (по контракту — просрочка фонового обновления свыше 300 с). Показать пользователю, что данные устарели, вместо молчаливого показа старых значений.
---
## Ограничения
- Граница прежняя: ваша зона — `src/antigravity_provider/router/ui/**`, `hermes_hub_app.py`, `tests/test_ui_*.py`. Остальное не трогать.
- Не добавлять сбор данных в UI. Не хватает поля — строка в разделе «Backend gaps» отчёта.
- Нет данных — «Н/Д» либо скрытый блок. Ноль и отсутствие значения не путать.
- Секреты маскировать, в логи не писать.
- Тег `v0.1.1` не создавать.
---
## Критерии приёмки
1. Ни один файл чужой зоны не изменён.
2. `PlanBadge` отображается и различает известный тариф от выведенного.
3. Отсутствие данных о квоте отрисовано как отсутствие, а не как ноль; причина видна пользователю.
4. Экран «Команда» показывает состояние квоты активного аккаунта агента.
5. Экран «Маршрутизация» показывает причину переключения там, где она есть в состоянии.
6. Устаревание снапшота видно пользователю.
7. Тесты на каждый пункт: известный и выведенный тариф, корзина без данных против нулевой, причина переключения, устаревший снапшот.
8. Прогон **в обоих окружениях** — без UI-зависимостей и с `customtkinter`/`pillow`/`psutil`; обе команды и оба результата в отчёте. Прогон только headless не считается: ваши UI-тесты там пропускаются.
9. `ruff check .` чисто; release gate не ухудшен.
10. Отчёт: `BASE_SHA`, `FINAL_SHA`, изменённые файлы, что подключено, что осталось, раздел «Backend gaps».
## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA` и не вести разработку поверх него до вердикта.