From e96389778f3fe9aafbc296fa704a73d3cab172dc Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 9 Sep 2026 18:56:03 +0300 Subject: [PATCH] =?UTF-8?q?ci:=20mirror=20process.yml=20and=20validate.yml?= =?UTF-8?q?=20from=20dev=20(#510=20=E2=80=94=20mutants=20on=20request,=20r?= =?UTF-8?q?eview=20gate)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The review pipeline runs from the default branch: the Validate-with- mutants gate before Review and the `mutants` dispatch input must exist here as well. Files are byte-identical to dev@33cb131b. Issue: #510 User-Visible: no --- .github/workflows/process.yml | 80 +++++++++++++++++++++++++++++----- .github/workflows/validate.yml | 24 +++++++--- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/.github/workflows/process.yml b/.github/workflows/process.yml index c6f3fed0..1a8e7927 100644 --- a/.github/workflows/process.yml +++ b/.github/workflows/process.yml @@ -474,6 +474,64 @@ jobs: --add-label S6-in-progress --remove-label S7-code-review echo "S7-code-review -> S6-in-progress (ревью не запускалось)" + # Мутанты по диффу бегут только по запросу (#510): до ревью конвейер + # запускает Validate с мутантами на материале и ждёт его. Красный или + # пропавший прогон возвращает задачу автору без ревью — цикл не + # тратится на код, который CI уже отверг (08.09: #437 дважды ушёл в S6 + # после запущенного 15-минутного ревью). Этап spec кода не несёт и + # гейт не проходит; повторное применение вердикта (#499) — тоже: там + # слияние само дожидается Validate на кандидате. + - name: Validate с мутантами на материале + id: gate + if: steps.rebase.outputs.conflict != 'true' + env: + GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }} + STAGE: ${{ needs.guard.outputs.stage }} + REUSE: ${{ steps.reuse.outputs.reuse }} + BRANCH: ${{ steps.branch.outputs.name }} + SHA: ${{ steps.material.outputs.sha }} + run: | + if [ "$STAGE" != "code" ] || [ "$REUSE" = "true" ] || [ -z "$BRANCH" ]; then + echo "гейт не применяется: этап $STAGE, reuse=${REUSE:-false}, ветка ${BRANCH:-dev}" + { echo 'proceed=true'; echo 'result=skipped'; } >> "$GITHUB_OUTPUT" + exit 0 + fi + if node scripts/validate-gate.mjs --repo="${{ github.repository }}" --ref="$BRANCH" --sha="$SHA"; then + echo 'proceed=true' >> "$GITHUB_OUTPUT" + else + echo 'proceed=false' >> "$GITHUB_OUTPUT" + fi + + - name: Validate красный — вернуть автору без ревью + if: steps.rebase.outputs.conflict != 'true' && steps.gate.outputs.proceed != 'true' + env: + GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }} + NUM: ${{ github.event.issue.number }} + BRANCH: ${{ steps.branch.outputs.name }} + SHA: ${{ steps.material.outputs.sha }} + RESULT: ${{ steps.gate.outputs.result }} + NOTE: ${{ steps.gate.outputs.note }} + URL: ${{ steps.gate.outputs.url }} + run: | + short=$(git rev-parse --short "$SHA") + cat > /tmp/gate.md < S6-in-progress (Validate с мутантами: $RESULT)" + # Ревьюер перегонял tsc, юниты и сборку заново в каждом раунде, хотя # Validate на том же SHA уже зелёный (#343). Это не тщательность: бюджет # ревью тратится на повторение CI вместо чтения кода. @@ -483,7 +541,7 @@ jobs: # ребейза SHA другой, прогона для него нет — и ревьюер честно гоняет сам. - name: Зелёные гейты на этом SHA id: validated - if: steps.rebase.outputs.conflict != 'true' + if: steps.gate.outputs.proceed == 'true' env: GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }} run: | @@ -512,21 +570,21 @@ jobs: # Зависимости ставятся ПОСЛЕ переключения на ветку задачи: lockfile мог # измениться именно в ней, и установка по копии из dev дала бы не то дерево. - name: Установить зависимости - if: steps.rebase.outputs.conflict != 'true' && steps.reuse.outputs.reuse != 'true' + if: steps.gate.outputs.proceed == 'true' && steps.reuse.outputs.reuse != 'true' run: npm ci # Браузер нужен не всякому ревью (см. правило выбора гейтов в промпте), # но когда нужен — качать его заново дороже, чем держать в кэше. - name: Кэш браузеров Playwright id: pw - if: steps.rebase.outputs.conflict != 'true' && steps.reuse.outputs.reuse != 'true' + if: steps.gate.outputs.proceed == 'true' && steps.reuse.outputs.reuse != 'true' uses: actions/cache@v6 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} - name: Установить Chromium - if: steps.rebase.outputs.conflict != 'true' && steps.reuse.outputs.reuse != 'true' && steps.pw.outputs.cache-hit != 'true' + if: steps.gate.outputs.proceed == 'true' && steps.reuse.outputs.reuse != 'true' && steps.pw.outputs.cache-hit != 'true' # Без --with-deps: системные библиотеки Chromium предустановлены в # образе ubuntu-latest, а apt при промахе кэша съедал минуты из бюджета # ревью и подолгу перебирал недоступное azure-зеркало (#175). Если @@ -542,7 +600,7 @@ jobs: # скачан в _actions к началу job), контрольную сумму — из манифеста релиза. - name: Установить Claude Code детерминированно id: claude_bin - if: steps.rebase.outputs.conflict != 'true' && steps.reuse.outputs.reuse != 'true' + if: steps.gate.outputs.proceed == 'true' && steps.reuse.outputs.reuse != 'true' run: | src=$(ls "$RUNNER_WORKSPACE"/../_actions/anthropics/claude-code-*/v1/src/entrypoints/run.ts 2>/dev/null | head -1) ver=$(grep -oE 'claudeCodeVersion = "[0-9]+\.[0-9]+\.[0-9]+"' "$src" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true) @@ -559,7 +617,7 @@ jobs: - name: Review id: review - if: steps.rebase.outputs.conflict != 'true' && steps.reuse.outputs.reuse != 'true' + if: steps.gate.outputs.proceed == 'true' && steps.reuse.outputs.reuse != 'true' uses: anthropics/claude-code-action@v1 env: # Вне рабочей копии: восстановление дерева ревьюером не должно @@ -783,7 +841,7 @@ jobs: # Ревьюер пишет только в docs/reviews/. Что именно попадёт в коммит, # решает этот шаг, а не модель: всё остальное откатывается. - name: Опубликовать документ ревью - if: steps.rebase.outputs.conflict != 'true' && steps.reuse.outputs.reuse != 'true' + if: steps.gate.outputs.proceed == 'true' && steps.reuse.outputs.reuse != 'true' env: TOKEN: ${{ secrets.HP_PROCESS_TOKEN }} BRANCH: ${{ steps.branch.outputs.name }} @@ -945,7 +1003,7 @@ jobs: # достижим там из необновлённой локальной ветки. Читателю отчёта от этого # пользы нет — он достанет только то, что есть на origin. - name: "Материал раунда воспроизводим (#413)" - if: steps.rebase.outputs.conflict != 'true' && steps.reuse.outputs.reuse != 'true' + if: steps.gate.outputs.proceed == 'true' && steps.reuse.outputs.reuse != 'true' env: NUM: ${{ github.event.issue.number }} STAGE: ${{ needs.guard.outputs.stage }} @@ -962,7 +1020,7 @@ jobs: git show "origin/$target:$doc" | node scripts/review-doc-guard.mjs --doc=- - name: Решение по вердикту id: decide - if: steps.rebase.outputs.conflict != 'true' + if: steps.gate.outputs.proceed == 'true' env: OUT: ${{ steps.review.outputs.structured_output }} STAGE: ${{ needs.guard.outputs.stage }} @@ -1017,7 +1075,7 @@ jobs: # dev действительно ушёл и вердикт зелёный, то есть слияние вот-вот # случится (#364). - name: dev ушёл вперёд, пока шло ревью - if: steps.rebase.outputs.conflict != 'true' && needs.guard.outputs.stage == 'code' + if: steps.gate.outputs.proceed == 'true' && needs.guard.outputs.stage == 'code' env: GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }} NUM: ${{ github.event.issue.number }} @@ -1063,7 +1121,7 @@ jobs: --issue="$NUM" --repo="${{ github.repository }}" - name: Переставить метку - if: steps.rebase.outputs.conflict != 'true' + if: steps.gate.outputs.proceed == 'true' env: # Именно PAT: с GITHUB_TOKEN следующий шаг конвейера не запустится. GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 07dbe1c2..8896cd3f 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -18,9 +18,15 @@ on: workflow_dispatch: inputs: full: - description: 'Полный набор: смоки, golden, performance_smoke' + description: 'Полный набор: смоки, golden, performance_smoke, мутанты по диффу' type: boolean default: true + # Мутанты по диффу бегут по запросу (#510): ревью-конвейер и слияние + # кандидата запускают `-f full=false -f mutants=true` на материале. + mutants: + description: 'Мутанты по диффу на этом SHA (кандидат ревью)' + type: boolean + default: false # A new push supersedes an unfinished validation for the same branch or PR. # Exact-SHA release gates never depend on an obsolete commit. @@ -218,10 +224,10 @@ jobs: unknown_inputs: ${{ steps.classify.outputs.unknown_inputs }} # Тяжёлые job только на кандидате/по кнопке/на PR (#479), см. шаг heavy. heavy: ${{ steps.heavy.outputs.heavy }} + mutants_requested: ${{ steps.heavy.outputs.mutants_requested }} # #510 base: ${{ steps.base.outputs.base }} - # Разные вещи под разными именами намеренно: `base` — до какого коммита - # классифицировать файлы ветки (#387), `range_base` — от какого судить - # диапазон на dev (#388): общее имя — чужая база у потребителя. + # `base` — до какого коммита классифицировать файлы ветки (#387), + # `range_base` — от какого судить диапазон на dev (#388). range_base: ${{ steps.base.outputs.range_base }} steps: # `git diff --name-only` содержимого файлов не читает вовсе, поэтому @@ -241,6 +247,7 @@ jobs: EVENT_NAME: ${{ github.event_name }} HEAD_MESSAGE: ${{ github.event.head_commit.message }} FULL_INPUT: ${{ inputs.full }} + MUTANTS_INPUT: ${{ inputs.mutants }} run: node scripts/classify-changes.mjs --heavy | tee -a "$GITHUB_OUTPUT" - id: base if: github.event_name != 'pull_request' @@ -476,10 +483,17 @@ jobs: # что и остальные гейты ветки (#387/#388). Релизный диапазон всё же может # задеть сотню свидетелей, поэтому #480 делит тот же набор на три # детерминированных непересекающихся шарда вместо ослабления проверки. + # С #510 job бежит не на каждом пуше, а на кандидате: ревью-конвейер и + # слияние кандидата запускают Validate по кнопке с `mutants=true` на + # материале, ночной прогон/PR/кандидат беты берут её сами. За 08–09.09 на + # промежуточных пушах она стоила 48 из 56 часов job-минут и в основном + # отменялась следующим пушем. Когда мутанты запрошены, job бежит даже при + # диффе без входов (отбор пустой, минута на checkout): гейт ревью читает + # её исход по job, и skipped был бы неотличим от «не запрашивали». changed_mutants: name: "Мутанты по диффу (${{ matrix.shard }}/3): затронутые свидетели краснеют" needs: changes - if: needs.changes.outputs.frontend == 'true' || needs.changes.outputs.backend == 'true' || needs.changes.outputs.mutants == 'true' + if: needs.changes.outputs.mutants_requested == 'true' strategy: fail-fast: false matrix: