# Задание A26: аккаунты без слотов, распределение по агентам и пороги квот

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

## База
Проверочный HEAD на момент выдачи: **`d4b99a4`**.

## Ветка
`antigravity/accounts-without-slots`

## Порядок исполнения

Два прохода: **Flash** реализует, **Pro** проводит аудит. Пункт **P0-6** написан для аудитора.

---

## Порядок работы с git

```
cd <каталог репозитория>; git fetch origin --prune; git status
git checkout main; git pull --ff-only origin main
git checkout -b antigravity/accounts-without-slots
git commit -m "..."          <- сначала коммит
git push -u origin antigravity/accounts-without-slots
```

В `main` напрямую не пушить. В конце — push и проверка `git log --oneline -1`, `git status` чистый.

---

## Ради чего это всё

Дословно от владельца:

> «вот для этого хаб и делался. чтобы вручную правильно распределять аккаунты и играться с лимитами. где подзаканчиваются, там ставить другой аккаунт»

Сейчас продукт этого не даёт. Модель «слотов» — десять предсозданных ячеек Antigravity, три Codex, три OpenCode — навязывает пользователю внутреннее устройство конфигурации. Владелец выбирает слот, ничего не зная о последствиях, и получает результат, которого не ожидал: аккаунт подключён, а на экранах его нет, потому что слот не входит ни в одну цепочку. Это уже случилось вживую.

Нужен обратный порядок: **сначала аккаунты, потом назначение**.

---

## Что уже проверено исполнением — заново не выясняйте

Три факта, снятые ревьюером на `d4b99a4`. Они сильно сокращают работу.

**1. Один профиль может обслуживать все шесть ролей.** Движок это держит уже сейчас:

```
persist_role_chain(role, ['ag-w1']) для всех шести ролей -> True
конфигурация: все шесть цепочек равны ['ag-w1']
```

Значит требование «если есть только Antigravity, он встаёт во всех агентах» **не требует изменений в маршрутизаторе**. Не переписывайте `router_engine`.

**2. Произвольный идентификатор профиля регистрируется.**

```
ensure_profile_definition('openai-codex', 'codex-worker-9') -> True
'codex-worker-9' in load_router_config().profiles -> True
```

Значит потолок «три аккаунта Codex» — это **только** зашитый список `provider_slots` в `auto_assigner.py:144`, а не структурное ограничение. Снятие потолка не требует переделки формата конфигурации.

**3. Порогов квот в коде нет вообще.** Поиск по `threshold|min_quota|quota_floor|switch_at` в `router/` не даёт ни одного совпадения. Это делается с нуля.

---

## P0-1. Снять потолок на число аккаунтов

Дословно: «например у меня есть 4 кодекса, и их все я хочу добавить. или 5 опенкодов».

Сейчас `AutoAssigner.find_free_slot` (`auto_assigner.py:140`) перебирает жёсткий список и возвращает `None`, когда список кончился. Четвёртый Codex подключить невозможно.

Требуется: число аккаунтов провайдера **ничем не ограничено**. Когда предопределённые имена кончились, идентификатор выдаётся автоматически по понятной схеме (`codex-4`, `codex-5`, …), с проверкой, что такого ещё нет.

Идентификатор — деталь реализации, и в интерфейсе он не должен быть главным. Владелец мыслит аккаунтами и почтами, а не слотами.

## P0-2. «Аккаунты» показывают только настоящие аккаунты

Дословно: «надо убрать все ячейки со вкладки аккаунты. как добавляю аккаунт, тогда он там появляется. не надо захламлять страницу».

Сейчас в умолчаниях предсозданы **24 профиля**, и страница показывает их все, включая никогда не подключённые: «Аккаунт не добавлен», «Холодный резерв». У владельца это 24 карточки при одном реальном аккаунте.

Требуется показывать **только подключённые**.

Осторожно: не удаляйте профили из конфигурации молча — на них ссылаются цепочки ролей. Речь о том, что показывать, а не о том, что хранить. Решите чистить и конфигурацию — обоснуйте в отчёте и сохраните работоспособность цепочек.

## P0-3. Назначение аккаунта агенту — в «Обзоре»

Дословно: «просто загрузка аккаунтов, а уже в обзоре прикреплять нужный аккаунт к агенту».

На «Обзоре» уже есть схема ролей и выбор модели в узле (A24). Добавить туда выбор **аккаунта**: какой обслуживает эту роль и в каком порядке резервирования.

Действия существуют — `assign_role`, `save_chain`, `reorder_chain`; второй реализации не заводить.

Ключевое требование, вытекающее из факта №1: **один аккаунт можно назначить сразу нескольким агентам**, вплоть до всех шести. Интерфейс не должен этому препятствовать и не должен считать это ошибкой.

## P0-4. Кнопка «Авто»

Дословно: «или при нажатии авто, сам распределяет аккаунты согласно маршрутизации».

Действие `auto_assign_all` существует (`action_handler.py:509`), но что оно делает и совпадает ли с ожиданием владельца — **проверьте исполнением и опишите в отчёте**. Отчёт без запуска не принимается.

Ожидаемое поведение:

