docs: review document for #700

Issue: #700
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-29 01:22:14 +03:00
committed by Claude
parent e45bc87c6d
commit a005aae5b6
+184
View File
@@ -0,0 +1,184 @@
# CODE-REVIEW-700-r1
Issue: #700 · Предполёт не красит ветку задачи чужими причинами: зеркало workflow и внешние ссылки
Этап: code · Заход: r1 · Трек: show · Материал: `89d57e054cdf940d3ead83ec509075164118ee3b` (один коммит поверх `origin/dev` @ `c716bb0f63104f5afb9d660bc058e095256d433d`)
## Скоуп
Инфраструктурная задача трека `show`: предполёт `validate.yml` не должен красить
ветку задачи двумя внешними по отношению к её диффу причинами —
1. расхождением зеркала тонких вызывающих workflow (`main` vs `dev`);
2. упавшим внешним сайтом при проверке ссылок в документации (`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
владельцу»:
```bash
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 (run `36482200722`) без ошибки парсинга 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.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/700-preflight-warnings`, коммит `89d57e054cdf` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `945dad8823268ab0bbce926bd9ffe5e71d1f6cb2`
```
git log --all --format='%H %T' | grep 945dad882326
```
- Тело issue: `a0c40d40c1401844c3d04ff0695dd347e76172d990004ea2799cc4df38729fce`
- Вердикт конвейера: `yellow` · High 0