Files

13 KiB
Raw Permalink Blame History

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

02 — ТЗ (TRZ): ORCH-098 — FND: машинный журнал уроков

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

ТЗ описывает конкретные изменения к реализации, выведенные из BRD и фактического кода. Архитектурное обоснование/решения (точные сигнатуры врезок, индексы, дедуп, ретенция) — задача архитектора (06-adr).

1. Сводка изменения

Ввести машинный журнал уроков — аддитивную таблицу lessons + чистый leaf-модуль src/lessons.py (never-raise, kill-switch) по образцу serial_gate.py / coverage_gate.py / metrics.py. Модуль несёт: helper записи урока (record), read-only выборку (get_lessons), обновление (update_lesson), snapshot() для GET /queue. Автозапись ≥23 типов отклонений — тонкими best-effort врезками в существующие choke-point stage_engine.py / merge_gate.py / launcher.py. Два новых HTTP-эндпоинта (GET /lessons, POST /lessons) в main.py. Схема несёт сразу нуллабельные колонки атрибуции (требование Славы 10.06). Конвейер (STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict) — не тронут; enduro — не затронут.

2. Задействованные модули / пути

Путь Действие
src/db.py изменить — CREATE TABLE IF NOT EXISTS lessons в init_db(); helper'ы record_lesson / get_lessons / update_lesson / lessons_snapshot
src/lessons.py создать — leaf: record(...), get(...), update(...), snapshot(), константы LessonType/Attribution/Domain, applies(), never-raise
src/config.py изменить — флаг lessons_enabled (env ORCH_LESSONS_ENABLED, дефолт True) + опц. lessons_query_limit_default
src/stage_engine.py изменить — best-effort врезка lessons.record(...) в _handle_qg_failure_rollbacks (откаты gate-fail) и в ветку post-deploy DEGRADED → freeze
src/merge_gate.py изменить — best-effort врезка в HOLD/regression-guard HOLD и в транзиент-классификатор (_classify_merge_response == "transient" / merge-retry-исчерпан)
src/agents/launcher.py изменить — best-effort врезка при timeout-kill / транзиент-requeue агента
src/main.py изменить — эндпоинты GET /lessons, POST /lessons (+опц. POST /lessons/{id}); блок lessons в GET /queue
tests/test_lessons.py создать — unit + integration (см. 04-test-plan.yaml)
CLAUDE.md, docs/architecture/README.md, CHANGELOG.md изменить — документация

3. Функциональные требования

FR-1 — Таблица lessons (BR-1, BR-2)

Аддитивная идемпотентная таблица в db.init_db() (паттерн repo_freeze/coverage_baseline):

CREATE TABLE IF NOT EXISTS lessons (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    created_at    TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at    TEXT,
    -- тип отклонения (slug-конвенция, не enum-констрейнт)
    lesson_type   TEXT NOT NULL,
    -- контекст
    work_item_id  TEXT,
    task_id       INTEGER,
    stage         TEXT,
    agent         TEXT,
    repo          TEXT,
    -- анализ
    root_cause    TEXT,
    suggestion    TEXT,
    -- статус
    status        TEXT NOT NULL DEFAULT 'new',   -- new|in_progress|closed|linked
    related_task  TEXT,
    -- АТРИБУЦИЯ (BR-2, Слава 10.06) — нуллабельные, заполняются позже
    attribution   TEXT,                          -- platform|project|both|unknown
    target_repo   TEXT,                          -- кого касается (orchestrator|enduro-trails|…)
    target_domain TEXT,                          -- reliability|quality|economy|features|scale
    -- учёт
    source        TEXT,                          -- auto|manual
    detail        TEXT                           -- свободный JSON/текст (payload детектора)
);
CREATE INDEX IF NOT EXISTS idx_lessons_type_status ON lessons (lesson_type, status);
CREATE INDEX IF NOT EXISTS idx_lessons_repo        ON lessons (repo);

Колонки атрибуции создаются сразу и нуллабельны (NFR-6). На уже созданной таблице новые колонки добавляются _ensure_column (forward-safe). Никакого enum-констрейнта — значения суть конвенция строковых слагов (forward-compatible).

FR-2 — Helper записи lessons.record(...) (BR-3, BR-5; NFR-1)

Сигнатура (уточняет архитектор), напр.: record(lesson_type, *, work_item_id=None, task_id=None, stage=None, agent=None, repo=None, root_cause=None, suggestion=None, status="new", related_task=None, attribution=None, target_repo=None, target_domain=None, source="auto", detail=None) -> int | None.

  • При lessons_enabled is False → немедленный no-op (None), без обращения к БД.
  • Оборачивает db.record_lesson в try/except → при любой ошибке logger.warning + None (never-raise, NFR-1). Возвращает id вставленной строки при успехе.
  • source="auto" для детекторов, source="manual" для ручной записи.

FR-3 — Автозапись отклонений (BR-3)

