Files
houseplan-card/.github/workflows/_nightly.yml
T
Claudeandclaude[bot] e58d7d06f8 feat(process): nightly ship batch review, reused by the beta gate by patch set (#727)
Ship tasks merge without a model review and their code was first read by
the batch review right before a beta: one session over the whole range,
ten to forty-five minutes on the release path, days after the merge. The
gate also knew a single document (SHIP-REVIEW-<tag>.md) and covered tasks
by number only, so a commit that landed after the review under the same
trailer still counted as read.

- scripts/ship-review.mjs: the patch set of a task is the sorted
  `git patch-id --stable` of its range commits, without `Release:`
  commits (the beta candidate carries every Issue: of the line) and
  commits touching only docs/reviews/**; the diff options are explicit
  so a local git config cannot change it. shipCoverage rates every ship
  task from the documents of the same base (candidate and origin/dev,
  latest publication wins): clean, high, stale, none; documents without
  `patches` cover by number. `tag=nightly` is a reserved mode: the
  candidate is required, the document is
  SHIP-REVIEW-<base>-dev-<sha12>.md, only none/stale tasks are read and
  nothing runs when nothing is uncovered. The beta reads the same delta
  (force=true reads everything, as before); the brief names what the
  night already read. The gate refuses none/stale with the command and
  keeps the High refusal with force=true; all clean passes without a tag
  document. The machine block gains `mode` and `patches` at its end.
  comment-high writes one line per task of a nightly document with High,
  once per document (hp:ship-review-high).
- _ship-review.yml: prepare refuses nightly without a candidate before
  defaulting to the dev tip, computes the document from base and SHA and
  no longer reads a prepare failure behind `| tee` as "no ship tasks";
  publish takes mode and patches from prepare, never from the model
  result; a new step comments High at night with HP_PROCESS_TOKEN.
- _nightly.yml: the Validate run SHA is a separate step output before
  the wait; a new job dispatches ship-review.yml -f tag=nightly on it
  whatever Validate's outcome, waits only for the run to appear and
  never colours the night. Thin files in main are unchanged.
- reviews-index/reviews-archive: the nightly name is a ship document
  with nightly: true; a beta base archives with its line, a stable base
  with the nearest archived line newer than the base, or stays.
- PROCESS.md §11.7, §10.4 and REVIEWER.md describe the nightly mode,
  patch set, coverage and beta delta; the digest test pins the key rule.

Tests run the prepare, publish and comment steps and the nightly steps
on real bash with real git in temporary repositories; only push
transport and gh are faked.

Issue: #727
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 03:25:24 +00:00

152 lines
9.1 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ночной полный прогон (#479).
#
# Тяжёлые job Validate — смоки, golden, performance_smoke — на обычном пуше не
# идут: они ни разу не ловили дефект в момент ревью и стоили ~6 минут
# критического пути на каждую итерацию. Полный набор идёт на кандидате беты
# (трейлер `Release:`), по кнопке и здесь — каждую ночь на голове `dev`.
#
# Почему не `schedule` прямо в validate.yml: расписание исполняется на ветке
# по умолчанию (`main`), а проверять надо `dev`. Один dispatch с `--ref dev`
# делает это без переписывания checkout во всех job. Reuse (#208) сохраняется:
# при неизменённом дереве ночной прогон обойдётся маркерами.
#
# Красный ночной прогон — сигнал автору последних коммитов на dev, не гейт:
# гейт беты по-прежнему требует зелёный Validate на точном SHA кандидата, и
# там полный набор идёт заново.
#
# Сигнал обязан быть настоящим (#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 }}
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"