From a358ed11fd46c3244600824e02c581dd37a4e63c Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 9 Sep 2026 00:18:22 +0300 Subject: [PATCH] docs: input manifest and exact-candidate merge in TESTING.md and PROCESS.md Issue: #492 User-Visible: no --- PROCESS.md | 22 +++++++++++++++++++ docs/TESTING.md | 57 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index 3146dda4..a1bf8552 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -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`: работа действительно вернулась к автору, только осталась не правка кода, а ребейз. Вердикт при этом в силе, переделывать нечего. После ребейза diff --git a/docs/TESTING.md b/docs/TESTING.md index 46ac2e51..d649214e 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -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 :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=` печатает входы, `--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