work_item: ORCH-098
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-10
model_used: claude-opus-4-8
title: "Журнал уроков: таблица, автозапись отклонений, выборка, ручная запись, never-raise"
framework: pytest
scope: >
  Покрывается: создание аддитивной таблицы lessons (идемпотентность, поля атрибуции),
  helper записи record(), автозапись из choke-point (gate-fail/HOLD/transient), read-only
  выборка get_lessons + snapshot, ручная запись/обновление, kill-switch, never-raise.
  Вне покрытия: ретроспективщик (E2), приоритизатор (E3), автоклассификация атрибуции,
  слой-3 детекция здоровья продукта.
notes: >
  Тесты используют изолированную временную SQLite-БД (фикстура init_db во временном файле).
  Полный регресс tests/ должен оставаться зелёным. Self-hosting: журнал never-raise — ни один
  тест не должен показать, что сбой записи урока роняет конвейер.

tests:
  - id: TC-01
    type: unit
    description: "init_db() создаёт таблицу lessons идемпотентно (двойной вызов не падает, нет дублей); присутствуют все поля BR-1."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-02
    type: unit
    description: "Схема lessons несёт нуллабельные колонки атрибуции attribution/target_repo/target_domain; запись без них проходит (NULL/unknown), update проставляет их позже."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-03
    type: unit
    description: "lessons.record() вставляет строку с переданными полями (source=auto/manual), возвращает id; created_at заполняется."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-04
    type: unit
    description: "never-raise: при замоканной падающей БД record/get/update/snapshot возвращают None/[]/{} и не бросают исключение (logger.warning)."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-05
    type: unit
    description: "kill-switch: при lessons_enabled=False record/get/update/snapshot инертны (no-op, без обращения к БД)."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-06
    type: unit
    description: "get_lessons фильтрует по type/status/repo/work_item и соблюдает limit; порядок ORDER BY id DESC."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-07
    type: unit
    description: "update_lesson меняет status/attribution/target_*/related_task и стампит updated_at; несуществующий id безопасен."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-08
    type: integration
    description: "Автозапись gate-fail: смоделированный откат на development в _handle_qg_failure_rollbacks создаёт строку lessons type=gate_failure с контекстом (stage/agent/work_item/repo)."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-09
    type: integration
    description: "Автозапись transient/HOLD: транзиент-ветка merge_gate (или timeout/requeue launcher) пишет урок type=transient_retry/merge_hold; сбой записи не ломает основной путь (never-raise в горячем пути)."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-10
    type: integration
    description: "GET /lessons возвращает 200 с массивом и фильтрами; GET /queue содержит read-only блок lessons; чтение не мутирует данные."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-11
    type: integration
    description: "POST /lessons создаёт ручной урок (source=manual, с атрибуцией); POST /lessons/{id} обновляет его; при lessons_enabled=False эндпоинты отдают {enabled:false}."
    module: tests/test_lessons.py
    expected: PASS

  - id: TC-12
    type: unit
    description: "Инварианты конвейера не тронуты: STAGE_TRANSITIONS/QG_CHECKS/machine-verdict-ключи неизменны (структурный анти-регресс по составу реестра)."
    module: tests/test_lessons.py
    expected: PASS
