--- work_item: ORCH-098 stage: architecture author_agent: architect status: proposed created_at: 2026-06-10 model_used: claude-opus-4-8 --- # 08 — Требования к данным: ORCH-098 — машинный журнал уроков `lessons` Work Item: **ORCH-098** · Repo: **orchestrator** · Стадия: architecture > When-applicable: задача **добавляет** одну таблицу на общую прод-БД. Схемы существующих таблиц — > не затрагиваются. ## Изменения схемы БД **Новая аддитивная таблица `lessons`** + три индекса, создаются идемпотентно в `db.init_db()` (`CREATE TABLE IF NOT EXISTS` / `CREATE INDEX IF NOT EXISTS`), restart-safe (паттерн `repo_freeze`, `coverage_baseline`). На уже существующей таблице новые/будущие колонки добавляются через `_ensure_column(conn, "lessons", "", "")` (`src/db.py:341`) — forward-safe, без миграции данных. DDL — см. ADR-001 D1. Существующие таблицы (`tasks`/`jobs`/`agent_runs`/`events`/`job_deps`/`repo_freeze`/ `coverage_baseline`/`tracker_messages`) — **байт-в-байт не тронуты** (NFR-3, AC-8). ## Новые/изменённые сущности Сущность **`lesson`** — одна запись структурированного отклонения конвейера. Колонки: | Колонка | Тип | Null | Назначение | |---|---|---|---| | `id` | INTEGER PK AUTOINCREMENT | — | суррогатный ключ | | `created_at` | TEXT `DEFAULT datetime('now')` | NOT NULL | момент записи | | `updated_at` | TEXT | NULL | момент последнего `update` | | `lesson_type` | TEXT | NOT NULL | slug-тип (`gate_failure`/`merge_hold`/`transient_retry`/`deploy_degraded`/…) | | `work_item_id` | TEXT | NULL | контекст: задача (`ORCH-NNN`/`ET-NNN`) | | `task_id` | INTEGER | NULL | контекст: внутренний id задачи | | `stage` | TEXT | NULL | контекст: стадия конвейера | | `agent` | TEXT | NULL | контекст: агент-роль | | `repo` | TEXT | NULL | контекст: репозиторий, **разрез выборки** | | `root_cause` | TEXT | NULL | анализ: корневая причина (если известна) | | `suggestion` | TEXT | NULL | анализ: предложенное улучшение (если есть) | | `status` | TEXT `DEFAULT 'new'` | NOT NULL | `new`/`in_progress`/`closed`/`linked` | | `related_task` | TEXT | NULL | связанная заведённая задача | | `attribution` | TEXT | **NULL** | **АТРИБУЦИЯ:** `platform`/`project`/`both`/`unknown` | | `target_repo` | TEXT | **NULL** | **АТРИБУЦИЯ:** кого касается улучшение | | `target_domain` | TEXT | **NULL** | **АТРИБУЦИЯ:** `reliability`/`quality`/`economy`/`features`/`scale` | | `source` | TEXT | NULL | `auto` (детектор) / `manual` (оператор/Стрим) | | `detail` | TEXT | NULL | свободный JSON/текст — payload детектора | **Инварианты данных:** - Три колонки **атрибуции** (`attribution`/`target_repo`/`target_domain`) присутствуют в исходной схеме, **нуллабельны** (требование Славы 10.06, NFR-6, AC-2) — при автозаписи допустимо пусто/`unknown`; проставляются позже через `update` (AC-5). - **Без `enum`/`CHECK`-констрейнтов** — значения `lesson_type`/`attribution`/`target_domain` суть конвенция строковых слагов (forward-compatible: новый тип не требует миграции). - Индексы: `idx_lessons_type_status (lesson_type, status)` — выборка/snapshot; `idx_lessons_repo (repo)` — репо-разрез; `idx_lessons_wi_type (work_item_id, lesson_type)` — дедуп автозаписи (ADR-001 D4). ## Совместимость данных / миграции - **Аддитивно / идемпотентно / restart-safe:** только новая таблица + индексы; повторный `init_db()` не падает и не дублирует (NFR-4). - **Общая прод-БД (self-hosting):** таблица создаётся на том же файле БД, что обслуживает orchestrator и enduro-trails. Уроки про любой репо хранятся в одной таблице; **изоляция enduro** — таблица аддитивна и не участвует в пайплайне enduro (NFR-3); репо-разрез — поле `repo` + фильтр выборки (ADR-001 D2). - **Объём строки** — короткие текстовые поля; `detail` — компактный payload. Запись — один `INSERT`, чтение — простой параметризованный `SELECT … ORDER BY id DESC LIMIT ?` (NFR-5; общий хост впритык: RAM/диск). - **Ретенция / архивация** — вне объёма v1; тренд роста и будущая стратегия — `10-tech-risks.md` (TR-2). - **Миграция исторических уроков из `memory/`** — вне объёма (BRD §2).