ADR 0140 — Повторяющийся timeout pnpm audit — принятый риск; находка по-прежнему отклоняет
Контекст
2026-09-04 endpoint advisories npm
(POST registry.npmjs.org/-/npm/v1/security/advisories/bulk) часами работал
с перебоями. Job Auditoria de dependências отклонил четыре разных
PR (#464, #466, #396, #468), всегда с одной и той же сигнатурой:
[WARN] POST .../security/advisories/bulk error (23). Will retry in 10 seconds. 2 retries left.
[WARN] POST .../security/advisories/bulk error (503). Will retry in 1 minute. 1 retries left.
TimeoutError: The operation was aborted due to timeout
Дело было не в дереве зависимостей, не в runner и не в какой-либо из PR.
Это было воспроизведено вне CI прямым обращением к endpoint с другой
машины: то же отсутствие ответа за 20 с, тогда как обычный GET к тому же
registry отвечал за 130 мс. pnpm audit уже повторяет три раза внутри
(две строки retry выше); сервис просто не отвечал.
Цена была не только во времени. Она была в поведении, которому учит ситуация: каждый такой сбой решается перезапуском job, пока он не пройдёт, и тот, кто научился перезапускать до прохождения, перезапускает и тогда, когда красное настоящее. Gate, который кричит без причины, не остаётся нейтральным — он подрывает доверие к gates, которые кричат по делу.
Решение
Решение владельца продукта: три попытки без ответа от registry — ПРИНЯТЫЙ РИСК. Job остаётся зелёным, с громким предупреждением в сводке.
Чего решение не ослабляет, и что делает его защитимым:
| ситуация | вердикт | повтор |
|---|---|---|
pnpm audit завершается с 0 | проходит | — |
| отчёт об уязвимости | отклоняет | никакого — ответ уже пришёл, и он "нет" |
| запрос не завершился (timeout/5xx/DNS/socket) | infra | до 3 |
| 3 infra подряд | проходит, объявлено | — |
| нераспознанный вывод | отклоняет | никакого |
Приоритет — суть этого. scripts/ci/auditoria-de-dependencias.ts
классифицирует вывод, и маркеры НАХОДКИ проверяются до сетевых:
отчёт, который случайно упоминает timeout — пакет с таким именем, CVE
о timeout, — не может быть прощён как недоступность. Есть тест
ровно на этот случай.
Неизвестный сбой отклоняет (fail closed). Если вывод не совпадает ни с одной
из двух сигнатур, вердикт — achado. Единственное, что хуже gate,
который отклоняет слишком много, — это gate, одобряющий то, чего он не понял.
Предупреждение говорит, чего выполнение НЕ утверждает. Когда риск принят, сводка job фиксирует, что дерево не было проверено — никогда, что оно чисто. Зелёное из-за недоступности и зелёное из-за чистого аудита — разные состояния, и job не путает их в тексте.
Логика живёт в тестируемом скрипте, а не в bash внутри YAML:
классификация — чистая функция с 11 случаями, включая РЕАЛЬНЫЕ выводы
прогона 33838507158 (того, что мотивировал всё это), а не перефразировки.
Последствия
Существует реальное окно риска, и оно объявлено. При длительной недоступности npm PR входят без аудита зависимостей. Это зафиксировано в cadeia-de-suprimentos-do-ci.md, в разделе "What is still trusted on faith", рядом с другими принятыми доверительными допущениями — не спрятано в комментарии workflow.
Альтернатива была измерена, прежде чем её отвергли. Сохранение красного из-за падения третьей стороны стоило в тот день четырёх застрявших PR и нескольких повторных запусков по ~5 мин каждый, без того чтобы хоть один из них что-то обнаружил о коде. Что покупается этой жёсткостью — видимость строгости; что оплачивается — привычка игнорировать красное.
Меняется только сторона pnpm. mix hex.audit и mix deps.audit (Elixir) остаются
как были — инцидент был с endpoint npm, а расширение исключения на тех,
кто о нём не просил, превратило бы точечное решение в общую политику.
Что остаётся без ответа: нет оповещения, когда риск принимается часто. Три timeout в одиночной PR — шум третьей стороны; три timeout в каждой PR за неделю — фактически отключённый gate, и сегодня ничто не различает эти два случая, кроме того, что кто-то заметит. Остаётся объявленным пробелом, здесь не решённым.