refactor(frontmatter): unified frontmatter contract + handoff spec (ORCH-52c)
All checks were successful
CI / test (push) Successful in 32s
CI / test (pull_request) Successful in 35s

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>
This commit is contained in:
2026-06-09 14:03:49 +03:00
parent 0cf5bfc84b
commit 428461898d
14 changed files with 1043 additions and 109 deletions

View File

@@ -39,7 +39,7 @@ created → analysis → architecture → development → review → testing →
**Реестр QG** (`QG_CHECKS`): check_analysis_approved, check_analysis_complete, check_architecture_done, check_ci_green, check_review_approved, check_tests_passed, check_reviewer_verdict, check_tests_local, check_deploy_status, check_staging_status, check_branch_mergeable (ORCH-043), check_staging_image_fresh (ORCH-058), check_security_gate (ORCH-022).
**Канон гейтов:** машинные вердикты читаются ТОЛЬКО из YAML-frontmatter, никогда из прозы. Лог-файлы мержатся в `origin/main` отдельным PR; гейт читает из `origin/main`.
**Канон гейтов:** машинные вердикты читаются ТОЛЬКО из YAML-frontmatter, никогда из прозы. Лог-файлы мержатся в `origin/main` отдельным PR; гейт читает из `origin/main`. **Единый frontmatter-контракт (ORCH-52c / ORCH-076):** парсинг YAML-frontmatter сведён к одной точке — `src/frontmatter.parse_frontmatter` (структура `data/has_block/malformed/yaml_error`, never-raise); пять вердикт-парсеров (`check_reviewer_verdict`, `_parse_tests_verdict`, `_parse_deploy_status`, `_parse_staging_status`, `parse_security_status`) делегируют ей вместо дублированной ad-hoc логики. Модуль также несёт writer (`render/write_frontmatter`), валидатор обязательной схемы (`validate_schema`/`REQUIRED_FIELDS`, warning-only по умолчанию; hard-fail только под kill-switch `frontmatter_validation_strict`, дефолт `False`) и общий `strip_frontmatter`. Семантика вердиктов / `STAGE_TRANSITIONS` / состав `QG_CHECKS` — без изменений (1:1).
### Стандарт документов конвейера (ORCH-075, ORCH-52b)
Структура номерных документов work item (`00-business-request.md``17-security-report.md`),
@@ -49,9 +49,13 @@ created → analysis → architecture → development → review → testing →
[`docs/_templates/`](../_templates/). Манифест **документирует** поведение гейтов (источник истины
остаётся код: `src/stages.py`, `src/qg/checks.py`), честно различая machine-verdict доки
(`12/13/14/15/17` — несут читаемый гейтом ключ) и информационные (`00/08/10/16` — гейтом не
парсятся). Это слой 1 (описательный); машинная проверка соответствия — задача ORCH-52c. ADR:
[adr-0019](adr/adr-0019-pipeline-docs-standard.md), детально —
`docs/work-items/ORCH-075/06-adr/ADR-001-pipeline-docs-standard.md`.
парсятся). Это слой 1 (описательный). **Слой 2 (машинный) реализован в ORCH-52c (ORCH-076):**
единый frontmatter-контракт `src/frontmatter.py` + формальная спека handoff «стадия → обязательный
выход» с обязательной frontmatter-схемой (`REQUIRED_FIELDS`) —
[`docs/_standards/HANDOFF_PROTOCOL.md`](../_standards/HANDOFF_PROTOCOL.md). ADR:
[adr-0019](adr/adr-0019-pipeline-docs-standard.md) / [adr-0020](adr/adr-0020-frontmatter-contract.md),
детально — `docs/work-items/ORCH-075/06-adr/ADR-001-pipeline-docs-standard.md`,
`docs/work-items/ORCH-076/06-adr/ADR-001-frontmatter-contract.md`.
### Модель и эффорт по ролям (ORCH-41, валидация ORCH-74)
Модель и `--effort` каждого агента берутся из config (`src/config.py`), резолвятся `launcher.resolve_agent_model` / `resolve_agent_effort` по приоритету **project-override (`projects_json` `agent_models`/`agent_efforts`) > `ORCH_AGENT_MODEL_<AGENT>`/`ORCH_AGENT_EFFORT_<AGENT>` > `*_default` > CLI-дефолт (без флага)**. **Эффорт (ORCH-081):** ниже `*_default` добавлен непустой **per-role floor** — class-default поля `agent_effort_<role>` из `config.py` (его пустой env перебить не может). Floor — строго последний уровень (ниже default) и срабатывает ТОЛЬКО когда все уровни пусты, поэтому пустые прод-`ORCH_AGENT_EFFORT_*=` (которые pydantic трактует как явное `''` и обнуляют дефолт) больше не приводят к запуску без `--effort`: каждая роль получает свой канонический пол (developer=`xhigh`, tester/deployer=`medium`, прочие=`high`). Непустой явный конфиг по-прежнему побеждает floor; опечатка вне `VALID_EFFORTS` дропается валидацией ДО floor (never-break, не маскируется). См. `docs/work-items/ORCH-081/06-adr/ADR-001-effort-resolution-floor.md`. frontmatter `model:` в `.openclaw/agents/*.md` **удалён** (ORCH-74 G1) — он был мёртвой/лживой декларацией (launcher его не читает); config — единственный источник правды о модели. Model-routing (G3) НЕ включён — все 6 агентов на `claude-opus-4-8`.
@@ -642,7 +646,7 @@ Monitoring after Deploy → Done
```
- **Длительность** считается launcher'ом (`_monitor_agent`) и пробрасывается в `_post_usage_comments`; для analyst (коммент строится в `stage_engine`) используется DB-фоллбэк `usage.get_agent_duration(task_id, agent)`.
- **Vердикт-парсер** — `src/frontmatter.read_frontmatter_value(...)` (defensive, никогда не raise). Машинные ключи: reviewer → `verdict:` (12-review.md); **testing-гейт `check_tests_passed` (13-test-report.md) → любое из трёх равноправных: `result:` (канон промпта тестера), `verdict:`, `status:`** (ORCH-047, ADR-001); deployer → `deploy_status:` (14-deploy-log.md), `staging_status:` (15-staging-log.md). Negative-токен в любом поле авторитетен (перебивает positive).
- **Vердикт-парсер** — единый контракт `src/frontmatter.py` (defensive, never-raise): коммент-хелпер использует `read_frontmatter_value(...)` (single-key, BC), гейты — `parse_frontmatter(...)` (ORCH-52c). Машинные ключи: reviewer → `verdict:` (12-review.md); **testing-гейт `check_tests_passed` (13-test-report.md) → любое из трёх равноправных: `result:` (канон промпта тестера), `verdict:`, `status:`** (ORCH-047, ADR-001); deployer → `deploy_status:` (14-deploy-log.md), `staging_status:` (15-staging-log.md). Negative-токен в любом поле авторитетен (перебивает positive).
- Формат коммента **не** меняет реестр гейтов и стадий; коммент — отображение, не управление.
## База данных (SQLite)