Files
houseplan-card/docs/reviews/CODE-REVIEW-555-r2.md
T
2026-09-13 13:01:27 +00:00

119 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`) не
расследовал отдельно: обе ссылки относятся к одному SHA `d0ecd538`, а прогон, названный
ревьюеру как подтверждённый для этого материала, зелёный — этого достаточно для решения
не перегонять дорогие гейты.
## Унаследовано из 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 не требуется — изменение не видно пользователю продукта.
**Зелёный.**
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/555-process-reconciler`, коммит `d0ecd5383155` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `8133acd9b590c43a9bc938d35fef2a570f2c9c84`
```
git log --all --format='%H %T' | grep 8133acd9b590
```
- Тело issue: `61feb887bc5d5cae5bac106b48e7f07161e697938ac473cc87c5e47a8ca8d4e6`
- Вердикт конвейера: `green` · High 0