build: закрыть обходы релизного гейта тестов и проверять весь граф npm

Два гейта сборки проверяли не то, что обещали.

1. pnpm audit проверял production-подграф вместо всего lock-графа.

   Гейт запускался с --prod под обоснованием «devDependencies в артефакт не
   попадают». Для frontend build tooling это неверно по существу: vite и
   rollup действительно не копируются на production-сервер как node_modules,
   но они ИСПОЛНЯЮТСЯ на build-машине, читают наши исходники и порождают тот
   самый production-бандл, который уезжает в артефакт.

   Это не гипотеза: DOM clobbering в Rollup затрагивал именно генерируемый
   бандл, и `pnpm audit --prod` его не показывал — по всему графу тот же
   прогон дал 33 предупреждения против нуля. Критерий приёмки №47 в docs/11
   формулировал «по всему графу» правильно ещё до того, как это стало правдой
   в коде.

   На текущем lock-файле полный граф на пороге high чист.

2. SKIP_TESTS позволял собрать production-артефакт без тестов.

   Переменная была описана как «аварийное отключение тестов; для
   release-сборок недопустимо». Недопустимость держалась исключительно на этой
   фразе: ни metadata, ни финальная приёмка архива не проверяли, что тесты
   запускались. То есть

       SKIP_TESTS=true ./tools/build/build.sh

   доходила до конца и выдавала обычный tarball с build_profile=production и
   dependency_security_gate=true — артефакт, по которому невозможно отличить
   проверенную сборку от непроверенной.

   Глушила она при этом не только тесты: под тем же флагом пропускались
   `tsc --noEmit` для оркестратора и `go vet` для админки, то есть проверка
   типов и статический анализ того самого кода, который уезжает в production.

   Выбран тот же строгий вариант, что уже принят для проверки зависимостей:
   обхода нет. Готовый пакет объявляет tests_gate=true в metadata, и это
   утверждение опирается на результат — обе функции прогона выставляют свой
   флаг только после успешного завершения, а write_metadata отказывается
   писать метаданные, если хотя бы один не подтверждён.

Приёмка закрепляет оба инварианта: --prod не может вернуться в гейт, SKIP_TESTS
не может вернуться ни в один модуль сборки и ни в README/docs, tests_gate=true
обязателен в metadata, а утверждение о прогоне обязано следовать за прогоном.
This commit is contained in:
2026-08-30 18:18:13 +05:00
parent e84fdedc4b
commit 594525dd73
8 changed files with 290 additions and 20 deletions
+43 -1
View File
@@ -152,7 +152,20 @@ bundle из кода, который не проходит проверку ти
| Проверка | Что покрывает | Порог |
| --- | --- | --- |
| `govulncheck ./...` | Go-граф **и stdlib**, с анализом достижимости: уязвимость считается только при наличии пути вызова из нашего кода | любая вызываемая |
| `pnpm audit --prod` | production-зависимости frontend, без анализа достижимости | `PNPM_AUDIT_LEVEL` |
| `pnpm audit` | **весь** lock-граф frontend, включая build tooling, без анализа достижимости | `PNPM_AUDIT_LEVEL` |
Про «весь граф» отдельно, потому что здесь стояло `--prod` с обоснованием
«devDependencies в артефакт не попадают».
Для frontend build tooling это обоснование неверно по существу. `vite` и
`rollup` действительно не копируются на production-сервер как `node_modules`.
Но они **исполняются на build-машине, читают наши исходники и порождают тот
самый production-бандл**, который уезжает в артефакт. Уязвимость в них — это
уязвимость в том, что мы выпускаем.
Это не гипотеза: DOM clobbering в Rollup затрагивал именно генерируемый бандл, а
проверка по одному production-подграфу его не показывала. По всему графу тот же
прогон дал 33 предупреждения против нуля.
Версия `govulncheck` пиньтся в `versions.env`, а база уязвимостей подтягивается
на каждом запуске: пин инструмента не должен превращаться в пин знаний о мире.
@@ -180,6 +193,35 @@ bundle из кода, который не проходит проверку ти
локальной работы обходить нечего: `go test ./...`, `govulncheck ./...` и
`pnpm audit` запускаются напрямую и tarball не создают.
### Тесты и типы
Та же политика и по той же причине. Аварийного выхода у этого шага **нет**:
переменной, отключающей тесты, не существует.
Проверяется на трёх участках:
| Шаг сборки | Что запускается |
| --- | --- |
| `run_orchestrator_tests` | `bun x tsc --noEmit`, `bun test` |
| `bundle_ui` | `pnpm run typecheck` (`vue-tsc --noEmit`) до сборки bundle |
| `run_admin_tests` | `go vet ./...`, `go test ./...` |
Готовый пакет объявляет об этом полем `tests_gate=true` в
`metadata/package.env` — так же, как `dependency_security_gate` и
`hysteria_compat_gate`. Значение у поля ровно одно, потому что не бывает
пакета, собранного с пропущенными тестами: обе функции прогона выставляют свой
флаг **после** успешного завершения, а `write_metadata` отказывается писать
метаданные, если хотя бы один из них не выставлен. То есть поле остаётся
утверждением о результате, а не переключателем.
Здесь существовала переменная, описанная как «аварийное отключение тестов; для
release-сборок недопустимо». Недопустимость держалась исключительно на этой
фразе: ни metadata, ни финальная приёмка архива не проверяли, что тесты
запускались, поэтому сборка с ней доходила до конца и выдавала внешне
неотличимый production-tarball. Глушила она при этом не только тесты, но и
`tsc --noEmit` с `go vet` — то есть проверку типов и статический анализ того
самого кода, который уезжает в production. История — в `CHANGELOG.md`.
### Проверка, а не генерация
`profile.ts`, `package/config/hy2xs.env` и `packageManager` в двух `package.json`