work_item: ORCH-027
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-10
model_used: claude-opus-4-8
title: "Code coverage gate — защита от деградации покрытия тестами"
framework: pytest
scope: >
  Покрываются: чистая логика вердикта покрытия (режимы absolute/baseline/both, границы,
  epsilon), ratchet-обновление базовой линии, условность (kill-switch + per-repo область),
  fail-open/fail-closed при ошибке инструмента, never-raise, наблюдаемость (GET /queue,
  Telegram при FAIL), интеграция гейта в advance_stage / точку конвейера. Вне покрытия:
  фактические измерители не-Python стеков (jest/jacoco), мутационное тестирование.
notes: >
  Тесты не должны исполнять реальный прод-деплой и не трогают prod-контейнер/main.
  Измерение покрытия в тестах мокается/стабится (фиктивная метрика), реальный pytest-прогон
  под coverage проверяется отдельным интеграционным тестом на минимальном фикстур-репо/worktree.
  Полный регресс tests/ должен оставаться зелёным (нулевая регрессия для enduro-trails).

tests:
  - id: TC-01
    type: unit
    description: "compute_coverage_verdict, policy=absolute: measured>=floor → PASS; measured<floor-epsilon → FAIL; ровно на пороге → PASS"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-02
    type: unit
    description: "compute_coverage_verdict, policy=baseline: measured>=baseline → PASS; ниже baseline-epsilon → FAIL (no-regression / ratchet)"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-03
    type: unit
    description: "compute_coverage_verdict, policy=both: PASS только при выполнении обоих условий; нарушение любого → FAIL"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-04
    type: unit
    description: "epsilon-допуск: дрожание покрытия в пределах epsilon у границы не заворачивает задачу (анти-флап, NFR-4)"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-05
    type: unit
    description: "Ratchet базовой линии: при слиянии baseline растёт до смёрженного покрытия только если >= текущей; меньшее значение не понижает baseline"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-06
    type: unit
    description: "Bootstrap базовой линии: первичная инициализация фактическим покрытием main при отсутствии сохранённого значения"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-07
    type: unit
    description: "Условность applies(repo): пустой coverage_gate_repos → только self-hosting (is_self_hosting_repo); repo вне области → no-op (True, 'N/A'), дорогой прогон не запускается"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-08
    type: unit
    description: "Kill-switch coverage_gate_enabled=False → гейт инертен, advance_stage ведёт себя 1:1 как до ORCH-027"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-09
    type: unit
    description: "Fail-open по умолчанию: ошибка/недоступность coverage-инструмента и coverage_tool_fail_closed=False → PASS + WARNING-лог; флаг True → FAIL (fail-closed)"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-10
    type: unit
    description: "never-raise: внутреннее исключение (битый вывод coverage, отсутствие worktree) перехватывается, не всплывает в advance_stage"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-11
    type: unit
    description: "Запись/чтение отчёта: write_coverage_report пишет coverage_status: PASS|FAIL во frontmatter; parse читает обратно из того же файла через src/frontmatter.py (single source of truth)"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-12
    type: unit
    description: "Self-hosting безопасность: гейт не вызывает деплой-хук, не перезапускает прод-контейнер, не пушит/форс-пушит в main/master"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-13
    type: integration
    description: "Гейт в конвейере: при measured ниже политики advance_stage не продвигает к деплою и инициирует откат на development (инкремент developer-retry); при PASS — продвигает штатно"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-14
    type: integration
    description: "Реальное измерение: pytest под coverage в ensure_worktree на минимальном фикстур-репо возвращает корректную метрику line coverage и тайм-аутится по coverage_run_timeout_s"
    module: tests/test_coverage_gate.py
    expected: PASS

  - id: TC-15
    type: integration
    description: "Наблюдаемость: FAIL даёт Telegram-алерт с кликабельным номером (измеренное/порог/дельта); GET /queue несёт read-only блок coverage; совместимость — STAGE_TRANSITIONS/QG_CHECKS/существующие вердикт-ключи не изменены"
    module: tests/test_coverage_gate.py
    expected: PASS
