From 319b23b4fcae4dce028b662a93271c98adcd1ae5 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Mon, 8 Jun 2026 15:52:20 +0300 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=380 --- docs/work-items/ORCH-073/01-brd.md | 98 +++++++++++++ docs/work-items/ORCH-073/02-trz.md | 129 ++++++++++++++++++ .../ORCH-073/03-acceptance-criteria.md | 77 +++++++++++ docs/work-items/ORCH-073/04-test-plan.yaml | 117 ++++++++++++++++ 4 files changed, 421 insertions(+) create mode 100644 docs/work-items/ORCH-073/01-brd.md create mode 100644 docs/work-items/ORCH-073/02-trz.md create mode 100644 docs/work-items/ORCH-073/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-073/04-test-plan.yaml diff --git a/docs/work-items/ORCH-073/01-brd.md b/docs/work-items/ORCH-073/01-brd.md new file mode 100644 index 0000000..9079559 --- /dev/null +++ b/docs/work-items/ORCH-073/01-brd.md @@ -0,0 +1,98 @@ +# 01 — BRD: ORCH-073 — CRIT: эрозия main (код ORCH-067/069 затёрт ребейзами, не доехал) + +- **Work Item:** ORCH-073 +- **Тип:** BUG CRITICAL — целостность `main`, накопительный регресс/эрозия +- **Репозиторий:** orchestrator (self-hosting) +- **Ветка:** `feature/ORCH-073-crit-main-orch-067-069` +- **Связь:** усиливает/чинит ORCH-071 (merge-verify); НЕ покрыт ORCH-071. + +## 1. Бизнес-проблема + +Код успешно «задеплоенных» и переведённых в `done` задач **ORCH-067** (tracker bump, +Plane-статусы, кликабельные ссылки `plane_issue_link`) и **ORCH-069** (`qg0_title_max`) +**физически отсутствовал в `origin/main`**, хотя обе прошли весь конвейер, Confirm Deploy, +merge-verify `CONFIRMED` и стали `done`. В `main` попадали только их **docs-коммиты** +(staging-log / verdict через отдельные авто docs-PR), но НЕ код feature-веток. + +Внешнее проявление (нашёл Слава, 08.06): «ссылок на задачу в Plane нет», карточка Telegram +показывает сырой номер задачи вместо кликабельной ссылки — потому что код ссылок есть в ветке +ORCH-067, но не в `main`. + +**Накопительный характер:** каждая новая задача срезает ветку от УСТАРЕВШЕГО `main` и при merge +тихо (без конфликт-маркеров) затирает код предшественника. Уже потеряны ORCH-067 и ORCH-069; +без системного фикса теряется код каждой следующей задачи с правкой `CHANGELOG.md`. + +## 2. Подтверждённый root cause (git-аудит 08.06, не гипотеза) + +1. **`verify_merged_to_main` подтверждает merge по ложному признаку.** + `src/merge_gate.py::verify_merged_to_main` возвращает `True`, если выполнено **ЛИБО** + `pr_already_merged(repo, branch)`, **ЛИБО** `git merge-base --is-ancestor origin/main`. + Первая ветка (`pr_already_merged`) и есть дыра. +2. **`pr_already_merged` засчитывает ЛЮБОЙ merged PR ветки.** + `src/merge_gate.py::pr_already_merged` делает `GET /pulls?state=all&head=` и + возвращает `True`, если **хоть один** PR `merged==True`. У одной ветки несколько PR + (code-PR + авто docs-PR со staging/deploy-логами). Сливается docs-PR → функция говорит + «already-merged» → `verify_merged_to_main`=`True` → merge-verify `CONFIRMED` → `done`, + хотя code-PR НЕ слит. **Ложно-зелёный.** +3. **CHANGELOG.md-ребейзы — вторичный усилитель.** + Merge-gate `auto_rebase_onto_main` при конфликте `CHANGELOG.md` откатывает `deploy-staging → + development`; повторный ребейз ветки от старого `main` несёт устаревшие версии файлов + (`notifications.py`/`config.py`/`webhooks/plane.py`), которые при merge тихо затирают + соседний код (фантом-эффект, как в ORCH-071, без конфликт-маркеров). + +> Уточнение для архитектора: в ТЗ упомянута «инвертированная проверка `merge-base --is-ancestor +> origin/main HEAD` (merge_gate.py ~76)» — это `branch_is_behind_main` (детектор «ветка +> свежая»), он корректен для своей цели. Фактический дефект merge-verify — это OR-ветка +> `pr_already_merged` в `verify_merged_to_main` (строка ~649), которая засчитывает docs-PR. + +## 3. Состояние на момент анализа (G1) + +Аудит `origin/main` показал, что **восстановительный PR #76** (`restore(main): re-merge +ORCH-067 + ORCH-069 (ORCH-073)`) уже вернул код в `main`: +- `plane_issue_link` присутствует (`src/notifications.py`), `qg0_title_max` присутствует + (`src/config.py`, `src/webhooks/plane.py`), `verify_merged_to_main` присутствует. + +Таким образом **G1 (восстановление кода) фактически выполнено** ручным restore-PR. Задача +ORCH-073 должна **подтвердить и зафиксировать** это в критериях приёмки (AC-1) и сосредоточиться +на **системном фиксе навсегда** (G2–G5 / FR-1…FR-5), иначе регресс повторится. + +## 4. Цели (Goals) + +- **G1.** КОД ORCH-067 и ORCH-069 присутствует в `origin/main` одновременно с ORCH-071 + (подтвердить restore-PR #76, зафиксировать маркеры > 0). Pytest зелёный. Прод задеплоен. +- **G2 (FR-2/FR-3).** `merge`/`pr_already_merged` различают **code-PR** и **docs-PR** — merge + засчитывается только за PR с кодом ветки (`base==main`, `head==`). +- **G3 (FR-1, ядро).** `verify_merged_to_main` подтверждает merge **ТОЛЬКО** по факту «deployed + SHA — предок `origin/main`». PR-флаги вспомогательны, не достаточны. +- **G4 (FR-4).** Защита от CHANGELOG-затирания: `.gitattributes` с `CHANGELOG.md merge=union` + (+ опц. `docs/*.md merge=union` для append-only). +- **G5 (FR-5, регресс-гард навсегда).** После деплоя — sanity-проверка целостности `main`: + deployed SHA в `main` И набор маркеров ранее-merged задач не уменьшился. Откат соседнего кода + → alert «main regressed», задача НЕ `done`. + +## 5. Не-цели (Out of scope) + +- Не менять Plane / схему БД. +- Не отменять self-hosting safety (не ронять прод, merge только через PR-API, без force-push в `main`). +- Не менять ручной гейт `Confirm Deploy`. +- Не менять поведение merge/verify для non-self репозиториев (enduro-trails) — обратная совместимость. + +## 6. Инварианты + +- **INV-1.** never-raise на верификации (alert, не падение). +- **INV-2.** self-hosting safety: прод не падает; merge только PR-API, без force-push в `main`. +- **INV-3.** ручной `Confirm Deploy` сохранён. +- **INV-4.** Идемпотентность: повторный прогон / reaper не делает второй merge; idempotency + опирается на «SHA-в-main», а не на «любой merged PR». +- **INV-5.** Обратная совместимость non-self (enduro): поведение merge/verify без изменений. + +## 7. Заинтересованные стороны + +- **Owner / Слава** — потребитель (видит кликабельные ссылки в карточке; доверие к merge-verify). +- **Все проекты на инстансе** (enduro-trails) — общий `main`/очередь/БД; регресс орка = групповой риск. + +## 8. Срочность + +КРИТИКАЛ. Без FR-1/FR-4/FR-5 каждая новая задача с правкой `CHANGELOG.md` продолжает терять код +предшественников (уже потеряны 067, 069). Ложно-зелёный merge-verify подрывает само ядро +автономности конвейера. diff --git a/docs/work-items/ORCH-073/02-trz.md b/docs/work-items/ORCH-073/02-trz.md new file mode 100644 index 0000000..3caacdb --- /dev/null +++ b/docs/work-items/ORCH-073/02-trz.md @@ -0,0 +1,129 @@ +# 02 — ТЗ: ORCH-073 — системный фикс эрозии main + восстановление кода 067/069 + +> ТЗ описывает ТРЕБУЕМОЕ ПОВЕДЕНИЕ и точки изменения. Выбор конкретного дизайна +> (где именно резать docs-PR от code-PR, формат набора регресс-маркеров) — за архитектором (`06-adr`). +> Запрещено комментировать ТЗ задним числом: если требование не годится — вернуть в Анализ. + +## 1. Задействованные модули `src/` + +| Модуль | Роль в фиксе | FR | +| --- | --- | --- | +| `src/merge_gate.py` | `verify_merged_to_main`, `pr_already_merged`, `merge_pr`, новый регресс-гард | FR-1, FR-2, FR-3, FR-5 | +| `src/stage_engine.py` | `_handle_merge_verify` (под-гейт `deploy → done`) — точка вызова FR-1/FR-5 | FR-1, FR-5 | +| `src/config.py` | (опц.) настройки регресс-гарда: kill-switch + набор маркеров/таймаут | FR-5 | +| `.gitattributes` (корень репо, новый) | `CHANGELOG.md merge=union` (+ опц. `docs/*.md merge=union`) | FR-4 | +| `docs/architecture/README.md` | раздел merge-verify — обновить под новую семантику | AC-8 | +| `CHANGELOG.md` | запись Unreleased | AC-8 | +| `docs/work-items/ORCH-073/06-adr/` | ADR на новую семантику merge-verify + регресс-гард | AC-8 | + +## 2. Требуемые изменения по коду + +### FR-1 (G3, ядро) — `verify_merged_to_main` чинит семантику +**Текущее (баг):** `src/merge_gate.py::verify_merged_to_main(repo, branch, sha)` возвращает `True`, +если `pr_already_merged(...)` **ИЛИ** `git merge-base --is-ancestor origin/main`. +OR-ветка `pr_already_merged` засчитывает docs-PR → ложно-зелёный. + +**Требование:** подтверждение merge — **ТОЛЬКО** прямой факт «deployed commit является предком +`origin/main`»: +- после `git fetch origin main` выполнить `git merge-base --is-ancestor origin/main`; +- `rc==0` → `True` (код в main), иначе → `False`. +- `pr_already_merged` **НЕ может быть единственным/достаточным** условием `True`. Допустимо + оставить PR-флаг только как **вспомогательный** сигнал (idempotency / диагностика), но он НЕ + должен подтверждать merge при отсутствии SHA в main. +- Пустой `sha` → неопределённо → `False` (fail-closed: alert + HOLD), как сейчас. +- never-raise: любая git/HTTP-ошибка → `False` (INV-1). + +### FR-2 (G2) — `pr_already_merged` различает code-PR и docs-PR +**Текущее (баг):** `src/merge_gate.py::pr_already_merged` возвращает `True` за ЛЮБОЙ +`merged==True` PR из `GET /pulls?state=all&head=` — включая авто docs-PR. + +**Требование (на выбор архитектора, предпочтителен вариант «б»):** +- **(а)** засчитывать merged только для PR, реально несущего код ветки: `base.ref==main` + И `head.ref==` (исключить docs/* ветки и docs-only PR); **или** +- **(б, предпочтительно)** понизить роль `pr_already_merged` до **idempotency-guard**: единственный + критерий «merged/done» — SHA-предок-`main` (FR-1); PR-флаги вспомогательны. +- Поведение для non-self репо (enduro) не меняется (INV-5). +- never-raise → `False` (консервативно). + +### FR-3 (G2) — `merge_pr` реально сливает code-ветку +**Требование:** `src/merge_gate.py::merge_pr` мержит ИМЕННО feature-PR с кодом (`base==main`, +`head==`), а не полагается на docs-PR. После merge — обязательная верификация +по FR-1 (SHA в main) как единственный источник истины. Merge только через Gitea PR-merge API, +никогда push/force-push в `main` (INV-2). + +### FR-5 (G3 регресс-гард, защита навсегда) — sanity-проверка целостности main +**Требование:** перед фиксацией `done` (в `_handle_merge_verify`, ПОСЛЕ зелёного +`check_deploy_status`, до `update_task_stage`): +1. Подтвердить FR-1 (deployed SHA — предок `origin/main`). +2. (опц., по дизайну) Проверить, что в `origin/main` присутствует **набор маркеров** ключевых + функций недавно-merged задач (regression marker set) — merge не уменьшил его. +3. При откате соседнего кода / отсутствии маркера → **alert** «main regressed: code of missing» (Telegram + Plane), задача **НЕ `done`** (HOLD), как ветка not-merged в ORCH-071. +- Реакция — **ALERT-only + HOLD**, без авто-отката на `development` (это инфра-дефект, не код-фолт). +- never-raise (INV-1); kill-switch (как `merge_verify_enabled`); условность только для self-hosting + / `merge_verify_repos` (INV-5). +- Набор маркеров — конфигурируемый/декларативный (например, в `src/config.py` или рядом), чтобы + следующие задачи могли его расширять. Точный формат — за архитектором. + +### FR-4 (G2/G4 корень) — `.gitattributes` с `merge=union` +**Требование:** в корне репо завести `.gitattributes`: +``` +CHANGELOG.md merge=union +# опционально для append-only документов: +# docs/**/*.md merge=union # ВНИМАНИЕ: union НЕ годится для файлов, где правки + # переписывают строки — применять только к append-only +``` +- `merge=union` встроен в git (драйвер по умолчанию), доп. конфиг хоста не требуется — но + проверить, что атрибут реально применяется в worktree агентов (`git check-attr merge CHANGELOG.md`). +- Эффект: при `auto_rebase_onto_main` правки `## [Unreleased]` авто-сливаются (обе записи + сохраняются) без конфликта → ветка не откатывается в `development` и не затирает соседний код. + +## 3. Изменения API + +- **Внешних HTTP API оркестратора (`src/main.py` endpoints) НЕ менять.** +- Внутренние сигнатуры: + - `verify_merged_to_main(repo, branch, sha) -> bool` — семантика меняется, сигнатура сохраняется. + - `pr_already_merged(repo, branch) -> bool` — семантика/назначение уточняется. + - `merge_pr(repo, branch) -> tuple[bool, str]` — поведение уточняется (фильтр code-PR). + - (опц.) новая функция регресс-гарда в `merge_gate.py` — `tuple[bool, str]`/`bool`, never-raise. +- `GET /queue` `merge_verify_status()` — допустимо дополнить счётчиком регресс-алертов (read-only, + не источник истины). +- Внешние вызовы Gitea — те же эндпоинты (`/pulls`, `/pulls/{index}/merge`). + +## 4. Изменения схемы БД + +- **НЕТ.** Схема БД (`src/db.py`) не трогается (Не-цель). Регресс-гард опирается на git/`origin/main`, + не на новые таблицы. + +## 5. Требования к новым/изменённым QG checks + +- **Новых зарегистрированных QG-checks не вводить.** Логика остаётся **под-гейтом** в + `advance_stage` (`_handle_merge_verify`), как ORCH-071 — не новый элемент реестра `QG_CHECKS`. +- Реестр `QG_CHECKS`, `check_deploy_status`, `_parse_deploy_status`, merge-gate + (`check_branch_mergeable`), image-freshness — **без изменений**. + +## 6. Конфигурация (`src/config.py` / `.env.example`) + +- Существующие `merge_verify_enabled` (kill-switch, дефолт `true`), `merge_verify_repos` (пусто → + только self-hosting), `merge_pr_timeout_s`, `merge_verify_timeout_s` — переиспользовать. +- (опц., по дизайну) новые: kill-switch регресс-гарда и декларация набора маркеров. Дефолты — + безопасные (для non-self — no-op). Любой новый ключ задокументировать в `.env.example`. + +## 7. Артефакты pipeline, которые должны быть созданы/обновлены + +- `docs/work-items/ORCH-073/06-adr/ADR-001-*.md` — решение по новой семантике merge-verify + (FR-1/FR-2/FR-3) + регресс-гард (FR-5) + `.gitattributes` (FR-4). +- `docs/architecture/README.md` — обновить раздел «Merge-в-main + пост-деплой верификация» + (ORCH-071) под FR-1 (SHA как единственный критерий) и добавить регресс-гард FR-5. +- `CHANGELOG.md` — запись в `## [Unreleased]`. +- `docs/work-items/ORCH-073/10-tech-risks.md`, `12-review.md`, `13-test-report.md`, + `14-deploy-log.md`, `15-staging-log.md` — по ходу конвейера. +- `04-test-plan.yaml` (этот пакет) — реализовать тесты в `tests/`. + +## 8. Аудит G4 (зафиксировать в ADR / 06-adr) + +Зафиксировать подтверждённую причину docs-only merge: у feature-ветки 067/069 в `main` попадали +только авто docs-PR (staging-log / deploy-log / CLAUDE.md / CHANGELOG), а code-PR не сливался, +при этом `pr_already_merged` засчитывал docs-PR → merge-verify ложно `CONFIRMED` → `done`. +Корень устранён FR-1+FR-2+FR-3. Восстановление кода (G1) уже выполнено restore-PR #76 — +подтвердить маркеры в `origin/main` (AC-1). diff --git a/docs/work-items/ORCH-073/03-acceptance-criteria.md b/docs/work-items/ORCH-073/03-acceptance-criteria.md new file mode 100644 index 0000000..73d7f37 --- /dev/null +++ b/docs/work-items/ORCH-073/03-acceptance-criteria.md @@ -0,0 +1,77 @@ +# 03 — Критерии приёмки: ORCH-073 + +Каждый критерий — однозначный PASS/FAIL. Reviewer/Tester проверяют буквально. + +## AC-1 — Код 067/069/071 одновременно в main (G1) +`origin/main` содержит **одновременно**: `plane_issue_link` + кликабельный заголовок (ORCH-067), +`qg0_title_max` (ORCH-069), `verify_merged_to_main` (ORCH-071). +- **PASS:** все три маркера присутствуют, счётчики > 0: + `git grep -c plane_issue_link origin/main -- src/notifications.py` > 0; + `git grep -c qg0_title_max origin/main -- src/` > 0; + `git grep -c verify_merged_to_main origin/main -- src/merge_gate.py` > 0. +- **FAIL:** хотя бы один маркер == 0. + +## AC-2 — `verify_merged_to_main` подтверждает merge ТОЛЬКО по SHA-в-main (FR-1) +`verify_merged_to_main(repo, branch, sha)` возвращает `True` **только** когда `sha` — реальный +предок `origin/main`. +- **PASS:** unit-тест: `sha` НЕ в `main` → `False`, **даже если** существует merged docs-PR той же + ветки (mock `pr_already_merged`/Gitea возвращает merged docs-PR). `sha` в `main` → `True`. +- **FAIL:** функция возвращает `True` при `sha` не в `main` из-за merged docs-PR. + +## AC-3 — Воспроизведение исходного бага → НЕ done + alert (FR-1/FR-2) +Задача с merged **docs-PR**, но БЕЗ merged **code-PR** (SHA не в main): merge-verify НЕ +`CONFIRMED`. +- **PASS:** `_handle_merge_verify` возвращает HOLD (intervened) → задача остаётся на `deploy`, + НЕ `done`, отправлен alert «not merged» (Telegram + Plane `set_issue_blocked`). Mock + воспроизводит сценарий ORCH-067/069. +- **FAIL:** задача доходит до `done` / нет alert. + +## AC-4 — `.gitattributes CHANGELOG.md merge=union` (FR-4) +В корне репо есть `.gitattributes` с `CHANGELOG.md merge=union`. +- **PASS:** файл существует, `git check-attr merge CHANGELOG.md` → `merge: union`; тест: два + последовательных ребейза/слияния с правкой `## [Unreleased]` НЕ дают конфликта, обе записи + сохранены в результирующем `CHANGELOG.md`. +- **FAIL:** атрибут отсутствует/не применяется ИЛИ возникает конфликт-маркер при ребейзе. + +## AC-5 — Регресс-гард ловит откат соседнего кода (FR-5) +После деплоя `main` без маркера ранее-merged задачи → alert, задача НЕ `done`. +- **PASS:** тест: симуляция `main`, где deployed SHA есть, но набор маркеров уменьшился (или + deployed SHA НЕ предок main) → `_handle_merge_verify` HOLD + alert «main regressed», НЕ `done`. +- **FAIL:** регресс соседнего кода не пойман, задача `done`. + +## AC-6 — Happy-path без ложных alert (INV-5 / AC-5 ТЗ) +Код реально в `main` (deployed SHA — предок `origin/main`) → задача `done` штатно, без ложного +alert; для non-self репо (enduro) merge/verify без изменений. +- **PASS:** тест happy-path: SHA в main → `verify_merged_to_main`=`True`, `_handle_merge_verify` + возвращает «advance» (не intervened); non-self репо → под-гейт no-op. +- **FAIL:** ложный alert на корректном merge ИЛИ изменение поведения для enduro. + +## AC-7 — Идемпотентность по SHA-в-main (INV-4) +Повторный прогон/reaper уже-слитой задачи (SHA в main) → no-op, без второго merge. +- **PASS:** тест: re-drive задачи с SHA-в-main → `merge_pr` no-op («already-merged»/idempotent), + второго Gitea POST merge нет; задача остаётся `done`. +- **FAIL:** второй merge / дубликат / ошибка. + +## AC-8 — Документация и тесты обновлены (правило агентов §2/§6) +- **PASS:** обновлены `CHANGELOG.md` (Unreleased), `docs/architecture/README.md` (раздел + merge-verify под FR-1 + регресс-гард FR-5), создан ADR в `docs/work-items/ORCH-073/06-adr/`; + pytest зелёный (`pytest tests/ -q`). +- **FAIL:** доки/ADR не обновлены ИЛИ pytest красный. + +## AC-9 — G4 аудит задокументирован +Причина docs-only merge (code-PR не слит, `pr_already_merged` засчитал docs-PR) зафиксирована в +ADR/06-adr, корень устранён (FR-1+FR-2+FR-3). +- **PASS:** ADR содержит раздел «Root cause / G4 audit» с воспроизведением и устранением. +- **FAIL:** аудит отсутствует. + +## AC-10 — Воспроизведение на staging «исправлено навсегда» (G3/AC-9 ТЗ) +2 задачи, обе с правкой `CHANGELOG.md`, прогнаны через staging → обе доезжают в `main` без потери +кода друг друга. +- **PASS:** зафиксировано в `15-staging-log.md`: оба набора маркеров присутствуют в `main` после + обоих merge; ни одна правка CHANGELOG не вызвала конфликт/откат. +- **FAIL:** код одной задачи затёрт другой ИЛИ конфликт CHANGELOG. + +## AC-11 — self-hosting safety сохранена (INV-2/INV-3) +- **PASS:** merge только через PR-API (без force-push в `main`); прод-контейнер не падал в рамках + задачи; ручной `Confirm Deploy` сохранён. +- **FAIL:** force-push в main / рестарт прод-контейнера в рамках merge / обход Confirm Deploy. diff --git a/docs/work-items/ORCH-073/04-test-plan.yaml b/docs/work-items/ORCH-073/04-test-plan.yaml new file mode 100644 index 0000000..de3d1cc --- /dev/null +++ b/docs/work-items/ORCH-073/04-test-plan.yaml @@ -0,0 +1,117 @@ +work_item: ORCH-073 +title: "CRIT: эрозия main — системный фикс merge-verify + восстановление кода 067/069" +notes: > + Покрытие FR-1..FR-5 / AC-1..AC-11. Все верификаторы — never-raise (INV-1): + при ошибке git/HTTP → False (fail-closed), не падение. Gitea/git вызовы мокаются + (monkeypatch httpx + subprocess), как в существующих тестах merge_gate/stage_engine. + Тесты регресс-гарда и .gitattributes используют временный git-репозиторий (tmp_path). + +tests: + # ---- FR-1: verify_merged_to_main — SHA-в-main как единственный критерий ---- + - id: TC-01 + type: unit + description: "verify_merged_to_main: sha — предок origin/main → True (happy-path, AC-6)." + module: tests/test_orch073_merge_verify.py + expected: PASS + - id: TC-02 + type: unit + description: "verify_merged_to_main: sha НЕ предок main И существует merged docs-PR ветки → False (баг 067/069, AC-2)." + module: tests/test_orch073_merge_verify.py + expected: PASS + - id: TC-03 + type: unit + description: "verify_merged_to_main: пустой sha → False (неопределённо, fail-closed)." + module: tests/test_orch073_merge_verify.py + expected: PASS + - id: TC-04 + type: unit + description: "verify_merged_to_main: git fetch/merge-base бросает исключение → False (never-raise, INV-1)." + module: tests/test_orch073_merge_verify.py + expected: PASS + + # ---- FR-2: pr_already_merged различает code-PR / docs-PR ---- + - id: TC-05 + type: unit + description: "pr_already_merged/идентификация PR: merged docs-PR (head=docs/*, base=main) НЕ засчитывается как merge кода ветки." + module: tests/test_orch073_pr_classify.py + expected: PASS + - id: TC-06 + type: unit + description: "merged code-PR (head=, base=main) корректно распознаётся как code-merge." + module: tests/test_orch073_pr_classify.py + expected: PASS + - id: TC-07 + type: unit + description: "pr_already_merged: HTTP-ошибка/не-200 → False (never-raise, консервативно)." + module: tests/test_orch073_pr_classify.py + expected: PASS + + # ---- FR-3: merge_pr сливает именно code-ветку ---- + - id: TC-08 + type: unit + description: "merge_pr выбирает open PR с head== и base==main (не docs/*), вызывает Gitea POST merge." + module: tests/test_orch073_merge_pr.py + expected: PASS + - id: TC-09 + type: unit + description: "merge_pr: нет open code-PR → (False, 'no open PR'); никогда не push/force-push main (INV-2)." + module: tests/test_orch073_merge_pr.py + expected: PASS + - id: TC-10 + type: unit + description: "merge_pr идемпотентен: уже-слитый code-PR (SHA в main) → no-op, без второго POST merge (AC-7/INV-4)." + module: tests/test_orch073_merge_pr.py + expected: PASS + + # ---- FR-4: .gitattributes CHANGELOG.md merge=union ---- + - id: TC-11 + type: integration + description: ".gitattributes в корне репо содержит 'CHANGELOG.md merge=union'; git check-attr подтверждает driver=union (AC-4)." + module: tests/test_orch073_gitattributes.py + expected: PASS + - id: TC-12 + type: integration + description: "Во временном git-репо два ребейза/слияния с правкой '## [Unreleased]' НЕ дают конфликта; обе записи в CHANGELOG сохранены (AC-4)." + module: tests/test_orch073_gitattributes.py + expected: PASS + + # ---- FR-5: регресс-гард целостности main + интеграция в _handle_merge_verify ---- + - id: TC-13 + type: unit + description: "_handle_merge_verify: SHA в main И маркеры на месте → return False (advance к done, happy-path AC-6)." + module: tests/test_orch073_regression_guard.py + expected: PASS + - id: TC-14 + type: unit + description: "_handle_merge_verify: SHA НЕ в main (docs-only merge) → return True (HOLD), alert + set_issue_blocked, НЕ done (AC-3)." + module: tests/test_orch073_regression_guard.py + expected: PASS + - id: TC-15 + type: unit + description: "Регресс-гард: deployed SHA есть, но набор маркеров ранее-merged задач уменьшился → HOLD + alert 'main regressed', НЕ done (AC-5)." + module: tests/test_orch073_regression_guard.py + expected: PASS + - id: TC-16 + type: unit + description: "_handle_merge_verify: внутренняя ошибка верификатора → HOLD + alert, без проброса исключения в advance_stage (never-raise, INV-1)." + module: tests/test_orch073_regression_guard.py + expected: PASS + + # ---- Условность / обратная совместимость ---- + - id: TC-17 + type: unit + description: "merge_verify_applies: non-self репо (enduro) или kill-switch off → под-гейт no-op, поведение merge/verify без изменений (AC-6/INV-5)." + module: tests/test_orch073_conditionality.py + expected: PASS + - id: TC-18 + type: unit + description: "Регресс-гард уважает kill-switch (merge_verify_enabled=False) → no-op; для non-self → no-op (INV-5)." + module: tests/test_orch073_conditionality.py + expected: PASS + + # ---- Регресс существующего поведения ---- + - id: TC-19 + type: integration + description: "Существующие тесты merge_gate/stage_engine (ORCH-065/071) остаются зелёными; полный pytest tests/ -q green (AC-8)." + module: tests/ + expected: PASS