mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Волна 5 эпика #674, перенос ТЗ (класс C). Из 240 файлов `docs/specs/` в `legacy/specs/` уехали 219: на них не ссылается ни один живой файл (код, тесты, скрипты, workflow, документы вне архива и ревью). Остались 21 ТЗ — на которые ссылаются код, ADR, ISOMETRIC, SUN, RADAR, LIGHT (`docs/specs/067`), DECOR-EDITOR, support-relay, и те, на которые ссылаются они сами; README каталога объясняет, где искать остальное. Открытых issue с файлом ТЗ среди перенесённых нет. Относительные ссылки перенесённых файлов переписаны (`../X` → `../../docs/X`, соседние оставшиеся ТЗ → `../../docs/specs/…`) — все 26 резолвятся. Попутно: битая ссылка в `089-isometric-view-stage1.md:8` на удалённый `089-isometric-view.md` — теперь команда `git show` по истории. Строка в `legacy/README.md`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
105 lines
14 KiB
Markdown
Executable File
105 lines
14 KiB
Markdown
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. Скоуп
|
||
|
||
1. `scripts/e2e-gate.mjs` (новый) — запуск и ожидание E2E на теге (§4).
|
||
2. `release.yml`, job `gate` — шаг «Require green E2E on a real Home Assistant for a stable release» после Full Performance (§5).
|
||
3. `houseplan-e2e/.github/workflows/e2e.yml` — сьют `journeys-dev` только по расписанию (§6).
|
||
4. Документация (§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`.
|
||
|
||
1. `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»; ассет не публикуется.
|
||
2. Опознание своего прогона: `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`.
|
||
3. Ожидание завершения `tracked` до `totalMs` (45 мин), опрос каждые 20 с. `success` → `green`; `failure`/`timed_out` → `red`; `cancelled` → `red` с пометкой «отменён вручную» (concurrency-группа dispatch в `e2e.yml` не отменяет — `cancel-in-progress: false`, значит отмена рукотворная); таймаут → `red`.
|
||
4. 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 }}`:
|
||
|
||
```yaml
|
||
- 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`) не получает ассеты, пока dispatch `e2e.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` невозможен по событию; владелец перезапускает job `gate` кнопкой «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` имеет `repo` scope (пушит в `dev` и переставляет метки) — dispatch в соседний репозиторий владельца им доступен; иначе — `E2E_DISPATCH_TOKEN`.
|
||
- Имя job в `e2e.yml` с `HP ${{ matrix.ref }}` — стабильный контракт для опознания; тест в houseplan-e2e его не пинит, поэтому фраза фиксируется в README e2e.
|