ci: гейты наблюдаемых свойств перед publish — image-smoke, migration-order на push, allowlist fail-closed, property-тесты #476
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Мотивация
Ретроспектива мержей 22.06–10.07 показала повторяющийся класс промаха: локальная логика верна, интеграционное свойство не проверено. Каждый инцидент-класс двух недель проскочил в прод именно потому, что проверялся юнит чистой функции, а не наблюдаемое свойство системы:
migration-orderработает только наpull_request, прямой push его обходит);publishсобирается, но ни разу не запускается — e2e гоняются из исходников, а не из артефакта, который тянет watchtower);@fastify/staticперезаписывалsetHeaders; юнит покрывал чистую функцию);null— «запретить все» невыразимо;Состав (один PR)
1. Boot-smoke Docker-образа в
publish(.github/workflows/develop.yml+scripts/ci/image-smoke.sh)В job
publishдобавить services (postgres pgvector + redis, как уe2e-mcp) и перед push: build сload: true(кэш-хит из параллельного build-job), запуск контейнера с--network host, проверки:/api/health→ 200 за 120s (стартовый мигратор + boot);POST /api/auth/setup→ 2xx;GET /→ 200 и содержит/assets/(клиентский dist внутри образа);/assets/*.jsиз HTML →curl -sI -H 'Accept-Encoding: br'→cache-control: …immutableиcontent-encoding: br.Fail любой проверки = образ не пушится. На failure —
docker logs.2. Гейт
migration-orderна push (.github/workflows/test.yml)База сравнения по событию: PR —
origin/${base_ref}(как сейчас); push —${{ github.event.before }};before == 0000…→ skip с notice; force-push (git cat-file -eпадает) → fail-closed с указанием прогнатьworkflow_dispatchпосле ручной проверки.3. Allowlist внешних MCP: fail-closed + сквозной тест enforcement
[]больше НЕ нормализуется вnull— «запретить все тулы» выразимо (apps/server/src/database/utils.ts);tool_allowlist→ deny-all (пустой набор) + error-лог вместоnull=«всё разрешено» (ai-mcp-server.repo.ts);["alpha"]→ только alpha;null→ оба; legacy double-encoded → только alpha; corrupt → ноль;[]→ ноль.4. Два property-теста
packages/mcp/test-e2e.mjs):exportPageMarkdown → updatePageMarkdown → exportPageMarkdown→ byte-equal;packages/mcp/test/unit/): mock-transport считает connect'ы; две мутации одной identity с разными свежими токенами → ровно один connect (закрепляет урок #435/#439).Критерий приёмки
Каждый инцидент-класс (#361, #353, #452, #172, #435) имеет именованную проверку, падающую до push образа
:develop.Вне scope (отдельно)
Hit-rate метрики/алерты для тихих деградаций под реальным трафиком (паттерн #403), post-deploy curl против env-дрейфа, «upgrade-path»-тест миграций на БД с историей.