architect(ET): auto-commit from architect run_id=442
This commit is contained in:
@@ -130,6 +130,46 @@ Self-hosting зацикливался на `deploy-staging`: `scripts/staging_ch
|
||||
`docs/work-items/ORCH-088/06-adr/ADR-001-serial-gate.md`,
|
||||
`docs/work-items/ORCH-088/08-data-requirements.md`.
|
||||
|
||||
### Авто-режим по лейблам: autoApprove + autoDeploy (ORCH-089 — design)
|
||||
Конвейер имеет два **человеческих** гейта, тормозящих пакетный автономный прогон (эпик
|
||||
ORCH-088): гейт BRD (`analysis`: ждёт ручного `Approved`) и гейт прод-деплоя (`deploy`:
|
||||
Phase A ждёт ручного `Confirm Deploy`, ORCH-059). ORCH-089 снимает **только эти два
|
||||
человеческих решения** — выборочно (лейбл Plane на задаче), декларативно, обратимо, **не
|
||||
трогая ни одной технической проверки**. Аддитивно, по образцу условных под-гейтов
|
||||
(ORCH-035/043/058/059/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`). Без дублирования переходной логики.
|
||||
- **`autoDeploy`** → врезка в `stage_engine._handle_self_deploy_phase_a` сразу после advance
|
||||
на `deploy` + `clear_state`: лог/Telegram/Plane-коммент + `_handle_self_deploy_phase_b(...)`
|
||||
(idempotency-маркер `INITIATED`, статус `Deploying`, finalizer). Пропускаются лишь
|
||||
индикативно-человеческие шаги (`Awaiting Deploy` + «ask-human»). **BR-5 структурно:** Phase A
|
||||
достигается только после зелёных под-гейтов ребра `deploy-staging → deploy` (security →
|
||||
merge-gate → image-freshness → staging) → autoDeploy физически не деплоит сломанное.
|
||||
- **Чтение лейблов** — `plane_sync.fetch_issue_labels` (поле `labels` issue, `None` при
|
||||
ошибке ≠ `[]`) + `get_project_labels` (`{normalized_name→uuid}`, TTL-кэш по образцу
|
||||
`get_project_states`); сопоставление по нормализованному имени (`strip().casefold()`),
|
||||
неоднозначность → «нет лейбла». Источник истины — Plane API, не payload вебхука. Новый
|
||||
сеттер `set_issue_approved` (ключ `approved` уже в `_DEFAULT_STATES`).
|
||||
- **Флаги** (`config.py`): `auto_label_enabled` (kill-switch), `auto_approve_label`/
|
||||
`auto_deploy_label`, `auto_label_repos` (CSV; **пусто → self-hosting only**),
|
||||
`auto_label_states_ttl_s`. `applies(repo)` (локальный) проверяется ПЕРВЫМ; `has_label`
|
||||
(сеть) — только при `applies==True` → при выключенном флаге нулевой сетевой оверхед,
|
||||
нулевая регрессия для enduro.
|
||||
- **Fail-safe (never auto):** любая ошибка/недоступность Plane/неоднозначность →
|
||||
«нет авто» → ручной гейт (never-raise). **Идемпотентность:** autoApprove — advance один раз
|
||||
(поздний Approved/F-2 видят `architecture`); autoDeploy — маркер `INITIATED`. **Прозрачность
|
||||
(AC-7):** лог + Telegram + Plane-коммент + live-карточка; блок `auto_labels` в `GET /queue`.
|
||||
- **Инфра-предусловие:** создать лейблы `autoApprove`/`autoDeploy` в Plane-проекте ORCH
|
||||
(labels API); их отсутствие = `has_label` False = ручной режим (fail-safe).
|
||||
|
||||
Подробнее: [adr-0018](adr/adr-0018-auto-label-gates.md), детально —
|
||||
`docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md`,
|
||||
`docs/work-items/ORCH-089/07-infra-requirements.md`.
|
||||
|
||||
### Исполняемый самодеплой стадии `deploy` (ORCH-36)
|
||||
`deploy` перестаёт быть «бумажной»: для self-hosting (`is_self_hosting_repo`) стадия
|
||||
РЕАЛЬНО деплоит прод (8500) через хост-хук `scripts/orchestrator-deploy-hook.sh`,
|
||||
|
||||
59
docs/architecture/adr/adr-0018-auto-label-gates.md
Normal file
59
docs/architecture/adr/adr-0018-auto-label-gates.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# ADR-0018: Авто-режим по лейблам — autoApprove / autoDeploy (ORCH-089)
|
||||
|
||||
## Статус
|
||||
Accepted (реализация — ORCH-089)
|
||||
|
||||
## Контекст
|
||||
Конвейер имеет два **человеческих** гейта, тормозящих пакетный автономный прогон
|
||||
(эпик ORCH-088, «10–20 задач за ночь»):
|
||||
1. **BRD** (`analysis`): ждёт ручного Plane-статуса `Approved` → advance на `architecture`.
|
||||
2. **Прод-деплой** (`deploy`): Phase A ставит `Awaiting Deploy` и ждёт ручного
|
||||
`Confirm Deploy` (ORCH-059) → Phase B (`initiate_deploy`).
|
||||
|
||||
Для доверенных задач оба клика избыточны. Нужно снять **только эти два человеческих
|
||||
решения**, выборочно/декларативно (лейбл Plane на задаче), не ослабляя ни одной
|
||||
технической проверки.
|
||||
|
||||
## Решение
|
||||
Аддитивно, по образцу условных под-гейтов (ORCH-035/043/058/059/088): leaf-модуль чистой
|
||||
логики `src/labels.py` (never-raise) + точечные врезки + флаги. `STAGE_TRANSITIONS`, реестр
|
||||
`QG_CHECKS`, все `check_*`, схема БД — **не трогаются**.
|
||||
|
||||
- **`autoApprove`** (лейбл задачи) → в `_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`). Без
|
||||
дублирования переходной логики.
|
||||
- **`autoDeploy`** (лейбл задачи) → в `_handle_self_deploy_phase_a` сразу после advance на
|
||||
`deploy` + `clear_state`: лог/Telegram/Plane-коммент + `_handle_self_deploy_phase_b(...)`
|
||||
(idempotency-маркер `INITIATED`, `Deploying`, finalizer). Пропускаются лишь
|
||||
индикативно-человеческие шаги (`Awaiting Deploy` + «ask-human»).
|
||||
- **Чтение лейблов** — `plane_sync.fetch_issue_labels` + `get_project_labels` (TTL-кэш,
|
||||
образец `get_project_states`); сопоставление по нормализованному имени; источник истины —
|
||||
Plane API (не payload). Новый сеттер `set_issue_approved` (ключ `approved` уже в states).
|
||||
- **Флаги:** `auto_label_enabled` (kill-switch), `auto_approve_label`/`auto_deploy_label`
|
||||
(имена), `auto_label_repos` (CSV; **пусто → self-hosting only**), `auto_label_states_ttl_s`.
|
||||
`applies(repo)` (локальный) проверяется ПЕРВЫМ; `has_label` (сеть) — только если
|
||||
`applies==True` → при выключенном флаге нулевой сетевой оверхед.
|
||||
|
||||
## Критические инварианты
|
||||
- **Авто-режим снимает ТОЛЬКО человеческое решение**, не ослабляя ни один тех-гейт
|
||||
(CI / staging / security / merge-gate / image-freshness / merge-verify / regression-guard /
|
||||
post-deploy). autoDeploy живёт в точке, где все под-гейты ребра `deploy-staging → deploy`
|
||||
уже зелёные → структурно «никогда не деплоит сломанное».
|
||||
- **Fail-safe (never auto):** любая ошибка/недоступность Plane/неоднозначность имени →
|
||||
«нет авто» → ручной гейт (согласовано с fail-closed-практикой ORCH-059). never-raise.
|
||||
- **Нулевая регрессия:** без лейблов / `auto_label_enabled=False` / репо вне scope →
|
||||
поведение 1:1 как до ORCH-089 (enduro не затронут).
|
||||
- **Идемпотентность:** autoApprove — advance применяется один раз (поздний Approved/F-2
|
||||
видят уже `architecture`); autoDeploy — маркер `INITIATED`.
|
||||
|
||||
## Последствия
|
||||
**+** минимальная поверхность, единый источник истины перехода, декларативно/обратимо,
|
||||
независимые лейблы, безопасный дефолт. **−** Approved-статус транзиентен (durable-аудит —
|
||||
лог/Telegram/коммент); 1–2 GET к Plane на гейт применимого репо (TTL-кэш карты лейблов);
|
||||
требуется однократно создать лейблы в Plane-проекте ORCH (инфра-предусловие; их отсутствие =
|
||||
fail-safe ручной режим).
|
||||
|
||||
Детально: `docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md`,
|
||||
`07-infra-requirements.md`, `10-tech-risks.md`.
|
||||
220
docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md
Normal file
220
docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# ADR-001: Авто-режим по лейблам — autoApprove (гейт BRD) + autoDeploy (гейт прод-деплоя)
|
||||
|
||||
## Статус
|
||||
Accepted
|
||||
|
||||
## Контекст
|
||||
|
||||
В конвейере два **человеческих** гейта (точки, где конвейер останавливается и ждёт
|
||||
ручного клика человека):
|
||||
|
||||
1. **Гейт BRD** (`analysis`): после успешного analyst задача переводится в `In Review`
|
||||
(`_handle_analysis_approved_flow`, ветка `files_ok`) и ждёт ручного Plane-статуса
|
||||
**Approved**. Approved прилетает вебхуком → `handle_verdict(approved=True)` →
|
||||
`_try_advance_stage` → `advance_stage(..., finished_agent=None)` (ветка
|
||||
`check_analysis_approved` / `approved-via-status`) → advance `analysis → architecture`
|
||||
+ `mark_brd_review_ended`.
|
||||
2. **Гейт прод-деплоя** (`deploy`): на ребре `deploy-staging → deploy` после зелёных
|
||||
под-гейтов (security → merge-gate → image-freshness → staging) выполняется Phase A
|
||||
(`_handle_self_deploy_phase_a`): advance на `deploy` + `Awaiting Deploy` + маркер
|
||||
`APPROVE_REQUESTED` + просьба сменить статус на **Confirm Deploy**. Confirm Deploy
|
||||
прилетает вебхуком → `handle_confirm_deploy` → `advance_stage(..., confirm_deploy=True)`
|
||||
→ `_handle_self_deploy_phase_b` (`initiate_deploy` + маркер `INITIATED` + finalizer).
|
||||
|
||||
Для задач, которым **доверяем** (пакетный автономный прогон, эпик ORCH-088), оба ручных
|
||||
клика избыточны и тормозят прогон «10–20 задач за ночь». Нужно снять **только эти два
|
||||
человеческих решения** — выборочно (на уровне отдельной задачи), декларативно (лейблом
|
||||
Plane), обратимо, прозрачно и **не трогая ни одной технической проверки** (BRD §4).
|
||||
|
||||
Прошлый подход «Стрим ревьюит и апрувит BRD» (09.06) ОТМЕНЁН. Актуальная модель —
|
||||
лейблы на задаче (`autoApprove`, `autoDeploy`), независимые, без участия людей.
|
||||
|
||||
## Решение
|
||||
|
||||
Аддитивная врезка по образцу условных под-гейтов проекта (ORCH-035/043/058/059/088):
|
||||
**leaf-модуль чистой логики (never-raise) + точечные врезки в существующие точки принятия
|
||||
решений + флаги в `config.py`**. `STAGE_TRANSITIONS`, реестр `QG_CHECKS` и все `check_*`
|
||||
**НЕ трогаются** — авто-режим переиспользует уже существующие переходы и гейты, лишь
|
||||
устраняя ожидание человеческого сигнала.
|
||||
|
||||
### D1. Новый leaf-модуль `src/labels.py` (чистая логика, never-raise)
|
||||
|
||||
Контракт «никогда не падает; при любой ошибке/неоднозначности → "нет авто"»
|
||||
(fail-safe к ручному гейту, BR-6/AC-6). Публичная поверхность:
|
||||
|
||||
| Функция | Контракт |
|
||||
|---------|----------|
|
||||
| `auto_approve_applies(repo) -> bool` | scope autoApprove (см. D5). False при kill-switch/ошибке. |
|
||||
| `auto_deploy_applies(repo) -> bool` | scope autoDeploy (см. D5). False при kill-switch/ошибке. |
|
||||
| `has_label(work_item_id, label_name, project_id=None) -> bool` | True ⇔ на issue навешен лейбл с именем `label_name` (нормализованным). **Любая** ошибка/неоднозначность/недоступность Plane → **False**. |
|
||||
| `snapshot() -> dict` | read-only для `GET /queue` (enabled, имена лейблов, scope). never-raise. |
|
||||
|
||||
`has_label` резолвит так (всё внутри одного `try/except → False`):
|
||||
1. `labels = plane_sync.fetch_issue_labels(work_item_id, project_id)` — список uuid
|
||||
лейблов issue (None при ошибке → `has_label=False`);
|
||||
2. `name_map = plane_sync.get_project_labels(project_id)` — `{normalized_name → uuid}`
|
||||
карта лейблов проекта (кэш с TTL, см. D4);
|
||||
3. нормализация искомого имени (`_normalize`: `strip().casefold()`);
|
||||
4. `target_uuid = name_map.get(normalized)`; если нет совпадения **или** имя
|
||||
неоднозначно (две записи проекта свелись к одному нормализованному имени) →
|
||||
**False** (fail-safe);
|
||||
5. `return target_uuid in set(labels)`.
|
||||
|
||||
> Источник истины лейблов — **Plane API**, не payload вебхука: обе точки врезки —
|
||||
> launcher-path (analyst-finished / staging-deployer-finished), где payload недоступен;
|
||||
> API надёжнее и единообразен. (Подтверждено: `src/webhooks/plane.py` не несёт `labels`.)
|
||||
|
||||
### D2. Чтение лейблов в `src/plane_sync.py`
|
||||
|
||||
- `fetch_issue_labels(work_item_id, project_id=None) -> list[str] | None` —
|
||||
`GET …/issues/{issue_id}/` → поле `labels` (список uuid). Через
|
||||
`_resolve_project_id` + `find_issue_id` + `PLANE_HEADERS`, таймаут 10с (как соседи).
|
||||
Ошибка/issue-not-found → `None` (отличимо от пустого списка `[]` = «лейблов нет»).
|
||||
- `get_project_labels(project_id) -> dict[str,str]` —
|
||||
`GET …/projects/{pid}/labels/` → `{normalized_name → uuid}`. **Кэш по образцу
|
||||
`get_project_states`** (`_LABELS_CACHE` per-project + TTL `_cache_record_fresh`),
|
||||
чтобы не бить API на каждом гейте. Стейл-кэш при сетевой ошибке отдаётся как у
|
||||
`get_project_states` (safer-than-empty). Пустой результат / ошибка без кэша → `{}`
|
||||
→ `has_label=False`.
|
||||
- `set_issue_approved(work_item_id, project_id=None)` — новый сеттер, 1:1 калька
|
||||
`set_issue_in_review`: `state_id = get_project_states(pid)["approved"]` →
|
||||
`_set_issue_state_direct`. Ключ `approved` уже существует в `_DEFAULT_STATES`
|
||||
и `_PLANE_NAME_TO_KEY` (`"Approved" → "approved"`), отдельная инфра-настройка не нужна.
|
||||
|
||||
### D3. Врезка autoApprove — `_handle_analysis_approved_flow`, ветка `files_ok`
|
||||
|
||||
После существующих шагов (`set_issue_in_review` + analyst-коммент + `notify_approve_requested`
|
||||
— оставлены ради клока/прозрачности/симметрии с ручным путём), ДО `return`:
|
||||
|
||||
```
|
||||
if labels.auto_approve_applies(repo) and labels.has_label(work_item_id, settings.auto_approve_label):
|
||||
plane_sync.set_issue_approved(work_item_id) # индикация (AC-1), транзиентна*
|
||||
logger.info("Task …: label autoApprove → BRD auto-approved")
|
||||
plane_add_comment(work_item_id, "<auto-approve via label autoApprove>", author="analyst")
|
||||
send_telegram("✅ <ORC-NNN>: BRD авто-подтверждён (лейбл autoApprove)")
|
||||
auto = advance_stage(task_id, current_stage, repo, work_item_id, branch, finished_agent=None)
|
||||
result.advanced = auto.advanced; result.to_stage = auto.to_stage
|
||||
result.note = "auto-approved-via-label"
|
||||
return
|
||||
# (нет лейбла / fail-safe) → прежнее поведение: return, ждём человека.
|
||||
```
|
||||
|
||||
**Ключевое требование — НЕ дублировать переходную логику.** Авто-аппрув идёт через тот
|
||||
же `advance_stage(..., finished_agent=None)`, что и человеческий Approved-вебхук: ветка
|
||||
`check_analysis_approved` с `agent is None` → `qg_passed=True` (`approved-via-status`) →
|
||||
advance `analysis → architecture` → `mark_brd_review_ended` (клок) → штатные
|
||||
post-эффекты (карточка, plane-sync, enqueue architect). Единый источник истины перехода.
|
||||
|
||||
> *Транзиентность Approved-статуса:* сразу после advance `plane_notify_stage` выставит
|
||||
> статус `Architecture`, перекрыв `Approved`. Это ожидаемо — `set_issue_approved` даёт
|
||||
> мгновенную индикацию/симметрию, а **durable**-прозрачность несут лог + Telegram + Plane-коммент
|
||||
> (AC-7). Re-entrancy безопасна: вложенный `advance_stage` не возвращается в
|
||||
> `_handle_analysis_approved_flow` (та ветка требует `agent=='analyst'`; вложенный вызов
|
||||
> идёт с `finished_agent=None`) — рекурсии нет.
|
||||
|
||||
### D4. Врезка autoDeploy — `_handle_self_deploy_phase_a`, ранняя ветка
|
||||
|
||||
Сразу после `update_task_stage(task_id, "deploy")` + `notify_stage_change` +
|
||||
`self_deploy.clear_state(repo, work_item_id)` (всегда — wipe стейл-маркеров), ДО
|
||||
«ask-human» блока:
|
||||
|
||||
```
|
||||
if labels.auto_deploy_applies(repo) and labels.has_label(work_item_id, settings.auto_deploy_label):
|
||||
logger.info("Task …: label autoDeploy → prod deploy auto-confirmed")
|
||||
plane_add_comment(work_item_id, "<auto-confirm prod deploy via label autoDeploy>", author="deployer")
|
||||
send_telegram("🚀 <ORC-NNN>: прод-деплой авто-подтверждён (лейбл autoDeploy)")
|
||||
_handle_self_deploy_phase_b(task_id, repo, work_item_id, branch, result) # INITIATED + Deploying + finalizer
|
||||
return
|
||||
# (нет лейбла / fail-safe) → прежний Phase A: set_issue_awaiting_deploy + APPROVE_REQUESTED + «смените на Confirm Deploy».
|
||||
```
|
||||
|
||||
При autoDeploy пропускаются ТОЛЬКО индикативно-человеческие шаги (`set_issue_awaiting_deploy`
|
||||
+ `APPROVE_REQUESTED` + «ask-human» коммент/Telegram) — статус `Deploying` выставит сам
|
||||
Phase B. Идемпотентность прод-деплоя обеспечена существующим маркером `INITIATED` внутри
|
||||
`_handle_self_deploy_phase_b` (повторный заход — no-op). Phase B/C, merge-verify,
|
||||
regression-guard, post-deploy monitor — **неизменны**.
|
||||
|
||||
**Почему BR-5/AC-5 выполнены структурно:** Phase A достигается ТОЛЬКО после зелёных
|
||||
под-гейтов ребра `deploy-staging → deploy` (security → merge-gate → image-freshness →
|
||||
staging — они исполняются ВЫШЕ в `advance_stage` и при FAIL откатывают/возвращают БЕЗ
|
||||
выхода на Phase A). autoDeploy лишь заменяет ручной клик в точке, где все тех-проверки
|
||||
уже зелёные — он физически не может задеплоить сломанное.
|
||||
|
||||
### D5. Scope и kill-switch (флаги `src/config.py`)
|
||||
|
||||
| Флаг | Тип / дефолт | Назначение |
|
||||
|------|--------------|------------|
|
||||
| `auto_label_enabled` | `bool=True` (`ORCH_AUTO_LABEL_ENABLED`) | Глобальный kill-switch обоих авто-режимов. `False` → строго прежнее поведение, **ни одного нового сетевого вызова на гейтах** (AC-8). |
|
||||
| `auto_approve_label` | `str="autoApprove"` | Имя лейбла гейта BRD. |
|
||||
| `auto_deploy_label` | `str="autoDeploy"` | Имя лейбла гейта деплоя. |
|
||||
| `auto_label_repos` | `str=""` (CSV) | Scope. **Пусто → self-hosting only** (`orchestrator`). |
|
||||
| `auto_label_states_ttl_s` | `int=300` | TTL кэша карты лейблов проекта (образец `plane_states_ttl_s`). |
|
||||
|
||||
`auto_approve_applies`/`auto_deploy_applies` — калька `self_deploy_applies`:
|
||||
`auto_label_enabled=False` → всегда False; непустой `auto_label_repos` → только
|
||||
перечисленные репо; пустой → **self-hosting only** (`is_self_hosting_repo`). Решение
|
||||
по дефолту scope (BRD оставил выбор): **self-hosting only** — безопасный дефолт (BR-10),
|
||||
к тому же autoDeploy-врезка живёт в Phase A, которая существует только для self-hosting.
|
||||
Единый scope-флаг на оба лейбла (минимальная матрица); раздельные репо-скоупы — follow-up
|
||||
при необходимости.
|
||||
|
||||
**Порядок проверки на гейте (важно для AC-8):** `applies(repo)` проверяется ПЕРВЫМ
|
||||
(локальный, без сети). Только если `applies==True` вызывается `has_label` (сеть). При
|
||||
выключенном флаге `applies` сразу False → `has_label` не вызывается → нулевой сетевой
|
||||
оверхед, нулевая регрессия для enduro.
|
||||
|
||||
### D6. Идемпотентность и взаимодействие с reconciler/serial-gate
|
||||
|
||||
- **autoApprove vs реальный Approved-вебхук / reconciler F-2:** после авто-advance стадия
|
||||
= `architecture`. Поздний человеческий Approved или F-2 (plane-side) увидят уже
|
||||
`architecture` → не повторят analysis-advance (тот же эффект, что и человеческий
|
||||
double-click сегодня). Advance применяется один раз.
|
||||
- **autoDeploy:** идемпотентность — существующий маркер `INITIATED` (Phase B).
|
||||
- **serial-gate (ORCH-088):** сериализует claim analyst-job на уровне FIFO — авто-режим
|
||||
ортогонален (убирает паузы ВНУТРИ прохода одной задачи), не конфликтует.
|
||||
- **reconciler F-1** analysis не трогает (человеческий гейт) — авто-аппрув идёт через
|
||||
launcher-path, не через F-1.
|
||||
|
||||
### D7. Наблюдаемость (AC-7)
|
||||
|
||||
Каждый авто-проход → `logger.info` (label X → действие) + Telegram + Plane-коммент
|
||||
(автор `analyst` для BRD, `deployer` для деплоя — образец существующих гейт-комментов) +
|
||||
обновление live-карточки через штатный advance/notify. Аддитивный read-only блок
|
||||
`auto_labels` в `GET /queue` (`labels.snapshot()`: enabled, имена лейблов, scope) — образец
|
||||
блоков `reconcile`/`serial_gate`. Счётчики авто-проходов — best-effort/опционально (v1
|
||||
можно in-memory или опустить; БД не трогаем).
|
||||
|
||||
### D8. Схема БД — без изменений
|
||||
|
||||
Авто-режим stateless относительно БД: источник истины лейблов — Plane (читается на гейте);
|
||||
идемпотентность autoDeploy — существующие sentinel-маркеры (`APPROVE_REQUESTED`/`INITIATED`);
|
||||
клок `brd_review_*` уже существует (ORCH-087). Миграции нет (restart-safe через Plane/маркеры).
|
||||
|
||||
## Последствия
|
||||
|
||||
**Плюсы:**
|
||||
- Минимальная, аддитивная поверхность изменения; `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`
|
||||
неприкосновенны (AC-10). Единый источник истины перехода (переиспользование advance/Phase B).
|
||||
- Все тех-гейты на месте; autoDeploy структурно не может задеплоить сломанное (BR-5/AC-5).
|
||||
- Декларативно и обратимо (снял лейбл → ручной режим). Независимые лейблы (AC-9).
|
||||
- Fail-safe by default (never auto при любой неоднозначности, AC-6); kill-switch + scope
|
||||
→ нулевая регрессия для enduro (AC-8).
|
||||
|
||||
**Минусы / ограничения:**
|
||||
- Approved-статус при autoApprove транзиентен (перекрывается `Architecture`) — durable-аудит
|
||||
несут лог/Telegram/коммент, не Plane-статус.
|
||||
- Чтение лейблов добавляет 1–2 GET к Plane на каждом из двух гейтов применимого репо (с TTL-кэшем
|
||||
карты лейблов; вызывается только когда `applies==True`). При недоступности Plane → fail-safe
|
||||
к ручному гейту (не блок конвейера).
|
||||
- Доверие выражается лейблом — оператор отвечает за то, что autoDeploy навешан осознанно
|
||||
(тех-гейты страхуют от поломки, но не от «не той фичи»).
|
||||
|
||||
**Инфра-предусловие:** лейблы `autoApprove`/`autoDeploy` должны существовать в Plane-проекте
|
||||
ORCH (создать однократно через labels API) — см. `07-infra-requirements.md`. Нет лейбла в
|
||||
проекте → `has_label` всегда False → ручной режим (fail-safe), без ошибок.
|
||||
|
||||
## Связанные
|
||||
- BRD/ТЗ/AC: `docs/work-items/ORCH-089/{01-brd,02-trz,03-acceptance-criteria}.md`
|
||||
- Образцы условной врезки: ADR-0003 (staging), 0006 (merge-gate), 0007 (self-deploy),
|
||||
0017 (serial-gate); ORCH-059 (Confirm Deploy status).
|
||||
- Глобальный ADR: `docs/architecture/adr/adr-0018-auto-label-gates.md`.
|
||||
63
docs/work-items/ORCH-089/07-infra-requirements.md
Normal file
63
docs/work-items/ORCH-089/07-infra-requirements.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# 07 — Инфра-требования: ORCH-089 (авто-режим по лейблам)
|
||||
|
||||
## I-1. Создать лейблы в Plane-проекте ORCH (однократно, обязательно)
|
||||
|
||||
Авто-режим управляется лейблами на задаче. В Plane-проекте ORCH сейчас лейблов
|
||||
`autoApprove`/`autoDeploy` **нет** — их нужно создать один раз через labels API:
|
||||
|
||||
```
|
||||
POST {PLANE_BASE}/workspaces/{WORKSPACE}/projects/{ORCH_PROJECT_ID}/labels/
|
||||
Headers: PLANE_HEADERS
|
||||
Body: {"name": "autoApprove"}
|
||||
|
||||
POST {PLANE_BASE}/workspaces/{WORKSPACE}/projects/{ORCH_PROJECT_ID}/labels/
|
||||
Body: {"name": "autoDeploy"}
|
||||
```
|
||||
|
||||
Имена должны **точно** соответствовать `auto_approve_label` / `auto_deploy_label`
|
||||
(дефолты `autoApprove` / `autoDeploy`). Сопоставление в коде — по нормализованному имени
|
||||
(`strip().casefold()`), т.е. регистр/пробелы не критичны, но рекомендуется создать ровно
|
||||
как в дефолте.
|
||||
|
||||
**Fail-safe при отсутствии:** если лейбл в проекте не создан, `labels.has_label` всегда
|
||||
вернёт `False` → задача идёт ручным путём (нулевой риск, без ошибок). То есть создание
|
||||
лейблов — предусловие активации фичи, а не условие стабильности конвейера.
|
||||
|
||||
## I-2. Сброс кэша состояний/лейблов после создания (рекомендуется)
|
||||
|
||||
`get_project_labels` кэширует карту лейблов проекта с TTL `auto_label_states_ttl_s`
|
||||
(дефолт 300с). После создания новых лейблов карта подтянется автоматически в течение TTL;
|
||||
для немедленного эффекта — рестарт не требуется, достаточно дождаться TTL или (если будет
|
||||
добавлен) вызвать reload-хелпер кэша лейблов по образцу `reload_project_states`.
|
||||
|
||||
## I-3. Конфигурация (env, хост mva154)
|
||||
|
||||
По умолчанию фича включена (`auto_label_enabled=True`) и применима только к self-hosting
|
||||
репо (`auto_label_repos=""` → `orchestrator`). Управляющие env (опционально, в `.env`):
|
||||
|
||||
| Env | Дефолт | Эффект |
|
||||
|-----|--------|--------|
|
||||
| `ORCH_AUTO_LABEL_ENABLED` | `true` | Глобальный kill-switch. `false` → оба гейта ручные, нулевой сетевой оверхед. |
|
||||
| `ORCH_AUTO_APPROVE_LABEL` | `autoApprove` | Имя лейбла гейта BRD. |
|
||||
| `ORCH_AUTO_DEPLOY_LABEL` | `autoDeploy` | Имя лейбла гейта деплоя. |
|
||||
| `ORCH_AUTO_LABEL_REPOS` | `` (пусто) | CSV scope. Пусто → self-hosting only. |
|
||||
| `ORCH_AUTO_LABEL_STATES_TTL_S` | `300` | TTL кэша карты лейблов проекта. |
|
||||
|
||||
## I-4. Сетевые/доступ
|
||||
|
||||
Новых endpoint оркестратора нет. Дополнительные **исходящие** вызовы к Plane API v1
|
||||
(те же креды `PLANE_HEADERS`, таймаут 10с):
|
||||
- `GET …/issues/{issue_id}/` (поле `labels`) — чтение лейблов задачи на гейте;
|
||||
- `GET …/projects/{pid}/labels/` — карта лейблов проекта (кэш с TTL);
|
||||
- `PATCH …/issues/{issue_id}/` `{"state": <approved_uuid>}` — индикация авто-аппрува.
|
||||
|
||||
Вызовы — только когда `applies(repo)==True` (kill-switch off / репо вне scope → нет
|
||||
вызовов). Недоступность Plane → fail-safe к ручному гейту (конвейер не блокируется).
|
||||
|
||||
## I-5. Топология / прод-риск
|
||||
|
||||
Self-hosting не меняется: autoDeploy лишь авто-инициирует **существующий** Phase B
|
||||
(detached host-деплой через `scripts/orchestrator-deploy-hook.sh`). Никакого нового пути
|
||||
рестарта прод-контейнера не вводится. Phase C / merge-verify / regression-guard /
|
||||
post-deploy monitor продолжают верифицировать результат. Раскат — под kill-switch
|
||||
(`ORCH_AUTO_LABEL_ENABLED`), деплой self — через обязательный staging-гейт (8501), как всегда.
|
||||
20
docs/work-items/ORCH-089/10-tech-risks.md
Normal file
20
docs/work-items/ORCH-089/10-tech-risks.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 10 — Технические риски: ORCH-089 (авто-режим по лейблам)
|
||||
|
||||
| # | Риск | Вероятность / Impact | Митигация |
|
||||
|---|------|----------------------|-----------|
|
||||
| R-1 | **Ложный авто-проход гейта** при ошибке чтения лейблов (Plane вернул мусор/частичный ответ) → задача авто-проходит, хотя лейбла нет. | Низк. / **Критич.** (групповой self-hosting риск). | `has_label` обёрнут в единый `try/except → False`; `fetch_issue_labels` различает `None` (ошибка) и `[]` (нет лейблов); неоднозначность имени → False. Любая неопределённость → ручной гейт (BR-6/AC-6). Дополнительно: тех-гейты страхуют от деплоя сломанного даже при ложном autoDeploy. |
|
||||
| R-2 | **Двойной advance / гонка** автоApprove с реальным Approved-вебхуком или reconciler F-2. | Сред. / Низк. | Advance применяется один раз: после авто-advance стадия = `architecture`; поздний Approved/F-2 видят `architecture` и не повторяют analysis-переход (как человеческий double-click сегодня). |
|
||||
| R-3 | **Двойной прод-деплой** при autoDeploy (повторный заход Phase A / дубль staging-deployer-finished). | Низк. / Высок. | Идемпотентность Phase B по маркеру `INITIATED`. Phase A после первого прохода advance'ит стадию на `deploy` → guard `current_stage=="deploy-staging"` больше не матчится, повторный Phase A не запускается. `clear_state` в Phase A wipe'ит маркеры только при входе в свежий проход. |
|
||||
| R-4 | **Re-entrancy** вложенного `advance_stage` из `_handle_analysis_approved_flow` → рекурсия. | Низк. / Сред. | Вложенный вызов идёт с `finished_agent=None` и попадает в ветку `approved-via-status`, НЕ в `_handle_analysis_approved_flow` (та требует `agent=='analyst'`). Рекурсии нет. |
|
||||
| R-5 | **Регрессия для enduro / при выключенном флаге** (лишние сетевые вызовы, изменение поведения). | Низк. / Высок. | `applies(repo)` (локальный, без сети) проверяется ПЕРВЫМ; `has_label` (сеть) — только при `applies==True`. `auto_label_enabled=False` или репо вне scope → `applies==False` → нулевой сетевой оверхед, поведение 1:1 (AC-8). |
|
||||
| R-6 | **Лейбл не создан в Plane-проекте** → фича «молча не работает». | Сред. / Низк. | `has_label==False` → ручной гейт (fail-safe, не ошибка). Инфра-предусловие задокументировано (`07-infra-requirements.md` I-1). Прозрачность: отсутствие авто-прохода видно по тому, что задача встала на ручном гейте. |
|
||||
| R-7 | **Транзиентность Approved-статуса** (перекрывается `Architecture` сразу после advance) → оператор не увидит, что прошёл именно авто-аппрув. | Сред. / Низк. | Durable-прозрачность — лог + Telegram + Plane-коммент («auto-approved via label autoApprove») + live-карточка (AC-7). Plane-статус Approved — лишь мгновенная индикация. |
|
||||
| R-8 | **Stale-кэш карты лейблов** (`get_project_labels`) → недавно созданный/снятый лейбл не виден. | Низк. / Низк. | TTL `auto_label_states_ttl_s` (300с) — самозалечивание без рестарта (образец `plane_states_ttl_s`/ORCH-068). Окно ≤ TTL. |
|
||||
| R-9 | **Plane API недоступен на гейте** → задержка/блок конвейера. | Низк. / Сред. | Таймаут 10с (как соседи), never-raise → «нет авто» → ручной гейт. Конвейер не блокируется; задача просто ждёт человека (прежнее поведение). |
|
||||
| R-10 | **Доверие выражено лейблом ошибочно** (autoDeploy навешан не на ту задачу). | Сред. / Сред. | Тех-гейты блокируют поломку (не «не ту фичу»). Лейбл обратим (снять → ручной режим). Зона ответственности оператора; прозрачность авто-прохода (AC-7) даёт раннее обнаружение. |
|
||||
|
||||
## Вывод
|
||||
Доминирующий риск — **R-1 (ложный авто-проход)**; закрывается строгим never-raise / fail-safe
|
||||
контрактом leaf-модуля и тем, что тех-гейты остаются последней линией защиты. Все риски
|
||||
укладываются в установленные проектом паттерны (условный под-гейт + kill-switch + scope +
|
||||
fail-safe), новых классов риска фича не вводит.
|
||||
Reference in New Issue
Block a user