167 lines
16 KiB
Markdown
167 lines
16 KiB
Markdown
---
|
||
work_item: ORCH-027
|
||
stage: analysis
|
||
author_agent: analyst
|
||
status: ready-for-review
|
||
created_at: 2026-06-10
|
||
model_used: claude-opus-4-8
|
||
---
|
||
|
||
# 01 — BRD (бизнес-требования): ORCH-027 — Code coverage как гейт (защита от деградации покрытия тестами)
|
||
|
||
Work Item: **ORCH-027** · Repo: **orchestrator** · Стадия: analysis
|
||
|
||
## 1. Бизнес-контекст и проблема
|
||
|
||
Оркестратор ведёт **автономную** разработку: код пишет агент `developer` без человеческого
|
||
фильтра, а на стадии `testing` агент `tester` сам решает, достаточно ли тестов. Существующие
|
||
тестовые гейты проверяют только **факт прохождения** тестов, а не их **полноту**:
|
||
|
||
- `check_ci_green` (ребро `development → review`) — зелёный прогон `pytest tests/` в Gitea CI
|
||
(`.gitea/workflows/ci.yml`), судит по exit-code, покрытие **не меряет**.
|
||
- `check_tests_passed` (ребро `testing → deploy-staging`) — читает machine-verdict
|
||
`result:`/`verdict:`/`status:` из `13-test-report.md`; это вердикт LLM-`tester`'а, а не
|
||
измеренная метрика.
|
||
- Merge-gate re-test (ORCH-043) — повторный `pytest` на догнанной ветке, тоже только exit-code.
|
||
|
||
Ни один гейт не замечает, что фича добавила 300 строк кода и 0 тестов, или что багфикс
|
||
изменил поведение без регрессионного теста. При пакетном автономном прогоне (эпик ORCH-088,
|
||
«10–20 задач за ночь») это означает **монотонную деградацию покрытия**: каждая задача может
|
||
«срезать угол» на тестах, и за десятки задач проект тихо теряет тестируемость. Предложено
|
||
Стрим, одобрено Славой (`00-business-request.md`).
|
||
|
||
**Задача вводит измеримый гейт покрытия**: покрытие тестами измеряется инструментально и не
|
||
должно опускаться ниже политики (абсолютный порог и/или «не ниже базовой линии»). Это
|
||
структурная защита от деградации, аналогичная по духу security-гейту (ORCH-022) —
|
||
детерминированная метрика вместо доверия суждению агента.
|
||
|
||
> **Self-hosting.** Гейт работает на инструменте, который в проде обслуживает все проекты из
|
||
> общей БД и очереди (`CLAUDE.md` §self-hosting). Измерение покрытия — это исполнение тест-сьюта
|
||
> в изолированном worktree; оно **не трогает прод-контейнер и не касается `main`**.
|
||
|
||
## 2. Объём (scope)
|
||
|
||
### В объёме
|
||
- Инструментальное измерение покрытия тестами для репозитория `orchestrator` (стек Python /
|
||
pytest) перед слиянием ветки задачи в `main`.
|
||
- Гейт-решение: покрытие **не ниже** заданной политики порога. Политика поддерживает два режима:
|
||
абсолютный порог (`%`) и «не ниже базовой линии» (no-regression / ratchet), а также их
|
||
комбинацию.
|
||
- Хранение и обновление **базовой линии** покрытия (last-known покрытие `main`).
|
||
- Наблюдаемость результата: артефакт-отчёт о покрытии с machine-readable вердиктом, строка в
|
||
`GET /queue`, сигнал в Telegram при провале.
|
||
- Конфигурируемость: kill-switch + per-repo область + настраиваемый порог/политика +
|
||
поведение при ошибке инструмента (fail-open/closed).
|
||
|
||
### Вне объёма
|
||
- Реализация измерения покрытия для НЕ-Python стеков (jest / jacoco для будущих репозиториев) —
|
||
фактическая интеграция инструментов оставлена на будущее; в ORCH-027 закладывается лишь
|
||
расширяемость (политика и хранилище не должны быть жёстко завязаны на Python).
|
||
- Изменение существующей семантики `check_ci_green` / `check_tests_passed` /
|
||
`check_reviewer_verdict` для репозиториев, где гейт покрытия выключен.
|
||
- Принудительное доведение покрытия до 100% или установка агрессивного абсолютного порога —
|
||
стартовая политика консервативна (см. NFR-4).
|
||
- Покрытие самих тестовых файлов и мутационное тестирование.
|
||
- Выбор конкретного инструмента/механизма интеграции и его расположения в конвейере как
|
||
архитектурного решения — это зона архитектора (`06-adr/`); BRD/ТЗ фиксируют требования и
|
||
кандидатные точки, выведенные из фактического кода.
|
||
|
||
## 3. Заинтересованные стороны
|
||
|
||
- **Заказчик / инициатор:** Стрим (предложение), Слава (одобрение).
|
||
- **Затрагиваются:** конвейер `orchestrator` (self-hosting); агенты `developer`/`tester`
|
||
(теперь обязаны держать покрытие); проект enduro-trails — **не должен быть затронут** (гейт
|
||
по умолчанию неактивен вне сконфигурированных репозиториев).
|
||
- **Принимает результат:** reviewer (стадия `review`) + финальная стадия конвейера; владелец
|
||
(Owner) — по факту работы гейта в проде.
|
||
|
||
## 4. Бизнес-требования (BR)
|
||
|
||
- **BR-1 — Измерение покрытия.** Перед слиянием ветки задачи в `main` покрытие тестами
|
||
репозитория измеряется инструментально (исполнением тест-сьюта под coverage-инструментацией),
|
||
а не оценивается на глаз. Результат — числовая метрика покрытия (как минимум line coverage).
|
||
- **BR-2 — Гейт деградации.** Если измеренное покрытие нарушает политику (ниже абсолютного
|
||
порога ИЛИ ниже базовой линии — в зависимости от выбранного режима), конвейер **не
|
||
пропускает** задачу дальше к деплою и инициирует штатный откат на `development` для доработки
|
||
тестов.
|
||
- **BR-3 — Базовая линия (ratchet).** Поддерживается режим «не ниже предыдущего»: гейт
|
||
сравнивает покрытие ветки с зафиксированной базовой линией `main`. Базовая линия **обновляется
|
||
вверх** при успешном слиянии задачи в `main` (покрытие может только расти или держаться, но
|
||
не падать).
|
||
- **BR-4 — Конфигурируемость и нулевая регрессия.** Гейт управляется kill-switch'ем и
|
||
per-repo областью (по образцу `merge_gate`/`security_gate`/`image_freshness`,
|
||
ORCH-035/043/058). Для репозиториев вне области (в частности enduro-trails) гейт — **полный
|
||
no-op**, поведение конвейера 1:1 как до задачи. Порог, политика (absolute|baseline|both) и
|
||
поведение при ошибке инструмента — настраиваемы.
|
||
- **BR-5 — Наблюдаемость.** Результат измерения виден: (а) артефакт-отчёт о покрытии с
|
||
machine-readable вердиктом в `docs/work-items/<id>/`; (б) read-only блок в `GET /queue`;
|
||
(в) уведомление в Telegram при провале гейта (кликабельный номер задачи, как у прочих
|
||
алертов). Сообщение указывает измеренное покрытие, порог/базовую линию и дельту.
|
||
- **BR-6 — Стек-расширяемость.** Логика политики (PASS/FAIL по метрике/базовой линии) и
|
||
хранилище базовой линии не зависят от конкретного инструмента; добавление измерителя для
|
||
другого стека (jest/jacoco) в будущем не требует переписывания ядра гейта.
|
||
|
||
## 5. Нефункциональные требования (NFR)
|
||
|
||
- **NFR-1 — never-raise / fail-safe.** Ядро гейта — изолированный leaf-модуль (по образцу
|
||
`src/security_gate.py`, `src/serial_gate.py`, `src/labels.py`): любая внутренняя ошибка
|
||
обрабатывается, исключение **никогда** не всплывает в `advance_stage` и не роняет конвейер
|
||
всех проектов.
|
||
- **NFR-2 — Поведение при недоступности/ошибке инструмента.** По умолчанию ошибка измерения
|
||
(coverage-инструмент упал/недоступен) → **fail-open + громкий warning** (анти-петля,
|
||
прецедент ORCH-061/ORCH-022 dep-audit), переключаемое в fail-closed флагом. Дефолт не должен
|
||
заклинивать автономный конвейер из-за инфраструктурного сбоя.
|
||
- **NFR-3 — Self-hosting безопасность.** Гейт только исполняет тесты в изолированном worktree,
|
||
читает метрику, пишет отчёт и принимает решение. Он **никогда** не вызывает деплой-хук, не
|
||
перезапускает прод-контейнер, не пушит/форс-пушит в `main`.
|
||
- **NFR-4 — Консервативный старт (анти-флап).** Стартовая политика не должна массово заворачивать
|
||
существующие задачи: базовая линия инициализируется фактическим покрытием `main`, абсолютный
|
||
порог — как мягкий backstop. Допускается малый отрицательный допуск (epsilon) на шум измерения,
|
||
чтобы дрожание ±доли процента не заворачивало задачу.
|
||
- **NFR-5 — Совместимость.** `STAGE_TRANSITIONS`, состав/семантика `QG_CHECKS` и `check_*`,
|
||
machine-verdict ключи существующих доков (`verdict:`/`result:`/`deploy_status:`/
|
||
`staging_status:`/`security_status:`) — не меняются. Любая новая БД-сущность — аддитивна
|
||
(без миграции существующих таблиц). Restart-safe.
|
||
- **NFR-6 — Детерминизм.** Решение гейта — чистая функция от (измеренное покрытие, базовая
|
||
линия, порог, политика); без участия LLM в критическом пути (как security/merge/image-freshness
|
||
под-гейты).
|
||
|
||
## 6. Допущения и ограничения
|
||
|
||
- Тест-сьют `orchestrator` запускается командой `python -m pytest tests/` из корня репозитория
|
||
(подтверждено `.gitea/workflows/ci.yml`, `pytest.ini` `testpaths = tests`); измерение покрытия
|
||
накладывается на этот же прогон.
|
||
- Coverage-инструмент для Python (`coverage.py` / `pytest-cov`) добавляется как pip-зависимость;
|
||
он не требует сети во время измерения.
|
||
- Репозиторий `orchestrator` — единственный self-hosting (предикат `is_self_hosting_repo`);
|
||
стартовая область гейта — он. enduro-trails и прочие репозитории по умолчанию вне области.
|
||
- Базовая линия привязана к покрытию `main`; её первичная инициализация выполняется один раз
|
||
(bootstrap) фактическим замером текущего `main`.
|
||
- Тесты исполняются в per-branch worktree (`ensure_worktree`), что безопасно при параллельных
|
||
активных задачах (прецедент `check_tests_local`/merge-gate re-test).
|
||
|
||
## 7. Критерии успеха
|
||
|
||
- Покрытие тестами `orchestrator` измеряется на каждой задаче и не может опуститься ниже
|
||
политики, не заблокировав продвижение к деплою.
|
||
- При выключенном флаге / вне области — конвейер ведёт себя 1:1 как до ORCH-027 (нулевая
|
||
регрессия для enduro-trails).
|
||
- Сбой coverage-инструмента не заклинивает автономный конвейер (дефолт fail-open + warning).
|
||
- Результат измерения прозрачен (отчёт + `GET /queue` + Telegram при провале).
|
||
|
||
Детальные PASS/FAIL — `03-acceptance-criteria.md`.
|
||
|
||
## 8. Риски
|
||
|
||
- **Флап на шуме измерения** — недетерминированное покрытие (например, зависящее от порядка/
|
||
окружения) может дрожать у границы → ложные заворота. Митигировать epsilon-допуском (NFR-4).
|
||
- **Петля заворотов** — слишком высокий абсолютный порог завернёт многие задачи в бесконечный
|
||
rework. Митигировать консервативной стартовой политикой и baseline-режимом.
|
||
- **Гонка базовой линии** при параллельных слияниях — два слияния в `main` могут конкурентно
|
||
обновлять baseline. Требуется атомарное/сериализованное обновление (опереться на окно
|
||
сериализации merge-lease, ORCH-043).
|
||
- **Инфраструктурная хрупкость** — coverage-инструмент недоступен/несовместим с версией pytest →
|
||
закрыто требованием NFR-2 (fail-open + warning).
|
||
|
||
Детальная техническая проработка рисков — `10-tech-risks.md` (заполняет архитектор).
|