Files
houseplan-card/docs/reviews/SPEC-REVIEW-512-r2.md
T
2026-09-09 18:52:44 +03:00

82 lines
16 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.
# SPEC-REVIEW-512-r2
- **Issue:** #512 — «Golden: текст версии через seam вне кадров; `docs:accept --identical` по попиксельной идентичности»
- **Этап:** ревью ТЗ (PROCESS.md §2.4)
- **Материал:** `docs/specs/512-golden-version-seam-and-docs-identical-accept.md` на HEAD `8fba3987` (ветка на этом коммите же и стоит); дельта — `git diff e606b808..HEAD -- docs/specs/512-golden-version-seam-and-docs-identical-accept.md`.
- **Заход:** r2 · блокирующих циклов израсходовано 1 из 4 (зелёный вердикт бюджет не тратит, §4/#227).
- **Предыдущий раунд:** SPEC-REVIEW-512-r1, вердикт красный, High 1 (H1), получен на SHA `e606b808`.
## Скоуп ревью
Раунд не первый и делта локальна: правка задевает только §6 (`docs:accept --identical`, п.1 и п.3), формулировку AC3 и список §11 «Затронутые файлы». Остальные разделы (§1–5, §7–10, §12) байт-в-байт совпадают с версией, проверенной в r1. Ребейза нет, контракт поведения продукта не менялся (`User-Visible: no` по-прежнему), новая подсистема не затронута — оснований для полного повторного разбора нет. Разбираю дельту и всё, до чего она дотягивается: закрытие H1, консистентность нового текста §6 с реальным кодом `demo/docs/capture.mjs`/`scripts/docs-accept.mjs`, и не подрывает ли новая формулировка соседние AC (AC4, AC5), которых дельта не называет впрямую.
## Как проверялось
- Прочитан вердикт r1 из комментариев issue: находка H1 (правка `demo/docs/capture.mjs` через новый флаг `--out` ломает отдельный от `sourceFingerprint` инвариант `captureScriptSha256`, из-за чего первый же кандидат беты красит `docs` в `check-docs --screenshots=strict` независимо от визуальных отличий; AC3 не называл, обновляется ли это поле).
- `git diff e606b808..HEAD -- docs/specs/512-golden-version-seam-and-docs-identical-accept.md` — единственный содержательный диф раунда (второй файл в `git diff --stat` — сам `docs/reviews/SPEC-REVIEW-512-r1.md`, публикация предыдущего раунда, не предмет ревью).
- Подтверждено, что `src/**`/`scripts/**` в дереве всё ещё не тронуты: `git diff origin/main...HEAD --stat` показывает только три файла в `docs/**`. Гейты кода по-прежнему неприменимы (см. «Чего не проверял»).
- Новый текст §6 п.1 сверен построчно с реальным `demo/docs/capture.mjs`:
- `OUTPUT = resolve(ROOT, 'docs/images')` (`capture.mjs:20`) — скрипт действительно пишет PNG и `screenshots.json` прямо в `docs/images`, а не в параметризуемый каталог; отказ ТЗ от флага `--out` в пользу «бэкап → штатный прогон на месте → восстановление» соответствует реальной сигнатуре инструмента, а не выдуман.
- Манифест, который пишет `capture.mjs` в конце прогона (`capture.mjs:342-355`), уже содержит `captureScriptSha256: sha256(readFileSync(SCRIPT))` — то есть кандидат, полученный локальным прогоном без правки `capture.mjs`, естественно несёт **актуальный** хеш текущего файла. Формулировка §6 п.3 «`captureScriptSha256` берётся из кандидата» технически исполнима без дополнительного вычисления — это то же поле, что кандидат и так посчитал.
- `scripts/docs-accept.mjs` (`verifyDocsCandidate`, строки 67-69) уже сегодня сравнивает `manifest.captureScriptSha256` с фактическим sha диска для существующего флоу `--from`/`--reviewed` — семантика поля («хеш `capture.mjs` на момент съёмки кандидата») в проекте уже установлена, новый режим её не изобретает, а переиспользует.
- Текущий закоммиченный `docs/images/screenshots.json` содержит ровно поля, которые §6 п.3 перечисляет как «из закоммиченного» (`imageSha256` для каждого кадра, `chromium`, `oxipng`) и «из кандидата» (`sourceFingerprint`, `captureScriptSha256`) — набор полей в ТЗ и в реальном манифесте совпадает 1:1.
- Число кадров: `grep -c '"file":' docs/images/screenshots.json` = 11, совпадает с «11 кадров» в новом предложении §6 п.3 про одноразовую переприёмку.
- Проверено отсутствие остаточных упоминаний отменённого флага `--out` где-либо в файле ТЗ (`grep -n -- '--out\b'` — пусто) — старая формулировка не оставила следов в §8/§11.
- Обновлённое AC3 и обновлённая строка теста `test/docs-accept.test.mjs` в §8 сверены на согласованность: AC3 требует «идентичные кадры → только fingerprint и `captureScriptSha256` в манифесте», тест описывает то же плюс явно называет, что `imageSha256`/`chromium` остаются прежними и что кадры на диске не меняются (через инъекцию, без реального Chromium в unit-тесте) — согласовано с §6 п.1 «в любом исходе возвращает байты кадров на место».
- Перечитаны §7 (симметричный кейс для golden) и §12 (принятые предположения) — дельта их не касается и не должна: правка H1 закрывает вопрос иначе (авто-обновление поля при любом будущем прогоне инструмента), а не через добавление ещё одного разового шага вида §7.
## Закрытие раунда r1
| Находка (r1) | Чем закрыта | Где видно |
|---|---|---|
| **H1** — ТЗ правит `demo/docs/capture.mjs` (`--out`), из-за чего меняется его sha256, `captureScriptSha256` расходится с диском, и `check-docs --screenshots=strict` красит `docs` на первом кандидате беты; AC3 не называл, чинит ли это `--identical` | ТЗ отказалось от правки `capture.mjs`: новый механизм §6 п.1 запускает штатный, неизменённый `capture.mjs` (пишет прямо в `docs/images`, как и сегодня), инструмент сам бэкапит закоммиченные кадры+манифест во временную папку и восстанавливает байты кадров при любом исходе; манифест-приёмка (§6 п.3) теперь берёт `captureScriptSha256` из кандидата наравне с `sourceFingerprint` — это закрывает класс дефекта навсегда (любая будущая правка `capture.mjs` без визуальных изменений чинится тем же локальным прогоном), а не только для этой задачи | `docs/specs/512-…md` §6 п.1 («не меняется», «в любом исходе возвращает байты кадров на место»), §6 п.3 («`captureScriptSha256` берётся из кандидата»), AC3, §11 (`demo/docs/capture.mjs` убран из списка правок, `docs/images/screenshots.json` добавлен как «fingerprint-only, через `--identical`»), §8 (обновлённая строка теста) |
Находка закрыта полностью: и убрана причина (правка файла-сторожа), и добавлена общая защита (поле обновляется из кандидата), а не только точечный костыль под эту задачу.
## Унаследовано из r1
Без повторной проверки приняты (документ `docs/reviews/SPEC-REVIEW-512-r1.md`, SHA `e606b808`, дельта их не касается):
- Обоснование полного трека и провал критерия §5 «одна поверхность» (семь мест продукта + отдельный инструмент приёмки).
- §4 «Seam версии» — построчное совпадение перечня точек чтения `CARD_VERSION` в `houseplan-card.ts`/`houseplan-editor-runtime.ts` с реальным кодом, включая исключения (`hp_retry`, `console.info`) и то, что `release-contract.mjs` продолжает читать константы, а не seam.
- §5 «Golden-харнес» — единственность использования `cardVersion` из `package.json` в `harness.mjs:1923`, сохранение `integrationVersion: '0.0.0-golden-backend'` в `matrix.mjs`, нормализация версии бандла в `baselines-index.json` через `foreignFingerprintNormalizer` (#481).
- §8 мутанты (`version-seam-ignores-override`, `docs-identical-accepts-any-frame`) — конкретны и ловятся названными тестами по образцу существующего `test/houseplan-source.mjs`.
- Отсутствие продуктового вопроса владельцу: задача `User-Visible: no`, единственное DOM-видимое место `gs.about_version` — внутри `support-dialog`, ключ и текст не меняются (AC6).
- Низкоприоритетное наблюдение r1 (не находка): `--identical` даёт 0 отличий только в среде, воспроизводящей растеризацию шрифтов закоммиченных кадров (тот же канон Linux/WSL, что и у `docs:capture`) — сценарий §1.1 обещает «минуту локально» без этой оговорки; не блокирует, стоит упомянуть в будущей правке `docs/DEVELOPMENT.md` (уже в списке затронутых файлов).
## Находки
Нет.
## Что проверено и корректно
- Новый текст §6 технически исполним и не содержит непомеченных догадок: каждое фактическое утверждение (что `capture.mjs` пишет прямо в `docs/images`, что манифест уже содержит `captureScriptSha256`, что существующий `verifyDocsCandidate` уже понимает семантику этого поля) сверено с реальным кодом, а не принято на слово автора.
- AC3 однозначен и доказуем тем же способом, что был заявлен в r1 (unit на компаратор + интеграционный прогон на реальных кадрах текущего дерева), формулировка соответствует новому механизму §6.
- §11 «Затронутые файлы» согласован с новым §6: `demo/docs/capture.mjs` корректно исключён (не правится), `docs/images/screenshots.json` корректно добавлен (одноразовая fingerprint-only переприёмка в этом же PR, т.к. seam трогает `src/**`, входящий в `sourceFingerprint`).
- Новая формулировка §6 п.3 не противоречит §7 (переприёмка golden) и не нуждается в симметричном шаге по образцу §7 — выбранное решение («поле обновляется из кандидата всегда») по факту сильнее того, что просил r1 (не разовый костыль, а постоянное поведение инструмента).
- Дельта не расширяет и не сужает скоуп (§2/§3 не менялись), не вводит нового продуктового поведения — `User-Visible: no` остаётся верным.
## Чего не проверял
- Гейты кода (`npx tsc --noEmit`, `npm test`, `npm run build`, `node scripts/check-docs.mjs`) не прогонял: на этапе ревью ТЗ они неприменимы и в r1, и сейчас — изменений в `src/**`/`scripts/**` по-прежнему нет, `git diff origin/main...HEAD --stat` показывает только три файла в `docs/**`. Менять нечего, прогонять не на чем.
- Не проверял поведение нового механизма §6 при аварийном прерывании процесса (`SIGKILL`) между записью кандидата и восстановлением бэкапа — это деталь реализации (try/finally на уровне кода), не тестируемая на уровне ТЗ/unit; не поднимаю как находку: не новый класс риска для проекта (общий для любого инструмента, временно перезаписывающего рабочее дерево с последующим восстановлением), ни один AC от этого не ломается, ничего не коммитится автоматически (`docs-accept.mjs` прямо документирует «здесь не делается коммит»).
- `docs/USER-GUIDE.ru.md` — не проверял повторно, задача не меняет видимое поведение (наследовано из r1, дельта тем более не меняет).
- Демо/смоки, golden:verify, инварианты модели, performance-профили — не запускал: нет ни одного изменения в `src/**` (спек-стадия), поэтому эти гейты неприменимы, а не пропущены.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/512-golden-version-seam-docs-identical`, коммит `8fba3987df92` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `66d626bb0e5fbf4c669c8c0e796af0dbf0dffeba`
```
git log --all --format='%H %T' | grep 66d626bb0e5f
```
- ТЗ `docs/specs/512-golden-version-seam-and-docs-identical-accept.md`, блоб `0ff211f5598812b6d66f31cb136727b161b9c7ed`
```
git log --all --find-object=0ff211f5598812b6d66f31cb136727b161b9c7ed -- docs/specs/512-golden-version-seam-and-docs-identical-accept.md
```
- Вердикт конвейера: `green` · High 0