diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 6729dfa..501c293 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -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`, diff --git a/docs/architecture/adr/adr-0018-auto-label-gates.md b/docs/architecture/adr/adr-0018-auto-label-gates.md new file mode 100644 index 0000000..b12c722 --- /dev/null +++ b/docs/architecture/adr/adr-0018-auto-label-gates.md @@ -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`. diff --git a/docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md b/docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md new file mode 100644 index 0000000..a82adc9 --- /dev/null +++ b/docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md @@ -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, "", author="analyst") + send_telegram("✅ : 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, "", author="deployer") + send_telegram("🚀 : прод-деплой авто-подтверждён (лейбл 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`. diff --git a/docs/work-items/ORCH-089/07-infra-requirements.md b/docs/work-items/ORCH-089/07-infra-requirements.md new file mode 100644 index 0000000..909a68d --- /dev/null +++ b/docs/work-items/ORCH-089/07-infra-requirements.md @@ -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": }` — индикация авто-аппрува. + +Вызовы — только когда `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), как всегда. diff --git a/docs/work-items/ORCH-089/10-tech-risks.md b/docs/work-items/ORCH-089/10-tech-risks.md new file mode 100644 index 0000000..8d1ad29 --- /dev/null +++ b/docs/work-items/ORCH-089/10-tech-risks.md @@ -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), новых классов риска фича не вводит.