Минимум 23 типа, best-effort (каждая врезка обёрнута/делегирует в never-raise record):

  • FR-3a — gate-fail / rollback — в stage_engine._handle_qg_failure_rollbacks: при откате на development (reviewer REQUEST_CHANGES, tester check_tests_passed FAIL, staging FAILED, deploy FAILED) → record("gate_failure", stage=…, agent=…, work_item_id=…, repo=…, root_cause=reason). Тип откатной причины → в detail/root_cause.
  • FR-3b — merge HOLD / regression-guard HOLD — в merge_gate (путь HOLD _handle_merge_verify / main_regressed_alerts_total инкремент) → record("merge_hold", …, root_cause=…).
  • FR-3c — транзиент-ретрай — в merge_gate._classify_merge_response-ветке "transient" (Gitea 405/5xx) и/или launcher timeout-kill / транзиент-requeue (Anthropic Overloaded) → record("transient_retry", …, detail=<код/причина>).
  • FR-3d (желательно) — post-deploy DEGRADED — в ветке stage_engine, где post-deploy DEGRADED/rollback ведёт к set_repo_freeze (ORCH-088/021) → record("deploy_degraded", …, attribution=None|"unknown", target_repo=repo) — урок «деплой OK / прод сломан» (слой-3, ET-8), атрибуцию проставит ретроспективщик/человек позже.

Дедуп/частота автозаписи (чтобы не плодить дубли на ретраях) — решение архитектора (например, ключ work_item_id+stage+lesson_type в окне); если не реализуется в v1 — отметить в 10-tech-risks.md.

FR-4 — Read-only выборка (BR-4)

db.get_lessons(*, lesson_type=None, status=None, repo=None, work_item_id=None, limit=N) -> list[dict] (параметризованный SELECT … ORDER BY id DESC LIMIT ?). lessons.get(...) — never-raise обёртка → [] при ошибке. lessons.snapshot() — лёгкая сводка (счётчики по типу/статусу, последние N) для GET /queue, never-raise → {}.

FR-5 — Ручная запись + обновление (BR-5, BR-6)

  • POST /lessons (тело JSON) → lessons.record(..., source="manual"). Возвращает {id}.
  • POST /lessons/{id} (или поля в POST /lessons) → lessons.update(id, status=…, attribution=…, target_repo=…, target_domain=…, related_task=…, root_cause=…, suggestion=…)db.update_lesson (UPDATE … SET … updated_at=datetime('now')). Позволяет ретроспективщику/ человеку классифицировать автозаписанный unknown. never-raise.

FR-6 — Kill-switch + изоляция (NFR-2, NFR-3)

lessons_enabled=Falserecord/get/update/snapshot инертны, эндпоинты возвращают {"enabled": false} (паттерн metrics_endpoint_enabled), врезки no-op. Поведение конвейера — байт-в-байт прежнее. enduro не затронут (общая БД, аддитивная таблица).

4. Изменения API

Новые эндпоинты в src/main.py (стиль GET /queue / POST /coverage/baseline):

  • GET /lessons — read-only выборка. Query: type, status, repo, work_item, limit (дефолт из конфига). Ответ: {"enabled": bool, "lessons": [ {…строка…} ]}. Всегда 200.
  • POST /lessons — ручная запись. Тело: lesson_type (обяз.) + опциональные поля контекста/ анализа/атрибуции. Ответ: {"id": <int>} или {"enabled": false}.
  • (опц.) POST /lessons/{id} — обновление статуса/атрибуции/привязки задачи. Ответ {"ok": bool}.
  • GET /queue — добавить read-only ключ "lessons": lessons.snapshot() (рядом с serial_gate/ coverage/bug_fast_track). Существующие ключи — без изменений.

GET /health / GET /status / GET /metrics / прочие эндпоинты — байт-в-байт прежние.

5. Изменения схемы БД

Новая аддитивная таблица lessons (FR-1) + два индекса, всё IF NOT EXISTS / _ensure_column. Существующие таблицы (tasks/jobs/agent_runs/events/job_deps/repo_freeze/ coverage_baseline/tracker_messages) — не тронуты. Колонки атрибуции — сразу, нуллабельные (BR-2/NFR-6). Restart-safe, идемпотентно, безопасно на живой общей прод-БД (enduro не затронут).

6. Требования к новым/изменённым QG checks

Нет. Журнал уроков — наблюдатель, не Quality Gate. QG_CHECKS / check_* / machine-verdict-ключи (verdict:/result:/staging_status:/deploy_status:/security_status:/ coverage_status:) — байт-в-байт не тронуты. Журнал не влияет на продвижение по стадиям.

7. Совместимость / регресс

  • Kill-switch lessons_enabled (env ORCH_LESSONS_ENABLED, дефолт True): False → полная инертность, нулевая регрессия.
  • never-raise на всех публичных функциях и врезках (NFR-1) — сбой журнала не роняет конвейер.
  • Аддитивно: только новая таблица + leaf + эндпоинты + тонкие врезки; ничего существующего не переписывается.
  • Изоляция enduro: общая БД, новая таблица; репо-скоуп через поле/фильтр выборки.
  • Обратимость: выключение флага возвращает прод к доресурсному поведению мгновенно.
  • Self-hosting безопасность (NFR-7): модуль не деплоит/не рестартит прод/не трогает main/без процессов/сети.
  • Артефакты pipeline: задача создаёт/обновляет стандартный пакет (0104 + 06-adr от архитектора, 12/13/14/15/17/18 по ходу конвейера). Сам журнал — БД-сущность, не номерной артефакт.