mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 21:01:21 +00:00
A red nightly Validate was a signal "to the author of the latest dev commits" that nobody received: the red run was visible only in Actions, and nobody computed who the author was. - scripts/night-red.mjs: acts only on conclusion=failure of the red run (cancelled, timed_out and the rest are a summary line). The last green night is the newest of the last 50 Validate workflow_dispatch runs on dev that completed successfully, was created before the red run, sits on an ancestor of the red SHA and has a green ci-proof under the release policy, so a light green run (stale) never counts. Suspects are the Issue: trailers of `git rev-list --no-merges G..R` commits that touch a class A/B file and carry no Release: trailer: docs-only and beta-candidate commits do not count, a branch merged by a merge commit brings its second-parent commits, a commit without a trailer is a "no task" summary line, an empty range means a likely flake. One comment per task names both runs, up to ten of its commits and the failed jobs, says "suspect, not guilty" and ends with the marker hp:night-red green=<G> red=<R> commits=<all sha12>. No comment goes to a closed task or to a task whose marker with the same green already lists all its current range commits: one comment per series of red nights until the task commits again; a green night starts a new series. Failures become a ::warning:: and a summary line, exit code 0. - _nightly.yml: dispatch also outputs run_id; a new job night_red runs after it only when dispatch failed with a known run, continue-on-error, permissions actions: read and contents: read (the union with the other jobs is unchanged, thin files in main are untouched), checks out dev with full history without blobs, reads Actions with github.token and writes issues with HP_PROCESS_TOKEN. The header names the addressee. - PROCESS.md §10.4: the "Красная ночь" paragraph next to the nightly ship review. Tests run the scripted rules on real git in temporary repositories with real ci-proof fixtures, and the workflow step on real bash with a local Actions API server and a fake gh. Issue: #736 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
201 lines
12 KiB
YAML
201 lines
12 KiB
YAML
# Ночной полный прогон (#479).
|
||
#
|
||
# Тяжёлые job Validate — смоки, golden, performance_smoke — на обычном пуше не
|
||
# идут: они ни разу не ловили дефект в момент ревью и стоили ~6 минут
|
||
# критического пути на каждую итерацию. Полный набор идёт на кандидате беты
|
||
# (трейлер `Release:`), по кнопке и здесь — каждую ночь на голове `dev`.
|
||
#
|
||
# Почему не `schedule` прямо в validate.yml: расписание исполняется на ветке
|
||
# по умолчанию (`main`), а проверять надо `dev`. Один dispatch с `--ref dev`
|
||
# делает это без переписывания checkout во всех job. Reuse (#208) сохраняется:
|
||
# при неизменённом дереве ночной прогон обойдётся маркерами.
|
||
#
|
||
# Красный ночной прогон — сигнал автору последних коммитов на dev, не гейт:
|
||
# гейт беты по-прежнему требует зелёный Validate на точном SHA кандидата, и
|
||
# там полный набор идёт заново. Адресат сигнала (#736) — задачи, чьи коммиты
|
||
# классов A/B вошли в dev после последней зелёной ночи: job `night_red` пишет
|
||
# каждой один комментарий (`scripts/night-red.mjs`, PROCESS.md §10.4).
|
||
#
|
||
# Сигнал обязан быть настоящим (#492 §7): до этой задачи job завершалась
|
||
# успехом в момент постановки Validate в очередь, и красный полный прогон не
|
||
# делал ночной workflow красным. Теперь job находит запущенный прогон и ждёт
|
||
# его: успешный dispatch — не успешная проверка.
|
||
#
|
||
# #727: после Validate при любом его исходе ночь запускает пакетное ревью ship
|
||
# (`ship-review.yml -f tag=nightly`) на SHA прогона Validate: оно читает код, а
|
||
# не прогон. Ship-задача узнаёт о дефектах в ночь после слияния, а бета читает
|
||
# только то, что ночь не прочла (PROCESS.md §11.7). Цвет ночи — цвет Validate.
|
||
name: "Ночной полный прогон dev · тело (#623)"
|
||
|
||
on:
|
||
# #623: тело вызывается тонким файлом `nightly.yml` из ветки по умолчанию
|
||
# по ссылке `@dev`; триггеры, run-name и concurrency живут там.
|
||
workflow_call:
|
||
|
||
permissions:
|
||
actions: write
|
||
contents: read
|
||
|
||
jobs:
|
||
dispatch:
|
||
name: "Запустить Validate на dev с полным набором и дождаться результата"
|
||
runs-on: ubuntu-24.04
|
||
timeout-minutes: 90
|
||
outputs:
|
||
# #727: SHA прогона Validate — отдельным шагом до ожидания, красный Validate его не теряет.
|
||
head_sha: ${{ steps.validate.outputs.head_sha }}
|
||
# #736: прогон Validate для разбора красной ночи — тем же шагом, до ожидания.
|
||
run_id: ${{ steps.validate.outputs.run_id }}
|
||
steps:
|
||
# #658: при плане 02:30 UTC ночь фактически стартовала в 07:42–08:05 и
|
||
# кончалась в рабочее время владельца. Сдвиг старта больше часа — видимое
|
||
# предупреждение, а не молча съеденная ночь. `github.event.schedule` —
|
||
# строка cron вызывающего `nightly.yml`; NOW_EPOCH подставляет только тест.
|
||
- name: "Сдвиг старта ночи против расписания"
|
||
if: github.event_name == 'schedule'
|
||
env:
|
||
SCHEDULE: ${{ github.event.schedule }}
|
||
run: |
|
||
set -euo pipefail
|
||
read -r minute hour _ <<< "$SCHEDULE"
|
||
now=${NOW_EPOCH:-$(date -u +%s)}
|
||
planned=$(date -u -d "@$now" +%F)
|
||
planned=$(date -u -d "$planned $hour:$minute" +%s)
|
||
[ "$planned" -le "$now" ] || planned=$((planned - 86400))
|
||
lag=$(( (now - planned) / 60 ))
|
||
echo "- Старт по расписанию \`$SCHEDULE\` (UTC): сдвиг $lag мин" >> "$GITHUB_STEP_SUMMARY"
|
||
if [ "$lag" -gt 60 ]; then
|
||
echo "::warning::ночной прогон стартовал на $lag мин позже расписания ($SCHEDULE UTC) — очередь расписаний GitHub (#658)"
|
||
fi
|
||
- name: "Запустить Validate и найти его прогон"
|
||
id: validate
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
run: |
|
||
set -euo pipefail
|
||
since=$(date -u +%FT%TZ)
|
||
gh workflow run validate.yml --repo "$REPO" --ref dev -f full=true
|
||
echo "Validate(dev, full=true) поставлен в очередь: $since"
|
||
# Найти именно этот прогон: workflow_dispatch на dev, созданный не
|
||
# раньше момента запуска. До трёх минут на появление в списке.
|
||
run_id=""
|
||
for _ in $(seq 1 18); do
|
||
sleep 10
|
||
run_id=$(gh run list --repo "$REPO" --workflow validate.yml --branch dev \
|
||
--event workflow_dispatch --json databaseId,createdAt --limit 5 \
|
||
--jq "[.[] | select(.createdAt >= \"$since\")] | sort_by(.createdAt) | last | .databaseId // empty")
|
||
[ -n "$run_id" ] && break
|
||
done
|
||
if [ -z "$run_id" ]; then
|
||
echo "::error::прогон Validate не появился за 3 минуты — dispatch не равен проверке"
|
||
exit 1
|
||
fi
|
||
url="${{ github.server_url }}/$REPO/actions/runs/$run_id"
|
||
echo "дочерний прогон: $url"
|
||
echo "- Validate(dev, full=true): $url" >> "$GITHUB_STEP_SUMMARY"
|
||
head_sha=$(gh run view "$run_id" --repo "$REPO" --json headSha --jq .headSha)
|
||
echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
|
||
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
|
||
- name: "Дождаться Validate"
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
RUN_ID: ${{ steps.validate.outputs.run_id }}
|
||
run: |
|
||
set -euo pipefail
|
||
run_id="$RUN_ID"
|
||
# Ждём завершения; красный дочерний прогон — красный ночной.
|
||
gh run watch "$run_id" --repo "$REPO" --exit-status --interval 30
|
||
|
||
# #727 К6: ночное пакетное ревью ship на SHA прогона Validate — при любом его
|
||
# исходе. Dispatch тонкого `ship-review.yml` (`github.token` порождает прогон,
|
||
# как у Validate; входы и права тонких файлов уже в main). Job ждёт только
|
||
# появления прогона, не его конца; не появился — предупреждение, не красная
|
||
# ночь: цвет ночи — цвет Validate.
|
||
ship_review:
|
||
name: "Запустить ночное пакетное ревью ship на SHA прогона Validate"
|
||
needs: dispatch
|
||
if: always()
|
||
continue-on-error: true
|
||
runs-on: ubuntu-24.04
|
||
timeout-minutes: 10
|
||
steps:
|
||
- name: "Запустить ship-ревью и дождаться появления прогона"
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
CANDIDATE: ${{ needs.dispatch.outputs.head_sha }}
|
||
run: |
|
||
set -euo pipefail
|
||
if [ -z "$CANDIDATE" ]; then
|
||
echo "::warning::нет SHA прогона Validate — ночное ship-ревью не запущено"
|
||
echo "- Ship-ревью ночью: не запущено — нет SHA прогона Validate" >> "$GITHUB_STEP_SUMMARY"
|
||
exit 0
|
||
fi
|
||
since=$(date -u +%FT%TZ)
|
||
if ! gh workflow run ship-review.yml --ref dev -f tag=nightly -f candidate="$CANDIDATE" --repo "$REPO"; then
|
||
echo "::warning::ship-ревью ночью не запущено: dispatch отклонён — ночь не красится"
|
||
echo "- Ship-ревью ночью: dispatch отклонён" >> "$GITHUB_STEP_SUMMARY"
|
||
exit 0
|
||
fi
|
||
run_id=""
|
||
for _ in $(seq 1 18); do
|
||
sleep 10
|
||
run_id=$(gh run list --repo "$REPO" --workflow ship-review.yml --branch dev \
|
||
--event workflow_dispatch --json databaseId,createdAt --limit 5 \
|
||
--jq "[.[] | select(.createdAt >= \"$since\")] | sort_by(.createdAt) | last | .databaseId // empty")
|
||
[ -n "$run_id" ] && break
|
||
done
|
||
if [ -z "$run_id" ]; then
|
||
echo "::warning::прогон ship-ревью не появился за 3 минуты — ночь не красится, её цвет — цвет Validate"
|
||
echo "- Ship-ревью ночью (\`$CANDIDATE\`): прогон не появился за 3 минуты" >> "$GITHUB_STEP_SUMMARY"
|
||
exit 0
|
||
fi
|
||
echo "- Ship-ревью ночью (\`$CANDIDATE\`): ${{ github.server_url }}/$REPO/actions/runs/$run_id" >> "$GITHUB_STEP_SUMMARY"
|
||
|
||
# #736: красная ночь — один комментарий в каждую задачу, чьи коммиты классов
|
||
# A/B вошли в dev после последней зелёной ночи (`scripts/night-red.mjs`,
|
||
# PROCESS.md §10.4). Только при красном dispatch с известным прогоном;
|
||
# исход прогона (failure, а не cancelled/timed_out) скрипт читает сам.
|
||
# Actions — `github.token`, issue — `HP_PROCESS_TOKEN`: `issues: write` у
|
||
# ночи нет, и потолок тонкого `nightly.yml` в main не меняется. Сбой —
|
||
# предупреждение, не красная ночь сверх Validate.
|
||
night_red:
|
||
name: "Красная ночь: комментарий в задачи, слитые после последней зелёной"
|
||
needs: dispatch
|
||
if: always() && needs.dispatch.result == 'failure' && needs.dispatch.outputs.run_id != ''
|
||
continue-on-error: true
|
||
runs-on: ubuntu-24.04
|
||
timeout-minutes: 15
|
||
permissions:
|
||
actions: read
|
||
contents: read
|
||
steps:
|
||
# Нужны коммиты и деревья, не блобы; скрипт и его импорты — без зависимостей, npm ci не нужен.
|
||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||
with:
|
||
ref: dev
|
||
fetch-depth: 0
|
||
filter: blob:none
|
||
persist-credentials: false
|
||
|
||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||
with:
|
||
node-version: 22
|
||
|
||
- name: "Комментарий в задачи диапазона от последней зелёной ночи до красной"
|
||
env:
|
||
ACTIONS_TOKEN: ${{ github.token }}
|
||
GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }}
|
||
REPO: ${{ github.repository }}
|
||
RED_RUN: ${{ needs.dispatch.outputs.run_id }}
|
||
run: |
|
||
set -euo pipefail
|
||
# Сбои API, токена и git скрипт сам сводит к предупреждению; упавший
|
||
# скрипт — тоже предупреждение: цвет ночи — цвет Validate.
|
||
if ! node scripts/night-red.mjs --repo="$REPO" --red-run="$RED_RUN"; then
|
||
echo "::warning::красная ночь (#736): скрипт упал — комментарии не написаны; цвет ночи — цвет Validate"
|
||
echo "- Красная ночь (#736): скрипт упал — комментарии не написаны" >> "$GITHUB_STEP_SUMMARY"
|
||
fi
|