ci: фиксировать material ночных мутаций (#549)

Issue: #549
User-Visible: no
This commit is contained in:
Sergey Matyunin
2026-09-13 11:21:40 +03:00
parent 5b0cb687c9
commit 723ec6d362
6 changed files with 341 additions and 50 deletions
+78 -15
View File
@@ -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 @ <SHA main>» (ревью 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, а не одно на неделю: открытое с тем же маркером в заголовке