Files
houseplan-card/docs/reviews/CODE-REVIEW-596-r1.md
T
2026-09-19 07:43:58 +00:00

13 KiB
Raw Blame History

Код-ревью #596 — заход r1

Материал: 337d85ea4c40db8308e35caac4303254c0874306 (ветка issue/596-merge-candidate-maxbuffer, один коммит). Класс B: scripts/merge-candidate.mjs, test/merge-candidate.test.mjs, scripts/mutation-registry.mjs. Файлов класса A нет. Трейлеры коммита: Issue: #596, User-Visible: no — верно, изменение внутреннего инструмента конвейера, продуктового поведения не касается.

Скоуп задачи

sh() в scripts/merge-candidate.mjs вызывал spawnSync без maxBuffer (умолчание 1 МиБ). Шаг слияния считает patch-id материала и кандидата через git diff --full-index, когда dev сдвинулся за время ревью; дифф задачи, пересобирающей бандл, несёт три копии houseplan-card.js — воспроизведено на #594 (7 103 616 байт). Процесс убивался по ENOBUFS, status становился null, а status: r.status ?? 1 выдавало это за «git вернул 1» — с усечённым stdout в сообщении об ошибке (must() берёт stderr || stdout, stderr был пуст). Задача — issue #596, инфраструктурная, класс B, входит в флоу на S7-code-review сразу (#562).

Как проверялось

Дешёвые гейты (npx tsc --noEmit, npm test, npm run build) на этом SHA уже зелёные — Validate https://github.com/Matysh/houseplan-card/actions/runs/35429741728, повторно не гонял. node scripts/check-docs.mjs не нужен: diff не трогает src/** (только scripts/** и test/**). Инварианты модели не нужны: diff не трогает геометрию, layout, marker.space, open_spans, толщину стен. Браузерные смоки не нужны: смоки исполняют собранный бандл и DOM (src/**/demo/**), а диффа там нет — смок-раздел smoke-select.mjs рассчитан на связь с изменённым исполняемым символом фронтенда, здесь такого символа нет в принципе (правка Node-скрипта CI, не фронтенда). golden:verify, pytest tests_backend, перф-профили — не применимы, не тронуты соответствующие пути.

