docs: review document for #551
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 40s
Проверка (CI) / Классификация изменённых файлов (push) Successful in 32s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Skipped
Проверка (CI) / HACS: валидация репозитория (push) Skipped
Проверка (CI) / Hassfest: манифест интеграции (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Failing after 46s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 25s

Issue: #551
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-13 11:52:39 +00:00
parent 62e0eff219
commit c83f19ae52
+188
View File
@@ -0,0 +1,188 @@
# CODE-REVIEW-551-r4
## Скоуп
Issue #551 (инфраструктурная, метка `infra`) — разнесение конвейера ревью на
три job с независимыми бюджетами. Раунды r1–r3 уже признали конструкцию
выполненной (зелёный, без находок); в задачу этот вердикт до сих пор не
применён — оба последних захода (r2, r3) закончились зелёной моделью, но
**стадия публикации/интеграции падала** на живых прогонах уже после
слияния/повторной активации `process.yml`, и автор возвращал задачу в
`S6-in-progress` для фикса именно интеграции, а не по замечанию ревью.
Материал этого раунда — ровно `62e0eff21999eebfc9aecbfe99edb14d9ccf716f`.
Дельта отсчитывается от материала r2/r3 (`23f48829c26deec968af6645301900880899139e`,
дельта r2 от r3 — нулевая, см. ниже): один коммит `62e0eff2` поверх
`23f48829`, 3 файла, +26/−5 строк (`git diff --shortstat 23f48829..62e0eff2`):
- `.github/workflows/process.yml` — шаг «Проверить полноту и происхождение
результата» в job `integrate`: запись `structured_output` в
`$GITHUB_OUTPUT` заменена с `{ echo <<EOF_RESULT; cat verdict.json; echo
EOF_RESULT; }` на явный `printf` с гарантированным `\n` перед закрывающим
delimiter'ом;
- `scripts/mutation-gate.mjs` — новый мутант
`review-integration-delimiter-follows-json-without-newline`;
- `test/review-doc-guard.test.mjs` — два новых `assert` в существующем тесте
`#551: gates, модель и интеграция…`, закрепляющие фикс.
Класса A нет (тот же трек, что r1–r3), ТЗ/ревью ТЗ не требуются.
## Что чинит эта дельта (по логам, не со слов автора)
Живой дефект: run [34754400084](https://github.com/Matysh/houseplan-card/actions/runs/34754400084)
упал в `integrate` с `Matching delimiter not found` при чтении
`structured_output`. Причина в бывшем коде: `cat "$dir/verdict.json"` пишет
JSON как есть (если файл без завершающего LF — а `jq -e`/сериализаторы это не
гарантируют), а следующая по счёту команда `echo 'EOF_RESULT'` в блоке `{ ...
} >> file` — это отдельный `write()`, который ничего не подставляет между
предыдущим выводом и своим: без LF в конце JSON delimiter приклеивается к
последней строке значения, и GitHub Actions не находит начало delimiter'а на
отдельной строке.
Фикс (`process.yml:1212-1216` после дельты):
```
printf 'structured_output<<EOF_RESULT\n' >> "$GITHUB_OUTPUT"
cat "$dir/verdict.json" >> "$GITHUB_OUTPUT"
printf '\nEOF_RESULT\n' >> "$GITHUB_OUTPUT"
```
Явный `\n` перед `EOF_RESULT` гарантирует делимитер на отдельной строке
независимо от того, кончается ли `verdict.json` переводом строки. Если файл
уже кончается LF — на выходе будет один лишний пустой перенос перед
delimiter'ом; это не влияет на `jq`-парсинг ниже по конвейеру (`OUT: ...
structured_output`, `printf '%s' "$OUT" | jq -r`) — JSON с хвостовым
whitespace для `jq` эквивалентен исходному. Прочитано и проверено вручную.
Это прямое попадание в AC3 issue («timeout/cancel/**failure** каждой стадии
оставляет понятное состояние; лимиты review cycles не расходуются на
невыполненный review») — двумя живыми прогонами (r2→r3 и r3→r4) подтверждено,
что счётчик циклов действительно не тратился на несостоявшуюся интеграцию
(«блокирующих циклов израсходовано 0 из 4» в постановке этого захода), т.е.
защитное свойство AC3 сработало даже при поломке самой публикации.
## Проверено
- `git diff 23f48829..62e0eff2` — три файла, ровно эти три хунка, ничего
лишнего; job `guard`, `prepare`, `model_review`, структура контракта,
проверки sha256/полей — не тронуты ни строкой.
- Прочитано построчно: новый код в `process.yml:1212-1216` синтаксически и
семантически корректен (см. разбор выше), остальной шаг («Проверить
полноту и происхождение результата») не менялся вне этих трёх строк.
- Независимо (не полагаясь на заявление автора) прогнал:
- `node --test --test-name-pattern="#551" test/review-doc-guard.test.mjs`
→ 1/1 pass;
- `node scripts/mutation-gate.mjs --id=review-integration-delimiter-follows-json-without-newline`
→ «чистый прогон» + «заявленный тест покраснел на мутанте» → **поймано 1
из 1**. Мутант возвращает ровно старый баг (`printf 'EOF_RESULT\n'` без
ведущего `\n`), и новый `assert.match(resultOutput, /cat ...>>
"\$GITHUB_OUTPUT"[\s\S]*printf '\\nEOF_RESULT\\n' >>.../)` действительно
красится на этом мутанте — дисциплина «тест умеет падать» соблюдена для
прогнанного мной гейта.
- `node scripts/smoke-select.mjs --base 23f48829 --head 62e0eff2` →
«Исполняемого frontend-диффа нет… Browser-smoke этим диффом не
выбираются» — выбирать нечего, `src/**` не тронут.
- ID нового мутанта уникален в реестре (`grep -n
'review-integration-delimiter-follows-json-without-newline'
scripts/mutation-gate.mjs` — одно определение), guard-команда та же, что и
у соседних #551-мутантов, конфликтов нет.
- Трейлеры коммита `62e0eff2`: `Issue: #551`, `User-Visible: no` — верно,
правка меняет только служебный CI-механизм передачи данных между stages,
видимое поведение продукта не меняется; changelog не требуется.
Не перегонял `npx tsc --noEmit` / `npm test` / `npm run build` + сверку трёх
копий бандла — Validate зелёный на этом точном SHA:
https://github.com/Matysh/houseplan-card/actions/runs/34754874119 (упомянут
автором и подтверждён как ссылка на прогон именно этого коммита). Не
перегонял `check-docs.mjs`, `golden:verify`, backend pytest, инварианты
модели, performance-профили и полный набор browser-смоков — diff не касается
`src/**` (кроме тестового файла `test/review-doc-guard.test.mjs`, не
влияющего на отпечаток документации), Python, геометрии/`layout`/
`marker.space`/`open_spans`, визуального рендера или производительности;
`smoke-select.mjs` подтверждает пустой отбор.
## Закрытие раунда r3
r3 не содержал находок («Находок нет», зелёный, High 0 / Medium 0) — этому
раунду нечего закрывать по замечаниям. Причина существования r4 —
операционный сбой стадии публикации/интеграции ПОСЛЕ зелёного вердикта r3
(run [34754400084](https://github.com/Matysh/houseplan-card/actions/runs/34754400084)),
диагностированный автором как отдельный дефект границы стадий (delimiter
приклеен к JSON). Дельта этого раунда — прямой фикс именно этого сбоя (см.
разбор выше); других изменений кода в диапазоне r3→r4 нет.
## Унаследовано из r3 (без повторной проверки)
Документ r3 не был закоммичен в `docs/reviews/` (интеграция того захода
упала на дефекте, который правит именно материал этого раунда) — источник
вывода r3: комментарий ревью в issue #551 от 2026-09-13T11:29:02Z, материал
`23f48829c26deec968af6645301900880899139e` (дельта от r2/r3 к r4 — только
три файла выше, всё остальное дерево идентично). Наследуется:
- разнесение конвейера на три job (`prepare`/`model_review`/`integrate`) с
независимыми бюджетами 55/45/55 минут и их назначение по коду —
подтверждено в r1 (`docs/reviews/CODE-REVIEW-551-r1.md`, материал `31ef70ce`)
и повторно не тронуто ни в r2, ни в r3, ни в этой дельте;
- контракт передачи между стадиями (`prepared.json`/`manifest.sha256`,
полная сверка полей через `jq -e`, `git rev-parse HEAD`/`HEAD^{tree}`
против заявленного материала) — из r1, не задет;
- поведение при timeout/cancel/failure каждой стадии и правило «ранний отказ
не расходует цикл» — из r1, не задет; двумя живыми провалами интеграции
(r2→r3, r3→r4) это правило дополнительно эмпирически подтверждено вне
ревью;
- отказ на неполном/чужом evidence (точный набор имён файлов artifact'а,
sha256, отказ от failed/cancelled/skipped результата модели) — из r1, не
задет;
- фикс r2 «structured verdict сохраняется как объект, а не boolean предиката
`jq -e`» (`process.yml`, шаг «Запечатать результат модели», строки до
`- name: Передать результат интеграции`) и его мутационный свидетель
`review-model-seals-verdict-as-boolean` — заявлен в r2, независимо
перепроверен в r3 (`node --test --test-name-pattern="#551"` + прогон
мутанта, «поймано 1 из 1»); дельта r3→r4 этот шаг не трогает
(`git diff 23f48829..62e0eff2` не включает диапазон `seal`), поэтому
принимается без повторного прогона в этом раунде;
- три исходных мутационных свидетеля r1 (`review-model-checks-out-moving-dev`,
`review-integration-skips-evidence-checksum`,
`review-integration-trusts-failed-model`) — из r1, не задеты ни одной
дельтой r2/r3/r4.
## Найдено
Находок нет — ни High, ни Medium, ни Low.
## Не проверялось (и почему)
- Полный набор `tsc`/`test`/`build`+сверка бандла, `check-docs.mjs`,
`golden:verify`, backend pytest, инварианты модели, performance-профили,
полный набор browser-смоков — не требуются по соразмерности (PROCESS.md
§8) и по факту зелёного Validate на этом точном SHA (см. выше) плюс
пустому отбору `smoke-select.mjs`.
- Реальное поведение исправленного шага в проде за пределами уже
показанных run — не переисполнялось повторно мной (GitHub Actions
недоступен для интерактивного запуска из этого ревью); проверка —
чтением кода плюс воспроизведением локального юнит-теста и мутанта,
которые моделируют тот же самый путь (`process.yml`-текст плюс regex по
нему), а не реальным запуском workflow.
## Вердикт
Зелёный. Дельта r4 — точечный фикс одной строки протокола передачи данных
между стадиями (`GITHUB_OUTPUT` delimiter), выявленный вторым живым прогоном
после r3. Фикс корректен (разобран построчно), закреплён тестом и
мутационным свидетелем, который я прогнал независимо и подтвердил, что он
красит именно старое поведение. Всё содержательное из AC issue #551
(разнесение бюджетов, контракт, отказ на неполном evidence, поведение при
провале стадии) наследуется из r1 без изменений; правка r2 (boolean-подмена)
перепроверена в r3 и не задета этой дельтой. Находок нет, скоуп не расширен.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/551-review-stage-budgets`, коммит `62e0eff21999` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `8ea4718a882bb877b6288a234823406709f5659c`
```
git log --all --format='%H %T' | grep 8ea4718a882b
```
- Тело issue: `824f489aab52c7d76ade6634cbdb819c9c36ff9261ae2982484d5199a4bc23d0`
- Вердикт конвейера: `green` · High 0