--- 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` по оси документации отсутствует.