Merge-gate auto_rebase_onto_main bounced this branch back: ORCH-100 landed in main first and claimed global ADR number adr-0033 (adr-0033-sidecar-watchdog), while this branch had created adr-0033-lessons-journal. Resolved the genuine land race: - rebased feature/ORCH-098-fnd onto current origin/main (linear history) - resolved docs/architecture/README.md component-list conflict — both the Lessons-journal and Sidecar-watchdog bullets now coexist - renamed docs/architecture/adr/adr-0033-lessons-journal.md → adr-0034-lessons-journal.md (next free global ADR number) + fixed the in-file header - updated all cross-references (CLAUDE.md, README.md, work-item ADR-001, 12-review.md) 0033→0034 for the lessons journal; ORCH-100's adr-0033 (sidecar) left intact - recovered the ORCH-098 CHANGELOG entry silently dropped by the rebase auto-merge (now above ORCH-100, ADR ref corrected to 0034) No code semantics changed; src/** auto-merged cleanly (ORCH-100 did not touch src/**). ruff: n/a locally (CI). pytest tests/ -q: 1630 passed. Refs: ORCH-098 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
93 lines
7.4 KiB
Markdown
93 lines
7.4 KiB
Markdown
---
|
||
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`.
|