ci: refuse a review round that cites an unreachable SHA

SPEC-REVIEW-403-r2 объявил материал раунда на `HEAD = 83005c3c`, и тот же SHA
независимо назвал автор ТЗ в комментарии issue. Разбор подтвердил находку и
уточнил её: коммит существовал, но к моменту публикации был осиротевшим.
Ветку перебазировали за пятнадцать минут ДО публикации документа — спец-коммит
переехал в 94502d3d с тем же сообщением и тем же содержимым (блоб ТЗ у обоих
56a92e12). Через раунд команда `git diff 83005c3c..HEAD` из §2.10 буквально не
работала, и r3 восстанавливал коммит по содержимому диффа руками.

Гейт судит только объявление материала в шапке документа, а не каждое
шестнадцатеричное слово: в прозе SHA упоминаются исторически, и обещания
воспроизводимости на них нет. Границы кандидата подобраны по корпусу — 7–40
знаков, хотя бы одна буква, не после `#`, не внутри длинного хеша; это
отсекает sha256, цвета и номера прогонов.

Достижимость считается от refs/remotes/origin, а не от локальных ссылок.
Разница не теоретическая: осиротевший 83005c3c до сих пор достижим в клоне
автора из необновлённой локальной ветки — локальная проверка сказала бы «всё в
порядке» ровно на той машине, где ошибку и совершили.

Шаг стоит ПОСЛЕ публикации и ДО перестановки метки. Артефакт ревью терялся
здесь трижды (#171, #220), и «вердикт без документа» дороже мёртвой ссылки:
документ сначала спасается, потом судится. Инвариант «метка не сменилась =
прогон упал» при этом сохраняется.

Проверено на настоящих документах: SPEC-REVIEW-403-r2 отказ, CODE-REVIEW-390-r1
проходит, документ без объявления материала не судится.

Issue: #413
User-Visible: no
This commit is contained in:
Matysh
2026-09-02 07:45:39 +03:00
parent afe5e98944
commit 206732e9f5
3 changed files with 216 additions and 1 deletions
+36
View File
@@ -745,6 +745,42 @@ jobs:
fi
echo "документ опубликован в $target: $doc"
# Материал раунда обязан быть достижим с origin (#413).
#
# SPEC-REVIEW-403-r2 объявил материал на `HEAD = 83005c3c`, и тот же SHA
# независимо назвал автор ТЗ в комментарии issue. Коммит существовал, но
# к моменту публикации был осиротевшим: ветку перебазировали за 15 минут
# ДО публикации документа, спец-коммит переехал в 94502d3d с тем же
# сообщением и тем же содержимым. Через раунд команда `git diff
# 83005c3c..HEAD` из §2.10 буквально не работала, и r3 восстанавливал
# реальный коммит по содержимому диффа руками.
#
# Проверка стоит ПОСЛЕ публикации намеренно. Артефакт ревью терялся здесь
# трижды (#171, #220), и «вердикт без документа» в этом репозитории
# дороже мёртвой ссылки: документ сначала спасается, потом судится. Шаг
# при этом идёт ДО «Переставить метку», поэтому инвариант «метка не
# сменилась = прогон упал» сохраняется.
#
# Достижимость считается от `refs/remotes/origin/*`, а не от локальных
# ссылок: осиротевший 83005c3c до сих пор лежит в клоне автора и
# достижим там из необновлённой локальной ветки. Читателю отчёта от этого
# пользы нет — он достанет только то, что есть на origin.
- name: "Материал раунда воспроизводим (#413)"
if: steps.rebase.outputs.conflict != 'true'
env:
NUM: ${{ github.event.issue.number }}
STAGE: ${{ needs.guard.outputs.stage }}
CYCLE: ${{ needs.guard.outputs.cycle }}
BRANCH: ${{ steps.branch.outputs.name }}
run: |
marker=CODE-REVIEW
if [ "$STAGE" = "spec" ]; then marker=SPEC-REVIEW; fi
doc="docs/reviews/${marker}-${NUM}-r${CYCLE}.md"
target="${BRANCH:-dev}"
git fetch -q origin "$target"
# Судится опубликованная версия, а не рабочая копия: именно её прочтёт
# следующий раунд.
git show "origin/$target:$doc" | node scripts/review-doc-guard.mjs --doc=-
- name: Решение по вердикту
id: decide
if: steps.rebase.outputs.conflict != 'true'