15 KiB
CODE-REVIEW-700-r1
Issue: #700 · Предполёт не красит ветку задачи чужими причинами: зеркало workflow и внешние ссылки
Этап: code · Заход: r1 · Трек: show · Материал: 89d57e054cdf940d3ead83ec509075164118ee3b (один коммит поверх origin/dev @ c716bb0f63104f5afb9d660bc058e095256d433d)
Скоуп
Инфраструктурная задача трека show: предполёт validate.yml не должен красить
ветку задачи двумя внешними по отношению к её диффу причинами —
- расхождением зеркала тонких вызывающих workflow (
mainvsdev); - упавшим внешним сайтом при проверке ссылок в документации (
check-docs.mjs).
Оба пункта на ветках issue/* становятся предупреждением в сводке, не красят
вердикт; на push в dev (там, где кандидат беты и релиз фактически и
проверяются — см. ниже) поведение остаётся прежним, красным. Плюс — на push в
dev расхождение зеркала автоматически заводит одно owner-issue
[workflow-sync] (или комментирует уже открытое), по образцу ночного
мутационного гейта (#472).
Файлы: .github/workflows/validate.yml, scripts/check-docs.mjs,
scripts/mutation-registry.mjs, PROCESS.md §10.4,
test/validate-workflow.test.mjs, test/docs-freshness.test.mjs,
test/classify-changes.test.mjs. Файлов класса A/D нет — согласен с автором.
Как проверялось
Дешёвые гейты (typecheck/test/build/bundle-policy) подтверждены зелёным
Validate на этом же SHA (workflow_dispatch, issue/700-preflight-warnings,
run 36482200722, conclusion: success) — не перегонялись повторно.
Дополнительно к зелёному Validate прогнал сам:
| Гейт | Результат |
|---|---|
node --test test/validate-workflow.test.mjs |
27/27 зелёных |
node --test test/classify-changes.test.mjs |
21/21 зелёных |
node --test test/docs-freshness.test.mjs |
3/3 зелёных |
node --test test/mutation-gate.test.mjs (структура реестра + исполнение guard-команд обоих новых мутантов среди прочих) |
69/69 зелёных |
node scripts/smoke-select.mjs --base origin/dev --head HEAD |
«Исполняемого frontend-диффа нет (src/**/*.ts не тронут)» — браузерные смоки нечего выбирать, не пропуск |
Мутация вручную: advise → check в .github/workflows/validate.yml, тест #700: на ветке задачи… |
падает (1 fail), восстановлено |
Мутация вручную: externalErrors = EXTERNAL_WARN ? warnings : errors → = errors в scripts/check-docs.mjs, тест #700: check-docs… и docs-freshness.test.mjs |
оба падают, восстановлено |
Также прочитал живой лог run 36482200722, job «Предполёт»: на этом прогоне
REF=refs/heads/issue/700-preflight-warnings реально прошёл веткой
--external=warn (скриншоты документации: режим warn →
Documentation checks passed (7 files, 12 external links)), а шаг «Вердикт
предполётных проверок» реально выполнил task_branch=true и напечатал
ok тонкие вызывающие workflow в main и dev через advise (все шесть
файлов зеркала совпали, поэтому и advise, и check в этом прогоне дали бы
одинаковый «ok» — само разветвление логики живьём не проверено на красном
входе, это закрыто юнит-тестами и ручной мутацией выше). Шаг «Расхождение
зеркала на dev — issue владельцу» на этом прогоне не выполнялся (условие
push && refs/heads/dev не совпало) — ожидаемо, не тестировался живьём,
только юнит-тестом на структуру шага.
Находки
Medium (в скоупе) — gh issue list fail-open вместо fail-safe в новом шаге создания issue
.github/workflows/validate.yml, шаг «Расхождение зеркала на dev — issue
владельцу»:
existing=$(gh issue list --repo "$REPO" --state open --search "\"$marker\" in:title" \
--json number,title --jq '[.[] | select(.title | startswith("[workflow-sync]"))][0].number // empty' || true)
if [ -n "$existing" ]; then
gh issue comment "$existing" ...
exit 0
fi
gh issue create --repo "$REPO" --title "$marker ..." ...
|| true глушит именно чтение списка issue: если gh issue list падает
(сетевой сбой, рейт-лимит), existing остаётся пустой строкой, и скрипт идёт
в ветку gh issue create — заводит новое [workflow-sync] issue, даже если
открытое уже есть. Это расходится и с заявленным контрактом задачи («если оно
уже открыто, в него добавляется комментарий»/PROCESS.md §10.4 «одно issue»),
и с намерением автора («Сбой API — предупреждение: шаг сообщает, а не
судит») — фактическое поведение при сбое чтения не «сообщает», а действует
(плодит дубликат).
Сравнение с прецедентом, на который автор явно ссылается (#472,
.github/workflows/_mutation-gate.yml:327): та же операция там без
|| true — gh issue list без защиты, при её падении шаг GitHub Actions
(shell по умолчанию bash -eo pipefail) прерывается целиком, дубликат не
заводится. Новый шаг в #700 сознательно ослабил именно эту защиту.
Чем краснеет: мутация || true → убрать (или заменить на
|| { echo "::warning::не удалось проверить открытые issue — issue не заводится"; exit 0; }) — тест на этот путь в диффе отсутствует, поэтому
проверено чтением, не исполнением; воспроизвести можно, подменив gh в PATH
шага на скрипт, который для issue list возвращает ненулевой код, — тогда
следующий gh issue create реально уйдёт, хотя нужного issue список не нашёл
не потому, что его нет, а потому что сам не смог проверить.
Почему Medium, не High: окно узкое (нужен сбой именно у gh issue list,
а не у последующих команд), шаг не участвует в вердикте (check/advise его
исход не читают), последствие — лишнее owner-facing issue, а не красный
Validate и не пропущенное реальное расхождение. Но это дефект поведения,
прямо противоречащий описанному в задаче и в PROCESS.md контракту «одно
issue», поэтому не Low: правка тривиальна (одна строка) и находится в этом
же диффе, значит чинится в этом же issue (§3 п.8, §2.7), не заводится
отдельно.
Что проверено и корректно
- Блокирующий периметр не сузился там, где должен остаться красным.
«Кандидат беты» в этом репозитории — не ветка
issue/*, а коммит с трейлеромRelease:(PROCESS.md, класс D) либоworkflow_dispatchтега вrelease.yml, оба целятся вdev/точный SHA, не вissue/*-ветку — значитtask_branch=false, иcheck(красный) для workflow_sync и--external(безwarn) для внешних ссылок продолжают действовать на этих путях. Отдельно проверил: «кандидат слияния» (merge-candidate.mjs) действительно публикуется push’ем в саму ветку задачи (PROCESS.md §… «публикация кандидата в ветку задачи») и там расхождение зеркала будет предупреждением — но финальный гейт слияния всё равно упирается в push вdevс--force-with-lease, а этот push уже неissue/*, и там проверка красная по-прежнему. Сквозной дыры в периметре нет. validate.ymlне входит в список из шести тонких зеркалируемых файлов (process.yml,mutation-gate.yml,process-resume.yml,nightly.yml,process-reconcile.yml,process-metrics.yml,test/default-branch-workflows.test.mjs) — этому диффу зеркалирование вmainне требуется, сам он не самозеркалируемый файл.check-docs.mjs:EXTERNAL_WARNкорректно разводитexternalErrorsнаwarnings/errors, не создавая двойного пуша в оба стока; выход поerrors.lengthне тронут — предупреждения никогда не красятexitCode.transientHosts(уже существовавший путь всегда-предупреждение) не задет.- Трейлеры коммита:
Issue: #700,User-Visible: no— корректно, поведение для конечного пользователя карточки не меняется (это только CI/процесс), изменений вdocs/USER-GUIDE.ru.mdне требуется и не сделано. - Одно число, один источник (§8): числовых значений, видимых пользователю карточки, дифф не вводит и не дублирует — весь дифф про CI-вердикт.
- Тесты умеют падать: обе новые мутации в
scripts/mutation-registry.mjs(task-branch-workflow-sync-red-again,external-link-warn-mode-ignored) проверены вручную — соответствующие guard-тесты красные на мутированном коде, зелёные на исходном (см. таблицу гейтов выше). PROCESS.md§10.4 дополнен точно тем, что реализовано: явно назван список из шести файлов, явно — чтоperformance.ymlв него не входит (не тронуто), явно — что на ветке задачи это предупреждение, а блокируетdev/кандидат/релиз.
Чего не проверял
actionlint validate.yml— заявлен автором, локально бинарник недоступен (command not found); не перепроверял. Косвенное подтверждение — сам YAML реально исполнился на CI (run36482200722) без ошибки парсинга workflow.mutation-gate --check(полный) и весьnpm run gate:small— не перегонял: дешёвые гейты уже зелёные на этом SHA (см. ссылку в задаче ревью), а трекshowне запрашивает мутанты по диффу (#696) — их отсутствие в этом отчёте не находка.- Ветвь
pull_request/иных не-issue/*, не-devрефов (например, гипотетический feature-branch без префиксаissue/) — в этом репозитории такие пуши процессом не предусмотрены (pre-pushгейт + прямые пуши, PR-флоу не используется по факту виденных коммитов), поэтому не разбирал отдельно; если он всё же встретится,task_branch=falseи поведение останется прежним (строгим) — то есть ничего не ослабляется по умолчанию. - Живое воспроизведение красного
workflow_sync(реальное расхождениеmain/dev) и живое исполнение шага создания issue — не запускал (это разрушительное действие: реально завело бы issue в репозитории); логика закрыта юнит-тестами и ручными мутациями, шаг создания issue — только чтением кода плюс находкой выше. golden:verify,pytest tests_backend, инварианты модели, performance — не применимо, дифф не трогаетsrc/**, рендер, геометрию или Python.
Вердикт
Единственная находка — Medium, в скоупе задачи, с указанной строкой и минимальной правкой. По правилам трека без High это жёлтый вердикт и возврат автору в этом же issue.
Материал раунда
- Ветка:
issue/700-preflight-warnings, коммит89d57e054cdf— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
945dad8823268ab0bbce926bd9ffe5e71d1f6cb2git log --all --format='%H %T' | grep 945dad882326 - Тело issue:
a0c40d40c1401844c3d04ff0695dd347e76172d990004ea2799cc4df38729fce - Вердикт конвейера:
yellow· High 0