---
work_item: ORCH-098
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---

# adr-0034: Машинный журнал уроков — таблица `lessons` + observer-leaf (ORCH-098)

## Статус
Proposed

## Контекст

Оркестратор автономно ведёт задачи по конвейеру (ORCH-54), но **развивается** вручную: инциденты →
уроки → задачи. Уроки живут свободным текстом в `memory/` — не машиночитаемы: нельзя считать
паттерны, приоритизировать, предлагать улучшения. ORCH-098 — шаг 1 эпика саморазвития (домен 0
«Фундамент», F2): «топливо» петли самообучения 8A. Нужна **структурированная таблица отклонений
конвейера**, на которой позже встанут ретроспективщик (E2), приоритизатор RICE (E3) и Стрим.

Нормативное требование Славы (10.06): схема ДОЛЖНА **сразу** нести поля **атрибуции** урока
(`platform`/`project`/`both`/`unknown` + целевой репо + домен улучшения), иначе позже придётся
переделывать схему на живой общей прод-БД.

**Кросс-каттинговость** (почему сквозной ADR): новый компонент `src/lessons.py` + аддитивная
таблица на **общей прод-БД** (self-hosting, разделяемой с enduro-trails) + врезки автозаписи в
несколько горячих choke-point'ов (`stage_engine`/`merge_gate`/`launcher`) + новый раздел контракта
`GET /queue`. Фундамент для будущих задач-потребителей → регистрируется глобально.

## Решение

Журнал уроков — **observer (наблюдатель), НЕ Quality Gate**. Аддитивная таблица + чистый leaf,
по образцу `serial_gate`/`coverage_gate`/`metrics`/`bug_fast_track`.

1. **Таблица `lessons`** (`db.init_db()`, `CREATE TABLE IF NOT EXISTS` + 3 индекса, идемпотентно,
   restart-safe) — поля контекста (`work_item_id`/`task_id`/`stage`/`agent`/`repo`), анализа
   (`root_cause`/`suggestion`), статуса (`status`/`related_task`), **атрибуции сразу и нуллабельно**
   (`attribution`/`target_repo`/`target_domain`) + `source`/`detail`. Без `enum`-констрейнтов
   (слаги forward-compatible). Будущие колонки — `_ensure_column`.

2. **Leaf `src/lessons.py`** (never-raise, импортирует только `config`+`db`): `record()` / `get()` /
   `update()` / `snapshot()`. **Расхождение с гейт-шаблоном: журнал НЕ скоупится по репо** — он
   observer-only и не *действует* ни на один репо; единственный регулятор — глобальный kill-switch
   `lessons_enabled`. Запись урока про enduro ценна и **не затрагивает** пайплайн enduro (чистая
   память орка); репо-разрез — на выборке (`repo`-колонка/фильтр).

3. **Автозапись 4 типов** (`source="auto"`, best-effort, дедуп в окне; `transient_retry` — только на
   исчерпании бюджета ретраев): `gate_failure` (`stage_engine._handle_qg_failure_rollbacks`),
   `merge_hold` (`merge_gate._handle_merge_verify` HOLD), `transient_retry` (merge-retry/launcher
   transient budget-exhaustion), `deploy_degraded` (post-deploy `DEGRADED → set_repo_freeze`, урок
   слоя-3 «деплой OK / прод сломан», ET-8). Каждая врезка — одиночный вызов в защитном `try/except`.

4. **Эндпоинты** `GET /lessons` (read-only, фильтры), `POST /lessons` (ручная запись,
   `source="manual"`), `POST /lessons/{id}` (update — доклассификация `unknown`), + read-only ключ
   `"lessons": snapshot()` в `GET /queue`. При выключенном флаге → `{"enabled": false}`.

**Инвариант (нерушимый):** `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict-ключи
(`verdict:`/`result:`/`staging_status:`/`deploy_status:`/`security_status:`/`coverage_status:`) /
схемы существующих таблиц — **байт-в-байт не тронуты**. Журнал не влияет на продвижение по стадиям.

## Композиция с существующими механизмами
- **Self-hosting (общая БД):** аддитивная таблица; enduro не затронут (NFR-3).
- **serial-gate (ORCH-088) / post-deploy (ORCH-021):** детектор `deploy_degraded` врезан рядом с
  `set_repo_freeze`, не меняя freeze-логику.
- **merge-gate (ORCH-043/071/093):** `merge_hold`/`transient_retry` читают исход актора, не меняя
  классификатор/ретрай.
- **metrics (ORCH-099):** журнал — историческая память петли (best-effort запись), `/metrics` —
  realtime-сырьё для sidecar; разные роли, оба observer-only.

## Условность и откат
- Флаг `lessons_enabled` (env `ORCH_LESSONS_ENABLED`, дефолт `True`; kill-switch) +
  `lessons_dedup_window_s` / `lessons_query_limit_default`. `False` → полная инертность, нулевая
  регрессия, конвейер байт-в-байт прежний.
- **never-raise** на всех публичных функциях и врезках (NFR-1) — сбой журнала не роняет конвейер.
- Откат — флаг в `false` (мгновенно) или revert диффа; таблица не касается существующих.

## Последствия
- **+** Машиночитаемые уроки — фундамент E2/E3/Стрим; атрибуция forward-proof (без передела живой БД).
- **+** Нулевая регрессия; проверенный additive-observer-leaf шаблон → низкий риск; enduro изолирован.
- **−** Рост таблицы (митигейшн: лёгкие строки + дедуп + budget-exhaustion; ретенция — будущее).
- **−** Дедуп-запрос в `record()` (один indexed-SELECT, только `auto`).

## Ссылки
- Локальный ADR: `docs/work-items/ORCH-098/06-adr/ADR-001-lessons-journal.md`
- BRD/TRZ/AC: `docs/work-items/ORCH-098/01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`
- Data/Infra/Risks: `docs/work-items/ORCH-098/08-data-requirements.md`, `07-infra-requirements.md`,
  `10-tech-risks.md`
- Эпик: `docs/epics/self-evolution.md` (домен 0 «Фундамент», F2; петля 8A)
- Сверено по коду: `src/serial_gate.py`, `src/coverage_gate.py`, `src/db.py`, `src/stage_engine.py`,
  `src/merge_gate.py`, `src/agents/launcher.py`, `src/main.py`, `src/qg/checks.py`.
