Skip to content
Exlogare

Приватность и отчёт «State of CI Failures»

Как Exlogare готовит ежеквартальный публичный отчёт о причинах падений CI — что попадает в агрегаты, что мы никогда не публикуем и как отключить участие.

Exlogare ежеквартально публикует открытый отчёт State of CI Failures — YYYY Qn о главных причинах падений CI у наших клиентов. Отчёт собирается автоматически из тех же RCA, которые питают ваш дашборд, но с жёсткими правилами приватности, зашитыми в pipeline агрегации.

Эта страница описывает, что туда попадает, что — никогда, и как отказаться от участия.

Что мы публикуем

В каждом выпуске State of CI Failures:

  • Топ первопричин — представительная формулировка root cause, число команд, у которых эта причина встречалась (k), суммарные хиты и severity.
  • Топ провайдеров CI — количество падений по каждому провайдеру (github_actions, gitlab, bitbucket, circleci, jenkins, drone, teamcity, generic, …), чтобы видеть концентрацию.
  • Narrative-секция, которую пишет человек из команды Exlogare и где разбирает тренды и даёт ссылки на product docs.

Черновики генерируются автоматической квартальной задачей и проходят ручной review до публикации. В тело черновика попадают только агрегаты — человек добавляет интерпретацию, но не дополнительные строки.

Чего мы не публикуем никогда

В pipeline агрегации зашиты три жёстких правила. Их нельзя обойти ручной правкой.

  1. Opt-out проверяется в момент агрегации, не в момент публикации. Если команда выключила Делиться анонимной статистикой, она исключена из следующего квартального прогона. Проверка делается по живому значению tenants.share_anonymized_stats; мы не кэшируем флаг.
  2. k-anonymity = 5. Кластер попадает в отчёт, только если его видели минимум 5 разных команд. Всё, что ниже этого порога, просто отбрасывается. Этот же порог неявно действует и для топа провайдеров через ту же выборку.
  3. Никаких идентификаторов. Агрегатор никогда не отдаёт tenant id, project id, путь проекта, имя репозитория, имя ветки, commit SHA, email пользователя или URL пайплайна. Падения группируются по SHA-1 от нормализованного root cause + severity — fingerprint считается в момент анализа.

Fingerprint — односторонний хэш. Даже сотрудник Exlogare не может восстановить исходный текст первопричины из fingerprint, не имея доступа к самому ряду анализа в вашем tenant.

Как работает агрегация

Квартальная задача запускается в 10:00 UTC 2-го января, апреля, июля и октября. Она агрегирует предыдущий календарный квартал (≈92 дня) строк failure_clusters у tenant’ов с share_anonymized_stats = true. На выходе — два ряда BlogPost: state-of-ci-YYYY-qN для lang=en и lang=ru, оба с published_at = NULL.

Если черновик с этим slug уже существует, задача его не перезаписывает — повторный запуск идемпотентен.

Как отключить

  • В дашборде: Settings → Приватность → выключить Делиться анонимной статистикой.
  • Через API: PATCH /api/tenants/me/privacy с телом {"share_anonymized_stats": false}. Доступно любому аутентифицированному участнику команды.

Изменение применяется сразу. Если выключить в середине квартала, вы исключены из ближайшей квартальной агрегации и всех последующих, пока флаг не будет включён обратно. Уже опубликованные отчёты не пересобираются (это markdown-строки в блоге), но в них и так никогда не было идентификаторов вашей команды.

Зачем нужен баннер

Когда пользователь из вашей команды впервые открывает дашборд после введения этого флага, на странице Overview появляется баннер с просьбой подтвердить или отключить участие. Любое из действий — Оставить включённым, Отключить или X — фиксирует выбор и проставляет share_anonymized_stats_acknowledged_at. Баннер больше не появляется.

Audit-трейл

Каждое переключение флага приватности записывается в audit log команды под action’ом tenant_privacy_updated — с email актора и предыдущим / новым значениями. Audit log живёт в дашборде в Settings → Журнал событий (нужна роль admin) и экспортируется в CSV.

Ссылки

  • Кластеры падений — откуда берутся per-team count и fingerprint_hash.
  • API-токены — для PATCH /api/tenants/me/privacy токен с scope read не нужен, используются обычные сессионные учётные данные.