---
work_item: ORCH-027
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---

# 08 — Требования к данным: ORCH-027 — Code coverage как гейт

Work Item: **ORCH-027** · Repo: **orchestrator** · Стадия: architecture

> When-applicable / информационный (гейтом не парсится). Затрагивается схема БД — вводится
> **одна аддитивная таблица** базовой линии покрытия. Существующие таблицы не мигрируются.

## Изменения схемы БД

Новая аддитивная таблица `coverage_baseline` (паттерн `repo_freeze`/`job_deps` —
`CREATE TABLE IF NOT EXISTS` в `init_db`, `src/db.py`; без `ALTER`/миграции существующих):

```sql
CREATE TABLE IF NOT EXISTS coverage_baseline (
    repo        TEXT PRIMARY KEY,   -- репозиторий (напр. "orchestrator")
    coverage    REAL NOT NULL,      -- last-known базовая линия покрытия main (%, line coverage)
    source_sha  TEXT,               -- SHA main, на котором зафиксирована базовая линия (аудит)
    updated_at  TEXT NOT NULL       -- ISO-таймстамп последнего ratchet/bootstrap
);
```

Доступ — через аддитивные read-only/мутирующие хелперы `src/db.py`:
- `get_coverage_baseline(repo) -> float | None` (None ⇒ bootstrap-режим, базовой линии ещё нет);
- `ratchet_coverage_baseline(repo, coverage, sha) -> bool` — **атомарный compare-and-set**:
  `INSERT` при отсутствии строки; иначе `UPDATE ... SET coverage=?, source_sha=?, updated_at=?
  WHERE repo=? AND coverage <= ?` (базовая линия **никогда не понижается**);
- `set_coverage_baseline(repo, coverage, sha)` — безусловная установка (ручной override D8 /
  `POST /coverage/baseline`).

## Новые/изменённые сущности

- **`coverage_baseline`** — одна строка на репозиторий; keyed by `repo`. Инвариант: `coverage`
  монотонно не убывает через `ratchet_coverage_baseline` (только `set_coverage_baseline`/ручной
  override может понизить — легитимный разовый случай, D8). На общей прод-БД (self-hosting)
  строки разных репозиториев изолированы первичным ключом.
- **Артефакт `18-coverage-report.md`** — НЕ БД-сущность: файл в `docs/work-items/<id>/`,
  несёт frontmatter `coverage_status: PASS|FAIL` + `measured_coverage`/`baseline`/`floor`/
  `policy`/`delta`. Source of truth измеренного значения для ratchet (D5).

Существующие таблицы (`tasks`, `jobs`, `job_deps`, `repo_freeze`, `agent_runs`,
`tracker_messages`, …) — **не изменяются** (NFR-5/AC-8).

## Совместимость данных / миграции

- **Аддитивность:** только `CREATE TABLE IF NOT EXISTS` — ни один существующий столбец/таблица
  не трогается; миграции существующих данных нет.
- **Идемпотентность:** `CREATE TABLE IF NOT EXISTS` безопасен при повторном старте; bootstrap
  (первый `INSERT`) выполняется один раз на репозиторий.
- **Restart-safe:** базовая линия персистентна; in-flight измерение при рестарте переигрывается
  штатным механизмом стадии (idempotent — гейт пересчитает вердикт, ratchet — атомарный
  compare-and-set, повтор не понизит и не задвоит).
- **Атомарность / анти-гонка:** ratchet — единичный SQL `UPDATE ... WHERE coverage <= ?` (или
  `INSERT`), выполняется под held merge-lease (ORCH-043, per-repo сериализация merge) → двойная
  защита от параллельных слияний.
- **Влияние на общую прод-БД:** одна маленькая таблица (≤ числа репозиториев строк); нулевой
  риск для enduro-trails и прочих проектов (строки изолированы по `repo`, гейт для них no-op).
- При `coverage_gate_enabled=False` таблица может существовать пустой/инертной — нулевая
  регрессия.
