# CODE-REVIEW — issue #619, заход r1 Материал: `origin/dev..HEAD`, вершина `0d047450ce2dca2062c3fdd038092abc221173af` (рабочая копия соответствует, `git status` чист). Трек — инфраструктурный (§1): ни один изменённый файл не относится к классу A (`src/**`, `custom_components/houseplan/**/*.py`, манифесты, i18n не тронуты) — `.github/workflows/validate.yml`, `scripts/ci-proof.mjs`, `scripts/release-gate.mjs`, `docs/DEVELOPMENT.md`, три тестовых файла — это классы B/C. Ускоренный вход `S7-code-review` без ТЗ применён правомерно. ## Скоуп задачи Дефект из аудита: на `main` после fast-forward с `dev` диапазон классификации `merge-base(origin/dev, HEAD) == HEAD` даёт пустой диф → тяжёлые job (`smoke`, `golden`, `performance_smoke`) пропускаются → `ci-proof.mjs` требует полный proof и получает `skipped` → release-gate падает на самом свежем (и единственно судимом) прогоне, хотя более старый прогон того же коммита был зелёным. Владелец принял два из трёх предложенных исправлений: 1. `main` классифицируется как `dev` (полный набор, без фильтра путей); 2. `release-gate.mjs`/`ci-proof.mjs` перебирают все прогоны точного SHA и принимают любой зелёный, а не только самый новый. E2E retry/window (третий вариант) сознательно не тронут — это заявлено в хендоффе и не входит в обязательные AC. ## Как проверялось - Прочитан весь diff (`git diff origin/dev...HEAD`), построчно разобраны `scripts/ci-proof.mjs` (`selectCiProofVerdict`) и `scripts/release-gate.mjs` (`classifyValidateProofs`), а также правки `validate.yml` (шаги `base` и `classify` в job `changes`, шаг «Новый код не добавляет any» в job `frontend`). - Дешёвые гейты не перегонялись целиком: Validate на `0d047450` зелёный (https://github.com/Matysh/houseplan-card/actions/runs/35813271933), этого прогона достаточно для `tsc`/`npm test`/`npm run build`/сверки бандла. - Точечно прогнан изменённый тестовый материал: `node --test test/release-gate.test.mjs test/ci-proof.test.mjs test/validate-workflow.test.mjs` → 50/50 зелёных, включая три новых теста под #619. - Дисциплина «тест умеет падать»: временно откатил `scripts/ci-proof.mjs`, `scripts/release-gate.mjs` и `.github/workflows/validate.yml` к версиям `origin/dev` и перегнал те же три файла — упали ровно 4 теста (три новых под #619 плюс проверка текста `docs/DEVELOPMENT.md`), 46 остались зелёными. Рабочая копия восстановлена в исходное состояние сразу после проверки (`git status --porcelain` пуст). - `node scripts/smoke-select.mjs --base origin/dev --head HEAD` → «Исполняемого frontend-диффа нет … Browser-smoke этим диффом не выбираются». Смоки не запускал — инструмент подтверждает, что выбирать нечего (diff не касается `src/**`). - `node scripts/check-docs.mjs` не запускал: diff не трогает `src/**`, условие «любая правка фронтенда» не выполняется. - Инварианты модели (`npm run invariants`) не запускал: diff не касается геометрии, толщины стен, `layout`, `marker.space`, `open_spans`. - `golden:verify`, `pytest tests_backend` — не запускал: рендер и backend не тронуты. - Трейлеры коммита: `Issue: #619`, `User-Visible: no` — присутствуют и верны (изменение процесса CI, видимого поведения продукта нет); changelog-файлы в diff отсутствуют, что и требуется при `User-Visible: no`. ## Разбор по AC **AC1** (`release-gate.test.mjs`: новейший прогон failed из-за пропущенного smoke, старший — green → вердикт green). Доказано тестом `#619: any complete green proof on the exact SHA survives a newer failed duplicate` (release-gate.test.mjs:114) и вспомогательным `selectCiProofVerdict([red, newerFull]).status === 'green'` (ci-proof.test.mjs:136). Прочитан механизм: `classifyValidateProofs` теперь идёт по прогонам от новых к старым и останавливается (`break`) только когда находит `status === 'green'` (было: останавливался на первом не-`cancelled`/не-`stale`, то есть на самом решающем и необязательно зелёном). `selectCiProofVerdict` фильтрует `cancelled`/`stale`, среди оставшихся ищет `green`; если находит — отдаёт его вне зависимости от позиции в списке; если нет — откатывается к самому новому решающему (то же fail-closed поведение, что раньше). Корректность подтверждена и падением тестов на отклоненном коде (см. выше). **AC2** (на `main` тяжёлые job не пропускаются из-за пустого диффа). Доказано тестом `#619: Validate на main раскрывает полный набор так же, как на dev` (validate-workflow.test.mjs:181): проверяет, что паттерн `[ "$REF" = "refs/heads/dev" ] || [ "$REF" = "refs/heads/main" ]` встречается дважды в job `changes` (шаги `base` и `classify`) и что в job `frontend` шаг «Новый код не добавляет any» использует ту же пару условий для выбора `PROVEN_BASE`. Прочитан код: шаг `classify` при `REF` = `dev` или `main` вызывает `classify-changes.mjs --all` (все флаги true, включая триггер для `smoke`/`golden`/`performance_smoke` через `needs: [changes, frontend, reuse]`) — пропуска по пустому диффу для `main` больше нет. Шаг `base` считает `range_base` тем же путём для `main`, что и раньше только для `dev`, и это тот самый доказанный предок, который берёт «новый код не добавляет any», а не `HEAD..HEAD`. **AC3** (первый стабильный релиз после слияния проходит с первой попытки — зафиксировать в issue). Не может быть доказан до реального релиза после merge; автор явно пометил его как «проверяется постфактум» и не выдал результат за готовый. Это корректно для этого раунда: AC1/AC2 — код и тесты, AC3 — наблюдение, которое существует только после слияния и следующего релиза. Не блокирует ревью, но зафиксировано как открытый пункт, подлежащий проверке позже (в issue или в S8 после факта). ## Что проверено и корректно - Классы файлов — только B/C, ускоренный infra-трек применён по правилам. - Комментарии в коде и `docs/DEVELOPMENT.md` синхронно объясняют новое поведение (`#619` проставлен и там и там), формулировка «Any complete green full proof … is sufficient» дословно совпадает с regex-проверкой в `release-gate.test.mjs:132`. - Безопасность нового допущения «принять любой зелёный на SHA»: `sha`/`tree` candidate сверяются внутри `evaluateCiProof` для каждого прогона отдельно, так что «зелёный, но для другого дерева» не может проскочить — Git SHA однозначно определяет дерево, а `evaluateCiProof` их сверяет явно (`candidate: { sha, tree }`). Риск «стащить чужой зелёный proof» не подтвердился. - Изменение эффекта на `review`/`merge`-потребителей: `docs/DEVELOPMENT.md` прямо говорит, что они используют ту же `missing`/`pending`/… state machine — новая семантика «любой зелёный на SHA побеждает» действует одинаково для всех трёх потребителей, это осознанное и документированное расширение, не побочный эффект. - Стоимость: полный Validate на `main` дороже прежнего пустого прогона, но `main` пушится только при релизном fast-forward — редко, риск принят и назван в хендоффе. ## Чего не проверял и почему - Полный `npx tsc --noEmit` / `npm test` / `npm run build` — зелёный прогон Validate на этом самом SHA уже это доказал. - `check-docs.mjs`, инварианты модели, `golden:verify`, backend pytest, performance-профили, браузерные смоки — diff их не касается (`src/**`, геометрия, backend, рендер не тронуты); выбор подтверждён `smoke-select.mjs`. - AC3 — по определению не проверяется в этом раунде (см. выше). ## Итог Оба обязательных AC (AC1, AC2) доказаны тестами, которые умеют падать (это проверено вручную откатом кода). Изменение сфокусировано на дефекте из аудита, не расширяет скоуп, задокументировано, трейлеры верны. Блокирующих находок нет. **Вердикт: зелёный** --- ## Материал раунда - Ветка: `issue/619-release-gate-main-proof`, коммит `0d047450ce2d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `d4073f7337335628d277195b3e561393ca63d582` ``` git log --all --format='%H %T' | grep d4073f733733 ``` - Тело issue: `b87774de2903ecd45752cc104e9f482c95e51fd3920252ae985c38166ea8d74a` - Вердикт конвейера: `green` · High 0