Issue: #571 User-Visible: no
15 KiB
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 в обоих коммитах.
Как проверялось
- Прочитан весь 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/**). - Прослежена логика
accept.mjsпострочно для всех пяти сценариев приёмки из тела issue (см. таблицу ниже) — доказательство чтением плюс сверка с автотестами, которые эти же сценарии исполняют. - Тесты не просто прочитаны — прогнаны лично:
node --test --test-name-pattern="#571" test/golden-capture-provenance.test.mjs→7 pass / 0 fail. Тесты запускают настоящийaccept.mjsв изолированном каталоге эталонов (--baselines=<tmp>), не подменяют модуль. - Дисциплина «тест умеет падать» проверена сама, не со слов автора. Вручную
внесены обе мутации из
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соответствующей записи реестра.
- Сверены неизменные инварианты:
imageSha256во всех 11 записяхdocs/images/screenshots.jsonне менялся (git diff ... | grep -E '^[+-].*imageSha256'пусто) — изменился толькоsourceFingerprint/sourceSha256, что подтверждает заявление «пересъёмка не потребовалась, картинки байт-в-байт совпали». Аналогично не менялисьdemo/golden/baselines/**(нет ни одного файла в диффе) — PNG эталоны не трогались, поэтому трейлерыRelease:/Baseline-Reviewed:не требуются. node scripts/smoke-select.mjs --base origin/dev --head HEAD→ «Исполняемого frontend-диффа нет… Browser-smoke этим диффом не выбираются». Смоки не прогонялись — выбирать нечего, диффа вsrc/**нет.- Проверено, что
docs-accept.mjs(приёмка скриншотов документации) не задета и не несёт аналогичного дефекта: там нет поляcapturedOn, есть толькоacceptedOn(платформа приёмщика, честно так и названная) — предположение автора о возможном родственном разрыве не подтвердилось, отдельный issue не нужен. - Проверены трейлеры обоих коммитов (
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) — сошлись на этом прогоне как источнике для этих трёх гейтов. - Прогнал сам, адресно:
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/**в диффе), индекс скриншотов документации переснят вторым коммитом с сохранением всех 11imageSha256— не переложено на будущую задачу. - Свидетели гоняют настоящий
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 подтверждены автотестами, исполненными лично, две мутации проверены руками поверх заявления автора.
Вердикт
Зелёный.
Материал раунда
- Ветка:
issue/571-golden-capture-provenance, коммитf77bdaf52eb1— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
8c131f1d70153bc7634264d5f83dab0b656088c4git log --all --format='%H %T' | grep 8c131f1d7015 - Тело issue:
3ca7e6df6ede172521144a672fefa1a946c0af10242125f62e6b835a399babe0 - Вердикт конвейера:
green· High 0