# HANDOFF_PROTOCOL — формальный контракт handoff «стадия → обязательный выход»

> **Назначение.** Нормативная спека: что КАЖДАЯ стадия конвейера обязана оставить на выходе —
> какие документы и какие frontmatter-ключи. Дополняет [`PIPELINE_DOCS.md`](PIPELINE_DOCS.md)
> (карта «документ → агент → стадия → гейт → machine-key») «вертикальным» срезом по стадиям и
> вводит **обязательную frontmatter-схему** для машинной проверки.
>
> **Статус истины (важно).** Источник истины поведения — **код**: `src/stages.py`
> (`STAGE_TRANSITIONS`), `src/qg/checks.py` (`QG_CHECKS` / `check_*` / `_parse_*`),
> `src/stage_engine.py` (врезки под-гейтов). Машинный контракт чтения/записи/валидации
> frontmatter — `src/frontmatter.py`. Эта спека **документирует**; при расхождении первичен код
> (правило ORCH-075).

Введено задачей **ORCH-076** (ORCH-52c — слой 2 эпика ORCH-52: машинный контракт). Слой 1
(ORCH-075/52b) дал описательный стандарт документов; ORCH-52c реализовала единый машинный
frontmatter-контракт (reader + writer + валидатор) и свела чтение пяти вердиктов к одной точке
парсинга. Сквозной ADR: [`adr-0020-frontmatter-contract.md`](../architecture/adr/adr-0020-frontmatter-contract.md);
детально — [`ORCH-076/06-adr/ADR-001-frontmatter-contract.md`](../work-items/ORCH-076/06-adr/ADR-001-frontmatter-contract.md).

---

## 1. Обязательная frontmatter-схема (машинный источник: `frontmatter.REQUIRED_FIELDS`)

Forward-looking аддитивная схема: набор полей, которые handoff-документ стадии **должен** нести
в ведущем YAML-frontmatter. Машинный источник истины — кортеж
[`src/frontmatter.py`](../../src/frontmatter.py) `REQUIRED_FIELDS`:

| Поле | Смысл |
|------|-------|
| `work_item` | ID задачи (`ORCH-NNN` / `ET-NNN`) — к какой задаче относится выход |
| `stage` | стадия, на выходе которой написан документ (`analysis` … `deploy`) |
| `author_agent` | роль-автор (`analyst` / `architect` / `developer` / `reviewer` / `tester` / `deployer`) |
| `status` | человеко/машинно-читаемый статус выхода стадии |
| `created_at` | дата создания артефакта (YYYY-MM-DD) |
| `model_used` | модель агента, сгенерировавшего артефакт (`claude-…`) |

**Режим проверки (ORCH-52c, критично для self-hosting).** Валидатор схемы
`frontmatter.validate_schema` / `maybe_warn_schema` по умолчанию **warning-only** и **никогда не
влияет на boolean-вердикт ни одного гейта**: отсутствие полей логируется (`logger.warning`), но не
роняет конвейер и не заваливает гейт. Жёсткий режим (hard-fail) зарезервирован на будущее
(ORCH-52d) и включается ТОЛЬКО kill-switch'ем `frontmatter_validation_strict`
(env `ORCH_FRONTMATTER_VALIDATION_STRICT`, дефолт `False`). Схема **аддитивна**: старый
документ-вердикт без этих полей читается гейтом ровно как раньше (см. §3).

---

## 2. Контракт handoff по стадиям

Категории документов — как в `PIPELINE_DOCS.md` §2: **required** (всегда), **when-applicable**
(при наличии предмета: инфра / данные / security / post-deploy — отсутствие не нарушение).
«Machine-verdict ключ» — поле, которое exit-гейт/под-гейт ребра читает ТОЛЬКО из frontmatter
(никогда из прозы). Набор документов/ключей/гейтов **согласован 1:1 с `PIPELINE_DOCS.md` §2–§3**.

| Стадия (выход) | Агент | Обязательные документы на выходе | Machine-verdict ключ (читает гейт ребра) | Гейт ребра |
|----------------|-------|----------------------------------|------------------------------------------|------------|
| `created` | система (`_create_initial_docs`) / заказчик | `00-business-request.md` | — (вход, не гейтится) | — |
| `analysis` | analyst | `01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`, `04-test-plan.yaml` | — (гейт проверяет наличие файлов + Approved) | `check_analysis_approved` |
| `architecture` | architect | `06-adr/ADR-NNN-<slug>.md` (≥1); `07-infra-requirements.md`, `08-data-requirements.md`, `10-tech-risks.md` (when-applicable/required-info) | — (гейт проверяет наличие `06-adr/` ≥1 ИЛИ `07-…`) | `check_architecture_done` |
| `development` | developer | код + тесты в ветке (артефакт-док не пишется; гейт — зелёный CI) | — (гейт читает CI-статус Gitea) | `check_ci_green` |
| `review` | reviewer | `12-review.md` | `verdict:` (`APPROVED` \| `REQUEST_CHANGES`) | `check_reviewer_verdict` |
| `testing` | tester | `13-test-report.md` | `result:` / `verdict:` / `status:` (`PASS` \| `FAIL` \| `BLOCKED`; три равноранговых, ORCH-047) | `check_tests_passed` |
| `deploy-staging` | deployer | `15-staging-log.md` (required для self-hosting); `17-security-report.md` (security-под-гейт, when-applicable) | `staging_status:` (`SUCCESS` \| `FAILED`); `security_status:` (`PASS` \| `FAIL`) | `check_staging_status` (ребро); под-гейты ребра `deploy-staging→deploy`: `check_security_gate` → `check_branch_mergeable` → `check_staging_image_fresh` |
| `deploy` | deployer / deploy-finalizer | `14-deploy-log.md` | `deploy_status:` (`SUCCESS` \| `FAILED`) | `check_deploy_status` |
| `done` | — | — (терминал) | — | — |
| пост-`done` наблюдение | post-deploy-monitor | `16-post-deploy-log.md` (when-applicable, ORCH-021) | `post_deploy_status:` (`HEALTHY` \| `DEGRADED`) — **информационный, не гейт** | — (телеметрия петли уроков / наблюдаемость) |

