21 KiB
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 from52124529) - Заход: 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 на другой коммит.
- Прочитан
scripts/e2e-gate.mjsцеликом (149 строк):previousStable,isOurRun,classifyRun,e2eGate,realOps, CLI-обвязка. - Прочитаны
test/e2e-gate.test.mjs(12 тестов) иtest/release-workflow.test.mjs(2 теста) — сопоставлены с AC1–AC3, AC5. - Прочитан диф
.github/workflows/release.yml— положение нового шага, условие, токен. - Прочитаны три новых определения в
scripts/mutation-gate.mjs(release-ships-on-red-e2e,release-upgrades-stable-onto-itself,release-trusts-foreign-e2e-run). - Прочитан диф
PROCESS.md,AGENTS.md,docs/DEVELOPMENT.md,docs/TESTING.md,docs/specs/README.md— сверены с фактическим положением вставки (номера строк из ТЗ и хендоффа совпали). houseplan-e2e/.github/workflows/e2e.ymlполучен напрямую (gh api repos/Matysh/houseplan-e2e/contents/...), не со слов хендоффа: подтверждены jobplan(собирает матрицу,journeys-devтолько приschedule), jobe2eс именем"${{ matrix.suite }} · HP ${{ matrix.ref }} · HA ${{ matrix.ha }}",concurrency.cancel-in-progress: false.- Оба живых прогона, названных в хендоффе, проверены напрямую через
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(jobupgradeнесёт тег предыдущего 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отдаёт новые релизы первыми (реализация полагается на этот порядок без явной сортировки).
- Прогнаны тесты и мутанты (список — в разделе «Гейты»).
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самих jobe2e.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— прочитан целиком в контексте (jobgate/build,needs: gate), положение и условие нового шага верны; убедился, чтоbuildне может выполниться при падении шага (черезneeds: gate).scripts/mutation-gate.mjs— три новых определения прочитаны, мутации осмысленны (реально снимают заявленную защиту, а не косметическую строку), прогнаны лично — все поймано.houseplan-e2e/.github/workflows/e2e.yml— получен напрямую с GitHub, не из пересказа хендоффа; jobplan/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— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
47cb0575e5e31df27eb1d4c56e109a440a23011fgit log --all --format='%H %T' | grep 47cb0575e5e3 - ТЗ
docs/specs/514-e2e-stable-release-gate.md, блоб30795778fee11c8df610440dd86b745112e120c4git log --all --find-object=30795778fee11c8df610440dd86b745112e120c4 -- docs/specs/514-e2e-stable-release-gate.md - Вердикт конвейера:
green· High 0