docs: review document for #555

Issue: #555
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-13 13:01:27 +00:00
parent d0ecd53831
commit d5e0c49198
+118
View File
@@ -0,0 +1,118 @@
# 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