Two findings from the live run on v1.73.0 (houseplan-e2e run 34393136097): the upgrade job carries the previous stable's tag in its name, so "any job with HP <tag>" let a gate for v1.72.0 adopt the v1.73.0 run — recognition now keys on `journeys`/`first-run`; and the first poll after a dispatch sees only the matrix-planning job, which marked the run as foreign forever — a run without any `· HP … ·` job is undecided and polled again. Live: v1.73.0 → green with the run link, v1.72.0 → no run of its own. Issue: #514 User-Visible: no
14 KiB
Executable File
#514 — E2E на реальном HA как гейт стабильного релиза
- Issue: https://github.com/Matysh/houseplan-card/issues/514
- Тип / приоритет: infra, process / P2
- Трек: полный — две поверхности (
release.yml+ скрипт гейта здесь;e2e.ymlвhouseplan-e2e), плюс секрет владельца - Оценка: ценность 8/10 (единственная проверка релиза на настоящем HA перед HACS); сложность 3/10; риск 2/10 (ложный красный задерживает stable, не ломает ничего)
- Связано: #511 (release-gate по последнему прогону), #510 (validate-gate — образец ожидания dispatch-прогона), houseplan-e2e (13 сценариев, 08–09.09), AUD-159B7-02 (ассет ждёт гейта)
1. Проблема
Стабильный релиз проходит Validate и Full Performance на точном SHA, но ни разу не запускается в настоящем Home Assistant: боковая панель, роли, рестарт HA, обновление интеграции проверяются только ночным прогоном houseplan-e2e на «последней бете», результат которого никто не ждёт и который не привязан к тегу. Между последней бетой и stable бывают коммиты (v1.73.0 = beta.9 + два коммита бюджетов) — stable уходит в HACS непроверенным.
1.1. Сценарий
Владелец публикует stable-релиз (как сейчас: тег → GitHub Release с ассетами → release.yml). Гейт release.yml для stable дополнительно запускает e2e.yml в houseplan-e2e на этом теге и ждёт зелёного; красный — ассет не публикуется, в логе гейта ссылка на прогон E2E с трейсами. Беты — без изменений.
1.2. Что человек увидит до и после
Пользователь HACS — ничего (кроме того, что stable, который падает на реальном HA, до него не доедет). Владелец — один дополнительный шаг в job Гейт (~4–6 мин) и его ссылка в логе.
2. Скоуп
scripts/e2e-gate.mjs(новый) — запуск и ожидание E2E на теге (§4).release.yml, jobgate— шаг «Require green E2E on a real Home Assistant for a stable release» после Full Performance (§5).houseplan-e2e/.github/workflows/e2e.yml— сьютjourneys-devтолько по расписанию (§6).- Документация (§8).
3. Не-скоуп
- Беты, nightly, ревью-конвейер, merge-candidate — E2E там не запускается.
- Новые сценарии E2E, матрица HA beta для гейта (гейт — только
ha_version=stable). - Автоматическая публикация stable (остаётся ручной/скриптовой, как сейчас).
4. scripts/e2e-gate.mjs
Чистая функция e2eGate({ tag, ops, appearMs, totalMs, pollMs }) поверх инъектируемых ops (образец — validate-gate.mjs #510) и realOps({ repo: 'Matysh/houseplan-e2e', workflow: 'e2e.yml', token }) на gh.
ops.dispatch(tag, upgradeFrom)→gh workflow run e2e.yml --repo Matysh/houseplan-e2e --ref main -f houseplan_ref=<tag> -f upgrade_from=<предыдущий stable> -f ha_version=stable.upgradeFrom— новейший не-пре-релиз, не черновик, с тегом ≠<tag>(gh release list --repo Matysh/houseplan-card); нет такого —stable. Уточнение после живого прогона в S6 (09.09): к моментуrelease: publishedсам тег — уже «stable», иupgrade_from=stableзаставлял сьютupgradeобновлять v1.73.0 на v1.73.0 (Expected: not "1.73.0"). Ошибка запуска (403 — токен безactions: writeнаhouseplan-e2e) →result=errorс текстом «нужен секретE2E_DISPATCH_TOKENс правом Actions: write на houseplan-e2e»; ассет не публикуется.- Опознание своего прогона:
ops.listRuns()(gh run list --workflow e2e.yml --event workflow_dispatch --json databaseId,status,conclusion,url,createdAt --limit 10) → кандидаты сcreatedAt ≥ t0 − 60 с; для каждогоops.jobs(id)— прогон наш, если job сьюта, ставящего сам тег (journeys · HP <tag> · …илиfirst-run · HP <tag> · …), названа по нему (имя job вe2e.ymlнесётmatrix.ref; уupgradeэтоupgrade_from— тег предыдущего stable, по нему опознавать нельзя: гейт для v1.72.0 принял бы прогон v1.73.0 — живой прогон 09.09). Прогон, у которого пока видна только job «Матрица прогона», — не чужой, а ещё не решённый: опрос повторяется. Первый подошедший —tracked; чужие dispatch (владелец запустил руками другой тег) игнорируются. Не появился заappearMs(3 мин) →missing. - Ожидание завершения
trackedдоtotalMs(45 мин), опрос каждые 20 с.success→green;failure/timed_out→red;cancelled→redс пометкой «отменён вручную» (concurrency-группа dispatch вe2e.ymlне отменяет —cancel-in-progress: false, значит отмена рукотворная); таймаут →red. - CLI:
node scripts/e2e-gate.mjs --tag=<tag> [--repo=Matysh/houseplan-e2e], печатаетresult=,url=,note=(и в$GITHUB_OUTPUT), код выхода 0 только наgreen. Константы 3/45 мин — изmerge-candidate.mjs(VALIDATE_APPEAR_MS,VALIDATE_TOTAL_MS).
5. release.yml
Job gate, после «Require full performance for a stable release», с тем же if: ${{ !github.event.release.prerelease }}:
- name: Require green E2E on a real Home Assistant for a stable release
if: ${{ !github.event.release.prerelease }}
env:
GH_TOKEN: ${{ secrets.E2E_DISPATCH_TOKEN || secrets.HP_PROCESS_TOKEN }}
TAG: ${{ github.event.release.tag_name }}
run: node scripts/e2e-gate.mjs --tag="$TAG"
Ассет houseplan.zip к моменту события published уже приложен (release-prerelease.mjs и ручной stable-рецепт публикуют из черновика с ассетами; release-zip.yml лишь перекладывает), поэтому E2E ставит ровно те байты, что скачает HACS. Если ассета нет (публикация мимо рецепта) — install-houseplan.mjs падает с «no houseplan.zip asset», гейт красный с этой строкой в логе E2E: это правильный отказ, не флейк.
Токен: HP_PROCESS_TOKEN — classic PAT со scope repo+workflow (им конвейер пушит в dev), он даёт workflow_dispatch на все репозитории владельца; секрет E2E_DISPATCH_TOKEN — запасной вход на случай fine-grained токена. Проверка — первый stable после слияния; до него — dispatch release.yml невозможен (событие release), поэтому шаг проверяется тестом на текст и живым прогоном скрипта с gh владельца в хендоффе (node scripts/e2e-gate.mjs --tag=v1.73.0 → green/red по факту).
6. houseplan-e2e/e2e.yml
Сьют journeys-dev (снимок dev) не относится к тегу и может краснеть по причинам, не связанным со stable: job получает if: matrix.suite != 'journeys-dev' || github.event_name == 'schedule'. Остальные три сьюта на dispatch с houseplan_ref=<tag>: journeys и first-run — тег, upgrade — с предыдущего stable (upgrade_from вычисляет гейт, §4 п.1) на тег. Условие «только по расписанию» для journeys-dev реализуется job plan, собирающей матрицу: job-level if не читает matrix.* (первая правка упала на парсинге workflow). Отдельный коммит в houseplan-e2e (там процесс не ведётся; ссылка на коммит — в хендоффе).
7. Тесты и мутанты
test/e2e-gate.test.mjs(новый, fake ops с снимками иjobsById): dispatch и ожидание; чужой dispatch безHP <tag>в именах job игнорируется, свой отслеживается; red на failure; missing поappearMs; red поtotalMs;cancelled→ red с пометкой; ошибка dispatch →errorс текстом про секрет.test/release-workflow.test.mjs(новый): шаг есть, стоит после Full Performance, условие!prerelease, токен с фолбэком, вызываетscripts/e2e-gate.mjs --tag.- Мутанты (
scripts/mutation-gate.mjs, гардnode --test test/e2e-gate.test.mjs):release-ships-on-red-e2e(failure читается как green),release-trusts-foreign-e2e-run(опознание по имени job снято — любой dispatch считается своим),release-upgrades-stable-onto-itself(upgrade_fromвсегдаstable). Каждый — отрицательным прогоном штатным раннером.
8. Документация
PROCESS.md §8 «Гейт стабильного релиза»: «+ E2E на реальном HA зелёный на теге (houseplan-e2e)». docs/DEVELOPMENT.md §релиз (строка про Full Performance): E2E, что делать при красном (открыть прогон, трейсы в артефактах, починить → новый тег). AGENTS.md/docs/TESTING.md: одна строка про место E2E. README houseplan-e2e: «dispatch с тегом — гейт stable в houseplan-card».
9. Совместимость и откат
Беты не затронуты. Откат — удалить шаг из release.yml; e2e-gate.mjs без вызова безвреден.
10. Критерии приёмки
- AC1. Stable-релиз (
!prerelease) не получает ассеты, пока dispatche2e.ymlна этом теге не завершилсяsuccess; красный/отсутствующий/отменённый прогон и ошибка запуска → гейт красный с понятной причиной и ссылкой (тестыe2e-gate,release-workflow). - AC2. Гейт опознаёт свой прогон по
HP <tag>в именах job и не принимает чужой dispatch (тест + мутантrelease-trusts-foreign-e2e-run). - AC3. Пре-релизы (
prerelease: true) шаг не выполняют (условие в yml, тест). - AC4.
journeys-devне бежит на dispatch (коммит в houseplan-e2e, ссылка в хендоффе; проверка — dispatch e2e.yml наv1.73.0показывает 3 job). - AC5. Все три мутанта §7 пойманы штатным раннером.
- AC6. Документы §8 обновлены;
User-Visible: no; UX/i18n/модель данных/перф не затронуты.
10.0. UX, модель данных, i18n
Не затрагиваются: изменения только в workflow, скриптах CI и документации.
10.1. Риски и меры
- E2E-флейк задержит stable → повторный запуск
release.ymlневозможен по событию; владелец перезапускает jobgateкнопкой «Re-run failed jobs» (release-gate ждёт по последнему прогону, #511, для e2e — новый dispatch). - Токен без прав → гейт краснеет с явной инструкцией про
E2E_DISPATCH_TOKEN, а не молчит. - Параллельный ручной dispatch E2E владельцем на другой тег — не мешает опознанию (AC2).
11. Затронутые файлы
scripts/e2e-gate.mjs (новый), .github/workflows/release.yml, scripts/mutation-gate.mjs, test/e2e-gate.test.mjs (новый), test/release-workflow.test.mjs (новый), PROCESS.md, docs/DEVELOPMENT.md, docs/TESTING.md, AGENTS.md, docs/specs/README.md; в houseplan-e2e: .github/workflows/e2e.yml, README.md.
12. Принятые предположения
HP_PROCESS_TOKENимеетreposcope (пушит вdevи переставляет метки) — dispatch в соседний репозиторий владельца им доступен; иначе —E2E_DISPATCH_TOKEN.- Имя job в
e2e.ymlсHP ${{ matrix.ref }}— стабильный контракт для опознания; тест в houseplan-e2e его не пинит, поэтому фраза фиксируется в README e2e.