Issue: #512 User-Visible: no
24 KiB
CODE-REVIEW-512-r1
- Issue: #512 — «Golden: текст версии через seam вне кадров;
docs:accept --identicalпо попиксельной идентичности» - Этап: код-ревью (PROCESS.md §2.7)
- Материал:
git log --oneline origin/dev..HEAD/git diff origin/dev...HEADна SHAffe2ec51c0ca841931d4073f6d9d06aa1ddc4462(рабочая копия стоит на нём же, ничего не подтягивалось и не переключалось). - Заход: r1 · блокирующих циклов израсходовано 0 из 4.
- Предыдущий этап: ревью ТЗ прошло два захода (SPEC-REVIEW-512-r1 — красный, H1; SPEC-REVIEW-512-r2 — зелёный). Первая попытка запуска code review не состоялась вовсе — конфликт при ребейзе на
devвернул задачу вS6до чтения кода (комментарий issue 15:50); после ребейза (15:55) материал стал текущимffe2ec51. Это первый фактический прогон код-ревью, поэтому разбор полный — разделов «Закрытие раунда» и «Унаследовано» не требуется (правило дельты применяется от r2).
Скоуп ревью
71 файл в git diff origin/dev...HEAD --stat, из них по существу:
- Класс A (
src/**):src/card-version.ts(новый файл, seam), точечные правки чтения версии вsrc/houseplan-card.tsиsrc/houseplan-editor-runtime.ts— по 5–7 строк на файл. - Класс B (
scripts/**,test/**,demo/**,tsconfig.test.json):scripts/docs-accept.mjs(+125 строк, новый режим--identical),scripts/png-identical.mjs(новый),scripts/mutation-gate.mjs(+22, два новых мутанта),demo/golden/harness.mjs(seam-override перед монтированием карточки), новые тесты. - Класс C (
docs/**,AGENTS.md,PROCESS.md): описания seam и--identicalвdocs/TESTING.md,demo/golden/README.md,docs/DEVELOPMENT.md, плюс упоминания вAGENTS.md/PROCESS.md, спека и оба ревью-документа спек-этапа,docs/specs/README.md. - Класс D (
dist/**,custom_components/houseplan/frontend/**,demo/golden/baselines/**): пересборка бандла (хеши чанков сменились из-за правки исходников) и переприёмка 7 golden-эталонов с версией.
Остальное (docs/reviews/SPEC-REVIEW-512-r1.md, r2.md) — публикация предыдущего этапа, не предмет этого ревью.
Продукт не меняется для пользователя (User-Visible: no на всех коммитах, корректно — единственная видимая строка gs.about_version не меняет ни текст, ни поведение вне харнесов).
Как проверялось
Прочитан весь диф построчно (git diff origin/dev...HEAD по каждому из файлов классов A/B/C, поимённо), не только сгенерированные части класса D.
Соответствие спеке (docs/specs/512-...md) коду, построчно:
- §4 «Seam версии»:
src/card-version.ts—displayVersion(fallback)читаетglobalThis.__HP_VERSION_OVERRIDE__, при непустой строке возвращает её, иначеfallback; ровно то, что написано в ТЗ. Все перечисленные в ТЗ точки заменены наdisplayVersion(CARD_VERSION): PDFversion:(houseplan-card.ts:10732),frontendVersion(:2131),card_versionвsupport/preview(houseplan-editor-runtime.ts:8980) иexport/create/backup (:9616),gs.about_version(:9198),cardVersionв preflight-документе (:9343),_preflightVersionsDiffer(:9371).hp_retry(4 места) иconsole.infoостались на литерале — как и требует §3 «не-скоуп».release-contract.mjsне тронут, продолжает читать оба литерала регэкспом. - §5 «Golden-харнес»:
cardVersionвdemo/golden/harness.mjsтеперь константа'0.0.0-golden'вместо чтенияpackage.json;window.__HP_VERSION_OVERRIDE__ = cardVersionставится вpage.evaluateдоdocument.createElement('houseplan-card');card._haIntegrationVersion = cardVersion(было и раньше, теперь несёт ту же константу);integrationVersion: '0.0.0-golden-backend'вmatrix.mjsне тронут — отношение frontend≠backend в version-mismatch сценариях сохранено. - §6 «
docs:accept --identical»: прочитан целиком новый кодscripts/docs-accept.mjs(identicalDocsManifest,identicalDecision,acceptIdentical) иscripts/png-identical.mjs(compareDecodedPixels,compareInPage,compareFramePairs). Подтверждено по коду:demo/docs/capture.mjsне тронут (git diffпо нему пуст) — это прямое закрытие H1 из ревью ТЗ r1; кадры на диске всегда восстанавливаются из бэкапа (restoreFrames()вызывается и на успешной, и на отказной, и на аварийной ветке,finallyподчищает temp-каталог); при полном совпадении манифест берётsourceFingerprint,scenarios[*].sourceSha256иcaptureScriptSha256из кандидата, аimageSha256/chromium/oxipng/acceptance.*— из закоммиченного, ровно как в AC3. - §7 «Переприёмка golden»: коммит
ffe2ec51несёtRelease: v1.74.0-beta.1иBaseline-Reviewed: .../runs/34366858855, семь кадров (3 version-mismatch, 3 PDF-футера, support-preview) заменены на0.0.0-golden, остальные 162 сохранены как есть. - §8 «Тесты и мутанты»: все перечисленные проверки на месте (source-text тест объединён с
test/card-version.test.mjsвместо отдельного файлаtest/houseplan-card-version-seam.test.mjs— расхождение с именем файла в ТЗ, не с содержанием; не поднимаю как находку, это внутренняя деталь реализации, которую ТЗ прямо отдаёт автору). - §11 «Затронутые файлы»: полностью совпадает со списком реально изменённых файлов, включая
docs/images/screenshots.json(fingerprint-only) иdemo/golden/baselines/**.
Подлинность Baseline-Reviewed-ссылки (не принято на слово):
gh api repos/Matysh/houseplan-card/commits/605991ef1287ce3636a582b2902fadf857bf1525 — коммит существует на GitHub, его сообщение слово в слово совпадает с локальным b7d8b9e6 (тем же диффом src/card-version.ts — сверено байт-в-байт через gh api .../commits/605991ef... --jq '.files[]...'). Локально этого SHA нет (git cat-file -t — «bad object»), потому что ветка была ребейзнута на dev после dispatch-прогона (issue-комментарий 15:55: «Материал теперь ffe2ec51 (= b7d8b9e6 код + ffe2ec51 golden)… Содержимое коммитов не менялось»). Разница между 605991ef и текущим деревом ограничена файлами класса D (dist/**, custom_components/houseplan/frontend/**) — то есть ребейз пересобрал бандл, не тронув источник, для которого прогонялся golden. Само gh run view 34366858855 подтверждает: workflow Проверка (CI), event: workflow_dispatch, job «Golden-кадры против принятых эталонов» — failure (это ожидаемо: dispatch и предназначен для получения артефакта расхождений, из которого затем принимается переприёмка), «Смоки: все шарды зелёные», «Перф-смок» — success; три job «Мутанты по диффу» — cancelled (не относится к golden-переприёмке). Ссылка не выдумана и указывает на реальный прогон по факту тому же коду.
Собственные гейты (зелёного Validate на ffe2ec51 не найдено, прогнал сам):
npx tsc --noEmit— чисто, без вывода.npm test— 2432 passed / 1 skipped / 0 failed (tests 2433, из них 1 subtest пропущен — не относится к этой задаче).npm run build— детерминированная пересборка,git status --porcelainпосле неё пуст (дифф с закоммиченнымdist/**нулевой).npm run bundle:sync— синхронизация трёх копий бандла (dist→custom_components/houseplan/frontend→demo/srv/assets) прошла без изменений в рабочем дереве — три копии совпадают байт-в-байт с закоммиченными.node scripts/check-docs.mjs— «Documentation checks passed (7 files, 12 external links)»:sourceFingerprintвdocs/images/screenshots.jsonсовпадает с текущимsrc/**,--identical-переприёмка (605991ef/b7d8b9e6) не устарела.node scripts/smoke-select.mjs --base origin/dev --head HEAD— НЕОПРЕДЕЛЁННОСТЬ: изменено 3 файлаsrc/**, символыCARD_VERSION/displayVersionне связаны доказуемо ни с одним из 236 смоков (эти символы не рендерят собственный DOM-контракт, который смоки называют по имени). Инструмент явно не решает «смоки не нужны» — решил по коду: диф трогает ровно 4 видимые поверхности (about/support-превью/бэкап-экспорт, версия в баннере version-recovery, PDF-футер), для каждой вdemo/есть тематический смок. Прогнал все четыре:demo/smoke_version_recovery.mjs,demo/smoke_pdf_export.mjs,demo/smoke_support_feedback.mjs,demo/smoke_backup_transfer.mjs— все зелёные (OK). Полный набор 236 смоков не прогонял: диф не задевает геометрию/толщину/Zigbee/этот класс подсистем, где выборка по теме исторически подводила (#234); здесь выборка «по факту прочитанного кода», а не только по имени, и она узкая и предсказуемая — версия печатается в фиксированном, коротком списке мест, все они перечислены выше построчно.- Мутанты AC5 — не гонял весь тяжёлый набор (первая попытка через неверный флаг
--onlyпо ошибке запустила часть общего реестра и упёрлась в отсутствующийpytestв бэкенд-мутанте, не относящемся к задаче — прервано, не в счёт). Верно:node scripts/mutation-gate.mjs --id=version-seam-ignores-override→ «поймано 1 из 1»;node scripts/mutation-gate.mjs --id=docs-identical-accepts-any-frame→ «поймано 1 из 1». Полный--changed-прогон (гейт CI «Мутанты по диффу») не гонял целиком — упёрся в таймаут при пробе (гейт объективно тяжёлый, это его штатное место в конвейере Validate, а не в ручном код-ревью); точечная проверка обоих мутантов AC5 равноценна для целей этого ревью. npm run golden:verify— диф меняет видимый рендер (текст версии в кадрах), гейт в категории «по необходимости» применим. Прогнал локально: 173 из 227 сценариев обработаны, все 173 —passed, включая все триversion-mismatch-*, все триpdf-export-*иsupport-desktop-preview-dark-en— ровно те кадры, которые переприняты в §7. Прогон оборвался без сообщения об ошибке (процесс пропал из списка процессов среды между двумя последовательными проверками; похоже на исчерпание ресурсов рантайм-раннера, не связано с кодом) — не переигрывал: это тяжёлый пред-релизный гейт (PROCESS.md §8), а не гейт ревью, и вместе с уже проверенной подлинностьюBaseline-Reviewed-прогона (полный матрикс, 227/227, на коде, идентичном текущему за вычетом класса D) и с зафиксированным здесь частичным локальным подтверждением (0 расхождений на всех переприятых кадрах) даёт достаточную уверенность без повторной тяжёлой попытки.python -m pytest tests_backend— не прогонял: диф не касаетсяcustom_components/**/*.py.- Инварианты модели (
npm run invariants) — не прогонял: диф не касается геометрии (рёбра, толщина,layout,marker.space,open_spans); ни один из изменённых файлов не входит в эти подсистемы.
Находки
Нет находок High или Medium.
Low, не блокирует (снимаю с записью, не требую правки): текст в docs/DEVELOPMENT.md, добавленный этой задачей для docs:accept -- --identical, не повторяет предупреждение о зависимости от окружения съёмки (0 отличий воспроизводимо только на том же каноне рендеринга шрифтов, что и docs:capture — Linux/WSL). Ревью ТЗ r2 уже отметило это тем же способом («не блокирует, стоит упомянуть в будущей правке docs/DEVELOPMENT.md, уже в списке затронутых файлов») — правка этого файла произошла именно в этой задаче, повод был, автор им не воспользовался. Ничего не ломает: инструмент на другом окружении просто откажет с честным «--identical не даёт 0» и не спрячет проблему, а сценарий §1.1 не заявлен как AC. Не создаю отдельный issue (Low либо правится на месте, либо снимается запиской — здесь снимаю).
Что проверено и корректно
- Обе точки закрытия H1 из ревью ТЗ (
demo/docs/capture.mjsне правится;captureScriptSha256в манифесте берётся из кандидата при--identical) реализованы кодом один в один с решением ТЗ. - Единственный источник отображаемой версии —
displayVersion(); ни одна из шести перечисленных в ТЗ точек рендера/запроса не читаетCARD_VERSIONнапрямую (проверено и построчным чтением диффа, и тестомtest/card-version.test.mjs, который сверяет исходный текст обоих файлов регулярками — тест умеет падать: мутантversion-seam-ignores-overrideкрасит его штатно, поймано «1 из 1»). release-contract.mjs,hp_retry-кэшбастинг иconsole.infoнамеренно не тронуты — совпадает с §3 «не-скоуп» ТЗ и не расходится с релизным контрактом.docs:accept --identical: кадры на диске гарантированно возвращаются к закоммиченным в любом исходе (успех/отказ/исключение) — прочитано по коду (try/catch/finallyвacceptIdentical), закреплено тестамиtest/docs-accept.test.mjs(обе ветки — идентичные кадры и один отличающийся пиксель — гоняют реальный код через инъекцию съёмки/компаратора, а не мокают его целиком).- Компаратор пикселей строгий и не имеет допуска (включая альфа-канал) — тест
test/png-identical.test.mjsявно проверяет обнаружение единичного различия байта альфы; мутантdocs-identical-accepts-any-frameпойман. - Golden-харнес пинит версию до создания карточки единой константой,
integrationVersionversion-mismatch сценариев не тронут — отношение frontend≠backend, которое эти сценарии проверяют, не пострадало (подтверждено локальным прогономgolden:verify, все триversion-mismatch-*—passed). - Трейлеры:
Issue: #512иUser-Visible: noна каждом коммите; коммит сdemo/golden/baselines/**несёт такжеRelease: v1.74.0-beta.1иBaseline-Reviewed:— ссылка проверена как настоящая (см. выше), не выдумана.User-Visible: noкорректно: единственное DOM-видимое место (gs.about_version) не меняет ни ключ, ни текст (src/i18n/*.jsonне в диффе), changelog не тронут — верно, что не тронут. - Переприёмка golden сделана дисциплинированно: приняты только семь кадров, где реально текст версии; девять кадров с подпороговым дрейфом окружения в других сценариях (tray, resize handles, компас, junction) намеренно не приняты — не растащено «заодно», соответствует правилу «не приёмка ради зелёного CI».
- «Одно число — один источник»: отображаемая версия и раньше, и теперь имеет ровно один источник на страницу —
CARD_VERSION, читаемый один раз в каждой точке рендера через одну и ту же функцию; seam ничего не дублирует и не считает по-новому, только подменяет литерал на условие. Дублирования не вносится.
Чего не проверял
- Полный набор из 236 браузерных смоков — не прогонял целиком, обоснование выше (§ «Как проверялось»); прогнал 4 темово и содержательно связанных.
- Полный
--changed-прогон мутантов по диффу (job «Мутанты по диффу» в Validate) — упёрся в таймаут при локальной пробе; это штатный тяжёлый гейт конвейера, не ручного ревью; оба именованных в AC5 мутанта проверены точечно и оба пойманы. - Полный
golden:verifyне досмотрен до 227/227 — процесс пропал без диагностики на середине; 173/227 (все — pass, включая все переприятые кадры) плюс независимо проверенный подлинныйBaseline-Reviewed-прогон на полном матриксе закрывают вопрос без повторной тяжёлой попытки. python -m pytest tests_backend,npm run invariants— не применимы к этому диффу (не тронутыcustom_components/**/*.pyи геометрия), не прогонял.- Поведение
acceptIdenticalприSIGKILLмежду съёмкой и восстановлением бэкапа — то же самое, что уже разобрано и снято как не-находка в ревью ТЗ r2 (не новый класс риска, ничего не коммитится автоматически); заново не поднимаю. - Не тестировал вручную в браузере (кроме смоков и golden-сравнения) — доступа к интерактивной сессии нет; visual-заключение опирается на golden/smokes.
Вывод
Все шесть AC ТЗ выполнены и доказаны либо автотестом, способным упасть (unit на seam, unit на компаратор, интеграционный docs-accept.test.mjs, оба мутанта отдельно пойманы), либо реальным прогоном на текущем дереве (check-docs, частичный и полный-по-CI golden:verify), либо чтением кода с явной пометкой (release-contract.mjs, i18n-ключ). Находок High/Medium нет; единственная Low-находка снята запиской, не требует правки. Вердикт — зелёный.
Материал раунда
- Ветка:
issue/512-golden-version-seam-docs-identical, коммитffe2ec51c0ca— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
dbe34717af925e03fb5bc6b52501bc640dbb0c2cgit log --all --format='%H %T' | grep dbe34717af92 - ТЗ
docs/specs/512-golden-version-seam-and-docs-identical-accept.md, блоб0ff211f5598812b6d66f31cb136727b161b9c7edgit log --all --find-object=0ff211f5598812b6d66f31cb136727b161b9c7ed -- docs/specs/512-golden-version-seam-and-docs-identical-accept.md - Вердикт конвейера:
green· High 0