# adr-0020: Единый frontmatter-контракт + спека handoff (reader/writer/валидатор)

Статус: **Accepted** · Дата: 2026-06-09 · Источник: **ORCH-076** (ORCH-52c)
Детально: [`docs/work-items/ORCH-076/06-adr/ADR-001-frontmatter-contract.md`](../../work-items/ORCH-076/06-adr/ADR-001-frontmatter-contract.md)

## Контекст

Слой 1 эпика ORCH-52 (ORCH-075/52b) дал **описательный** стандарт документов
(`docs/_standards/PIPELINE_DOCS.md`), явно отложив машинную проверку на ORCH-52c. В коде:
`src/frontmatter.py` — только single-key reader (never-raise), а ~10-строчный блок парсинга
YAML-frontmatter **продублирован** в 5 вердикт-парсерах (`check_reviewer_verdict`,
`_parse_tests_verdict`, `_parse_deploy_status`, `_parse_staging_status`, `parse_security_status`)
+ в `_strip_frontmatter`/`extract_security_findings`. Единого контракта чтения, writer'а, схемы
и формальной спеки handoff — нет. Эти парсеры читают вердикты **на гейтах self-hosting**
инструмента, обслуживающего прод других проектов из общего инстанса → любой регресс = стоп
конвейера всех проектов.

## Решение

1. **`src/frontmatter.py` → полный frontmatter-контракт** (функции в существующем leaf-модуле,
   контракт **never-raise**): сохранённый `read_frontmatter_value` (без изменений) + единый
   парс-примитив `parse_frontmatter(content) -> FrontmatterParse` (единственная точка
   YAML-логики, структура различает no-block / malformed / yaml-error / data) + `render_/
   write_frontmatter` (writer) + `validate_schema` (обязательная схема
   `work_item, stage, author_agent, status, created_at, model_used`) + `strip_frontmatter`.
2. **Унифицируется механизм парсинга, НЕ семантика.** Все 5 вердикт-парсеров читают YAML через
   `parse_frontmatter`; token-наборы, upper-casing, приоритет негативного токена, 3-полевой
   контракт tester'а (ORCH-047), fallback `worktree→origin/main` — **1:1**. Сигнатуры и
   `tuple[bool, str]` — неизменны. Reason-строки переносятся дословно.
3. **Валидатор не hard-fail по умолчанию.** Флаг `frontmatter_validation_strict` (env
   `ORCH_FRONTMATTER_VALIDATION_STRICT`, дефолт `False`): default — warning/лог, **вне
   вердикт-пути гейтов** (нулевая регрессия); hard-fail — зарезервированный strict-режим
   (включение — с ORCH-52d). Иначе ORCH-52c заблокировала бы собственный деплой.
4. **Формальная спека handoff** `docs/_standards/HANDOFF_PROTOCOL.md` — «стадия → обязательный
   выход» (документы + frontmatter-ключи), согласована 1:1 с `PIPELINE_DOCS.md` §2–§3; источник
   истины — код. `PIPELINE_DOCS.md` обновляется ссылкой + отметкой о реализации машинного слоя.
5. **Без изменений** `STAGE_TRANSITIONS`, состава `QG_CHECKS`, API, схемы БД.

## Альтернативы

- Общий «умный» verdict-резолвер (поле+токены для всех гейтов) — отклонён: различия token-логики
  → риск тонкого регресса на гейте при self-hosting. Унифицируем только парс YAML.
- Класс/новый пакет — отклонён: состояния нет, лишний blast radius.
- Hard-fail валидатор по умолчанию — отклонён (NFR-3: self-block собственного деплоя).
- Сторонняя `python-frontmatter` — отклонена: лишняя зависимость ради ~30 строк.

## Последствия

- **+** Конец дублирования/рассинхрона парсинга; writer+валидатор+схема готовы к ORCH-52d;
  спека handoff закрывает пробел контракта стадий.
- **+** Нулевая регрессия по построению: семантика и reason-строки 1:1, валидатор инертен при
  дефолте, never-raise сохранён, enduro 1:1.
- **−** Унификация частичная (парс, не семантика); strict-режим «спящий» до ORCH-52d.
- **Обратимость:** `frontmatter_validation_strict=False` ⇒ прежнее поведение; перевод гейтов
  поведенчески инвариантен.
- **Риск:** первый боевой `autoDeploy` орка (ORCH-089) — наблюдение за стадией `deploy`
  (`docs/work-items/ORCH-076/10-tech-risks.md`).

## Связи

- Опирается: adr-0019 (pipeline-docs-standard, ORCH-075), ORCH-016 (reader), ORCH-047
  (3-полевой tester), adr-0012 (security-гейт), adr-0018 (auto-label/`autoDeploy`).
- Готовит: ORCH-52d (эмиссия полной схемы агентами; возможное включение strict).