- **Один провайдер.** Есть только Antigravity — он встаёт во все шесть ролей. Владелец дальше сам меняет модель у каждого агента. Это нормальный режим, а не вырожденный случай.
- **Несколько провайдеров.** Распределение идёт по назначению роли: «оркестратор у меня первый кодекс, а кодер антигравити» — у каждой роли есть предпочтительный провайдер, и авто-распределение ему следует, а не раскладывает аккаунты подряд.
- **Несколько аккаунтов одного провайдера** разводятся по разным ролям, чтобы не жечь квоту одного на всё сразу.

Порядок предпочтений по ролям возьмите из текущего `config/router_profiles.example.yaml` — он и выражает замысел владельца. Решите его менять — сначала спросите в отчёте, молча не меняйте.

## P0-5. Пороги квот: предупреждение или переключение

Дословно: «выставлять минимальные лимиты (например 10% или 5%) и при достижении этих лимитов или оповещение или автоматом переключает на резервный аккаунт».

Это то, ради чего хаб задумывался. Требуется:

1. **Настраиваемый порог** — общий и, если несложно, отдельный для аккаунта. Значения владельца: 10% и 5%. **В код их не зашивать**, это умолчание, а не константа.
2. **Выбор поведения** при достижении: только оповестить либо переключиться на следующий аккаунт в цепочке. Решает владелец, не код.
3. **Оповещение видно в интерфейсе** — в «Состоянии» и в журнале событий. Достаточно ли тоста — решите и обоснуйте.
4. **Переключение обратимо.** Квоты восстанавливаются по `reset_at`; отставленный по порогу аккаунт обязан вернуться в строй сам. Урок A23 уже оплачен: у `AUTH_REQUIRED` не было выхода, и шесть профилей выпали навсегда. **Повторять нельзя.**

Данные есть: `quota_collector` собирает проценты и `reset_at`, они видны в карточках.

Честность обязательна: порог срабатывает по **измеренной** квоте. Неизвестная квота («Н/Д») — **не** повод считать её нулевой и переключаться. Нет данных — так и сказать, поведение не менять.

## P0-6. Аудит вторым проходом

Для проверяющего. Список составлен из дефектов, уже проходивших мимо первого прохода.

1. **Выдуманные значения.** Находились захардкоженные коды устройства `GRK-7842`, `CDX-9104`, запасной коммит `fb23bff` в манифесте. Здесь особый риск в P0-5: соблазн подставить «разумный» процент при отсутствии данных.
2. **Тесты, закрепляющие дефект.** `test_headless_server_auth_matrix` дважды защищал заглушку: сначала требовал неверный адрес `x.ai/device`, затем слова «Headless» и «agy», хотя вход через веб уже работал. Новые тесты должны описывать желаемое поведение.
3. **Запустить изменённое.** Импорт ничего не доказывает. На днях вложенная функция оказалась не видна части точек вызова и давала `NameError` внутри обработки успеха — поймал `ruff`, а не тест.
4. **Кэш состояния.** Только что чинилось: учётные данные сохранялись, но `refresh(force_scan=False)` состояние не обновлял, и подключённый аккаунт не появлялся никогда. Любое изменение состава аккаунтов обязано быть видно **сразу**, без перезапуска.
5. **Побочные изменения** в файлах, которых задание не касалось, — объяснить каждое.
6. **Пропущенный пункт назвать пропущенным.**

---

## Ограничения

- Десктоп (`router/ui/**`) не трогать: он выводится из обращения.
- Правило честности без исключений. Нет данных — «Н/Д» и причина.
- Действия только через существующий `action_handler`; второй реализации `assign_role`, `save_chain`, `set_model` быть не должно.
- Меняете контракт — правьте `docs/web-api/CONTRACT.md` и скажите в отчёте.
- Конфигурацию владельца литералами не править.
- Тег `v0.1.1` не создавать.

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

1. Ветка в `origin`, `git status` чист.
2. Подключается **пятый** аккаунт одного провайдера; проверено исполнением, вывод в отчёте.
3. «Аккаунты» показывают только подключённые; при нуле подключённых — внятное пустое состояние, а не 24 пустые карточки.
4. Аккаунт назначается агенту из «Обзора»; один аккаунт назначается **всем шести** ролям; изменение доходит до `router_profiles.yaml` и переживает перезапуск.
5. «Авто» описано по факту запуска: что делает при одном провайдере, при двух, при нескольких аккаунтах одного провайдера.
6. Порог настраивается, значение не зашито; при достижении срабатывает выбранное поведение; при неизвестной квоте не срабатывает.
7. Аккаунт, отставленный по порогу, возвращается в строй после восстановления квоты **без ручного вмешательства**; проверено тестом.
8. Изменения состава аккаунтов видны в интерфейсе сразу, без перезапуска.
9. Ни одного выдуманного значения; проверено отдельно и описано в отчёте.
10. `ruff check .` чисто; релизный гейт не ухудшен.
11. **Скриншоты:** «Аккаунты» с одним аккаунтом, «Обзор» с назначением, настройка порога.
12. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`. На `main` сейчас **432 passed, 2 skipped**.

## Главное

Владелец хочет управлять аккаунтами, а не слотами: загрузил аккаунты, распределил по агентам, следит за квотами и переставляет, когда они подходят к концу. Всё остальное в задании обслуживает это.

## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`. Сдано только после появления коммита в `origin`.
