architect(ET): auto-commit from architect run_id=408
This commit is contained in:
@@ -206,6 +206,39 @@ merge-в-main вообще**. Detached host-деплой лишь retag'ал о
|
||||
`docs/work-items/ORCH-071/06-adr/ADR-001-merge-verify-gate.md`,
|
||||
`docs/work-items/ORCH-073/06-adr/ADR-001-merge-verify-sha-truth-and-regression-guard.md`.
|
||||
|
||||
#### Гарантированный код-PR перед merge-verify (ORCH-082 — фикс ложного HOLD «no open PR»)
|
||||
Под-гейт merge-verify (ORCH-071/073) детерминированно мержит **открытый** код-PR ветки в `main`
|
||||
(`merge_pr`, фильтр `head.ref==branch` И `base.ref=="main"`). Но конвейер **не гарантировал**, что
|
||||
к моменту merge у ветки этот PR есть: PR создаётся единственной `launcher._ensure_pr` **только** на
|
||||
developer-пути и **только** при свежем worktree-коммите. На деплое ORCH-074 (08.06, первая задача
|
||||
после ручных восстановлений `main`) у ветки не оказалось открытого код-PR → `merge_pr` вернул
|
||||
`("False", "no open PR")` → защита ORCH-073 верно удержала задачу (HOLD, не ложный `done`), но это
|
||||
лечило следствие. ORCH-082 закрывает **отсутствующий инвариант** «к merge-verify у ветки есть
|
||||
открытый код-PR» аддитивно, внутри того же под-гейта, не трогая машину стадий:
|
||||
- **Новый leaf-актор `merge_gate.ensure_open_pr(repo, branch) -> (status, detail)`** (never-raise):
|
||||
`GET …/pulls?state=open` с фильтром `head.ref==branch` И `base.ref=="main"` (**идентичен**
|
||||
`merge_pr`/ORCH-073 FR-3 — авто-docs-PR `base != main` НЕ код-PR) → `("existed", N)`; иначе
|
||||
`POST …/pulls` → `("created", N)`; гонка «PR exists»/409/422 → повторный GET → `existed` (без
|
||||
дублей); любая иная ошибка → `("failed", reason)`.
|
||||
- **Врезка в `_handle_merge_verify`** ПОСЛЕ резолва `validated_revision` и **ПЕРЕД** `merge_pr`:
|
||||
`created|existed` → штатно к `merge_pr` → `verify_merged_to_main`; `failed` → честный HOLD+alert
|
||||
через новый helper `_hold_pr_create_failed` (текст «PR создать не удалось» — отличим от
|
||||
not-merged HOLD; `result.note="pr-create-failed-hold"`), задача остаётся на `deploy`, БЕЗ отката
|
||||
на development.
|
||||
- **Защита ORCH-073 неприкосновенна и приоритетна:** подтверждение merge остаётся ТОЛЬКО
|
||||
`verify_merged_to_main` (SHA-в-main) + `check_main_regression`; `ensure_open_pr` устраняет лишь
|
||||
**ложный** HOLD «no open PR», но не маскирует реально невлитый код (тот → HOLD как прежде).
|
||||
- **`launcher._ensure_pr`** рекомендуется делегировать в `ensure_open_pr` (единый код создания PR),
|
||||
сохранив прежний триггер «только developer-путь».
|
||||
- **Условность как ORCH-35/43/58/71:** kill-switch `merge_verify_autocreate_pr_enabled` (дефолт
|
||||
`true`); область — `merge_verify_applies(repo)` (self-hosting / `merge_verify_repos`); non-self —
|
||||
no-op. `False` → поведение ORCH-074 1:1. Идемпотентность из Gitea (наличие открытого PR), **без
|
||||
миграции БД** (restart-safe). `STAGE_TRANSITIONS`, `QG_CHECKS`, схема БД, `check_deploy_status`,
|
||||
exit-коды хука, merge-gate, image-freshness — без изменений; `main` не push/force-push.
|
||||
|
||||
Подробнее: [adr-0016](adr/adr-0016-ensure-open-pr-before-merge-verify.md) (amends 0013/0014);
|
||||
детально — `docs/work-items/ORCH-082/06-adr/ADR-001-ensure-open-pr-before-merge-verify.md`.
|
||||
|
||||
### Post-deploy наблюдение прода + реакция на деградацию (ORCH-021 — реализовано)
|
||||
Конвейер заканчивался на `deploy → done` и **забывал про прод**: «успех» = health-check
|
||||
в момент рестарта (~60с). Класс «зелёный деплой, красный прод» (прецедент ET-8 —
|
||||
|
||||
@@ -21,12 +21,14 @@ Per-work-item решения живут в `docs/work-items/<id>/06-adr/ADR-NNN-
|
||||
| adr-0013 | Merge-в-main + пост-деплой верификация как условие `done` | accepted | 2026-06-08 | ORCH-071 |
|
||||
| adr-0014 | SHA-в-main — единственный критерий merge-verify + регресс-гард | accepted | 2026-06-08 | ORCH-073 |
|
||||
| adr-0015 | Зависимости задач (B ждёт A) + сериализация merge внутри репо | accepted | 2026-06-08 | ORCH-026 |
|
||||
| adr-0016 | ensure_open_pr — гарантированный код-PR перед merge-verify | accepted | 2026-06-09 | ORCH-082 |
|
||||
|
||||
> ⚠️ Историческая коллизия: номер `0007` занят двумя файлами —
|
||||
> `adr-0007-reconciler.md` (ORCH-053) и `adr-0007-executable-self-deploy.md`
|
||||
> (ORCH-036). Оба accepted; для новых сквозных ADR использовать следующий
|
||||
> свободный номер (текущий максимум — `0015`).
|
||||
> свободный номер (текущий максимум — `0016`).
|
||||
> adr-0014 **amends** adr-0013 (меняет критерий merge-verify на «SHA-в-main»).
|
||||
> adr-0016 **amends** adr-0013/0014 (гарантирует открытый код-PR перед merge_pr, ORCH-082).
|
||||
|
||||
## Формат
|
||||
**Контекст → Решение → Альтернативы → Последствия → Связи.** Статус: proposed / accepted / superseded.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# ADR-0016: ensure_open_pr — гарантированный код-PR перед merge-verify (ORCH-082)
|
||||
|
||||
## Статус
|
||||
Accepted — амендмент к [adr-0013](adr-0013-merge-verify-gate.md) и
|
||||
[adr-0014](adr-0014-merge-verify-sha-source-of-truth.md). Детально:
|
||||
`docs/work-items/ORCH-082/06-adr/ADR-001-ensure-open-pr-before-merge-verify.md`.
|
||||
|
||||
## Контекст
|
||||
Merge-verify (ORCH-071/073) — под-гейт ребра `deploy → done`: детерминированно мержит код-PR в
|
||||
`main` (`merge_pr`) и подтверждает merge **только** по «SHA-в-main» (`verify_merged_to_main`,
|
||||
ORCH-073). На деплое ORCH-074 (08.06) `merge_pr` вернул `("False", "no open PR")`: у ветки **не
|
||||
было** открытого PR с `head==branch` И `base=="main"`. Защита ORCH-073 верно удержала задачу
|
||||
(HOLD, не ложный `done`), но это лечило **следствие**.
|
||||
|
||||
Первопричина (код-аудит): PR создаётся в конвейере **единственной** функцией
|
||||
`launcher._ensure_pr`, вызываемой **только** на developer-пути и **только** при свежем
|
||||
worktree-коммите. Любой сценарий без свежего developer-коммита (бойнс без правок, повторный
|
||||
прогон, **ручное восстановление ветки/`main`** — случай ORCH-074) оставляет ветку без код-PR.
|
||||
Инвариант «к merge-verify у ветки есть открытый код-PR» в конвейере **отсутствовал** → блокер
|
||||
автономного деплоя (ORCH-54).
|
||||
|
||||
## Решение
|
||||
Аддитивно обеспечить инвариант **внутри того же под-гейта**, ПЕРЕД `merge_pr`, не трогая машину
|
||||
стадий:
|
||||
|
||||
1. **Новый leaf-актор `merge_gate.ensure_open_pr(repo, branch) -> (status, detail)`** (never-raise):
|
||||
`GET …/pulls?state=open` с фильтром **`head.ref==branch` И `base.ref=="main"`** (идентичен
|
||||
`merge_pr`/ORCH-073 FR-3 — авто-docs-PR не считается код-PR) → `("existed", N)`; иначе
|
||||
`POST …/pulls` → `("created", N)`; гонка «PR exists» → повторный GET → `existed` (без дублей);
|
||||
любая ошибка → `("failed", reason)`.
|
||||
2. **Врезка в `_handle_merge_verify`** ПОСЛЕ резолва `validated_revision` и ПЕРЕД `merge_pr`:
|
||||
`created|existed` → штатно к `merge_pr`; `failed` → честный HOLD+alert через новый helper
|
||||
`_hold_pr_create_failed` (текст «PR создать не удалось» — отличим от not-merged HOLD), задача
|
||||
остаётся на `deploy`, БЕЗ отката на development.
|
||||
3. **Kill-switch `merge_verify_autocreate_pr_enabled`** (дефолт `True`); область —
|
||||
`merge_verify_applies` (self-hosting / `merge_verify_repos`). `False` → поведение ORCH-074 1:1.
|
||||
4. **`launcher._ensure_pr`** рекомендуется делегировать в `ensure_open_pr` (единый код создания
|
||||
PR), сохранив прежний триггер «только developer-путь».
|
||||
|
||||
## Последствия
|
||||
- **Защита ORCH-073 неприкосновенна и приоритетна:** подтверждение merge остаётся ТОЛЬКО
|
||||
`verify_merged_to_main` (SHA-в-main) + `check_main_regression`. Создание PR устраняет лишь
|
||||
**ложный** HOLD «no open PR», но не маскирует реально невлитый код (тот → HOLD как прежде).
|
||||
- **Без миграций:** идемпотентность выводится из Gitea (наличие открытого PR), схема БД не меняется
|
||||
— restart-safe; повторный заход (reaper/reconciler/re-approve) → `existed`, дублей нет.
|
||||
- **Инварианты целы:** `STAGE_TRANSITIONS`, `QG_CHECKS`, схема БД, `check_deploy_status`,
|
||||
exit-коды хука, merge-gate (ORCH-043), image-freshness (ORCH-058) — без изменений; `main` не
|
||||
push/force-push; never-raise на всём пути.
|
||||
- **Наблюдаемость:** один однозначный исход в логах на проход — created / existed / failed; HOLD по
|
||||
failed текстуально отличим от HOLD not-merged.
|
||||
- **Минус:** код-PR может создаваться после прохождения гейтов — безопасно, т.к. гейты валидируют
|
||||
код ветки, а merge-verify идёт ПОСЛЕ всех гейтов; PR — лишь механизм слияния, ревью не обходится.
|
||||
@@ -0,0 +1,221 @@
|
||||
# ADR-001: Гарантированный идемпотентный код-PR перед merge-verify (ensure_open_pr)
|
||||
|
||||
- Work Item: **ORCH-082** (Plane-заголовок «ORCH-81»)
|
||||
- Repo: `orchestrator` (self-hosting)
|
||||
- Связь: амендмент к merge-verify ([adr-0013](../../../architecture/adr/adr-0013-merge-verify-gate.md),
|
||||
[adr-0014](../../../architecture/adr/adr-0014-merge-verify-sha-source-of-truth.md));
|
||||
глобально зафиксировано в [adr-0016](../../../architecture/adr/adr-0016-ensure-open-pr-before-merge-verify.md)
|
||||
- BRD/ТЗ/AC: `01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`
|
||||
|
||||
## Статус
|
||||
Accepted
|
||||
|
||||
## Контекст
|
||||
|
||||
### Что случилось (инцидент ORCH-074, 08.06)
|
||||
Деплой ORCH-074 встал на под-гейте merge-verify (ребро `deploy → done`):
|
||||
`run_deploy_finalizer → _handle_merge_verify` вызвал `merge_gate.merge_pr(repo, branch)` и
|
||||
получил `ok=False, "no open PR"` — в Gitea для ветки `feature/ORCH-074-…` **не было открытого
|
||||
PR** с `head.ref==branch` И `base.ref=="main"`. Защита ORCH-073 (fail-closed по «SHA-в-main»)
|
||||
**отработала правильно**: задача удержана на `deploy` (НЕ `done`), Plane → Blocked, Telegram-alert,
|
||||
ложно-зелёного `done` не произошло. Разблокировано вручную — PR #79 создан через Gitea API,
|
||||
finalizer перезапущен, код честно влит, задача `done`. Это **workaround, не фикс**.
|
||||
|
||||
### Root cause (G1, подтверждён код-аудитом)
|
||||
PR создаётся в конвейере **ровно в одном месте** — `AgentLauncher._ensure_pr`
|
||||
(`src/agents/launcher.py:1079`), и вызывается он из `_monitor_agent` **только** по цепочке
|
||||
условий (`src/agents/launcher.py:751–753`):
|
||||
|
||||
```
|
||||
exit_code == 0
|
||||
→ git status --porcelain непусто (есть worktree-изменения)
|
||||
→ git commit succeeded
|
||||
→ git push succeeded
|
||||
→ agent == "developer" ←── ТОЛЬКО здесь вызывается self._ensure_pr(...)
|
||||
```
|
||||
|
||||
Отсюда класс «ветка без PR» структурно неизбежен. Подтверждённые код-аудитом ветви:
|
||||
|
||||
- **R-A (условное создание) — структурный первопричинный дефект.** Если в конкретном
|
||||
developer-run нет свежих изменений (`git status` пуст: ветка уже была закоммичена/запушена
|
||||
ранее, бойнс REQUEST_CHANGES без новых правок, повторный прогон, **ручное восстановление
|
||||
ветки**) — `_ensure_pr` **не вызывается вовсе**. PR не появится никогда. Никакого
|
||||
персистентного флага «PR создан» в БД нет, поэтому идемпотентность чтения внутри `_ensure_pr`
|
||||
до merge-стадии не доходит.
|
||||
- **R-C (разъехавшееся состояние ветки/PR) — проксимальный триггер ORCH-074.** ORCH-074 — первая
|
||||
задача после серии **ручных восстановлений `main` 08.06**: открытый код-PR был закрыт/не
|
||||
пересоздан, у ветки мог остаться лишь авто-docs-PR (`base != main`), который `merge_pr` (фильтр
|
||||
`base=="main"`, ORCH-073 FR-3) корректно НЕ считает кодовым.
|
||||
- **R-B (тихий сбой создания) — потенциальная, не первопричина здесь.** `_ensure_pr` глотает любое
|
||||
исключение (`except Exception → logger.error → return None`): транзиентная ошибка Gitea на
|
||||
`POST …/pulls` теряется без ретрая и эскалации.
|
||||
|
||||
**Вывод:** в конвейере **отсутствует инвариант** «к моменту merge-verify у ветки есть открытый
|
||||
код-PR». Защита ORCH-073 верно ловит следствие, но причина — выше по потоку. Любая следующая
|
||||
задача с тем же стечением обстоятельств застрянет тем же образом → автономный деплой (ORCH-54)
|
||||
заблокирован.
|
||||
|
||||
### Ограничения, которые нельзя нарушать
|
||||
- Защита ORCH-073 (SHA-в-main + регресс-гард) — приоритетна. Создание PR **не должно** маскировать
|
||||
реально невлитый код.
|
||||
- `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, схема БД, `check_deploy_status`/`_parse_deploy_status`,
|
||||
exit-коды хука, merge-gate (ORCH-043), image-freshness (ORCH-058) — без изменений.
|
||||
- Весь путь merge-verify — **never-raise**.
|
||||
- Слияние только через PR; `main` никогда не push/force-push.
|
||||
|
||||
## Решение
|
||||
|
||||
Закрыть пробел инвариантом «обеспечить открытый код-PR» **внутри того же под-гейта merge-verify**,
|
||||
ПЕРЕД детерминированным `merge_pr`. Три точечные врезки, симметричные существующему дизайну
|
||||
ORCH-071/073 (leaf-актор в `merge_gate` + врезка в `_handle_merge_verify` + kill-switch). Машина
|
||||
стадий и реестры не трогаются.
|
||||
|
||||
### Р-1. Новый идемпотентный leaf-актор `merge_gate.ensure_open_pr(repo, branch)`
|
||||
|
||||
Сигнатура (решение архитектора по ТЗ §1):
|
||||
|
||||
```python
|
||||
def ensure_open_pr(repo: str, branch: str) -> tuple[str, str]:
|
||||
"""Гарантировать открытый код-PR (head==branch, base==main). never-raise.
|
||||
Возврат: ("existed", "<number>") | ("created", "<number>") | ("failed", "<reason>").
|
||||
"""
|
||||
```
|
||||
|
||||
Алгоритм (FR-1):
|
||||
1. `GET …/pulls?state=open` → найти PR с **`head.ref==branch` И `base.ref=="main"`**. Фильтр
|
||||
**идентичен** `merge_pr`/ORCH-073 FR-3 — авто-docs-PR (`base != main`) НЕ считается код-PR
|
||||
(AC-6). Нашли → `("existed", <number>)`.
|
||||
2. Иначе `POST …/pulls` (`head=branch`, `base="main"`, авто-заголовок/тело) → `201` →
|
||||
`("created", <number>)`.
|
||||
3. **Идемпотентность при гонке:** если на `POST` Gitea вернёт «PR exists»/`409`/`422` —
|
||||
повторный `GET` (шаг 1) подтверждает существующий PR → `("existed", …)`. Дубль не плодится
|
||||
(AC-2, FR-5).
|
||||
4. Любая иная HTTP/parse/сетевая ошибка → `("failed", <reason>)`. **Never-raise** (`except
|
||||
Exception → ("failed", str(e))`).
|
||||
|
||||
Актор — **leaf** (зависит только от `settings` + `httpx`, без импорта `stage_engine`), как
|
||||
`merge_pr`/`verify_merged_to_main`. Таймауты — переиспользовать `settings.merge_pr_timeout_s`
|
||||
(тот же класс Gitea-вызовов).
|
||||
|
||||
> **Почему фильтр `base=="main"` критичен** (грабли ORCH-073): у ветки одновременно бывают код-PR
|
||||
> и авто-docs-PR. Без фильтра актор «увидит» docs-PR как existed и не создаст нужный код-PR, а
|
||||
> `merge_pr` потом не найдёт что мержить → петля. Один и тот же предикат `head==branch &&
|
||||
> base=="main"` гарантирует, что `ensure_open_pr` и `merge_pr` работают с одним и тем же PR.
|
||||
|
||||
### Р-2. Врезка в `_handle_merge_verify` (ребро `deploy → done`)
|
||||
|
||||
В существующем `_handle_merge_verify` (`src/stage_engine.py:1324`), **ПОСЛЕ**
|
||||
`merge_verify_applies(repo)`-гейта и резолва `sha = image_freshness.validated_revision(...)`,
|
||||
но **ПЕРЕД** `merge_pr`:
|
||||
|
||||
```python
|
||||
sha = image_freshness.validated_revision(repo, branch)
|
||||
|
||||
# ORCH-082: гарантировать открытый код-PR ДО детерминированного merge_pr.
|
||||
if settings.merge_verify_autocreate_pr_enabled:
|
||||
pr_status, pr_detail = merge_gate.ensure_open_pr(repo, branch)
|
||||
logger.info(
|
||||
f"Task {task_id}: merge-verify ensure_open_pr -> {pr_status} ({pr_detail})"
|
||||
)
|
||||
if pr_status == "failed":
|
||||
return _hold_pr_create_failed(
|
||||
task_id, repo, work_item_id, branch, pr_detail, result
|
||||
)
|
||||
# "created" | "existed" -> штатно продолжаем к merge_pr.
|
||||
|
||||
merged_ok, merge_msg = merge_gate.merge_pr(repo, branch)
|
||||
...
|
||||
```
|
||||
|
||||
Семантика (FR-2):
|
||||
- `created | existed` → продолжаем штатно к `merge_pr` → `verify_merged_to_main` → регресс-гард.
|
||||
- `failed` → **честный HOLD + alert** через новый helper `_hold_pr_create_failed` (см. Р-3); задача
|
||||
остаётся на `deploy` (НЕ `done`), БЕЗ отката на development — симметрично текущему not-merged/
|
||||
regressed HOLD.
|
||||
- kill-switch off → блок пропускается целиком → поведение 1:1 как до фикса (AC-8).
|
||||
|
||||
Место выбрано так, что **никакой существующий шаг не сдвигается**: `merge_pr` и
|
||||
`verify_merged_to_main` остаются на своих местах с теми же контрактами. Создание PR — это только
|
||||
страховка инварианта ДО них.
|
||||
|
||||
### Р-3. Новый HOLD-helper `_hold_pr_create_failed` (распознаваемость причины, FR-4/AC-5)
|
||||
|
||||
Зеркало существующего `_hold_main_regressed` (`src/stage_engine.py:1280`). Текст HOLD **обязан
|
||||
отличаться** от not-merged HOLD: оператор должен видеть, что причина — **невозможность создать
|
||||
PR** (Gitea недоступна), а не **невозможность слить уже созданный**:
|
||||
|
||||
```python
|
||||
def _hold_pr_create_failed(task_id, repo, work_item_id, branch, reason, result) -> bool:
|
||||
merge_gate.note_not_merged_alert(work_item_id) # переиспользуем счётчик-нотификатор
|
||||
msg = (f"PR создать не удалось: {reason} (repo={repo}, branch={branch}, "
|
||||
f"wi={work_item_id}). Открытый код-PR отсутствует и не создан — задача "
|
||||
f"удержана на `deploy` (НЕ done). Нужно проверить доступность Gitea / создать PR.")
|
||||
# set_issue_blocked + plane_add_comment + send_telegram (каждый в try/except, never-break HOLD)
|
||||
result.alerted = True
|
||||
result.note = "pr-create-failed-hold" # отличается от "merge-not-verified-hold"
|
||||
result.advanced = False
|
||||
return True
|
||||
```
|
||||
|
||||
Это сохраняет инвариант «никогда не пробрасываем исключение в `advance_stage`»: `failed` —
|
||||
структурированный исход, а не throw.
|
||||
|
||||
### Р-4. Единый источник кода создания PR (опционально, рекомендуется)
|
||||
|
||||
`launcher._ensure_pr` рекомендуется **делегировать** в `merge_gate.ensure_open_pr`, чтобы создание
|
||||
PR жило в одном месте и одинаково логировало created/existed/failed (G3). **Поведенческий
|
||||
инвариант:** триггер «создавать PR только в developer-пути со свежим коммитом» **НЕ ужесточается**
|
||||
(BRD/ТЗ §1) — меняется лишь реализация под капотом, не условие вызова. Это снижает риск
|
||||
рассинхрона двух копий логики «выбрать/создать PR». Если делегирование увеличивает диффу/риск —
|
||||
допустимо оставить `_ensure_pr` как есть и лишь усилить его логирование (created/existed/failed);
|
||||
функциональная цель ORCH-082 достигается врезкой Р-2 независимо.
|
||||
|
||||
### Р-5. Kill-switch и область действия
|
||||
|
||||
- `merge_verify_autocreate_pr_enabled: bool = True`
|
||||
(env `ORCH_MERGE_VERIFY_AUTOCREATE_PR_ENABLED`) в `src/config.py`, рядом с
|
||||
`merge_verify_enabled`/`regression_guard_enabled`.
|
||||
- `False` → ровно прежнее поведение: авто-создания нет, «no open PR» → HOLD как в ORCH-074 (AC-8).
|
||||
- Область — `merge_gate.merge_verify_applies(repo)` (self-hosting / `merge_verify_repos`); прочие
|
||||
репо — no-op, создание PR остаётся за прежним механизмом (AC-9). Отдельного `*_repos` для
|
||||
авто-создания НЕ вводим: семантически оно неотделимо от merge-verify, у которого уже есть область.
|
||||
|
||||
## Последствия
|
||||
|
||||
### Плюсы
|
||||
- Закрыт структурный пробел: к merge-verify ветка гарантированно имеет открытый код-PR; ложный
|
||||
HOLD «no open PR» больше не требует ручного вмешательства (AC-2/AC-3).
|
||||
- Защита ORCH-073 цела и приоритетна: верификация остаётся **только** `verify_merged_to_main`
|
||||
(SHA-в-main) + `check_main_regression`. Реально невлитый код → HOLD как прежде (AC-4/FR-3).
|
||||
- Идемпотентность по факту Gitea (наличие открытого PR), без новой колонки/таблицы — согласуется с
|
||||
restart-safe-моделью merge-verify; повторный заход (reaper/reconciler/re-approve) → `existed`,
|
||||
дублей нет (FR-5/AC-2).
|
||||
- Распознаваемые исходы в логах и в HOLD-тексте: created / existed / failed (G3/AC-5).
|
||||
- Инварианты сохранены: `STAGE_TRANSITIONS`, `QG_CHECKS`, схема БД, `check_deploy_status`,
|
||||
exit-коды хука, merge-gate, image-freshness — не тронуты (AC-10). `main` не push/force-push.
|
||||
|
||||
### Минусы / ограничения
|
||||
- Auto-создание PR на ребре `deploy → done` означает, что код-PR может появиться **после** того,
|
||||
как все гейты (security/merge-gate/staging/image-freshness) уже пройдены по ветке. Это безопасно
|
||||
по времени (BRD §6 допущение): ревью/гейты валидируют **код ветки**, а PR — лишь механизм
|
||||
слияния; merge-verify исполняется ПОСЛЕ всех гейтов. PR здесь не обходит ревью.
|
||||
- При недоступности Gitea задача попадёт в HOLD (как и сегодня) — но теперь с явным текстом
|
||||
«PR создать не удалось» вместо «PR не влит». Это сознательный fail-closed (AC-7): never-raise,
|
||||
честный HOLD, не ложно-зелёный `done`.
|
||||
- Небольшое дублирование Gitea-вызовов между `ensure_open_pr` и `merge_pr` (оба GET список PR). Это
|
||||
приемлемо: два независимых leaf-актора с одинаковым фильтром важнее микро-оптимизации; объединять
|
||||
в один вызов — увеличить связность без пользы.
|
||||
|
||||
### Влияние на self-hosting
|
||||
Изменение строго аддитивно и под kill-switch (`True`). Прод-контейнер не рестартится этой задачей;
|
||||
выкат — через staging-гейт (8501) как любая ORCH-задача. На ребре `deploy → done` риск-профиль не
|
||||
растёт: при любом сбое — HOLD, не падение `advance_stage`, конвейер всех проектов не встаёт.
|
||||
|
||||
## Связанные документы
|
||||
- BRD/ТЗ/AC: `01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`
|
||||
- Тех-риски: `10-tech-risks.md`
|
||||
- Глобальный амендмент: [adr-0016](../../../architecture/adr/adr-0016-ensure-open-pr-before-merge-verify.md)
|
||||
- Контекст merge-verify: [adr-0013](../../../architecture/adr/adr-0013-merge-verify-gate.md),
|
||||
[adr-0014](../../../architecture/adr/adr-0014-merge-verify-sha-source-of-truth.md)
|
||||
- Постмортем фантомного merge: `docs/history/LESSONS_2026-06-08_phantom-merge.md`,
|
||||
runbook `docs/operations/PHANTOM_MERGE_RUNBOOK.md`
|
||||
27
docs/work-items/ORCH-082/10-tech-risks.md
Normal file
27
docs/work-items/ORCH-082/10-tech-risks.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 10 — Технические риски: ORCH-082 (ORCH-81)
|
||||
|
||||
Риски точечной врезки «ensure_open_pr перед merge-verify». Все — в зоне ребра `deploy → done`
|
||||
(self-hosting), под kill-switch `merge_verify_autocreate_pr_enabled`.
|
||||
|
||||
| ID | Риск | Вероятн. | Влияние | Митигация |
|
||||
|----|------|----------|---------|-----------|
|
||||
| **R1** | `ensure_open_pr` выбирает/создаёт **не тот** PR (авто-docs-PR `base != main`) → `merge_pr` мержит/верифицирует не тот PR | Сред. | Высокое | Фильтр `head.ref==branch` И `base.ref=="main"`, **идентичный** `merge_pr` (ORCH-073 FR-3). Тест AC-6: ветка с docs-PR (`base!=main`) → актор его игнорирует и создаёт код-PR на `main`. |
|
||||
| **R2** | Создание PR **маскирует** реально невлитый код → ложно-зелёный `done` (регресс ORCH-073) | Низк. | Критич. | Верификация остаётся ТОЛЬКО `verify_merged_to_main` (SHA-в-main) + `check_main_regression`; `ensure_open_pr` НЕ влияет на вердикт merge. Регресс-тест AC-4: `verify_merged_to_main→False` ⇒ HOLD, не `done`. |
|
||||
| **R3** | Гонка: параллельно создаётся 2 PR → дубль | Низк. | Сред. | Идемпотентность FR-1.3: на ошибку «PR exists»/409/422 — повторный GET → `existed`; PR создаётся только если GET пуст. Тест AC-2. |
|
||||
| **R4** | Исключение из `ensure_open_pr` пробрасывается в `advance_stage` → падение перехода | Низк. | Высокое | Контракт never-raise (`except Exception → ("failed", reason)`); врезка обёрнута внешним try/except `_handle_merge_verify`. `failed` → структурированный HOLD, не throw. Тест AC-7. |
|
||||
| **R5** | Gitea недоступна на ребре `deploy → done` → задача в HOLD | Низк. | Сред. | Сознательный fail-closed: `failed` → честный HOLD+alert (`_hold_pr_create_failed`), НЕ ложный `done`. Текст HOLD отличим от not-merged (AC-5) — оператор видит причину. Reaper/reconciler/re-approve переиграют, когда Gitea вернётся (FR-5). |
|
||||
| **R6** | Оператор не различит HOLD «PR не создан» и HOLD «PR не влит» | Сред. | Низк. | Отдельный helper `_hold_pr_create_failed` с собственным текстом и `result.note="pr-create-failed-hold"` (≠ `merge-not-verified-hold`); лог-строка `ensure_open_pr -> failed: <reason>`. AC-5. |
|
||||
| **R7** | Расхождение логики выбора/создания PR между `launcher._ensure_pr` и `merge_gate.ensure_open_pr` | Сред. | Сред. | Рекомендованное делегирование `_ensure_pr → ensure_open_pr` (единый код). Если не делегируем — обе копии используют ОДИН фильтр `head==branch && base==main`; тест на согласованность. |
|
||||
| **R8** | Включение по умолчанию (`True`) меняет прод-поведение скрытно | Низк. | Сред. | Поведение строго аддитивно: при наличии PR → `existed`/no-op; меняется лишь ранее-падавший путь «no open PR». Kill-switch `False` → 1:1 ORCH-074 (AC-8). Выкат через staging-гейт (8501). |
|
||||
| **R9** | Регресс инвариантов (`STAGE_TRANSITIONS`/`QG_CHECKS`/схема БД/exit-коды) | Низк. | Высокое | Под-гейт-врезка в `advance_stage`, НЕ новый `QG_CHECKS`-элемент и НЕ новая стадия; БД не трогается (идемпотентность из Gitea). Тест AC-10 + полный `pytest`. |
|
||||
|
||||
## Зоны без изменений (подтверждение границ)
|
||||
- **Инфраструктура/топология** — без изменений → `07-infra-requirements.md` не требуется.
|
||||
- **Схема БД** — без изменений (идемпотентность выводится из Gitea) → `08-data-requirements.md`
|
||||
не требуется.
|
||||
- `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_deploy_status`/`_parse_deploy_status`, exit-коды хука,
|
||||
merge-gate (ORCH-043), image-freshness (ORCH-058), terminal-sync — не тронуты.
|
||||
|
||||
## Главный архитектурный приоритет
|
||||
При любом конфликте «создать PR» **проигрывает** «не дать ложно-зелёный `done`» (защита ORCH-073).
|
||||
Создание PR — страховка инварианта ДО merge_pr, никогда не подмена верификации merge.
|
||||
Reference in New Issue
Block a user