analyst(ET): auto-commit from analyst run_id=380

This commit is contained in:
2026-06-08 15:52:20 +03:00
committed by stream
parent e54d1fc4ac
commit 319b23b4fc
4 changed files with 421 additions and 0 deletions

View File

@@ -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 <sha> origin/main`.
Первая ветка (`pr_already_merged`) и есть дыра.
2. **`pr_already_merged` засчитывает ЛЮБОЙ merged PR ветки.**
`src/merge_gate.py::pr_already_merged` делает `GET /pulls?state=all&head=<branch>` и
возвращает `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) и сосредоточиться
на **системном фиксе навсегда** (G2G5 / 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==<feature-branch>`).
- **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 подрывает само ядро
автономности конвейера.

View File

@@ -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 <sha> origin/main`.
OR-ветка `pr_already_merged` засчитывает docs-PR → ложно-зелёный.
**Требование:** подтверждение merge — **ТОЛЬКО** прямой факт «deployed commit является предком
`origin/main`»:
- после `git fetch origin main` выполнить `git merge-base --is-ancestor <deployed_sha> 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=<branch>` — включая авто docs-PR.
**Требование (на выбор архитектора, предпочтителен вариант «б»):**
- **(а)** засчитывать merged только для PR, реально несущего код ветки: `base.ref==main`
И `head.ref==<feature-branch>` (исключить 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==<feature-branch>`), а не полагается на 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 <prev
tasks> 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).

View File

@@ -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.

View File

@@ -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=<feature-branch>, 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==<feature-branch> и 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