diff --git a/agents/inbox/2026-08-21-A4-antigravity-credential-isolation.md b/agents/inbox/2026-08-21-A4-antigravity-credential-isolation.md new file mode 100644 index 0000000..5513219 --- /dev/null +++ b/agents/inbox/2026-08-21-A4-antigravity-credential-isolation.md @@ -0,0 +1,95 @@ +# Задание 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` и не вести разработку поверх него до вердикта.