23 KiB
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, что равно tiporigin/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, плюс перенумерация
заголовков).
Как проверялось
- Восстановлена история раунда из комментариев issue #291 (аналитика → ТЗ →
вердикт r1 → уточняющий вопрос Q1 → правка M1/M2 → ответ владельца на Q1) и
сверена с
git logветкой: коммит8b99df6e— тот же SHA, что назвал комментарий «ТЗ готово» и на который ссылается самSPEC-REVIEW-291-r1.md. git diff 8b99df6e..HEAD -- docs/specs/291-lattice-coordinate-write-barrier.mdпострочно сопоставлен с находками M1/M2 из r1: подтверждено, что каждая исправлена по существу, а не переформулирована на словах (таблица ниже).- Прочитан весь текущий файл ТЗ целиком (404 строки) — не только диф — чтобы поймать дефект, который правка по одному замечанию могла внести в нетронутую часть (регрессия §2.10, прецедент #102). Проверена сквозная нумерация разделов 1–15 после сдвига — последовательна, разрывов и повторов нет, внутренних ссылок на номера разделов в тексте (кроме заголовков) не найдено, значит перенумерация не оставила «висячих» ссылок.
- Новый раздел 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), а не как факт от автора. - Сверены 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 текстом. - Гейты кода не прогонялись — диф не содержит класса 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совпадает с tiporigin/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-4threshold, формула 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.