analyst(ET): auto-commit from analyst run_id=573
This commit is contained in:
143
docs/work-items/ORCH-098/01-brd.md
Normal file
143
docs/work-items/ORCH-098/01-brd.md
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
work_item: ORCH-098
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-10
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 01 — BRD (бизнес-требования): ORCH-098 — FND: машинный журнал уроков (структурированная база отклонений)
|
||||
|
||||
Work Item: **ORCH-098** · Repo: **orchestrator** · Стадия: analysis
|
||||
|
||||
## 1. Бизнес-контекст и проблема
|
||||
|
||||
Оркестратор уже автономно проводит задачи через конвейер (ORCH-54), но **развивает** платформу
|
||||
по-прежнему вручную связка Слава+Стрим: ловим инциденты → формулируем уроки → заводим задачи.
|
||||
Уроки сегодня живут **свободным текстом** в `memory/` — они не машиночитаемы, по ним нельзя
|
||||
считать паттерны, нельзя приоритизировать, нельзя автоматически предлагать улучшения.
|
||||
|
||||
ORCH-098 — шаг 1 эпика саморазвития (`docs/epics/self-evolution.md`, **домен 0 «Фундамент», F2**,
|
||||
ORCH-8). Это **«топливо» вертикали-двигателя** (петля самообучения 8A): формализовать свободный
|
||||
текст в **машинную структурированную таблицу отклонений конвейера**. Каждый урок — запись с
|
||||
полями для машинного анализа паттернов. Журнал — фундамент, на котором позже встанут
|
||||
ретроспективщик (E2), приоритизатор RICE (E3) и Стрим как потребители.
|
||||
|
||||
**Установленные факты-источники сигналов («уроков»)** — из памяти орка (инциденты 06–09.06) и §8A
|
||||
эпика:
|
||||
- Провал гейта (BLOCKED / FAILED / REQUEST_CHANGES).
|
||||
- **Ручное вмешательство человека — самый ценный сигнал** (каждый ручной пинок = дыра автономности).
|
||||
- Ретраи, откаты деплоя, таймауты агентов.
|
||||
- Ложные срабатывания гейтов (исторический пример: substring `PASS` в `check_tests_passed`).
|
||||
- «Деплой SUCCESS, а прод не работает» (урок ET-8); транзиенты (Gitea `405`, Anthropic `Overloaded`).
|
||||
|
||||
**Решение Славы 10.06 (ОБЯЗАТЕЛЬНО учесть на этапе схемы):** схема журнала ДОЛЖНА **с самого
|
||||
начала** нести поля для будущей **АТРИБУЦИИ** урока (иначе потом переделывать схему на живой
|
||||
общей прод-БД). Атрибуция (`platform-level` / `project-level` / `both` / `unknown`), целевой
|
||||
проект и целевой домен улучшения — это §8A эпика «platform-level vs project-level». При автозаписи
|
||||
поля атрибуции могут быть пустыми/`unknown` (классификацию позже ставит ретроспективщик/Стрим), но
|
||||
**колонки в схеме должны существовать сразу** — аддитивные, нуллабельные.
|
||||
|
||||
**Связь со слоями наблюдения (§2 эпика):** деградация продукта (слой 3, урок ET-8) — один из типов
|
||||
урока; журнал должен уметь его хранить с атрибуцией `platform`/`project`.
|
||||
|
||||
## 2. Объём (scope)
|
||||
|
||||
### В объёме
|
||||
- Аддитивная идемпотентная таблица БД `lessons` для структурированных уроков со всеми полями
|
||||
контекста, анализа, статуса **и атрибуции** (колонки атрибуции — сразу, нуллабельные).
|
||||
- Leaf-модуль `src/lessons.py` (never-raise, kill-switch) + helper записи урока.
|
||||
- **Автозапись** ≥2–3 типов отклонений из кода через best-effort точки врезки в
|
||||
`stage_engine.py` / `merge_gate.py` / `launcher.py` (провал гейта/откат, HOLD, транзиент-ретрай).
|
||||
- **Read-only выборка** уроков (HTTP-эндпоинт + блок в `GET /queue`) — для будущего
|
||||
ретроспективщика и Стрим.
|
||||
- **Ручная запись** урока (HTTP-эндпоинт / helper) — Стрим/оператор кладёт урок руками.
|
||||
- Доки (CLAUDE.md / architecture README / ADR) + `CHANGELOG.md`.
|
||||
|
||||
### Вне объёма
|
||||
- **Анализ паттернов / ретроспективщик (E2)** — отдельная задача-потребитель журнала.
|
||||
- **Приоритизатор RICE (E3)** — отдельная задача.
|
||||
- **Автоматическая классификация атрибуции** — её ставит ретроспективщик/человек позже; здесь —
|
||||
только колонки и возможность проставить значение руками/через update.
|
||||
- **Банк идей (D4 / идеатор, E5)** — отдельный реестр, НЕ путать с журналом уроков.
|
||||
- **Слой-3 детекция здоровья продукта** (мониторинг задеплоенного приложения) — отдельная
|
||||
D4/D5-способность; журнал лишь умеет **хранить** такой урок, когда детектор появится.
|
||||
- Изменение `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict-ключей / любых
|
||||
существующих таблиц.
|
||||
- Миграция исторических уроков из `memory/` (ручной разовый импорт — вне объёма).
|
||||
|
||||
## 3. Заинтересованные стороны
|
||||
- **Заказчик:** Слава (требование атрибуции 10.06 — нормативно).
|
||||
- **Прямой потребитель (будущее):** агент-ретроспективщик E2, приоритизатор E3, Стрим (ручной
|
||||
разбор).
|
||||
- **Затрагивается:** self-hosting прод-инстанс orchestrator (общая БД и очередь с enduro-trails) —
|
||||
enduro **не должен быть затронут** (аддитивность, never-raise).
|
||||
- **Принимает результат:** reviewer/tester конвейера + Слава.
|
||||
|
||||
## 4. Бизнес-требования (BR)
|
||||
|
||||
- **BR-1 — Структурированная таблица уроков.** Аддитивная, идемпотентная (`CREATE TABLE IF NOT
|
||||
EXISTS`) таблица `lessons` на общей прод-БД с полями: тип отклонения; контекст
|
||||
(work_item/task/стадия/агент/repo); корневая причина (если известна); предложенное улучшение
|
||||
(если есть); статус (`new`/`in_progress`/`closed`/`linked`) + связанная задача; timestamp.
|
||||
- **BR-2 — Поля атрибуции с самого начала.** Схема несёт **сразу** нуллабельные колонки:
|
||||
`attribution` (`platform`/`project`/`both`/`unknown`), `target_repo` (кого касается:
|
||||
`orchestrator`/`enduro-trails`/др.), `target_domain` (домен улучшения:
|
||||
`reliability`/`quality`/`economy`/`features`/`scale`). При автозаписи допустимо пусто/`unknown`.
|
||||
- **BR-3 — Автозапись ≥2–3 типов отклонений.** Из кода, best-effort, в детерминированных
|
||||
choke-point: (а) провал гейта / откат на `development` (reviewer REQUEST_CHANGES, tester FAIL,
|
||||
staging/deploy FAILED), (б) HOLD merge-актора / regression-guard HOLD, (в) транзиент-ретрай
|
||||
(Gitea-merge `405`/`5xx`, Anthropic `Overloaded`/agent-timeout requeue). Дополнительно желательно
|
||||
(г) post-deploy `DEGRADED` (урок «деплой OK / прод сломан», слой-3, ET-8) с атрибуцией.
|
||||
- **BR-4 — Read-only выборка.** HTTP-эндпоинт `GET /lessons` (фильтры: тип/статус/repo/work_item,
|
||||
лимит) + read-only блок `lessons` в `GET /queue` (сводка). Только чтение.
|
||||
- **BR-5 — Ручная запись.** HTTP-эндпоинт `POST /lessons` (+ публичный helper) — оператор/Стрим
|
||||
кладёт урок руками, в т.ч. с проставленной атрибуцией.
|
||||
- **BR-6 — Обновление урока.** Возможность сменить статус / проставить атрибуцию / привязать
|
||||
задачу после создания (helper/эндпоинт `POST /lessons/{id}` или поля в `POST /lessons`) — чтобы
|
||||
ретроспективщик/человек позже классифицировал автозаписанный `unknown`.
|
||||
|
||||
## 5. Нефункциональные требования (NFR)
|
||||
|
||||
- **NFR-1 — never-raise (критично, self-hosting).** Сбой записи/чтения урока **никогда** не роняет
|
||||
и не тормозит конвейер. Любая ошибка детектора/записи → лог WARNING + продолжение основного
|
||||
потока. Журнал — наблюдатель, не участник пайплайна.
|
||||
- **NFR-2 — Kill-switch.** Флаг `lessons_enabled` (env `ORCH_LESSONS_ENABLED`). `False` →
|
||||
автозапись и эндпоинты инертны (нулевая регрессия, поведение конвейера байт-в-байт прежнее).
|
||||
- **NFR-3 — Аддитивность / изоляция enduro.** Только новая таблица + новый leaf + новые эндпоинты +
|
||||
тонкие врезки. `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict-ключи / схема
|
||||
существующих таблиц — **байт-в-байт не тронуты**. Общая БД: enduro-trails не затронут.
|
||||
- **NFR-4 — Restart-safe / идемпотентность таблицы.** `CREATE TABLE IF NOT EXISTS` + `_ensure_column`
|
||||
(паттерн `repo_freeze`/`coverage_baseline`) — безопасно на живой БД, повторный старт без эффекта.
|
||||
- **NFR-5 — Лёгкость.** Запись — один `INSERT`, чтение — простые `SELECT` (общий хост впритык:
|
||||
RAM 171Mi free, диск 92%). Никаких фоновых потоков/сканов.
|
||||
- **NFR-6 — Схема-forward-proof.** Колонки атрибуции добавлены сразу (BR-2), чтобы не
|
||||
переделывать схему на живой БД, когда появится ретроспективщик.
|
||||
- **NFR-7 — Self-hosting безопасность.** Модуль только пишет/читает БД и отдаёт JSON — не
|
||||
деплоит, не рестартит прод, не трогает `main`, не порождает процессы/сеть.
|
||||
|
||||
## 6. Допущения и ограничения
|
||||
- Журнал уроков — **исключение** из правила «наблюдатель отделён от наблюдаемого» (§2 эпика): это
|
||||
историческая память петли, не realtime-мониторинг → допустимо в БД орка; запись best-effort.
|
||||
- Точки автозаписи привязаны к существующим choke-point: `stage_engine._handle_qg_failure_rollbacks`
|
||||
(откаты), `merge_gate` (HOLD/transient-классификатор ORCH-093), `launcher` (timeout/requeue
|
||||
транзиентов). Архитектор уточняет точный набор и сигнатуры врезок.
|
||||
- Набор значений `lesson_type` / `attribution` / `target_domain` — конвенция (строковые слаги),
|
||||
не enum-констрейнт БД (forward-compatible; новый тип не требует миграции).
|
||||
- Общая прод-БД с enduro: любое поле repo-scoped, фильтрация на уровне выборки.
|
||||
|
||||
## 7. Критерии успеха
|
||||
Таблица `lessons` создаётся идемпотентно на старте; автозаписаны ≥2–3 типа отклонений из реального
|
||||
прогона; `GET /lessons` и `POST /lessons` работают; атрибутивные колонки присутствуют и
|
||||
проставляемы; kill-switch выключает всё без регрессии; `pytest tests/ -q` зелёный; доки+CHANGELOG
|
||||
обновлены. Детальные PASS/FAIL — `03-acceptance-criteria.md`.
|
||||
|
||||
## 8. Риски
|
||||
- Врезка детектора в горячий путь конвейера → риск регрессии при сбое записи. Митигация: NFR-1
|
||||
never-raise + kill-switch.
|
||||
- Рост таблицы со временем (автозапись на каждом откате/ретрае). Митигация: лёгкие строки;
|
||||
будущая ретенция — вне объёма, отметить в `10-tech-risks.md` (архитектор).
|
||||
- Недооформленная схема атрибуции → переделка на живой БД. Митигация: BR-2/NFR-6 (колонки сразу).
|
||||
- Детали и архитектурные развилки (точные точки врезки, индексы, дедуп автозаписей) — задача
|
||||
архитектора (`06-adr/`, `10-tech-risks.md`).
|
||||
163
docs/work-items/ORCH-098/02-trz.md
Normal file
163
docs/work-items/ORCH-098/02-trz.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
work_item: ORCH-098
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-10
|
||||
model_used: 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`. Автозапись ≥2–3 типов отклонений —
|
||||
тонкими 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`):
|
||||
|
||||
```sql
|
||||
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)
|
||||
Минимум 2–3 типа, 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=False` → `record`/`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:** задача создаёт/обновляет стандартный пакет (`01`–`04` + `06-adr` от
|
||||
архитектора, `12`/`13`/`14`/`15`/`17`/`18` по ходу конвейера). Сам журнал — БД-сущность, не
|
||||
номерной артефакт.
|
||||
123
docs/work-items/ORCH-098/03-acceptance-criteria.md
Normal file
123
docs/work-items/ORCH-098/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
work_item: ORCH-098
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-10
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 03 — Критерии приёмки (Acceptance Criteria): ORCH-098 — FND: машинный журнал уроков
|
||||
|
||||
Work Item: **ORCH-098** · Repo: **orchestrator** · Стадия: analysis
|
||||
|
||||
Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** (что
|
||||
считается провалом). Reviewer/tester проверяет их буквально по файлам репозитория и тестам.
|
||||
|
||||
---
|
||||
|
||||
## AC-1 — Аддитивная таблица уроков
|
||||
|
||||
**Условие:** `db.init_db()` создаёт таблицу `lessons` идемпотентно.
|
||||
- **PASS:** в `src/db.py` есть `CREATE TABLE IF NOT EXISTS lessons (...)` со всеми полями
|
||||
(`lesson_type`, контекст `work_item_id/task_id/stage/agent/repo`, `root_cause`, `suggestion`,
|
||||
`status`+`related_task`, `created_at`); повторный `init_db()` не падает и не дублирует; таблица
|
||||
создаётся на общей прод-БД без изменения существующих таблиц.
|
||||
- **FAIL:** таблицы нет / создаётся не идемпотентно / отсутствует любое поле из BR-1 / меняется
|
||||
схема существующей таблицы.
|
||||
|
||||
---
|
||||
|
||||
## AC-2 — Поля атрибуции присутствуют с самого начала
|
||||
|
||||
**Условие:** схема `lessons` несёт нуллабельные колонки атрибуции (требование Славы 10.06).
|
||||
- **PASS:** колонки `attribution` (`platform`/`project`/`both`/`unknown`), `target_repo`,
|
||||
`target_domain` существуют сразу, нуллабельны, допускают пустое/`unknown` при автозаписи и
|
||||
проставляются позже через update.
|
||||
- **FAIL:** хотя бы одной из трёх колонок нет в исходной схеме / колонка `NOT NULL` без дефолта /
|
||||
атрибуцию нельзя проставить после создания записи.
|
||||
|
||||
---
|
||||
|
||||
## AC-3 — Автозапись ≥2–3 типов отклонений
|
||||
|
||||
**Условие:** из кода автоматически (best-effort, `source="auto"`) пишутся минимум 2–3 типа уроков.
|
||||
- **PASS:** есть врезки `lessons.record(...)` минимум в двух-трёх точках из:
|
||||
`stage_engine._handle_qg_failure_rollbacks` (gate-fail/откат), `merge_gate` (HOLD/transient),
|
||||
`launcher` (timeout/transient-requeue); интеграционный тест подтверждает появление строки в
|
||||
`lessons` после смоделированного отклонения.
|
||||
- **FAIL:** автозаписи нет / реализован <2 типов / врезка может бросить исключение в горячий путь.
|
||||
|
||||
---
|
||||
|
||||
## AC-4 — Read-only выборка
|
||||
|
||||
**Условие:** уроки можно прочитать через эндпоинт и сводку в `GET /queue`.
|
||||
- **PASS:** `GET /lessons` возвращает `200` с массивом уроков, поддерживает фильтры
|
||||
(type/status/repo/work_item/limit); `GET /queue` содержит read-only блок `lessons`; ни один
|
||||
путь чтения не мутирует данные.
|
||||
- **FAIL:** эндпоинта нет / не фильтрует / чтение мутирует данные / блока в `/queue` нет.
|
||||
|
||||
---
|
||||
|
||||
## AC-5 — Ручная запись и обновление
|
||||
|
||||
**Условие:** оператор/Стрим кладёт урок руками и может его доклассифицировать.
|
||||
- **PASS:** `POST /lessons` создаёт урок (`source="manual"`, можно задать атрибуцию); обновление
|
||||
(`POST /lessons/{id}` или поля) меняет `status`/`attribution`/`target_*`/`related_task` и
|
||||
стампит `updated_at`.
|
||||
- **FAIL:** ручной записи нет / нельзя проставить атрибуцию / нельзя обновить автозаписанный урок.
|
||||
|
||||
---
|
||||
|
||||
## AC-6 — never-raise (сбой журнала не роняет конвейер)
|
||||
|
||||
**Условие:** любая ошибка записи/чтения урока изолирована от пайплайна.
|
||||
- **PASS:** все публичные функции `src/lessons.py` и все врезки обёрнуты так, что исключение БД/
|
||||
любого источника → `logger.warning` + безопасный дефолт (`None`/`[]`/`{}`); юнит-тест с
|
||||
замоканной падающей БД подтверждает, что вызывающий код (откат/HOLD/retry) не падает.
|
||||
- **FAIL:** исключение из журнала пробивается в `stage_engine`/`merge_gate`/`launcher`/эндпоинт.
|
||||
|
||||
---
|
||||
|
||||
## AC-7 — Kill-switch и нулевая регрессия
|
||||
|
||||
**Условие:** `lessons_enabled=False` делает функционал инертным.
|
||||
- **PASS:** при `False` `record`/`get`/`update`/`snapshot` — no-op (без обращения к БД), эндпоинты
|
||||
отдают `{"enabled": false}`, врезки не пишут; поведение конвейера и `GET /queue` (помимо нового
|
||||
блока) — байт-в-байт прежнее; enduro-trails не затронут.
|
||||
- **FAIL:** при `False` журнал что-то пишет/ломает / меняется поведение конвейера / затронут enduro.
|
||||
|
||||
---
|
||||
|
||||
## AC-8 — Инварианты конвейера не тронуты
|
||||
|
||||
**Условие:** изменение не касается машины стадий и гейтов.
|
||||
- **PASS:** `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, функции `check_*`, machine-verdict-ключи и
|
||||
схема существующих таблиц — **диффом не затронуты**; журнал не влияет на продвижение по стадиям.
|
||||
- **FAIL:** изменён любой из перечисленных артефактов / журнал участвует в решении гейта.
|
||||
|
||||
---
|
||||
|
||||
## AC-9 — Тесты, документация, CHANGELOG
|
||||
|
||||
**Условие:** изменение проверено и задокументировано.
|
||||
- **PASS:** `pytest tests/ -q` зелёный (включая новый `tests/test_lessons.py` с unit+integration);
|
||||
обновлены `CLAUDE.md` + `docs/architecture/README.md`; в задаче есть `06-adr/` (архитектор);
|
||||
`CHANGELOG.md` дополнен.
|
||||
- **FAIL:** тесты падают / нет покрытия новой логики / документация или CHANGELOG не обновлены.
|
||||
|
||||
---
|
||||
|
||||
## Сводная матрица AC ↔ FR/BR
|
||||
| AC | Покрывает |
|
||||
|----|-----------|
|
||||
| AC-1 | BR-1 / FR-1 |
|
||||
| AC-2 | BR-2 / FR-1 / NFR-6 |
|
||||
| AC-3 | BR-3 / FR-2 / FR-3 |
|
||||
| AC-4 | BR-4 / FR-4 |
|
||||
| AC-5 | BR-5 / BR-6 / FR-5 |
|
||||
| AC-6 | NFR-1 / FR-2 |
|
||||
| AC-7 | NFR-2 / NFR-3 / FR-6 |
|
||||
| AC-8 | NFR-3 / FR-6 |
|
||||
| AC-9 | NFR-1…NFR-7 (верификация) |
|
||||
91
docs/work-items/ORCH-098/04-test-plan.yaml
Normal file
91
docs/work-items/ORCH-098/04-test-plan.yaml
Normal file
@@ -0,0 +1,91 @@
|
||||
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
|
||||
Reference in New Issue
Block a user