20 KiB
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), jobchanged_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_TOKENscopeworkflow/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
Материал раунда
- Ветка:
issue/510-mutants-on-candidate-and-review-gate, коммит34bf81a1ed51— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
0ca8ddf79d45debb76c69e832504a826d78a8e7fgit log --all --format='%H %T' | grep 0ca8ddf79d45 - ТЗ
docs/specs/510-mutants-on-candidate-and-review-waits-validate.md, блобd6e47a72256e888541f3a2bcdb085bfad81ff52dgit log --all --find-object=d6e47a72256e888541f3a2bcdb085bfad81ff52d -- docs/specs/510-mutants-on-candidate-and-review-waits-validate.md - Вердикт конвейера:
yellow· High 0