---
verdict: APPROVED
work_item: ORCH-098
stage: review
author_agent: reviewer
status: approved
created_at: 2026-06-10
model_used: claude-opus-4-8
type: review
work_item_id: ORCH-098
version: 1
---

# Review ORCH-098 — FND: машинный журнал уроков

## Summary

Реализация полностью соответствует ТЗ (`02-trz.md`), критериям приёмки (`03-acceptance-criteria.md`)
и ADR-001/adr-0034. Введён чистый observer-leaf `src/lessons.py` (never-raise, единственный
kill-switch `lessons_enabled`, без repo-скоупа — по решению D2), аддитивная идемпотентная таблица
`lessons` с нуллабельными колонками атрибуции сразу (NFR-6, требование Славы 10.06), 4 типа
автозаписи best-effort, дедуп для `auto`, три HTTP-эндпоинта + блок `lessons` в `GET /queue`.

**Инварианты конвейера не тронуты (AC-8):** `src/stages.py` (`STAGE_TRANSITIONS`), `src/qg/checks.py`
(`QG_CHECKS`/`check_*`), `src/merge_gate.py`, machine-verdict-ключи и схемы существующих таблиц —
**диффом не затронуты** (подтверждено `git diff --name-only`). `tests/test_lessons.py` (TC-01…TC-12,
13 тестов) — **зелёный** локально. Документация обновлена в том же PR.

Все findings — P2/P3 (advisory), блокеров нет.

## Findings

### P0 — Blocker
- Нет.

### P1 — Must fix
- Нет.

### P2 — Should fix
- [ ] **Кросс-задачный дедуп `transient_retry` теряет сигнал.** Врезка в
  `launcher._finalize_transient` (`src/agents/launcher.py:~1024`) передаёт `task_id`, но **не**
  `work_item_id` и **не** `stage` → ключ дедупа `db.lessons_recent_dup_exists` становится
  `(work_item_id IS NULL, lesson_type='transient_retry', stage IS NULL)`. В окне
  `lessons_dedup_window_s` (дефолт 1ч) **разные** задачи, исчерпавшие бюджет ретраев, схлопываются в
  одну запись — теряется урок про вторую задачу. Поскольку `task_id` локально доступен, дедуп-ключ
  стоило бы доопределять им при `work_item_id is None` (или включать `task_id` в ключ дедупа).
  Это observer/best-effort (не влияет на конвейер, AC-3 формально выполнен — 4 типа автозаписи
  работают), потому не блокер, но ослабляет ценность самого сигнала, ради которого фича вводится.
  Ссылка: ADR-001 D4 («ключ `work_item_id+stage+lesson_type`»).

### P3 — Nice to have
- [ ] **Мелкая неточность ADR vs код.** `06-adr/ADR-001` (D3, таблица) и `adr-0034` указывают
  choke-point `merge_hold` как `merge_gate._handle_merge_verify`, фактически `_handle_merge_verify`
  живёт в `src/stage_engine.py` (туда и врезан `merge_hold`; `merge_gate.py` диффом не тронут).
  Функционально корректно; рекомендуется поправить адрес в ADR для трассировки. Также
  `transient_retry` в `merge_gate` (merge-retry exhausted) не реализован — но ADR формулирует это как
  «**and/or** launcher», т.е. опционально; реализация через launcher достаточна.

## Документация

**Обновлена полностью в том же PR — ось «документация» PASS:**
- `CLAUDE.md` — добавлен раздел «Машинный журнал уроков (ORCH-098)» (D1–D5, флаги, инвариант).
- `docs/architecture/README.md` — компонент «Lessons journal», строка таблицы `lessons` в разделе
  схемы БД, три новых эндпоинта в таблице API, обновлена строка `GET /queue` (`+ lessons (ORCH-098)`).
- `docs/architecture/adr/adr-0034-lessons-journal.md` — сквозной ADR (новый).
- `docs/work-items/ORCH-098/06-adr/ADR-001-lessons-journal.md` — локальный ADR (присутствует).
- `CHANGELOG.md` — запись `[Unreleased]` с разбивкой D1–D5 + регресс.
- `README.md` «Известные ограничения» — пунктов, закрываемых этой задачей, нет (ORCH-079 N/A).

Изменение `src/` ⇒ требование «документация = golden source» выполнено; основание для
`REQUEST_CHANGES` по оси документации отсутствует.
