docs: input manifest and exact-candidate merge in TESTING.md and PROCESS.md

Issue: #492
User-Visible: no
This commit is contained in:
Codex
2026-09-08 21:55:07 +00:00
committed by claude[bot]
parent 32b1baa189
commit a358ed11fd
2 changed files with 74 additions and 5 deletions
+22
View File
@@ -933,6 +933,28 @@ S7-code-review → код-ревью → слияние в dev → S8-merged л
ветки и пушем автор мог запушить коммит, и слепой `--force` потерял бы его молча.
Расхождение lease — падение прогона, а не предупреждение.
**В `dev` уезжает точный кандидат, и только проверенный** (#492,
`scripts/merge-candidate.mjs`). Ревью длится десятки минут, `dev` за это время
двигается; ребейз после вердикта даёт дерево, которого никто не видел, — а чистый
ребейз ничего не доказывает: соседняя правка в `dev` меняет поведение без единого
конфликта. Шаг слияния поэтому:
- сверяет вершину ветки с материалом ревью (#312) — иначе `S6-in-progress`;
- если `dev` не двигался — push с `--force-with-lease` на текущую вершину;
- если двигался — ребейз (конфликт — `S6-in-progress`, как раньше), сравнение
patch-id проверенного и получившегося диффа (различие — `S7-code-review`: вердикт
к другому диффу не применим, §7.2), публикация кандидата в ветку задачи, ожидание
зелёного Validate **на этом SHA** и только затем push в `dev` с lease на ту
вершину, поверх которой кандидат собран. Отклонённый lease — `dev` двинулся снова
— новая попытка; после третьей — `S6-in-progress` с комментарием;
- красный Validate на кандидате или прогон, не появившийся за три минуты, —
`S6-in-progress` с ссылкой; `S8-merged` ставится только после push.
Проверка кандидата — обычный Validate ветки: лёгкий набор плюс диффозависимые
гейты. Тяжёлые гейты остаются за кандидатом релиза (#479): слияние не превращает
каждое движение `dev` в двадцатиминутный прогон, а проверяет ровно то, что
проверил бы пуш той же дельты.
Поэтому зелёное код-ревью с неудавшимся слиянием ведёт не в `S8-merged`, а в
`S6-in-progress`: работа действительно вернулась к автору, только осталась не
правка кода, а ребейз. Вердикт при этом в силе, переделывать нечего. После ребейза
+52 -5
View File
@@ -33,18 +33,65 @@
`--shard=i/4`), перед стабильным релизом и по понедельникам. Дешёвая половина
идёт с юнитами: `test/mutation-gate.test.mjs`. Локально для дельты задачи —
`node scripts/mutation-gate.mjs --changed origin/dev..HEAD`: гоняются только
мутанты, чьи patch-файлы или файлы гарда задеты диффом (#332, #475). Бандл
собирается только мутантам с браузерным гвардом; компиляция тестов в worktree
стартует с тёплого `test-build/` основного дерева.
мутанты, чьи patch-файлы или **входы гарда** задеты диффом (#332, #475, #492).
Входы гарда — это не только файлы, названные в команде: обёртка
(`scripts/*-guard.mjs`) объявляет запускаемые тесты в `export const
GUARD_INPUTS` (умолчание `backend-test-guard.mjs` —
`tests_backend/test_ha_import_export.py`, действует без третьего аргумента), а
от каждого файла гарда берётся замыкание импортов и путей: смок тянет
`demo/serve.mjs`, compat-хелперы и фикстуры, pytest-модуль — `conftest.py`.
`src/**` в замыкание не входит — это сторона патча (§6.4 ТЗ #492). Дифф,
трогающий сам реестр, дополнительно отбирает добавленные и изменённые
определения относительно реестра базы (`git show <base>:scripts/mutation-gate.mjs`).
Бандл собирается только мутантам с браузерным гвардом; компиляция тестов в
worktree стартует с тёплого `test-build/` основного дерева.
В CI `changed_mutants` добавляет `--ledger=<файл>` — журнал пойманных
свидетелей (#481): после каждого пойманного мутанта в файл пишется отпечаток
его входов (файлы патча и гарда, объявление мутанта; строка версии продукта
нормализована), и мутант с тем же отпечатком в следующем прогоне не гоняется.
его входов (файлы патча, все входы гарда по замыканию выше, объявление мутанта;
строка версии продукта нормализована), и мутант с тем же отпечатком в следующем
прогоне не гоняется.
Журнал живёт в кэше Actions по шарду, сохраняется при любом исходе шага, так
что отменённый пуш или таймаут не пропадают даром. Полный прогон и `--id`
журнал не читают; `--ledger` без `--changed` — ошибка.
## Manifest входов: какие job запускать и что хешировать (#492)
Один модуль, `scripts/check-inputs.mjs`, объявляет каждую проверку Validate
(`preflight`, `frontend`, `changed_mutants`, `integration`, `smoke`, `golden`,
`performance_smoke`, `backend`) через корни и точки входа, а остальное
вычисляет: от точек входа берётся замыкание — импорты транзитивно, строковые
пути как листья, каталог по строке — все текстовые файлы под ним. Из этого
manifest читают и `classify-changes.mjs` (job `changes`: job запускается,
если дифф задел хотя бы один её вход), и `gate-reuse.mjs` (ключ реюза =
хеш содержимого всех входов job). Два места не могут разойтись: до #492 у
бэкенда ключ не знал relay, converter и schema, а golden/perf — `serve.mjs`,
`demo.html` и compat-хелперов.
Правила, которые стоит знать:
- `validate.yml` — вход toolchain каждой job: правка workflow гоняет всё;
- `src/**` — вход только браузерных job; backend от UI не зависит, а
`custom_components/houseplan/manifest.json` (версия) держит правило «кандидат
релиза прогоняет всё» и для него;
- **неизвестный исполняемый вход** — файл под `scripts/`, `demo/`, `test/`,
`tests_backend/`, `.github/`, `custom_components/`, `src/`, которого нет в
manifest ни одной проверки, — расширяет прогон до полного набора и называется
в summary. Лист покрытия (`node scripts/check-inputs.mjs --coverage`,
`test/check-inputs.test.mjs`) требует, чтобы каждый такой файл был чьим-то
входом либо стоял в `NOT_AN_INPUT` с причиной: новый скрипт без записи —
красный юнит, не вечное расширение прогонов;
- `--check=<job>` печатает входы, `--why=<файл>` — цепочку, по которой файл
стал входом.
Отрицательные пробы (`test/gate-reuse.test.mjs`, `test/classify-changes.test.mjs`,
`test/check-inputs.test.mjs`) держат представителей каждой категории входов и
обратную пробу для UI ↔ backend; мутанты `manifest-drops-workflow-input`,
`classify-unknown-input-is-unaffected`, `reuse-backend-hashes-ui`,
`guard-inputs-ignore-wrapper-defaults`, `registry-diff-not-selected`,
`merge-pushes-unvalidated-candidate`, `merge-ignores-lease-rejection`,
`nightly-does-not-wait` держат сам протокол.
Чистые Python-контракты канонизации, которым не нужен Home Assistant, находятся
в `tests_backend/test_coordinate_canonicalization_pure.py`. Они обязаны реально
исполняться в локальном `pytest tests_backend`, а не исчезать за module-level