---
work_item: ORCH-098
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-10
model_used: claude-opus-4-8
---

# 03 — Критерии приёмки (Acceptance Criteria): ORCH-098 — FND: машинный журнал уроков

Work Item: **ORCH-098** · Repo: **orchestrator** · Стадия: analysis

Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** (что
считается провалом). Reviewer/tester проверяет их буквально по файлам репозитория и тестам.

---

## AC-1 — Аддитивная таблица уроков

**Условие:** `db.init_db()` создаёт таблицу `lessons` идемпотентно.
- **PASS:** в `src/db.py` есть `CREATE TABLE IF NOT EXISTS lessons (...)` со всеми полями
  (`lesson_type`, контекст `work_item_id/task_id/stage/agent/repo`, `root_cause`, `suggestion`,
  `status`+`related_task`, `created_at`); повторный `init_db()` не падает и не дублирует; таблица
  создаётся на общей прод-БД без изменения существующих таблиц.
- **FAIL:** таблицы нет / создаётся не идемпотентно / отсутствует любое поле из BR-1 / меняется
  схема существующей таблицы.

---

## AC-2 — Поля атрибуции присутствуют с самого начала

**Условие:** схема `lessons` несёт нуллабельные колонки атрибуции (требование Славы 10.06).
- **PASS:** колонки `attribution` (`platform`/`project`/`both`/`unknown`), `target_repo`,
  `target_domain` существуют сразу, нуллабельны, допускают пустое/`unknown` при автозаписи и
  проставляются позже через update.
- **FAIL:** хотя бы одной из трёх колонок нет в исходной схеме / колонка `NOT NULL` без дефолта /
  атрибуцию нельзя проставить после создания записи.

---

## AC-3 — Автозапись ≥2–3 типов отклонений

**Условие:** из кода автоматически (best-effort, `source="auto"`) пишутся минимум 2–3 типа уроков.
- **PASS:** есть врезки `lessons.record(...)` минимум в двух-трёх точках из:
  `stage_engine._handle_qg_failure_rollbacks` (gate-fail/откат), `merge_gate` (HOLD/transient),
  `launcher` (timeout/transient-requeue); интеграционный тест подтверждает появление строки в
  `lessons` после смоделированного отклонения.
- **FAIL:** автозаписи нет / реализован <2 типов / врезка может бросить исключение в горячий путь.

---

## AC-4 — Read-only выборка

**Условие:** уроки можно прочитать через эндпоинт и сводку в `GET /queue`.
- **PASS:** `GET /lessons` возвращает `200` с массивом уроков, поддерживает фильтры
  (type/status/repo/work_item/limit); `GET /queue` содержит read-only блок `lessons`; ни один
  путь чтения не мутирует данные.
- **FAIL:** эндпоинта нет / не фильтрует / чтение мутирует данные / блока в `/queue` нет.

---

## AC-5 — Ручная запись и обновление

**Условие:** оператор/Стрим кладёт урок руками и может его доклассифицировать.
- **PASS:** `POST /lessons` создаёт урок (`source="manual"`, можно задать атрибуцию); обновление
  (`POST /lessons/{id}` или поля) меняет `status`/`attribution`/`target_*`/`related_task` и
  стампит `updated_at`.
- **FAIL:** ручной записи нет / нельзя проставить атрибуцию / нельзя обновить автозаписанный урок.

---

## AC-6 — never-raise (сбой журнала не роняет конвейер)

**Условие:** любая ошибка записи/чтения урока изолирована от пайплайна.
- **PASS:** все публичные функции `src/lessons.py` и все врезки обёрнуты так, что исключение БД/
  любого источника → `logger.warning` + безопасный дефолт (`None`/`[]`/`{}`); юнит-тест с
  замоканной падающей БД подтверждает, что вызывающий код (откат/HOLD/retry) не падает.
- **FAIL:** исключение из журнала пробивается в `stage_engine`/`merge_gate`/`launcher`/эндпоинт.

---

## AC-7 — Kill-switch и нулевая регрессия

**Условие:** `lessons_enabled=False` делает функционал инертным.
- **PASS:** при `False` `record`/`get`/`update`/`snapshot` — no-op (без обращения к БД), эндпоинты
  отдают `{"enabled": false}`, врезки не пишут; поведение конвейера и `GET /queue` (помимо нового
  блока) — байт-в-байт прежнее; enduro-trails не затронут.
- **FAIL:** при `False` журнал что-то пишет/ломает / меняется поведение конвейера / затронут enduro.

---

## AC-8 — Инварианты конвейера не тронуты

**Условие:** изменение не касается машины стадий и гейтов.
- **PASS:** `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, функции `check_*`, machine-verdict-ключи и
  схема существующих таблиц — **диффом не затронуты**; журнал не влияет на продвижение по стадиям.
- **FAIL:** изменён любой из перечисленных артефактов / журнал участвует в решении гейта.

---

## AC-9 — Тесты, документация, CHANGELOG

**Условие:** изменение проверено и задокументировано.
- **PASS:** `pytest tests/ -q` зелёный (включая новый `tests/test_lessons.py` с unit+integration);
  обновлены `CLAUDE.md` + `docs/architecture/README.md`; в задаче есть `06-adr/` (архитектор);
  `CHANGELOG.md` дополнен.
- **FAIL:** тесты падают / нет покрытия новой логики / документация или CHANGELOG не обновлены.

---

## Сводная матрица AC ↔ FR/BR
| AC | Покрывает |
|----|-----------|
| AC-1 | BR-1 / FR-1 |
| AC-2 | BR-2 / FR-1 / NFR-6 |
| AC-3 | BR-3 / FR-2 / FR-3 |
| AC-4 | BR-4 / FR-4 |
| AC-5 | BR-5 / BR-6 / FR-5 |
| AC-6 | NFR-1 / FR-2 |
| AC-7 | NFR-2 / NFR-3 / FR-6 |
| AC-8 | NFR-3 / FR-6 |
| AC-9 | NFR-1…NFR-7 (верификация) |
