Files
houseplan-card/docs/reviews/SPEC-REVIEW-291-r2.md
T
2026-08-24 17:11:27 +03:00

227 lines
23 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-291-r2
- **Issue:** https://github.com/Matysh/houseplan-card/issues/291
- **Этап:** ТЗ на ревью (PROCESS.md §2.4)
- **Заход:** r2 · блокирующих циклов израсходовано 1 из 4 до этого вердикта
- **Артефакт ТЗ:** `docs/specs/291-lattice-coordinate-write-barrier.md`
- **Ветка:** `issue/291-lattice-coordinate-barrier`
- **SHA ревью r1:** `8b99df6e` (зафиксирован в `docs/reviews/SPEC-REVIEW-291-r1.md`,
не был назван в тексте вердикта-комментария — отмечаю это как процессный
пробел r1, не блокирующий: сам документ ревью SHA называет)
- **SHA этого ревью (r2):** `23fb41d4` = текущий `HEAD`, ветка не отстаёт от
`origin/dev` (`git merge-base origin/dev HEAD` = `523190d8`, что равно tip
`origin/dev` на момент проверки) — ребейза не было, полный разбор по §2.10
не требуется, разбор по дельте.
- **Трек:** обычный (не `small`/`trivial`) — без изменений с r1.
## Скоуп ревью
Второй заход. Предмет — дельта `git diff 8b99df6e..HEAD`, два коммита:
- `5da14e08` «docs: add product context and rollback» — правка Medium-находок
r1 (M1: продуктовые разделы §7.1; M2: разделы «Риски»/«Откат»);
- `23fb41d4` «docs: record optimize report decision» — фиксация ответа
владельца на Q1 (степень детализации отчёта Optimize) в теле ТЗ.
Диапазон `git diff origin/dev...HEAD --stat` — по-прежнему только два файла
документации (`docs/reviews/SPEC-REVIEW-291-r1.md`,
`docs/specs/291-lattice-coordinate-write-barrier.md`), класс C. Кода нет,
поэтому полный разбор не требуется ни по признаку ребейза (его не было), ни по
признаку смены контракта (контракт AC1–AC6, AC8–AC12 и разделы 4–6, 8, 10
дельтой не тронуты), ни по объёму (дельта — 138 добавленных/переставленных
строк из ~400, точечно в тех двух местах, которые назвал r1, плюс перенумерация
заголовков).
## Как проверялось
1. Восстановлена история раунда из комментариев issue #291 (аналитика → ТЗ →
вердикт r1 → уточняющий вопрос Q1 → правка M1/M2 → ответ владельца на Q1) и
сверена с `git log` веткой: коммит `8b99df6e` — тот же SHA, что назвал
комментарий «ТЗ готово» и на который ссылается сам `SPEC-REVIEW-291-r1.md`.
2. `git diff 8b99df6e..HEAD -- docs/specs/291-lattice-coordinate-write-barrier.md`
построчно сопоставлен с находками M1/M2 из r1: подтверждено, что каждая
исправлена по существу, а не переформулирована на словах (таблица ниже).
3. Прочитан весь текущий файл ТЗ целиком (404 строки) — не только диф — чтобы
поймать дефект, который правка по одному замечанию могла внести в
нетронутую часть (регрессия §2.10, прецедент #102). Проверена сквозная
нумерация разделов 1–15 после сдвига — последовательна, разрывов и повторов
нет, внутренних ссылок на номера разделов в тексте (кроме заголовков) не
найдено, значит перенумерация не оставила «висячих» ссылок.
4. Новый раздел 3 «Пользовательский контекст» и обновлённый раздел 7 сверены с
`docs/USER-GUIDE.ru.md` (терминология интерфейса) — строки 1396–1420, 1444.
Там же сверен факт, который был предметом M1: действующий (до #291) отчёт
Optimize уже показывает один общий счётчик «шум координат» и отдельную
строку сдвинутых элементов; per-space таблицы нет. Ключ `src/i18n/ru.json:817`
(`gs.optimize_changes`) подтверждает то же самое буквально: один общий
`{p}`, без разбивки по пространствам. Значит новое AC7 (per-space строки
только для изменённых пространств) действительно вводит новый видимый
текст, а не переописывает существующий — и именно это теперь оформлено как
решение владельца (Q1), а не как факт от автора.
5. Сверены 10 сопоставимых ТЗ (090, 094, 150, 178, 179, 210, 211, 226, 252,
279) на предмет конвенции разделов «i18n» и «Риски/Откат» — второй раз,
целенаправленно на предмет i18n, поскольку сам факт наличия раздела не был
предметом явной проверки в r1 (см. находку M3 ниже). Ближайший по смыслу
документ — `docs/specs/252-optimize-orphan-layout-report.md` (тоже про
Optimize report): у него нет отдельного заголовка «i18n», но каждая новая
строка отчёта именует свой ключ инлайн (`gs.optimize_reference_warning`) и
AC явно требуют «RU/EN i18n и unit matrices». Другой сопоставимый документ,
`094-universal-state-toggle.md:645-663`, даёт образец полноты: список
конкретных новых/меняющихся строк с RU/EN текстом.
6. Гейты кода не прогонялись — диф не содержит класса A/B (см. «Чего не
проверял»).
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где видно |
|---|---|---|
| **M1** — нет продуктовых разделов §7.1 (персона/поверхность/момент, «до/после»); степень детализации Optimize-отчёта подана как факт, а не как предположение/вопрос | Добавлен раздел «3. Пользовательский контекст и результат» (персона — владелец дома с несколькими этажами, поверхность — Plan/Optimize, момент — обслуживающее действие и обычная запись; «до»/«после» одним абзацем каждое). Степень детализации отчёта переведена из факта автора в явный вопрос владельцу (Q1, комментарий 10:54:35) с default-вариантом и получила прямой ответ владельца (комментарий 11:40:47), зафиксированный как «Решения владельца» п.4 | `docs/specs/291-*.md` §3 (было добавлено в `5da14e08`); §2 п.4 и §7 (обновлены в `23fb41d4` по ответу владельца); issue-комментарии `5394220413` (вопрос) и `5394680424` (ответ) |
| **M2** — нет разделов «Риски» и «Откат», обязательных по DoR §2.5 и во всех 10 сопоставимых ТЗ | Добавлены разделы «11. Риски и меры» (5 пунктов риск→мера, привязанных к конкретным AC) и «12. Откат» (revert коммита, без миграции схемы, честная оговорка про уже канонизированные биты и Undo для явного Optimize) | `docs/specs/291-*.md` §11–§12, коммит `5da14e08` |
| **L1** (Low) — пример вывода CLI в AC3 (`noise: 0 (0.00%)`) не совпадает с реальным форматом `latticeReport()` | Не тронуто в этой дельте | `docs/specs/291-*.md`, раздел AC3 (текущий §9) — прежний текст без изменений |
| **L2** (Low) — тело issue называет задачу «Стадия 1 из ADR #282», хотя это Stage 0-продолжение, а не Stage 1 ADR | Не тронуто — тело issue #291 на момент этого ревью содержит ту же фразу | Тело issue #291, первый абзац |
M1 и M2 — единственные Medium из r1, обе закрыты по существу, не текстом
«исправлено». L1/L2 были явно необязательны к этому заходу (решение
ревьюера r1) и остаются открытыми ниже как унаследованные, не блокирующие.
## Находки этого раунда
### Medium (в скоупе задачи — чинится в текущем issue)
**M3. Раздел «i18n», обязательный по цепочке §7.1, отсутствует; новые строки
отчёта Optimize (per-space breakdown, строка максимального сдвига), введённые
именно этой дельтой через принятое решение по Q1, не имеют перечисленных
ключей en/ru.**
- **Файл:** `docs/specs/291-lattice-coordinate-write-barrier.md` — весь
документ; ближе всего к месту дефекта разделы «7. Existing data и explicit
Optimize report» и «13. Ожидаемые файлы».
- **Что не так:** `PROCESS.md` §7.1 перечисляет обязательные разделы ТЗ:
«...модель данных и миграция · **i18n** · критерии приёмки...», а DoR-чеклист
§2.5 требует «i18n: ключи en + ru перечислены» как отдельного пункта,
без которого «статус не «Готово к разработке», как бы ни хотелось начать».
В документе i18n встречается только как имя двух файлов в разделе 13
(`src/i18n/en.json`, `src/i18n/ru.json`), без единого имени ключа и без
текста строки.
Это не было заметно до этой дельты: до ответа на Q1 отчёт был
неспецифицирован (M1), и требовать ключи для неопределённого контента было
бы рано. Теперь контент решён (§2 п.4, §7, AC7 этого документа), и именно
дельта, закрывшая M1, обнажила этот пробел — проверка «дошла ли дельта» по
§2.10 обязывает досмотреть его сейчас, а не отложить на код-ревью.
Действующий ключ `gs.optimize_changes` (`src/i18n/ru.json:817`) — плоская
строка формата `«...устранён шум координат: {p}; объединено отрезков...»`;
для новых per-space строк и строки максимального сдвига неясно, расширяется
ли этот же ключ параметрами, добавляется отдельный ключ на строку,
добавляется отдельный ключ-шаблон на пространство с интерполяцией — три
разных решения с разными последствиями для pluralisation и для существующего
теста паритета i18n.
Сопоставимые документы называют реальную практику: `docs/specs/252-optimize-orphan-layout-report.md`
(тот же диалог Optimize) без отдельного заголовка «i18n», но с инлайн-именами
ключей (`gs.optimize_reference_warning`) и явным AC на RU/EN unit matrices;
`docs/specs/094-universal-state-toggle.md:645-663` — образец отдельного
раздела с перечнем конкретных новых/меняющихся строк. В 291-м ни того, ни
другого нет.
- **Сценарий, где это ломается:** разработчик на код-ревью добавляет новый
ключ `gs.optimize_lattice_space` со своей pluralisation-схемой; ревьюер кода
не может сверить её с ТЗ, потому что ТЗ не называло ни имени, ни структуры —
решение принимается кодом, а не ТЗ, что прямо противоречит цели §7.1
(«размытое место не додумывается [в коде], а решается в ТЗ либо явно
выносится предположением»).
- **Что сделать:** добавить короткий раздел (или подпункт к §7) с перечнем
новых/меняющихся ключей en+ru — по образцу `094-*.md`: имя ключа, RU/EN текст
или явное «расширяет `gs.optimize_changes` параметром `{spaces}`»; либо, если
автор считает именование чисто техническим и несмотрящимся пользователю
вопросом, добавить его явным пунктом в §15 «Принятые технические
предположения» — но не оставлять полностью неупомянутым, как сейчас.
### Low (унаследованные, не блокируют, статус не изменился)
**L1** (из r1) — пример CLI-вывода в AC3 остаётся текстово рассинхронизирован
с `latticeReport()`. Не тронуто этой дельтой. Решение ревьюера прежнее: не
блокирует, правится заодно со следующей правкой документа.
**L2** (из r1) — тело issue #291 всё ещё называет задачу «Стадия 1 из ADR
#282»; расхождение с фактическим содержанием Stage 1 ADR (stable wall ids)
остаётся. Не относится к файлу ТЗ, не блокирует.
## Что проверено и признано корректным
- **M1 закрыта по существу, не по форме.** Раздел 3 отвечает на оба
обязательных вопроса §7.1 (кто/где/когда встретит; что видно до/после), и,
что важнее прежней находки, спорная деталь (степень детализации отчёта)
перестала быть догадкой автора: она прошла через реальный вопрос владельцу
с default-вариантом и получила его явное решение, зафиксированное текстом
«Решения владельца» — это сильнее минимальной планки «пометить
предположением», которую просил r1.
- **M2 закрыта содержательно.** Пять пунктов «риск → мера» в §11 адресуют
именно те угрозы, которые делают риск 10/10 нетривиальным (широкий threshold,
расхождение TS/Python округления, обход barrier, перф деградация, тихая
очистка старого шума обычной записью) — каждый привязан к конкретному AC,
не декларативен. §12 «Откат» честно называет то, что не откатывается
автоматически (уже канонизированные биты), и почему это не считается
потерей — это ровно тот уровень честности, которого не было в r1.
- **Перенумерация не внесла дефектов.** Прочитан весь документ целиком после
сдвига разделов 3→15; последовательность 1–15 без разрывов и повторов,
внутренних ссылок на устаревшие номера разделов не найдено.
- **Обновлённый §7/AC7 не противоречит уже принятому в r1 техническому
контракту.** Формула распределения (total = Σ по изменённым пространствам,
far отдельно, max shift отдельно от `moved/maxShiftCm`) не меняет ничего из
§4–§6, §8–§9 (кроме самого AC7), которые дельта не трогала.
- **Новый текст §3/§7 не противоречит `docs/USER-GUIDE.ru.md`.** Формулировки
про «отдельную строку шума» и «одну серверную отмену» описывают то же самое,
что уже документировано как действующее поведение (строки 1396–1420,
1444–1454) — ТЗ расширяет существующий контракт, а не изобретает его задним
числом.
- **Ветка не отстаёт от `origin/dev`, ребейза не было** — `git merge-base
origin/dev HEAD` совпадает с tip `origin/dev`; полный повторный разбор по
§2.10 (случай ребейза) не требуется.
## Унаследовано из r1
Принято без повторной проверки в этом раунде — документ
`docs/reviews/SPEC-REVIEW-291-r1.md`, SHA `8b99df6e`:
- Численный контракт §3.1–3.2 (ныне §4.1–4.2 после сдвига): `GRID_N=240`,
`1e-4` threshold, формула deviation — сверены в r1 с реализованным
`scripts/model-invariants.mjs`; дельта эти разделы не трогала.
- Разграничение `canonicalizeLatticeCoordinate`/`canonicalizeScalar` и
совместимость с nine-decimal contract #224 — не изменено дельтой.
- Allow-list полей (§5, было §4) — сверен в r1 с `modelCoordinates()`
(`scripts/model-invariants.mjs:316-358`); не изменено дельтой.
- Write barrier §6 (frontend/backend, source/AST guard) — не изменено
дельтой.
- AC1–AC6, AC8–AC12 (было AC1-AC6, AC8-AC12) — текст не изменён дельтой,
кроме AC7; численные диапазоны, мутанты, perf-бюджет приняты в r1.
- Scope/не-скоуп (§8, было §7) — не изменено дельтой.
- §15 п.1–5 (принятые технические предположения) — приняты в r1 как корректно
технические, не подмена продуктового решения; дельта добавила только п.6
(layout без named owner), рассмотренный в этом раунде отдельно (см. «Что
проверено»).
- Трек/трейлеры (обычный трек, не `small`) — не изменено.
## Чего не проверял
- Код — не существует и в этом раунде: `git diff origin/dev...HEAD --stat`
показывает два файла документации, класс C. Гейты `typecheck`, `npm test`,
`npm run build`, `check-docs.mjs`, `invariants`, смоки, perf — неприменимы,
не прогонялись. Причина — отсутствие класса A/B в диффе, а не пропуск.
- Полный повторный разбор AC1–AC6, AC8–AC12 и разделов 4–6, 8, 10 — дельта их
не касалась, приняты по наследству из r1 (раздел выше), а не перепроверены
заново построчно.
- Существующие i18n-ключи вне `gs.optimize_changes` (тот, что напрямую сверен
с M1/M3) — не проверял остальной `gs.optimize_*` блок построчно; выборочная
проверка ограничена тем, что нужно для находок M1/M3.
- Не связывался с владельцем: находка M3 сформулирована с предложенным
решением (либо перечислить ключи, либо явно вынести именование в §15) и
снимается автором в следующей редакции, а не эскалацией — вопрос
технический (именование/структура ключа), а не продуктовый по критерию
§7.1.
## Итог
0 High, 1 Medium (M3, в скоупе — чинится в этом же issue коротким добавлением
раздела/пункта), 2 Low унаследованных (не блокируют, статус не менялся).
Обе Medium-находки r1 (M1, M2) закрыты по существу и подтверждены построчно, не
на слово автора. Технический контракт, принятый в r1, дельтой не нарушен.
Без High — жёлтый вердикт: документ возвращается автору на добавление
i18n-раздела/пункта; заход r3 расходует второй цикл из лимита 4.