### Примечания (нормативные)

- **Под-гейты ребра `deploy-staging → deploy`** (`check_security_gate` → `check_branch_mergeable`
  → `check_staging_image_fresh`) — это **врезки в `advance_stage`**, а НЕ строки
  `STAGE_TRANSITIONS`. Их порядок и условность раската не меняются этой спекой.
- **`15-staging-log.md`** обязателен только для self-hosting репо (`orchestrator`); для прочих
  репо staging-гейт — N/A (ORCH-35), документ не требуется.
- **`16-post-deploy-log.md`** несёт `post_deploy_status:`, но это **информационный** ключ
  (телеметрия ORCH-8 / наблюдаемость), гейтом он НЕ парсится.
- **`09-…` / `05-…` / `11-…`** — зарезервированные/legacy номера; канон reviewer'а — `12-review.md`.

---

## 3. Machine-verdict доки vs информационные (честный механизм проверки)

Полностью согласовано с `PIPELINE_DOCS.md` §3. Machine-verdict док — гейт читает ТОЛЬКО
YAML-frontmatter (через единый `frontmatter.parse_frontmatter`), маппит ключ в вердикт; имя ключа
чувствительно к регистру, значение парсер приводит к верхнему регистру.

| Документ | Machine-key | Парсер | Эффект вердикта |
|----------|-------------|--------|-----------------|
| `12-review.md` | `verdict:` | `check_reviewer_verdict` | `APPROVED` → дальше; `REQUEST_CHANGES` → откат на `development` |
| `13-test-report.md` | `result:` / `verdict:` / `status:` | `_parse_tests_verdict` | `PASS` → дальше; `FAIL`/`BLOCKED` → откат (негативный токен авторитетен) |
| `14-deploy-log.md` | `deploy_status:` | `_parse_deploy_status` | `SUCCESS` → `done`; `FAILED` → откат (БАГ-8) |
| `15-staging-log.md` | `staging_status:` | `_parse_staging_status` | `SUCCESS` → дальше; `FAILED` → откат (self-hosting; иначе N/A) |
| `17-security-report.md` | `security_status:` | `check_security_gate` → `parse_security_status` | `PASS` → дальше; `FAIL` → откат |

**Информационные доки** (гейтом НЕ парсятся): `00-business-request.md`, `08-data-requirements.md`,
`10-tech-risks.md`, `16-post-deploy-log.md`.

**Аддитивность схемы (§1).** Документ-вердикт БЕЗ полей схемы из §1, но с вердикт-ключом, читается
гейтом РОВНО как раньше: схема не участвует в вычислении вердикта при дефолтном
`frontmatter_validation_strict=False`.

---

## 4. Единый машинный контракт — `src/frontmatter.py`

Все операции с frontmatter сведены в один leaf-модуль (never-raise):

- `read_frontmatter_value(path, key) -> str | None` — single-key reader (контракт неизменен, BC).
- `parse_frontmatter(content) -> FrontmatterParse` — **единственная точка** парсинга YAML-frontmatter
  (`data` / `has_block` / `malformed` / `yaml_error`); пять вердикт-парсеров делегируют сюда.
- `parse_frontmatter_dict` / `read_frontmatter` — ярлыки к распарсенному mapping.
- `render_frontmatter` / `write_frontmatter` — writer (формат совместим с существующими парсерами).
- `validate_schema` / `REQUIRED_FIELDS` / `maybe_warn_schema` — схема §1 (warning-only по умолчанию).
- `strip_frontmatter` — общий хелпер тела (заменил дубли).
- Kill-switch жёсткой валидации: `config.frontmatter_validation_strict`
  (env `ORCH_FRONTMATTER_VALIDATION_STRICT`, дефолт `False`).

> Перед написанием номерного дока бери скелет из [`docs/_templates/`](../_templates/) и **не меняй
> имя machine-key frontmatter** (регистр чувствителен — иначе гейт упадёт ложно).
