# SPEC-REVIEW-369-r2 - Issue: https://github.com/Matysh/houseplan-card/issues/369 - Этап: ТЗ на ревью (PROCESS.md §2.4), полный трек (владелец назвал критерий «одна поверхность» §5 как не выполненный — семь несвязанных поверхностей) - Артефакт: `docs/specs/369-audit-lows.md` - Материал ревью: дельта `cbf9318e..c50d9e42` («ревизия 2» документа, `docs: #369 spec revision 2 per SPEC-REVIEW-369-r1`) — тот же единственный файл, 3 хунка (+11/-7 строк), `User-Visible: no`, корректно для docs-коммита - Заход: **r2** · блокирующих циклов израсходовано **1 из 4** (лимит для полного трека — 4) ## Несовпадение номера раунда в заголовке задачи — зафиксировано, не находка по ТЗ Заголовок этой задачи ревью объявлял «Заход: r1 · блокирующих циклов 0 из 4». Это неверно относительно фактического состояния issue: в `docs/reviews/` уже лежит `SPEC-REVIEW-369-r1.md` с вердиктом «жёлтый» (три Medium: M1, M2, M3), и это подтверждено комментарием issue от `2026-08-29T07:41:01Z` («Вердикт: жёлтый · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 3 → в задаче»), а следующий комментарий владельца («Ревизия 2 ТЗ (dev `c50d9e4`) по r1: **M1**...**M2**...**M3**... Возвращаю S4-spec-review») подтверждает, что это и есть возврат на правки по r1. Жёлтый вердикт тратит бюджет §4 (не зелёный, #227) — значит фактический счёт: заход **r2**, циклов **1/4**, а не r1/0. Использую r2 в имени этого документа: назвать его r1 означало бы затереть уже существующий `SPEC-REVIEW-369-r1.md` — ровно тот сценарий, от которого предостерегает сам процесс триггера. Не продуктовая находка (не про ТЗ #369), но стоит сообщить о рассинхроне источника номера раунда в автоматизации, которая формирует заголовок задачи — иначе следующий заход рискует получить то же самое имя дважды. ## Скоуп раунда (правило дельты, PROCESS.md §2.9, #214) Дельта — три точечных правки одного файла, отвечающие ровно на находки r1: 1. `- Ревизия: 1` → `- Ревизия: 2 ... — по SPEC-REVIEW-369-r1 (M1–M3)` (метаданные, не содержательно). 2. Контракт (ж): список файлов расширен `docs/FURNITURE.md:41` (M1). 3. AC1: доказательство переформулировано с «check-docs зелёный» на «код-ревью читает три файла» (M2). 4. AC8: добавлен мутант для (д) — «attach выброшен → смок AC5 красный по free-флагу и счётчику слушателей» (M3). Ни рекомбейза на ушедший вперёд `dev`, ни смены контракта поведения, ни новой подсистемы — дельта локальна классически. Разбор этого раунда ограничен M1–M3 и тем, не сломала ли точечная правка что-то из уже подтверждённого в r1 (правки чисто аддитивны: добавлено имя файла, снята формулировка доказательства AC1, добавлен один пункт списка мутантов — ни один не пересекается текстуально с AC2–AC7 или разделами «Риски», «Release-артефакты», «Вне скоупа», «Откат»). ## Как проверялось - `git diff cbf9318e..c50d9e42 -- docs/specs/369-audit-lows.md` — прочитан целиком, три хунка перечислены выше. - M1: контракт (ж) в текущем тексте (строка 66-68) действительно называет четыре файла, включая `docs/FURNITURE.md:41`. `grep -rn Matyushin docs/ assets/` на HEAD (`c50d9e42`) по-прежнему находит все четыре целевых файла (правка кода ещё не сделана — стадия ТЗ, ожидаемо) плюс историческую прозу `docs/reviews/SPEC-REVIEW-369-r1.md` и сам текст спеки, цитирующие старое имя как часть описания правки — см. находку L1 ниже. - M2: AC1 в текущем тексте (строка 72-74) — «доказательство: код-ревью читает три файла (check-docs содержимое не проверяет, он лишь не должен покраснеть от ссылок/фингерпринта)» — снимает именно то расхождение инструмент/утверждение, которое было найдено в r1 (`scripts/check-docs.mjs` не читает содержимое абзацев). - M3: AC8 (строка 92-96) теперь перечисляет мутант для (д) отдельно от (б)/(в)/(г)/(е), с двумя названными детекторами (смена free-флага, счётчик addEventListener/removeEventListener) — то же, что требовало M3. - Трейлеры `c50d9e42`: `Issue: #369`, `User-Visible: no` — верно для docs-only правки класса C (PROCESS.md §1). - Диапазон изменений всей спек-стадии #369 (`cbf9318e` и `c50d9e42`) — единственный файл `docs/specs/369-audit-lows.md` оба раза (`git show --stat` на каждом коммите); `src/**`/`custom_components/**` не тронуты ни разу. Дешёвые гейты (`typecheck`/`test`/`build`/`check-docs`) на этом заходе не запускал — тот же довод, что в r1: диапазон дельты не содержит ни одного файла класса A/B (PROCESS.md §1), гейты нечего было бы доказать. Смоки, golden, инварианты модели — не запускались по той же причине (ни геометрия, ни `src/**`, ни визуальный результат в этой дельте не задеты). ## Закрытие раунда r1 | Находка (r1) | Чем закрыта | Где видно | |---|---|---| | **M1** — AC7 требовал пустой repo-wide grep, но контракт (ж) не покрывал все вхождения имени (`docs/FURNITURE.md:41` пропущен) | Контракт (ж) расширен до четырёх файлов, включая `docs/FURNITURE.md:41` | `docs/specs/369-audit-lows.md:66-68`, diff-хунк 2 (`cbf9318e..c50d9e42`) | | **M2** — AC1 называл `check-docs` доказательством содержимого абзаца, хотя скрипт содержимое не проверяет | AC1 переформулирован: доказательство — прямое чтение трёх файлов на код-ревью, `check-docs` оставлен только структурным требованием | `docs/specs/369-audit-lows.md:72-74`, diff-хунк 3 | | **M3** — AC8 не называл мутант для (д), смок AC5 нельзя было отличить «проверяет логику» от «проходит без неё» | AC8 получил явный мутант «attach выброшен → смок красный по free-флагу и счётчику слушателей» | `docs/specs/369-audit-lows.md:92-96`, diff-хунк 4 | Все три Medium из r1 закрыты предметно (текст ТЗ, не заявление автора). ## Унаследовано из r1 Документ: `docs/reviews/SPEC-REVIEW-369-r1.md`, разобран на SHA `cbf9318e`. Дельта r2 не касается ничего из перечисленного ниже — принимается без повторной построчной проверки: - Сценарий («до/после», персона home admin/десктоп-редакторы) и соответствие J1/J2/J4/J5/J6 из `docs/SCOPE.md` — нарушения не найдено. - Все шесть кодовых утверждений контракта (а)–(е) сверены r1 построчно с деревом `dev` на `cbf9318e` (`src/devices.ts:1402-1410`, `src/device-presentation.ts:210-224` + `src/ha-binding-status.ts:14-17,440-444`, `src/vacuum.ts:250-256`, `src/houseplan-editor-runtime.ts:4756`, `src/houseplan-card.ts:6109`→`_decorPointerDown`, `custom_components/houseplan/trails.py:252-274`) — ни одно не оказалось догадкой, выданной за факт. - AC2–AC6 — однозначны, способ доказательства (юнит/смок) назван, дельта их текст не трогает. - «Вне скоупа», «Откат», «Риски», «Release-артефакты» — не тронуты дельтой, корректны по r1. - **L1** (избыточное условие в контракте (г): вторая половина условия по инварианту `resolveHaBindingStatus` не добавляет новых случаев) — снята r1 с записью, не блокирует; дельта (г) не касалась. - **L2** (не все разделы §7.1 вынесены отдельными заголовками) — снята r1 с записью как необязательная рекомендация «на следующую правку»; ревизия 2 её не применила — это не нарушение, поскольку r1 явно зафиксировал находку как dismissed (не «to-do»), см. PROCESS.md: Low «либо правится, либо снимается с записью» — уже снята. ## Находки (этот раунд) ### Low — AC7 не станет буквально «grep по репо пуст» даже после точечной правки; положительная проверка не покрывает добавленный файл `docs/specs/369-audit-lows.md`, AC7 (строка 88-91): «(ж) grep «Matyushin» по репо пуст; pack.json/LICENSE.md/README.md несут «Sergey Matyunin»…». Два самостоятельных нюанса одной находки: 1. Позитивная часть AC7 («несут «Sergey Matyunin»») по-прежнему называет только три исходных файла и не упоминает добавленный `docs/FURNITURE.md` — асимметрично контракту (ж), который теперь называет четыре. Негативная часть («grep пуст») это транзитивно покрывает, так что асимметрия не создаёт дыру в доказательстве, только небольшую редакционную рассогласованность. 2. Буквально пустой repo-wide grep недостижим и после полной правки: сам этот файл ТЗ (`docs/specs/369-audit-lows.md`) в прозе контракта (ж) и в AC7 цитирует «Sergey Matyushin» как часть описания правки, `docs/reviews/ SPEC-REVIEW-369-r1.md` цитирует то же самое как историческую находку, и этот документ (SPEC-REVIEW-369-r2) продолжит цитировать обе формы имени после публикации в `docs/reviews/`. Ровно тот же класс дефекта AC уже разбирался в прецеденте `docs/reviews/CODE-REVIEW-269-r1.md` (Low, «буквальная формулировка AC2 не выполняется, хотя цель AC2 достигнута») — там тоже historical-документ ревью цитировал старый текст, и находка была снята без правки: архивный документ «описывает код, которого уже нет» (§7.3), переписывать его задним числом бессмысленно и запрещено духом канона. *Почему не блокирует:* цель AC7 — отсутствие живого расхождения имени в активных ассетах/документации (`assets/furniture/houseplan-0.3.0/**`, `docs/FURNITURE.md`), а не буквальный ноль совпадений во всём git-дереве включая архив ревью. Это то же различие буквы и духа AC, что и в #269. *Снимается без правки, с записью:* код-ревьюер этой задачи должен читать AC7 как «`grep -rn Matyushin` пуст вне `docs/specs/**` и `docs/reviews/**`» — не нужно заводить эту находку заново на код-ревью, ссылка на этот раздел и на прецедент #269 достаточна. High: 0. Medium: 0 (у обеих Medium из r1 — M1, M2, M3 — закрытие подтверждено выше построчно). ## Что проверено и корректно - Все три Medium из r1 (M1, M2, M3) закрыты текстом документа, не заявлением автора — см. таблицу «Закрытие раунда r1». - Дельта аддитивна и не пересекается текстуально ни с одним из унаследованных разделов — риск регрессии на уже подтверждённых AC (класс дефекта #102) отсутствует: правки не касаются формулировок AC2–AC6, контрактов (а)–(е), разделов «Риски»/«Release-артефакты»/«Вне скоупа»/«Откат». - Трейлеры коммита `c50d9e42` (`Issue: #369`, `User-Visible: no`) корректны для docs-коммита класса C. - Владелец вернул задачу в `S4-spec-review` после правки — процесс §2.4 соблюдён (комментарий `2026-08-29T07:41:51Z`). ## Чего не проверял - `typecheck`/`test`/`build`/`check-docs`/смоки/golden/инварианты модели — не запускал: за всю спек-стадию #369 (оба коммита, `cbf9318e` и `c50d9e42`) единственный тронутый файл — `docs/specs/369-audit-lows.md`; ни `src/**`, ни `custom_components/**/*.py` не задеты ни разу, гейтам нечего было бы доказать. - Не проверял живьём будущие юниты/смоки для (б)–(е) и (д) — их ещё нет, сверка «умеет ли тест падать» относится к код-ревью (§2.7). - Не проверял точную будущую формулировку абзацев VACUUM.md/USER-GUIDE — контракт (а) намеренно не фиксирует финальный текст, только смысл; это было явно принято в r1 и дельта этого не меняла. - Не проверял `docs/CONFIG-COMPATIBILITY.md` — ни один из семи пунктов не трогает конфигурацию/схему маркера (унаследовано из r1, дельта не расширяет скоуп). ## Вердикт Зелёный: обе стороны условия закрытия соблюдены — все три Medium из r1 (M1, M2, M3) закрыты предметно текстом документа, новых Medium/High не найдено. Одна Low-находка этого раунда (AC7: буквально недостижимый repo-wide grep, асимметрия позитивной проверки) снята без правки, с записью и ссылкой на прямой прецедент #269. Расхождение номера раунда в автоматике (задача звала себя «r1/0», фактически это «r2/1») зафиксировано отдельно выше — не находка по ТЗ, но стоит сообщить в процесс.