# Задание A5 (Antigravity): настоящая телеметрия вызовов и закрытие долгов ## Дата поступления 2026-08-21 ## База Проверочный HEAD: **`31e4e7f`**, `origin/main` = `31e4e7f`. `git fetch`, зафиксировать `BASE_SHA`. ## Ветка `antigravity/telemetry` --- ## Что принято по A4 Проверено исполнением: - **Утечка учётных данных закрыта — самый старый дефект проекта.** Подставил в окружение `OPENAI_API_KEY`, `CODEX_TOKEN_X`, `DEEPSEEK_API_KEY`, `ANTHROPIC_API_KEY`, перехватил окружение дочернего процесса `agy`: 31 переменная, **ноль ключей провайдеров**, изоляция профиля через `USERPROFILE` сохранена. - `selection_trace` — объяснение выбора провайдера появилось в метаданных ответа. - Изоляция реестра в тестах установщика доработана. - Граница соблюдена. Прогон: headless 175 passed, с UI-зависимостями **222 passed**, ruff чисто, гейт PASSED. --- ## Контекст: почему это задание Владелец сравнил установленную сборку с утверждёнными макетами. На макетах ключевое место занимают показатели: ``` 98 ms P95 · 842 rps · 99.98% успешные · 1.23% ошибки 1.2M / 3.0M токенов · история использования в USD CPU 28% · Память 61% · Диск 39% · Сеть 42 Мбит/с ``` Сейчас контракт объявляет их отсутствующими (Gap 12), и UI обязан показывать «Н/Д». **Но это верно не для всех.** Разберём по источнику данных: | Показатель | Источник | Вердикт | |---|---|---| | Латентность вызова | `router_engine.py:254-256` уже замеряет `elapsed` | **измеримо, данные выбрасываются** | | Токены | `agy_subprocess.py:623` уже извлекает `prompt_tokens`/`completion_tokens`; HTTP-адаптеры получают `usage` от провайдера | **измеримо, данные выбрасываются** | | Количество переключений | `failover_count` и `failover_trail` уже считаются | **измеримо, данные выбрасываются** | | Доля ошибок | классификация ошибок уже есть | **измеримо** | | Стоимость | нужна таблица цен по моделям | измеримо **при наличии прайса** | | RPS провайдера, SLA, uptime | провайдеры не отдают | **неизмеримо** | | CPU, память, диск, сеть | к работе роутера отношения не имеют | **неизмеримо и не нужно** | То есть четыре показателя из семи — настоящие, и мы их просто теряем. Задание: перестать их терять. --- ## P0-1. Сохранять телеметрию собственных вызовов Сейчас `router_metadata` собирается и отдаётся в ответе, после чего пропадает. Ни одного места, где эти цифры накапливаются, в проекте нет. Завести хранилище телеметрии — по одной записи на вызов: ``` timestamp role profile_id provider model outcome (success | failover | error | quota_exhausted | rate_limited) latency_seconds prompt_tokens completion_tokens failover_count error_category (если применимо) ``` Требования: - запись не должна замедлять вызов — писать асинхронно либо буферизовать; - ограниченный размер: кольцевой буфер в памяти плюс ротация файла, без неограниченного роста; - ни одного секрета и ни одного фрагмента содержимого запроса или ответа — только метаданные; - переживать перезапуск приложения (агрегаты можно хранить в файле рядом с `router_state.json`, атомарно, как уже сделано там). **Токены брать только из `usage`, который вернул провайдер.** Если провайдер `usage` не вернул — поля пустые, а не оценка по длине текста. ## P0-2. Агрегаты, которые честно выводятся из записей Поверх хранилища — агрегаты по провайдеру, профилю, модели и роли за окно времени: - латентность: P50, P95, максимум, число вызовов; - токены: суммарно на вход и на выход; - переключения: сколько раз уходили с профиля и по какой причине; - доля ошибок: доля неуспешных вызовов от общего числа. Это **наши собственные наблюдения за нашими же вызовами**, а не заявления провайдера. В контракте так и записать: источник `own_measurement`, а не `*_api`. Разница принципиальна и должна быть видна в данных. Если вызовов в окне не было — отдавать отсутствие данных, а не нули. «Ноль вызовов» и «ноль миллисекунд» — разные вещи, ровно как «нет данных» и «квота исчерпана». ## P1-3. Стоимость — только при наличии прайса Стоимость считать, если в конфигурации задана таблица цен по моделям (вход/выход за миллион токенов). Нет таблицы — нет стоимости, поле пустое. Не зашивать цены в код: они меняются, а зашитый прайс через месяц станет тихой ложью. Формат — в конфиге пользователя, с примером в `config/`. ## P1-4. Обновить контракт Gap 12 переформулировать: часть показателей переходит из «неизмеримо» в «измеряется нами». Для каждого нового поля указать источник (`own_measurement`), окно агрегации и поведение при отсутствии вызовов. Разделы «Closed Gaps» и «Active Limitations» сохранить в текущем виде — они удачные. RPS провайдера, SLA, uptime, CPU/память/диск/сеть остаются в «Active Limitations» с прежним требованием к UI: «Н/Д» либо блок отсутствует. ## P1-5. Закрыть три долга Проверено на `31e4e7f`, всё ещё открыто и не зафиксировано как осознанный долг: | Долг | Состояние | |---|---| | `Registry.CurrentUser` в `HermesHubSetup.cs` | 2 вызова | | `fastapi` / `uvicorn` в обязательных зависимостях | `gui_server` живёт в `legacy/` и не поставляется | | Комментарии `router_profiles.yaml` | 5 строк → 2 при сохранении | По каждому — закрыть либо записать обоснование в отчёте. Третий раз переношу их из задания в задание; молчаливый перенос дальше не годится. --- ## Ограничения - Граница прежняя: зона Codex (`router/ui/**`, `hermes_hub_app.py`, `tests/test_ui_*.py`) — не трогать. - Никаких оценок вместо измерений. Нет данных — нет числа. - Секреты и содержимое запросов в телеметрию не попадают. - Тег `v0.1.1` не создавать. ## Критерии приёмки 1. Ни один файл зоны Codex не изменён. 2. После серии вызовов (моками) в телеметрии есть записи с латентностью, токенами и исходом; проверено тестом. 3. Агрегаты считаются корректно: тест с известным набором значений проверяет P50/P95 и суммы токенов. 4. При отсутствии вызовов агрегаты отдают отсутствие данных, а не нули; проверено тестом. 5. Токены не появляются, если провайдер не вернул `usage`; проверено тестом. 6. В телеметрии нет секретов и содержимого запросов; проверено тестом с подставным ключом и текстом. 7. Размер хранилища ограничен; ротация проверена тестом. 8. Контракт обновлён: новые поля с источником `own_measurement`, неизмеримое осталось в «Active Limitations». 9. Три долга закрыты либо зафиксированы с обоснованием. 10. Прогон **в обоих окружениях**; обе команды и оба результата в отчёте. 11. `ruff check .` чисто; release gate PASSED на финальном коммите. 12. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`. ## Порядок сдачи Передать точный `FINAL_COMMIT_SHA`.