docs(task): A4 — credential isolation, remaining debts, provider explain trace

The agy subprocess still inherits the entire environment, so every provider key
travels into it; _safe_env() was written for exactly this and has never been
called. That is the oldest unfixed finding in the project.

Also scopes out the "AI news" proposal: lifecycle supervisor, skill registry and
MCP lifecycle target modules this repo deleted as dead code or never had, so
only credential isolation and the provider explain trace are adopted from it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-21 10:10:46 +07:00
parent d2d6d95188
commit 33b99982b2

View file

@ -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` и не вести разработку поверх него до вердикта.