architect(ET): auto-commit from architect run_id=519
This commit is contained in:
@@ -585,6 +585,39 @@ sentinel-state, `write_post_deploy_log`.
|
||||
Подробнее: [adr-0010](adr/adr-0010-post-deploy-monitor.md), детально —
|
||||
`docs/work-items/ORCH-021/06-adr/ADR-001-post-deploy-monitor.md`.
|
||||
|
||||
### Terminal-window-aware гард deploy-статусов: done-задача держит Done (ORCH-094 — design)
|
||||
Терминальная (`done`) задача в Plane **не держала `Done`**: непрерывный флапп
|
||||
`Awaiting Deploy ⟷ Monitoring after Deploy` (верифицировано на **ORCH-061**, task 47, done с 07.06 —
|
||||
273 активности, само не затихает). Причина: три code-писателя deploy-фазовых статусов
|
||||
(`stage_engine.py:404/1218/1316`) делегируют в тонкие сеттеры `plane_sync`, которые **БД-стадию не
|
||||
читают** ⇒ терминал-слепы; любой повторный/стейл вызов под бот-токеном орка перезаписывает `Done`
|
||||
обратно. Тонкость: `update_task_stage("done")` (стр. 369) пишет стадию **раньше** легитимного
|
||||
`set_issue_monitoring` (стр. 404) ⇒ пост-деплой-окно ORCH-021 by-design индицируется поверх уже-`done`
|
||||
задачи; наивный гард «stage==done → Done» затёр бы легитимный `Monitoring` (регресс).
|
||||
|
||||
Решение — **единый terminal-window-aware гард на входе трёх deploy-фазовых сеттеров** (новый leaf
|
||||
`src/deploy_status_guard.py`, never-raise, config-gated; образец `serial_gate`/`labels`/`cancel`).
|
||||
- **Инвариант:** deploy-фазовый статус легитимен ⇔ задача **нетерминальна** ИЛИ (`done` И активно
|
||||
пост-деплой-окно). `decide(work_item_id, target) → ALLOW | CONVERGE_DONE | SUPPRESS`: off / чужой
|
||||
issue / не-self репо / нетерминал → ALLOW; `cancelled` → SUPPRESS; `done`+`monitoring`+`window_active`
|
||||
→ ALLOW; `done` иначе → CONVERGE_DONE (`set_issue_done`, идемпотентно); исключение → ALLOW+warning.
|
||||
- **Окно** — новый `post_deploy.window_active(repo,wi)` = `has_marker(ARMED) and not has_marker(DONE)`
|
||||
(restart-safe). **Перенос арм-блока перед terminal-sync** в `advance_stage` блок `next_stage=="done"`
|
||||
⇒ на стр. 404 `ARMED` уже есть ⇒ легитимный первый `Monitoring` проходит; re-drive после закрытия
|
||||
окна сходится к `Done`.
|
||||
- **Харднинг монитора:** страж `has_marker(...DONE)` (ранний return) + тик no-op при `cancelled`
|
||||
мид-окно; тики привязаны к активному job'у (нет job → нет тика, нет статус-PATCH).
|
||||
- **Наблюдаемость:** каждый вердикт логируется (`work_item`/`caller`/`target`/`db_stage`/
|
||||
`window_active`/вердикт); подавление — явно.
|
||||
- **Инварианты:** `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/machine-verdict ключи/схема БД — НЕ тронуты;
|
||||
`main`/force-push/прод-контейнер/detached-деплой — НЕ тронуты; рабочий self-deploy-цикл 1:1; не-self
|
||||
репо инертны. Kill-switch `ORCH_DEPLOY_STATUS_GUARD_ENABLED` (→ 1:1), область
|
||||
`ORCH_DEPLOY_STATUS_GUARD_REPOS` (пусто → self-hosting). Ограничение: внешняя Plane-automation (если
|
||||
таков актор) закрывается буфером сходимости, а не code-фиксом — локализация актора в задаче (BR-7).
|
||||
|
||||
Подробнее: [adr-0028](adr/adr-0028-terminal-window-aware-deploy-status-guard.md), детально —
|
||||
`docs/work-items/ORCH-094/06-adr/ADR-001-terminal-window-aware-deploy-status-guard.md`.
|
||||
|
||||
### Свежесть артефакта BUILD-ONCE: провенанс staging-образа (ORCH-058 — реализовано)
|
||||
BUILD-ONCE retag (ORCH-36) промоутит `SOURCE_IMAGE=orchestrator-orchestrator-staging` в прод
|
||||
**без rebuild**, полагаясь на «staging-образ свеж и провалидирован». Этой гарантии нет:
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
work_item: ORCH-094
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-09
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# adr-0028: Terminal-window-aware гард выставления deploy-фазовых статусов Plane
|
||||
|
||||
Сквозной (cross-cutting) ADR. **Амендмент** к [adr-0010](adr-0010-post-deploy-monitor.md)
|
||||
(post-deploy monitor, ORCH-021) и Plane-статусной модели (ORCH-066): вводит инвариант
|
||||
«deploy-фазовые Plane-статусы — terminal-window-aware» поверх общих сеттеров `plane_sync` и
|
||||
переупорядочивает блок `next_stage == "done"` в `advance_stage`. Детальное решение задачи —
|
||||
`docs/work-items/ORCH-094/06-adr/ADR-001-terminal-window-aware-deploy-status-guard.md`.
|
||||
|
||||
> Регистрируется как сквозной, т.к. правит **общие** сеттеры `set_issue_awaiting_deploy`/
|
||||
> `set_issue_deploying`/`set_issue_monitoring` (используются системно) и трогает маркированный блок с
|
||||
> `ORCH-021`/`ORCH-066` (`docs/_standards/TRACEABILITY.md`).
|
||||
|
||||
## Статус
|
||||
Proposed
|
||||
|
||||
## Контекст
|
||||
|
||||
Терминальная (`done`) задача в Plane **не держит `Done`**: непрерывный флапп
|
||||
`Awaiting Deploy ⟷ Monitoring after Deploy` (верифицировано живьём на **ORCH-061**, task 47, done с
|
||||
07.06 — 273 активности, само не затихает). Установлено по коду/логам/БД прода:
|
||||
|
||||
- Три code-писателя deploy-фазовых статусов (`src/stage_engine.py:404/1218/1316`) делегируют в тонкие
|
||||
сеттеры `src/plane_sync.py`, которые **БД-стадию не читают** ⇒ терминал-слепы: любой повторный вызов
|
||||
перезаписывает `Done` обратно на промежуточный статус.
|
||||
- **Ordering:** `update_task_stage("done")` (`stage_engine.py:369`) пишет `tasks.stage='done'`
|
||||
**раньше** легитимного `set_issue_monitoring` (стр. 404) ⇒ пост-деплой-окно ORCH-021 — by-design
|
||||
индикация поверх уже-`done` задачи. Наивный гард «stage==done → Done» ⇒ регресс легитимного окна.
|
||||
- Актор всех 273 переходов — бот-токен орка (`daf4d3f4-…`), не привязан к активной task/job; в БД нет
|
||||
активного post-deploy-monitor для task 47 (окно 15 мин закрыто). Реконсилятор F-1 пропускает
|
||||
`done`/`cancelled`, F-2 опрашивает только `[to_analyse, approved, rejected]` ⇒ механизма привести
|
||||
застрявшую на deploy-статусе done-задачу к `Done` нет.
|
||||
|
||||
## Решение
|
||||
|
||||
**Единый terminal-window-aware гард на низком чокпоинте** — на входе трёх deploy-фазовых сеттеров
|
||||
`plane_sync`. Чистую логику держит **новый leaf-модуль `src/deploy_status_guard.py`** (never-raise,
|
||||
config-gated; образец `serial_gate.py`/`labels.py`/`cancel.py`); сеттеры исполняют вердикт.
|
||||
|
||||
- **Инвариант легитимности:** deploy-фазовый статус легитимен ⇔ задача **нетерминальна** ИЛИ
|
||||
(`done` **И** активно пост-деплой-окно). Иначе — идемпотентное схождение к `Done`.
|
||||
`decide(work_item_id, target) -> ALLOW | CONVERGE_DONE | SUPPRESS`:
|
||||
kill-switch off / чужой issue / не-self репо / нетерминал → **ALLOW**; `cancelled` → **SUPPRESS**;
|
||||
`done` + `target==monitoring` + `window_active` → **ALLOW**; `done` иначе → **CONVERGE_DONE**
|
||||
(`set_issue_done`, идемпотентно); любое исключение → **ALLOW** + warning (never-raise).
|
||||
- **Новый helper** `post_deploy.window_active(repo, wi)` = `has_marker(ARMED) and not
|
||||
has_marker(DONE)` (restart-safe).
|
||||
- **Перенос арм-блока** (`post_deploy.arm_monitor`) **перед** terminal-sync в блоке
|
||||
`next_stage == "done"`: на стр. 404 `ARMED` уже записан ⇒ `window_active==True` ⇒ легитимный первый
|
||||
`Monitoring` проходит; re-drive после закрытия окна сходится к `Done`.
|
||||
- **Харднинг монитора:** идемпотентный страж `has_marker(...DONE)` (ранний return без PATCH/реэнкью)
|
||||
+ тик no-op при `cancelled` мид-окно; тики привязаны к активному job'у (нет job → нет тика).
|
||||
- **Наблюдаемость:** каждый вердикт логируется (`work_item`/`caller`/`target`/`db_stage`/
|
||||
`window_active`/вердикт); подавление/схождение — явно.
|
||||
- **Флаги** (`config.py`): `deploy_status_guard_enabled=True`
|
||||
(`ORCH_DEPLOY_STATUS_GUARD_ENABLED`, kill-switch → 1:1) + `deploy_status_guard_repos=""`
|
||||
(`ORCH_DEPLOY_STATUS_GUARD_REPOS`, пусто → self-hosting only) с локальным `applies(repo)`.
|
||||
|
||||
## Альтернативы
|
||||
|
||||
- **Гард в caller'ах `stage_engine`** — отвергнуто: не ловит неизвестный/стейл путь под бот-токеном,
|
||||
размазывает инвариант.
|
||||
- **Наивный «stage==done → Done» без предиката окна** — отвергнуто: регресс легитимного `Monitoring`.
|
||||
- **Bypass-флаг на доверенном вызове 404** — отвергнуто в пользу переноса арм-блока (один предикат).
|
||||
- **Активная сходимость в реконсиляторе F-2** — отвергнуто как основной механизм (лишний polling,
|
||||
правка маркированного F-2); гард на сеттере гасит непрерывный флапп.
|
||||
|
||||
## Последствия
|
||||
|
||||
- Терминальная задача стабильно держит `Done`; маятник гаснет за один цикл независимо от актора.
|
||||
- Легитимный пост-деплой `Monitoring` и рабочий self-deploy-цикл — 1:1 (предикат окна + перенос арм).
|
||||
- `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключи / схема БД — **не тронуты**.
|
||||
- `main`/force-push/прод-контейнер/detached-деплой — не тронуты; не-self репо инертны.
|
||||
- Ограничение: если актор флаппа — внешняя Plane-automation (вне кода орка), гард — буфер на стороне
|
||||
орка; локализация (FR-1) и итог документируются (BR-7).
|
||||
- **Откат:** `ORCH_DEPLOY_STATUS_GUARD_ENABLED=false` → поведение 1:1; полный — revert ветки.
|
||||
|
||||
## Связи
|
||||
|
||||
- [adr-0010](adr-0010-post-deploy-monitor.md) (ORCH-021 — пост-деплой-окно, sentinel `armed`/`done`,
|
||||
арм-блок) — амендмент: окно становится предикатом легитимности `Monitoring`.
|
||||
- ORCH-066 (Plane-статусная модель — слой B индикации; `deploy→done` self ⇒ `Monitoring`) — инвариант
|
||||
сохранён.
|
||||
- [adr-0026](adr-0026-stop-cancel-task.md) (ORCH-090 — терминал `cancelled`) — гард не штампует
|
||||
deploy-статус поверх `cancelled`.
|
||||
- ORCH-068/086 (терминал-скип реконсилятора) — этот ADR распространяет идею терминал-aware на
|
||||
выставление deploy-статусов.
|
||||
- Детально: `docs/work-items/ORCH-094/06-adr/ADR-001-terminal-window-aware-deploy-status-guard.md`.
|
||||
</content>
|
||||
@@ -0,0 +1,234 @@
|
||||
---
|
||||
work_item: ORCH-094
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-09
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# ADR-001: Terminal-window-aware гард выставления deploy-фазовых статусов Plane
|
||||
|
||||
Work Item: **ORCH-094** — терминальная (done) задача флаппит deploy-статусы в Plane
|
||||
(`Awaiting Deploy ⟷ Monitoring after Deploy`), не держит `Done`.
|
||||
Стадия: **architecture**
|
||||
Сквозная регистрация: **`docs/architecture/adr/adr-0028-terminal-window-aware-deploy-status-guard.md`**
|
||||
(кросс-каттинг: правит общие сеттеры `plane_sync` + переупорядочивает маркированный блок
|
||||
`next_stage == "done"` ORCH-021/066).
|
||||
|
||||
## Статус
|
||||
Proposed
|
||||
|
||||
## Контекст
|
||||
|
||||
Сверено по коду ветки `feature/ORCH-094-…`:
|
||||
|
||||
- **Три code-писателя deploy-фазовых статусов** — все в `src/stage_engine.py`, все вызывают
|
||||
тонкие сеттеры `src/plane_sync.py`, которые делегируют в общий `_set_issue_state_direct`
|
||||
(PATCH issue.state; never-raise; **БД-стадию не читает**):
|
||||
- `set_issue_awaiting_deploy` (Phase A, `stage_engine.py:1218`),
|
||||
- `set_issue_deploying` (Phase B, `stage_engine.py:1316`),
|
||||
- `set_issue_monitoring` (terminal-sync `deploy → done` для self-hosting, `stage_engine.py:404`).
|
||||
- `set_issue_done` (`plane_sync.py:913`) — **терминальная цель**, отдельно.
|
||||
- **Критический факт ordering'а:** в `advance_stage` строка **369** `update_task_stage(task_id, "done")`
|
||||
пишет `tasks.stage='done'` **РАНЬШЕ**, чем строка **404** `set_issue_monitoring(...)`. То есть в
|
||||
момент **легитимного** первого выставления `Monitoring after Deploy` задача в БД **уже `done`**.
|
||||
Пост-деплой-окно ORCH-021 — это by-design индикация поверх уже-терминальной (`done`) задачи
|
||||
(«ответственность ЗА `done`»). ⇒ **наивный гард «stage==done → редирект на Done» подавил бы
|
||||
легитимный `Monitoring` → регресс AC-4.**
|
||||
- **Арм пост-деплой-монитора** (`stage_engine.py:431` → `post_deploy.arm_monitor`) выполняется
|
||||
**ПОСЛЕ** строки 404. Sentinel `ARMED` пишется в `arm_monitor`; окно закрывается sentinel'ом
|
||||
`DONE` (`post_deploy.mark_done`); идемпотентный страж `has_marker(...DONE)` в
|
||||
`run_post_deploy_monitor` (~1729).
|
||||
- **Симптом (верифицирован живьём на ORCH-061, task 47, done с 07.06):** Plane не держит `Done` —
|
||||
непрерывный флапп `Awaiting ⟷ Monitoring` парами каждые ~сек, 273 активности, само не затихает.
|
||||
В БД **нет активного post-deploy-monitor** для task 47 (окно 15 мин давно закрыто); реконсилятор
|
||||
F-1 пропускает `done`/`cancelled`, F-2 опрашивает только `[to_analyse, approved, rejected]` —
|
||||
механизма «привести застрявшую на deploy-статусе done-задачу обратно к Done» нет. Актор всех 273
|
||||
переходов — бот-токен орка (`daf4d3f4-…`), т.е. PATCH-и шлёт **что-то под токеном орка**, не
|
||||
привязанное к активной task/job. Точный актор подлежит инструментальной локализации (FR-1,
|
||||
developer); фикс должен быть **буфером, гасящим маятник на стороне орка независимо от актора**.
|
||||
|
||||
**Почему «как есть» не годится:** сеттеры deploy-статусов терминал-слепы — любой повторный вызов
|
||||
(стейл-job, двойной webhook, неизвестный внутренний путь под бот-токеном) перезаписывает `Done`
|
||||
обратно на промежуточный deploy-статус, и наоборот, бесконечно. Нет ни идемпотентного схождения к
|
||||
`Done` для терминальной задачи, ни наблюдаемости «кто/почему» ставит статус.
|
||||
|
||||
## Решение
|
||||
|
||||
### Сводка
|
||||
|
||||
Вводим **единый terminal-window-aware гард на самом низком чокпоинте** — на входе трёх
|
||||
deploy-фазовых сеттеров `plane_sync`. Решение принимает **новый leaf-модуль
|
||||
`src/deploy_status_guard.py`** (чистая, never-raise, config-gated логика; по образцу
|
||||
`serial_gate.py`/`labels.py`/`cancel.py`), сеттеры лишь исполняют вердикт. Ключевой инвариант:
|
||||
**deploy-фазовый статус легитимен ⇔ задача нетерминальна ИЛИ (`done` И активно пост-деплой-окно)**;
|
||||
иначе — идемпотентное схождение к `Done`. Чтобы легитимный первый `Monitoring` на строке 404
|
||||
проходил, **арм-блок переносится перед terminal-sync-блоком** (предикат «окно активно» становится
|
||||
истинным до выставления `Monitoring`). Всё под kill-switch, аддитивно, в зоне self-hosting; реестры
|
||||
конвейера не тронуты.
|
||||
|
||||
### D1 — Где гард: единый чокпоинт в deploy-фазовых сеттерах `plane_sync`
|
||||
|
||||
Гард ставится на входе **`set_issue_awaiting_deploy` / `set_issue_deploying` / `set_issue_monitoring`**
|
||||
(а НЕ в caller'ах `stage_engine`). Это перехватывает **любой** путь к этим статусам — известные
|
||||
(stage_engine), будущие и **неизвестный актор под бот-токеном** (если он проходит через код орка) —
|
||||
одной точкой. `set_issue_done` **не гардится** (это цель схождения). Привязка: **FR-2, BR-1, BR-2**.
|
||||
|
||||
> Альтернатива «гард в caller'ах stage_engine» отвергнута: не ловит неизвестный/стейл путь, который
|
||||
> и есть подозреваемый источник 061-флаппа; размазывает инвариант по трём местам. См. «Альтернативы».
|
||||
|
||||
### D2 — Предикат легитимности: терминал **И окно**, не только стадия
|
||||
|
||||
Вердикт `deploy_status_guard.decide(work_item_id, target_status) -> ALLOW | CONVERGE_DONE | SUPPRESS`:
|
||||
|
||||
1. `not settings.deploy_status_guard_enabled` → **ALLOW** (kill-switch off ⇒ поведение 1:1).
|
||||
2. `task = <lookup по work_item_id>`; `task is None` → **ALLOW** (чужой/не наш issue — не вмешиваемся).
|
||||
3. `not deploy_status_guard.applies(task.repo)` → **ALLOW** (не-self репо ⇒ нулевая регрессия; для них
|
||||
`Monitoring`/`Awaiting`/`Deploying` и так не выставляются — terminal-sync идёт сразу в `Done`).
|
||||
4. `stage = task.stage`; `stage NOT IN ('done','cancelled')` → **ALLOW** (нетерминальная задача —
|
||||
легитимный рабочий deploy-цикл; **AC-4**).
|
||||
5. `stage == 'cancelled'` → **SUPPRESS** (не штампуем deploy-статус поверх терминала `cancelled`;
|
||||
cancel-flow ORCH-090 уже привёл Plane к своему терминалу — гард лишь не затирает его).
|
||||
6. `stage == 'done'`:
|
||||
- `target == 'monitoring'` **И** `post_deploy.window_active(repo, work_item_id)` → **ALLOW**
|
||||
(легитимное пост-деплой-окно — `Monitoring` корректен; **AC-4**);
|
||||
- иначе → **CONVERGE_DONE** (для `done` `Awaiting`/`Deploying` всегда спуриозны — Phase A/B
|
||||
случаются строго **до** `deploy → done`; и `Monitoring` при закрытом/неарм'ленном окне —
|
||||
спуриозен, как 061).
|
||||
7. **Любое исключение / невозможность определить стадию** → **ALLOW** + `logger.warning`
|
||||
(never-raise, fail-safe к прежнему поведению; **NFR-1**). БД-чтение локальное (SQLite) и надёжное —
|
||||
в штатном случае стадия читается, маятник не возникает.
|
||||
|
||||
Сеттер исполняет вердикт: `ALLOW` → штатный PATCH; `CONVERGE_DONE` → `set_issue_done(work_item_id)`
|
||||
(идемпотентно — уже-`Done` ⇒ no-op PATCH-эквивалент); `SUPPRESS` → ничего не патчим. Привязка:
|
||||
**FR-2, BR-1, BR-2, AC-1, AC-2, AC-4**.
|
||||
|
||||
**Новый helper** `post_deploy.window_active(repo, wi) -> bool` = `has_marker(ARMED) and not
|
||||
has_marker(DONE)` (never-raise; restart-safe — sentinel'ы на диске переживают рестарт; **NFR-4**).
|
||||
|
||||
### D3 — Перенос арм-блока перед terminal-sync (чтобы D2 пропускал легитимный первый `Monitoring`)
|
||||
|
||||
В `advance_stage`, внутри ветки `next_stage == "done"`, **арм-блок** (`post_deploy.arm_monitor`,
|
||||
сейчас стр. 431) перемещается **выше** terminal-sync-блока (`set_issue_monitoring`, стр. 404). После
|
||||
переноса в момент строки 404: `ARMED` уже записан, `DONE` отсутствует ⇒ `window_active==True` ⇒
|
||||
вердикт **ALLOW** ⇒ легитимный `Monitoring` проходит как раньше. Re-drive `deploy → done` **после**
|
||||
закрытия окна (`DONE` присутствует) ⇒ `window_active==False` ⇒ **CONVERGE_DONE** (не воскрешает
|
||||
`Monitoring`).
|
||||
|
||||
Перенос безопасен: `arm_monitor` лишь пишет sentinel + ставит отложенный job — не зависит ни от
|
||||
Plane-статуса, ни от merge-lease (release остаётся после terminal-sync). Инварианты ORCH-021
|
||||
(идемпотентный арм по `ARMED`) и ORCH-066 (`deploy → done` для self ⇒ `Monitoring`, не `Done`)
|
||||
сохранены. Привязка: **AC-4, BR-5**; маркеры `ORCH-021`/`ORCH-066` (прочитаны: `06-adr/ADR-001`,
|
||||
`adr-0010`).
|
||||
|
||||
> Альтернатива «bypass-флаг `force=True` на доверенном вызове 404 вместо переноса» отвергнута: плодит
|
||||
> два определения «легитимности» и доверенный обход; перенос оставляет **один** предикат «окно активно».
|
||||
|
||||
### D4 — Харднинг пост-деплой-монитора: нет «зомби»-тиков/PATCH после закрытия окна
|
||||
|
||||
`run_post_deploy_monitor` (`stage_engine.py` ~1698): сохранить существующий идемпотентный страж
|
||||
`has_marker(...DONE)` (~1729; первым — ранний `return` без PATCH/реэнкью). Аддитивно: тик
|
||||
**no-op без PATCH и без перепостановки**, если задача стала терминальной аномально (`stage ==
|
||||
'cancelled'` мид-окно → закрыть окно `mark_done`, без статус-PATCH). Перепостановка тика остаётся
|
||||
строго при `HEALTHY and ticks < budget` — тики **привязаны к активному job'у** (тик и есть job; нет
|
||||
job → нет тика). После закрытия окна (`DONE`) или исчерпания бюджета — **0 последующих** статус-PATCH;
|
||||
любой стейл-вызов `set_issue_monitoring` теперь добивается гардом D2 (`window_active==False` →
|
||||
CONVERGE_DONE). `arm_monitor` уже идемпотентен по `ARMED` (повторный арм done-задачи → no-op). Привязка:
|
||||
**FR-3, BR-3, BR-4, AC-3, NFR-4**.
|
||||
|
||||
### D5 — Наблюдаемость «кто/почему» (FR-4)
|
||||
|
||||
Каждый вердикт гарда логируется структурно одной записью: `work_item`, `caller` (короткая причина —
|
||||
аддитивный BC-kwarg `reason: str | None = None` у трёх сеттеров; call-site передаёт напр.
|
||||
`"advance:deploy->done"`/`"phase_a"`/`"phase_b"`/`"monitor-tick"`), `target_status`, `db_stage`,
|
||||
`window_active`, итоговый вердикт (`ALLOW`/`CONVERGE_DONE`/`SUPPRESS`). Подавление/схождение
|
||||
(`CONVERGE_DONE`/`SUPPRESS`) логируется **явно** («что подавили и почему»). Достаточно, чтобы по
|
||||
логу однозначно атрибутировать будущий флапп. Привязка: **FR-4, BR-6, AC-5**.
|
||||
|
||||
### D6 — Обратимость, скоуп, флаги (FR-5)
|
||||
|
||||
`src/config.py` (по образцу ORCH-088/090):
|
||||
- `deploy_status_guard_enabled: bool = True` — env `ORCH_DEPLOY_STATUS_GUARD_ENABLED` (kill-switch;
|
||||
`False` → сеттеры терминал-слепы, поведение **1:1** прежнее).
|
||||
- `deploy_status_guard_repos: str = ""` — env `ORCH_DEPLOY_STATUS_GUARD_REPOS` (CSV; **пусто →
|
||||
self-hosting only**). `applies(repo)` (локальный, без сети) — единственная точка скоупа.
|
||||
|
||||
Дефолт `enabled=True` + `repos=""` ⇒ активен только для self-hosting (`orchestrator`), где deploy-фазовые
|
||||
статусы вообще выставляются; не-self репо (enduro-trails) гард не трогает (D2 шаг 3). Привязка: **NFR-3,
|
||||
BR-5, FR-5, AC-4, AC-5**.
|
||||
|
||||
### D7 — Что НЕ трогаем (инварианты)
|
||||
|
||||
`STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключи
|
||||
(`deploy_status:`/`staging_status:`/`security_status:`) — **байт-в-байт**. Схема БД — **без миграции**
|
||||
(гард читает существующую `tasks.stage`; окно — существующие sentinel'ы `post_deploy.py`; привязка к
|
||||
job — существующая таблица `jobs`). `main`/force-push/прод-контейнер/detached-деплой — **не трогаются**.
|
||||
Рабочий критический путь self-deploy (Phase A→B→C, merge-gate, freeze-на-DEGRADED ORCH-088) —
|
||||
сохранён 1:1. Реконсилятор F-1/F-2 — **без изменений** (гард на сеттере субсумирует «sync → Done»:
|
||||
любой путь, дёрнувший deploy-сеттер для done-задачи, сходится к `Done`). Привязка: **NFR-2, NFR-5, AC-5**.
|
||||
|
||||
### D8 — Лукап задачи по `work_item_id` (реализационная заметка для developer)
|
||||
|
||||
Сеттеры принимают `work_item_id` (напр. `"ORCH-061"`). В `src/db.py` существующий
|
||||
`get_task_by_plane_id` матчит `plane_id`/`plane_issue_id` (UUID-ы), **не** человекочитаемый
|
||||
`work_item_id`. Developer добавляет минимальный **read-only** аксессор
|
||||
`get_task_by_work_item_id(work_item_id)` (`SELECT * FROM tasks WHERE work_item_id = ?`; живой ряд
|
||||
матчит точно — тумбстоны ORCH-090 имеют суффикс `#cancelled-<id>`), **без изменения схемы**. Один
|
||||
локальный SELECT отдаёт и `repo`, и `stage` для D2.
|
||||
|
||||
## Альтернативы
|
||||
|
||||
- **Гард в caller'ах `stage_engine` (а не в сеттерах)** — отвергнуто: не ловит неизвестный/стейл
|
||||
актор под бот-токеном (вероятный источник 061-флаппа), размазывает инвариант по трём врезкам,
|
||||
слабее как буфер BR-2 «сходимость из любого пути».
|
||||
- **Наивный гард «stage==done → редирект на Done» (без предиката окна)** — отвергнуто: подавляет
|
||||
легитимный пост-деплой `Monitoring` (он by-design поверх уже-`done` задачи, стр. 369 < 404) ⇒
|
||||
прямой регресс **AC-4**.
|
||||
- **Bypass-флаг `force=True` на доверенном вызове 404** (вместо переноса арм-блока) — отвергнуто:
|
||||
два определения легитимности + доверенный обход; перенос даёт один предикат «окно активно».
|
||||
- **Активная сходимость в реконсиляторе (F-2 опрашивает Awaiting/Monitoring → set_issue_done)** —
|
||||
отвергнуто как **основной** механизм (лишний Plane-polling, правка маркированного F-2). Гард на
|
||||
сеттере уже гасит непрерывный флапп (каждый вызов актора сходится к `Done` за один цикл). Возможен
|
||||
как **необязательный** follow-up для разовой зачистки quiescent-застрявшего статуса (вне scope —
|
||||
такой кейс чинится разовым ручным sync; наблюдаемый дефект — непрерывный флапп, который буфер
|
||||
покрывает).
|
||||
- **Колонка-маркер в `tasks` для состояния окна** — отвергнуто: миграция на проде; sentinel'ы
|
||||
`post_deploy.py` уже restart-safe (как ORCH-021/036).
|
||||
|
||||
## Последствия
|
||||
|
||||
- **+** Терминальная (`done`) задача стабильно держит `Done`: любой deploy-сеттер для неё сходится к
|
||||
`Done` идемпотентно, маятник гаснет за один цикл независимо от актора (буфер BR-1/BR-2, AC-1/AC-2).
|
||||
- **+** Легитимный пост-деплой `Monitoring` сохранён точно (предикат «окно активно» + перенос
|
||||
арм-блока); рабочий deploy-цикл 1:1 (AC-4).
|
||||
- **+** Наблюдаемость: лог однозначно атрибутирует «кто/почему» при будущем флаппе (AC-5).
|
||||
- **+** Единый низкий чокпоинт ловит и неизвестный внутренний путь под бот-токеном.
|
||||
- **−** Один локальный SELECT (`tasks`) на каждый deploy-фазовый PATCH-вызов self-репо. Митигейшн:
|
||||
читается тот же ряд, что даёт `repo` для `applies`; SQLite-чтение пренебрежимо против сетевого PATCH;
|
||||
для не-self/выключенного флага — ранний ALLOW без лукапа окна.
|
||||
- **−** Если фактический актор флаппа — **внешняя** Plane-automation под другим токеном (вне кода
|
||||
орка), code-фикс не закроет G1 полностью. Митигейшн: гард — буфер на стороне орка; локализация
|
||||
актора (FR-1) и итог документируются (BR-7) — этот ADR фиксирует гипотезу «под бот-токеном орка».
|
||||
- **−** Перенос арм-блока меняет порядок внутри маркированного блока ORCH-021/066. Митигейшн:
|
||||
инварианты обоих ADR проверены сохранёнными (D3); анти-регресс — TC-11 (рабочий цикл) + структурные
|
||||
тесты.
|
||||
- **Откат:** `ORCH_DEPLOY_STATUS_GUARD_ENABLED=false` → сеттеры терминал-слепы, поведение 1:1
|
||||
прежнее (D2 шаг 1). Полный откат — revert ветки (перенос арм-блока + leaf + config + сеттер-врезки).
|
||||
|
||||
## Ссылки
|
||||
|
||||
- BRD: `docs/work-items/ORCH-094/01-brd.md`
|
||||
- TRZ: `docs/work-items/ORCH-094/02-trz.md`
|
||||
- Acceptance: `docs/work-items/ORCH-094/03-acceptance-criteria.md`
|
||||
- Tech-risks: `docs/work-items/ORCH-094/10-tech-risks.md`
|
||||
- Сквозной ADR: `docs/architecture/adr/adr-0028-terminal-window-aware-deploy-status-guard.md`
|
||||
- Сверено по коду: `src/stage_engine.py` (369/404/431/1218/1316/~1698-1729),
|
||||
`src/plane_sync.py` (913/954/964/974, `_set_issue_state_direct`), `src/post_deploy.py`
|
||||
(`arm_monitor`/`has_marker`/`ARMED`/`DONE`/`state_dir`), `src/reconciler.py` (F-1/F-2),
|
||||
`src/config.py` (флаги ORCH-088/021/036), `src/db.py` (`get_task_by_plane_id`).
|
||||
- Маркеры (прочитаны, не сломаны): ORCH-021 (`adr-0010` / `06-adr/ADR-001`), ORCH-066
|
||||
(`06-adr/ADR-001-plane-status-model`), ORCH-086/068 (терминал-скип), ORCH-088 (freeze),
|
||||
ORCH-090 (cancelled-терминал).
|
||||
</content>
|
||||
</invoke>
|
||||
91
docs/work-items/ORCH-094/10-tech-risks.md
Normal file
91
docs/work-items/ORCH-094/10-tech-risks.md
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
work_item: ORCH-094
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-09
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 10 — Технические риски: ORCH-094 — terminal-window-aware гард deploy-статусов
|
||||
|
||||
Work Item: **ORCH-094** · Repo: **orchestrator** · Стадия: architecture
|
||||
|
||||
Формат: каждый риск — **вероятность × влияние**, причина, **митигейшн**, привязка к AC/ADR-решению.
|
||||
|
||||
---
|
||||
|
||||
## R-1 — Гард подавляет ЛЕГИТИМНЫЙ `Monitoring` у реально деплоящейся задачи (регресс AC-4)
|
||||
- **Вероятность:** средняя (без точного предиката — высокая) · **Влияние:** высокое.
|
||||
- **Причина:** `update_task_stage("done")` (стр. 369) выполняется **раньше** `set_issue_monitoring`
|
||||
(стр. 404) ⇒ в момент легитимного `Monitoring` задача в БД уже `done`. Наивный гард
|
||||
«stage==done → Done» затёр бы легитимную индикацию.
|
||||
- **Митигейшн:** предикат **«терминал И НЕ активное окно»** (D2 шаг 6) + **перенос арм-блока перед
|
||||
terminal-sync** (D3): `window_active==True` на стр. 404 ⇒ ALLOW. Анти-регресс — **TC-11**
|
||||
(рабочий цикл `Awaiting→Deploying→Monitoring→Done` без подавления) + **TC-03** (stage=deploy
|
||||
проходит).
|
||||
|
||||
## R-2 — Фактический актор флаппа — внешняя Plane-automation (вне кода орка)
|
||||
- **Вероятность:** низкая · **Влияние:** среднее (G1 закрыт не полностью).
|
||||
- **Причина:** все 273 перехода — под бот-токеном орка; гипотеза H-внешнее не исключена до
|
||||
инструментальной локализации (FR-1).
|
||||
- **Митигейшн:** гард — **буфер на стороне орка** (BR-2): если PATCH идёт через код орка — гасится;
|
||||
developer локализует актора (FR-1) и фиксирует в ADR/CHANGELOG (BR-7). Если актор реально внешний —
|
||||
это документируется как known-limitation, гард остаётся защитой от внутренних путей.
|
||||
|
||||
## R-3 — Перенос арм-блока ломает инвариант ORCH-021/066
|
||||
- **Вероятность:** низкая · **Влияние:** высокое (self-hosting прод).
|
||||
- **Причина:** правка порядка внутри маркированного блока `next_stage == "done"`.
|
||||
- **Митигейшн:** `arm_monitor` не зависит от Plane-статуса/merge-lease (пишет sentinel + ставит
|
||||
отложенный job); merge-lease release остаётся после terminal-sync; идемпотентность арма по `ARMED`
|
||||
и инвариант ORCH-066 (`deploy→done` self ⇒ `Monitoring`) сохранены (D3). Прочитаны `adr-0010` +
|
||||
`06-adr/ADR-001-plane-status-model`. Тесты TC-06/TC-08 + TC-11.
|
||||
|
||||
## R-4 — `never-raise`-деградация маскирует флапп (fail-safe = ALLOW)
|
||||
- **Вероятность:** низкая · **Влияние:** низкое.
|
||||
- **Причина:** при ошибке лукапа стадии / сетевой ошибке гард делает ALLOW (прежнее поведение), что
|
||||
в теории не гасит маятник.
|
||||
- **Митигейшн:** БД-чтение — локальный SQLite (надёжно; ошибка редка); в штатном случае стадия
|
||||
читается ⇒ сходимость работает. Деградация **логируется** `warning` (D5) ⇒ видно в диагностике.
|
||||
NFR-1 приоритезирует «не падать/не блокировать конвейер всех проектов» над агрессивным подавлением.
|
||||
Тест TC-05.
|
||||
|
||||
## R-5 — «Зомби»-тик пост-деплой-монитора после рестарта/стейл-job шлёт статус-PATCH
|
||||
- **Вероятность:** низкая · **Влияние:** среднее.
|
||||
- **Причина:** стейл-job `post-deploy-monitor` в очереди после закрытия окна/рестарта мог бы дёрнуть
|
||||
`set_issue_monitoring`.
|
||||
- **Митигейшн:** идемпотентный страж `has_marker(...DONE)` (ранний return без PATCH/реэнкью, ~1729) +
|
||||
тик no-op при `cancelled` мид-окно (D4) + **гард D2** (`window_active==False` ⇒ CONVERGE_DONE).
|
||||
restart-safe (sentinel'ы на диске). Тесты TC-06/TC-07.
|
||||
|
||||
## R-6 — Стоимость лукапа `tasks` на каждый deploy-PATCH
|
||||
- **Вероятность:** низкая · **Влияние:** пренебрежимое.
|
||||
- **Причина:** новый SELECT на каждый вызов deploy-сеттера self-репо.
|
||||
- **Митигейшн:** тот же ряд даёт `repo` для `applies`; SQLite-чтение ничтожно против сетевого PATCH;
|
||||
не-self/выключенный флаг → ранний ALLOW. Без кэша (корректность > микро-оптимизация).
|
||||
|
||||
## R-7 — Регресс не-self репозиториев (enduro-trails)
|
||||
- **Вероятность:** очень низкая · **Влияние:** среднее.
|
||||
- **Причина:** общий инстанс/БД; правка общих сеттеров `plane_sync`.
|
||||
- **Митигейшн:** `applies(repo)` (D2 шаг 3, `deploy_status_guard_repos=""` → self-hosting only);
|
||||
для не-self deploy-фазовые статусы и так не выставляются (terminal-sync сразу `Done`). Тест TC-12.
|
||||
|
||||
## R-8 — Лукап по `work_item_id` не матчит (нет аксессора)
|
||||
- **Вероятность:** низкая · **Влияние:** низкое (деградирует в ALLOW).
|
||||
- **Причина:** `get_task_by_plane_id` матчит UUID-ключи, не человекочитаемый `work_item_id`.
|
||||
- **Митигейшн:** developer добавляет read-only `get_task_by_work_item_id` (D8, без миграции); при
|
||||
промахе — ALLOW (never-raise). Тумбстоны ORCH-090 (`#cancelled-<id>`) не коллизируют с живым рядом.
|
||||
|
||||
---
|
||||
|
||||
## Сводка по инвариантам (не нарушены)
|
||||
|
||||
| Инвариант | Статус |
|
||||
|-----------|--------|
|
||||
| `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключи | не тронуты (D7) |
|
||||
| Схема БД | без миграции (read-only аксессор) (D7/D8) |
|
||||
| `main` / force-push / прод-контейнер / detached-деплой | не тронуты (D7, NFR-2) |
|
||||
| Рабочий self-deploy (Phase A→B→C, merge-gate, freeze ORCH-088) | 1:1 (D7, AC-4) |
|
||||
| Реконсилятор F-1/F-2 | без изменений (гард субсумирует sync→Done) (D7) |
|
||||
| Обратимость (kill-switch → 1:1) | `ORCH_DEPLOY_STATUS_GUARD_ENABLED` (D6) |
|
||||
</content>
|
||||
Reference in New Issue
Block a user