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

23 KiB
Raw Blame History

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.