10 KiB
CODE-REVIEW #555 · r2
Материал: d0ecd5383155bfa39c84d66961f89c8f829db279 (рабочая копия — HEAD detached на этом SHA).
Предыдущий раунд: r1, вердикт красный, документ на e3bf893e3d76b3276a548ee9182f073c901a12e9
(см. комментарий issue от 2026-09-13T12:43:17Z).
Скоуп раунда
r1 нашёл один High: в relabel() вторая («rollback») попытка --add-label не проверялась,
а addComment() стоял в коде после relabel() — при отказе восстановления диагностика не
публиковалась и терялась запись в summary.json, создавая риск бесконечного молчаливого
цикла retry без единой эскалации. Остальная матрица decideReconciliation и race-guard
«перечитать перед записью» были приняты r1 без замечаний.
Дельта r1→r2 (git diff e3bf893e..d0ecd538 --stat):
.github/workflows/process-reconcile.yml | 1 +
docs/reviews/CODE-REVIEW-555-r1.md | 242 ++++++++++++++++++++++
scripts/mutation-gate.mjs | 35 +++++
scripts/process-reconcile.mjs | 76 +++++++---
test/process-reconcile.test.mjs | 64 ++++++++-
test/review-doc-guard.test.mjs | 2 +
Дельта локальна: правка одной функции записи (relabel), выделение write-path в
applyReconciliationDecision, обработка ошибки в reconcileAll и один if: always()
в workflow. Логика decideReconciliation (матрица фикстур) не тронута — полный разбор
не требуется, разбираю дельту и то, до чего она дотягивается (сам write-path и его тесты).
Закрытие раунда r1
| находка r1 | чем закрыта | где видно |
|---|---|---|
Вторая попытка --add-label не проверялась, ошибка первой маскировала реальный результат |
relabel() теперь использует результат второй попытки как авторитетный: if (restore.status !== 0) throw ... |
scripts/process-reconcile.mjs:296-305 (было: безусловный throw с данными первой попытки) |
addComment() шёл после relabel() → при отказе восстановления диагностика не публиковалась |
Write-path вынесен в applyReconciliationDecision: сначала ops.comment(...), затем (только для retry) ops.relabel(...) |
scripts/process-reconcile.mjs:314-320 |
Исключение из relabel/addComment не перехватывалось в reconcileAll, весь снимок тика терялся |
Вызов обёрнут в try/catch; ошибка кладётся в record.error, инкрементирует failures, цикл for продолжается |
scripts/process-reconcile.mjs:375-383 |
--output=summary.json не писался при исключении → upload-artifact без if: always() терял снимок |
CLI пишет summary.json до установки process.exitCode; exitCode=1 выставляется только после записи файла, если summary.failures |
scripts/process-reconcile.mjs:415-423 |
upload-artifact не имел if: always() |
Добавлен if: always() к шагу «Опубликовать компактный machine-readable итог» |
.github/workflows/process-reconcile.yml:52 |
Находка была в скоупе задачи и правится в этой же ветке — отдельный issue не заводился (соответствует правилу для High без выхода за скоуп).
Как проверялось
Прочитан полный diff git diff e3bf893e..d0ecd538 (все 6 файлов). Прогнано точечно (Validate
на этом SHA уже green — см. ниже, полный набор не перегонял):
node --test test/process-reconcile.test.mjs— 11/11 green, включая 3 новых теста на write-path (порядок comment→relabel при отказе комментария, авторитетность второй попытки--add-label, сохранение ошибки в summary без обрыва снимка).node --test --test-name-pattern="#555" test/review-doc-guard.test.mjs— 1/1 green, включая новую проверкуif: always()в workflow-файле; эта проверка умеет падать — строкиif: always()не было в файле на SHA r1 (видно из diff).- Три новых mutation witness прогнаны точечно через
node scripts/mutation-gate.mjs --id=<id>для каждого изprocess-reconcile-relabels-before-durable-marker,process-reconcile-ignores-failed-label-restore,process-reconcile-write-error-aborts-summary— все 1/1 «поймано», подтверждает заявление автора «3/3». - Прочитан весь итоговый
reconcileAll/relabel/applyReconciliationDecisionпострочно — подтверждено, чтоrecord.appliedпо умолчаниюfalse, ошибка одной задачи не растёт вmutations,failuresне сбрасывается между итерациями цикла,alreadyReportedзащищает от повторной публикации комментария на следующем тике (комментарий уже сохранён к моменту отказа relabel — проверено чтением, не исполнением, т.к. это требует реального GitHub API). - Проверено
git diff --stat -- src/— пусто, docs-гейт (check-docs.mjs) не требуется. Геометрия/layout/marker.space/толщины не задеты — инварианты модели не требуются. Diff не касается рендера — golden не требуется.custom_components/**/*.pyне тронут — pytest не требуется. Смоки не применимы (не браузерный код).
Чего не проверял
- Полный
npm run typecheck/npm test/npm run buildне перегонял — Validate наd0ecd538подтверждён зелёным (https://github.com/Matysh/houseplan-card/actions/runs/34758274801), код с тех пор не менялся. - Полный ночной mutation-gate (все ~200+ мутантов) не гонял — вместо этого точечно проверил 3 новых мутанта, относящихся к делте; остальные не касаются изменённого кода.
- Реальный прогон workflow
process-reconcile.ymlна живой очереди GitHub Actions (нет доступа/не требуется по PROCESS.md — ручное тестирование в цикле не предусмотрено). - Расхождение SHA в ссылках на Validate (автор в комментарии r2 привёл
.../actions/runs/34758118947, а этому раунду дан.../actions/runs/34758274801) не расследовал отдельно: обе ссылки относятся к одному SHAd0ecd538, а прогон, названный ревьюеру как подтверждённый для этого материала, зелёный — этого достаточно для решения не перегонять дорогие гейты.
Унаследовано из r1
Без повторной проверки приняты (документ r1: комментарий issue от 2026-09-13T12:43:17Z,
на SHA e3bf893e3d76b3276a548ee9182f073c901a12e9; эта часть кода не в дельте r2):
- Матрица
decideReconciliationпо всем фикстурам AC (cancellation/timeout/guard failure/потерянное событие/applied verdict/running) — 8/8 green на r1, код функции не изменился в дельте r2. - Race-guard «перечитать перед записью» (сам факт двойного чтения
snapshot/processRunsперед мутацией) — логика не менялась, изменилось только то, что происходит после неё (сама операция записи), что и было предметом High-находки. - Идентичность прогона по
run-name, трейлеры и класс изменений — не затронуты дельтой.
Вердикт
High из r1 закрыт: показано построчно (таблица выше), тесты и mutation witnesses
подтверждают, что фикс действительно закрывает сценарий (комментарий персистентен до
фолбл relabel, вторая попытка restore авторитетна, ошибка записи не топит весь снимок
тика). Новых находок в дельте не обнаружено. Трейлеры (Issue: #555, User-Visible: no)
корректны, changelog не требуется — изменение не видно пользователю продукта.
Зелёный.
Материал раунда
- Ветка:
issue/555-process-reconciler, коммитd0ecd5383155— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
8133acd9b590c43a9bc938d35fef2a570f2c9c84git log --all --format='%H %T' | grep 8133acd9b590 - Тело issue:
61feb887bc5d5cae5bac106b48e7f07161e697938ac473cc87c5e47a8ca8d4e6 - Вердикт конвейера:
green· High 0