Files
houseplan-card/docs/reviews/CODE-REVIEW-514-r1.md
T
2026-09-09 21:18:51 +00:00

21 KiB
Raw Blame History

CODE-REVIEW-514-r1

  • Issue: https://github.com/Matysh/houseplan-card/issues/514 — «E2E на реальном HA как гейт стабильного релиза»
  • ТЗ: docs/specs/514-e2e-stable-release-gate.md, ревью ТЗ зелёное (docs/reviews/SPEC-REVIEW-514-r1.md, r1, High 0/Medium 0)
  • Материал ревью: 395b0e38f4b3e34071e2bcf236440bf476b2c75a (рабочая копия на нём, HEAD detached from 52124529)
  • Заход: r1 · блокирующих циклов израсходовано 0 из 4
  • Вердикт: зелёный

Скоуп ревью

Диапазон origin/dev..HEAD — 5 коммитов, ровно issue #514 (fd4f1077 ТЗ, e226ac56 документ ревью ТЗ, 9b9e306b/5e9fe662/395b0e38 реализация и две правки по живым прогонам). Файлы только класса B (scripts/**, test/**, .github/workflows/release.yml) и C (docs/**); класс A не задет — src/**, custom_components/** не тронуты. User-Visible: no на всех пяти коммитах, что верно: изменение не видно ни пользователю HACS, ни пользователю карточки — только владельцу в логе гейта.

Проверялись все 6 AC ТЗ, три заявленных мутанта, соответствие houseplan-e2e/e2e.yml контракту, который scripts/e2e-gate.mjs полагает существующим, и трейлеры/процесс-гейт.

Как проверялось

Материал ревью — код на точном SHA 395b0e38, без git fetch/pull/checkout на другой коммит.

  1. Прочитан scripts/e2e-gate.mjs целиком (149 строк): previousStable, isOurRun, classifyRun, e2eGate, realOps, CLI-обвязка.
  2. Прочитаны test/e2e-gate.test.mjs (12 тестов) и test/release-workflow.test.mjs (2 теста) — сопоставлены с AC1–AC3, AC5.
  3. Прочитан диф .github/workflows/release.yml — положение нового шага, условие, токен.
  4. Прочитаны три новых определения в scripts/mutation-gate.mjs (release-ships-on-red-e2e, release-upgrades-stable-onto-itself, release-trusts-foreign-e2e-run).
  5. Прочитан диф PROCESS.md, AGENTS.md, docs/DEVELOPMENT.md, docs/TESTING.md, docs/specs/README.md — сверены с фактическим положением вставки (номера строк из ТЗ и хендоффа совпали).
  6. houseplan-e2e/.github/workflows/e2e.yml получен напрямую (gh api repos/Matysh/houseplan-e2e/contents/...), не со слов хендоффа: подтверждены job plan (собирает матрицу, journeys-dev только при schedule), job e2e с именем "${{ matrix.suite }} · HP ${{ matrix.ref }} · HA ${{ matrix.ha }}", concurrency.cancel-in-progress: false.
  7. Оба живых прогона, названных в хендоффе, проверены напрямую через gh api, а не приняты на слово:
    • gh api repos/Matysh/houseplan-e2e/actions/runs/34393136097/jobs → Матрица прогона, journeys · HP v1.73.0 · HA stable, first-run · HP v1.73.0 · HA stable, upgrade · HP v1.72.0 · HA stable (conclusion всех success) — подтверждает AC4 (3 сьюта, без journeys-dev) и обоснованность правки в isOurRun (job upgrade несёт тег предыдущего stable, не тег под тестом).
    • gh api repos/Matysh/houseplan-e2e/actions/runs/34392391382/jobs → upgrade · HP stable · HA stable = failure — подтверждает заявленную причину появления previousStable (обновление v1.73.0 на себя же).
    • gh release list --repo Matysh/houseplan-card --json tagName,createdAt,isPrerelease — подтверждает допущение previousStable, что gh release list отдаёт новые релизы первыми (реализация полагается на этот порядок без явной сортировки).
  8. Прогнаны тесты и мутанты (список — в разделе «Гейты»).
  9. node scripts/process-gate.mjs --range origin/dev..HEAD — «гейт пройден, предупреждений 0».

