Files

15 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-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.