# Задание A4 (Antigravity): изоляция учётных данных и последние долги

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

## База
Проверочный HEAD: **`d2d6d95`**, `origin/main` = `d2d6d95`. Перед началом `git fetch`, зафиксировать фактический `BASE_SHA`.

## Ветка
`antigravity/credential-isolation`. Ветки `state-layer`, `contract-gaps`, `release-readiness` влиты в `main` и больше не нужны.

---

## Что принято по A3

Проверено исполнением:

- **Релизный ассет выложен и проверяем.** `package_url` → HTTP 200, GitHub Release `Hermes Hub v0.1.1` существует, sha256 загруженного архива совпадает с манифестом посимвольно (`29cfe190…`). Гейт теперь честно печатает `MANIFEST_LIVE=True, PACKAGE_LIVE=True, PACKAGE_HASH_VERIFIED=True`.
- **Контракт исправлен ровно как требовалось**: два раздела — «Closed Gaps & Audit Status» со ссылками на коммиты и «Active Limitations & Backend Constraints», куда вернулись Gap 4 и Gap 12 с явными требованиями к UI. Gap 5 закрыт с обоснованием. Закрытое и оставшееся видны одновременно.
- **Диагностическая команда** и **запись причины переключения маршрута** в журнал сделаны.
- Граница соблюдена, прогон: headless 170 passed, с UI-зависимостями **217 passed, 0 падений**, ruff чисто, гейт PASSED.

Это лучшая сдача за все раунды. Осталось немного.

---

## P0-1. Учётные данные не должны наследоваться subprocess'ом

**Самый старый неисправленный дефект проекта** — отмечен ещё в первом аудите и жив до сих пор:

```
antigravity_adapter.py:43   custom_env = dict(os.environ)
agy_subprocess.py:404       def _safe_env()      ← вызовов: 0
```

В subprocess `agy` уходит **весь** environment, включая `OPENAI_API_KEY`, `CODEX_TOKEN_*`, `OPENCODE_GO_API_KEY`, `DEEPSEEK_API_KEY` и всё остальное. При этом функция фильтрации `_safe_env()` написана автором изначально и не вызывается ни разу — то есть намерение было, реализация не подключена.

Требуется:

- сформировать окружение дочернего процесса **явно**: минимальный набор системных переменных плюс список разрешённых, а не копия `os.environ`;
- профильная изоляция (`USERPROFILE`/`HOME`/`HOMEPATH`) сохраняется как есть — она нужна;
- ключи провайдеров, не относящихся к вызываемому, в дочерний процесс не попадают;
- секреты не пишутся в логи и в диагностическую команду.

Правило на будущее: `os.environ.copy()` и `dict(os.environ)` для внешнего процесса — запрещены без фильтрации. Стоит закрепить тестом, который падает при появлении такого вызова в `src/`.

**Тест обязателен:** запуск с подставным окружением, где заданы ключи нескольких провайдеров, и проверка, что в `custom_env` дочернего процесса их нет.

## P1-2. Оставшиеся долги

Проверено на `d2d6d95`, всё ещё открыто:

| Долг | Состояние |
|---|---|
| Комментарии `router_profiles.yaml` | 5 строк → 2 при сохранении |
| `Registry.CurrentUser` в установщике | 2 вызова, реестр переменными окружения не перенаправляется |
| `fastapi` / `uvicorn` | в обязательных зависимостях, хотя `gui_server` живёт в `legacy/` и не поставляется |
| Сериализация Antigravity | `_AGY_INVOCATION_LOCK` держит вызовы профилей со своим auth по одному |

По каждому — либо закрыть, либо записать как осознанный долг с обоснованием. Гадать не нужно: решение ваше, но оно должно быть зафиксировано, а не забыто.

## P1-3. Объяснение выбора провайдера

Роутер уже собирает `failover_trail` и с A3 пишет причину переключения в журнал. Довести до полноценного объяснения выбора: какие профили рассматривались, какие отсеяны и почему (здоровье, квота, авторизация, лимит параллелизма, несовпадение возможностей), какой выбран.

Данные для этого уже есть в `route_request` — нужно их сохранить в `router_metadata` и в событие, а не терять. Codex отрисует это отдельным заданием; вам — только источник.

---

## Чего делать НЕ нужно

По итогам разбора документа «обновление по новостям ИИ от 21.08.2026»: **не начинать** Lifecycle Supervisor v2, Skill Registry v2, MCP Lifecycle Manager, Park/Resume и process-tree ownership.

Причины: `lifecycle_supervisor`, `skill_registry` и `capability_matrix` были удалены как мёртвый код по решению предыдущего задания; подсистемы MCP в проекте нет вообще; роутер не владеет диалогом, поэтому park/resume нечего сохранять. Это спецификация другого продукта, и часть её должна принадлежать самому Hermes Agent, а не плагину-роутеру.

Из того документа берём ровно два пункта — изоляцию учётных данных (P0-1) и объяснение выбора провайдера (P1-3). Остальное — в бэклог следующей версии.

---

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

1. Ни один файл зоны Codex (`router/ui/**`, `hermes_hub_app.py`, `tests/test_ui_*.py`) не изменён.
2. В дочерний процесс `agy` не попадают ключи провайдеров; доказано тестом с подставным окружением.
3. В `src/` нет `os.environ.copy()` / `dict(os.environ)` для внешнего процесса без фильтрации; закреплено тестом.
4. По каждому долгу из P1-2 — закрыт либо зафиксирован с обоснованием в отчёте.
5. Объяснение выбора провайдера доступно потребителю (метаданные ответа и событие), с перечнем отсеянных кандидатов и причинами.
6. Прогон **в обоих окружениях** — без UI-зависимостей и с `customtkinter`/`pillow`/`psutil`; обе команды и оба результата в отчёте.
7. `ruff check .` чисто; release gate PASSED **на финальном коммите**.
8. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`.

## Тег
`v0.1.1` по-прежнему **не создавать**. Тег ставится после ручного прогона продукта человеком — это отдельное решение владельца.

## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA` и не вести разработку поверх него до вердикта.
