15 KiB
work_item, stage, author_agent, status, created_at, model_used
| work_item | stage | author_agent | status | created_at | model_used |
|---|---|---|---|---|---|
| ORCH-027 | analysis | analyst | ready-for-review | 2026-06-10 | claude-opus-4-8 |
02 — ТЗ (TRZ): ORCH-027 — Code coverage как гейт
Work Item: ORCH-027 · Repo: orchestrator · Стадия: analysis
ТЗ описывает конкретные требования к реализации, выведенные из BRD и фактического кода. Архитектурное обоснование и выбор механизма (где именно врезать гейт, как хранить базовую линию, какой инструмент) — задача архитектора (
06-adr/). Ниже зафиксированы требования и кандидатные точки интеграции, грунтованные реальным кодом; финальное решение по каждой отмеченной точке принимает архитектор.
1. Сводка изменения
Вводится детерминированный гейт покрытия тестами для репозитория orchestrator. Гейт
измеряет покрытие исполнением тест-сьюта под coverage-инструментацией, сравнивает с политикой
(абсолютный порог и/или базовая линия main) и блокирует продвижение задачи к деплою при
деградации, инициируя штатный откат на development. Ядро — изолированный leaf-модуль с чистой
логикой решения (по образцу security_gate/serial_gate), управляемый kill-switch'ем и per-repo
областью; вне области — полный no-op. Базовая линия покрытия main хранится персистентно и
обновляется вверх при слиянии (ratchet).
2. Задействованные модули / пути
| Путь | Действие | Назначение |
|---|---|---|
requirements.txt |
изменить | добавить coverage-зависимость Python (coverage.py / pytest-cov; точный выбор — архитектор) |
src/coverage_gate.py |
создать | NEW leaf-модуль: измерение покрытия (run suite под coverage в ensure_worktree), чистые функции compute_coverage_verdict(measured, baseline, floor, policy, epsilon) и классификация, чтение/запись отчёта; never-raise; импортирует только config/git_worktree (+ лениво qg.checks.is_self_hosting_repo/notifications) |
src/config.py |
изменить | добавить флаги гейта (см. §6 ниже / раздел совместимости) |
src/qg/checks.py |
изменить | зарегистрировать механизм проверки покрытия (новый check_* ЛИБО делегирование из под-гейта); семантика существующих check_* не меняется |
src/stage_engine.py |
изменить (кандидат) | врезка под-гейта в advance_stage по образцу _handle_security_gate/_handle_merge_gate — если выбран механизм «edge sub-gate» (см. §3 FR-3) |
src/db.py |
изменить (кандидат) | аддитивная таблица базовой линии покрытия (coverage_baseline per-repo), если базовая линия хранится в БД, а не в файле; _ensure_column/CREATE TABLE IF NOT EXISTS — без миграции существующих |
.gitea/workflows/ci.yml |
изменить (кандидат) | если измерение делается в CI-шаге — добавить --cov/порог в прогон pytest; точка измерения — решение архитектора |
src/main.py |
изменить | read-only блок coverage в GET /queue (наблюдаемость) |
docs/work-items/<id>/<NN>-coverage-report.md |
создать (артефакт run-time) | отчёт о покрытии с machine-readable вердиктом (см. §4/§6); номер/имя и регистрация в docs/_standards/PIPELINE_DOCS.md + скелет в docs/_templates/ — оформляет архитектор |
tests/test_coverage_gate.py |
создать | unit/integration по 04-test-plan.yaml |
3. Функциональные требования
FR-1 — Измерение покрытия (привязка BR-1)
Гейт исполняет тест-сьют orchestrator (python -m pytest tests/, см. .gitea/workflows/ci.yml)
под coverage-инструментацией в изолированном per-branch worktree (ensure_worktree, прецедент
check_tests_local) и извлекает числовую метрику покрытия (как минимум суммарный line coverage,
%). Тайм-аут на прогон ограничен (по образцу merge_retest_timeout_s / security_scan_timeout_s).
FR-2 — Решение гейта (привязка BR-2, BR-3)
Чистая функция compute_coverage_verdict(measured, baseline, floor, policy, epsilon) -> (ok, reason):
policy = absolute→ PASS ⇔measured >= floor - epsilon.policy = baseline→ PASS ⇔measured >= baseline - epsilon.policy = both(дефолт) → PASS ⇔ выполнены оба условия.- FAIL → гейт инициирует штатный откат на
developmentдля доработки тестов (по образцу_handle_security_gate/ merge-gate rollback), с инкрементом счётчика developer-retry. epsilon— малый неотрицательный допуск на шум измерения (NFR-4), настраиваемый.
FR-3 — Точка в конвейере (привязка BR-2; кандидат, решает архитектор)
Бизнес-запрос указывает «на testing-гейте». Грунтованные кодом кандидаты (выбрать один):
- (a) Edge sub-gate в
advance_stageна ребреdeploy-staging → deploy(рядом с_handle_security_gate/_handle_merge_gate/_handle_image_freshness) — даёт гарантию «гейт ДО слияния вmain», детерминирован, владеет исходом на вмешательстве. Предпочтительно для соответствия NFR-3/NFR-6. - (b) Под-гейт/расширение на ребре
testing → deploy-staging(рядом сcheck_tests_passed). - (c) CI-шаг в
.gitea/workflows/ci.yml(реброdevelopment → review, читаетсяcheck_ci_green) — порог проверяется самим pytest-прогоном. Требование, инвариантное к выбору: гейт обязан отработать до фактического merge вmainи не пропускать деградацию вmain.
FR-4 — Базовая линия и её обновление (привязка BR-3)
- Персистентное per-repo хранилище базовой линии покрытия
main(БД-таблица ИЛИ файл в репо — решает архитектор; при БД — аддитивная таблица, NFR-5). - Bootstrap: первичная инициализация фактическим замером текущего
main. - Ratchet-up: при успешном слиянии задачи в
mainбазовая линия обновляется значением смёрженного покрытия, только если оно ≥ текущей (покрытие не откатывается вниз). Обновление должно быть атомарным/сериализованным относительно параллельных слияний (опереться на окно merge-lease, ORCH-043).
FR-5 — Условность и kill-switch (привязка BR-4)
coverage_gate_enabled=False→ гейт инертен, конвейер 1:1 как до ORCH-027.coverage_gate_repos(CSV) — область применения; пусто → только self-hosting (is_self_hosting_repo, по образцуmerge_gate/security_gate/image_freshness).- Вне области → no-op
(True, "Coverage gate N/A")(прецедентcheck_staging_statusдля не-self-hosting, ORCH-035). applies(repo)(локальная проверка) выполняется ПЕРВОЙ; дорогой прогон измерения — только приapplies==True.
FR-6 — Поведение при ошибке инструмента (привязка NFR-2)
Ошибка/недоступность coverage-инструмента или невозможность распарсить метрику → по умолчанию
fail-open + WARNING (coverage_tool_fail_closed=False, прецедент security_dep_audit_fail_closed);
флаг переключает в fail-closed. Поведение логируется явной observability-строкой.
FR-7 — Наблюдаемость (привязка BR-5)
- Артефакт-отчёт
<NN>-coverage-report.mdс machine-readable вердиктом (см. §4). - Read-only блок
coverageвGET /queue(per-repo:enabled/policy/floor/baseline/ последнее измеренное/вердикт). - При FAIL —
send_telegram(notifying) с кликабельным номером задачи (plane_issue_link), измеренным покрытием, порогом/базовой линией и дельтой.
4. Изменения API
GET /queue— добавить read-only блокcoverage(наблюдаемость; форма прочих блоковserial_gate/security/merge). Без изменения существующих полей ответа.- Опционально (решает архитектор): ручной эндпоинт сброса/override базовой линии
(
POST /coverage/baseline?repo=…) — по образцуPOST /serial-gate/unfreeze, на случай легитимного разового снижения покрытия. Если не вводится — override выполняется через конфиг. - Существующие webhook-роуты (
/webhook/plane,/webhook/gitea) — без изменений.
5. Изменения схемы БД
Зависит от выбора хранилища базовой линии (FR-4):
- Если БД: аддитивная таблица
coverage_baseline(repo TEXT PRIMARY KEY, coverage REAL, updated_at, source_sha TEXT)черезCREATE TABLE IF NOT EXISTS(паттернrepo_freeze/job_deps). Существующие таблицы — не мигрируются (NFR-5). - Если файл в репо: изменений схемы БД нет (базовая линия — версионируемый файл вроде
.coverage-baseline.json, читаемый/обновляемый под merge-lease).
Выбор — архитектор; ТЗ требует лишь: персистентность, restart-safe, аддитивность, атомарность обновления.
6. Требования к новым/изменённым QG checks
- Новый машинный вердикт покрытия. Если гейт реализован как edge sub-gate (FR-3a/b), он
сам вычисляет вердикт (как
check_security_gate) и пишет отчёт<NN>-coverage-report.mdс frontmatter-ключомcoverage_status:(PASS|FAIL), читаемым обратно из того же файла (single source of truth, по образцуsecurity_status:в17-security-report.md). Имя ключа фиксируется и регистр чувствителен. - Реестр
QG_CHECKS. Допустимо добавитьcheck_coverage_gateв реестр (если механизм — зарегистрированный QG) ЛИБО оставить его врезкой-под-гейтом (как security/merge/image-freshness, которые вQG_CHECKSприсутствуют, но исполняются как врезки). Семантика и состав существующихcheck_*— без изменений (NFR-5). - Парсинг frontmatter вердикта — через единый контракт
src/frontmatter.py(parse_frontmatter/read_frontmatter_value), как все вердикт-парсеры (ORCH-052c). Если отчёт несёт обязательную 6-польную схему 52c — добавить её аддитивно, не трогаяcoverage_status:.
7. Совместимость / регресс
- Обратная совместимость: при
coverage_gate_enabled=Falseили для репозитория внеcoverage_gate_repos— поведение конвейера байт-в-байт прежнее; enduro-trails не затронут. - Kill-switch + поэтапный раскат:
coverage_gate_enabled(глобальный),coverage_gate_repos(область). Старт — толькоorchestrator. - Конфиг-флаги (итог §3/§6):
coverage_gate_enabled(bool),coverage_gate_repos(CSV),coverage_min_percent(float, абсолютный порог),coverage_policy(absolute|baseline|both, дефолтboth),coverage_epsilon(float, допуск шума),coverage_tool_fail_closed(bool, дефолтFalse),coverage_run_timeout_s(int). Имена env —ORCH_COVERAGE_*. - never-raise / fail-open в hot-path: ядро не роняет
advance_stage; ошибка инструмента → fail-open + warning по умолчанию (NFR-2). Прод-контейнер/main/force-push — не трогаются (NFR-3). - Restart-safe: базовая линия персистентна; in-flight измерение при рестарте переигрывается штатным механизмом стадии (idempotent).
- Документация (golden source): при выборе механизма архитектор регистрирует артефакт
<NN>-coverage-report.mdи его machine-key вdocs/_standards/PIPELINE_DOCS.md+docs/_templates/, и обновляетdocs/architecture/README.mdиCHANGELOG.mdв том же PR.