mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 13:48:57 +00:00
240 lines
20 KiB
Markdown
240 lines
20 KiB
Markdown
# 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
|