mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 21:01:21 +00:00
227 lines
23 KiB
Markdown
227 lines
23 KiB
Markdown
# 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.
|