Files
houseplan-card/docs/reviews/SPEC-REVIEW-399-r1.md
T
2026-08-31 01:23:19 +00:00

195 lines
17 KiB
Markdown

# SPEC-REVIEW-399-r2
- Issue: https://github.com/Matysh/houseplan-card/issues/399
- ТЗ: `docs/specs/399-backend-gate-honesty.md`
- Материал: SHA `6d229c66f1e25460b6cc7efcbb67a88a723a3acb` (ветка
`issue/399-backend-gate-honesty`, ревизия 2 поверх `dev`)
- Заход: **r2**, не r1 — см. «Расхождение с заданием раунда» ниже
- Блокирующих циклов израсходовано: 1/4 (израсходован r1, жёлтый; зелёный
цикла не тратит, #227)
- Трек: полный (не изменился, наследуется из r1)
- Вердикт: **жёлтый**
## Расхождение с заданием раунда (нужно проговорить явно)
Задание на этот прогон присвоило заходу номер r1 и указало
«блокирующих циклов израсходовано 0 из 4». Это не соответствует
фактическому состоянию:
- `docs/reviews/SPEC-REVIEW-399-r1.md` уже существует в репозитории
(коммит `627a5359`) — это реальный, опубликованный документ ревью, а не
черновик;
- на issue #399 уже есть комментарий-вердикт `Вердикт: жёлтый · заход r1 ·
блокирующих циклов 0/4 · High: 0 · Medium: 1 → в задаче`, оставленный
ревьюером;
- автор ответил на этот вердикт комментарием «Ревизия 2 — Medium закрыт» и
запушил `6d229c66 docs: #399 spec revision 2 per SPEC-REVIEW-399-r1`,
который редактирует ровно ту находку, которую назвал r1.
То есть цикл r1 реально состоялся и стоил бюджета (жёлтый, 1/4). Присвоение
этому прогону имени `r1` создало бы `docs/reviews/SPEC-REVIEW-399-r1.md` поверх
уже существующего документа — ровно тот сценарий, от которого предостерегает
инструкция «два документа с одинаковым номером затёрли бы друг друга».
Поэтому документ и вердикт этого прогона используют номер **r2**, полученный
из фактического состояния issue и репозитория, а не из присланного
заголовка задания. Далее применяется процедура §2.9/§2.10 для «не первого»
цикла: делта, закрытие r1, наследование.
## Дельта, которая является предметом этого раунда
`git diff a1d7e0da..6d229c66 -- docs/specs/399-backend-gate-honesty.md`
— ровно три хунка:
1. строка «Ревизия» в шапке (2, с указанием, что правка отвечает на
SPEC-REVIEW-399-r1);
2. абзац контракта в разделе «(3) Low «в»» — добавлено явное описание
«второй половины дыры» (жёстко заданный массив `['validate.yml',
'mutation-gate.yml']`) и переформулирован контракт: перебор идёт по
**каталогу** `.github/workflows/*.yml`, для каждого файла ровно два
исхода (ставит зависимости бэкенда → пины; не ставит → доказывается явно,
не через отсутствие подстроки);
3. **AC5** переписан под тот же контракт: доказательство — тест на
синтетическом каталоге, где новый (третий) workflow ставит `pip install
pytest` без версий и обязан краснеть; плюс зафиксирован факт «сегодня в
репозитории девять workflow, установку python-зависимостей делают два».
Ничего за пределами этих трёх хунков не менялось: разделы про M3 (пин
фронтенда) и Low «а» (скоуп ruff), скоуп/не-скоуп, AC1–AC4/AC6, риски,
откат, release-артефакты — байт в байт те же, что видел r1.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где видно |
|---|---|---|
| Medium: AC5 не фиксирует, перебор по каталогу `.github/workflows/*.yml` или по жёстко заданному списку из двух имён — обе версии формально проходят AC4/AC5, но только первая закрывает цель issue буквально | Контракт п.(3) и AC5 переписаны: явно требуется перебор **каталога**, а не списка; для каждого файла — либо пины, либо явное («не ставит») доказательство отсутствия установки, не через отсутствие подстроки; доказательство AC5 теперь называет синтетический каталог с **третьим** workflow, которого в старом списке из двух не было бы | `docs/specs/399-backend-gate-honesty.md:82-94` (контракт), `:136-144` (AC5), SHA `6d229c66` |
Находка закрыта по существу: слабое прочтение (а) из r1 (список из двух
файлов, замена implicit `continue` на explicit skip-list **внутри тех же
двух**) текстом контракта теперь прямо исключено — новый p.(3) требует
перебора каталога, а не редактирования списка. Это единственная версия,
которую r1 признавал закрывающей issue буквально.
## Унаследовано из r1 (без повторной проверки)
Со ссылкой на `docs/reviews/SPEC-REVIEW-399-r1.md`, SHA `a1d7e0da`:
- §7.1: все обязательные разделы на месте (сценарий, что увидит человек,
проблема по пунктам, скоуп/не-скоуп, UX/данные/i18n — н/п обоснованно,
AC с доказательствами, план автотестов, риски, откат, release-артефакты)
— делта их не трогает, кроме плана автотестов (см. новую находку ниже).
- Классификация трека (полный, класс B, три несвязанные поверхности) —
делта не меняет ни поверхности, ни причину.
- Технические утверждения о разделах (1) M3 и (2) Low «а» — номера строк,
содержимое `tests_backend/requirements.txt:29`, `pyproject.toml:5`,
`.github/workflows/validate.yml:787-788` — делта их не касается, r1 их
построчно сверил.
- AC1, AC2, AC3, AC4, AC6 — текст не менялся этой ревизией, доказательства
оценены r1 как однозначные.
- Продуктовое обрамление и персоны SCOPE.md (задача инфраструктурная,
J-строка не затронута) — не изменилось.
- «Чего не проверял» из r1 (сеть для `package_constraints.txt`, объём
ruff-долга, аудит-документ вне репозитория) — остаётся в силе, делта
этих вопросов не касается.
## Новая находка этого раунда
### Medium (в скоупе) — «План автотестов» (п.4) не обновлён вслед за AC5 и описывает как раз ту слабую версию, которую контракт теперь запрещает
**Файл**: `docs/specs/399-backend-gate-honesty.md:155` (раздел «План
автотестов», пункт 4 списка Unit-тестов).
**Текст сейчас**: «Явный список обходимых файлов зафиксирован; добавление
нового файла в workflows без обновления списка краснеет (AC5).»
**В чём разрыв**: это дословно формулировка **старой** (ревизия 1) версии
AC5 — «перечисление файлов, которые проверка обходит, зафиксировано в самом
тесте» — которую сам автор в этой же ревизии заменил на «перебор каталога, а
не фиксированного списка имён» (строка 137). Пункт 4 плана автотестов не
редактировался в этой ревизии (его нет в диффе), и он по-прежнему описывает
работу через **список**, который требуется вручную **обновлять** при
появлении нового файла — то есть ту самую конструкцию, о которой новый
контракт п.(3) говорит: «доказывается явно… а не выводится из отсутствия
подстроки [и не из членства в списке]».
Смысловая разница не косметическая. Реализация, которая берёт «План
автотестов» как техническое задание для теста (а не прозу AC5 несколькими
абзацами выше), может на законных основаниях построить: `readdirSync` +
хардкод-массив «эти N файлов не ставят python-зависимостей» + фейл, если имя
нового файла не найдено ни в списке пиновых, ни в списке-исключений. Такая
реализация проходит буквальный текст п.4 («список… краснеет при отсутствии
файла в нём»), но именно она возвращает эксплуатационную нагрузку —
необходимость вручную поддерживать список — которую контракт п.(3) и
переписанный AC5 сознательно убрали («доказывается явно… а не выводится из
отсутствия подстроки»). Расхождение между двумя разделами одного и того же
ТЗ — тот же класс дыры, что и находка r1: AC/раздел проходятся двумя
существенно разными реализациями, только на этот раз конфликт не внутри
одного AC, а между «Критериями приёмки» и «Планом автотестов».
Это не гипотетическая тонкость: п.4 — единственное место в документе, где
явно описано, ЧТО проверяет unit-тест для AC5, и оно противоречит только что
исправленному AC5 в этом же файле.
**Почему не High**: реализация всё равно проходит написанные тесты в любом
из двух прочтений, продуктового кода нет, откат тривиален. Это вопрос
формулировки одного ТЗ-раздела, а не невозможности выполнить задачу.
**Почему Medium, а не Low**: буквальный текст, на который будет смотреть
исполнитель при написании теста, указывает в сторону реализации, которую сам
документ (двумя разделами выше) явно отверг. Это создаёт тот же риск
«формально выполнено, но не решает то, из-за чего заведено», который r1 уже
один раз поймал в AC5 — здесь тот же риск переехал в соседний раздел
вследствие правки, а не был там с начала (в ревизии 1 AC5 и п.4 плана
автотестов формулировались одинаково слабо и не противоречили друг другу;
конфликт появился именно этой правкой). Правится в этой же задаче (#202 не
применяется — находка в скоупе).
**Как закрыть**: одна строка. Например: «4. Синтетический каталог
`.github/workflows/*.yml`, где один из файлов не входит в набор
`validate.yml`/`mutation-gate.yml`, ставит `pip install pytest` без версий и
не имеет явного маркера «не устанавливает python-зависимости» — тест
краснеет; никакого списка имён для ручного обновления в реализации нет
(AC5).» Дёшево: правка одного предложения, план автотестов приводится в
соответствие с уже переписанным AC5.
## Что проверено и корректно (в дополнение к унаследованному)
- Новый факт в AC5 «в репозитории девять workflow, установку
python-зависимостей делают два» — перепроверен на текущем дереве:
`ls .github/workflows/*.yml` даёт ровно 9 файлов; `git grep -l "pip
install" .github/workflows/*.yml` даёт ровно `mutation-gate.yml` и
`validate.yml`. Число и состав совпадают дословно.
- Переписанный контракт п.(3) и AC5 внутренне непротиворечивы друг другу
(оба говорят про каталог, оба про «явное доказательство отсутствия
установки», оба про мутанта с третьим workflow) — конфликт только с планом
автотестов, описанным выше.
- Правка не меняет трек, скоуп/не-скоуп, риски и не переносит вопрос
владельцу — расширение контракта («перебор каталога») лежит внутри уже
согласованной цели issue («отсутствие обеих зацепок — отказ, а не
пропуск»), это техническое уточнение, а не продуктовое решение (§7.1: не
выносится владельцу).
- Заголовок «Ревизия 2» корректно ссылается на SPEC-REVIEW-399-r1 —
трассировка причины правки на месте.
## Чего не проверял
- Сетевое содержимое `package_constraints.txt` тега `2026.8.3`
`home-assistant/core` (претензия M3) — как и в r1, недоступно в этой
среде; не блокирует, AC1 требует доказательства без сети, число будет
видно на код-ревью.
- Объём ruff-долга при исходе (2) для Low «а» — не факт, требующий проверки
на этом этапе; делта его не касается.
- `npm test`/`npx tsc --noEmit`/`npm run build` — это ревью документа
(ТЗ), а не кода; правка не трогает `src/**`/`custom_components/**`, гейты
кода к этому этапу не относятся и будут прогнаны на код-ревью.
- Аудит-документ `AUDIT-2026-08-31-v1700beta1.md` вне репозитория — как и
в r1, не найден локально, тело issue самодостаточно.
## Вердикт
Жёлтый: единственная блокирующая находка — Medium, без High. Прежняя
Medium-находка (AC5: список vs каталог) закрыта; ревизией 2 введена новая
Medium-находка того же семейства в соседнем разделе («План автотестов»,
п.4). Автор приводит формулировку п.4 в соответствие с переписанным AC5 и
проходит ещё один заход ревью ТЗ (полный трек, лимит 4 цикла, израсходован
1/4 после r1; после этого раунда — 2/4).