22 KiB
SPEC-REVIEW-510-r3
- Issue: https://github.com/Matysh/houseplan-card/issues/510
- ТЗ:
docs/specs/510-mutants-on-candidate-and-review-waits-validate.md - Этап: spec (S4-spec-review) · заход r3 · блокирующих циклов израсходовано 2/4 до этого раунда
- Материал: ТЗ на HEAD рабочей копии (
3854fe72ac59b1e37a80b25faea0f8468356597a), дельта против материала r2 (5fc235c0e8de) - Предыдущий раунд: SPEC-REVIEW-510-r2, вердикт жёлтый, SHA материала
5fc235c0e8de, High 0 · Medium 1 · Low 0
Скоуп ревью
Разбор — по дельте (PROCESS.md §2.9/§2.10, issue #214): дельта локальна — правка того же ТЗ, той же ветки, без ребейза и без смены подсистемы, объём (3 предложения в одном разделе, git diff --stat — 4 вставки/4 удаления в одном файле) на порядок меньше исходной задачи. Но, как и в r2, дельта сама переписывает межшаговую проводку process.yml (§5.2) — контракт, по которому ветвятся все шаги ревью после gate. Поэтому разбор по дельте здесь не сводится к чтению трёх изменённых строк текста: нужно (1) закрыть находку r2 построчно, как и предписано форматом, и (2) проверить новую формулировку контракта против реального process.yml и существующих тестов — именно так r2 нашла свою находку не в новом тексте, а в столкновении нового текста со старым. Разделы, которых дельта не касается текстуально и чей контракт не меняется этой правкой (§1–§4, §5.1, §6, §7, §8 кроме связки со строкой 72, §9, §10.0, §10.1, §11, §12), не перепроверялись — унаследованы (см. ниже).
Как проверялось
git diff 5fc235c0..3854fe72 -- docs/specs/510-mutants-on-candidate-and-review-waits-validate.md— вся дельта, один хунк из 3 изменённых предложений в §5.2 (строки 69, 71, 72; строка 73 — только добавление «на этапе code», не меняющее её смысл).git show 3854fe72— сообщение коммита прочитано целиком, а не только дифф: «All step conditions now branch onproceedonly (true = green or skipped, false = red/missing)» — это явная формулировка проектного решения, а не побочный эффект правки формулировок, и именно она проверялась.- Находка r2 (единственная) сверена построчно с новым текстом (таблица ниже).
- Реальный
process.yml(grep -n "steps.reuse.outputs.reuse\|id: reuse\|id: gate\|Установить зависимости\|name: Review") — нынешние условия шагов «Установить зависимости», кэш/установка Chromium (пять мест) и шагаReview(вызывающего модель) — вездеsteps.rebase.outputs.conflict != 'true' && steps.reuse.outputs.reuse != 'true', не толькоrebase.conflict != 'true', как упрощённо называет это ТЗ. test/review-doc-guard.test.mjs:592-603— существующий (не из этой задачи) тест «конвейер: зелёный вердикт применяется повторно без модели» жёстко требуетassert.match(reviewStep, /steps\.reuse\.outputs\.reuse != 'true'/, 'модель не вызывается при повторном применении'). Этот тест в §11 ТЗ №510 назван как затрагиваемый файл, но ни разу не упомянут в §8 (плане тестов) как изменяемый или заменяемый.- Перечитан весь фрагмент §5.2 целиком (строки 69–76), а не только изменённые предложения по отдельности — так же, как это делала r2 при поиске своей находки.
Закрытие раунда r2
| Находка r2 | Чем закрыта | Где это видно |
|---|---|---|
Medium 1 — три соседних предложения называли условие продолжения/возврата тремя разными именами (result/proceed), и буквальное result != 'green' для красного возврата (строка 71 r2) давало бы паразитный S7→S6 на skip-ветке (spec/reuse) |
Частично. Красный-возврат (строка 71) переписан на steps.rebase.outputs.conflict != 'true' && steps.gate.outputs.proceed != 'true' — на skip-ветке proceed == 'true', шаг не срабатывает, паразитный возврат устранён. Формально устранена и текстовая нестыковка трёх формулировок: строка 69, строка 71 и строка 72 теперь одинаково используют proceed, а не смесь result/proceed. |
docs/specs/510-*.md:71 (красный-возврат — исправлено верно); :69,72 (унификация терминов — сделана) |
| — та же находка, часть про строку 72 («все последующие шаги ревью») | Не закрыта, закрыта неверно. r2 явно предлагала для этой группы оставить result == 'green' буквально, потому что она (в отличие от красного-возврата) обязана оставаться заблокированной на skip-ветке — reuse обязан коротко замкнуть без реального ревью. r3 вместо этого заменила условие всей группы (validated, зависимости, Chromium, Claude, Review, публикация, решение, слияние, перестановка метки) на proceed == 'true', а proceed == 'true' истинно и на skip-ветке (reuse) по определению той же строки 69 («true = green или skipped»). Это не formальность: см. находку ниже — это открывает шаг Review (вызов модели) и установку зависимостей/Chromium на reuse-ветке, где сейчас они не выполняются вовсе. |
docs/specs/510-*.md:72 (новая формулировка); противоречит реальному process.yml (условия шагов «Установить зависимости», кэш/установка Chromium, Review) и test/review-doc-guard.test.mjs:596 |
Находка r2 закрыта только в своей «текстовой» части (три формулировки сведены к одному слову) — но одна из трёх сведена к неверному значению. Синтаксическая цель ревью r2 («одна переменная — одно условие») достигнута; содержательная («группа „продолжение ревью“ не должна срабатывать на skip-ветке») — нарушена этим же изменением.
Находки
Medium (в скоупе) — 1: унификация «всё через proceed» ломает reuse-fast-path (#499) — деплой-зависимости, Chromium и вызов модели получили бы условие, истинное и на ветке, где их сейчас не выполняют вовсе
Файл: docs/specs/510-mutants-on-candidate-and-review-waits-validate.md, §5.2, строки 69 и 72.
Строка 69 вводит абсолютное утверждение: «Единственная переменная, по которой ветвятся последующие шаги, — proceed». Строка 72 применяет это к группе «все последующие шаги ревью (validated, зависимости, Chromium, Claude, Review, публикация, решение, слияние, перестановка метки)» — их условие меняется на steps.gate.outputs.proceed == 'true' вместо нынешнего steps.rebase.outputs.conflict != 'true'.
Но нынешнее условие этих конкретных шагов в реальном process.yml — не rebase.conflict != 'true' в одиночку, а steps.rebase.outputs.conflict != 'true' && steps.reuse.outputs.reuse != 'true' (проверено чтением файла: «Установить зависимости», пять мест кэша/установки Chromium, шаг Review, вызывающий модель, — везде оба условия). ТЗ называет только первый конъюнкт как заменяемый и ни словом не упоминает второй — а коммит r3 («All step conditions now branch on proceed only») прямо формулирует замену как тотальную, не частичную.
Раз proceed по той же строке 69 истинен и на skip-ветке (stage != code, reuse == true или нет ветки — «true = green или skipped»), буквальная реализация строки 72 даёт: на reuse fast-path (reuse == true) условие proceed == 'true' истинно → шаги «Установить зависимости», кэш/установка Chromium и Review (вызов модели) выполнятся, хотя сегодня они не выполняются вовсе — в этом и была точка #499: «вердикт прошлого захода применяется повторно: модель не вызывается» (комментарий в process.yml перед шагом reuse). Конкретное воспроизведение: test/review-doc-guard.test.mjs:592-603, тест «конвейер: зелёный вердикт применяется повторно без модели», жёстко требует steps.reuse.outputs.reuse != 'true' именно в условии шага Review — при реализации §5.2 буквально по тексту r3 этот существующий тест либо ломается, либо реализация должна тихо отступить от буквального текста ТЗ и сохранить && steps.reuse.outputs.reuse != 'true' вопреки заявленному «единственная переменная — proceed». Ни то, ни другое ТЗ не называет явно; test/review-doc-guard.test.mjs при этом перечислен в §11 как затрагиваемый файл, но конкретно эта проверка не упомянута в §8 ни как сохраняемая, ни как заменяемая.
Здесь же собственное противоречие внутри абзаца: строка 73 (не изменена по смыслу в этом раунде) описывает шаг validated как срабатывающий «при proceed == 'true' на этапе code» — то есть его поведение зависит не только от proceed, но и от stage, отдельной переменной. Заявление строки 69 «единственная переменная — proceed» опровергается уже следующим абзацем того же документа.
Воспроизведение (конкретный сценарий, не гипотетический): round задачи применяет reuse (зелёный вердикт прошлого захода переносится без вызова модели, ровно сценарий #437/#499, ради которого и заведён этот механизм) → gate пишет proceed=true, result=skipped (строка 69) → по буквальному тексту строки 72 шаг Review получает условие proceed == 'true' → модель вызывается повторно на том же дереве, за которое уже заплачен один заход, что и есть ровно тот перерасход, который #499 закрыл, только на новом месте.
Чем закрывается в этом же ТЗ: явно развести, как и предлагала r2, но с учётом факта, найденного в этом раунде — существующего reuse конъюнкта, о котором ТЗ не сказало ни слова:
- красный-возврат (строка 71) —
proceed != 'true'(эта часть уже верна, менять не нужно); - группа «продолжение ревью» (строка 72) — либо явно сохранить существующий
&& steps.reuse.outputs.reuse != 'true'в дополнение кproceed == 'true'для тех конкретных шагов, что сегодня его несут (зависимости, Chromium,Review), либо (эквивалентно и проще для «одной переменной») зафиксировать, чтоgateпишетproceed=false, а неtrue, на reuse-ветке — но тогда нужно менять и красный-возврат (строка 71), который на этой ветке не должен сработать, то есть развести «reuse» как третье, отдельно поименованное значение, а не смешивать его сskipped-для-spec; - строку 69 («Единственная переменная —
proceed») — либо снять как неточную (строка 73 её же опровергает), либо явно ограничить область действия: «единственная переменная, добавляемая этим ТЗ» — не единственная вообще. Правка — уточнение слов в тех же трёх местах плюс явное упоминание существующегоreuse-конъюнкта, отдельный issue не заводится (Medium в скоупе, PROCESS.md §2.4).
Что проверено и корректно
- Красный-возврат (строка 71) исправлен верно и именно так, как предлагала r2:
proceed != 'true'не срабатывает на skip-ветке — паразитныйS7 → S6для spec/reuse-заходов устранён. - Формальная (лексическая) нестыковка трёх формулировок из r2 устранена: строки 69/71/72 больше не называют одно и то же разными именами.
- Остальной текст §5.2 (порядок шага
gateотносительноreuse/«Конфликт с dev», алгоритмvalidate-gate.mjs, §5.1, §5.3) дельтой не тронут и остаётся тем, что уже проверено в r1/r2 построчно против кода. - AC1, AC3, AC4, AC5, AC6 дельтой этого раунда не задеты по существу — унаследованы.
Чего не проверял
npx tsc --noEmit,npm test,npm run build,node scripts/check-docs.mjs— не запускал: дельта r2→r3 — 4 вставки/4 удаления в одном.md-файле внеsrc/**/scripts/**/test/**, класс C (PROCESS.md §1), гейты неприменимы; то же самое было верно для r1 и r2, поведение не изменилось.- Golden/смоки/perf/backend/инварианты модели — задача не трогает геометрию, рендер, состояние или бэкенд; не изменилось с r2.
- Права токена
HP_PROCESS_TOKEN, риски §10.1 — не менялись дельтой, унаследованы из r1/r2. - Реальная реализация
process.yml/validate-gate.mjs— их нет, этап spec их не предполагает; находка выше проверена чтением существующегоprocess.ymlи существующего теста, не запуском.
Унаследовано из r2
Без повторной проверки в этом раунде — дельта их не касается:
- Обязательные разделы §7.1 присутствуют и на своём месте — из SPEC-REVIEW-510-r1 (SHA
34bf81a1) и подтверждено в SPEC-REVIEW-510-r2 (SHA5fc235c0). - Скоуп/не-скоуп (§2/§3), включая границу с #479/#480/#499, и совместимость условия job
changed_mutants(§4) с существующим механизмом--changed— из SPEC-REVIEW-510-r2, SHA5fc235c0; дельта этого раунда §4 не касается. - Раздел §10.0 (UX/i18n/модель данных не затрагиваются) — из SPEC-REVIEW-510-r2, SHA
5fc235c0; текст не менялся. - Порядок шага
gateотносительноmaterial/reuse/«Конфликт с dev» (§5.2, первое предложение) — сверен в r2 с реальными строкамиprocess.yml(414/430/445/485), дельтой r3 не тронут — из SPEC-REVIEW-510-r2, SHA5fc235c0. - Алгоритм
scripts/validate-gate.mjs(§5.1), включая закрытие Medium 2 из r1 («доказательство только при исполненных и зелёных job мутантов») — из SPEC-REVIEW-510-r1 (SHA34bf81a1) и SPEC-REVIEW-510-r2 (SHA5fc235c0), текст §5.1 дельтой r3 не тронут. - §6 (слияние кандидата), §7 (правила хендоффа), §9 (откат), §10.1 (риски), §11 (затронутые файлы), §12 (предположения) — из SPEC-REVIEW-510-r1/r2, текст не менялся ни в r2, ни в r3.
- AC1, AC3, AC4, AC5, AC6 — из SPEC-REVIEW-510-r1/r2, способ доказательства не изменился.
Вердикт
High: 0 · Medium (в скоупе): 1 · Low: 0.
Находка r2 закрыта только частично: красный-возврат исправлен верно, но формулировка «единственная переменная — proceed», применённая ко всей группе «продолжение ревью» (включая установку зависимостей, Chromium и вызов модели), конфликтует с реальным кодом process.yml (условия этих шагов сегодня требуют ещё и steps.reuse.outputs.reuse != 'true') и с существующим тестом test/review-doc-guard.test.mjs:596, который жёстко фиксирует, что модель не вызывается на reuse-ветке (#499). Буквальная реализация текста r3 открывает ровно тот перерасход (повторный вызов модели на уже принятом дереве), ради устранения которого заведён #499. Это не гипотетика — сценарий воспроизведён по коду и существующему тесту, а не по документу самому по себе. Закрывается в этом же ТЗ уточнением трёх формулировок §5.2 с явным упоминанием reuse-конъюнкта.
Вердикт: жёлтый. Возврат в «ТЗ в работе» для уточнения §5.2 (строки 69, 72); далее — повторный цикл ревью по дельте (PROCESS.md §2.9/§2.10).
Материал раунда
- Ветка:
issue/510-mutants-on-candidate-and-review-gate, коммит3854fe72ac59— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
45ab656b9462fc4f8a3a6f5eaad139d4b14d9f99git log --all --format='%H %T' | grep 45ab656b9462 - ТЗ
docs/specs/510-mutants-on-candidate-and-review-waits-validate.md, блоб66bd41844726bd574d4fe59b506cd1f4389c34f8git log --all --find-object=66bd41844726bd574d4fe59b506cd1f4389c34f8 -- docs/specs/510-mutants-on-candidate-and-review-waits-validate.md - Вердикт конвейера:
yellow· High 0