Гейты — что прогнано и почему

Гейт Результат Основание
npx tsc --noEmit, npm test, npm run build + сверка бандла не перегонялись Validate зелёный на этом точном SHA (395b0e38, https://github.com/Matysh/houseplan-card/actions/runs/34394268282) — сошлись на этом прогоне
node --test test/e2e-gate.test.mjs 12/12 green, прогнан лично новый файл, доказывает AC1/AC2
node --test test/release-workflow.test.mjs 2/2 green, прогнан лично новый файл, доказывает AC1/AC3
node --test test/release-contract.test.mjs test/performance-workflow.test.mjs test/validate-workflow.test.mjs 34/34 green, прогнан лично регресс-проверка: новый шаг в release.yml не сломал соседние тесты по этому же файлу
node scripts/mutation-gate.mjs --id=release-ships-on-red-e2e поймано 1 из 1 защитный AC1/AC5 — гейт не должен читать failure как green
node scripts/mutation-gate.mjs --id=release-upgrades-stable-onto-itself поймано 1 из 1 защитный AC5 — upgrade_from не должен всегда быть stable
node scripts/mutation-gate.mjs --id=release-trusts-foreign-e2e-run поймано 1 из 1 защитный AC2/AC5 — гейт не должен принимать чужой dispatch
node scripts/smoke-select.mjs --base origin/dev --head HEAD «исполняемого frontend-диффа нет … выбирать нечего» src/**/*.ts не тронут — смоки проверяют собранную карточку, её нет в этом диффе
npm run golden:verify не прогонялся diff не меняет рендер/визуал (нет src/**)
node scripts/check-docs.mjs не прогонялся diff не трогает src/** — условие «прогонять» не выполнено
node scripts/model-invariants.mjs не прогонялся геометрия/layout/толщина/marker.space не затронуты
python -m pytest tests_backend -q не прогонялся custom_components/**/*.py не тронут
node scripts/process-gate.mjs --range origin/dev..HEAD «гейт пройден, предупреждений 0», прогнан лично трейлеры/имя ветки/докладка small

Полный список смоков (ls demo/smoke_*.mjs | wc -l) не запрашивался — инструмент выбора уже прямо ответил «выбирать нечего», уточнять число бессмысленно.

Защитные AC — таблица «чем краснеет» (§2.7)

AC Чем доказан Чем краснеет
AC1 (красный/отсутствующий/отменённый/ошибка dispatch → гейт красный) test/e2e-gate.test.mjs (7 тестов), test/release-workflow.test.mjs мутант release-ships-on-red-e2e (снята проверка conclusion === 'success') — поймано 1/1, прогнано лично
AC2 (опознание своего прогона, игнор чужого) test/e2e-gate.test.mjs (4 теста), живой прогон 34393136097 (v1.72.0-гейт не принял бы run v1.73.0) мутант release-trusts-foreign-e2e-run (isOurRun всегда true) — поймано 1/1, прогнано лично
AC3 (пре-релизы шаг не выполняют) test/release-workflow.test.mjs (regex на if:) чтением: единственная точка контроля — if: ${{ !github.event.release.prerelease }} в самом yml, тест сверяет буквальную строку; отдельного мутанта на YAML-условие в репозитории для соседних гейтов тоже нет (см. Require full performance)
AC4 (journeys-dev не бежит на dispatch) коммит в houseplan-e2e + живой прогон 34393136097 (3 job, без journeys-dev) чтением + живым прогоном: job.plan собирает матрицу и добавляет journeys-dev только if (schedule) — код прочитан напрямую через gh api, не со слов хендоффа; мутанта нет, но у houseplan-e2e нет собственного процесса код-ревью с мутантами (внешний репозиторий)
AC5 (все три мутанта пойманы) штатный mutation-gate.mjs --id=… прогнано лично, все три «поймано 1 из 1»
AC6 (документация, User-Visible: no) чтением дифа PROCESS.md/AGENTS.md/DEVELOPMENT.md/TESTING.md/specs/README.md + трейлеры коммитов не защитный AC (расположение/текст) — таблица не требуется по исключению §2.7

Находки

Не найдено ни одной High- или Medium-находки.

Low (снимаю без правки — не искажает ни один AC, не создаёт риска пропуска красного гейта):

  • L1. Если job plan в e2e.yml сама завершится с failure до того, как заведётся хотя бы одна job вида · HP … · (например, транзиентная ошибка в тривиальном node -e-скрипте планирования матрицы), classifyRun вернёт 'unknown', а поскольку прогон уже completed, код e2eGate (строка if (kind === 'foreign' || candidate.status === 'completed') foreign.add(...)) навсегда добавит его в foreign и продолжит ждать. Результат — missing через appearMs вместо диагностичного «наш прогон упал на планировании матрицы». Инвариант «не публиковать ассеты на красном» не нарушается: missing — это тоже небезопасный (не-green) результат, шаг release.yml падает, build не выполняется. Единственная цена — менее понятное сообщение владельцу в этом маловероятном сценарии (сам скрипт планирования — детерминированный node -e без внешних вызовов). Не блокирует, вне рамок AC (AC1 требует «красный/отсутствующий/… → гейт красный с причиной», а не конкретный текст причины для этого под-случая).
  • L2 (унаследовано из ревью ТЗ, переносится без повторной проверки). Таймаут totalMs = 45 мин взят из merge-candidate.mjs, а не выведен из timeout-minutes: 40 самих job e2e.yml — даёт 5 минут запаса, чего достаточно (уже отмечено ревью ТЗ как L2, снято без правки).

Проверено чтением и/или исполнением — по каждому файлу

  • scripts/e2e-gate.mjs — прочитан целиком, логика прослежена вручную по всем ветвям (green/red/missing/cancelled/error, чужой/свой прогон, ещё не решённый прогон) и подтверждена тестами + мутантами.
  • test/e2e-gate.test.mjs, test/release-workflow.test.mjs — прогнаны лично, зелёные; содержание тестов сверено построчно с тем, что они утверждают доказать (не только «зелёный», но и что именно проверяет каждый assert).
  • .github/workflows/release.yml — прочитан целиком в контексте (job gate/build, needs: gate), положение и условие нового шага верны; убедился, что build не может выполниться при падении шага (через needs: gate).
  • scripts/mutation-gate.mjs — три новых определения прочитаны, мутации осмысленны (реально снимают заявленную защиту, а не косметическую строку), прогнаны лично — все поймано.
  • houseplan-e2e/.github/workflows/e2e.yml — получен напрямую с GitHub, не из пересказа хендоффа; job plan/e2e прочитаны целиком.
  • Живые прогоны 34393136097 и 34392391382 — сверены напрямую через gh api, не приняты на слово хендоффа.
  • PROCESS.md, AGENTS.md, docs/DEVELOPMENT.md, docs/TESTING.md, docs/specs/README.md — прочитаны в диффе, соответствуют факту (проверено предыдущими пунктами: положение шага, имя job, токен, поведение journeys-dev).
  • Трейлеры всех 5 коммитов (Issue: #514, User-Visible: no) — проверены git show -s --format=full, корректны.
  • node scripts/process-gate.mjs --range origin/dev..HEAD — «гейт пройден, предупреждений 0».

Чего не проверял

  • tsc/npm test (полный)/npm run build — не перегонял, полагаюсь на зелёный Validate этого точного SHA (ссылка выше), как разрешает правило «дешёвые гейты уже подтверждены».
  • Реальную работоспособность HP_PROCESS_TOKEN/E2E_DISPATCH_TOKEN на workflow_dispatch в houseplan-e2e при событии release: published — недоступно ревьюеру (нет прав на секреты, событие release нельзя сэмулировать без публикации настоящего релиза). ТЗ и хендофф это честно называют «первая живая проверка — следующий stable»; путь фейл-safe (403 → красный шаг с текстом про секрет, что покрыто тестом #514 AC1: a dispatch refused by the token…).
  • Полный набор мутантов mutation-gate.mjs (весь файл, 8000+ строк) — прогонял только три новых по --id, не весь --only/полный корпус: задача добавляет ровно три мутанта, остальные к диффу не относятся и это предрелизный, а не ревью-гейт.
  • golden:verify, check-docs.mjs, model-invariants.mjs, pytest tests_backend — не прогонялись: diff не затрагивает src/**, геометрию или custom_components/**/*.py (см. таблицу гейтов и обоснование там же).
  • Полный список demo/smoke_*.mjs через wc -l не запрашивал — smoke-select.mjs прямо ответил «выбирать нечего».

Что проверено и корректно

  • Все 6 AC ТЗ пронумерованы и выполнены, каждый доказан тестом/мутантом/чтением с указанием, чем именно.
  • Три заявленных мутанта реально снимают заявленную защиту и пойманы штатным раннером (1 из 1 каждый), прогнано лично, не принято на слово хендоффа.
  • Контракт имени job в houseplan-e2e/e2e.yml ("${suite} · HP ${ref} · HA ${ha}"), на который опирается isOurRun, подтверждён прямым чтением workflow чужого репозитория, а не пересказом.
  • Два живых прогона, на которые ссылается хендофф, подтверждены напрямую через gh api — job-состав и conclusion совпадают с заявленным.
  • previousStable полагается на порядок gh release list (новые первыми) — порядок подтверждён прямым вызовом на реальном репозитории, а не одним лишь тестом с придуманным порядком входа.
  • Трейлеры, имя ветки, User-Visible: no + отсутствие изменений в changelog (верно — no user-visible изменение) — корректны, process-gate.mjs подтверждает.
  • Диапазон изменений строго в рамках скоупа ТЗ (§2): scripts/e2e-gate.mjs, release.yml, mutation-gate.mjs, два новых тестовых файла, документация. Скоуп не расширен, попутных правок «раз уж я здесь» не обнаружено.
  • Одно число/один источник — неприменимо: диф не добавляет и не меняет ни одной пользователем видимой величины.

Продуктовое рассуждение

Задача закрывает не строку docs/SCOPE.md напрямую (это инфраструктура процесса релиза, не поверхность продукта), а укрепляет процесс, которым продукт поставляется в HACS: раньше houseplan-e2e существовал и был зелёным, но stable-релиз уходил пользователям, не дождавшись его. Теперь дождётся. Это ровно то, что заявлено в issue и ТЗ, без расширения и без ухудшения смежного поведения (беты не тронуты, что подтверждено условием if и тестом AC3).

Вопросы владельцу

Нет. Открытых продуктовых вопросов не осталось — единственный технический вопрос из тела issue (нужен ли E2E_DISPATCH_TOKEN) снят автором фолбэком на уровне кода, реализация фейл-safe при отсутствии прав.


Материал раунда

  • SHA материала: 395b0e38f4b3e34071e2bcf236440bf476b2c75a
  • Диапазон: origin/dev..HEAD (5 коммитов)
  • ТЗ: docs/specs/514-e2e-stable-release-gate.md, ревью ТЗ: docs/reviews/SPEC-REVIEW-514-r1.md (зелёное, r1)
  • Заход: r1 · это первый заход код-ревью, унаследовать нечего

Материал раунда

  • Ветка: issue/514-e2e-stable-release-gate, коммит 5212452994ce — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 47cb0575e5e31df27eb1d4c56e109a440a23011f
    git log --all --format='%H %T' | grep 47cb0575e5e3
    
  • ТЗ docs/specs/514-e2e-stable-release-gate.md, блоб 30795778fee11c8df610440dd86b745112e120c4
    git log --all --find-object=30795778fee11c8df610440dd86b745112e120c4 -- docs/specs/514-e2e-stable-release-gate.md
    
  • Вердикт конвейера: green · High 0