# SPEC-REVIEW — issue #454 · заход r2 - Issue: https://github.com/Matysh/houseplan-card/issues/454 - Этап: ТЗ на ревью (PROCESS.md §2.4), заход r2, блокирующих циклов израсходовано 1 из 4 - Материал: `docs/specs/454-review-round-counter.md` на коммите `f3e60578` (комментарий автора «Замечание r1 (M1) исправлено, ТЗ обновлено: `f3e60578`», ветка `issue/454-review-round-counter`) - Предыдущий раунд: r1, жёлтый, документ `docs/reviews/SPEC-REVIEW-454-r1.md`, материал `ebcf1223` - Ревьюер: свежая сессия, без контекста написания ТЗ и r1 ## Скоуп разбора Разбор по дельте (PROCESS.md §2.9/§2.10). Дельта — один коммит `f3e60578`, `git diff ebcf1223..f3e60578 -- docs/specs/454-review-round-counter.md`: 32 строки в одном файле, три места: 1. одна строка таблицы «Факты по #449» (маркер в теле код-вердикта 16:01); 2. абзац под таблицей, объясняющий эту строку, плюс следствие в «не-скоупе» про необратимую потерю `spent` первого спек-раунда #449; 3. переформулировка **AC2** + новый **AC2b** + синхронный абзац в «Плане тестирования». Ребейза не было (родитель дельты — тот же `ebcf1223`, на котором получен r1). Контракт поведения (§1–§5 ТЗ), скоуп/не-скоуп по составу, карта реализации, риски, release-артефакты — не тронуты дельтой, разбираю их по r1, без повторной проверки. Задета только доказательная база одного AC и одна фактическая иллюстрация в разделе «Подтверждённая проблема» — сокращённый разбор оправдан, дельта локальна. ## Как проверялось Правки не вношу — только читаю и перепроверяю исполнением/рассуждением на реальных данных. - Прочитан диф `git diff ebcf1223..f3e60578` целиком (не только резюме автора). - Independently (не со слов автора) снят фактический снимок #449 на сегодня: - `gh issue view 449 --repo Matysh/houseplan-card --json comments` — все комментарии-вердикты с их точным текстом; - `git show origin/dev:docs/reviews/SPEC-REVIEW-449-r1.md` / `…-r2.md` / `CODE-REVIEW-449-r1.md` / `…-r2.md` — содержимое и собственная итоговая строка вердикта каждого файла; - `git show :docs/reviews/SPEC-REVIEW-449-r1.md` на трёх исторических блобах (`1ce62613`, `070c276e`, `b39f99b3`), названных автором, чтобы подтвердить, что `-r1.md` физически содержит тело второго раунда, а `-r2.md` — третьего. - По этим данным вручную пересчитаны обе формулы контракта §1–§3 ТЗ для фикстур AC2 и AC2b (см. «Закрытие раунда r1» ниже) — не поверил заявлению автора на слово, получил числа заново из первичных данных. - Прочитан весь остаток документа (шапка, скоуп/не-скоуп, контракт, крайние случаи, AC1–AC9, карта реализации, риски, release-артефакты, принятые предположения) на предмет того, не сломала ли дельта что-то, что дельта не должна была трогать — не сломала. Гейты (`tsc`/`test`/`build`/`check-docs`/`invariants`/смоки/`golden`) не гонял: класс изменения — документация ТЗ, реализации на ветке нет (`scripts/review-doc-guard.mjs`, `test/review-doc-guard.test.mjs` на ветке задачи отсутствуют), это унаследовано из r1 и дельтой не задето. ## Закрытие раунда r1 | Находка r1 | Чем закрыта | Где это видно | |---|---|---| | **M1** (Medium, в скоупе): AC2 требовала `attempt=3, spent=2` на фикстуре «реальные данные #449», но буквальный пересчёт по реальным файлам/комментариям даёт `spent=1` — план тестирования не указывал, какое из двух прочтений (буквальное или реконструкция) имеется в виду | AC2 переписан на буквальный слепок (`attempt=3, spent=1`), добавлен отдельный AC2b на реконструкцию («история, прожитая с исправлением», `attempt=4, spent=2`); «План тестирования» называет обе фикстуры по имени и раздельно; в «не-скоупе» явно зафиксировано, что `spent=1` на сегодняшних данных — правильный ответ, а не недосчёт | `docs/specs/454-review-round-counter.md:96‑111,174‑194` (коммит `f3e60578`). Перепроверено мной независимо: `SPEC-REVIEW-449-r1.md` на `origin/dev` содержит тело раунда 2 (собственная финальная строка — «жёлтый вердикт... возвращается автору»), `…-r2.md` — тело раунда 3 (финал — «зелёный вердикт... бюджет циклов не тратит»); `attemptFromFiles=max(1,2)+1=3`, `spentFromFiles=1` (только `-r1.md` блокирующий); `spentFromComments`: из трёх комментариев маркер несут два (14:56, 15:17), из них блокирующий один (14:56) → `1`. `max(1,1)=1`. Совпадает с новой формулировкой AC2 | | L1 (Low, была снята с записью в r1, без обязательства правки) — нет разделов «UX»/«i18n»; r1 предлагал добавить строки-заглушки «заодно», если раунд правит документ | Не добавлено — но это была необязательная опция («было бы уместно… не отдельным циклом»), не требование; L1 уже была закрыта самим r1 без возврата автору | Текущий текст ТЗ: шапка по-прежнему содержит только `Touch editor: not exposed`, строк «UX:»/«i18n:» нет — не регрессия, а неиспользованная опция | | L2 (Low, к сведению, не в счёт вердикта) — cohesion `review-doc-guard.mjs` | Не поднималась к правке в r1 и не поднимается сейчас — technical, оставлена автору кода | Карта реализации не изменилась дельтой | ## Унаследовано из r1 Без повторной проверки принимаю из `docs/reviews/SPEC-REVIEW-454-r1.md` (материал `ebcf1223`) — дельта этих мест не касается: - **Контракт §1** (источник истины — файлы, `max+1`, а не количество) и его проверка на крайних случаях (дыра в нумерации, пустое множество файлов). - **Контракт §2** (строка вердикта, `жёлт`/`красн` без учёта регистра) — совпадает с существующим `process.yml:100`. - **Контракт §3** (страховка максимумом) — `max(a,b)` не может дать перерасчёт при данном построении источников. - **AC1, AC3–AC7** — формулировки однозначны, способ доказательства достижим; AC4/AC5/AC7 согласованы с уже действующими инвариантами #227/#89. - **AC8, AC9** — синхронизация `process.yml` main/dev через существующий шаг Validate; логика в тестируемом модуле, а не в inline-shell. - **Не-скоуп** (ручная правка задним числом комментария #449, восстановление `SPEC-REVIEW-449-r1.md`, обязательный гейт «назови файл») — исключения корректны и не додуманы. - **Ограничение «`process.yml` идентичен в main и dev»**, **план отката**, **release-артефакты** (`User-Visible: no`, класс B, трейлеры). - Технические наблюдения L1/L2 (не блокируют, см. таблицу выше). ## Что проверено и корректно (дельта) - **AC2 (новая формулировка).** Число `attempt=3, spent=1` подтверждено независимым пересчётом по первичным данным (см. «Как проверялось» и таблицу закрытия) — не осталось расхождения, из-за которого r1 вернул ТЗ. Фикстура названа однозначно («буквальный слепок сегодняшнего состояния ветки»), способ доказательства не изменился (unit). - **AC2b (новый).** Формула контракта §1/§3, применённая к трём независимым файлам (`r1` жёлтый, `r2` жёлтый, `r3` зелёный), даёт `attemptFromFiles=max(1,2,3)+1=4`, `spentFromFiles=2` (r1, r2 блокирующие, r3 нет) — совпадает с заявленным `attempt=4, spent=2`. Формулировка не оставляет второго прочтения: явно названа «реконструкция», а не факт. - **Правка в «Подтверждённая проблема» про код-вердикт 16:01.** Автор дополнительно (сверх M1) исправил собственную фактическую ошибку: красный код-вердикт 16:01 содержит подстроку `CODE-REVIEW` в свободном тексте («путь соберёт шаг публикации, `CODE-REVIEW-449-r1`»), а не отсутствует, как было написано раньше. Проверено дословно по тексту комментария (`gh issue view 449 --json comments`, комментарий `2026-09-04T16:01:28Z`, последний абзац) — подстрока действительно там. Утверждение «guard видит… один код-вердикт из одного (`attempt=2`, верно)» было точным на момент написания дельты (единственный код-вердикт на #449 в тот момент — этот красный). Это иллюстрация в разделе с фактажом, не часть контракта или AC — её точность на дальнейшее развитие живого issue #449 не влияет на доказательность AC2/AC2b, которые используют замороженную (unit-)фикстуру, а не пересчёт по живому issue при каждом прогоне теста. - **Внутренняя согласованность документа после правки.** Прочитан документ целиком: старое утверждение «дефект повторится на первом же возврате #449 в S7-code-review» удалено (заменено на факт про 16:01), нигде в остальном тексте (скоуп, контракт, карта реализации, риски) на него больше нет ссылок — висячих противоречий не осталось. ## Чего не проверял - Не гонял гейты (`tsc`/`test`/`build`/`check-docs`/`invariants`/смоки/ `golden`/`pytest`) — класс изменения документация, кода на ветке нет; унаследовано из r1, дельта тоже только в `.md`. - Не проверял `gh api`/`git ls-remote` на предмет реальной способности перечислить файлы `docs/reviews/` на произвольной ветке без checkout — унаследовано из r1 как «техническая деталь реализации, не продуктовое ограничение», дельта эту часть контракта (§4 ТЗ) не трогает. - Не проверял дальнейшую судьбу код-этапа #449 после комментария 16:01: на момент этого ревью на issue #449 уже существует второй код-вердикт (зелёный, `2026-09-04T16:22:58Z`, опубликован как `docs/reviews/CODE-REVIEW-449-r2.md` без коллизии имени — комментарий сам явно называет свой документ). Это не расходится с текстом ТЗ (иллюстрация фиксирует факт на момент авторской правки, а не обещание, что живой issue #449 остановится) и не требует правки ТЗ — привожу для полноты, не как находку. - Не проверял отдельно раздел «Карта реализации»/«Риски» повторно — дельта их не касается, инвариант «унаследовано из r1» покрывает их. ## Итог Находка предыдущего раунда (M1) закрыта и перепроверена исполнением по первичным данным, а не со слов автора: `spent=1` для буквальной фикстуры и `spent=2` для реконструкции — оба числа независимо пересчитаны и совпадают с новыми AC2/AC2b. Новых High или Medium дельта не создала. Остаток контракта не тронут и наследуется из r1 без повторной проверки. Вердикт — зелёный, бюджет цикла не тратится. --- ## Материал раунда - Ветка: `issue/454-review-round-counter`, коммит `f3e60578d443` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `983c66cca25d56b4a147e535608bce8f2261ad05` ``` git log --all --format='%H %T' | grep 983c66cca25d ``` - ТЗ `docs/specs/454-review-round-counter.md`, блоб `b5af292101a495e4699d839397e837a3a2467c85` ``` git log --all --find-object=b5af292101a495e4699d839397e837a3a2467c85 -- docs/specs/454-review-round-counter.md ```