diff --git a/CHANGELOG.md b/CHANGELOG.md index 18554b1..3c1ea53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ Формат: [Keep a Changelog](https://keepachangelog.com/). Записи — на смысловой PR/задачу. ## [Unreleased] +- **Стандарт документов конвейера: `docs/_standards/PIPELINE_DOCS.md` + `docs/_templates/` + ADR-naming** (ORCH-075 / ORCH-52b, `docs`): зафиксирован golden source структуры номерных документов work item (`00-business-request.md` … `17-security-report.md`). **Docs-only**, нулевой рантайм-риск: `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / `src/stage_engine.py` / схема БД — **не трогаются** (изменения только под `docs/**` + `CLAUDE.md`). + - **Манифест** `docs/_standards/PIPELINE_DOCS.md` — карта «стадия → агент → документ → категория (`required`/`when-applicable`/`optional`) → гейт/механизм → frontmatter machine-key», сверенная с `src/stages.py` (`STAGE_TRANSITIONS`) и `src/qg/checks.py` (`_parse_*`). Манифест **документирует** поведение гейтов, но НЕ источник истины (источник — код, ADR-001 §D2); честно различает machine-verdict доки (`12`→`verdict:`, `13`→`result:`, `14`→`deploy_status:`, `15`→`staging_status:`, `17`→`security_status:`) и информационные (`00/08/10/16` — гейтом не парсятся). Под-гейты ребра `deploy-staging→deploy` (security/merge/image-freshness) помечены как врезки в `advance_stage`, а не строки `STAGE_TRANSITIONS`. + - **Шаблоны** `docs/_templates/*` (15 копируемых скелетов) — для каждого `required`/`when-applicable` дока; машинные доки несут точный frontmatter-ключ из ground-truth (`_parse_*`), чтобы скопированный скелет проходил гейт без угадывания. Служебные каталоги `docs/_standards/` / `docs/_templates/` лежат ВНЕ `docs/work-items//` → невидимы гейтам наличия файлов (`check_architecture_done`/`check_analysis_complete`). + - **ADR-naming** зафиксирован: `docs/work-items//06-adr/ADR-NNN-.md` (NNN с `001`); сквозные решения дублируются в `docs/architecture/adr/adr-NNNN-.md` (4-значная нумерация). Точки-ссылки: `CLAUDE.md` (раздел «Артефакты задачи» + правило 2), `docs/architecture/README.md` (раздел «Стандарт документов конвейера»). Тесты: `tests/test_orch_52b_docs_standard.py` (TC-01…TC-20, структурные проверки наличия/секций/frontmatter). ADR: `docs/work-items/ORCH-075/06-adr/ADR-001-pipeline-docs-standard.md`, сквозной `docs/architecture/adr/adr-0019-pipeline-docs-standard.md`. - **Авто-режим по лейблам: autoApprove (авто-BRD) + autoDeploy (авто-деплой)** (ORCH-089, `feat`): сняты **два** человеческих гейта конвейера, тормозящих пакетный автономный прогон (эпик ORCH-088) — гейт BRD (`analysis`: ручной `Approved`) и гейт прод-деплоя (`deploy` Phase A: ручной `Confirm Deploy`, ORCH-059). Решение выборочно (лейбл Plane на задаче), декларативно, обратимо и **не трогает ни одной технической проверки**. Аддитивно по образцу условных под-гейтов (ORCH-035/043/058/088): leaf `src/labels.py` (never-raise) + две точечные врезки + флаги; `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/схема БД — **без изменений**. - **`autoApprove`** → врезка в `stage_engine._handle_analysis_approved_flow` (ветка `files_ok`) ПОСЛЕ `In Review`+коммента: `set_issue_approved` (индикация) + лог/Telegram/Plane-коммент + `advance_stage(..., finished_agent=None)` — **тот же путь, что человеческий Approved** (`approved-via-status` → `analysis → architecture` + `mark_brd_review_ended`). Без дублирования переходной логики; re-entrancy безопасна (вложенный вызов идёт с `finished_agent=None`, не входит в analyst-ветку). - **`autoDeploy`** → врезка в `stage_engine._handle_self_deploy_phase_a` сразу после advance на `deploy`+`clear_state` (ДО «ask-human»): лог/Telegram/Plane-коммент + `_handle_self_deploy_phase_b(...)` (idempotency-маркер `INITIATED`, статус `Deploying`, finalizer). Пропускаются лишь индикативно-человеческие шаги (`APPROVE_REQUESTED`+`Awaiting Deploy`+«смените на Confirm Deploy»). **BR-5 структурно:** Phase A достигается только после зелёных под-гейтов ребра `deploy-staging → deploy` (security → merge-gate → image-freshness → staging) → autoDeploy физически не деплоит сломанное. diff --git a/CLAUDE.md b/CLAUDE.md index 5ab30f8..a2bca7c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -122,9 +122,11 @@ created → analysis → architecture → development → review → testing → ## Артефакты задачи (`docs/work-items//`) `00-business-request.md`, `01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`, `04-test-plan.yaml`, `06-adr/ADR-NNN-slug.md`, `07-infra-requirements.md`, `08-data-requirements.md`, `10-tech-risks.md`, `12-review.md`, `13-test-report.md`, `14-deploy-log.md`, `15-staging-log.md`, `16-post-deploy-log.md` (post-deploy наблюдение, ORCH-021), `17-security-report.md` (security-гейт: `security_status:`/secrets/deps, ORCH-022). +**Стандарт документов (ORCH-075, ORCH-52b):** структура каждого дока, карта «стадия→агент→документ→гейт→machine-key» и конвенция ADR-naming зафиксированы в `docs/_standards/PIPELINE_DOCS.md` (golden source); копируемые скелеты — в `docs/_templates/`. Перед написанием номерного дока бери скелет из `docs/_templates/` и не меняй имя machine-key frontmatter (регистр чувствителен — иначе гейт упадёт ложно). + ## Правила для агентов 1. Перед любым действием прочесть этот файл и `docs/architecture/README.md`. -2. **Документация = golden source наравне с кодом.** Изменил функционал → обнови доку В ТОМ ЖЕ PR. Архитектурное решение → заведи ADR. Обнови `CHANGELOG.md`. +2. **Документация = golden source наравне с кодом.** Изменил функционал → обнови доку В ТОМ ЖЕ PR. Архитектурное решение → заведи ADR (формат — `docs/_standards/PIPELINE_DOCS.md` §4). Структура номерных доков и шаблоны — `docs/_standards/PIPELINE_DOCS.md` + `docs/_templates/`. Обнови `CHANGELOG.md`. 3. Никогда не править артефакты других этапов. 4. Никогда не комментировать ТЗ задним числом — если ТЗ не годится, возвращай в Анализ. 5. Никогда не закрывать задачу самостоятельно — это делает CI / финальная стадия. diff --git a/docs/_standards/PIPELINE_DOCS.md b/docs/_standards/PIPELINE_DOCS.md new file mode 100644 index 0000000..a5fa33f --- /dev/null +++ b/docs/_standards/PIPELINE_DOCS.md @@ -0,0 +1,138 @@ +# PIPELINE_DOCS — стандарт документов конвейера (golden source структуры) + +> **Назначение.** Единая карта «стадия → агент → документ → категория → гейт/механизм → +> frontmatter machine-key» + конвенция ADR-naming. Это **golden source структуры** номерных +> документов work item (`00-business-request.md` … `17-security-report.md`), который каждая +> агентская роль пишет на своей стадии. +> +> **Статус истины (важно).** Манифест **документирует** текущее поведение гейтов, но НЕ является +> их источником истины. Источник истины — код: `src/stages.py` (`STAGE_TRANSITIONS`), +> `src/qg/checks.py` (`QG_CHECKS` / `check_*` / `_parse_*`), `src/stage_engine.py`. При будущей +> правке гейта первична правка кода, манифест обновляется следом (ORCH-075 / ADR-001 §D2). +> +> **Копируемые скелеты** каждого документа — в каталоге [`docs/_templates/`](../_templates/): +> «скопировал → заполнил → не угадываешь структуру/ключ». + +Введён задачей **ORCH-075** (ORCH-52b — слой 1 эпика ORCH-52). Сквозной ADR: +[`docs/architecture/adr/adr-0019-pipeline-docs-standard.md`](../architecture/adr/adr-0019-pipeline-docs-standard.md); +детально — `docs/work-items/ORCH-075/06-adr/ADR-001-pipeline-docs-standard.md`. + +--- + +## 1. Конвейер стадий (ground-truth `STAGE_TRANSITIONS`) + +``` +created → analysis → architecture → development → review → testing → deploy-staging → deploy → done + ↑ │ + └──── REQUEST_CHANGES ──────┘ (откат на development, max 3 retries) +``` + +Каждое ребро несёт ровно один exit-гейт (`src/stages.py`): +`check_analysis_approved → check_architecture_done → check_ci_green → check_reviewer_verdict → +check_tests_passed → check_staging_status → check_deploy_status`. + +**Под-гейты ребра `deploy-staging → deploy`** (`check_security_gate` → `check_branch_mergeable` → +`check_staging_image_fresh`) — это **врезки в `advance_stage`**, а НЕ строки `STAGE_TRANSITIONS`. +Аналогично под-гейт ребра `deploy → done` (`_handle_merge_verify`, ORCH-071/073) — врезка, не +зарегистрированный QG. Карта стадий о них не «лжёт»: они не являются стадиями. + +--- + +## 2. Манифест: документ → агент → категория → стадия → гейт → machine-key + +Категории: **required** (пишется всегда), **when-applicable** (пишется при наличии предмета: +инфра / данные / security / post-deploy — отсутствие не нарушение), **optional** / **legacy**. + +| Документ | Владелец-агент | Категория | Стадия написания | Гейт / механизм проверки | Frontmatter machine-key | +|----------|----------------|-----------|------------------|--------------------------|-------------------------| +| `00-business-request.md` | система (Plane webhook `_create_initial_docs`) / заказчик | required | `created` (инициализация) | не гейтится (вход) | — | +| `01-brd.md` | analyst | required | `analysis` | exit-гейт `analysis→architecture` = `check_analysis_approved` (Approved + полнота файлов); helper `check_analysis_complete` (наличие `01/02/03/04`) | — | +| `02-trz.md` | analyst | required | `analysis` | то же | — | +| `03-acceptance-criteria.md` | analyst | required | `analysis` | то же | — | +| `04-test-plan.yaml` | analyst | required | `analysis` | то же | — | +| `06-adr/ADR-NNN-.md` | architect | required | `architecture` | `check_architecture_done` (наличие каталога `06-adr/` ≥1 файл ИЛИ `07-infra-requirements.md`) | — | +| `07-infra-requirements.md` | architect | when-applicable | `architecture` | `check_architecture_done` (учитывается при наличии) | — | +| `08-data-requirements.md` | architect | when-applicable | `architecture` | информационный (гейтом не парсится) | — | +| `10-tech-risks.md` | architect | required | `architecture` | информационный (гейтом не парсится) | — | +| `12-review.md` | reviewer | required | `review` | `check_reviewer_verdict` | `verdict:` (`APPROVED` \| `REQUEST_CHANGES`) | +| `13-test-report.md` | tester | required | `testing` | `check_tests_passed` (`_parse_tests_verdict`) | `result:` / `verdict:` / `status:` (`PASS` \| `FAIL` \| `BLOCKED`; три равноранговых, ORCH-047) | +| `14-deploy-log.md` | deployer / deploy-finalizer | required | `deploy` | `check_deploy_status` (`_parse_deploy_status`) | `deploy_status:` (`SUCCESS` \| `FAILED`) | +| `15-staging-log.md` | deployer | required (self-hosting) | `deploy-staging` | `check_staging_status` (self-hosting; иначе N/A — ORCH-35) | `staging_status:` (`SUCCESS` \| `FAILED`) | +| `16-post-deploy-log.md` | post-deploy-monitor | when-applicable | пост-`done` наблюдение (ORCH-021; не ребро `STAGE_TRANSITIONS`) | информационный (гейтом не парсится) | `post_deploy_status:` (`HEALTHY` \| `DEGRADED`) | +| `17-security-report.md` | security-гейт (детерминированный, ORCH-022) | when-applicable | под-гейт ребра `deploy-staging→deploy` | `check_security_gate` (врезка в `advance_stage`) | `security_status:` (`PASS` \| `FAIL`) | + +### Примечания манифеста (нормативные) + +- **Под-гейты ребра `deploy-staging→deploy`** (`check_security_gate` → `check_branch_mergeable` → + `check_staging_image_fresh`) исполняются как врезки в `advance_stage`, а НЕ строки + `STAGE_TRANSITIONS`. Не путать с exit-гейтами рёбер. +- **`09-review.md`** — legacy fallback от старой нумерации; **канон — `12-review.md`**. В основную + таблицу как канон не вносится; reviewer пишет `12-review.md`. +- **Категория `when-applicable`** = документ пишется при наличии соответствующего предмета + (инфра / данные / security / post-deploy). Его отсутствие — не нарушение приёмки. +- **`05-…` / `09-…` / `11-…`** — зарезервированные/legacy номера, в текущем каноне не используются. + +--- + +## 3. Machine-verdict доки vs информационные (честный механизм проверки) + +**Machine-verdict доки** — гейт читает ТОЛЬКО YAML-frontmatter (никогда прозу), маппит ключ в +вердикт. Имя ключа чувствительно к регистру; значение парсер приводит к верхнему регистру. + +| Документ | Machine-key | Парсер (`src/qg/checks.py`) | Эффект вердикта | +|----------|-------------|-----------------------------|-----------------| +| `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` | `PASS` → дальше; `FAIL` → откат | + +**Информационные доки** — гейтом НЕ парсятся (структура ничего не блокирует): +`00-business-request.md` (вход), `08-data-requirements.md`, `10-tech-risks.md`, +`16-post-deploy-log.md` (несёт `post_deploy_status:`, но это телеметрия петли уроков ORCH-8 / +наблюдаемость, не гейт). + +--- + +## 4. Конвенция ADR-naming + +### Per-work-item ADR (основное) + +- **Путь:** `docs/work-items//06-adr/` +- **Имя файла:** `ADR-NNN-.md` + - `NNN` — 3-значный, начинается с `001`; инкремент при нескольких ADR в одной задаче + (`ADR-001-…`, `ADR-002-…`). + - `` — kebab-case (нижний регистр, слова через дефис), отражает суть решения. +- **Стадия:** пишет **architect** на стадии `architecture`; гейтится `check_architecture_done` + (наличие каталога `06-adr/` ≥ 1 файла). + +### Сквозной (cross-cutting) ADR + +Решения, затрагивающие несколько компонентов/ролей или поведение всего конвейера, **дублируются** +в глобальный реестр: + +- **Путь:** `docs/architecture/adr/` +- **Имя файла:** `adr-NNNN-.md` (4-значная сквозная нумерация, последовательная по + всему репозиторию; на момент ORCH-075 реестр доходит до `adr-0019`). + +### Примеры из репозитория (реальные, проверенные) + +- `docs/work-items/ORCH-088/06-adr/ADR-001-serial-gate.md` +- `docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md` +- `docs/work-items/ORCH-071/06-adr/ADR-001-merge-verify-gate.md` +- Сквозные: `docs/architecture/adr/adr-0017-serial-gate.md`, + `docs/architecture/adr/adr-0018-auto-label-gates.md`. + +--- + +## 5. Как пользоваться шаблонами + +1. Скопируй нужный скелет из [`docs/_templates/`](../_templates/) в + `docs/work-items//` под канонным именем (для ADR — `06-adr/ADR-001-.md`). +2. Заполни секции; **не удаляй** machine-key frontmatter у machine-verdict доков и **не меняй имя + ключа** (регистр чувствителен — иначе гейт упадёт ложно). +3. Сверяйся с манифестом (§2–§3): какой агент, на какой стадии, какой гейт читает документ. + +> Стандарт **описательный** (слой 1). Машинная проверка соответствия шаблонам/frontmatter — +> отдельная задача ORCH-52c; до неё соблюдение — на ответственности агента и reviewer'а +> (правило CLAUDE.md «обновлена ли документация»). diff --git a/docs/_templates/00-business-request.md b/docs/_templates/00-business-request.md new file mode 100644 index 0000000..dc6c00b --- /dev/null +++ b/docs/_templates/00-business-request.md @@ -0,0 +1,8 @@ +# Business Request: <краткий заголовок задачи> + +Work Item ID: ORCH-NNN + +## Description + +<Что хочет заказчик/Владелец своими словами: проблема, желаемый результат, контекст. +Допускается `TBD` на входе — analyst уточняет на стадии `analysis` и формализует в 01-brd.md.> diff --git a/docs/_templates/01-brd.md b/docs/_templates/01-brd.md new file mode 100644 index 0000000..f3f9ea9 --- /dev/null +++ b/docs/_templates/01-brd.md @@ -0,0 +1,34 @@ +# 01 — BRD (бизнес-требования): ORCH-NNN — <название> + +Work Item: **ORCH-NNN** · Repo: **** · Стадия: analysis + +## 1. Бизнес-контекст и проблема +<Зачем задача, какую боль/риск закрывает. Установленные факты — не изобретать.> + +## 2. Объём (scope) + +### В объёме +- <что делаем> + +### Вне объёма +- <что явно НЕ делаем — чтобы исключить расползание> + +## 3. Заинтересованные стороны +<Кто заказчик, кого затрагивает, кто принимает результат.> + +## 4. Бизнес-требования (BR) +- **BR-1** — <требование, проверяемое> +- **BR-2** — … + +## 5. Нефункциональные требования (NFR) +- **NFR-1** — <надёжность / совместимость / обратимость / безопасность> +- **NFR-2** — … + +## 6. Допущения и ограничения +<Допущения, на которых стоит решение; внешние ограничения.> + +## 7. Критерии успеха +<Резюме; детальные PASS/FAIL — в 03-acceptance-criteria.md.> + +## 8. Риски +<Краткий перечень; детали — 10-tech-risks.md (заполняет архитектор).> diff --git a/docs/_templates/02-trz.md b/docs/_templates/02-trz.md new file mode 100644 index 0000000..973d842 --- /dev/null +++ b/docs/_templates/02-trz.md @@ -0,0 +1,30 @@ +# 02 — ТЗ (TRZ): ORCH-NNN — <название> + +Work Item: **ORCH-NNN** · Repo: **** · Стадия: analysis + +> ТЗ описывает **конкретные изменения к реализации**, выведенные из BRD и фактического кода. +> Архитектурное обоснование/решения — задача архитектора (06-adr). + +## 1. Сводка изменения +<Что меняется, в одном-двух абзацах.> + +## 2. Задействованные модули / пути +| Путь | Действие | +|------|----------| +| `src/.py` | изменить / создать | + +## 3. Функциональные требования +### FR-1 — <название> +<Поведение, контракт, инварианты. Привязать к BR.> + +## 4. Изменения API +<Новые/изменённые эндпоинты; либо «Нет.».> + +## 5. Изменения схемы БД +<Таблицы/миграции/индексы; либо «Нет.».> + +## 6. Требования к новым/изменённым QG checks +<Изменения `QG_CHECKS` / `check_*`; либо «Нет.».> + +## 7. Совместимость / регресс +<Обратная совместимость, kill-switch, область раската, обратимость.> diff --git a/docs/_templates/03-acceptance-criteria.md b/docs/_templates/03-acceptance-criteria.md new file mode 100644 index 0000000..c576b25 --- /dev/null +++ b/docs/_templates/03-acceptance-criteria.md @@ -0,0 +1,31 @@ +# 03 — Критерии приёмки (Acceptance Criteria): ORCH-NNN — <название> + +Work Item: **ORCH-NNN** · Repo: **** · Стадия: analysis + +Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** +(что считается провалом). Любой машинный/ручной reviewer проверяет их буквально по файлам +репозитория. + +--- + +## AC-1 — <краткий заголовок> + +**Условие:** <проверяемое условие> +- **PASS:** <что должно быть истинно> +- **FAIL:** <что считается провалом> + +--- + +## AC-2 — <краткий заголовок> + +**Условие:** <…> +- **PASS:** <…> +- **FAIL:** <…> + +--- + +## Сводная матрица AC ↔ FR/BR +| AC | Покрывает | +|----|-----------| +| AC-1 | BR-1 / FR-1 | +| AC-2 | BR-2 / FR-2 | diff --git a/docs/_templates/04-test-plan.yaml b/docs/_templates/04-test-plan.yaml new file mode 100644 index 0000000..80e9dfb --- /dev/null +++ b/docs/_templates/04-test-plan.yaml @@ -0,0 +1,20 @@ +work_item: ORCH-NNN +title: "<краткое название тест-плана>" +framework: pytest +scope: "<что покрывается тестами; что вне покрытия>" +notes: > + <Свободные заметки: окружение, особенности, что считается регрессом. + Полный регресс tests/ должен оставаться зелёным.> + +tests: + - id: TC-01 + type: unit # unit | integration + description: "<что проверяет тест>" + module: tests/test_.py + expected: PASS + + - id: TC-02 + type: integration + description: "<…>" + module: tests/test_.py + expected: PASS diff --git a/docs/_templates/06-adr-ADR-NNN-slug.md b/docs/_templates/06-adr-ADR-NNN-slug.md new file mode 100644 index 0000000..a4ab810 --- /dev/null +++ b/docs/_templates/06-adr-ADR-NNN-slug.md @@ -0,0 +1,43 @@ +# ADR-NNN: <Заголовок решения> + +> **Шаблон ADR.** Скопируй в `docs/work-items//06-adr/ADR-NNN-.md`. +> `NNN` начинается с `001`, инкремент при нескольких ADR в задаче. `` — нижний +> регистр, слова через дефис. Сквозное (cross-cutting) решение дополнительно дублируй в +> `docs/architecture/adr/adr-NNNN-.md` (4-значная глобальная нумерация). +> См. `docs/_standards/PIPELINE_DOCS.md` §4. + +Work Item: **ORCH-NNN** — <короткое описание> +Стадия: **architecture** +Сквозная регистрация: **`docs/architecture/adr/adr-NNNN-.md`** (если решение +кросс-каттинговое; иначе — «N/A, локальное решение задачи»). + +## Статус +Proposed + +## Контекст +<Какую проблему решаем; факты, сверенные с кодом (`src/…`); почему «как есть» не годится.> + +## Решение + +### Сводка +<Суть выбранного решения в одном-двух абзацах.> + +### D1 — <название аспекта решения> +<Конкретное решение по аспекту, инварианты, привязка к FR/AC.> + +### D2 — <название аспекта решения> +<…> + +## Альтернативы +- **<альтернатива>** — отвергнуто: <почему>. + +## Последствия +- **+** <положительный эффект> +- **−** <издержка / приятый компромисс + митигейшн> +- **Откат:** <как полностью откатить изменение> + +## Ссылки +- BRD: `docs/work-items/ORCH-NNN/01-brd.md` +- TRZ: `docs/work-items/ORCH-NNN/02-trz.md` +- Acceptance: `docs/work-items/ORCH-NNN/03-acceptance-criteria.md` +- Сверено по коду: `src/…` diff --git a/docs/_templates/07-infra-requirements.md b/docs/_templates/07-infra-requirements.md new file mode 100644 index 0000000..9967cb8 --- /dev/null +++ b/docs/_templates/07-infra-requirements.md @@ -0,0 +1,19 @@ +# 07 — Инфра-требования: ORCH-NNN — <название> + +Work Item: **ORCH-NNN** · Repo: **** · Стадия: architecture + +> When-applicable. Если инфраструктура не затрагивается — оставить явные `N/A` по пунктам +> (файл создаётся для аудитопригодности, а не из-за изменения топологии). + +## I-1. Топология / окружения +<Контейнеры, порты, сеть, тома, хост; либо `N/A`.> + +## I-2. Переменные окружения / секреты +<Новые env-переменные, изменения `.env` / `.env.example`, секреты; либо `N/A`.> + +## I-3. Деплой / рестарт +<Требуется ли рестарт прод-контейнера; self-hosting инвариант (не ронять прод вне staging); +либо `N/A`.> + +## I-4. CI/CD +<Изменения `.gitea/workflows/`, новые тестовые шаги; либо «без изменений».> diff --git a/docs/_templates/08-data-requirements.md b/docs/_templates/08-data-requirements.md new file mode 100644 index 0000000..c4e68af --- /dev/null +++ b/docs/_templates/08-data-requirements.md @@ -0,0 +1,15 @@ +# 08 — Требования к данным: ORCH-NNN — <название> + +Work Item: **ORCH-NNN** · Repo: **** · Стадия: architecture + +> When-applicable / информационный (гейтом не парсится). Если данные/схема не затрагиваются — +> оставить явные `N/A`. + +## Изменения схемы БД +<Новые/изменённые таблицы, индексы, миграции (`init_db`); либо `N/A`.> + +## Новые/изменённые сущности +<Поля, колонки, инварианты данных; либо «Нет.».> + +## Совместимость данных / миграции +<Аддитивность, идемпотентность миграций, restart-safe, влияние на общую прод-БД; либо `N/A`.> diff --git a/docs/_templates/10-tech-risks.md b/docs/_templates/10-tech-risks.md new file mode 100644 index 0000000..1c066d7 --- /dev/null +++ b/docs/_templates/10-tech-risks.md @@ -0,0 +1,16 @@ +# 10 — Технические риски: ORCH-NNN — <название> + +Work Item: **ORCH-NNN** · Repo: **** · Стадия: architecture + +> Информационный (гейтом не парсится). Перечисляет риски реализации и их митигейшн. + +## Реестр рисков + +| ID | Риск | Вер. | Влия. | Митигейшн | +|----|------|------|-------|-----------| +| TR-1 | <описание риска> | Низ./Сред./Выс. | Низ./Сред./Выс. | <как снижаем> | +| TR-2 | <…> | | | | + +## Сводный вывод +<Доминирующий класс рисков; нужна ли эскалация `arch:major-change` / возврат в анализ; +итоговая оценка остаточного риска для прод-конвейера (self-hosting).> diff --git a/docs/_templates/12-review.md b/docs/_templates/12-review.md new file mode 100644 index 0000000..4063608 --- /dev/null +++ b/docs/_templates/12-review.md @@ -0,0 +1,31 @@ +--- +type: review +work_item_id: ORCH-NNN +verdict: APPROVED # APPROVED | REQUEST_CHANGES (machine-key — читает check_reviewer_verdict) +version: 1 +--- + +# Review ORCH-NNN + +> Машинный вердикт читается ТОЛЬКО из `verdict:` во frontmatter (никогда из прозы). +> `APPROVED` → дальше по конвейеру; `REQUEST_CHANGES` → откат на `development`. + +## Summary +<Краткая оценка: реализовано ли по ТЗ/ADR, покрытие тестами, обновлена ли документация.> + +## Оси проверки +<Корректность, соответствие ADR/инвариантам, тесты, документация, совместимость/регресс.> + +## Findings + +### P0 — Blocker +- (нет) + +### P1 — Must fix +- (нет) + +### P2 — Should fix +- (нет) + +## Документация +<Обновлена ли документация (README/CLAUDE/CHANGELOG/архитектура) в том же PR. Нет → REQUEST_CHANGES.> diff --git a/docs/_templates/13-test-report.md b/docs/_templates/13-test-report.md new file mode 100644 index 0000000..fd853e9 --- /dev/null +++ b/docs/_templates/13-test-report.md @@ -0,0 +1,33 @@ +--- +type: test-report +work_item_id: ORCH-NNN +result: PASS # PASS | FAIL | BLOCKED (machine-key — читает _parse_tests_verdict) +--- + +# Test Report — ORCH-NNN + +> Машинный вердикт читается ТОЛЬКО из frontmatter. Канонический ключ — `result:`; равнорангово +> допускаются `verdict:` / `status:` (ORCH-047). Любой негативный токен (`FAIL`/`BLOCKED`) — +> авторитетен. + +## Окружение +- Python: <версия> +- pytest: <версия> +- Дата: YYYY-MM-DD +- Worktree: `feature/ORCH-NNN-` + +## Результаты + +### Полный регресс +<`pytest tests/ -q` — итог (N passed); прод-контейнер не трогается.> + +### Профильные сюиты +<Целевые тесты задачи.> + +### Сопоставление с тест-планом +| TC ID | Описание | Тест-функция | Результат | +|-------|----------|--------------|-----------| +| TC-01 | <…> | test_… | PASS | + +### Сопоставление с критериями приёмки + diff --git a/docs/_templates/14-deploy-log.md b/docs/_templates/14-deploy-log.md new file mode 100644 index 0000000..2d2d98e --- /dev/null +++ b/docs/_templates/14-deploy-log.md @@ -0,0 +1,14 @@ +--- +deploy_status: SUCCESS # SUCCESS | FAILED (machine-key — читает _parse_deploy_status) +work_item: ORCH-NNN +hook_exit_code: 0 +deployed_by: deploy-finalizer +--- + +# Deploy log — ORCH-NNN + +> Машинный вердикт читается ТОЛЬКО из `deploy_status:` во frontmatter. +> `SUCCESS` → `done`; `FAILED` → откат на `development` (БАГ-8). + +<Краткое описание деплоя: что выкачено, exit-code хука, кто/что зафиксировало вердикт +(детерминированный finalizer Фаза C, не LLM, для self-hosting).> diff --git a/docs/_templates/15-staging-log.md b/docs/_templates/15-staging-log.md new file mode 100644 index 0000000..34b197f --- /dev/null +++ b/docs/_templates/15-staging-log.md @@ -0,0 +1,20 @@ +--- +staging_status: SUCCESS # SUCCESS | FAILED (machine-key — читает _parse_staging_status) +timestamp: YYYY-MM-DDTHH:MM:SSZ +base_url: http://localhost:8501 +--- + +# Staging Gate Log + +> Машинный вердикт читается ТОЛЬКО из `staging_status:` во frontmatter. Реален для self-hosting +> (`orchestrator`); для прочих репо гейт — N/A (ORCH-35). `SUCCESS` → дальше; `FAILED` → откат. + +Staging test suite — итог (например: «All REAL pipeline checks passed»). Запуск канонически +внутри контейнера `orchestrator-staging` (8501). + +## Results +- **Block A (SMOKE)**: <…> +- **Block B (ACCESS)**: <…> +- **Block C (E2E)**: <…> + +REAL failed: . diff --git a/docs/_templates/16-post-deploy-log.md b/docs/_templates/16-post-deploy-log.md new file mode 100644 index 0000000..749f5af --- /dev/null +++ b/docs/_templates/16-post-deploy-log.md @@ -0,0 +1,21 @@ +--- +post_deploy_status: HEALTHY # HEALTHY | DEGRADED (информационный, гейтом НЕ парсится — телеметрия ORCH-021) +action_taken: NONE # NONE | ALERT_ONLY | ROLLBACK_OK | ROLLBACK_FAILED +work_item: ORCH-NNN +window_s: 900 +checks_total: 0 +checks_failed: 0 +--- + +# Post-deploy log — ORCH-NNN + +> Пост-`done` наблюдение прода (ORCH-021). НЕ ребро `STAGE_TRANSITIONS`, гейтом не парсится — +> frontmatter машиночитаем для петли уроков ORCH-8 / наблюдаемости. + +Окно наблюдения: s; опросов всего: , с провалом: . + +## Серия наблюдений +<Краткая серия сигналов health / доли 5xx; классификация HEALTHY/DEGRADED.> + +## Решение +<Реакция: для self-hosting всегда `ALERT_ONLY` (ручной approve, тик не откатывает прод).> diff --git a/docs/_templates/17-security-report.md b/docs/_templates/17-security-report.md new file mode 100644 index 0000000..61dcdce --- /dev/null +++ b/docs/_templates/17-security-report.md @@ -0,0 +1,26 @@ +--- +security_status: PASS # PASS | FAIL (machine-key — читает check_security_gate) +work_item: ORCH-NNN +secrets_found: 0 +deps_blocking: 0 +deps_warning: 0 +deps_audit_degraded: false +--- + +# Security Report — ORCH-NNN + +> Детерминированный security-гейт (ORCH-022) — под-гейт ребра `deploy-staging→deploy` (врезка в +> `advance_stage`, не строка `STAGE_TRANSITIONS`). Машинный вердикт читается ТОЛЬКО из +> `security_status:`. `PASS` → дальше; `FAIL` → откат. + +## Verdict + + +## Secrets + + +## Dependencies (blocking) + + +## Dependencies (warning) +<Не блокирующие предупреждения зависимостей.> diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 3d591a0..6153351 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -41,6 +41,18 @@ created → analysis → architecture → development → review → testing → **Канон гейтов:** машинные вердикты читаются ТОЛЬКО из YAML-frontmatter, никогда из прозы. Лог-файлы мержатся в `origin/main` отдельным PR; гейт читает из `origin/main`. +### Стандарт документов конвейера (ORCH-075, ORCH-52b) +Структура номерных документов work item (`00-business-request.md` … `17-security-report.md`), +карта «стадия → агент → документ → категория → гейт/механизм → frontmatter machine-key» и +конвенция ADR-naming зафиксированы как golden source в +[`docs/_standards/PIPELINE_DOCS.md`](../_standards/PIPELINE_DOCS.md); копируемые скелеты — в +[`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`. + ### Модель и эффорт по ролям (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_`/`ORCH_AGENT_EFFORT_` > `*_default` > CLI-дефолт (без флага)**. **Эффорт (ORCH-081):** ниже `*_default` добавлен непустой **per-role floor** — class-default поля `agent_effort_` из `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`. diff --git a/tests/test_orch_52b_docs_standard.py b/tests/test_orch_52b_docs_standard.py new file mode 100644 index 0000000..2e62ef5 --- /dev/null +++ b/tests/test_orch_52b_docs_standard.py @@ -0,0 +1,214 @@ +"""ORCH-075 (ORCH-52b) — структурные проверки стандарта документов конвейера. + +Docs-only задача: проверяется НАЛИЧИЕ и СТРУКТУРА новых файлов-стандартов/шаблонов +(`docs/_standards/PIPELINE_DOCS.md`, `docs/_templates/*`) и обновление точек-ссылок +(`CLAUDE.md`, `docs/architecture/README.md`, `CHANGELOG.md`). Тесты НЕ меняют +`QG_CHECKS`/`STAGE_TRANSITIONS` и не вводят новый гейт (это ORCH-52c). + +Покрытие тест-плана 04-test-plan.yaml: TC-01…TC-20 (TC-21 — полный регресс `pytest tests/`). +""" + +from pathlib import Path + +import yaml + +REPO_ROOT = Path(__file__).resolve().parents[1] +STANDARDS = REPO_ROOT / "docs" / "_standards" +TEMPLATES = REPO_ROOT / "docs" / "_templates" +MANIFEST = STANDARDS / "PIPELINE_DOCS.md" + +# Все номерные доки реального набора (TRZ §FR-1, AC-1). +NUMBERED_DOCS = ["00", "01", "02", "03", "04", "06", "07", "08", "10", + "12", "13", "14", "15", "16", "17"] + +# Шаблоны required/when-applicable доков (TRZ §2, AC-2). +TEMPLATE_FILES = [ + "00-business-request.md", + "01-brd.md", + "02-trz.md", + "03-acceptance-criteria.md", + "04-test-plan.yaml", + "06-adr-ADR-NNN-slug.md", + "07-infra-requirements.md", + "08-data-requirements.md", + "10-tech-risks.md", + "12-review.md", + "13-test-report.md", + "14-deploy-log.md", + "15-staging-log.md", + "16-post-deploy-log.md", + "17-security-report.md", +] + + +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +# --- TC-01 ----------------------------------------------------------------- +def test_tc01_manifest_exists_and_nonempty(): + """docs/_standards/PIPELINE_DOCS.md существует и непустой.""" + assert MANIFEST.is_file(), f"манифест не найден: {MANIFEST}" + assert len(_read(MANIFEST).strip()) > 0, "манифест пустой" + + +# --- TC-02 ----------------------------------------------------------------- +def test_tc02_manifest_mentions_all_numbered_docs(): + """Манифест упоминает все номерные доки набора.""" + content = _read(MANIFEST) + missing = [] + for num in NUMBERED_DOCS: + # Документ упоминается как `NN-...` или `NN-adr` — ищем по префиксу `NN-`. + if f"{num}-" not in content: + missing.append(num) + assert not missing, f"в манифесте не упомянуты доки: {missing}" + + +# --- TC-03 ----------------------------------------------------------------- +def test_tc03_manifest_lists_owner_agents(): + """Манифест указывает владельцев-агентов конвейера (TC-03: analyst/architect/ + reviewer/tester/deployer/система). `developer` не владеет номерным доком — пишет код+PR.""" + content = _read(MANIFEST).lower() + for agent in ["analyst", "architect", "reviewer", "tester", "deployer"]: + assert agent in content, f"в манифесте нет владельца-агента: {agent}" + assert "систем" in content, "в манифесте нет системного владельца (Plane webhook)" + + +# --- TC-04 ----------------------------------------------------------------- +def test_tc04_manifest_has_categories(): + """Манифест содержит категории required / when-applicable / optional.""" + content = _read(MANIFEST) + for category in ["required", "when-applicable", "optional"]: + assert category in content, f"в манифесте нет категории: {category}" + + +# --- TC-05 ----------------------------------------------------------------- +def test_tc05_templates_dir_has_all_templates(): + """docs/_templates/ существует и содержит шаблоны для всех required/when-applicable доков.""" + assert TEMPLATES.is_dir(), f"каталог шаблонов не найден: {TEMPLATES}" + missing = [name for name in TEMPLATE_FILES if not (TEMPLATES / name).is_file()] + assert not missing, f"отсутствуют шаблоны: {missing}" + + +# --- TC-06..TC-11 — frontmatter machine-keys ------------------------------- +def _assert_frontmatter_key(template_name: str, key: str): + content = _read(TEMPLATES / template_name) + assert content.lstrip().startswith("---"), f"{template_name}: нет YAML-frontmatter" + head = content.split("---", 2) + assert len(head) >= 3, f"{template_name}: frontmatter не закрыт" + assert f"{key}:" in head[1], f"{template_name}: нет machine-key `{key}:` во frontmatter" + + +def test_tc06_review_template_has_verdict(): + """Шаблон 12-review содержит frontmatter-ключ verdict:.""" + _assert_frontmatter_key("12-review.md", "verdict") + + +def test_tc07_test_report_template_has_result(): + """Шаблон 13-test-report содержит frontmatter-ключ result:.""" + _assert_frontmatter_key("13-test-report.md", "result") + + +def test_tc08_deploy_log_template_has_deploy_status(): + """Шаблон 14-deploy-log содержит frontmatter-ключ deploy_status:.""" + _assert_frontmatter_key("14-deploy-log.md", "deploy_status") + + +def test_tc09_staging_log_template_has_staging_status(): + """Шаблон 15-staging-log содержит frontmatter-ключ staging_status:.""" + _assert_frontmatter_key("15-staging-log.md", "staging_status") + + +def test_tc10_security_report_template_has_security_status(): + """Шаблон 17-security-report содержит frontmatter-ключ security_status:.""" + _assert_frontmatter_key("17-security-report.md", "security_status") + + +def test_tc11_post_deploy_template_has_post_deploy_status(): + """Шаблон 16-post-deploy-log содержит frontmatter-ключ post_deploy_status:.""" + _assert_frontmatter_key("16-post-deploy-log.md", "post_deploy_status") + + +# --- TC-12 ----------------------------------------------------------------- +def test_tc12_brd_template_has_mandatory_sections(): + """Шаблон 01-brd содержит обязательные секции (TRZ §FR-2.1).""" + content = _read(TEMPLATES / "01-brd.md") + for section in ["Бизнес-контекст", "Объём", "Бизнес-требования", "Нефункциональные требования"]: + assert section in content, f"01-brd: нет секции `{section}`" + + +# --- TC-13 ----------------------------------------------------------------- +def test_tc13_trz_template_has_mandatory_sections(): + """Шаблон 02-trz содержит обязательные секции (TRZ §FR-2.1).""" + content = _read(TEMPLATES / "02-trz.md") + for section in ["Задействованные модули", "Изменения API", "Изменения схемы БД", + "QG checks"]: + assert section in content, f"02-trz: нет секции `{section}`" + + +# --- TC-14 ----------------------------------------------------------------- +def test_tc14_ac_template_has_pass_fail_block(): + """Шаблон 03-acceptance-criteria содержит блок AC-N с метками PASS и FAIL.""" + content = _read(TEMPLATES / "03-acceptance-criteria.md") + assert "AC-1" in content, "03-ac: нет блока AC-N" + assert "**PASS:**" in content, "03-ac: нет метки PASS" + assert "**FAIL:**" in content, "03-ac: нет метки FAIL" + + +# --- TC-15 ----------------------------------------------------------------- +def test_tc15_test_plan_template_is_valid_yaml(): + """Шаблон 04-test-plan.yaml — валидный YAML с work_item и списком tests.""" + data = yaml.safe_load(_read(TEMPLATES / "04-test-plan.yaml")) + assert isinstance(data, dict), "04-test-plan: корень не словарь" + assert "work_item" in data, "04-test-plan: нет ключа work_item" + assert isinstance(data.get("tests"), list) and data["tests"], "04-test-plan: tests не список" + first = data["tests"][0] + for key in ["id", "type", "description", "module", "expected"]: + assert key in first, f"04-test-plan: в элементе tests нет ключа `{key}`" + + +# --- TC-16 ----------------------------------------------------------------- +def test_tc16_adr_naming_section_present(): + """Раздел ADR-naming фиксирует формат ADR-NNN-.md с нумерацией с 001 и kebab-slug.""" + content = _read(MANIFEST) + assert "ADR-NNN-" in content, "нет формата ADR-NNN-.md" + assert "06-adr" in content, "нет пути 06-adr/" + assert "001" in content, "не зафиксирована нумерация с 001" + assert "kebab" in content.lower(), "нет правила kebab-case для slug" + + +# --- TC-17 ----------------------------------------------------------------- +def test_tc17_adr_naming_matches_real_repo(): + """ADR-naming совпадает с реальными ADR в репо (пример из манифеста существует).""" + # Манифест приводит ORCH-088 как пример; реальный файл должен существовать. + adr_dir = REPO_ROOT / "docs" / "work-items" / "ORCH-088" / "06-adr" + real = list(adr_dir.glob("ADR-001-*.md")) + assert real, f"реальный ADR-001-*.md не найден в {adr_dir}" + # И глобальный реестр следует 4-значной конвенции. + global_adr = REPO_ROOT / "docs" / "architecture" / "adr" + assert list(global_adr.glob("adr-0019-*.md")), "глобальный adr-0019-*.md не найден" + + +# --- TC-18 ----------------------------------------------------------------- +def test_tc18_claude_md_links_standard(): + """CLAUDE.md содержит ссылку на docs/_standards/PIPELINE_DOCS.md.""" + content = _read(REPO_ROOT / "CLAUDE.md") + assert "docs/_standards/PIPELINE_DOCS.md" in content + + +# --- TC-19 ----------------------------------------------------------------- +def test_tc19_architecture_readme_links_standard(): + """docs/architecture/README.md содержит ссылку на стандарт документов.""" + content = _read(REPO_ROOT / "docs" / "architecture" / "README.md") + assert "PIPELINE_DOCS.md" in content + + +# --- TC-20 ----------------------------------------------------------------- +def test_tc20_changelog_mentions_task(): + """CHANGELOG.md содержит запись об ORCH-52b/ORCH-075 в разделе Unreleased.""" + content = _read(REPO_ROOT / "CHANGELOG.md") + unreleased = content.split("## [Unreleased]", 1) + assert len(unreleased) == 2, "нет раздела ## [Unreleased]" + # Берём срез до следующего релизного заголовка, если он есть. + body = unreleased[1].split("\n## ", 1)[0] + assert "ORCH-075" in body or "ORCH-52b" in body, "в Unreleased нет записи ORCH-075/ORCH-52b"