From 48b9649d4595c970fcc4d79ad36e0774d2f9f12f Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Sun, 13 Sep 2026 11:46:19 +0300 Subject: [PATCH] =?UTF-8?q?ci:=20=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE?= =?UTF-8?q?=D0=BD=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?mutation-gate.yml=20=D1=81=20dev=20(#549)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: #549 User-Visible: no --- .github/workflows/mutation-gate.yml | 93 ++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 15 deletions(-) diff --git a/.github/workflows/mutation-gate.yml b/.github/workflows/mutation-gate.yml index 013c48a2..bf15a931 100644 --- a/.github/workflows/mutation-gate.yml +++ b/.github/workflows/mutation-gate.yml @@ -43,8 +43,30 @@ concurrency: cancel-in-progress: true jobs: + # #549: moving ref разрешается ровно один раз. Все шарды ниже получают один + # commit/tree, а не самостоятельно читают dev в разное время. + material: + name: "Зафиксировать неизменяемый материал" + runs-on: ubuntu-latest + outputs: + sha: ${{ steps.identity.outputs.sha }} + tree: ${{ steps.identity.outputs.tree }} + ref: ${{ steps.identity.outputs.ref }} + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || 'dev' }} + fetch-depth: 0 + - name: Зафиксировать commit и tree + id: identity + run: | + echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + echo "tree=$(git rev-parse 'HEAD^{tree}')" >> "$GITHUB_OUTPUT" + echo "ref=${{ github.event_name == 'workflow_dispatch' && inputs.ref || 'dev' }}" >> "$GITHUB_OUTPUT" + mutants: name: "Мутанты: каждый обязан красить тесты (шард ${{ matrix.shard }} из 4)" + needs: material runs-on: ubuntu-latest strategy: fail-fast: false @@ -56,7 +78,7 @@ jobs: steps: - uses: actions/checkout@v7 with: - ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || 'dev' }} + ref: ${{ needs.material.outputs.sha }} fetch-depth: 0 - uses: actions/setup-node@v7 @@ -101,18 +123,56 @@ jobs: # `tee` не съел код выхода раннера. - name: Каждый тест ловит свою поломку run: | - mkdir -p artifacts + mkdir -p artifacts/mutation-shard-${{ matrix.shard }} set -o pipefail - node scripts/mutation-gate.mjs --shard=${{ matrix.shard }}/4 2>&1 | tee artifacts/mutation-shard-${{ matrix.shard }}.log - - name: Сохранить лог шарда + node scripts/mutation-gate.mjs --shard=${{ matrix.shard }}/4 2>&1 | tee artifacts/mutation-shard-${{ matrix.shard }}/mutation-shard-${{ matrix.shard }}.log + - name: Записать identity шарда + if: always() + run: | + node scripts/mutation-gate-report.mjs \ + --write-evidence=artifacts/mutation-shard-${{ matrix.shard }}/evidence.json \ + --sha=${{ needs.material.outputs.sha }} \ + --tree=${{ needs.material.outputs.tree }} \ + --workflow-sha=${{ github.sha }} \ + --run-id=${{ github.run_id }} --run-attempt=${{ github.run_attempt }} \ + --shard=${{ matrix.shard }} --shards=4 + - name: Сохранить лог и identity шарда if: always() uses: actions/upload-artifact@v7 with: - name: mutation-shard-${{ matrix.shard }} - path: artifacts/mutation-shard-${{ matrix.shard }}.log + name: mutation-shard-${{ matrix.shard }}-attempt-${{ github.run_attempt }} + path: artifacts/mutation-shard-${{ matrix.shard }} if-no-files-found: warn retention-days: 30 + # Результат нельзя приписывать material, пока не доказаны все четыре шарда. + # always() нужен при красном мутанте: лог красного шарда всё равно evidence. + evidence: + name: "Доказать единый material всех шардов" + needs: [material, mutants] + if: always() + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.sha }} + - uses: actions/setup-node@v7 + with: + node-version: 22 + - name: Забрать evidence всех попыток + uses: actions/download-artifact@v7 + with: + pattern: mutation-shard-* + path: artifacts/mutation-logs + - name: Проверить полноту и identity + run: | + node scripts/mutation-gate-report.mjs --verify-only \ + --logs=artifacts/mutation-logs --shards=4 \ + --sha=${{ needs.material.outputs.sha }} \ + --tree=${{ needs.material.outputs.tree }} \ + --workflow-sha=${{ github.sha }} \ + --run-id=${{ github.run_id }} --run-attempt=${{ github.run_attempt }} + # Адресат у отказа (#472). Только по расписанию: ручной dispatch остаётся # для отладки самого гейта, его результат смотрят в прогоне — issue на # каждый такой отказ был бы шумом, который снова перестанут читать. @@ -121,8 +181,8 @@ jobs: # validate.yml, job с actions: read): перечислены все три. report: name: "Отказ расписания: issue и Telegram" - needs: mutants - if: always() && github.event_name == 'schedule' && needs.mutants.result != 'success' + needs: [material, mutants, evidence] + if: always() && github.event_name == 'schedule' && (needs.mutants.result != 'success' || needs.evidence.result != 'success') runs-on: ubuntu-latest permissions: contents: read @@ -131,11 +191,14 @@ jobs: steps: - uses: actions/checkout@v7 with: - ref: dev + # Код отчётчика берётся из того же workflow revision, а не из + # успевшего сдвинуться dev. Проверяемый material передаётся отдельно. + ref: ${{ github.sha }} - uses: actions/setup-node@v7 with: node-version: 22 - name: Забрать логи шардов + continue-on-error: true uses: actions/download-artifact@v7 with: pattern: mutation-shard-* @@ -145,14 +208,14 @@ jobs: env: RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | - # SHA — того дерева, которое чекаутили и гоняли (dev), а не - # github.sha: для расписания это вершина default-ветки main, и отчёт - # называл бы «dev @ » (ревью r1). Образец — process.yml. - SHA=$(git rev-parse HEAD) mkdir -p artifacts node scripts/mutation-gate-report.mjs \ - --logs=artifacts/mutation-logs --shards=4 \ - --run-url="$RUN_URL" --ref=dev --sha="$SHA" \ + --require-evidence --logs=artifacts/mutation-logs --shards=4 \ + --sha=${{ needs.material.outputs.sha }} \ + --tree=${{ needs.material.outputs.tree }} \ + --workflow-sha=${{ github.sha }} \ + --run-id=${{ github.run_id }} --run-attempt=${{ github.run_attempt }} \ + --run-url="$RUN_URL" --ref=${{ needs.material.outputs.ref }} \ --body-out=artifacts/mutation-report.md \ --telegram-out=artifacts/mutation-telegram.txt >> "$GITHUB_OUTPUT" # Одно issue, а не одно на неделю: открытое с тем же маркером в заголовке