src/frontmatter.py grows from a single-key reader into the full machine
contract: reader (read_frontmatter_value, unchanged), one parse primitive
(parse_frontmatter), writer (render/write_frontmatter), schema validator
(validate_schema/REQUIRED_FIELDS, warning-only by default) and a shared
strip_frontmatter helper. The five verdict gates (check_reviewer_verdict,
_parse_tests_verdict, _parse_deploy_status, _parse_staging_status,
parse_security_status) now read through the single parse_frontmatter point
instead of duplicated ad-hoc YAML logic; review_parse._strip_frontmatter and
security_gate.extract_security_findings reuse the shared helper.
Strictly backward compatible + never-raise: STAGE_TRANSITIONS, the QG_CHECKS
composition, verdict semantics (incl. ORCH-047 three-field tester + negative
token priority), reason-strings and worktree->origin/main fallback are 1:1.
The schema validator never influences a gate verdict by default; hard-fail is
reserved behind the frontmatter_validation_strict kill-switch (default False).
New formal handoff spec docs/_standards/HANDOFF_PROTOCOL.md ("stage -> required
output" + required frontmatter schema), aligned 1:1 with PIPELINE_DOCS.md.
Tests: test_frontmatter.py (TC-01..07), test_qg_verdicts.py (TC-08..15),
test_security_gate.py (TC-12), test_stages_invariants.py (TC-16). Full
tests/ green (1212).
Refs: ORCH-076
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
119 lines
11 KiB
Markdown
119 lines
11 KiB
Markdown
# 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** (регистр чувствителен — иначе гейт упадёт ложно).
|