# Задание A7 (Antigravity): два дефекта в данных дашборда ## Дата поступления 2026-08-21 ## База Проверочный HEAD: **`69cbefc`**, `origin/main` = `69cbefc`. `git fetch`, зафиксировать `BASE_SHA`. ## Ветка `antigravity/dashboard-fixes` --- ## Что принято по A6 Проверено исполнением, работа хорошая: - **разрезы телеметрии в снапшоте.** Прогнал 6 вызовов оркестратора и 2 исследователя (последние с переключением) — получил 10 записей, что верно: неуспешная попытка тоже учитывается. Доли сошлись точно: ``` openai-codex : total_calls=6, call_share=0.6 antigravity : total_calls=2, call_share=0.2 opencode-go : total_calls=2, call_share=0.2 сумма = 1.0 ``` - **разрез по ролям**: `orchestrator=6`, `research=4` — совпадает с числом попыток; - **пустое окно честно**: `has_data=False`, `latency_p50_ms=null`, `total_tokens=null`, при этом `total_calls=0` — счётчик как факт, а латентность как отсутствие. Ровно то различие, которого добивались; - **контракт снова разделён правильно**: Gap 13 закрыт как самоизмеряемый, для недоступного заведён **Gap 14** — RPS провайдера, SLA, очереди задач, окно обслуживания, с сохранённым требованием «Н/Д либо скрыть». Третий раз подряд без молчаливых пропаж; - прогон: headless 189 passed, с UI-зависимостями 235 passed, ruff чисто, гейт PASSED. --- ## P0-1. `active_calls` всегда ноль: два разных `LeaseManager` `state_store.py:172` читает синглтон: ```python active_leases_total = LeaseManager.get().total_active_count() ``` `router_engine.py:31` создаёт **собственный** экземпляр: ```python self.leases = leases or LeaseManager() ``` Роутер захватывает лизы в своём приватном менеджере, снапшот читает синглтон, который всегда пуст. Проверено: ``` движок и синглтон — один объект? False id(engine.leases) = 2732720631632 id(LeaseManager.get()) = 2732720631376 после acquire на движке: движок видит : 1 синглтон видит: 0 ``` Итог: `active_calls_total` в снапшоте будет **постоянно 0** при любой нагрузке. Блок «Активные задачи» на дашборде покажет ноль всегда — то есть правдоподобное, но неверное число. Требуется: один источник истины по лизам. Либо `RouterEngine` по умолчанию берёт синглтон, либо `state_store` читает лизы у движка. Второе честнее: лизы принадлежат движку, а не глобальному состоянию. **Тест обязателен:** захватить лиз через тот же путь, которым пользуется роутер, и убедиться, что снапшот его видит. Попутно: `ProfileHealthRecord.active_leases` по-прежнему всегда 0 и при этом выводится в CLI и API — это отмечалось ещё в первом аудите. Либо заполнять, либо убрать из вывода. ## P0-2. CPU при первом измерении всегда 0 % `host_metrics.py:57` — `psutil.cpu_percent(interval=None)`. Без предыдущей точки отсчёта первый вызов возвращает `0.0` по устройству самой библиотеки. Проверено: ``` первый вызов (холодный): 0.0 второй вызов: 44.1 третий вызов: 12.0 ``` На старте приложения пользователь увидит «CPU 0 %» — и это тот же класс дефекта, с которым мы боролись в квотах: число выглядит измеренным, но измерением не является. Требуется одно из: прогреть счётчик при инициализации сервиса (один вызов, результат отбросить), либо возвращать `None` для первого измерения, либо использовать короткий интервал. Первый вариант предпочтительнее — он не создаёт «дырки» в интерфейсе. **Тест обязателен:** первое значение не должно быть нулём, полученным из-за холодного старта. ## P1-3. Остаток по YAML Комментарии сохраняются только заголовочные, внутренние теряются: ``` # Antigravity (10 accounts) # OpenAI Codex (3 accounts) # OpenCode Go (3 accounts) # Optional: User Model Pricing Table (USD per 1M tokens) # Telemetry will compute call cost in USD only if a model price is defined below. ``` Первыми стираются пояснения к таблице цен, добавленные в A5. В отчёте статус указан «Закрыт» — это неточно. Либо довести до полного round-trip, либо переписать статус на «частично» с перечнем теряемого. --- ## P1-4. «Сеть» показывает счётчик, а не текущий показатель `host_metrics.py:79-80` отдаёт `net_bytes_sent` / `net_bytes_recv` — накопительные счётчики `psutil.net_io_counters()` с момента загрузки машины. `dashboard_view.py:630-633` складывает их и делит на 1024², получая «Сеть 56155 МБ». Это число стоит в одном ряду с CPU, памятью и диском — то есть читается как текущее состояние, а на деле это ~55 ГБ трафика за всё время работы системы. На макете в этой позиции была скорость («42 Мбит/с»). Число настоящее, вводит в заблуждение подача. Нужно одно из двух: считать скорость по разнице двух замеров (это и просил макет) либо отдавать значение с явной семантикой «всего с момента загрузки», чтобы интерфейс мог подписать его правильно. Отрисовка — зона Codex, но решение о том, что именно отдавать, принимается здесь: сейчас в контракте `net_bytes_*` описаны без указания, что это накопительный счётчик. ## Ограничения - Граница прежняя: зона Codex (`router/ui/**`, `hermes_hub_app.py`, `tests/test_ui_*.py`) — не трогать. - Никаких чисел без измерения. Ноль допустим только как результат подсчёта, а не как значение по умолчанию. - Тег `v0.1.1` не создавать. ## Критерии приёмки 1. Ни один файл зоны Codex не изменён. 2. Захваченный роутером лиз виден в снапшоте; проверено тестом через реальный путь захвата. 3. `ProfileHealthRecord.active_leases` заполняется либо убран из вывода CLI и API. 4. Первое измерение CPU не равно нулю из-за холодного старта; проверено тестом. 5. Статус YAML в отчёте и контракте соответствует фактическому поведению. 6. Прогон **в обоих окружениях**; обе команды и оба результата в отчёте. 7. `ruff check .` чисто; release gate PASSED на финальном коммите. 8. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`. ## Порядок сдачи Передать точный `FINAL_COMMIT_SHA`.