# SPEC-REVIEW-530-r2 **Issue:** #530 — «Экспорт в PDF: план занимает половину листа A4, а столбец выносов отбирает у него целый шаг масштаба» **Этап:** spec (ревью ТЗ, PROCESS.md §2.4) **Трек:** полный (см. r1 — обоснование не изменилось) **Заход:** r2 · блокирующих циклов израсходовано 1 из 4 (зелёный вердикт этого раунда бюджет не тратит, #227) ## Скоуп раунда Разбор по дельте (PROCESS.md §2.10): предмет этого раунда — правка тела issue, которую автор внёс в ответ на SPEC-REVIEW-530-r1.md (вердикт: жёлтый, Medium 1, Low 1). Раунд НЕ переоткрывает продуктовую рамку, AC1–AC6 и техническое обоснование полного трека — они не входят в дельту и наследуются из r1. ## Как найдена дельта Тело issue правится на месте (ТЗ живёт в теле issue, #517), поэтому вместо `git diff ` использован диф двух состояний тела issue: 1. якорь материала r1 в конце `docs/reviews/SPEC-REVIEW-530-r1.md`: `Тело issue: 3c6248e088a3948ba1b6a44fafbc15118b8ad7706edb941b92b3fd9a4c8135ac`; 2. история правок тела issue вытянута через GraphQL (`Repository.issue.userContentEdits`, поле `diff` = полный снимок тела на момент каждой правки, 5 снимков, автор — Matysh, последняя правка `2026-09-11T09:48:17Z`); 3. `sha256sum` снимка `2026-09-11T09:37:32Z` (правка, сделанная непосредственно перед комментарием «ТЗ готово — на ревью», `09:37:50Z`) — `3c6248e088a3948ba1b6a44fafbc15118b8ad7706edb941b92b3fd9a4c8135ac` — совпал побайтово с якорем r1. Это доказывает, что снимок и есть точный текст, получивший вердикт r1, а не приближение; 4. `diff -u` этого снимка с текущим телом issue (снимок `09:48:17Z`, совпадает с текущим телом issue с точностью до конечного перевода строки) дал дельту раунда — приведена целиком ниже. ```diff @@ раздел «ТЗ», шапка @@ -**Чего задача не трогает:** i18n, кроме удаления ключа `pdf.internal_dimensions`; ... +**Документация:** правка меняет канонический `docs/PDF-EXPORT.md` — раздел +«Sheet and measurement rules» описывает и выноски («required values that +cannot fit beside an edge use numbered callouts», «architecture, enabled +dimensions and callouts»), и правило отказа («If a fixed annotation itself +cannot fit on one A4 sheet, export stops with an error»), единственный +код-путь к которому — именно столбец. Документ правится тем же коммитом, +формулировки — в п. 1 и 5 контракта. + +**Чего задача не трогает:** i18n, кроме удаления ключа `pdf.internal_dimensions` +из словарей; ... @@ Контракт, п.1 @@ -... становится общим. +... становится общим. `docs/PDF-EXPORT.md`: фраза про numbered callouts +заменяется на «значение, которому не хватает места у ребра, не печатается», +упоминание callouts в перечне состава сцены снимается. @@ Контракт, п.5 @@ -5. **Отказ «fail closed» сохраняется** для архитектуры, которая не влезает ни - в один масштаб (#53). Существующий тест ... не удаляется молча. +5. **Отказ «fail closed» сохраняется только для архитектуры**, которая не + влезает ни в один масштаб (#53); формулировка в `docs/PDF-EXPORT.md` + сужается соответственно. Существующий тест ... не удаляется молча. +6. **Мутант `pdf-room-edge-dropped` переоформляется.** Сегодня он защищает + гарантию «каждое неукороченное ребро сохраняет прямое значение или + нумерованный вынос», которую отменяет п. 1. Его `because` и гард + переписываются под новую гарантию (значение печатается там, где + помещается, и не печатается дважды) либо мутант снимается с явной + записью — решение принимается при реализации и называется в хендоффе. @@ AC-таблица @@ +| AC7 | `docs/PDF-EXPORT.md` описывает новое поведение: ни «numbered + callouts», ни отказ из-за неразмещаемой аннотации | тест свежести + документации на слова-маркеры либо проверка ревьюером построчно | + расхождение документа и кода — находка ревью | @@ Откат @@ -Один revert: возвращаются блок выносов, метки и прежние кегли; golden-кадры -и два теста восстанавливаются из истории. ... +Один revert: возвращаются блок выносов, метки, прежние кегли и прежний текст +`docs/PDF-EXPORT.md`; golden-кадры и два теста восстанавливаются из истории. ... ``` Больше ничего в теле issue не изменилось (сверено полным `diff -u` двух снимков — выше единственные пять хвостов различий, плюс безобидное «нет финального перевода строки» в самом старом снимке). ## Закрытие раунда r1 | Находка r1 | Чем закрыта | Где это видно | |---|---|---| | **Medium** — правка меняет два поведения, задокументированные в `docs/PDF-EXPORT.md` (строки 32/58-59 про callouts, строки 41-42 про fail-closed), но документ не назван ни в скоупе, ни в release-артефактах | Добавлен абзац **«Документация»** в шапку ТЗ с прямой цитатой обеих исходных фраз документа и указанием, что они правятся тем же коммитом; п.1 контракта получил конкретную замену фразы про numbered callouts; п.5 сужен явно («только для архитектуры... формулировка в `docs/PDF-EXPORT.md` сужается соответственно»); добавлен **AC7**, прямо требующий отсутствия обеих устаревших формулировок в документе, с указанным способом доказательства | Тело issue #530, абзац «Документация» (после раздела «ТЗ»); Контракт п.1 и п.5; строка AC7 в таблице AC | | **Low** — мутант `pdf-room-edge-dropped` защищает гарантию, которую AC1 отменяет, и его судьба не названа | Добавлен **п.6 контракта**: мутант либо переоформляется под новую гарантию, либо снимается — решение и его запись обязательны в хендоффе реализации, то есть решается явно, а не молчанием | Тело issue #530, Контракт п.6 | Обе находки закрыты предметно (конкретной правкой текста, а не общим заявлением автора) — проверено чтением дельты выше, не пересказом комментария. ## Дельта: что перепроверено заново Дельта не задевает продуктовую рамку, контракт п.2–п.4, AC1–AC6, риски, блок допущений и таблицу критериев полного трека — эти AC перепроверке не подлежат в этом раунде (см. «Унаследовано из r1»). Перепроверено: - **Абзац «Документация» и правки п.1/п.5.** Сверено с текущим текстом `docs/PDF-EXPORT.md` (прочитан целиком, строки 29-59 — раздел «Sheet and measurement rules»): обе цитаты в ТЗ («required values that cannot fit beside an edge use numbered callouts», строка 58-59; «If a fixed annotation itself cannot fit on one A4 sheet, export stops with an error», строки 41-42) совпадают с документом дословно. Раздел «Release-артефакты» после правки всё ещё называет только два бюллетеня changelog и не включает документацию явным пунктом — но абзац «Документация» в шапке ТЗ и п.1/п.5 контракта закрывают именно то требование DoR §2.5 («документация... либо явное "нет"»), на которое указывала находка r1: документ назван, что в нём меняется — указано текстуально. Формального отдельного пункта в самом «Release-артефакты» нет, но это не тот же дефект: r1 требовал, чтобы задача **знала**, что трогает канон, и зафиксировала это — так и сделано, двумя явными местами в тексте, а не одним. Дробить находку второй раз за место расположения абзаца было бы формализмом без нового риска для DoR. - **AC7.** Проверяем самостоятельно, так как в r1 не существовал. Утверждение проверяемо построчным чтением: после правки в `PDF-EXPORT.md` не должно остаться ни фразы про «numbered callouts», ни фразы про отказ из-за неразмещаемой фиксированной аннотации, кроме случая «архитектура не влезает ни в один масштаб». Способ доказательства назван («тест свежести документации... либо проверка ревьюером построчно») — это ровно два метода, разрешённых DoR/§2.7 для этого класса критерия (`unit`-тест на маркерные слова, либо `«ревью кода»`/чтением). Формулировка «либо» не делает AC недоказуемым: она называет запасной путь на случай, если автоматический тест на слова-маркеры не будет написан, а не оставляет открытым вопрос, что именно проверяется. - **П.6 контракта (мутант).** Прочитан `scripts/mutation-gate.mjs:224-247` повторно: `pdf-room-edge-dropped` — единственный мутант, ссылающийся на тест `dense non-rectangular rooms keep mandatory dimensions in stable callouts`, который AC1 переписывает на противоположное утверждение. Два новых мутанта из AC1/AC2 (`pdf-restores-dimension-callouts`, `pdf-keeps-large-in-plan-labels`) покрывают ту же зону риска (столбец не возвращается; подписи не растут обратно) — значит, снятие старого мутанта без замены не оставляет гарантию непокрытой, и формулировка «переоформляется либо снимается» не открывает дыру в защите, а фиксирует, что она перенесена. - **Язык замены фразы.** П.1 даёт русскую формулировку-замену («значение, которому не хватает места у ребра, не печатается») для англоязычного документа. Не находка: весь текст ТЗ русский и описывает предполагаемую английскую формулировку по смыслу, а не диктует точную строку символ-в-символ — точная английская фраза прямо оставлена реализации (AGENTS.md: не-продуктовая формулировка — техническая свобода автора кода), и AC7 проверяет результат («нет старых фраз»), а не текст замены дословно. Ни один из пересмотренных пунктов не порождает новой High- или Medium-находки. ## Унаследовано из r1 Без повторной проверки в этом раунде — документ `docs/reviews/SPEC-REVIEW-530-r1.md`, материал `Тело issue: 3c6248e08...8135ac` (совпадение подтверждено выше): - продуктовая рамка §7.1 (персона, поверхность, момент, «до/после» одной фразой); - выбор полного трека и оба названных нарушенных критерия §5 (перф-бюджет 200 мс, смена UX-контракта — ориентация и потеря раздела); - все количественные утверждения о текущем коде: `PDF_SCALE_SERIES`, кегли 7/8/9-7/6/8/6, хром страницы 14/10/8/7, арифметика ×0.75; - AC1–AC6 по существу (состав, формулировка, способ доказательства и мутанты каждого) — дельта их текст не меняла; - содержание блока «Принято предположительно» (пилообразное заполнение вне задачи, поля/шапка/подвал не трогаются, выноска у стены — не эта задача); - риски (кегль 5.25 pt на грани практики, потеря семи коротких размеров, необходимость пересъёмки golden на Linux CI, смена ориентации на книжную); - откат — один revert (дельта лишь добавила упоминание `docs/PDF-EXPORT.md` в перечень возвращаемого, не изменив механику). ## Что проверено и корректно (этот раунд) - Обе находки r1 закрыты предметно, текстом, а не декларацией — таблица выше. - Новый AC7 сформулирован проверяемо и называет способ доказательства из разрешённого набора. - Новый п.6 контракта не оставляет гарантию, отменяемую AC1, без замены — два новых мутанта AC1/AC2 покрывают тот же риск. - Цитаты ТЗ из `docs/PDF-EXPORT.md` (строки 41-42, 58-59) сверены с файлом дословно — расхождений нет. - Дельта не расширяет скоуп и не меняет ни одного количественного значения из AC1–AC6. ## Чего не проверял - Не переоткрывал разделы, не затронутые дельтой (продуктовая рамка, AC1–AC6, риски, блок допущений) — согласно PROCESS §2.10 п.4, это наследуется из r1 без повторной проверки; см. раздел выше. - Не проверял `demo/smoke_pdf_export.mjs` и golden-фикстуры повторно — дельта их не касается, а r1 уже отметил их как непроверенные построчно (без видимого зазора при беглом grep) и это не изменилось. - Не запускал никаких гейтов (`tsc`, `test`, `build`) — этап ТЗ, продуктового кода ещё нет, оценивать нечем; так же было отмечено в r1. - Не проверял точность самих числовых замеров аналитики S2 повторно — это вне дельты, r1 объяснил, что это воспроизводимый прогон Node-скрипта, а не предмет ревью ТЗ. ## Вердикт Зелёный. Обе находки предыдущего раунда (Medium — документация не названа в скоупе/release-артефактах; Low — судьба мутанта `pdf-room-edge-dropped` не решена) закрыты предметной правкой текста ТЗ, а не заявлением. Дельта не вносит новых High- или Medium-находок: новый AC7 проверяем, новый п.6 контракта не оставляет отменяемую AC1 гарантию без замены. Остальное ТЗ (продуктовая рамка, AC1–AC6, риски, блок допущений, откат) не затронуто дельтой и наследуется из r1 без повторной проверки. --- Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0 --- ## Материал раунда - Тело issue #530: снимок `2026-09-11T09:48:17Z` (текущее состояние на момент ревью), сверено побайтово (`diff -u`, единственное расхождение — конечный перевод строки). - Предыдущий материал (r1): тело issue, снимок `2026-09-11T09:37:32Z`, `sha256: 3c6248e088a3948ba1b6a44fafbc15118b8ad7706edb941b92b3fd9a4c8135ac` — совпадает с якорем `docs/reviews/SPEC-REVIEW-530-r1.md`. - Источник снимков: GraphQL `Repository.issue.userContentEdits.nodes[].diff` (5 правок автора Matysh между `09:15:46Z` и `09:48:17Z`), так как тело issue правится на месте и `git diff ` неприменим к этапу spec. --- ## Материал раунда - Ветка: `dev`, коммит `af2081cbc40e` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `08815814f33b6c836ecbd32deabf793353fc1bb1` ``` git log --all --format='%H %T' | grep 08815814f33b ``` - Тело issue: `53fbdfb3b618a562787718e73ee5aa952eda2d9846768cdac42273c2de7ec382` - Вердикт конвейера: `green` · High 0