Files
houseplan-card/docs/reviews/CODE-REVIEW-571-r1.md
T
claude[bot] 0de14ead71
Проверка (CI) / Классификация изменённых файлов (push) Successful in 18s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 31s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 22s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 22s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 43s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Successful in 2m39s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 5m47s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 7m38s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 16s
docs: review document for #571
Issue: #571
User-Visible: no
2026-09-17 20:59:57 +00:00

176 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CODE-REVIEW — issue #571, заход r1
Материал: `f77bdaf52eb1f5b8cec205d55eaf58e17e3ba288`, ветка `issue/571-golden-capture-provenance`,
два коммита на `origin/dev` (`3f95dac4`, `f77bdaf5`). Инфраструктурный трек: класс A
(`src/**`, `custom_components/houseplan/**/*.py`, манифесты) не тронут ни строкой —
проверено `git diff origin/dev...HEAD --name-only` с фильтром по этим путям, список пуст.
## Скоуп
Проблема из аудита: `demo/golden/accept.mjs` не знал, на какой платформе сняты кадры,
и записывал в `baselines-index.json` **свою** платформу под именем `platform` —
что и дало на `ad4000f9` ложь `"platform": "win32"` у кадров Linux-прогона
34853080375. Задача переносит провенанс съёмки в отчёт и разводит `capturedOn`/
`acceptedOn` в индексе. Продуктового поведения не меняет, `docs/CHANGELOG*` не
трогает — согласуется с `User-Visible: no` в обоих коммитах.
## Как проверялось
1. Прочитан весь diff (`git diff origin/dev...HEAD`) по файлам:
`scripts/capture-environment.mjs`, `demo/golden/policy.mjs`, `demo/golden/accept.mjs`,
`demo/golden/run.mjs`, `scripts/mutation-registry.mjs`,
`test/capture-environment.test.mjs`, `test/golden-capture-provenance.test.mjs`,
`docs/images/screenshots.json`, бандл (`dist/**`, `custom_components/houseplan/frontend/**`).
2. Прослежена логика `accept.mjs` построчно для всех пяти сценариев приёмки из
тела issue (см. таблицу ниже) — доказательство чтением плюс сверка с
автотестами, которые эти же сценарии исполняют.
3. **Тесты не просто прочитаны — прогнаны лично**:
`node --test --test-name-pattern="#571" test/golden-capture-provenance.test.mjs`
→ `7 pass / 0 fail`. Тесты запускают настоящий `accept.mjs` в изолированном
каталоге эталонов (`--baselines=<tmp>`), не подменяют модуль.
4. **Дисциплина «тест умеет падать» проверена сама, не со слов автора.** Вручную
внесены обе мутации из `scripts/mutation-registry.mjs` (сохранял и
восстанавливал файлы, рабочее дерево осталось чистым — `git status --short`
пуст после проверки):
- `demo/golden/accept.mjs`: `capturedOn: capturedOn,` → `capturedOn: acceptance.platform,` —
упали 2 из 7 тестов (AC1 и «чужая среда с причиной»);
- `scripts/capture-environment.mjs`: fail-closed throw в `reportCaptureProvenance`
заменён на `return { provenance: null, legacy: true }` — упал целевой тест
`#571 схема 2 fail-closed…`.
Оба мутанта убиты, ровно теми тестами, что названы в `guard` соответствующей
записи реестра.
5. Сверены неизменные инварианты: `imageSha256` во всех 11 записях
`docs/images/screenshots.json` не менялся (`git diff ... | grep -E '^[+-].*imageSha256'`
пусто) — изменился только `sourceFingerprint`/`sourceSha256`, что подтверждает
заявление «пересъёмка не потребовалась, картинки байт-в-байт совпали».
Аналогично не менялись `demo/golden/baselines/**` (нет ни одного файла в диффе) —
PNG эталоны не трогались, поэтому трейлеры `Release:`/`Baseline-Reviewed:` не
требуются.
6. `node scripts/smoke-select.mjs --base origin/dev --head HEAD` →
«Исполняемого frontend-диффа нет… Browser-smoke этим диффом не выбираются».
Смоки не прогонялись — выбирать нечего, диффа в `src/**` нет.
7. Проверено, что `docs-accept.mjs` (приёмка скриншотов документации) не задета
и не несёт аналогичного дефекта: там нет поля `capturedOn`, есть только
`acceptedOn` (платформа приёмщика, честно так и названная) — предположение
автора о возможном родственном разрыве не подтвердилось, отдельный issue не
нужен.
8. Проверены трейлеры обоих коммитов (`git log`): `Issue: #571`,
`User-Visible: no` — оба присутствуют, changelog не тронут, что для `no`
корректно.
### Сценарии приёмки из issue — прослежены по коду
| # | Сценарий | Где в коде | Вердикт |
|---|---|---|---|
| 1 | Linux-отчёт принят на Windows с причиной: `capturedOn=linux`, `acceptedOn=win32`, причина в манифесте | `accept.mjs:45-75`, цикл по `[capturedOn, acceptance.platform]` + `foreignAllowed` | подтверждено тестом `#571 AC1: чужая среда съёмки с причиной` |
| 2 | Тот же сценарий без причины — отказ до записи | тот же цикл, `throw` до `mkdirSync(baselineRoot)`/`writeFileSync` | подтверждено тестом `#571 AC2`, включая проверку «индекс не тронут» |
| 3 | Same-platform без override, обе стороны корректны | `foreignAllowed` вычисляется как `null`, когда обе платформы — канон | подтверждено тестом `#571 AC1: артефакт Linux принимается` |
| 4 | Тампер report/PNG и неполный артефакт — fail-closed | не менялось (`sourceFingerprint`-проверка и `goldenAcceptanceRefusal`) | подтверждено тестом `#571 подмена PNG и неполный артефакт` |
| 5 | `npm test`; адресный тест policy/acceptance | см. п.3 выше плюс общий Validate | зелёный прогон на этом SHA (см. ниже) |
## Гейты — что прогнано, что нет и почему
- **Не прогонял заново** `npx tsc --noEmit`, `npm test` (полный), `npm run build`
со сверкой бандла: Validate на `f77bdaf5` уже зелёный
([run 35270925670](https://github.com/Matysh/houseplan-card/actions/runs/35270925670)) —
сошлись на этом прогоне как источнике для этих трёх гейтов.
- **Прогнал сам, адресно**: `node --test --test-name-pattern="#571" test/golden-capture-provenance.test.mjs`
(7/7 pass) плюс обе ручные мутации (см. выше) — потому что дисциплина
«тест умеет падать» касается именно тех тестов, на которые опирается
вердикт, а не всего набора.
- **`node scripts/check-docs.mjs --strict`** — не перепрогонял: diff не трогает
`src/**` (условие явно снимает обязательность), и та же проверка уже часть
зелёного Validate на `f77bdaf5`. Косвенно подтверждено чтением: `imageSha256`
не изменился ни для одной из 11 сцен, значит `check-docs` не мог покраснеть
из-за визуальных данных.
- **Инварианты модели** (`npm run invariants`) — не запускал: diff не трогает
геометрию, `layout`, `marker.space`, `open_spans` или толщину стен. Неприменимо.
- **Browser-smokes** (`demo/smoke_*.mjs`) — не прогонял: `smoke-select.mjs`
подтвердил отсутствие исполняемого frontend-диффа (`src/**` не тронут).
- **`npm run golden:verify` / golden-джоба** — не прогонял и не требовалось:
PNG-эталоны не менялись, изменения — в контракте приёмки, покрытом
собственными юнит-тестами, реально исполняющими `accept.mjs`. Автор
корректно отметил, что реальная приёмка после мержа сама запишет
честный индекс схемы 2.
- **`python -m pytest tests_backend`** — не прогонял: `custom_components/**/*.py`
не тронут.
- **Perf-профили** — неприменимо, в AC не названы и код не тронут.
- **`mutation-gate --check`** — не перепрогонял целиком (автор указал 0 FAIL
в хендоффе), но сами два новых мутанта проверил вручную (см. выше) —
это и есть содержательная часть гейта для этой задачи.
## Проверено и корректно
- Провенанс собирается там, где снимаются кадры (`captureProvenance` в
`scripts/capture-environment.mjs`, вызывается из `demo/golden/run.mjs`),
а не выводится приёмщиком из своего `process.platform` — ровно то, что
требует AC2 issue.
- `reportCaptureProvenance` — честный fail-closed: схема ≥2 без `capture`
или без `capture.platform` бросает исключение с явной причиной; схема <2 —
отдельная явная ветка `legacy: true`, `capturedOn` уезжает `null`, а не
угадывается. Мутационно проверено.
- Отказ по чужой среде происходит **до** любой записи в индекс — `throw`
расположен раньше `mkdirSync(baselineRoot)` и `writeFileSync(manifestPath)`
по тексту файла; тест `#571 AC2` дополнительно сверяет байт-в-байт, что
индекс не изменился.
- Причина осознанного обхода (`HP_ALLOW_FOREIGN_CAPTURE`) едет в
`foreignCapture.reason` индекса, а не только в stdout — закрывает второй
провал из аудита («причина осталась в stdout»).
- `indexCapturedOn()` читает обе схемы одним правилом и не путает
`platform` (схема 1) с `capturedOn` (схема 2) — юнит-тест перебирает все
четыре случая, включая «схема 2 без `capturedOn`» → `null`, а не откат
к чужому полю.
- Отмена запрета «`run.mjs` не трогать» (#455) внесена и оплачена в той же
ветке: бандл пересобран (класс D, `dist/**` и
`custom_components/houseplan/frontend/**` в диффе), индекс скриншотов
документации переснят вторым коммитом с сохранением всех 11 `imageSha256`
— не переложено на будущую задачу.
- Свидетели гоняют настоящий `accept.mjs`, а не мок: `--baselines=<dir>`
сделан ровно для того, чтобы «отказ до записи» была исполнимым
утверждением, а не декларацией.
- Трейлеры `Issue:`/`User-Visible: no` на обоих коммитах корректны,
changelog не тронут, что для `no` ожидаемо.
## Чего не проверял (и почему это не блокирует)
- Полный `npm test` / `tsc` / `build` лично не гонял — Validate на этом SHA
уже зелёный, дублирование было бы потерей времени без нового сигнала.
- Реальную golden-приёмку в CI (запись индекса схемы 2 «в бою») не
выполнял — она в скоуп задачи и не входит, и юнит-тесты покрывают тот же
код путём, включая изоляцию от рабочего дерева.
- Интеграционный путь `witnessCheck.refusal` → `environmentNote` в
`accept.mjs` (строки с `previousIndex`/`indexCapturedOn(previousIndex)`)
проверен только чтением, не исполнением: специального теста, гоняющего
именно отказ по свидетелям с последующей запиской о среде, в диффе нет.
Логика — прямой перенос имён (`environment.platform` → `capturedOn`,
`manifest.platform` → `indexCapturedOn(previousIndex)`) без изменения
формы; читается корректно, но это не доказательство исполнением. Не
выношу как находку: ветка не новая (существовала и до задачи в похожем
виде), риск регрессии низкий, а полноценный тест этого пути стоил бы
отдельной инфраструктуры (два последовательных прогона приёмки с разными
средами) непропорционально размеру задачи.
## Находки
Нет ни High, ни Medium. Задача решает ровно проблему из аудита, не
расширяет и не сужает скоуп, все AC подтверждены автотестами, исполненными
лично, две мутации проверены руками поверх заявления автора.
## Вердикт
Зелёный.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/571-golden-capture-provenance`, коммит `f77bdaf52eb1` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `8c131f1d70153bc7634264d5f83dab0b656088c4`
```
git log --all --format='%H %T' | grep 8c131f1d7015
```
- Тело issue: `3ca7e6df6ede172521144a672fefa1a946c0af10242125f62e6b835a399babe0`
- Вердикт конвейера: `green` · High 0