Ручная проверка (то, что Validate не покрывает и что специфично для этой задачи):

  1. Прочитал итоговый scripts/merge-candidate.mjs: sh() теперь передаёт maxBuffer: MAX_COMMAND_OUTPUT_BYTES (256 МиБ) первым в объекте опций, opts может его переопределить (порядок спреда { encoding, maxBuffer, ...opts } корректен — опции вызова, если есть, побеждают дефолт, как и раньше для остальных полей). r.error теперь читается: причина (ENOENT, ENOBUFS, таймаут) уходит в stderr результата, и при её наличии status принудительно 1 (не полагается на r.status ?? 1, которое для провала запуска всегда null).
  2. Прогнал целевые тесты изолированно: node --test --test-name-pattern="#596" test/merge-candidate.test.mjs → 2/2 pass.
  3. Дисциплина «тест умеет падать» — не поверил заявлению автора, воспроизвёл мутацию вручную импортом модуля и вызовом sh() с maxBuffer: 1024*1024 (то же, что делает мутант merge-candidate-truncates-the-candidate-diff в scripts/mutation-registry.mjs) на 3 МиБ вывода:
    status 1  stderr "…/node не выполнился: ENOBUFS"  stdoutLen 1114112
    
    Тест AC1 (assert.equal(r.status, 0, …)) в этом состоянии красный, как и заявлено. Причём само сообщение об ошибке называет ENOBUFS, а не отдаёт усечённый stdout — это и есть AC2 в действии на конкретном сценарии дефекта.
  4. Проверил, что scripts/mutation-registry.mjs синтаксически корректен и грузится (node -e "import('./scripts/mutation-registry.mjs')" → ok), новый элемент массива не ломает соседние (declared-baseline-review-run-never-checked идёт следом без искажений), id уникален по файлу.
  5. Проверил, что экспорт sh не используется больше нигде в scripts/*.mjs за пределами merge-candidate.mjs — расширение публичного API файла не создаёт скрытых потребителей и не меняет поведение realOps (сигнатура sh не поменялась, только опции по умолчанию и структура stderr).
  6. Просмотрел must(): stderr || stdout — при заполненном stderr (наш случай после исправления) в исключение уходит причина, а не дифф. Логика согласована с текстом AC2.

AC — построчно

  • AC1 (вывод >1 МиБ доезжает целиком, status: 0) — доказано автотестом test/merge-candidate.test.mjs:397-404, тест реальный (гоняет процесс, не заглушку), падение при откате правки подтверждено вручную (см. п.3 выше). Выполнено.
  • AC2 (сбой запуска называет причину в stderr, must() печатает её) — доказано автотестом test/merge-candidate.test.mjs:406-413 и чтением must() (scripts/merge-candidate.mjs:134, ${what}: ${r.stderr || r.stdout}). Выполнено.
  • AC3 (существующие тесты остаются зелёными, подмена exec не ломается) — сигнатура sh(cmd, args, opts) и форма возврата {status, stdout, stderr} не изменились, только дефолт maxBuffer и заполнение stderr при r.error; realOps конструирует git/exec через тот же sh по умолчанию, тестовые фикстуры подменяют exec целиком и его не затрагивают. Подтверждено прогоном Validate на этом SHA (2775/2776, 1 skip — без регрессии) и точечным прогоном новых тестов. Выполнено.
  • AC4 (мутант снимает maxBuffer, AC1 краснеет) — мутант merge-candidate-truncates-the-candidate-diff (scripts/mutation-registry.mjs:9940-9951) возвращает maxBuffer к 1024*1024; воспроизвёл эффект вручную (п.3) — AC1 действительно краснеет, и вдобавок причина в этом состоянии верно называется ENOBUFS, а не молчит. Выполнено.
  • AC5 (npm test, npm run typecheck, npm run build зелёные) — покрыто Validate на этом же SHA (ссылка выше), повторный прогон не требовался. Выполнено.

Продуктовое рассуждение (docs/SCOPE.md)

Задача не создаёт и не меняет пользовательского поведения; это инструмент самого ревью- конвейера (§2.4/§2.9 PROCESS.md, класс B). К Core user jobs (J1–J7) прямого отношения нет — она инфраструктурная и входит в флоу по отдельному правилу (#562), что и отражено в issue. Ничего в SCOPE.md эту работу не ограничивает и не блокирует.

Находки

Нет ни одной находки уровня High или Medium. Мелочей уровня Low, которые стоило бы отметить, тоже не нашёл: код лаконичен, комментарии по существу объясняют «почему», тесты целенаправленные и действительно проверяют регрессию, а не пересказывают код.

Что проверено и корректно

  • Причина исходного дефекта (ENOBUFS на 1 027 868 байт, тот же обрыв, что в комментарии о сбое #594) устранена явным maxBuffer с большим запасом (256 МиБ) — обоснование запаса («переживает рост бандла и задачу, которая трогает бандл и эталоны одновременно») разумно для CI-скрипта, не тянет памяти сверх нормы для однократного вызова git diff.
  • Вторая половина дефекта (сокрытие r.error) устранена: must() теперь получает причину, а не огрызок диффа, который «уводит разбор в сторону» — именно то, что случилось на #594.
  • sh экспортирован и используется в тестах напрямую (не только через realOps), так что оба AC имеют исполнимого свидетеля, а не рассуждение автора.
  • Мутационный тест зарегистрирован по тем же соглашениям, что и соседние записи в файле (guard, because, patches), синтаксис файла не нарушен.
  • Скоуп изменения не выходит за пределы заявленных трёх файлов, продуктовый код не тронут.

Чего не проверял и почему

  • npx tsc --noEmit, npm test (полный), npm run build со сверкой бандла — не гонял повторно: зелёный Validate уже есть на этом точном SHA (ссылка выше), диффа не по бандлу и не по типам, что подтверждается локальным точечным прогоном тестов файла.
  • node scripts/check-docs.mjs — diff не трогает src/**, гейт не устареет от этой правки.
  • npm run invariants — diff не трогает геометрию/толщину стен/layout/marker.space.
  • Браузерные смоки (demo/smoke_*.mjs) — diff не трогает фронтенд/бандл/DOM, смоки его в принципе не исполняют; smoke-select.mjs не запускал, так как предмет диффа (Node-скрипт CI-инструмента) вне домена, который инструмент оценивает.
  • npm run golden:verify — diff не может изменить видимый рендер.
  • python -m pytest tests_backend -q — custom_components/**/*.py не тронут.
  • Перф-профили — не названы в AC, чувствительные к перфу пути не тронуты.
  • node scripts/process-gate.mjs --issues — автор отметил недоступность gh в песочнице; это гейт публикации/CI, а не код-ревью, отдельно не гонял.

Вердикт

Все пять AC выполнены и доказаны исполнимыми тестами, один из которых (мутационный) я воспроизвёл вручную и убедился, что он действительно ловит регрессию, а не просто существует. Скоуп не нарушен, продуктовый код не затронут, трейлеры корректны. Находок нет.

Зелёный.


Материал раунда

  • Ветка: issue/596-merge-candidate-maxbuffer, коммит 337d85ea4c40 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 24ca27d21d24b5d66aba292f22d8e5aafd51c074
    git log --all --format='%H %T' | grep 24ca27d21d24
    
  • Тело issue: 9b589ac902b977dac360fa27b962cac1d90e89020e3987a38de00c4aae14c2df
  • Вердикт конвейера: green · High 0