--- work_item: ORCH-027 stage: analysis author_agent: analyst status: ready-for-review created_at: 2026-06-10 model_used: 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//-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) - Артефакт-отчёт `-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`) и пишет отчёт `-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):** при выборе механизма архитектор регистрирует артефакт `-coverage-report.md` и его machine-key в `docs/_standards/PIPELINE_DOCS.md` + `docs/_templates/`, и обновляет `docs/architecture/README.md` и `CHANGELOG.md` в том же PR.