# Задание B3 (Codex): подключить поля контракта v1.1

## Дата поступления
2026-08-21

## База
Проверочный HEAD: **`2c612f2`**, `origin/main` = `2c612f2`. Перед началом `git fetch` и обязательно подтянуть `main` — ваши фазы 2–6 уже влиты, поверх них есть правки.

## Ветка
`codex/contract-v11`. Ветка `codex/ui-redesign` влита в `main`.

---

## Что принято по фазам 2–6

Проверено исполнением, работа хорошая:

- граница соблюдена — вне вашей зоны только файл отчёта;
- **пересоздания виджетов не осталось нигде**: ни одного `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` и не вести разработку поверх него до вердикта.
