docs: review document for #571
Проверка (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

Issue: #571
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-17 20:59:57 +00:00
parent f77bdaf52e
commit 0de14ead71
+175
View File
@@ -0,0 +1,175 @@
# 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