fix(build): исключить SIGPIPE из релизных гейтов под pipefail
Поиск с флагом -q прекращает чтение на первом совпадении и закрывает свой конец
канала. Продюсер, которому осталось что писать, получает SIGPIPE и завершается
кодом 141, а `set -o pipefail` делает 141 статусом всей конструкции:
совпадение НАЙДЕНО -> продюсер оборван -> статус 141 -> «не найдено»
Для утвердительных проверок это ложный FAIL. Для отрицательных — «такой
конструкции в коде нет» — ложный PASS: запрещённая конструкция найдена, а гейт
зелёный. Отрицательными проверками закреплена половина инвариантов приёмки,
включая запрет обхода тестов и запрет `pnpm audit --prod`.
Порог резкий: пока вывод продюсера помещается в буфер канала (64 KiB на Linux),
он не блокируется и успевает завершиться раньше, чем потребитель начнёт читать.
Замер, 60 прогонов на размер: до 60 KiB — 0 отказов, ровно на 64 KiB — 58/60,
от 96 KiB — 60/60. То есть проверка выглядит исправной ровно до первого
источника крупнее буфера, а такие файлы в репозитории уже есть.
- 56 мест переведены на here-string: `grep -q PATTERN <<<"$content"`;
- продюсеры-команды (ss|awk, dpkg-query, /proc/cpuinfo, systemctl
list-unit-files) сначала читаются в переменную;
- введён code_has: десять отрицательных сканов держались на `|| true` внутри
code_without_comments, гасившем 141, — то есть на побочном эффекте
подавления ошибок, а не на заявленном свойстве;
- несуществующий путь в скане больше не означает успех: `2>/dev/null || true`
превращал опечатку в пустой вывод, а пустой вывод для проверки «этого в коде
нет» — это PASS. Проверка явная, а не через set -e: в контексте `! code_has`
bash отключает errexit на весь вызов;
- возврат пайплайна запрещён отдельной приёмкой.
This commit is contained in:
@@ -1447,3 +1447,47 @@ hy2xs-orchestrator doctor
|
||||
73. отработавший таймер не блокирует операцию навсегда: `RemainAfterElapse=no` выгружает его, а барьер дополнительно опознаёт `SubState=elapsed` у `*.timer` как покой
|
||||
74. обещанное окно отката — контракт systemd, а не намерение: у транзиентного таймера явно задан `AccuracySec=1s`, иначе умолчание `AccuracySec=1min` превращало «45 секунд» в 45–105
|
||||
75. состояние guard читает один наблюдатель: `status` берёт его у того же кода, что и барьер, и сообщает `unknown` вместо тихого «guard'ов нет» при отказе systemd
|
||||
76. ни один релизный гейт не подаёт вывод в поиск с флагом `-q` через пайплайн: под `set -o pipefail` оборванный продюсер отдаёт 141, и «совпадение найдено» превращается в ненулевой код — для отрицательных проверок это ложный PASS. Сравнение идёт через here-string, и возврат пайплайна запрещён отдельной приёмкой
|
||||
|
||||
### Почему пайплайн в `grep -q` запрещён
|
||||
|
||||
Поиск с флагом `-q` прекращает чтение на **первом** совпадении и закрывает свой
|
||||
конец канала. Продюсер, которому осталось что писать, получает `SIGPIPE` и
|
||||
завершается кодом 141, а `set -o pipefail` делает 141 статусом всей
|
||||
конструкции. Смысл инвертируется:
|
||||
|
||||
```text
|
||||
совпадение НАЙДЕНО -> продюсер оборван -> статус 141 -> «не найдено»
|
||||
```
|
||||
|
||||
Для утвердительной проверки это ложный FAIL — гейт отвергает корректный
|
||||
артефакт. Для отрицательной («такой конструкции в коде нет») — **ложный PASS**:
|
||||
запрещённая конструкция найдена, а гейт зелёный.
|
||||
|
||||
Порог измерим и резкий. Пока весь вывод продюсера помещается в буфер канала —
|
||||
64 KiB на Linux, — он записывает всё, не блокируясь, и успевает завершиться
|
||||
раньше, чем потребитель вообще начнёт читать. Замер на 60 прогонах каждого
|
||||
размера:
|
||||
|
||||
```text
|
||||
4 KiB … 60 KiB отказов 0
|
||||
64 KiB отказов 58/60
|
||||
96 KiB и больше отказов 60/60
|
||||
```
|
||||
|
||||
Поэтому такая проверка годами выглядит исправной, а переворачивается на первом
|
||||
источнике крупнее буфера. В этом репозитории файлы такого размера уже есть
|
||||
(`tools/build/lib/acceptance.sh` — 123 KiB, `orchestrator/src/steps/firewall.ts`
|
||||
— 67 KiB).
|
||||
|
||||
Правильная форма — here-string, у которого пайплайна нет вовсе:
|
||||
|
||||
```bash
|
||||
grep -q 'PATTERN' <<<"$content" || fail "..."
|
||||
```
|
||||
|
||||
Для содержимого файла есть `code_has FILE [флаги] -- PATTERN`: он читает код без
|
||||
комментариев в переменную **отдельным оператором** и сравнивает через
|
||||
here-string. Отдельный оператор важен: в контексте `! code_has …` bash отключает
|
||||
`errexit` на весь вызов, поэтому неудачное чтение проверяется явно, а не
|
||||
рассчитывает на `set -e`.
|
||||
|
||||
Reference in New Issue
Block a user