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

17 KiB
Raw Blame History

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).