architect(ET): auto-commit from architect run_id=442

This commit is contained in:
2026-06-09 12:04:24 +03:00
committed by orchestrator-deployer
parent 0b5fede802
commit f7488e9536
5 changed files with 402 additions and 0 deletions

View File

@@ -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`,

View File

@@ -0,0 +1,59 @@
# ADR-0018: Авто-режим по лейблам — autoApprove / autoDeploy (ORCH-089)
## Статус
Accepted (реализация — ORCH-089)
## Контекст
Конвейер имеет два **человеческих** гейта, тормозящих пакетный автономный прогон
(эпик ORCH-088, «1020 задач за ночь»):
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/коммент); 12 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`.

View 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), оба ручных
клика избыточны и тормозят прогон «1020 задач за ночь». Нужно снять **только эти два
человеческих решения** — выборочно (на уровне отдельной задачи), декларативно (лейблом
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-статус.
- Чтение лейблов добавляет 12 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`.

View 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), как всегда.

View 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), новых классов риска фича не вводит.