Files
houseplan-card/docs/reviews/SPEC-REVIEW-510-r1.md
T
2026-09-09 15:14:20 +00:00

240 lines
20 KiB
Markdown
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.
# SPEC-REVIEW-510-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/510
- **ТЗ:** `docs/specs/510-mutants-on-candidate-and-review-waits-validate.md`
- **Этап:** spec (S4-spec-review) · заход r1 · блокирующих циклов израсходовано 0/4 до этого раунда
- **Материал:** тело ТЗ на HEAD рабочей копии (`34bf81a1`), тело issue #510 и комментарий S2 (Codex, 09.09)
## Скоуп ревью
ТЗ описывает три поверхности протокола CI (`validate.yml`, `process.yml`,
`merge-candidate.mjs`) плюс правила хендоффа в `PROCESS.md`/`AGENTS.md`. Трек —
полный (сам автор в шапке ТЗ называет критерий, которому задача не
соответствует: «§5 "одна поверхность" не проходит» — это ровно то, что
требует AGENTS.md при выборе трека, замечаний к выбору трека нет).
Проверял: полноту обязательных разделов §7.1, однозначность и доказуемость
AC1…AC6, и — поскольку весь предмет ТЗ это низкоуровневый контракт CI —
техническую состоятельность описанных механизмов **против фактического кода
репозитория**, а не только внутреннюю логичность текста. Продуктовых вопросов
владельцу нет: задача инфраструктурная, ни один пункт не задевает то, что
видит пользователь карточки (AC6 прямо это фиксирует и проверяем — `src/**`
действительно нигде не упомянут в §11).
## Как проверялось
Читал ТЗ построчно и сверял каждое фактическое утверждение о текущем
состоянии кода с самим кодом:
- `.github/workflows/validate.yml` — вход `workflow_dispatch.inputs.full`,
группа `concurrency` (`validate-dispatch-<ref>` для dispatch, отдельная от
push), job `changed_mutants` и её текущее условие;
- `.github/workflows/process.yml` — шаги `material` (id, строка 414), `reuse`
(id, строка 429-440, условие `rebase.outputs.conflict != 'true' &&
guard.outputs.stage == 'code'`), «Конфликт с dev» (445), «Зелёные гейты на
этом SHA» / `id: validated` (484-510, ровно тот текст, что дан в задании как
«Зелёного Validate на этом SHA нет»), последующие шаги с условием
`rebase.outputs.conflict != 'true' && reuse.outputs.reuse != 'true'`;
- `.github/workflows/nightly.yml` — токен и `permissions: actions: write`,
которыми там уже пользуется `gh workflow run`;
- `scripts/classify-changes.mjs` — `heavyGatesRequested`, `hasReleaseTrailer`,
`CHECK_OF_OUTPUT`, чтобы сверить предложенную `mutantsRequested` с уже
существующим паттерном для `heavy`;
- `scripts/merge-candidate.mjs` — `realOps`, `waitValidate` (текущая сигнатура
без фильтра по событию, JSON-поля `databaseId,status,conclusion,url` без
`event`), константы `VALIDATE_APPEAR_MS`/`VALIDATE_TOTAL_MS`/`MAX_ATTEMPTS`;
- `scripts/mutation-gate.mjs` — реестр уже содержит мутанты на
`.github/workflows/*.yml`, `scripts/merge-candidate.mjs`,
`scripts/review-doc-guard.mjs`, т.е. AC4 (протокольные мутанты) технически
достижим тем же механизмом, а не новой инфраструктурой;
- `test/review-doc-guard.test.mjs` — способ проверки порядка шагов в текущих
тестах: `indexOf` по подстроке имени шага в сыром YAML, ровно то, что ТЗ
обещает для нового шага `gate`;
- `docs/specs/README.md` — обязательный раздел «release-артефакты» и его
требование для задач без пользовательского поведения.
Не проверял (не относится к этапу spec): реальный `scripts/validate-gate.mjs`
не существует, код не пишется на этом этапе — оценивался только контракт,
который ТЗ для него описывает.
## Находки
### Medium (в скоупе) — 1: место шага `gate` относительно `reuse` не зафиксировано и рискует сломать fast-path #499
**Файл:** `docs/specs/510-mutants-on-candidate-and-review-waits-validate.md`, §5.2, пункт 1 и 3.
ТЗ формулирует условие нового шага `gate`:
```
needs.guard.outputs.stage == 'code' && steps.rebase.outputs.conflict != 'true' && steps.reuse.outputs.reuse != 'true'
```
и одновременно говорит, что шаг идёт **«после `material`»** — а `material`
(строка 414 `process.yml`) исполняется **до** `reuse` (строка 429-440).
Шаги GitHub Actions читают вывод друг друга по фактическому порядку
объявления в файле: если `gate` буквально вставить сразу после `material` (то
есть перед `reuse`), выражение `steps.reuse.outputs.reuse` в этот момент ещё
не определено, поэтому `!= 'true'` вычисляется в `true` **независимо от
того, что реально решил `reuse`** — условие перестаёт что-либо фильтровать.
Практическое следствие — ровно тот сценарий, который #499 был написан
устранять: возврат ревью на дереве, где вне `docs/reviews/**` ничего не
менялось (например, ребейз-переприменение зелёного вердикта после конфликта
слияния). `reuse` в этом случае обязан коротко замкнуть путь без вызова
модели — но если `gate` не видит его результата и стоит раньше, конвейер всё
равно уйдёт в 10–45-минутный dispatch Validate с мутантами на practически
той же дельте кода, которую только что признали неизменной. Это тот же класс
потерь, который стоил #437 r4 12 минут (только дороже: там был holостой
проход ревьюера, здесь — ещё и полный прогон CI).
Описанный в §8 тест (`review-doc-guard.test.mjs`: «шаг gate стоит после
material и до установки зависимостей») эту ошибку не ловит: обе трактовки
порядка («сразу после material» и «после reuse») одинаково проходят проверку
«между material и install deps», потому что `reuse` тоже лежит в этом
диапазоне.
**Чем закрывается в этом же ТЗ:** одно явное предложение о порядке — «`gate`
стоит после `reuse`» (а не «после `material`»), и уточнение теста:
`indexOf('- name: ...reuse...') < indexOf('- id: gate')` наравне с уже
названной проверкой. Правки самого текста хватает, отдельный issue не
заводится (Medium в скоупе, PROCESS.md §2.4).
### Medium (в скоупе) — 2: критерий «подходящий прогон» в `validate-gate.mjs`/`merge-candidate.mjs` не проверяет, что мутанты реально запрошены и выполнены
**Файл:** тот же ТЗ, §5.1 пункт 2 и §6.
Алгоритм называет «подходящим» любой прогон с `event == 'workflow_dispatch'`
на нужном SHA и читает его **общий** `conclusion`. Но с этим же ТЗ вход
`mutants` получает `default: false`, то есть не любой `workflow_dispatch` на
эту ветку означает «мутанты были запрошены и `changed_mutants` реально
выполнилась»: `workflow_dispatch` с явным `full=false, mutants=false` (ручной
запуск для другой цели, например только чтобы проверить классификацию) на
том же SHA — и общий `conclusion` вполне может быть `success`, потому что
`changed_mutants` в нём просто `skipped`, а не `failed`. Алгоритм примет это
как доказательство и откроет ревью без единого запущенного мутанта — именно
то, от чего в этом же проекте написан целый раздел PROCESS.md §2.7 про
«третий столбец — чем краснеет» и разбор #423/#430, где зелёный тест без
реальной защиты дважды стоил дня.
Симметричная проблема в `merge-candidate.mjs` §6: `waitValidate(sha, { event:
'workflow_dispatch' })` описан так же — фильтр по типу события, не по факту
выполнения `changed_mutants` с успехом.
**Чем закрывается в этом же ТЗ:** уточнить критерий одним из двух способов —
(а) `validate-gate.mjs`/`waitValidate` дополнительно запрашивают
`gh run view <id> --json jobs` и требуют, чтобы job `changed_mutants` в этом
прогоне была `conclusion == success` (не `skipped`), либо (б) читают входы
самого dispatch-прогона (`gh run view --json ... ` содержит `event` payload с
`inputs`, либо `workflow_dispatch` API отдаёт `display_title`/`name` —
технический выбор автора) и проверяют `full=='true' || mutants=='true'`
явно, а не по одному факту события. Оба варианта дешёвы и укладываются в уже
описанный `ops`-интерфейс; фиксируется одним уточнением текста и одной
дополнительной строкой в описанных тестах `validate-gate.test.mjs` /
`merge-candidate.test.mjs` («дispatch без мутантов на нужном SHA → не
считается»).
### Low — 1: нет явной декларации «пользовательское поведение не меняется» и N/A по i18n/UX/модели данных
**Файл:** тот же ТЗ, разделы отсутствуют.
`docs/specs/README.md` требует прямо: «для чистого refactoring ТЗ должно
прямо зафиксировать отсутствие пользовательских изменений и перечислить
технические доказательства безопасного поведения», если раздел
release-артефактов не заполняется. В ТЗ #510 такой явной строки нет — AC6
(«Перф/touch/UX не затронуты: `src/**` без изменений») закрывает это по
смыслу, но не формально: нет ни отдельной строки i18n (§7.1 требует раздел
даже когда ответ «не применимо»), ни явного `User-Visible: no`.
Правится одной строкой в §12 или новым мини-разделом («Release-артефакты:
нет — `src/**` не меняется, `User-Visible: no`; i18n/UX/модель данных/миграция
— не применимо, инфраструктурная задача»). Не блокирует: по существу ответ
уже виден из AC6 и §11, найдено ревьюером, а не унесено молча — снимаю
находку с этой записью, если автор согласится с тем же текстом при правке;
иначе поднимаю до Medium в следующем заходе.
## Что проверено и корректно
- Обязательные продуктовые разделы §7.1 присутствуют и на своём месте:
сценарий (§1.1) и «что человек увидит» (§1.2) стоят первыми, отвечают на
оба вопроса именно для того персонажа, который тут есть — автора/ревьюера
как оператора CI (задача инфраструктурная, это ожидаемо и не является
подменой продуктовой рамки).
- Скоуп/не-скоуп (§2/§3) разделены чётко, не-скоуп явно исключает состав
мутантов, `heavy`-гейты и ретраи «до зелёного» — ни один пункт не пытается
расшириться на #479/#480/#499, которые ТЗ прямо называет смежными.
чтобы не путать с новым.
- Каждый AC1…AC6 указывает способ доказательства (конкретный тестовый файл
или «штатный раннер» мутантов) — ни один не оставлен без свидетеля.
«Догадка, выданная за факт» не встретилась: там, где ТЗ описывает
поведение существующего кода, я перепроверил и утверждение подтвердилось
(см. «Как проверялось») — включая менее очевидные детали вроде уже
существующей отдельной concurrency-группы для dispatch и точных имён/строк
шагов в `process.yml`.
- Откат (§9) и риски (§10.1) названы предметно, а не общими словами; риск про
токен (`HP_PROCESS_TOKEN` для нового вызова `gh workflow run` в
`process.yml`, впервые из PAT, а не `GITHUB_TOKEN`, как в `nightly.yml`)
подтверждён чтением `nightly.yml` — это не выдумка, это реальное различие
между двумя workflow, и план «проверить на первом прогоне» для чисто
инфраструктурного риска такого рода приемлем.
- AC4 (протокольные мутанты) реалистичен: реестр `scripts/mutation-gate.mjs`
уже содержит мутанты на `.github/workflows/*.yml` и `scripts/merge-
candidate.mjs`, механизм не изобретается заново.
- Совместимость (§9) честно называет цену перехода (одна лишняя мутантная
проверка на первый `S7` после внедрения) вместо того, чтобы её прятать.
## Чего не проверял
- Реальный код `scripts/validate-gate.mjs` — его нет, этап spec его не
предполагает; оценивался только контракт, который ТЗ ему назначает.
Соответственно не запускал `npm run typecheck`/`npm test`/`npm run build`:
на этом этапе нет диффа продукта или тестов, который они могли бы поймать
(класс B/C файлов ещё не существует).
`node scripts/check-docs.mjs` не запускал — `src/**` в ТЗ не упомянут и
не будет затронут по AC6, гейт неприменим до этапа кода.
Не проверял golden/смоки/perf/backend/инварианты модели — задача не
трогает геометрию, рендер, состояние комнат или бэкенд ни на йоту.
- Не проверял область `docs/specs/README.md` дальше цитированного правила о
release-артефактах (сама таблица со ссылкой на ТЗ уже верно добавлена в
P1 — это видно из чтения файла, отдельно не тестировал).
- Не оценивал права токена (`HP_PROCESS_TOKEN` scope `workflow`/`actions`) —
это внешняя конфигурация GitHub, не код репозитория; риск назван в ТЗ и
помечен «проверить на первом прогоне», это адекватно для находки такого
рода на этапе spec.
## Вердикт
High: 0 · Medium (в скоупе): 2 · Low: 1.
Обе Medium-находки — про сам механизм, который ТЗ вводит (порядок шага
`gate` относительно `reuse`, и точность критерия «мутанты действительно
выполнены») — конкретны, воспроизводимы по тексту ТЗ и фактическому коду, и
закрываются правкой текста ТЗ в этом же документе без изменения скоупа
задачи. High нет: ни один AC не сломан целиком, оба дефекта — недосказанность
в контракте, а не ошибочное решение.
**Вердикт: жёлтый.** Возврат в «ТЗ в работе» для уточнения §5.1/§5.2/§6 и,
по желанию автора, Low-находки; далее — повторный цикл ревью по дельте
(PROCESS.md §2.10).
## Материал раунда
- SHA ТЗ: `34bf81a1ed51f041fb3b5b967de84961ce566720`
- Файл: `docs/specs/510-mutants-on-candidate-and-review-waits-validate.md`
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/510-mutants-on-candidate-and-review-gate`, коммит `34bf81a1ed51` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `0ca8ddf79d45debb76c69e832504a826d78a8e7f`
```
git log --all --format='%H %T' | grep 0ca8ddf79d45
```
- ТЗ `docs/specs/510-mutants-on-candidate-and-review-waits-validate.md`, блоб `d6e47a72256e888541f3a2bcdb085bfad81ff52d`
```
git log --all --find-object=d6e47a72256e888541f3a2bcdb085bfad81ff52d -- docs/specs/510-mutants-on-candidate-and-review-waits-validate.md
```
- Вердикт конвейера: `yellow` · High 0