# 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).