mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -0,0 +1,146 @@
|
||||
# CODE-REVIEW-595-r1
|
||||
|
||||
**Issue:** [#595](https://github.com/Matysh/houseplan-card/issues/595) — Тест #573 краснеет на любом коммите приёмки эталонов: заглушка `loadContext` не моделирует `Baseline-Reviewed` run.
|
||||
**Класс:** B (инфраструктура/гейты) — ни одного файла класса A. Вход сразу на `S7-code-review` (#562), спецификация не требуется, AC заданы в теле issue.
|
||||
**Заход:** r1 · блокирующих циклов израсходовано 0 из 4.
|
||||
**Материал:** `eb145aba485fe62d363298bb4b09d33bd0bb096e` (рабочая копия пинована на этом SHA; `git fetch`/`checkout` на другой коммит не выполнялся).
|
||||
**Diff:** `scripts/mutation-registry.mjs` (+12), `test/release-gate.test.mjs` (+72/-4). Никаких изменений в `src/**`, `custom_components/**`, `dist/**`.
|
||||
|
||||
## Скоуп
|
||||
|
||||
Дефект: `test/release-gate.test.mjs` строил ожидания composite-evidence (`own`) из
|
||||
**живого** `HEAD` через `candidateExpectations`, а `localEvidence` читает
|
||||
`baselines.reviewedRun` из последнего сообщения коммита. Пока HEAD — обычный
|
||||
коммит, поле `null`; как только HEAD несёт трейлер `Baseline-Reviewed:`
|
||||
(коммит приёмки golden-эталонов), поле становится непустым, и
|
||||
`evaluateCiProof` законно требует объявленный прогон, которого тестовая
|
||||
заглушка `loadContext` не отдавала. Тест падал на **любой** ветке, чья
|
||||
вершина — коммит приёмки эталонов (воспроизведено автором на
|
||||
`issue/594-form-kit-room@68bb7558`, 2776/1). Продуктовый код (`scripts/ci-proof.mjs`,
|
||||
`scripts/release-gate.mjs`) не менялся и не должен был — контракт «объявленный
|
||||
прогон обязан существовать, быть завершённым, не отменённым, быть Validate»
|
||||
именно то, что задумано #573.
|
||||
|
||||
Правка: зелёный путь теста `#573` теперь считается на evidence с явно
|
||||
обнулённым `baselines.reviewedRun`, независимо от того, чем оказался HEAD.
|
||||
Контракт объявленного прогона вынесен в отдельный тест `#595` и проверяется с
|
||||
трёх сторон (завершённый неотменённый Validate с `conclusion: 'failure'` →
|
||||
green; прогона нет → failed; прогон отменён → failed). Добавлен мутант
|
||||
`declared-baseline-review-run-never-checked` на защиту `scripts/ci-proof.mjs`.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
Диапазон `origin/dev..HEAD` содержит один коммит и не трогает `src/**` —
|
||||
браузерные смоки, golden, backend pytest, perf-профили и geometry-invariants
|
||||
не выбираются диффом (`node scripts/smoke-select.mjs --base origin/dev --head HEAD`
|
||||
подтвердил: «Исполняемого frontend-диффа нет… Browser-smoke этим диффом не
|
||||
выбираются»). `check-docs` не требуется — отпечаток скриншотов не мог устареть,
|
||||
диффа в `src/**` нет.
|
||||
|
||||
**Дешёвые гейты уже подтверждены зелёным Validate на этом самом SHA** —
|
||||
перепроверено напрямую (`gh api repos/Matysh/houseplan-card/actions/runs/35425580065`
|
||||
→ `status: completed, conclusion: success, head_sha: eb145aba…`), поэтому
|
||||
`npx tsc --noEmit`, `npm test`, `npm run build` не перегонялись повторно.
|
||||
|
||||
| Гейт | Результат | Как |
|
||||
|---|---|---|
|
||||
| typecheck/test/build (Validate) | green | ссылка на прогон 35425580065, SHA сверен вручную — совпадает |
|
||||
| `node --test test/release-gate.test.mjs` (локально, на материале) | 10 pass / 0 fail | выполнено в этой сессии |
|
||||
| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | «нечего выбирать» | выполнено в этой сессии |
|
||||
| `node scripts/process-gate.mjs --issues` | гейт пройден, WARN п.8 (инфра-трек #562, статус до S7 не нужен) | выполнено в этой сессии, `gh` доступен в песочнице |
|
||||
| `node scripts/mutation-gate.mjs --check` (весь реестр, включая новый id) | все `ok`, включая `declared-baseline-review-run-never-checked` | выполнено в этой сессии |
|
||||
| golden / backend / браузерные смоки / perf | не прогонялись | diff не трогает `src/**`, `custom_components/**/*.py`; smoke-select подтвердил «нечего выбирать» |
|
||||
|
||||
### AC → доказательство (перепроверено независимо, не со слов автора)
|
||||
|
||||
| AC | Заявлено | Проверено ревьюером |
|
||||
|---|---|---|
|
||||
| **AC1** — тест зелёный и на ветке с `Baseline-Reviewed:` на вершине, и без него | автор привёл 8/1 → 10/0 на временной фикстуре, снесённой после замера | **Воспроизведено независимо** в отдельном `git worktree` (не в материале ревью): пустой коммит с трейлером `Baseline-Reviewed: …/actions/runs/35407468491` поставлен вершиной; на нём: старый (`origin/dev`) `test/release-gate.test.mjs` — **8 pass / 1 fail**, `AssertionError: 'failed' !== 'green'`, текст `Baseline-Reviewed run 35407468491 is missing, cancelled or not a Validate run` — то есть баг реален и воспроизводится не только на исходной ветке автора; новый `test/release-gate.test.mjs` на том же коммите — **10 pass / 0 fail**. Worktree удалён (`git worktree remove --force`), рабочая копия материала не пострадала (`git status` — чисто, HEAD не сдвигался) |
|
||||
| **AC2** — контракт проверяется обеими сторонами: существующий Validate → green, отсутствующий → failed с текстом про `Baseline-Reviewed run` | тест `#595`, 3 фикстуры | прочитан код теста и `evaluateCiProof` (scripts/ci-proof.mjs:295-306): `conclusion: 'failure'` в фикстуре — законный случай (`status==='completed' && conclusion !== 'cancelled'`, без требования `success`), совпадает с продуктовым контрактом. Прогнано локально — все три under-фикстуры дают заявленный статус |
|
||||
| **AC3** — зелёный путь не читает `reviewedRun` из HEAD | `withoutReviewed()` явно зануляет поле перед использованием в `#573`; `#595`-тест переопределяет `expected.baselines.reviewedRun` литералом, не читая его из HEAD | подтверждено чтением: оба места явно не зависят от фактического сообщения HEAD-коммита. Дополнительно подтверждено воспроизведением AC1 выше — тест зелёный именно тогда, когда HEAD **несёт** трейлер, то есть путь через `localEvidence(HEAD)` не мог быть источником зелёного результата |
|
||||
| **AC4** — гейты + мутант ловит подмену | `npm test`/`typecheck`/`build` зелёные (хендофф), мутант 1/1 | typecheck/test/build — через Validate на этом SHA (см. таблицу выше); мутант перепроверен вручную в этой сессии двумя способами: (а) ручной патч `scripts/ci-proof.mjs` (`const declared = null;`) + `node --test --test-name-pattern="#595"` → **AssertionError: expected 'failed', actual 'green'** на кейсе «прогона нет»; (б) `node scripts/mutation-gate.mjs --check` — весь реестр включая новый id `ok` |
|
||||
|
||||
### Защитный AC — таблица «чем краснеет» (§2.7)
|
||||
|
||||
| AC | Чем доказан | Чем краснеет |
|
||||
|---|---|---|
|
||||
| Объявленный `Baseline-Reviewed` прогон обязателен к резолву | `node --test --test-name-pattern="#595" test/release-gate.test.mjs` | Мутация `scripts/ci-proof.mjs`: `const declared = proof.evidence.baselines?.reviewedRun ?? null;` → `const declared = null;`. Воспроизведено вручную в этой сессии (см. AC4) — тест `#595` краснеет (`expected 'failed', actual 'green'` на кейсе с отсутствующим прогоном), а не просто заявлен автором |
|
||||
| Зелёный путь `#573` не зависит от содержимого HEAD | воспроизведение AC1 (worktree с фиктивным `Baseline-Reviewed` на вершине) | старый тест на этой фикстуре краснеет (8/1), новый — зелёный (10/0); граница показана прогоном, а не чтением |
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- Диапазон коммитов и трейлеры: один коммит, `Issue: #595`, `User-Visible: no` —
|
||||
верно, поведение продукта не меняется. `User-Visible: no` не требует правок
|
||||
changelog — оба файла не тронуты, это корректно.
|
||||
- Заявление «продуктовый код исправен» подтверждено: `scripts/ci-proof.mjs` и
|
||||
`scripts/release-gate.mjs` не входят в diff; контракт `evaluateCiProof`
|
||||
(строки 295-306 `ci-proof.mjs`) действительно не требует `success` от
|
||||
объявленного прогона — фикстура с `conclusion: 'failure'` в новом тесте
|
||||
соответствует реальности, а не поблажке теста.
|
||||
- Существующее покрытие `evaluateCiProof` в `test/ci-proof.test.mjs` (неверный id,
|
||||
неверный workflow, отменённый прогон — строки 311-325) не дублируется новым
|
||||
тестом: `#595` в `release-gate.test.mjs` бьёт по другому слою — фикстуре
|
||||
`loadContext`/`classifyValidateProofs`, где жил именно этот баг. Разделение
|
||||
ответственности между файлами тестов оправдано.
|
||||
- `process-gate.mjs --issues` подтверждает, что задача корректно распознана как
|
||||
инфраструктурная (#562): WARN, а не ошибка, статусная метка не требуется.
|
||||
- Реестр мутаций синтаксически корректен и интегрирован — `mutation-gate.mjs
|
||||
--check` проходит по всему реестру без падений после добавления записи.
|
||||
- Диагностика бага в issue (##Что сломано / Почему) точно соответствует коду:
|
||||
`localEvidence` (`ci-proof.mjs:103-127`) действительно читает
|
||||
`baselines.reviewedRun` из `git log -1 --format=%B <rev>`, а
|
||||
`candidateExpectations` (`release-gate.mjs:18-26`) действительно считает
|
||||
`own` только на checkout самого кандидата — ровно то место, которое правка
|
||||
меняет.
|
||||
|
||||
## Находки
|
||||
|
||||
Нет находок High или Medium. Скоуп задачи полностью закрыт минимальной правкой
|
||||
теста и мутационного якоря; изменение не расширяет и не сужает скоуп issue.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- **Golden, браузерные смоки, backend pytest, perf-профили, geometry-invariants** —
|
||||
diff не трогает `src/**`, `custom_components/**/*.py`, геометрию или ссылки на
|
||||
неё; `smoke-select.mjs` подтвердил «нечего выбирать». Прогон был бы пустой
|
||||
тратой времени, а не тщательностью.
|
||||
- **`npm run inventory`** — число тестов не менялось в отчёте вручную (не
|
||||
копировалось в документ), в документ вставлен только фактический вывод
|
||||
`node --test`, полученный в этой сессии.
|
||||
- **Полный `npm test`/`npx tsc --noEmit`/`npm run build`** заново не гонялись —
|
||||
засчитан зелёный Validate на точном SHA `eb145aba…` (сверено GitHub API
|
||||
напрямую, не со слов хендоффа). Локально прогнан только целевой файл
|
||||
`test/release-gate.test.mjs`, поскольку именно он — предмет правки.
|
||||
- **`npm run docs:accept`/`check-docs.mjs`** — не требуется, diff не в `src/**`.
|
||||
- Воспроизведение бага и мутанта выполнено во **временном `git worktree`**, не
|
||||
в материале ревью: рабочая копия на `eb145aba` не сдвигалась и не менялась
|
||||
(`git status` — чисто до и после), worktree удалён по завершении проверки.
|
||||
|
||||
## Вердикт
|
||||
|
||||
Зелёный. AC1-AC4 доказаны исполнением, а не заявлением — в том числе
|
||||
независимым воспроизведением исходного бага и подтверждением, что новый тест
|
||||
и его мутационный якорь действительно способны краснеть. Скоуп соответствует
|
||||
issue, класс B подтверждён, трейлеры корректны, лишних гейтов не прогонялось.
|
||||
|
||||
---
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- **Ветка/SHA:** `eb145aba485fe62d363298bb4b09d33bd0bb096e` (HEAD рабочей копии на момент ревью, detached).
|
||||
- **Дерево:** `git rev-parse HEAD^{tree}` на момент ревью соответствует рабочей копии; расхождений с материалом не найдено (`git status` чисто на всём протяжении ревью).
|
||||
- **Диапазон:** `origin/dev..HEAD` — 1 коммит.
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/595-release-gate-reviewed-run-fixture`, коммит `eb145aba485f` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `75ffd0790d06ec566a719305f13afe271807617f`
|
||||
```
|
||||
git log --all --format='%H %T' | grep 75ffd0790d06
|
||||
```
|
||||
- Тело issue: `2a34c60ce3779c75b08a16df990b4b0f4bdefc31396d9c5a81eb9fb71752a516`
|
||||
- Вердикт конвейера: `green` · High 0
|
||||
Reference in New Issue
Block a user